Login to Vote |
| AjaxVoter |
This template is a simple AJAX enabled Up/Down or Positive/Negative Voter. You are able to place many of these on one page to simulate the FeVote functionality. Since this uses page properties to save the voting results its not recommended for any use if security or 100% integrity maybe a concern. In order to vote the voter must have write capabilites for the page the results are being stored on. As written this voter can only be used by users that are logged in, no anon voting is allowed. It should also be noted that if a user votes twice their vote does not get counted twice instead their vote is changed. Please direct any questions / comments to this forum thread.
There are 2 methods of scoring "sum" and "percent" the default method is "sum". With the "sum" scoring method each positive vote is given a value of 1 and each negative vote is given a value of -1. The Score is calculated by the sum of all the positive and negative votes. For Example: Billy votes Positive or ThumbsUp, his vote will be given a value of 1. Jane votes Negative or ThumbsDown, her vote will be given a value of -1. The score for Billy and Jane's votes will be (1 + -1 = 0), if John comes along and votes ThumbsUp then the score would be +1 (1 + -1 + 1 = 1). The "percent" scoring method simply displays the percentage of ThumbsUp votes.
| Version | Date | Author | Description |
| 1.00 | 12/22/2009 | rberinger/NeilW | Initial Release |
| 1.01 | 1/11/2010 | NeilW | CSS fixed for IE7 |
These instructions will help you install the template'AjaxVoter' on your wiki quickly and correctly.
Perform the following steps:
HTML Source for Template:AjaxVoter
There are 2 properties created for each AjaxVoter. One that is controlled by the "name" argument and another that is the voterid argument preceded with "Totals@". i.e. If you created a voter with the name of TestVoter and a voterid of "TestVoter_id" there will be 2 properties created, "TestVoter" and "Totals@TestVoter_id". The Totals@voterid property is a map that has 3 values {negative, positive, score}. The Totals@ property allows you to quickly gather score totals without iterating the voter property list of maps to get the totals. This would possibly be useful for vote summary pages or reports. The unique voterID allows you to reset the voter by changing its name but keeping the Totals@ property the same so you do not have to change any summary pages gathering those results.
To help clarify the intended use of the Totals@ property I will try to layout a scenerio using a documentation page as an example:
As you can see I have a page rating voter at the top right of this documentation page. Over time the score will change when people vote this template up or down. If I notice a negative or low score that should indicate some much needed changes are required. Once I make those changes I may want to "reset" the counter. All I need to do is change the name parameter and the score will be reset. However if I was to view a previous version of this page I will be able to see the score prior to the name change. At the same time the voterid stays the same and the Totals@ property will get reset along with everything else but any slave functions (such as summary pages or templates) do not need to change because I only changed the name of the voter and not the voterid. I know I'm almost confused myself but sit and picture it in your mind and all should become clear.
| Name | Type | Default | Description |
| name | str? | voter | The Property name to save the Voter results under. This argument is only REQUIRED if more than one voter is present on a page or if the vote result properties are stored on another page that is also housing another voter. |
| pageid | num? | page.id | The Page ID of the page to store the results of the voting |
| voterid | str? | n/a | REQUIRED: This argument is a required argument. It should be unique between all the voters on a single property destination page. This allows for the totals@ property to stay with a particular voter even if the voter name changes. Useful if you want to reset the voter scores by changing its name but keeping versioning intact while at the same time not requiring the change of any summary reports that rely on the Totals@ property for its results. |
| {{ AjaxVoter{ name:'css_voter', voterid:'cssvoter_id', scoring:'sum' } }} | This example uses the sum scoring method, which simply sums the positive (value = 1) and Negative (Value = -1) votes to provide a numeric score. Hovering the mouse over the score will provide the actual percentage of Positive Vs. Negative votes. |
| {{ AjaxVoter{ name:'example_voter', voterid:'examplevoter_id', scoring:'percent'} }} | This example uses the "Percentage" scoring method. The score range will be from 0% - 100% according to the number of positive votes. |
| This example uses the sum scoring method, which simply sums the positive (value = 1) and Negative (Value = -1) votes to provide a numeric score. Hovering the mouse over the score will provide the actual percentage of Positive Vs. Negative votes. | |
| This example uses the "Percentage" scoring method. The score range will be from 0% - 100% according to the number of positive votes. |
Please direct any questions / comments to this forum thread..
Thanks to NeilW for his aesthetics insights and his work for the percentage scoring while developing this template.
DekiScript Source for Template: AjaxVoter
<input type="hidden" id=('TheProperty_' ..@id) value=($0 ?? $name ?? 'voter') />
<input type="hidden" id=('pageApi_' ..@id) value=($1 ?? $pageid ?? wiki.getpage(page.id).api) />
<input type="hidden" id=('CurUserID_' ..@id) value=(user.id) />
<input type="hidden" id=('UniqueId_' ..@id) value=($2 ?? $voterid ?? '') />
var scoretype = ($3 ?? $scoring ?? 'sum');
if(scoretype != 'sum' && scoretype != 'percent') {let scoretype = 'sum'}
<input type="hidden" id=('Scoring_' ..@id) value=(scoretype) />
if(($3 ?? $voterid ?? '') == '') {
<p>
'Template:AjaxVoter (ERROR): The $voterid Parameter MUST have a value and should be unique amongst all the AjaxVoters on this page.';
</p>
} else {
<table>
<tr>
<td style="padding:0px; border:1px solid black; min-width:75px; width:75px">
<div id=('voter-container_' ..@id) style="padding-bottom:5px">
<div id=('vote-results-container_' ..@id)>
<div id=('vote-results-percent_' ..@id) style="font-size:.8em;font-weight:bold;margin:auto;text-align:center"/>
<div id=('vote-results_' ..@id) style="font-size:1.5em;font-weight:bold;margin:auto;text-align:center"/>
<div id=('vote-loading_' ..@id) style="margin:auto;width:16px;height:16px;background-repeat: no-repeat; background-image:url('/skins/common/icons/anim-wait-circle.gif');" />
</div>
<div id=('results-footer_' ..@id) style="font-weight:bold;font-size:.8em;text-align:center;"></div>
<div id=('icon-wrap_' ..@id) style="text-align:center;">
if(!user.anonymous) {
<a href="#" id=('down_val_' ..@id)><img src="http://developer.mindtouch.com/@api/deki/files/5005/=Thumbs_downSQ.png" style="height:24px;width:24px;" /></a>
<a href="#" id=('up_val_' ..@id)><img src="http://developer.mindtouch.com/@api/deki/files/5004/=Thumbs_upSQ.png" style="height:24px;width:24px;" /></a>
}
else 'Login to Vote';
</div>
</div>
</td>
</tr>
</table>
}
<html><head>
// <link rel="stylesheet" type="text/css" href="" />
// <script type="text/javascript" src="" />
<script type="text/javascript">"
$(document).ready(function() {
var scoring = $('#Scoring_"..@id.."').val();
if (scoring == 'sum') $('#vote-results-container_"..@id.."').hover(ShowPercent"..@id..", ShowScore"..@id..");
$('#vote-results-percent_"..@id.."').hide();
$('#up_val_"..@id.."').click(function() {
var thePageApi = $('#pageApi_"..@id.."').val();
var thePropertyName = $('#TheProperty_"..@id.."').val();
ReadProperty( thePageApi, thePropertyName, IncreaseVote_"..@id..");
return false;
});
$('#down_val_"..@id.."').click(function() {
var thePageApi = $('#pageApi_"..@id.."').val();
var thePropertyName = $('#TheProperty_"..@id.."').val();
ReadProperty( thePageApi, thePropertyName, DecreaseVote_"..@id..");
return false;
});
GetVoteCount_"..@id.."();
});
function ShowPercent"..@id.."() {
$('#vote-results_"..@id.."').hide();
$('#vote-results-percent_"..@id.."').show();
}
function ShowScore"..@id.."() {
$('#vote-results-percent_"..@id.."').hide();
$('#vote-results_"..@id.."').show();
}
var SaveTotals_"..@id.." = function(curVal, status) {
var voteCounts = '';
var totalsPos = 0;
var totalsNeg = 0;
var thePageApi = $('#pageApi_"..@id.."').val();
var thePropertyName = 'Totals@' + $('#UniqueId_"..@id.."').val();
if(typeOf(curVal) == 'array') {
for(var i = 0; i < curVal.length; i++) {
if(curVal[i].vote == 1) {
totalsPos = totalsPos + 1;
} else {
totalsNeg = totalsNeg + -1;
}
}
voteCounts = {positive: totalsPos, negative: Math.abs(totalsNeg), score: totalsPos + totalsNeg};
} else {
voteCounts = {positive: 0, negative: 0, score: 0};
}
AddUpdateProperty(thePageApi, thePropertyName, voteCounts, '', '');
}
var IncreaseVote_"..@id.." = function(curVal, status) {
$('#icon-wrap_"..@id.."').hide();
$('#vote-loading_"..@id.."').show();
$('#vote-results_"..@id.."').hide();
var voteCounts = new Array;
var newUser = true;
var thePageApi = $('#pageApi_"..@id.."').val();
var thePropertyName = $('#TheProperty_"..@id.."').val();
var CurUserID = $('#CurUserID_"..@id.."').val();
if(typeOf(curVal) == 'array') {
for(var i = 0; i < curVal.length; i++) {
if(curVal[i].userid == CurUserID) {
curVal[i].vote = 1;
newUser = false;
break;
}
}
} else {
curVal = new Array;
}
if(newUser == true) {
curVal[curVal.length] = {userid: CurUserID, vote: 1};
}
AddUpdateProperty(thePageApi, thePropertyName, curVal, GetVoteCount_"..@id..", SaveTotals_"..@id..");
}
var DecreaseVote_"..@id.." = function(curVal, status) {
$('#icon-wrap_"..@id.."').hide();
$('#vote-loading_"..@id.."').show();
$('#vote-results_"..@id.."').hide();
var voteCounts = new Array;
var newUser = true;
var thePageApi = $('#pageApi_"..@id.."').val();
var thePropertyName = $('#TheProperty_"..@id.."').val();
var CurUserID = $('#CurUserID_"..@id.."').val();
if(typeOf(curVal) == 'array') {
for(var i = 0; i < curVal.length; i++) {
if(curVal[i].userid == CurUserID) {
curVal[i].vote = -1;
newUser = false;
break;
}
}
} else {
curVal = new Array;
}
if(newUser == true) {
curVal[curVal.length] = {userid: CurUserID, vote: -1};
}
AddUpdateProperty(thePageApi, thePropertyName, curVal, GetVoteCount_"..@id..", SaveTotals_"..@id..");
}
var GetVoteCount_"..@id.." = function(parm, status) {
$('#vote-loading_"..@id.."').show();
$('#vote-results_"..@id.."').hide();
var thePageApi = $('#pageApi_"..@id.."').val();
var thePropertyName = $('#TheProperty_"..@id.."').val();
var CurUserID = $('#CurUserID_"..@id.."').val();
ReadProperty( thePageApi, thePropertyName, ShowVoteCount_"..@id..");
}
var ShowVoteCount_"..@id.." = function(curVal, status) {
$('#icon-wrap_"..@id.."').show();
$('#vote-results_"..@id.."').show();
$('#vote-loading_"..@id.."').hide();
// calculate vote totals
var totalsPos = 0;
var runTotal = 0;
var totalVotes = 0;
var bgcolor;
var green = new Array(80,225,80);
var red = new Array(245,110,100);
var white = new Array(255,255,255);
var scoring = $('#Scoring_"..@id.."').val();
if(typeOf(curVal) == 'array') {
for(var i = 0; i < curVal.length; i++) {
runTotal = runTotal + curVal[i].vote
if(curVal[i].vote > 0) totalsPos ++;
}
totalVotes = curVal.length;
}
// calculate and output final score and color
if (scoring == 'sum') {
if (runTotal == 0)
bgcolor = 'rgb(' + white.join(',') + ')';
else if (runTotal > 0) {
runTotal = '+' + runTotal;
bgcolor = 'rgb(' + green.join(',') + ')';
}
else if (runTotal < 0)
bgcolor = 'rgb(' + red.join(',') + ')';
// update percentage overlay
var percentPos = Math.round((totalsPos / Math.max(totalVotes,1)) * 100);
$('#vote-results-percent_"..@id.."').html('Pos: ' + percentPos + '%<br/>Neg: ' + (100-percentPos) + '%').css('background-color', bgcolor);
}
else {
var outColor = new Array(192,192,192);
if (totalVotes == 0)
runTotal = 'NA';
else {
var degree = 2*totalsPos/totalVotes - 1;
var i;
for (i = 0; i < 3; i++)
outColor[i] = Math.round(white[i] + degree*
(degree > 0 ? green[i] - white[i] : white[i] - red[i]));
if (scoring == 'percent') runTotal = Math.round(100*totalsPos/totalVotes) + '%';
else runTotal = (degree > 0 ? '+' : '') + Math.round(100*degree);
}
bgcolor = 'rgb(' + outColor.join(',') + ')';
}
$('#vote-results_"..@id.."').html(runTotal).css('background-color', bgcolor);
$('#results-footer_"..@id.."').html(totalVotes + ' votes');
}
function typeOf(obj) {
if ( typeof(obj) == 'object' ) {
if (obj.length) {
return 'array';
} else {
return 'object';
}
} else {
return typeof(obj);
}
}
// This function is simply used to aid in debugging.
var AlertMe = function(text, status) {
alert(status + ':' + text);
}
function AddUpdateProperty(pageApi, propName, propValue, callback, totalsCallBack) {
// property name to update
var propertyname = 'urn:custom.mindtouch.com#' + propName;
propValueJson = YAHOO.lang.JSON.stringify(propValue);
// send AJAX request to find the property
$.ajax({
// set the uri for the properties API of the current page
// add parameter to only list properties that match the requested name
// add parameters to convert response to JSON and
url: pageApi + '/properties?dream.out.format=json&names=' + Deki.url.encode(propertyname),
// set the request HTTP verb
type: 'GET',
cache: false,
// check outcome of the request
complete: function(xhr) {
// check response status code
if(xhr.status == 200) {
// evaluate respone JSON data
var data = eval('(' + xhr.responseText + ')');
// read property href and ETag
var href = data.property && data.property.contents['@href'];
var etag = data.property && data.property['@etag'];
// check we the value was found
if(href && etag) {
// send AJAX request to update the property
$.ajax({
// set the request HTTP verb
url: href + '?dream.in.verb=PUT',
type: 'POST',
// set the value of the updated property
data: propValueJson,
contentType: 'text/plain',
processData: false,
// add the 'ETag' header which checks if the property was modified since we read it
beforeSend: function(xhr) {
// NOTE (see Update 1 below): remove content-encoding added by Apache mod_deflate
etag = etag.replace('-gzip', '').replace('-bzip2', '').replace('-zip', '');
xhr.setRequestHeader('ETag', etag);
return true;
},
// check response status code
complete: function(xhr) {
// check the response status code
if(xhr.status == 200) {
// alert('Property was successfully updated.');
if(typeof callback == 'function') callback(true, xhr.status); else return true;
if(typeof totalsCallBack == 'function') totalsCallBack(propValue, xhr.status); else return false;
} else {
// alert('Unable to update the property. It may no longer exist (try recreating it), it may have been changed, or you don\\'t have permission to update it.');
if(typeof callback == 'function') callback(false, xhr.status); else return false;
}
}
});
} else {
// CREATE PAGE PROPERTY
// send AJAX request using jQuery
$.ajax({
// set the uri for the properties API of the current page
url: pageApi + '/properties',
// set the request HTTP verb
type: 'POST',
// set the value of the new property
data: propValueJson,
contentType: 'text/plain',
processData: false,
// add the 'Slug' header which sets the property name
beforeSend: function(xhr) {
xhr.setRequestHeader('Slug', 'urn:custom.mindtouch.com#' + propName);
return true;
},
// check outcome of the request
complete: function(xhr) {
// check the response status code
if(xhr.status == 200) {
// alert('Property was successfully created.');
if(typeof callback == 'function') callback(true, xhr.status); else return true;
if(typeof totalsCallBack == 'function') totalsCallBack(propValue, xhr.status); else return false;
} else {
// alert('Unable to create the property. It may already exist (try deleting it) or you don\\'t have permission to create it.');
if(typeof callback == 'function') callback(true, xhr.status); else return true;
}
}
}); // End Ajax (Create Property)
}
} else {
// alert('Unable to query the property. You don\\'t have permission to read it.');
if(typeof callback == 'function') callback(false, xhr.status); else return false;
}
}
}); // End AJAX (Update Property)
} // End Function AddUpdateProperty
function DeleteProperty(pageApi, propName, callback) {
// property name to update
var propertyname = 'urn:custom.mindtouch.com#' + propName;
// send AJAX request to find the property
$.ajax({
// set the uri for the properties API of the current page
// add parameter to only list properties that match the requested name
// add parameters to convert response to JSON and
url: pageApi + '/properties?dream.out.format=json&names=' + Deki.url.encode(propertyname),
// set the request HTTP verb
type: 'GET',
cache: false,
// check outcome of the request
complete: function(xhr) {
// check response status code
if(xhr.status == 200) {
// evaluate respone JSON data
var data = eval('(' + xhr.responseText + ')');
// read property href
var href = data.property && data.property.contents['@href'];
// check we the value was found
if(href) {
// send AJAX request to delete the property
$.ajax({
// set the request HTTP verb
url: href + '?dream.in.verb=DELETE',
type: 'POST',
// check response status code
complete: function(xhr) {
// check the response status code
if(xhr.status == 200) {
// alert('Property was successfully deleted.');
if(typeof callback == 'function') callback(true, xhr.status); else return true;
} else {
// Who cares we were going to trash it anyhow
// alert('Unable to delete the property. It may not exist (try creating it) or you don\\'t have permission to delete it.');
if(typeof callback == 'function') callback(true, xhr.status); else return true;
}
}
});
} else {
// Who cares we were going to trash it anyhow
// alert('Unable to find the property. It may not exist (try creating it).');
if(typeof callback=='function') callback(true, xhr.status); else return true;
}
} else {
alert('Unable to query the property. You don\\'t have permission to read it.');
if(typeof callback=='function') callback(false, xhr.status); else return false;
}
}
});
} // End Function DeleteProperty
function ReadProperty(pageApi, propName, callback) {
// property name to read
var propertyname = 'urn:custom.mindtouch.com#' + propName;
// send AJAX request using jQuery
$.ajax({
// set the uri for the properties API of the current page
// add parameter to only list properties that match the requested name
// add parameters to convert response to JSON and
url: pageApi + '/properties?dream.out.format=json&names=' + Deki.url.encode(propertyname),
// set the request HTTP verb
type: 'GET',
cache: false,
async: false,
// check outcome of the request
complete: function(xhr) {
// check response status code
if(xhr.status == 200) {
// evaluate response JSON data
var data = eval('(' + xhr.responseText + ')');
// read property value
var value = data.property && data.property.contents['#text'];
// check we the value was found
if(value) {
value = YAHOO.lang.JSON.parse(value);
// alert('Property was successfully read.\\nIts value is \\'' + value + '\\'');
if(typeof callback == 'function') callback(value, xhr.status); else return value;
} else {
// alert('Unable to find the property. It may not exist (try creating it).');
if(typeof callback == 'function') callback(value, xhr.status); else return null;
}
} else {
// alert('Unable to query the property. You don\\'t have permission to read it.');
if(typeof callback == 'function') callback(value, xhr.status); else return null;
}
}
});
}
"</script>
<style type="text/css">"
"</style>
</head></html>
None.
| Images 0 | ||
|---|---|---|
| No images to display in the gallery. |
Copyright © 2011 MindTouch, Inc. Powered by