/*! * Raty - A Star Rating Plugin * * The MIT License * * author: Washington Botelho * github: wbotelhos/raty * version: 3.1.1 * */ (function(root, factory) { if (typeof define === 'function' && define.amd) { define(['jquery'], factory); } else if (typeof module === 'object' && module.exports) { module.exports = factory(require('jquery')); } else { factory(root.jQuery); } }(this, function($) { 'use strict'; $.raty = { cancelButton: false, cancelClass: 'raty-cancel', cancelHint: 'Cancel this rating!', cancelOff: 'cancel-off.png', cancelOn: 'cancel-on.png', cancelPlace: 'left', click: undefined, half: false, halfShow: true, hints: ['bad', 'poor', 'regular', 'good', 'gorgeous'], iconRange: undefined, iconRangeSame: false, mouseout: undefined, mouseover: undefined, noRatedMsg: 'Not rated yet!', number: 5, numberMax: 20, path: undefined, precision: false, readOnly: false, round: { down: 0.25, full: 0.6, up: 0.76 }, score: undefined, scoreName: 'score', single: false, space: true, starHalf: 'star-half.png', starOff: 'star-off.png', starOn: 'star-on.png', starType: 'img', target: undefined, targetFormat: '{score}', targetKeep: false, targetScore: undefined, targetText: '', targetType: 'hint' }; $.fn.raty = function(options) { return this.each(function() { var instance = new $.raty.Raty(this, options); return instance._create(); }); }; $.raty.Raty = (function() { var Raty = function(element, options) { this.element = element; this.self = $(element); this.opt = $.extend(true, {}, $.raty, options, this.self.data()); }; Raty.prototype = { _create: function() { this._executeCallbacks(); this._adjustNumber(); this._adjustHints(); this.opt.score = this._adjustedScore(this.opt.score); if (this.opt.starType !== 'img') { this._adjustStarName(); } this._setPath(); this._createStars(); if (this.opt.cancelButton) { this._createCancel(); } if (this.opt.precision) { this._adjustPrecision(); } this._createScore(); this._apply(this.opt.score); this._setTitle(this.opt.score); this._target(this.opt.score); if (this.opt.readOnly) { this._lock(); } else { this.element.style.cursor = 'pointer'; this._binds(); } this.self.data('raty', this); }, // TODO: model spec _adjustedScore: function(score) { if (score || score === 0) { return this._between(score, 0, this.opt.number); } }, _adjustHints: function() { if (!this.opt.hints) { this.opt.hints = []; } if (!this.opt.halfShow && !this.opt.half) { return; } var steps = this.opt.precision ? 10 : 2; for (var i = 0; i < this.opt.number; i++) { var group = this.opt.hints[i]; if (Object.prototype.toString.call(group) !== '[object Array]') { group = [group]; } this.opt.hints[i] = []; for (var j = 0; j < steps; j++) { var hint = group[j]; var last = group[group.length - 1]; if (last === undefined) { last = null; } this.opt.hints[i][j] = hint === undefined ? last : hint; } } }, _adjustNumber: function() { this.opt.number = this._between(this.opt.number, 1, this.opt.numberMax); }, _adjustPrecision: function() { this.opt.half = true; }, _adjustStarName: function() { var replaces = ['cancelOff', 'cancelOn', 'starHalf', 'starOff', 'starOn']; this.opt.path = ''; for (var i = 0; i < replaces.length; i++) { this.opt[replaces[i]] = this.opt[replaces[i]].replace('.', '-'); } }, // TODO: model spec _apply: function(score) { this._fill(score); if (score) { if (score > 0) { this.scoreField.val(score); } this._roundStars(score); } }, _attributesForIndex: function(i) { var name = this._nameForIndex(i); var attributes = { alt: i, src: this.opt.path + this.opt[name] }; if (this.opt.starType !== 'img') { attributes = { 'data-alt': i, 'class': this.opt[name] }; } attributes.title = this._getHint(i); return attributes; }, _between: function(value, min, max) { return Math.min(Math.max(parseFloat(value), min), max); }, // TODO: model spec _binds: function() { if (this.cancelButton) { this._bindOverCancel(); this._bindClickCancel(); this._bindOutCancel(); } this._bindOver(); this._bindClick(); this._bindOut(); }, // TODO: model spec _bindClick: function() { var that = this; this.stars.on('click.raty', function(evt) { if (that.self.data('readonly')) { return; } var execute = true; var score = (that.opt.half || that.opt.precision) ? that.self.data('score') : (this.alt || $(this).data('alt')); if (that.opt.half && !that.opt.precision) { score = that._roundHalfScore(score); } if (that.opt.click) { execute = that.opt.click.call(that.element, +score, evt); } if (execute || execute === undefined) { that._apply(+score); } }); }, // TODO: model spec _bindClickCancel: function() { this.cancelButton.on('click.raty', function(evt) { this.scoreField.removeAttr('value'); if (this.opt.click) { this.opt.click.call(this.element, null, evt); } }.bind(this)); }, // TODO: model spec _bindOut: function() { this.self.on('mouseleave.raty', function(evt) { var score = +this.scoreField.val() || undefined; this._apply(score); this._target(score, evt); this._resetTitle(); if (this.opt.mouseout) { this.opt.mouseout.call(this.element, score, evt); } }.bind(this)); }, // TODO: model spec _bindOutCancel: function() { var that = this; this.cancelButton.on('mouseleave.raty', function(evt) { var icon = that.opt.cancelOff; if (that.opt.starType !== 'img') { icon = that.opt.cancelClass + ' ' + icon; } that._setIcon(this, icon); if (that.opt.mouseout) { var score = +that.scoreField.val() || undefined; that.opt.mouseout.call(that.element, score, evt); } }); }, // TODO: model spec _bindOver: function() { var that = this; var action = that.opt.half ? 'mousemove.raty' : 'mouseover.raty'; this.stars.on(action, function(evt) { var score = that._getScoreByPosition(evt, this); that._fill(score); if (that.opt.half) { that._roundStars(score, evt); that._setTitle(score, evt); that.self.data('score', score); } that._target(score, evt); if (that.opt.mouseover) { that.opt.mouseover.call(that.element, score, evt); } }); }, // TODO: model spec _bindOverCancel: function() { var that = this; this.cancelButton.on('mouseover.raty', function(evt) { if (that.self.data('readonly')) { return; } var starOff = that.opt.path + that.opt.starOff; var icon = that.opt.cancelOn; if (that.opt.starType === 'img') { that.stars.attr('src', starOff); } else { icon = that.opt.cancelClass + ' ' + icon; that.stars.attr('class', starOff); } that._setIcon(this, icon); that._target(null, evt); if (that.opt.mouseover) { that.opt.mouseover.call(that.element, null); } }); }, // TODO: model spec _buildScoreField: function() { return $('', { name: this.opt.scoreName, type: 'hidden' }).appendTo(this.self); }, // TODO: model spec _createCancel: function() { var icon = this.opt.path + this.opt.cancelOff; var button = $('<' + this.opt.starType + ' />', { title: this.opt.cancelHint, 'class': this.opt.cancelClass }); if (this.opt.starType === 'img') { button.attr({ src: icon, alt: 'x' }); } else { // TODO: use $.data button.attr('data-alt', 'x').addClass(icon); } if (this.opt.cancelPlace === 'left') { this.self.prepend(' ').prepend(button); } else { this.self.append(' ').append(button); } this.cancelButton = button; }, // TODO: model spec _createScore: function() { var score = $(this.opt.targetScore); this.scoreField = score.length ? score : this._buildScoreField(); }, _createStars: function() { for (var i = 1; i <= this.opt.number; i++) { var attributes = this._attributesForIndex(i); $('<' + this.opt.starType + ' />', attributes).appendTo(this.element); if (this.opt.space && i < this.opt.number) { this.self.append(' '); } } this.stars = this.self.children(this.opt.starType); }, // TODO: model spec _error: function(message) { $(this).text(message); $.error(message); }, _executeCallbacks: function() { var options = ['number', 'readOnly', 'score', 'scoreName', 'target', 'path']; for (var i = 0; i < options.length; i++) { if (typeof this.opt[options[i]] === 'function') { var value = this.opt[options[i]].call(this.element); if (value) { this.opt[options[i]] = value; } else { delete this.opt[options[i]]; } } } }, // TODO: model spec _fill: function(score) { var hash = 0; if (this.opt.iconRangeSame && this.opt.iconRange) { while (hash < this.opt.iconRange.length && this.opt.iconRange[hash].range < score) { hash++; } } for (var i = 1; i <= this.stars.length; i++) { var icon; var star = this.stars[i - 1]; var turnOn = this._turnOn(i, score); if (this.opt.iconRange && this.opt.iconRange.length > hash) { var irange = this.opt.iconRange[hash]; icon = this._getRangeIcon(irange, turnOn); if (i <= irange.range) { this._setIcon(star, icon); } if (i === irange.range) { hash++; } } else { icon = this.opt[turnOn ? 'starOn' : 'starOff']; this._setIcon(star, icon); } } }, _getDecimal: function(number, fractions) { var decimal = number.toString().split('.')[1]; var result = 0; if (decimal) { result = parseInt(decimal.slice(0, fractions), 10); if (decimal.slice(1, 5) === '9999') { result++; } } return result; }, // TODO: model spec _getRangeIcon: function(irange, turnOn) { return turnOn ? irange.on || this.opt.starOn : irange.off || this.opt.starOff; }, // TODO: model spec _getScoreByPosition: function(evt, icon) { var score = parseInt(icon.alt || icon.getAttribute('data-alt'), 10); if (this.opt.half) { var size = this._getWidth(); var percent = parseFloat((evt.pageX - $(icon).offset().left) / size); score = score - 1 + percent; } return score; }, // TODO: model spec _getHint: function(score, evt) { if (score !== 0 && !score) { return this.opt.noRatedMsg; } var decimal = this._getDecimal(score, 1); var integer = Math.ceil(score); var group = this.opt.hints[(integer || 1) - 1]; var hint = group; var set = !evt || this.isMove; if (this.opt.precision) { if (set) { decimal = decimal === 0 ? 9 : decimal - 1; } hint = group[decimal]; } else if (this.opt.halfShow || this.opt.half) { decimal = set && decimal === 0 ? 1 : decimal > 5 ? 1 : 0; hint = group[decimal]; } return hint === '' ? '' : hint || score; }, // TODO: model spec _getWidth: function() { var width = this.stars[0].width || parseFloat(this.stars.eq(0).css('font-size')); if (!width) { this._error('Could not get the icon width!'); } return width; }, // TODO: model spec _lock: function() { var hint = this._getHint(this.scoreField.val()); this.element.style.cursor = ''; this.element.title = hint; this.scoreField.prop('readonly', true); this.stars.prop('title', hint); if (this.cancelButton) { this.cancelButton.hide(); } this.self.data('readonly', true); }, _nameForIndex: function(i) { return this.opt.score && this.opt.score >= i ? 'starOn' : 'starOff'; }, // TODO: model spec _resetTitle: function() { for (var i = 0; i < this.opt.number; i++) { this.stars[i].title = this._getHint(i + 1); } }, // TODO: model spec _roundHalfScore: function(score) { var integer = parseInt(score, 10); var decimal = this._getDecimal(score, 1); if (decimal !== 0) { decimal = decimal > 5 ? 1 : 0.5; } return integer + decimal; }, // TODO: model spec _roundStars: function(score, evt) { var name = this._starName(score, evt); if (name) { var icon = this.opt[name]; var star = this.stars[Math.ceil(score) - 1]; this._setIcon(star, icon); } // Full down: [x.00 .. x.25] }, // TODO: model spec _setIcon: function(star, icon) { star[this.opt.starType === 'img' ? 'src' : 'className'] = this.opt.path + icon; }, _setPath: function() { this.opt.path = this.opt.path || ''; if (this.opt.path && this.opt.path.slice(-1)[0] !== '/') { this.opt.path += '/'; } }, // TODO: model spec _setTarget: function(target, score) { if (score) { score = this.opt.targetFormat.toString().replace('{score}', score); } if (target.is(':input')) { target.val(score); } else { target.html(score); } }, // TODO: model spec _setTitle: function(score, evt) { if (score) { var integer = parseInt(Math.ceil(score), 10); var star = this.stars[integer - 1]; star.title = this._getHint(score, evt); } }, _starName: function(score, evt) { var decimal = +(score % 1).toFixed(2); if (evt || this.isMove) { return decimal > 0.5 ? 'starOn' : 'starHalf'; } if (decimal <= this.opt.round.down) { // Down: [x.00 ... x.25] return; } if (this.opt.halfShow && decimal < this.opt.round.up) { // Half: [x.26 ... x.75] return 'starHalf'; } if (decimal < this.opt.round.full) { // Off: [x.26 .. x.6] return 'starOff'; } return 'starOn'; // Up: [x.26 ...] || [x.6 ...] }, // TODO: model spec _target: function(score, evt) { if (this.opt.target) { var target = $(this.opt.target); if (!target.length) { this._error('Target selector invalid or missing!'); } var mouseover = evt && evt.type === 'mouseover'; if (score === undefined) { score = this.opt.targetText; } else if (score === null) { score = mouseover ? this.opt.cancelHint : this.opt.targetText; } else { if (this.opt.targetType === 'hint') { score = this._getHint(score, evt); } else if (this.opt.precision) { score = parseFloat(score).toFixed(1); } var mousemove = evt && evt.type === 'mousemove'; if (!mouseover && !mousemove && !this.opt.targetKeep) { score = this.opt.targetText; } } this._setTarget(target, score); } }, // TODO: model spec _turnOn: function(i, score) { return this.opt.single ? (i === score) : (i <= score); }, // TODO: model spec _unlock: function() { this.element.style.cursor = 'pointer'; this.element.removeAttribute('title'); this.scoreField.removeAttr('readonly'); this.self.data('readonly', false); this._resetTitle(); if (this.cancelButton) { this.cancelButton.css('display', ''); } }, // TODO: model spec cancel: function(click) { if (this.self.data('readonly') !== true) { this[click ? 'click' : 'score'].call(this, null); this.scoreField.removeAttr('value'); } }, // TODO: model spec click: function(score) { if (this.self.data('readonly') !== true) { score = this._adjustedScore(score); this._apply(score); if (this.opt.click) { this.opt.click.call(this.element, score, $.Event('click')); } this._target(score); } }, // TODO: model spec getScore: function() { var score = []; var value ; value = this.scoreField.val(); score.push(value ? +value : undefined); return (score.length > 1) ? score : score[0]; }, // TODO: model spec move: function(score) { var integer = parseInt(score, 10); var decimal = this._getDecimal(score, 1); if (integer >= this.opt.number) { integer = this.opt.number - 1; decimal = 10; } var width = this._getWidth(); var steps = width / 10; var star = $(this.stars[integer]); var percent = star.offset().left + steps * decimal; var evt = $.Event('mousemove', { pageX: percent }); this.isMove = true; star.trigger(evt); this.isMove = false; }, // TODO: model spec readOnly: function(readonly) { if (this.self.data('readonly') !== readonly) { if (readonly) { this.self.off('.raty').children(this.opt.starType).off('.raty'); this._lock(); } else { this._binds(); this._unlock(); } this.self.data('readonly', readonly); } }, // TODO: model spec score: function() { return arguments.length ? this.setScore.apply(this, arguments) : this.getScore(); }, setScore: function(score) { if (this.self.data('readonly') !== true) { score = this._adjustedScore(score); this._apply(score); this._target(score); } } }; return Raty; })(); }));