(function (factory){
"use strict";
if(typeof define==='function'&&define.amd){
define(['jquery'], factory);
}else{
if(typeof module==='object'&&module.exports){
module.exports=factory(require('jquery'));
}else{
factory(window.jQuery);
}}
}(function ($){
"use strict";
$.fn.ratingLocales={};
$.fn.ratingThemes={};
var $h, Rating;
$h={
NAMESPACE: '.rating',
DEFAULT_MIN: 0,
DEFAULT_MAX: 5,
DEFAULT_STEP: 0.5,
isEmpty: function (value, trim){
return value===null||value===undefined||value.length===0||(trim&&$.trim(value)==='');
},
getCss: function (condition, css){
return condition ? ' ' + css:'';
},
addCss: function ($el, css){
$el.removeClass(css).addClass(css);
},
getDecimalPlaces: function (num){
var m=('' + num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
return !m ? 0:Math.max(0, (m[1] ? m[1].length:0) - (m[2] ? +m[2]:0));
},
applyPrecision: function (val, precision){
return parseFloat(val.toFixed(precision));
},
handler: function ($el, event, callback, skipOff, skipNS){
var ev=skipNS ? event:event.split(' ').join($h.NAMESPACE + ' ') + $h.NAMESPACE;
if(!skipOff){
$el.off(ev);
}
$el.on(ev, callback);
}};
Rating=function (element, options){
var self=this;
self.$element=$(element);
self._init(options);
};
Rating.prototype={
constructor: Rating,
_parseAttr: function (vattr, options){
var self=this, $el=self.$element, elType=$el.attr('type'), finalVal, val, chk, out;
if(elType==='range'||elType==='number'){
val=options[vattr]||$el.data(vattr)||$el.attr(vattr);
switch (vattr){
case 'min':
chk=$h.DEFAULT_MIN;
break;
case 'max':
chk=$h.DEFAULT_MAX;
break;
default:
chk=$h.DEFAULT_STEP;
}
finalVal=$h.isEmpty(val) ? chk:val;
out=parseFloat(finalVal);
}else{
out=parseFloat(options[vattr]);
}
return isNaN(out) ? chk:out;
},
_parseValue: function (val){
var self=this, v=parseFloat(val);
if(isNaN(v)){
v=self.clearValue;
}
return (self.zeroAsNull&&(v===0||v==='0') ? null:v);
},
_setDefault: function (key, val){
var self=this;
if($h.isEmpty(self[key])){
self[key]=val;
}},
_initSlider: function (options){
var self=this, v=self.$element.val();
self.initialValue=$h.isEmpty(v) ? 0:v;
self._setDefault('min', self._parseAttr('min', options));
self._setDefault('max', self._parseAttr('max', options));
self._setDefault('step', self._parseAttr('step', options));
if(isNaN(self.min)||$h.isEmpty(self.min)){
self.min=$h.DEFAULT_MIN;
}
if(isNaN(self.max)||$h.isEmpty(self.max)){
self.max=$h.DEFAULT_MAX;
}
if(isNaN(self.step)||$h.isEmpty(self.step)||self.step===0){
self.step=$h.DEFAULT_STEP;
}
self.diff=self.max - self.min;
},
_initHighlight: function (v){
var self=this, w, cap=self._getCaption();
if(!v){
v=self.$element.val();
}
w=self.getWidthFromValue(v) + '%';
self.$filledStars.width(w);
self.cache={caption: cap, width: w, val: v};},
_getContainerCss: function (){
var self=this;
return 'rating-container' +
$h.getCss(self.theme, 'theme-' + self.theme) +
$h.getCss(self.rtl, 'rating-rtl') +
$h.getCss(self.size, 'rating-' + self.size) +
$h.getCss(self.animate, 'rating-animate') +
$h.getCss(self.disabled||self.readonly, 'rating-disabled') +
$h.getCss(self.containerClass, self.containerClass);
},
_checkDisabled: function (){
var self=this, $el=self.$element, opts=self.options;
self.disabled=opts.disabled===undefined ? $el.attr('disabled')||false:opts.disabled;
self.readonly=opts.readonly===undefined ? $el.attr('readonly')||false:opts.readonly;
self.inactive=(self.disabled||self.readonly);
$el.attr({disabled: self.disabled, readonly: self.readonly});
},
_addContent: function (type, content){
var self=this, $container=self.$container, isClear=type==='clear';
if(self.rtl){
return isClear ? $container.append(content):$container.prepend(content);
}else{
return isClear ? $container.prepend(content):$container.append(content);
}},
_generateRating: function (){
var self=this, $el=self.$element, $rating, $container, w;
$container=self.$container=$(document.createElement("div")).insertBefore($el);
$h.addCss($container, self._getContainerCss());
self.$rating=$rating=$(document.createElement("div")).attr('class', 'rating-stars').appendTo($container)
.append(self._getStars('empty')).append(self._getStars('filled'));
self.$emptyStars=$rating.find('.empty-stars');
self.$filledStars=$rating.find('.filled-stars');
self._renderCaption();
self._renderClear();
self._initHighlight();
$container.append($el);
if(self.rtl){
w=Math.max(self.$emptyStars.outerWidth(), self.$filledStars.outerWidth());
self.$emptyStars.width(w);
}
$el.appendTo($rating);
},
_getCaption: function (){
var self=this;
return self.$caption&&self.$caption.length ? self.$caption.html():self.defaultCaption;
},
_setCaption: function (content){
var self=this;
if(self.$caption&&self.$caption.length){
self.$caption.html(content);
}},
_renderCaption: function (){
var self=this, val=self.$element.val(), html, $cap=self.captionElement ? $(self.captionElement):'';
if(!self.showCaption){
return;
}
html=self.fetchCaption(val);
if($cap&&$cap.length){
$h.addCss($cap, 'caption');
$cap.html(html);
self.$caption=$cap;
return;
}
self._addContent('caption', '<div class="caption">' + html + '</div>');
self.$caption=self.$container.find(".caption");
},
_renderClear: function (){
var self=this, css, $clr=self.clearElement ? $(self.clearElement):'';
if(!self.showClear){
return;
}
css=self._getClearClass();
if($clr.length){
$h.addCss($clr, css);
$clr.attr({"title": self.clearButtonTitle}).html(self.clearButton);
self.$clear=$clr;
return;
}
self._addContent('clear',
'<div class="' + css + '" title="' + self.clearButtonTitle + '">' + self.clearButton + '</div>');
self.$clear=self.$container.find('.' + self.clearButtonBaseClass);
},
_getClearClass: function (){
var self=this;
return self.clearButtonBaseClass + ' ' + (self.inactive ? '':self.clearButtonActiveClass);
},
_toggleHover: function (out){
var self=this, w, width, caption;
if(!out){
return;
}
if(self.hoverChangeStars){
w=self.getWidthFromValue(self.clearValue);
width=out.val <=self.clearValue ? w + '%':out.width;
self.$filledStars.css('width', width);
}
if(self.hoverChangeCaption){
caption=out.val <=self.clearValue ? self.fetchCaption(self.clearValue):out.caption;
if(caption){
self._setCaption(caption + '');
}}
},
_init: function (options){
var self=this, $el=self.$element.addClass('rating-input'), v;
self.options=options;
$.each(options, function (key, value){
self[key]=value;
});
if(self.rtl||$el.attr('dir')==='rtl'){
self.rtl=true;
$el.attr('dir', 'rtl');
}
self.starClicked=false;
self.clearClicked=false;
self._initSlider(options);
self._checkDisabled();
if(self.displayOnly){
self.inactive=true;
self.showClear=false;
self.showCaption=false;
}
self._generateRating();
self._initEvents();
self._listen();
v=self._parseValue($el.val());
$el.val(v);
return $el.removeClass('rating-loading');
},
_initEvents: function (){
var self=this;
self.events={
_getTouchPosition: function (e){
var pageX=$h.isEmpty(e.pageX) ? e.originalEvent.touches[0].pageX:e.pageX;
return pageX - self.$rating.offset().left;
},
_listenClick: function (e, callback){
e.stopPropagation();
e.preventDefault();
if(e.handled!==true){
callback(e);
e.handled=true;
}else{
return false;
}},
_noMouseAction: function (e){
return !self.hoverEnabled||self.inactive||(e&&e.isDefaultPrevented());
},
initTouch: function (e){
var ev, touches, pos, out, caption, w, width, params, clrVal=self.clearValue||0,
isTouchCapable='ontouchstart' in window ||
(window.DocumentTouch&&document instanceof window.DocumentTouch);
if(!isTouchCapable||self.inactive){
return;
}
ev=e.originalEvent;
touches = !$h.isEmpty(ev.touches) ? ev.touches:ev.changedTouches;
pos=self.events._getTouchPosition(touches[0]);
if(e.type==="touchend"){
self._setStars(pos);
params=[self.$element.val(), self._getCaption()];
self.$element.trigger('change').trigger('rating:change', params);
self.starClicked=true;
}else{
out=self.calculate(pos);
caption=out.val <=clrVal ? self.fetchCaption(clrVal):out.caption;
w=self.getWidthFromValue(clrVal);
width=out.val <=clrVal ? w + '%':out.width;
self._setCaption(caption);
self.$filledStars.css('width', width);
}},
starClick: function (e){
var pos, params;
self.events._listenClick(e, function (ev){
if(self.inactive){
return false;
}
pos=self.events._getTouchPosition(ev);
self._setStars(pos);
params=[self.$element.val(), self._getCaption()];
self.$element.trigger('change').trigger('rating.change', params);
self.starClicked=true;
});
},
clearClick: function (e){
self.events._listenClick(e, function (){
if(!self.inactive){
self.clear();
self.clearClicked=true;
}});
},
starMouseMove: function (e){
var pos, out;
if(self.events._noMouseAction(e)){
return;
}
self.starClicked=false;
pos=self.events._getTouchPosition(e);
out=self.calculate(pos);
self._toggleHover(out);
self.$element.trigger('rating:hover', [out.val, out.caption, 'stars']);
},
starMouseLeave: function (e){
var out;
if(self.events._noMouseAction(e)||self.starClicked){
return;
}
out=self.cache;
self._toggleHover(out);
self.$element.trigger('rating:hoverleave', ['stars']);
},
clearMouseMove: function (e){
var caption, val, width, out;
if(self.events._noMouseAction(e)||!self.hoverOnClear){
return;
}
self.clearClicked=false;
caption='<span class="' + self.clearCaptionClass + '">' + self.clearCaption + '</span>';
val=self.clearValue;
width=self.getWidthFromValue(val)||0;
out={caption: caption, width: width, val: val};
self._toggleHover(out);
self.$element.trigger('rating:hover', [val, caption, 'clear']);
},
clearMouseLeave: function (e){
var out;
if(self.events._noMouseAction(e)||self.clearClicked||!self.hoverOnClear){
return;
}
out=self.cache;
self._toggleHover(out);
self.$element.trigger('rating:hoverleave', ['clear']);
},
resetForm: function (e){
if(e&&e.isDefaultPrevented()){
return;
}
if(!self.inactive){
self.reset();
}}
};},
_listen: function (){
var self=this, $el=self.$element, $form=$el.closest('form'), $rating=self.$rating,
$clear=self.$clear, events=self.events;
$h.handler($rating, 'touchstart touchmove touchend', $.proxy(events.initTouch, self));
$h.handler($rating, 'click touchstart', $.proxy(events.starClick, self));
$h.handler($rating, 'mousemove', $.proxy(events.starMouseMove, self));
$h.handler($rating, 'mouseleave', $.proxy(events.starMouseLeave, self));
if(self.showClear&&$clear.length){
$h.handler($clear, 'click touchstart', $.proxy(events.clearClick, self));
$h.handler($clear, 'mousemove', $.proxy(events.clearMouseMove, self));
$h.handler($clear, 'mouseleave', $.proxy(events.clearMouseLeave, self));
}
if($form.length){
$h.handler($form, 'reset', $.proxy(events.resetForm, self), true);
}
return $el;
},
_getStars: function (type){
var self=this, stars='<span class="' + type + '-stars">', i;
for (i=1; i <=self.stars; i++){
stars +='<span class="star">' + self[type + 'Star'] + '</span>';
}
return stars + '</span>';
},
_setStars: function (pos){
var self=this, out=arguments.length ? self.calculate(pos):self.calculate(), $el=self.$element,
v=self._parseValue(out.val);
$el.val(v);
self.$filledStars.css('width', out.width);
self._setCaption(out.caption);
self.cache=out;
return $el;
},
showStars: function (val){
var self=this, v=self._parseValue(val);
self.$element.val(v);
return self._setStars();
},
calculate: function (pos){
var self=this, defaultVal=$h.isEmpty(self.$element.val()) ? 0:self.$element.val(),
val=arguments.length ? self.getValueFromPosition(pos):defaultVal,
caption=self.fetchCaption(val), width=self.getWidthFromValue(val);
width +='%';
return {caption: caption, width: width, val: val};},
getValueFromPosition: function (pos){
var self=this, precision=$h.getDecimalPlaces(self.step), val, factor, maxWidth=self.$rating.width();
factor=(self.diff * pos) / (maxWidth * self.step);
factor=self.rtl ? Math.floor(factor):Math.ceil(factor);
val=$h.applyPrecision(parseFloat(self.min + factor * self.step), precision);
val=Math.max(Math.min(val, self.max), self.min);
return self.rtl ? (self.max - val):val;
},
getWidthFromValue: function (val){
var self=this, min=self.min, max=self.max, factor, $r=self.$emptyStars, w;
if(!val||val <=min||min===max){
return 0;
}
w=$r.outerWidth();
factor=w ? $r.width() / w:1;
if(val >=max){
return 100;
}
return (val - min) * factor * 100 / (max - min);
},
fetchCaption: function (rating){
var self=this, val=parseFloat(rating)||self.clearValue, css, cap, capVal, cssVal, caption,
vCap=self.starCaptions, vCss=self.starCaptionClasses;
if(val&&val!==self.clearValue){
val=$h.applyPrecision(val, $h.getDecimalPlaces(self.step));
}
cssVal=typeof vCss==="function" ? vCss(val):vCss[val];
capVal=typeof vCap==="function" ? vCap(val):vCap[val];
cap=$h.isEmpty(capVal) ? self.defaultCaption.replace(/\{rating}/g, val):capVal;
css=$h.isEmpty(cssVal) ? self.clearCaptionClass:cssVal;
caption=(val===self.clearValue) ? self.clearCaption:cap;
return '<span class="' + css + '">' + caption + '</span>';
},
destroy: function (){
var self=this, $el=self.$element;
if(!$h.isEmpty(self.$container)){
self.$container.before($el).remove();
}
$.removeData($el.get(0));
return $el.off('rating').removeClass('rating rating-input');
},
create: function (options){
var self=this, opts=options||self.options||{};
return self.destroy().rating(opts);
},
clear: function (){
var self=this, title='<span class="' + self.clearCaptionClass + '">' + self.clearCaption + '</span>';
if(!self.inactive){
self._setCaption(title);
}
return self.showStars(self.clearValue).trigger('change').trigger('rating:clear');
},
reset: function (){
var self=this;
return self.showStars(self.initialValue).trigger('rating:reset');
},
update: function (val){
var self=this;
return arguments.length ? self.showStars(val):self.$element;
},
refresh: function (options){
var self=this, $el=self.$element;
if(!options){
return $el;
}
return self.destroy().rating($.extend(true, self.options, options)).trigger('rating:refresh');
}};
$.fn.rating=function (option){
var args=Array.apply(null, arguments), retvals=[];
args.shift();
this.each(function (){
var self=$(this), data=self.data('rating'), options=typeof option==='object'&&option,
theme=options.theme||self.data('theme'), lang=options.language||self.data('language')||'en',
thm={}, loc={}, opts;
if(!data){
if(theme){
thm=$.fn.ratingThemes[theme]||{};}
if(lang!=='en'&&!$h.isEmpty($.fn.ratingLocales[lang])){
loc=$.fn.ratingLocales[lang];
}
opts=$.extend(true, {}, $.fn.rating.defaults, thm, $.fn.ratingLocales.en, loc, options, self.data());
data=new Rating(this, opts);
self.data('rating', data);
}
if(typeof option==='string'){
retvals.push(data[option].apply(data, args));
}});
switch (retvals.length){
case 0:
return this;
case 1:
return retvals[0]===undefined ? this:retvals[0];
default:
return retvals;
}};
$.fn.rating.defaults={
theme: '',
language: 'en',
stars: 5,
filledStar: '<i class="ion-android-star"></i>',
emptyStar: '<i class="ion-android-star-outline"></i>',
containerClass: '',
size: 'md',
animate: true,
displayOnly: false,
rtl: false,
showClear: true,
showCaption: true,
starCaptionClasses: {
0.5: 'label label-danger badge-danger',
1: 'label label-danger badge-danger',
1.5: 'label label-warning badge-warning',
2: 'label label-warning badge-warning',
2.5: 'label label-info badge-info',
3: 'label label-info badge-info',
3.5: 'label label-primary badge-primary',
4: 'label label-primary badge-primary',
4.5: 'label label-success badge-success',
5: 'label label-success badge-success'
},
clearButton: '<i class="glyphicon glyphicon-minus-sign"></i>',
clearButtonBaseClass: 'clear-rating',
clearButtonActiveClass: 'clear-rating-active',
clearCaptionClass: 'label label-default',
clearValue: null,
captionElement: null,
clearElement: null,
hoverEnabled: true,
hoverChangeCaption: true,
hoverChangeStars: true,
hoverOnClear: true,
zeroAsNull: true
};
$.fn.ratingLocales.en={
defaultCaption: '{rating} Stars',
starCaptions: {
0.5: 'Half Star',
1: 'One Star',
1.5: 'One & Half Star',
2: 'Two Stars',
2.5: 'Two & Half Stars',
3: 'Three Stars',
3.5: 'Three & Half Stars',
4: 'Four Stars',
4.5: 'Four & Half Stars',
5: 'Five Stars'
},
clearButtonTitle: 'Clear',
clearCaption: 'Not Rated'
};
$.fn.rating.Constructor=Rating;
$(document).ready(function (){
var $input=$('input.rating');
if($input.length){
$input.removeClass('rating-loading').addClass('rating-loading').rating();
}});
}));