hjg
2024-10-30 8cf23534166c07e711aac2a25911ada317ba01f0
提交 | 用户 | 时间
58d006 1 /*!
A 2  * jQuery Raty - A Star Rating Plugin
3  *
4  * The MIT License
5  *
6  * @author  : Washington Botelho
7  * @doc     : http://wbotelhos.com/raty
8  * @version : 2.7.0
9  *
10  */
11
12 ;
13 (function($) {
14   'use strict';
15
16   var methods = {
17     init: function(options) {
18       return this.each(function() {
19         this.self = $(this);
20
21         methods.destroy.call(this.self);
22
23         this.opt = $.extend(true, {}, $.fn.raty.defaults, options);
24
25         methods._adjustCallback.call(this);
26         methods._adjustNumber.call(this);
27         methods._adjustHints.call(this);
28
29         this.opt.score = methods._adjustedScore.call(this, this.opt.score);
30
31         if (this.opt.starType !== 'img') {
32           methods._adjustStarType.call(this);
33         }
34
35         methods._adjustPath.call(this);
36         methods._createStars.call(this);
37
38         if (this.opt.cancel) {
39           methods._createCancel.call(this);
40         }
41
42         if (this.opt.precision) {
43           methods._adjustPrecision.call(this);
44         }
45
46         methods._createScore.call(this);
47         methods._apply.call(this, this.opt.score);
48         methods._setTitle.call(this, this.opt.score);
49         methods._target.call(this, this.opt.score);
50
51         if (this.opt.readOnly) {
52           methods._lock.call(this);
53         } else {
54           this.style.cursor = 'pointer';
55
56           methods._binds.call(this);
57         }
58       });
59     },
60
61     _adjustCallback: function() {
62       var options = ['number', 'readOnly', 'score', 'scoreName', 'target'];
63
64       for (var i = 0; i < options.length; i++) {
65         if (typeof this.opt[options[i]] === 'function') {
66           this.opt[options[i]] = this.opt[options[i]].call(this);
67         }
68       }
69     },
70
71     _adjustedScore: function(score) {
72       if (!score) {
73         return score;
74       }
75
76       return methods._between(score, 0, this.opt.number);
77     },
78
79     _adjustHints: function() {
80       if (!this.opt.hints) {
81         this.opt.hints = [];
82       }
83
84       if (!this.opt.halfShow && !this.opt.half) {
85         return;
86       }
87
88       var steps = this.opt.precision ? 10 : 2;
89
90       for (var i = 0; i < this.opt.number; i++) {
91         var group = this.opt.hints[i];
92
93         if (Object.prototype.toString.call(group) !== '[object Array]') {
94           group = [group];
95         }
96
97         this.opt.hints[i] = [];
98
99         for (var j = 0; j < steps; j++) {
100           var
101             hint = group[j],
102             last = group[group.length - 1];
103
104           if (last === undefined) {
105             last = null;
106           }
107
108           this.opt.hints[i][j] = hint === undefined ? last : hint;
109         }
110       }
111     },
112
113     _adjustNumber: function() {
114       this.opt.number = methods._between(this.opt.number, 1, this.opt.numberMax);
115     },
116
117     _adjustPath: function() {
118       this.opt.path = this.opt.path || '';
119
120       if (this.opt.path && this.opt.path.charAt(this.opt.path.length - 1) !== '/') {
121         this.opt.path += '/';
122       }
123     },
124
125     _adjustPrecision: function() {
126       this.opt.half = true;
127     },
128
129     _adjustStarType: function() {
130       var replaces = ['cancelOff', 'cancelOn', 'starHalf', 'starOff', 'starOn'];
131
132       this.opt.path = '';
133
134       for (var i = 0; i < replaces.length; i++) {
135         this.opt[replaces[i]] = this.opt[replaces[i]].replace('.', '-');
136       }
137     },
138
139     _apply: function(score) {
140       methods._fill.call(this, score);
141
142       if (score) {
143         if (score > 0) {
144           this.score.val(score);
145         }
146
147         methods._roundStars.call(this, score);
148       }
149     },
150
151     _between: function(value, min, max) {
152       return Math.min(Math.max(parseFloat(value), min), max);
153     },
154
155     _binds: function() {
156       if (this.cancel) {
157         methods._bindOverCancel.call(this);
158         methods._bindClickCancel.call(this);
159         methods._bindOutCancel.call(this);
160       }
161
162       methods._bindOver.call(this);
163       methods._bindClick.call(this);
164       methods._bindOut.call(this);
165     },
166
167     _bindClick: function() {
168       var that = this;
169
170       that.stars.on('click.raty', function(evt) {
171         var
172           execute = true,
173           score   = (that.opt.half || that.opt.precision) ? that.self.data('score') : (this.alt || $(this).data('alt'));
174
175         if (that.opt.click) {
176           execute = that.opt.click.call(that, +score, evt);
177         }
178
179         if (execute || execute === undefined) {
180           if (that.opt.half && !that.opt.precision) {
181             score = methods._roundHalfScore.call(that, score);
182           }
183
184           methods._apply.call(that, score);
185         }
186       });
187     },
188
189     _bindClickCancel: function() {
190       var that = this;
191
192       that.cancel.on('click.raty', function(evt) {
193         that.score.removeAttr('value');
194
195         if (that.opt.click) {
196           that.opt.click.call(that, null, evt);
197         }
198       });
199     },
200
201     _bindOut: function() {
202       var that = this;
203
204       that.self.on('mouseleave.raty', function(evt) {
205         var score = +that.score.val() || undefined;
206
207         methods._apply.call(that, score);
208         methods._target.call(that, score, evt);
209         methods._resetTitle.call(that);
210
211         if (that.opt.mouseout) {
212           that.opt.mouseout.call(that, score, evt);
213         }
214       });
215     },
216
217     _bindOutCancel: function() {
218       var that = this;
219
220       that.cancel.on('mouseleave.raty', function(evt) {
221         var icon = that.opt.cancelOff;
222
223         if (that.opt.starType !== 'img') {
224           icon = that.opt.cancelClass + ' ' + icon;
225         }
226
227         methods._setIcon.call(that, this, icon);
228
229         if (that.opt.mouseout) {
230           var score = +that.score.val() || undefined;
231
232           that.opt.mouseout.call(that, score, evt);
233         }
234       });
235     },
236
237     _bindOver: function() {
238       var that   = this,
239           action = that.opt.half ? 'mousemove.raty' : 'mouseover.raty';
240
241       that.stars.on(action, function(evt) {
242         var score = methods._getScoreByPosition.call(that, evt, this);
243
244         methods._fill.call(that, score);
245
246         if (that.opt.half) {
247           methods._roundStars.call(that, score, evt);
248           methods._setTitle.call(that, score, evt);
249
250           that.self.data('score', score);
251         }
252
253         methods._target.call(that, score, evt);
254
255         if (that.opt.mouseover) {
256           that.opt.mouseover.call(that, score, evt);
257         }
258       });
259     },
260
261     _bindOverCancel: function() {
262       var that = this;
263
264       that.cancel.on('mouseover.raty', function(evt) {
265         var
266           starOff = that.opt.path + that.opt.starOff,
267           icon    = that.opt.cancelOn;
268
269         if (that.opt.starType === 'img') {
270           that.stars.attr('src', starOff);
271         } else {
272           icon = that.opt.cancelClass + ' ' + icon;
273
274           that.stars.attr('class', starOff);
275         }
276
277         methods._setIcon.call(that, this, icon);
278         methods._target.call(that, null, evt);
279
280         if (that.opt.mouseover) {
281           that.opt.mouseover.call(that, null);
282         }
283       });
284     },
285
286     _buildScoreField: function() {
287       return $('<input />', { name: this.opt.scoreName, type: 'hidden' }).appendTo(this);
288     },
289
290     _createCancel: function() {
291       var icon   = this.opt.path + this.opt.cancelOff,
292           cancel = $('<' + this.opt.starType + ' />', { title: this.opt.cancelHint, 'class': this.opt.cancelClass });
293
294       if (this.opt.starType === 'img') {
295         cancel.attr({ src: icon, alt: 'x' });
296       } else {
297         // TODO: use $.data
298         cancel.attr('data-alt', 'x').addClass(icon);
299       }
300
301       if (this.opt.cancelPlace === 'left') {
302         this.self.prepend('&#160;').prepend(cancel);
303       } else {
304         this.self.append('&#160;').append(cancel);
305       }
306
307       this.cancel = cancel;
308     },
309
310     _createScore: function() {
311       var score = $(this.opt.targetScore);
312
313       this.score = score.length ? score : methods._buildScoreField.call(this);
314     },
315
316     _createStars: function() {
317       for (var i = 1; i <= this.opt.number; i++) {
318         var
319           name  = methods._nameForIndex.call(this, i),
320           attrs = { alt: i, src: this.opt.path + this.opt[name] };
321
322         if (this.opt.starType !== 'img') {
323           attrs = { 'data-alt': i, 'class': attrs.src }; // TODO: use $.data.
324         }
325
326         attrs.title = methods._getHint.call(this, i);
327
328         $('<' + this.opt.starType + ' />', attrs).appendTo(this);
329
330         if (this.opt.space) {
331           this.self.append(i < this.opt.number ? '&#160;' : '');
332         }
333       }
334
335       this.stars = this.self.children(this.opt.starType);
336     },
337
338     _error: function(message) {
339       $(this).text(message);
340
341       $.error(message);
342     },
343
344     _fill: function(score) {
345       var hash = 0;
346
347       for (var i = 1; i <= this.stars.length; i++) {
348         var
349           icon,
350           star   = this.stars[i - 1],
351           turnOn = methods._turnOn.call(this, i, score);
352
353         if (this.opt.iconRange && this.opt.iconRange.length > hash) {
354           var irange = this.opt.iconRange[hash];
355
356           icon = methods._getRangeIcon.call(this, irange, turnOn);
357
358           if (i <= irange.range) {
359             methods._setIcon.call(this, star, icon);
360           }
361
362           if (i === irange.range) {
363             hash++;
364           }
365         } else {
366           icon = this.opt[turnOn ? 'starOn' : 'starOff'];
367
368           methods._setIcon.call(this, star, icon);
369         }
370       }
371     },
372
373     _getFirstDecimal: function(number) {
374       var
375         decimal = number.toString().split('.')[1],
376         result  = 0;
377
378       if (decimal) {
379         result = parseInt(decimal.charAt(0), 10);
380
381         if (decimal.slice(1, 5) === '9999') {
382           result++;
383         }
384       }
385
386       return result;
387     },
388
389     _getRangeIcon: function(irange, turnOn) {
390       return turnOn ? irange.on || this.opt.starOn : irange.off || this.opt.starOff;
391     },
392
393     _getScoreByPosition: function(evt, icon) {
394       var score = parseInt(icon.alt || icon.getAttribute('data-alt'), 10);
395
396       if (this.opt.half) {
397         var
398           size    = methods._getWidth.call(this),
399           percent = parseFloat((evt.pageX - $(icon).offset().left) / size);
400
401         score = score - 1 + percent;
402       }
403
404       return score;
405     },
406
407     _getHint: function(score, evt) {
408       if (score !== 0 && !score) {
409         return this.opt.noRatedMsg;
410       }
411
412       var
413         decimal = methods._getFirstDecimal.call(this, score),
414         integer = Math.ceil(score),
415         group   = this.opt.hints[(integer || 1) - 1],
416         hint    = group,
417         set     = !evt || this.move;
418
419       if (this.opt.precision) {
420         if (set) {
421           decimal = decimal === 0 ? 9 : decimal - 1;
422         }
423
424         hint = group[decimal];
425       } else if (this.opt.halfShow || this.opt.half) {
426         decimal = set && decimal === 0 ? 1 : decimal > 5 ? 1 : 0;
427
428         hint = group[decimal];
429       }
430
431       return hint === '' ? '' : hint || score;
432     },
433
434     _getWidth: function() {
435       var width = this.stars[0].width || parseFloat(this.stars.eq(0).css('font-size'));
436
437       if (!width) {
438         methods._error.call(this, 'Could not get the icon width!');
439       }
440
441       return width;
442     },
443
444     _lock: function() {
445       var hint = methods._getHint.call(this, this.score.val());
446
447       this.style.cursor = '';
448       this.title        = hint;
449
450       this.score.prop('readonly', true);
451       this.stars.prop('title', hint);
452
453       if (this.cancel) {
454         this.cancel.hide();
455       }
456
457       this.self.data('readonly', true);
458     },
459
460     _nameForIndex: function(i) {
461       return this.opt.score && this.opt.score >= i ? 'starOn' : 'starOff';
462     },
463
464     _resetTitle: function(star) {
465       for (var i = 0; i < this.opt.number; i++) {
466         this.stars[i].title = methods._getHint.call(this, i + 1);
467       }
468     },
469
470      _roundHalfScore: function(score) {
471       var integer = parseInt(score, 10),
472           decimal = methods._getFirstDecimal.call(this, score);
473
474       if (decimal !== 0) {
475         decimal = decimal > 5 ? 1 : 0.5;
476       }
477
478       return integer + decimal;
479     },
480
481     _roundStars: function(score, evt) {
482       var
483         decimal = (score % 1).toFixed(2),
484         name    ;
485
486       if (evt || this.move) {
487         name = decimal > 0.5 ? 'starOn' : 'starHalf';
488       } else if (decimal > this.opt.round.down) {               // Up:   [x.76 .. x.99]
489         name = 'starOn';
490
491         if (this.opt.halfShow && decimal < this.opt.round.up) { // Half: [x.26 .. x.75]
492           name = 'starHalf';
493         } else if (decimal < this.opt.round.full) {             // Down: [x.00 .. x.5]
494           name = 'starOff';
495         }
496       }
497
498       if (name) {
499         var
500           icon = this.opt[name],
501           star = this.stars[Math.ceil(score) - 1];
502
503         methods._setIcon.call(this, star, icon);
504       }                                                         // Full down: [x.00 .. x.25]
505     },
506
507     _setIcon: function(star, icon) {
508       try {//ACE
509         star[this.opt.starType === 'img' ? 'src' : 'className'] = this.opt.path + icon;
510       } catch(e) {}
511     },
512
513     _setTarget: function(target, score) {
514       if (score) {
515         score = this.opt.targetFormat.toString().replace('{score}', score);
516       }
517
518       if (target.is(':input')) {
519         target.val(score);
520       } else {
521         target.html(score);
522       }
523     },
524
525     _setTitle: function(score, evt) {
526       if (score) {
527         var
528           integer = parseInt(Math.ceil(score), 10),
529           star    = this.stars[integer - 1];
530
531         star.title = methods._getHint.call(this, score, evt);
532       }
533     },
534
535     _target: function(score, evt) {
536       if (this.opt.target) {
537         var target = $(this.opt.target);
538
539         if (!target.length) {
540           methods._error.call(this, 'Target selector invalid or missing!');
541         }
542
543         var mouseover = evt && evt.type === 'mouseover';
544
545         if (score === undefined) {
546           score = this.opt.targetText;
547         } else if (score === null) {
548           score = mouseover ? this.opt.cancelHint : this.opt.targetText;
549         } else {
550           if (this.opt.targetType === 'hint') {
551             score = methods._getHint.call(this, score, evt);
552           } else if (this.opt.precision) {
553             score = parseFloat(score).toFixed(1);
554           }
555
556           var mousemove = evt && evt.type === 'mousemove';
557
558           if (!mouseover && !mousemove && !this.opt.targetKeep) {
559             score = this.opt.targetText;
560           }
561         }
562
563         methods._setTarget.call(this, target, score);
564       }
565     },
566
567     _turnOn: function(i, score) {
568       return this.opt.single ? (i === score) : (i <= score);
569     },
570
571     _unlock: function() {
572       this.style.cursor = 'pointer';
573       this.removeAttribute('title');
574
575       this.score.removeAttr('readonly');
576
577       this.self.data('readonly', false);
578
579       for (var i = 0; i < this.opt.number; i++) {
580         this.stars[i].title = methods._getHint.call(this, i + 1);
581       }
582
583       if (this.cancel) {
584         this.cancel.css('display', '');
585       }
586     },
587
588     cancel: function(click) {
589       return this.each(function() {
590         var self = $(this);
591
592         if (self.data('readonly') !== true) {
593           methods[click ? 'click' : 'score'].call(self, null);
594
595           this.score.removeAttr('value');
596         }
597       });
598     },
599
600     click: function(score) {
601       return this.each(function() {
602         if ($(this).data('readonly') !== true) {
603           score = methods._adjustedScore.call(this, score);
604
605           methods._apply.call(this, score);
606
607           if (this.opt.click) {
608             this.opt.click.call(this, score, $.Event('click'));
609           }
610
611           methods._target.call(this, score);
612         }
613       });
614     },
615
616     destroy: function() {
617       return this.each(function() {
618         var self = $(this),
619             raw  = self.data('raw');
620
621         if (raw) {
622           self.off('.raty').empty().css({ cursor: raw.style.cursor }).removeData('readonly');
623         } else {
624           self.data('raw', self.clone()[0]);
625         }
626       });
627     },
628
629     getScore: function() {
630       var score = [],
631           value ;
632
633       this.each(function() {
634         value = this.score.val();
635
636         score.push(value ? +value : undefined);
637       });
638
639       return (score.length > 1) ? score : score[0];
640     },
641
642     move: function(score) {
643       return this.each(function() {
644         var
645           integer  = parseInt(score, 10),
646           decimal  = methods._getFirstDecimal.call(this, score);
647
648         if (integer >= this.opt.number) {
649           integer = this.opt.number - 1;
650           decimal = 10;
651         }
652
653         var
654           width   = methods._getWidth.call(this),
655           steps   = width / 10,
656           star    = $(this.stars[integer]),
657           percent = star.offset().left + steps * decimal,
658           evt     = $.Event('mousemove', { pageX: percent });
659
660         this.move = true;
661
662         star.trigger(evt);
663
664         this.move = false;
665       });
666     },
667
668     readOnly: function(readonly) {
669       return this.each(function() {
670         var self = $(this);
671
672         if (self.data('readonly') !== readonly) {
673           if (readonly) {
674             self.off('.raty').children('img').off('.raty');
675
676             methods._lock.call(this);
677           } else {
678             methods._binds.call(this);
679             methods._unlock.call(this);
680           }
681
682           self.data('readonly', readonly);
683         }
684       });
685     },
686
687     reload: function() {
688       return methods.set.call(this, {});
689     },
690
691     score: function() {
692       var self = $(this);
693
694       return arguments.length ? methods.setScore.apply(self, arguments) : methods.getScore.call(self);
695     },
696
697     set: function(options) {
698       return this.each(function() {
699         $(this).raty($.extend({}, this.opt, options));
700       });
701     },
702
703     setScore: function(score) {
704       return this.each(function() {
705         if ($(this).data('readonly') !== true) {
706           score = methods._adjustedScore.call(this, score);
707
708           methods._apply.call(this, score);
709           methods._target.call(this, score);
710         }
711       });
712     }
713   };
714
715   $.fn.raty = function(method) {
716     if (methods[method]) {
717       return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
718     } else if (typeof method === 'object' || !method) {
719       return methods.init.apply(this, arguments);
720     } else {
721       $.error('Method ' + method + ' does not exist!');
722     }
723   };
724
725   $.fn.raty.defaults = {
726     cancel       : false,
727     cancelClass  : 'raty-cancel',
728     cancelHint   : 'Cancel this rating!',
729     cancelOff    : 'cancel-off.png',
730     cancelOn     : 'cancel-on.png',
731     cancelPlace  : 'left',
732     click        : undefined,
733     half         : false,
734     halfShow     : true,
735     hints        : ['bad', 'poor', 'regular', 'good', 'gorgeous'],
736     iconRange    : undefined,
737     mouseout     : undefined,
738     mouseover    : undefined,
739     noRatedMsg   : 'Not rated yet!',
740     number       : 5,
741     numberMax    : 20,
742     path         : undefined,
743     precision    : false,
744     readOnly     : false,
745     round        : { down: 0.25, full: 0.6, up: 0.76 },
746     score        : undefined,
747     scoreName    : 'score',
748     single       : false,
749     space        : true,
750     starHalf     : 'star-half.png',
751     starOff      : 'star-off.png',
752     starOn       : 'star-on.png',
753     starType     : 'img',
754     target       : undefined,
755     targetFormat : '{score}',
756     targetKeep   : false,
757     targetScore  : undefined,
758     targetText   : '',
759     targetType   : 'hint'
760   };
761
762 })(jQuery);