JAVASCRIPT   33
RateIt
Guest on 14th September 2023 12:16:21 AM


  1. /*! RateIt | v1.1.2 / 03/28/2019
  2.     https://github.com/gjunge/rateit.js | Twitter: @gjunge
  3. */
  4. (function ($) {
  5.     $.rateit = {
  6.         aria: {
  7.             resetLabel: 'reset rating',
  8.             ratingLabel: 'rating'
  9.         }
  10.     };
  11.  
  12.     $.fn.rateit = function (p1, p2) {
  13.         //quick way out.
  14.         var index = 1;
  15.         var options = {}; var mode = 'init';
  16.         var capitaliseFirstLetter = function (string) {
  17.             return string.charAt(0).toUpperCase() + string.substr(1);
  18.         };
  19.  
  20.         if (this.length === 0) { return this; }
  21.  
  22.  
  23.         var tp1 = $.type(p1);
  24.         if (tp1 == 'object' || p1 === undefined || p1 === null) {
  25.             options = $.extend({}, $.fn.rateit.defaults, p1); //wants to init new rateit plugin(s).
  26.         }
  27.         else if (tp1 == 'string' && p1 !== 'reset' && p2 === undefined) {
  28.             return this.data('rateit' + capitaliseFirstLetter(p1)); //wants to get a value.
  29.         }
  30.         else if (tp1 == 'string') {
  31.             mode = 'setvalue';
  32.         }
  33.  
  34.         return this.each(function () {
  35.             var item = $(this);
  36.  
  37.  
  38.             //shorten all the item.data('rateit-XXX'), will save space in closure compiler, will be like item.data('XXX') will become x('XXX')
  39.             var itemdata = function (key, value) {
  40.  
  41.                 if (value != null) {
  42.                     //update aria values
  43.                     var ariakey = 'aria-value' + ((key == 'value') ? 'now' : key);
  44.                     var range = item.find('.rateit-range');
  45.                     if (range.attr(ariakey) != undefined) {
  46.                         range.attr(ariakey, value);
  47.                     }
  48.  
  49.                 }
  50.  
  51.                 arguments[0] = 'rateit' + capitaliseFirstLetter(key);
  52.                 return item.data.apply(item, arguments); ////Fix for WI: 523
  53.             };
  54.  
  55.             //handle programmatic reset
  56.             if (p1 == 'reset') {
  57.                 var setup = itemdata('init'); //get initial value
  58.                 for (var prop in setup) {
  59.                     item.data(prop, setup[prop]);
  60.                 }
  61.  
  62.                 if (itemdata('backingfld')) { //reset also backingfield
  63.                     var fld = $(itemdata('backingfld'));
  64.                     // If backing field is a select box with valuesrc option set to "index", reset its selectedIndex property; otherwise, reset its value.
  65.                     if (fld[0].nodeName == 'SELECT' && fld[0].getAttribute('data-rateit-valuesrc') === 'index') {
  66.                         fld.prop('selectedIndex', itemdata('value'));
  67.                     }
  68.                     else {
  69.                         fld.val(itemdata('value'));
  70.                     }
  71.                     fld.trigger('change');
  72.                     if (fld[0].min) { fld[0].min = itemdata('min'); }
  73.                     if (fld[0].max) { fld[0].max = itemdata('max'); }
  74.                     if (fld[0].step) { fld[0].step = itemdata('step'); }
  75.                 }
  76.                 item.trigger('reset');
  77.             }
  78.  
  79.             //add the rate it class.
  80.             if (!item.hasClass('rateit')) { item.addClass('rateit'); }
  81.  
  82.             var ltr = item.css('direction') != 'rtl';
  83.  
  84.             // set value mode
  85.             if (mode == 'setvalue') {
  86.                 if (!itemdata('init')) { throw 'Can\'t set value before init'; }
  87.  
  88.  
  89.                 //if readonly now and it wasn't readonly, remove the eventhandlers.
  90.                 if (p1 == 'readonly' && p2 == true && !itemdata('readonly')) {
  91.                     item.find('.rateit-range').unbind();
  92.                     itemdata('wired', false);
  93.                 }
  94.                 //when we receive a null value, reset the score to its min value.
  95.                 if (p1 == 'value') {
  96.                     p2 = (p2 == null) ? itemdata('min') : Math.max(itemdata('min'), Math.min(itemdata('max'), p2));
  97.                 }
  98.                 if (itemdata('backingfld')) {
  99.                     //if we have a backing field, check which fields we should update.
  100.                     //In case of input[type=range], although we did read its attributes even in browsers that don't support it (using fld.attr())
  101.                     //we only update it in browser that support it (&& fld[0].min only works in supporting browsers), not only does it save us from checking if it is range input type, it also is unnecessary.
  102.                     var fld = $(itemdata('backingfld'));
  103.                     // If backing field is a select box with valuesrc option set to "index", update its selectedIndex property; otherwise, update its value.
  104.                     if (fld[0].nodeName == 'SELECT' && fld[0].getAttribute('data-rateit-valuesrc') === 'index') {
  105.                         if (p1 == 'value') { fld.prop('selectedIndex', p2); }
  106.                     }
  107.                     else {
  108.                         if (p1 == 'value') { fld.val(p2); }
  109.                     }
  110.                     if (p1 == 'min' && fld[0].min) { fld[0].min = p2; }
  111.                     if (p1 == 'max' && fld[0].max) { fld[0].max = p2;}
  112.                     if (p1 == 'step' && fld[0].step) { fld[0].step = p2; }
  113.                 }
  114.  
  115.                 itemdata(p1, p2);
  116.             }
  117.  
  118.  
  119.             //init rateit plugin
  120.             if (!itemdata('init')) {
  121.  
  122.                 //get our values, either from the data-* html5 attribute or from the options.
  123.                 itemdata('mode', itemdata('mode') || options.mode)
  124.                 itemdata('icon', itemdata('icon') || options.icon)
  125.                 itemdata('min', isNaN(itemdata('min')) ? options.min : itemdata('min'));
  126.                 itemdata('max', isNaN(itemdata('max')) ? options.max : itemdata('max'));
  127.                 itemdata('step', itemdata('step') || options.step);
  128.                 itemdata('readonly', itemdata('readonly') !== undefined ? itemdata('readonly') : options.readonly);
  129.                 itemdata('resetable', itemdata('resetable') !== undefined ? itemdata('resetable') : options.resetable);
  130.                 itemdata('backingfld', itemdata('backingfld') || options.backingfld);
  131.                 itemdata('starwidth', itemdata('starwidth') || options.starwidth);
  132.                 itemdata('starheight', itemdata('starheight') || options.starheight);
  133.                 itemdata('value', Math.max(itemdata('min'), Math.min(itemdata('max'), (!isNaN(itemdata('value')) ? itemdata('value') : (!isNaN(options.value) ? options.value : options.min)))));
  134.                 itemdata('ispreset', itemdata('ispreset') !== undefined ? itemdata('ispreset') : options.ispreset);
  135.                 //are we LTR or RTL?
  136.  
  137.                 if (itemdata('backingfld')) {
  138.                     //if we have a backing field, hide it, override defaults if range or select.
  139.                     var fld = $(itemdata('backingfld')).hide();
  140.  
  141.                     if (fld.attr('disabled') || fld.attr('readonly')) {
  142.                         itemdata('readonly', true); //http://rateit.codeplex.com/discussions/362055 , if a backing field is disabled or readonly at instantiation, make rateit readonly.
  143.                     }
  144.  
  145.                     if (fld[0].nodeName == 'INPUT') {
  146.                         if (fld[0].type == 'range' || fld[0].type == 'text') { //in browsers not support the range type, it defaults to text
  147.  
  148.                             itemdata('min', parseInt(fld.attr('min')) || itemdata('min')); //if we would have done fld[0].min it wouldn't have worked in browsers not supporting the range type.
  149.                             itemdata('max', parseInt(fld.attr('max')) || itemdata('max'));
  150.                             itemdata('step', parseInt(fld.attr('step')) || itemdata('step'));
  151.                         }
  152.                     }
  153.                     if (fld[0].nodeName == 'SELECT' && fld[0].options.length > 1) {
  154.                         // If backing field is a select box with valuesrc option set to "index", use the indexes of its options; otherwise, use the values.
  155.                         if (fld[0].getAttribute('data-rateit-valuesrc') === 'index') {
  156.                             itemdata('min', (!isNaN(itemdata('min')) ? itemdata('min') : Number(fld[0].options[0].index)));
  157.                             itemdata('max', Number(fld[0].options[fld[0].length - 1].index));
  158.                             itemdata('step', Number(fld[0].options[1].index) - Number(fld[0].options[0].index));
  159.                         }
  160.                         else {
  161.                             itemdata('min', (!isNaN(itemdata('min')) ? itemdata('min') : Number(fld[0].options[0].value)));
  162.                             itemdata('max', Number(fld[0].options[fld[0].length - 1].value));
  163.                             itemdata('step', Number(fld[0].options[1].value) - Number(fld[0].options[0].value));
  164.                         }
  165.                         //see if we have a option that as explicity been selected
  166.                         var selectedOption = fld.find('option[selected]');
  167.                         if (selectedOption.length == 1) {
  168.                             // If backing field is a select box with valuesrc option set to "index", use the index of selected option; otherwise, use the value.
  169.                             if (fld[0].getAttribute('data-rateit-valuesrc') === 'index') {
  170.                                 itemdata('value', selectedOption[0].index);
  171.                             }
  172.                             else {
  173.                                 itemdata('value', selectedOption.val());
  174.                             }
  175.                         }
  176.                     }
  177.                     else {
  178.                         //if it is not a select box, we can get's it's value using the val function.
  179.                         //If it is a selectbox, we always get a value (the first one of the list), even if it was not explicity set.
  180.                         itemdata('value', fld.val());
  181.                     }
  182.  
  183.                    
  184.                 }
  185.  
  186.              
  187.  
  188.                 //Create the necessary tags. For ARIA purposes we need to give the items an ID. So we use an internal index to create unique ids
  189.                 var element = item[0].nodeName == 'DIV' ? 'div' : 'span';
  190.                 index++;
  191.  
  192.                 // tabindex="0" gets only added in readonly mode. When keyboard tabbing, no focus is needed in readonly mode.
  193.                 var html = '<button id="rateit-reset-{{index}}" type="button" data-role="none" class="rateit-reset" aria-label="' + $.rateit.aria.resetLabel + '" aria-controls="rateit-range-{{index}}"><span></span></button><{{element}} id="rateit-range-{{index}}" class="rateit-range"' + (itemdata('readonly') == true ? '' : ' tabindex="0"') + ' role="slider" aria-label="' + $.rateit.aria.ratingLabel + '" aria-owns="rateit-reset-{{index}}" aria-valuemin="' + itemdata('min') + '" aria-valuemax="' + itemdata('max') + '" aria-valuenow="' + itemdata('value') + '"><{{element}} class="rateit-empty"></{{element}}><{{element}} class="rateit-selected"></{{element}}><{{element}} class="rateit-hover"></{{element}}></{{element}}>';
  194.                 item.append(html.replace(/{{index}}/gi, index).replace(/{{element}}/gi, element));
  195.  
  196.                 //if we are in RTL mode, we have to change the float of the "reset button"
  197.                 if (!ltr) {
  198.                     item.find('.rateit-reset').css('float', 'right');
  199.                     item.find('.rateit-selected').addClass('rateit-selected-rtl');
  200.                     item.find('.rateit-hover').addClass('rateit-hover-rtl');
  201.                 }
  202.  
  203.                 if (itemdata('mode') == 'font') {
  204.                     item.addClass('rateit-font').removeClass('rateit-bg');
  205.                 }
  206.                 else {
  207.                     item.addClass('rateit-bg').removeClass('rateit-font');
  208.                 }
  209.  
  210.                 itemdata('init', JSON.parse(JSON.stringify(item.data()))); //cheap way to create a clone
  211.             }
  212.  
  213.             var isfont = itemdata('mode') == 'font';
  214.  
  215.            
  216.  
  217.  
  218.             //resize the height of all elements,
  219.             if (!isfont) {
  220.                 item.find('.rateit-selected, .rateit-hover').height(itemdata('starheight'));
  221.             }
  222.  
  223.  
  224.             var range = item.find('.rateit-range');
  225.             if (isfont) {
  226.                 //fill the ranges with the icons
  227.                 var icon = itemdata('icon');
  228.                 var stars = itemdata('max') - itemdata('min');
  229.  
  230.                 var txt = '';
  231.                 for(var i = 0; i< stars; i++){
  232.                     txt += icon;
  233.                 }
  234.                
  235.                 range.find('> *').text(txt);
  236.                
  237.  
  238.                 itemdata('starwidth', range.width() / (itemdata('max') - itemdata('min')))
  239.             }
  240.             else {
  241.                 //set the range element to fit all the stars.
  242.                 range.width(itemdata('starwidth') * (itemdata('max') - itemdata('min'))).height(itemdata('starheight'));
  243.             }
  244.  
  245.  
  246.             //add/remove the preset class
  247.             var presetclass = 'rateit-preset' + ((ltr) ? '' : '-rtl');
  248.             if (itemdata('ispreset')) {
  249.                 item.find('.rateit-selected').addClass(presetclass);
  250.             }
  251.             else {
  252.                 item.find('.rateit-selected').removeClass(presetclass);
  253.             }
  254.  
  255.             //set the value if we have it.
  256.             if (itemdata('value') != null) {
  257.                 var score = (itemdata('value') - itemdata('min')) * itemdata('starwidth');
  258.                 item.find('.rateit-selected').width(score);
  259.             }
  260.  
  261.             //setup the reset button
  262.             var resetbtn = item.find('.rateit-reset');
  263.             if (resetbtn.data('wired') !== true) {
  264.                 resetbtn.bind('click', function (e) {
  265.                     e.preventDefault();
  266.  
  267.                     resetbtn.blur();
  268.  
  269.                     var event = $.Event('beforereset');
  270.                     item.trigger(event);
  271.                     if (event.isDefaultPrevented()) {
  272.                         return false;
  273.                     }
  274.  
  275.                     item.rateit('value', null);
  276.                     item.trigger('reset');
  277.                 }).data('wired', true);
  278.  
  279.             }
  280.  
  281.             //this function calculates the score based on the current position of the mouse.
  282.             var calcRawScore = function (element, event) {
  283.                 var pageX = (event.changedTouches) ? event.changedTouches[0].pageX : event.pageX;
  284.  
  285.                 var offsetx = pageX - $(element).offset().left;
  286.                 if (!ltr) { offsetx = range.width() - offsetx };
  287.                 if (offsetx > range.width()) { offsetx = range.width(); }
  288.                 if (offsetx < 0) { offsetx = 0; }
  289.  
  290.                 return score = Math.ceil(offsetx / itemdata('starwidth') * (1 / itemdata('step')));
  291.             };
  292.  
  293.             //sets the hover element based on the score.
  294.             var setHover = function (score) {
  295.                 var w = score * itemdata('starwidth') * itemdata('step');
  296.                 var h = range.find('.rateit-hover');
  297.                 if (h.data('width') != w) {
  298.                     range.find('.rateit-selected').hide();
  299.                     h.width(w).show().data('width', w);
  300.                     var data = [(score * itemdata('step')) + itemdata('min')];
  301.                     item.trigger('hover', data).trigger('over', data);
  302.                 }
  303.             };
  304.  
  305.             var setSelection = function (value) {
  306.                 var event = $.Event('beforerated');
  307.                 item.trigger(event, [value]);
  308.                 if (event.isDefaultPrevented()) {
  309.                     return false;
  310.                 }
  311.  
  312.                 itemdata('value', value);
  313.                 if (itemdata('backingfld')) {
  314.                     // If backing field is a select box with valuesrc option set to "index", update its selectedIndex property; otherwise, update its value.
  315.                     if (fld[0].nodeName == 'SELECT' && fld[0].getAttribute('data-rateit-valuesrc') === 'index') {
  316.                         $(itemdata('backingfld')).prop('selectedIndex', value).trigger('change');
  317.                     }
  318.                     else {
  319.                         $(itemdata('backingfld')).val(value).trigger('change');
  320.                     }
  321.                 }
  322.                 if (itemdata('ispreset')) { //if it was a preset value, unset that.
  323.                     range.find('.rateit-selected').removeClass(presetclass);
  324.                     itemdata('ispreset', false);
  325.                 }
  326.                 range.find('.rateit-hover').hide();
  327.                 range.find('.rateit-selected').width(value * itemdata('starwidth') - (itemdata('min') * itemdata('starwidth'))).show();
  328.                 item.trigger('hover', [null]).trigger('over', [null]).trigger('rated', [value]);
  329.                 return true;
  330.             };
  331.  
  332.             if (!itemdata('readonly')) {
  333.                 //if we are not read only, add all the events
  334.  
  335.                 //if we have a reset button, set the event handler.
  336.                 if (!itemdata('resetable')) {
  337.                     resetbtn.hide();
  338.                 }
  339.  
  340.                 //when the mouse goes over the range element, we set the "hover" stars.
  341.                 if (!itemdata('wired')) {
  342.                     range.bind('touchmove touchend', touchHandler); //bind touch events
  343.                     range.mousemove(function (e) {
  344.                         var score = calcRawScore(this, e);
  345.                         setHover(score);
  346.                     });
  347.                     //when the mouse leaves the range, we have to hide the hover stars, and show the current value.
  348.                     range.mouseleave(function (e) {
  349.                         range.find('.rateit-hover').hide().width(0).data('width', '');
  350.                         item.trigger('hover', [null]).trigger('over', [null]);
  351.                         range.find('.rateit-selected').show();
  352.                     });
  353.                     //when we click on the range, we have to set the value, hide the hover.
  354.                     range.mouseup(function (e) {
  355.                         var score = calcRawScore(this, e);
  356.                         var value = (score * itemdata('step')) + itemdata('min');
  357.                         setSelection(value);
  358.                         range.blur();
  359.                     });
  360.  
  361.                     //support key nav
  362.                     range.keyup(function (e) {
  363.                         if (e.which == 38 || e.which == (ltr ? 39 : 37)) {
  364.                             setSelection(Math.min(itemdata('value') + itemdata('step'), itemdata('max')));
  365.                         }
  366.                         if (e.which == 40 || e.which == (ltr ? 37 : 39)) {
  367.                             setSelection(Math.max(itemdata('value') - itemdata('step'), itemdata('min')));
  368.                         }
  369.                     });
  370.  
  371.                     itemdata('wired', true);
  372.                 }
  373.                 if (itemdata('resetable')) {
  374.                     resetbtn.show();
  375.                 }
  376.             }
  377.             else {
  378.                 resetbtn.hide();
  379.             }
  380.  
  381.             range.attr('aria-readonly', itemdata('readonly'));
  382.         });
  383.     };
  384.  
  385.     //touch converter http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/
  386.     function touchHandler(event) {
  387.  
  388.         var touches = event.originalEvent.changedTouches,
  389.                 first = touches[0],
  390.                 type = "";
  391.         switch (event.type) {
  392.             case "touchmove": type = "mousemove"; break;
  393.             case "touchend": type = "mouseup"; break;
  394.             default: return;
  395.         }
  396.  
  397.         var simulatedEvent = document.createEvent("MouseEvent");
  398.         simulatedEvent.initMouseEvent(type, true, true, window, 1,
  399.                               first.screenX, first.screenY,
  400.                               first.clientX, first.clientY, false,
  401.                               false, false, false, 0/*left*/, null);
  402.  
  403.         first.target.dispatchEvent(simulatedEvent);
  404.         event.preventDefault();
  405.     };
  406.  
  407.     //some default values.
  408.     $.fn.rateit.defaults = { min: 0, max: 5, step: 0.5, mode: 'bg', icon: '★'�', starwidth: 16, starheight: 16, readonly: false, resetable: true, ispreset: false };
  409.  
  410.   //invoke it on all .rateit elements. This could be removed if not wanted.
  411. .
  412.     $(function () { $('div.rateit, span.rateit').rateit(); });
  413.  
  414. })(jQuery

Raw Paste

Login or Register to edit or fork this paste. It's free.