Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | espaco | 1 | /*! RateIt | v1.0.18 / 12/22/2013 | https://rateit.codeplex.com/license |
| 2 | http://rateit.codeplex.com | 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 | fld.val(itemdata('value')); |
||
| 65 | if (fld[0].min) fld[0].min = itemdata('min'); |
||
| 66 | if (fld[0].max) fld[0].max = itemdata('max'); |
||
| 67 | if (fld[0].step) fld[0].step = itemdata('step'); |
||
| 68 | } |
||
| 69 | item.trigger('reset'); |
||
| 70 | } |
||
| 71 | |||
| 72 | //add the rate it class. |
||
| 73 | if (!item.hasClass('rateit')) item.addClass('rateit'); |
||
| 74 | |||
| 75 | var ltr = item.css('direction') != 'rtl'; |
||
| 76 | |||
| 77 | // set value mode |
||
| 78 | if (mode == 'setvalue') { |
||
| 79 | if (!itemdata('init')) throw 'Can\'t set value before init'; |
||
| 80 | |||
| 81 | |||
| 82 | //if readonly now and it wasn't readonly, remove the eventhandlers. |
||
| 83 | if (p1 == 'readonly' && p2 == true && !itemdata('readonly')) { |
||
| 84 | item.find('.rateit-range').unbind(); |
||
| 85 | itemdata('wired', false); |
||
| 86 | } |
||
| 87 | //when we receive a null value, reset the score to its min value. |
||
| 88 | if (p1 == 'value') |
||
| 89 | p2 = (p2 == null) ? itemdata('min') : Math.max(itemdata('min'), Math.min(itemdata('max'), p2)); |
||
| 90 | if (itemdata('backingfld')) { |
||
| 91 | //if we have a backing field, check which fields we should update. |
||
| 92 | //In case of input[type=range], although we did read its attributes even in browsers that don't support it (using fld.attr()) |
||
| 93 | //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. |
||
| 94 | var fld = $(itemdata('backingfld')); |
||
| 95 | if (p1 == 'value') fld.val(p2); |
||
| 96 | if (p1 == 'min' && fld[0].min) fld[0].min = p2; |
||
| 97 | if (p1 == 'max' && fld[0].max) fld[0].max = p2; |
||
| 98 | if (p1 == 'step' && fld[0].step) fld[0].step = p2; |
||
| 99 | } |
||
| 100 | |||
| 101 | itemdata(p1, p2); |
||
| 102 | } |
||
| 103 | |||
| 104 | //init rateit plugin |
||
| 105 | if (!itemdata('init')) { |
||
| 106 | |||
| 107 | //get our values, either from the data-* html5 attribute or from the options. |
||
| 108 | itemdata('min', isNaN(itemdata('min')) ? options.min : itemdata('min')); |
||
| 109 | itemdata('max', isNaN(itemdata('max')) ? options.max : itemdata('max')); |
||
| 110 | itemdata('step', itemdata('step') || options.step); |
||
| 111 | itemdata('readonly', itemdata('readonly') !== undefined ? itemdata('readonly') : options.readonly); |
||
| 112 | itemdata('resetable', itemdata('resetable') !== undefined ? itemdata('resetable') : options.resetable); |
||
| 113 | itemdata('backingfld', itemdata('backingfld') || options.backingfld); |
||
| 114 | itemdata('starwidth', itemdata('starwidth') || options.starwidth); |
||
| 115 | itemdata('starheight', itemdata('starheight') || options.starheight); |
||
| 116 | itemdata('value', Math.max(itemdata('min'), Math.min(itemdata('max'), (!isNaN(itemdata('value')) ? itemdata('value') : (!isNaN(options.value) ? options.value : options.min) )))); |
||
| 117 | itemdata('ispreset', itemdata('ispreset') !== undefined ? itemdata('ispreset') : options.ispreset); |
||
| 118 | //are we LTR or RTL? |
||
| 119 | |||
| 120 | if (itemdata('backingfld')) { |
||
| 121 | //if we have a backing field, hide it, and get its value, and override defaults if range. |
||
| 122 | var fld = $(itemdata('backingfld')); |
||
| 123 | itemdata('value', fld.hide().val()); |
||
| 124 | |||
| 125 | if (fld.attr('disabled') || fld.attr('readonly')) |
||
| 126 | itemdata('readonly', true); //http://rateit.codeplex.com/discussions/362055 , if a backing field is disabled or readonly at instantiation, make rateit readonly. |
||
| 127 | |||
| 128 | |||
| 129 | if (fld[0].nodeName == 'INPUT') { |
||
| 130 | if (fld[0].type == 'range' || fld[0].type == 'text') { //in browsers not support the range type, it defaults to text |
||
| 131 | |||
| 132 | 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. |
||
| 133 | itemdata('max', parseInt(fld.attr('max')) || itemdata('max')); |
||
| 134 | itemdata('step', parseInt(fld.attr('step')) || itemdata('step')); |
||
| 135 | } |
||
| 136 | } |
||
| 137 | if (fld[0].nodeName == 'SELECT' && fld[0].options.length > 1) { |
||
| 138 | itemdata('min', Number(fld[0].options[0].value)); |
||
| 139 | itemdata('max', Number(fld[0].options[fld[0].length - 1].value)); |
||
| 140 | itemdata('step', Number(fld[0].options[1].value) - Number(fld[0].options[0].value)); |
||
| 141 | } |
||
| 142 | } |
||
| 143 | |||
| 144 | //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 |
||
| 145 | var element = item[0].nodeName == 'DIV' ? 'div' : 'span'; |
||
| 146 | index++; |
||
| 147 | var html = '<button id="rateit-reset-{{index}}" data-role="none" class="rateit-reset" aria-label="' + $.rateit.aria.resetLabel + '" aria-controls="rateit-range-{{index}}"></button><{{element}} id="rateit-range-{{index}}" class="rateit-range" 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-selected" style="height:' + itemdata('starheight') + 'px"></{{element}}><{{element}} class="rateit-hover" style="height:' + itemdata('starheight') + 'px"></{{element}}></{{element}}>'; |
||
| 148 | item.append(html.replace(/{{index}}/gi, index).replace(/{{element}}/gi, element)); |
||
| 149 | |||
| 150 | //if we are in RTL mode, we have to change the float of the "reset button" |
||
| 151 | if (!ltr) { |
||
| 152 | item.find('.rateit-reset').css('float', 'right'); |
||
| 153 | item.find('.rateit-selected').addClass('rateit-selected-rtl'); |
||
| 154 | item.find('.rateit-hover').addClass('rateit-hover-rtl'); |
||
| 155 | } |
||
| 156 | |||
| 157 | itemdata('init', JSON.parse(JSON.stringify(item.data()))); //cheap way to create a clone |
||
| 158 | } |
||
| 159 | //resize the height of all elements, |
||
| 160 | item.find('.rateit-selected, .rateit-hover').height(itemdata('starheight')); |
||
| 161 | |||
| 162 | //set the range element to fit all the stars. |
||
| 163 | var range = item.find('.rateit-range'); |
||
| 164 | range.width(itemdata('starwidth') * (itemdata('max') - itemdata('min'))).height(itemdata('starheight')); |
||
| 165 | |||
| 166 | |||
| 167 | //add/remove the preset class |
||
| 168 | var presetclass = 'rateit-preset' + ((ltr) ? '' : '-rtl'); |
||
| 169 | if (itemdata('ispreset')) |
||
| 170 | item.find('.rateit-selected').addClass(presetclass); |
||
| 171 | else |
||
| 172 | item.find('.rateit-selected').removeClass(presetclass); |
||
| 173 | |||
| 174 | //set the value if we have it. |
||
| 175 | if (itemdata('value') != null) { |
||
| 176 | var score = (itemdata('value') - itemdata('min')) * itemdata('starwidth'); |
||
| 177 | item.find('.rateit-selected').width(score); |
||
| 178 | } |
||
| 179 | |||
| 180 | //setup the reset button |
||
| 181 | var resetbtn = item.find('.rateit-reset'); |
||
| 182 | if (resetbtn.data('wired') !== true) { |
||
| 183 | resetbtn.bind('click', function (e) { |
||
| 184 | e.preventDefault(); |
||
| 185 | resetbtn.blur(); |
||
| 186 | item.rateit('value', null); |
||
| 187 | item.trigger('reset'); |
||
| 188 | }).data('wired', true); |
||
| 189 | |||
| 190 | } |
||
| 191 | |||
| 192 | //this function calculates the score based on the current position of the mouse. |
||
| 193 | var calcRawScore = function (element, event) { |
||
| 194 | var pageX = (event.changedTouches) ? event.changedTouches[0].pageX : event.pageX; |
||
| 195 | |||
| 196 | var offsetx = pageX - $(element).offset().left; |
||
| 197 | if (!ltr) offsetx = range.width() - offsetx; |
||
| 198 | if (offsetx > range.width()) offsetx = range.width(); |
||
| 199 | if (offsetx < 0) offsetx = 0; |
||
| 200 | |||
| 201 | return score = Math.ceil(offsetx / itemdata('starwidth') * (1 / itemdata('step'))); |
||
| 202 | }; |
||
| 203 | |||
| 204 | //sets the hover element based on the score. |
||
| 205 | var setHover = function (score) { |
||
| 206 | var w = score * itemdata('starwidth') * itemdata('step'); |
||
| 207 | var h = range.find('.rateit-hover'); |
||
| 208 | if (h.data('width') != w) { |
||
| 209 | range.find('.rateit-selected').hide(); |
||
| 210 | h.width(w).show().data('width', w); |
||
| 211 | var data = [(score * itemdata('step')) + itemdata('min')]; |
||
| 212 | item.trigger('hover', data).trigger('over', data); |
||
| 213 | } |
||
| 214 | }; |
||
| 215 | |||
| 216 | var setSelection = function (value) { |
||
| 217 | itemdata('value', value); |
||
| 218 | if (itemdata('backingfld')) { |
||
| 219 | $(itemdata('backingfld')).val(value); |
||
| 220 | } |
||
| 221 | if (itemdata('ispreset')) { //if it was a preset value, unset that. |
||
| 222 | range.find('.rateit-selected').removeClass(presetclass); |
||
| 223 | itemdata('ispreset', false); |
||
| 224 | } |
||
| 225 | range.find('.rateit-hover').hide(); |
||
| 226 | range.find('.rateit-selected').width(value * itemdata('starwidth') - (itemdata('min') * itemdata('starwidth'))).show(); |
||
| 227 | item.trigger('hover', [null]).trigger('over', [null]).trigger('rated', [value]); |
||
| 228 | }; |
||
| 229 | |||
| 230 | if (!itemdata('readonly')) { |
||
| 231 | //if we are not read only, add all the events |
||
| 232 | |||
| 233 | //if we have a reset button, set the event handler. |
||
| 234 | if (!itemdata('resetable')) |
||
| 235 | resetbtn.hide(); |
||
| 236 | |||
| 237 | //when the mouse goes over the range element, we set the "hover" stars. |
||
| 238 | if (!itemdata('wired')) { |
||
| 239 | range.bind('touchmove touchend', touchHandler); //bind touch events |
||
| 240 | range.mousemove(function (e) { |
||
| 241 | var score = calcRawScore(this, e); |
||
| 242 | setHover(score); |
||
| 243 | }); |
||
| 244 | //when the mouse leaves the range, we have to hide the hover stars, and show the current value. |
||
| 245 | range.mouseleave(function (e) { |
||
| 246 | range.find('.rateit-hover').hide().width(0).data('width', ''); |
||
| 247 | item.trigger('hover', [null]).trigger('over', [null]); |
||
| 248 | range.find('.rateit-selected').show(); |
||
| 249 | }); |
||
| 250 | //when we click on the range, we have to set the value, hide the hover. |
||
| 251 | range.mouseup(function (e) { |
||
| 252 | var score = calcRawScore(this, e); |
||
| 253 | var value = (score * itemdata('step')) + itemdata('min'); |
||
| 254 | setSelection(value); |
||
| 255 | range.blur(); |
||
| 256 | }); |
||
| 257 | |||
| 258 | //support key nav |
||
| 259 | range.keyup( function (e) { |
||
| 260 | if (e.which == 38 || e.which == (ltr ? 39 : 37)) { |
||
| 261 | setSelection(Math.min(itemdata('value') + itemdata('step'), itemdata('max'))); |
||
| 262 | } |
||
| 263 | if (e.which == 40 || e.which == (ltr ? 37 : 39)) { |
||
| 264 | setSelection(Math.max(itemdata('value') - itemdata('step'), itemdata('min'))); |
||
| 265 | } |
||
| 266 | }); |
||
| 267 | |||
| 268 | itemdata('wired', true); |
||
| 269 | } |
||
| 270 | if (itemdata('resetable')) { |
||
| 271 | resetbtn.show(); |
||
| 272 | } |
||
| 273 | } |
||
| 274 | else { |
||
| 275 | resetbtn.hide(); |
||
| 276 | } |
||
| 277 | |||
| 278 | range.attr('aria-readonly', itemdata('readonly')); |
||
| 279 | }); |
||
| 280 | }; |
||
| 281 | |||
| 282 | //touch converter http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/ |
||
| 283 | function touchHandler(event) { |
||
| 284 | |||
| 285 | var touches = event.originalEvent.changedTouches, |
||
| 286 | first = touches[0], |
||
| 287 | type = ""; |
||
| 288 | switch (event.type) { |
||
| 289 | case "touchmove": type = "mousemove"; break; |
||
| 290 | case "touchend": type = "mouseup"; break; |
||
| 291 | default: return; |
||
| 292 | } |
||
| 293 | |||
| 294 | var simulatedEvent = document.createEvent("MouseEvent"); |
||
| 295 | simulatedEvent.initMouseEvent(type, true, true, window, 1, |
||
| 296 | first.screenX, first.screenY, |
||
| 297 | first.clientX, first.clientY, false, |
||
| 298 | false, false, false, 0/*left*/, null); |
||
| 299 | |||
| 300 | first.target.dispatchEvent(simulatedEvent); |
||
| 301 | event.preventDefault(); |
||
| 302 | }; |
||
| 303 | |||
| 304 | //some default values. |
||
| 305 | $.fn.rateit.defaults = { min: 0, max: 5, step: 0.5, starwidth: 16, starheight: 16, readonly: false, resetable: true, ispreset: false}; |
||
| 306 | |||
| 307 | //invoke it on all .rateit elements. This could be removed if not wanted. |
||
| 308 | $(function () { $('div.rateit, span.rateit').rateit(); }); |
||
| 309 | |||
| 310 | })(jQuery); |