/** * Modified Star Rating - jQuery plugin * * Copyright (c) 2024 Wil Stuckey * * Original source available: http://sandbox.wilstuckey.com/jquery-ratings/ * Extensively modified by Lullabot: http://www.lullabot.com * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.html * http://www.gnu.org/licenses/gpl.html */ /** * Create a degradeable star rating interface out of a simple form structure. * Returns a modified jQuery object containing the new interface. * * @example jQuery('form.rating').fivestar(); * @cat plugin * @type jQuery * */ (function($){ // Create local scope. /** * Takes the form element, builds the rating interface and attaches the proper events. * @param {Object} $obj */ var buildRating = function($obj){ var $widget = buildInterface($obj), $stars = $('.star', $widget), $cancel = $('.cancel', $widget), $summary = $('.fivestar-summary', $obj), feedbackTimerId = 0, summaryText = $summary.html(), summaryHover = $obj.is('.fivestar-labels-hover'), currentValue = $("select", $obj).val(), cancelTitle = $('label', $obj).html(), voteTitle = cancelTitle != Drupal.settings.fivestar.titleAverage ? cancelTitle : Drupal.settings.fivestar.titleUser, voteChanged = false; // Record star display. if ($obj.is('.fivestar-user-stars')) { var starDisplay = 'user'; } else if ($obj.is('.fivestar-average-stars')) { var starDisplay = 'average'; currentValue = $("input[name=vote_average]", $obj).val(); } else if ($obj.is('.fivestar-combo-stars')) { var starDisplay = 'combo'; } else { var starDisplay = 'none'; } // Smart is intentionally separate, so the average will be set if necessary. if ($obj.is('.fivestar-smart-stars')) { var starDisplay = 'smart'; } // Record text display. if ($summary.size()) { var textDisplay = $summary.attr('class').replace(/.*?fivestar-summary-([^ ]+).*/, '$1').replace(/-/g, '_'); } else { var textDisplay = 'none'; } // Add hover and focus events. $stars .mouseover(function(){ event.drain(); event.fill(this); }) .mouseout(function(){ event.drain(); event.reset(); }); $stars.children() .focus(function(){ event.drain(); event.fill(this.parentNode) }) .blur(function(){ event.drain(); event.reset(); }).end(); // Cancel button events. $cancel .mouseover(function(){ event.drain(); $(this).addClass('on') }) .mouseout(function(){ event.reset(); $(this).removeClass('on') }); $cancel.children() .focus(function(){ event.drain(); $(this.parentNode).addClass('on') }) .blur(function(){ event.reset(); $(this.parentNode).removeClass('on') }).end(); // Click events. $cancel.click(function(){ currentValue = 0; event.reset(); voteChanged = false; // Inform a user that his vote is being processed if ($("input.fivestar-path", $obj).size() && $summary.is('.fivestar-feedback-enabled')) { setFeedbackText(Drupal.settings.fivestar.feedbackDeletingVote); } // Save the currentValue in a hidden field. $("select", $obj).val(0); // Update the title. cancelTitle = starDisplay != 'smart' ? cancelTitle : Drupal.settings.fivestar.titleAverage; $('label', $obj).html(cancelTitle); // Update the smart classes on the widget if needed. if ($obj.is('.fivestar-smart-text')) { $obj.removeClass('fivestar-user-text').addClass('fivestar-average-text'); $summary[0].className = $summary[0].className.replace(/-user/, '-average'); textDisplay = $summary.attr('class').replace(/.*?fivestar-summary-([^ ]+).*/, '$1').replace(/-/g, '_'); } if ($obj.is('.fivestar-smart-stars')) { $obj.removeClass('fivestar-user-stars').addClass('fivestar-average-stars'); } // Submit the form if needed. $("input.fivestar-path", $obj).each(function() { var token = $("input.fivestar-token", $obj).val(); $.ajax({ type: 'GET', data: { token: token }, dataType: 'xml', url: this.value + '/' + 0, success: voteHook }); }); return false; }); $stars.click(function(){ currentValue = $('select option', $obj).get($stars.index(this) + $cancel.size() + 1).value; // Save the currentValue to the hidden select field. $("select", $obj).val(currentValue); // Update the display of the stars. voteChanged = true; event.reset(); // Inform a user that his vote is being processed. if ($("input.fivestar-path", $obj).size() && $summary.is('.fivestar-feedback-enabled')) { setFeedbackText(Drupal.settings.fivestar.feedbackSavingVote); } // Update the smart classes on the widget if needed. if ($obj.is('.fivestar-smart-text')) { $obj.removeClass('fivestar-average-text').addClass('fivestar-user-text'); $summary[0].className = $summary[0].className.replace(/-average/, '-user'); textDisplay = $summary.attr('class').replace(/.*?fivestar-summary-([^ ]+).*/, '$1').replace(/-/g, '_'); } if ($obj.is('.fivestar-smart-stars')) { $obj.removeClass('fivestar-average-stars').addClass('fivestar-user-stars'); } // Submit the form if needed. $("input.fivestar-path", $obj).each(function () { var token = $("input.fivestar-token", $obj).val(); $.ajax({ type: 'GET', data: { token: token }, dataType: 'xml', url: this.value + '/' + currentValue, success: voteHook }); }); return false; }); var event = { fill: function(el){ // Fill to the current mouse position. var index = $stars.index(el) + 1; $stars .children('a').css('width', '100%').end() .filter(':lt(' + index + ')').addClass('hover').end(); // Update the description text and label. if (summaryHover && !feedbackTimerId) { var summary = $("select option", $obj)[index + $cancel.size()].text; var value = $("select option", $obj)[index + $cancel.size()].value; $summary.html(summary != index + 1 ? summary : ' '); $('label', $obj).html(voteTitle); } }, drain: function() { // Drain all the stars. $stars .filter('.on').removeClass('on').end() .filter('.hover').removeClass('hover').end(); // Update the description text. if (summaryHover && !feedbackTimerId) { var cancelText = $("select option", $obj)[1].text; $summary.html(($cancel.size() && cancelText != 0) ? cancelText : ' '); if (!voteChanged) { $('label', $obj).html(cancelTitle); } } }, reset: function(){ // Reset the stars to the default index. var starValue = currentValue/100 * $stars.size(); var percent = (starValue - Math.floor(starValue)) * 100; $stars.filter(':lt(' + Math.floor(starValue) + ')').addClass('on').end(); if (percent > 0) { $stars.eq(Math.floor(starValue)).addClass('on').children('a').css('width', percent + "%").end().end(); } // Restore the summary text and original title. if (summaryHover && !feedbackTimerId) { $summary.html(summaryText ? summaryText : ' '); } if (voteChanged) { $('label', $obj).html(voteTitle); } else { $('label', $obj).html(cancelTitle); } } }; var setFeedbackText = function(text) { // Kill previous timer if it isn't finished yet so that the text we // are about to set will not get cleared too early. feedbackTimerId = 1; $summary.html(text); }; /** * Checks for the presence of a javascript hook 'fivestarResult' to be * called upon completion of a AJAX vote request. */ var voteHook = function(data) { var returnObj = { result: { count: $("result > count", data).text(), average: $("result > average", data).text(), summary: { average: $("summary average", data).text(), average_count: $("summary average_count", data).text(), user: $("summary user", data).text(), user_count: $("summary user_count", data).text(), combo: $("summary combo", data).text(), count: $("summary count", data).text() } }, vote: { id: $("vote id", data).text(), tag: $("vote tag", data).text(), type: $("vote type", data).text(), value: $("vote value", data).text() }, display: { stars: starDisplay, text: textDisplay } }; // Check for a custom callback. if (window.fivestarResult) { fivestarResult(returnObj); } // Use the default. else { fivestarDefaultResult(returnObj); } // Update the summary text. summaryText = returnObj.result.summary[returnObj.display.text]; if ($(returnObj.result.summary.average).is('.fivestar-feedback-enabled')) { // Inform user that his/her vote has been processed. if (returnObj.vote.value != 0) { // check if vote has been saved or deleted setFeedbackText(Drupal.settings.fivestar.feedbackVoteSaved); } else { setFeedbackText(Drupal.settings.fivestar.feedbackVoteDeleted); } // Setup a timer to clear the feedback text after 3 seconds. feedbackTimerId = setTimeout(function() { clearTimeout(feedbackTimerId); feedbackTimerId = 0; $summary.html(returnObj.result.summary[returnObj.display.text]); }, 2024); } // Update the current star currentValue to the previous average. if (returnObj.vote.value == 0 && (starDisplay == 'average' || starDisplay == 'smart')) { currentValue = returnObj.result.average; event.reset(); } }; event.reset(); return $widget; }; /** * Accepts jQuery object containing a single fivestar widget. * Returns the proper div structure for the star interface. * * @return jQuery * @param {Object} $widget * */ var buildInterface = function($widget){ var $container = $('
'); var $options = $("select option", $widget); var size = $('option', $widget).size() - 1; var cancel = 1; for (var i = 1, option; option = $options[i]; i++){ if (option.value == "0") { cancel = 0; $div = $('
' + option.text + '
'); } else { var zebra = (i + cancel - 1) % 2 == 0 ? 'even' : 'odd'; var count = i + cancel - 1; var first = count == 1 ? ' star-first' : ''; var last = count == size + cancel - 1 ? ' star-last' : ''; $div = $('
' + option.text + '
'); } $container.append($div[0]); } $container.addClass('fivestar-widget-' + (size + cancel - 1)); // Attach the new widget and hide the existing widget. $('select', $widget).after($container).css('display', 'none'); return $container; }; /** * Standard handler to update the average rating when a user changes their * vote. This behavior can be overridden by implementing a fivestarResult * function in your own module or theme. * @param object voteResult * Object containing the following properties from the vote result: * voteResult.result.count The current number of votes for this item. * voteResult.result.average The current average of all votes for this item. * voteResult.result.summary.average The textual description of the average. * voteResult.result.summary.user The textual description of the user's current vote. * voteResult.vote.id The id of the item the vote was placed on (such as the nid) * voteResult.vote.type The type of the item the vote was placed on (such as 'node') * voteResult.vote.tag The multi-axis tag the vote was placed on (such as 'vote') * voteResult.vote.average The average of the new vote saved * voteResult.display.stars The type of star display we're using. Either 'average', 'user', or 'combo'. * voteResult.display.text The type of text display we're using. Either 'average', 'user', or 'combo'. */ function fivestarDefaultResult(voteResult) { // Update the summary text. $('div.fivestar-summary-'+voteResult.vote.tag+'-'+voteResult.vote.id).html(voteResult.result.summary[voteResult.display.text]); // If this is a combo display, update the average star display. if (voteResult.display.stars == 'combo') { $('div.fivestar-form-'+voteResult.vote.id).each(function() { // Update stars. var $stars = $('.fivestar-widget-static .star span', this); var average = voteResult.result.average/100 * $stars.size(); var index = Math.floor(average); $stars.removeClass('on').addClass('off').css('width', 'auto'); $stars.filter(':lt(' + (index + 1) + ')').removeClass('off').addClass('on'); $stars.eq(index).css('width', ((average - index) * 100) + "%"); // Update summary. var $summary = $('.fivestar-static-form-item .fivestar-summary', this); if ($summary.size()) { var textDisplay = $summary.attr('class').replace(/.*?fivestar-summary-([^ ]+).*/, '$1').replace(/-/g, '_'); $summary.html(voteResult.result.summary[textDisplay]); } }); } }; /** * Set up the plugin */ $.fn.fivestar = function() { var stack = []; this.each(function() { var ret = buildRating($(this)); stack.push(ret); }); return stack; }; // Fix ie6 background flicker problem. if ($.browser.msie == true) { try { document.execCommand('BackgroundImageCache', false, true); } catch(err) {} } Drupal.behaviors.fivestar = function(context) { $('div.fivestar-form-item:not(.fivestar-processed)', context).addClass('fivestar-processed').fivestar(); $('input.fivestar-submit', context).css('display', 'none'); } })(jQuery);