You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
586 lines
19 KiB
586 lines
19 KiB
1 year ago
|
/**
|
||
|
* jQuery Bar Rating Plugin v1.2.2
|
||
|
*
|
||
|
* http://github.com/antennaio/jquery-bar-rating
|
||
|
*
|
||
|
* Copyright (c) 2012-2016 Kazik Pietruszewski
|
||
|
*
|
||
|
* This plugin is available under the MIT license.
|
||
|
* http://www.opensource.org/licenses/mit-license.php
|
||
|
*/
|
||
|
(function (factory) {
|
||
|
if (typeof define === 'function' && define.amd) {
|
||
|
// AMD
|
||
|
define(['jquery'], factory);
|
||
|
} else if (typeof module === 'object' && module.exports) {
|
||
|
// Node/CommonJS
|
||
|
module.exports = factory(require('jquery'));
|
||
|
} else {
|
||
|
// browser globals
|
||
|
factory(jQuery);
|
||
|
}
|
||
|
}(function ($) {
|
||
|
|
||
|
var BarRating = (function() {
|
||
|
|
||
|
function BarRating() {
|
||
|
var self = this;
|
||
|
|
||
|
// wrap element in a wrapper div
|
||
|
var wrapElement = function() {
|
||
|
var classes = ['br-wrapper'];
|
||
|
|
||
|
if (self.options.theme !== '') {
|
||
|
classes.push('br-theme-' + self.options.theme);
|
||
|
}
|
||
|
|
||
|
self.$elem.wrap($('<div />', {
|
||
|
'class': classes.join(' ')
|
||
|
}));
|
||
|
};
|
||
|
|
||
|
// unwrap element
|
||
|
var unwrapElement = function() {
|
||
|
self.$elem.unwrap();
|
||
|
};
|
||
|
|
||
|
// find option by value
|
||
|
var findOption = function(value) {
|
||
|
if ($.isNumeric(value)) {
|
||
|
value = Math.floor(value);
|
||
|
}
|
||
|
|
||
|
return $('option[value="' + value + '"]', self.$elem);
|
||
|
};
|
||
|
|
||
|
// get initial option
|
||
|
var getInitialOption = function() {
|
||
|
var initialRating = self.options.initialRating;
|
||
|
|
||
|
if (!initialRating) {
|
||
|
return $('option:selected', self.$elem);
|
||
|
}
|
||
|
|
||
|
return findOption(initialRating);
|
||
|
};
|
||
|
|
||
|
// get empty option
|
||
|
var getEmptyOption = function() {
|
||
|
var $emptyOpt = self.$elem.find('option[value="' + self.options.emptyValue + '"]');
|
||
|
|
||
|
if (!$emptyOpt.length && self.options.allowEmpty) {
|
||
|
$emptyOpt = $('<option />', { 'value': self.options.emptyValue });
|
||
|
|
||
|
return $emptyOpt.prependTo(self.$elem);
|
||
|
}
|
||
|
|
||
|
return $emptyOpt;
|
||
|
};
|
||
|
|
||
|
// get data
|
||
|
var getData = function(key) {
|
||
|
var data = self.$elem.data('barrating');
|
||
|
|
||
|
if (typeof key !== 'undefined') {
|
||
|
return data[key];
|
||
|
}
|
||
|
|
||
|
return data;
|
||
|
};
|
||
|
|
||
|
// set data
|
||
|
var setData = function(key, value) {
|
||
|
if (value !== null && typeof value === 'object') {
|
||
|
self.$elem.data('barrating', value);
|
||
|
} else {
|
||
|
self.$elem.data('barrating')[key] = value;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// save data on element
|
||
|
var saveDataOnElement = function() {
|
||
|
var $opt = getInitialOption();
|
||
|
var $emptyOpt = getEmptyOption();
|
||
|
|
||
|
var value = $opt.val();
|
||
|
var text = $opt.data('html') ? $opt.data('html') : $opt.text();
|
||
|
|
||
|
// if the allowEmpty option is not set let's check if empty option exists in the select field
|
||
|
var allowEmpty = (self.options.allowEmpty !== null) ?
|
||
|
self.options.allowEmpty :
|
||
|
!!$emptyOpt.length;
|
||
|
|
||
|
var emptyValue = ($emptyOpt.length) ? $emptyOpt.val() : null;
|
||
|
var emptyText = ($emptyOpt.length) ? $emptyOpt.text() : null;
|
||
|
|
||
|
setData(null, {
|
||
|
userOptions: self.options,
|
||
|
|
||
|
// initial rating based on the OPTION value
|
||
|
ratingValue: value,
|
||
|
ratingText: text,
|
||
|
|
||
|
// rating will be restored by calling clear method
|
||
|
originalRatingValue: value,
|
||
|
originalRatingText: text,
|
||
|
|
||
|
// allow empty ratings?
|
||
|
allowEmpty: allowEmpty,
|
||
|
|
||
|
// rating value and text of the empty OPTION
|
||
|
emptyRatingValue: emptyValue,
|
||
|
emptyRatingText: emptyText,
|
||
|
|
||
|
// read-only state
|
||
|
readOnly: self.options.readonly,
|
||
|
|
||
|
// did the user already select a rating?
|
||
|
ratingMade: false
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// remove data on element
|
||
|
var removeDataOnElement = function() {
|
||
|
self.$elem.removeData('barrating');
|
||
|
};
|
||
|
|
||
|
// return current rating text
|
||
|
var ratingText = function() {
|
||
|
return getData('ratingText');
|
||
|
};
|
||
|
|
||
|
// return current rating value
|
||
|
var ratingValue = function() {
|
||
|
return getData('ratingValue');
|
||
|
};
|
||
|
|
||
|
// build widget and return jQuery element
|
||
|
var buildWidget = function() {
|
||
|
var $w = $('<div />', { 'class': 'br-widget' });
|
||
|
|
||
|
// create A elements that will replace OPTIONs
|
||
|
self.$elem.find('option').each(function() {
|
||
|
var val, text, html, $a;
|
||
|
|
||
|
val = $(this).val();
|
||
|
|
||
|
// create ratings - but only if val is not defined as empty
|
||
|
if (val !== getData('emptyRatingValue')) {
|
||
|
text = $(this).text();
|
||
|
html = $(this).data('html');
|
||
|
if (html) { text = html; }
|
||
|
|
||
|
$a = $('<a />', {
|
||
|
'href': '#',
|
||
|
'data-rating-value': val,
|
||
|
'data-rating-text': text,
|
||
|
'html': (self.options.showValues) ? text : ''
|
||
|
});
|
||
|
|
||
|
$w.append($a);
|
||
|
}
|
||
|
|
||
|
});
|
||
|
|
||
|
// append .br-current-rating div to the widget
|
||
|
if (self.options.showSelectedRating) {
|
||
|
$w.append($('<div />', { 'text': '', 'class': 'br-current-rating' }));
|
||
|
}
|
||
|
|
||
|
// additional classes for the widget
|
||
|
if (self.options.reverse) {
|
||
|
$w.addClass('br-reverse');
|
||
|
}
|
||
|
|
||
|
if (self.options.readonly) {
|
||
|
$w.addClass('br-readonly');
|
||
|
}
|
||
|
|
||
|
return $w;
|
||
|
};
|
||
|
|
||
|
// return a jQuery function name depending on the 'reverse' setting
|
||
|
var nextAllorPreviousAll = function() {
|
||
|
if (getData('userOptions').reverse) {
|
||
|
return 'nextAll';
|
||
|
} else {
|
||
|
return 'prevAll';
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// set the value of the select field
|
||
|
var setSelectFieldValue = function(value) {
|
||
|
// change selected option
|
||
|
findOption(value).prop('selected', true);
|
||
|
|
||
|
self.$elem.change();
|
||
|
};
|
||
|
|
||
|
// reset select field
|
||
|
var resetSelectField = function() {
|
||
|
$('option', self.$elem).prop('selected', function() {
|
||
|
return this.defaultSelected;
|
||
|
});
|
||
|
|
||
|
self.$elem.change();
|
||
|
};
|
||
|
|
||
|
// display the currently selected rating
|
||
|
var showSelectedRating = function(text) {
|
||
|
// text undefined?
|
||
|
text = text ? text : ratingText();
|
||
|
|
||
|
// special case when the selected rating is defined as empty
|
||
|
if (text == getData('emptyRatingText')) {
|
||
|
text = '';
|
||
|
}
|
||
|
|
||
|
// update .br-current-rating div
|
||
|
if (self.options.showSelectedRating) {
|
||
|
self.$elem.parent().find('.br-current-rating').text(text);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// return rounded fraction of a value (14.4 -> 40, 0.99 -> 90)
|
||
|
var fraction = function(value) {
|
||
|
return Math.round(((Math.floor(value * 10) / 10) % 1) * 100);
|
||
|
};
|
||
|
|
||
|
// remove all classes from elements
|
||
|
var resetStyle = function() {
|
||
|
// remove all classes starting with br-*
|
||
|
self.$widget.find('a').removeClass(function(index, classes) {
|
||
|
return (classes.match(/(^|\s)br-\S+/g) || []).join(' ');
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// apply style by setting classes on elements
|
||
|
var applyStyle = function() {
|
||
|
var $a = self.$widget.find('a[data-rating-value="' + ratingValue() + '"]');
|
||
|
var initialRating = getData('userOptions').initialRating;
|
||
|
var baseValue = $.isNumeric(ratingValue()) ? ratingValue() : 0;
|
||
|
var f = fraction(initialRating);
|
||
|
var $all, $fractional;
|
||
|
|
||
|
resetStyle();
|
||
|
|
||
|
// add classes
|
||
|
$a.addClass('br-selected br-current')[nextAllorPreviousAll()]()
|
||
|
.addClass('br-selected');
|
||
|
|
||
|
if (!getData('ratingMade') && $.isNumeric(initialRating)) {
|
||
|
if ((initialRating <= baseValue) || !f) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$all = self.$widget.find('a');
|
||
|
|
||
|
$fractional = ($a.length) ?
|
||
|
$a[(getData('userOptions').reverse) ? 'prev' : 'next']() :
|
||
|
$all[(getData('userOptions').reverse) ? 'last' : 'first']();
|
||
|
|
||
|
$fractional.addClass('br-fractional');
|
||
|
$fractional.addClass('br-fractional-' + f);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// check if the element is deselectable?
|
||
|
var isDeselectable = function($element) {
|
||
|
if (!getData('allowEmpty') || !getData('userOptions').deselectable) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return (ratingValue() == $element.attr('data-rating-value'));
|
||
|
};
|
||
|
|
||
|
// handle click events
|
||
|
var attachClickHandler = function($elements) {
|
||
|
$elements.on('click.barrating', function(event) {
|
||
|
var $a = $(this),
|
||
|
options = getData('userOptions'),
|
||
|
value,
|
||
|
text;
|
||
|
|
||
|
event.preventDefault();
|
||
|
|
||
|
value = $a.attr('data-rating-value');
|
||
|
text = $a.attr('data-rating-text');
|
||
|
|
||
|
// is current and deselectable?
|
||
|
if (isDeselectable($a)) {
|
||
|
value = getData('emptyRatingValue');
|
||
|
text = getData('emptyRatingText');
|
||
|
}
|
||
|
|
||
|
// remember selected rating
|
||
|
setData('ratingValue', value);
|
||
|
setData('ratingText', text);
|
||
|
setData('ratingMade', true);
|
||
|
|
||
|
setSelectFieldValue(value);
|
||
|
showSelectedRating(text);
|
||
|
|
||
|
applyStyle();
|
||
|
|
||
|
// onSelect callback
|
||
|
options.onSelect.call(
|
||
|
self,
|
||
|
ratingValue(),
|
||
|
ratingText(),
|
||
|
event
|
||
|
);
|
||
|
|
||
|
return false;
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// handle mouseenter events
|
||
|
var attachMouseEnterHandler = function($elements) {
|
||
|
$elements.on('mouseenter.barrating', function() {
|
||
|
var $a = $(this);
|
||
|
|
||
|
resetStyle();
|
||
|
|
||
|
$a.addClass('br-active')[nextAllorPreviousAll()]()
|
||
|
.addClass('br-active');
|
||
|
|
||
|
showSelectedRating($a.attr('data-rating-text'));
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// handle mouseleave events
|
||
|
var attachMouseLeaveHandler = function($elements) {
|
||
|
self.$widget.on('mouseleave.barrating blur.barrating', function() {
|
||
|
showSelectedRating();
|
||
|
applyStyle();
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// somewhat primitive way to remove 300ms click delay on touch devices
|
||
|
// for a more advanced solution consider setting `fastClicks` option to false
|
||
|
// and using a library such as fastclick (https://github.com/ftlabs/fastclick)
|
||
|
var fastClicks = function($elements) {
|
||
|
$elements.on('touchstart.barrating', function(event) {
|
||
|
event.preventDefault();
|
||
|
event.stopPropagation();
|
||
|
|
||
|
$(this).click();
|
||
|
});
|
||
|
};
|
||
|
|
||
|
// disable clicks
|
||
|
var disableClicks = function($elements) {
|
||
|
$elements.on('click.barrating', function(event) {
|
||
|
event.preventDefault();
|
||
|
});
|
||
|
};
|
||
|
|
||
|
var attachHandlers = function($elements) {
|
||
|
// attach click event handler
|
||
|
attachClickHandler($elements);
|
||
|
|
||
|
if (self.options.hoverState) {
|
||
|
// attach mouseenter event handler
|
||
|
attachMouseEnterHandler($elements);
|
||
|
|
||
|
// attach mouseleave event handler
|
||
|
attachMouseLeaveHandler($elements);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var detachHandlers = function($elements) {
|
||
|
// remove event handlers in the ".barrating" namespace
|
||
|
$elements.off('.barrating');
|
||
|
};
|
||
|
|
||
|
var setupHandlers = function(readonly) {
|
||
|
var $elements = self.$widget.find('a');
|
||
|
|
||
|
if (fastClicks) {
|
||
|
fastClicks($elements);
|
||
|
}
|
||
|
|
||
|
if (readonly) {
|
||
|
detachHandlers($elements);
|
||
|
disableClicks($elements);
|
||
|
} else {
|
||
|
attachHandlers($elements);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.show = function() {
|
||
|
// run only once
|
||
|
if (getData()) return;
|
||
|
|
||
|
// wrap element
|
||
|
wrapElement();
|
||
|
|
||
|
// save data
|
||
|
saveDataOnElement();
|
||
|
|
||
|
// build & append widget to the DOM
|
||
|
self.$widget = buildWidget();
|
||
|
self.$widget.insertAfter(self.$elem);
|
||
|
|
||
|
applyStyle();
|
||
|
|
||
|
showSelectedRating();
|
||
|
|
||
|
setupHandlers(self.options.readonly);
|
||
|
|
||
|
// hide the select field
|
||
|
self.$elem.hide();
|
||
|
};
|
||
|
|
||
|
this.readonly = function(state) {
|
||
|
if (typeof state !== 'boolean' || getData('readOnly') == state) return;
|
||
|
|
||
|
setupHandlers(state);
|
||
|
setData('readOnly', state);
|
||
|
self.$widget.toggleClass('br-readonly');
|
||
|
};
|
||
|
|
||
|
this.set = function(value) {
|
||
|
var options = getData('userOptions');
|
||
|
|
||
|
if (self.$elem.find('option[value="' + value + '"]').length === 0) return;
|
||
|
|
||
|
// set data
|
||
|
setData('ratingValue', value);
|
||
|
setData('ratingText', self.$elem.find('option[value="' + value + '"]').text());
|
||
|
setData('ratingMade', true);
|
||
|
|
||
|
setSelectFieldValue(ratingValue());
|
||
|
showSelectedRating(ratingText());
|
||
|
|
||
|
applyStyle();
|
||
|
|
||
|
// onSelect callback
|
||
|
if (!options.silent) {
|
||
|
options.onSelect.call(
|
||
|
this,
|
||
|
ratingValue(),
|
||
|
ratingText()
|
||
|
);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.clear = function() {
|
||
|
var options = getData('userOptions');
|
||
|
|
||
|
// restore original data
|
||
|
setData('ratingValue', getData('originalRatingValue'));
|
||
|
setData('ratingText', getData('originalRatingText'));
|
||
|
setData('ratingMade', false);
|
||
|
|
||
|
resetSelectField();
|
||
|
showSelectedRating(ratingText());
|
||
|
|
||
|
applyStyle();
|
||
|
|
||
|
// onClear callback
|
||
|
options.onClear.call(
|
||
|
this,
|
||
|
ratingValue(),
|
||
|
ratingText()
|
||
|
);
|
||
|
};
|
||
|
|
||
|
this.destroy = function() {
|
||
|
var value = ratingValue();
|
||
|
var text = ratingText();
|
||
|
var options = getData('userOptions');
|
||
|
|
||
|
// detach handlers
|
||
|
detachHandlers(self.$widget.find('a'));
|
||
|
|
||
|
// remove widget
|
||
|
self.$widget.remove();
|
||
|
|
||
|
// remove data
|
||
|
removeDataOnElement();
|
||
|
|
||
|
// unwrap the element
|
||
|
unwrapElement();
|
||
|
|
||
|
// show the element
|
||
|
self.$elem.show();
|
||
|
|
||
|
// onDestroy callback
|
||
|
options.onDestroy.call(
|
||
|
this,
|
||
|
value,
|
||
|
text
|
||
|
);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
BarRating.prototype.init = function (options, elem) {
|
||
|
this.$elem = $(elem);
|
||
|
this.options = $.extend({}, $.fn.barrating.defaults, options);
|
||
|
|
||
|
return this.options;
|
||
|
};
|
||
|
|
||
|
return BarRating;
|
||
|
})();
|
||
|
|
||
|
$.fn.barrating = function (method, options) {
|
||
|
return this.each(function () {
|
||
|
var plugin = new BarRating();
|
||
|
|
||
|
// plugin works with select fields
|
||
|
if (!$(this).is('select')) {
|
||
|
$.error('Sorry, this plugin only works with select fields.');
|
||
|
}
|
||
|
|
||
|
// method supplied
|
||
|
if (plugin.hasOwnProperty(method)) {
|
||
|
plugin.init(options, this);
|
||
|
if (method === 'show') {
|
||
|
return plugin.show(options);
|
||
|
} else {
|
||
|
// plugin exists?
|
||
|
if (plugin.$elem.data('barrating')) {
|
||
|
plugin.$widget = $(this).next('.br-widget');
|
||
|
return plugin[method](options);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// no method supplied or only options supplied
|
||
|
} else if (typeof method === 'object' || !method) {
|
||
|
options = method;
|
||
|
plugin.init(options, this);
|
||
|
return plugin.show();
|
||
|
|
||
|
} else {
|
||
|
$.error('Method ' + method + ' does not exist on jQuery.barrating');
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
$.fn.barrating.defaults = {
|
||
|
theme:'',
|
||
|
initialRating:null, // initial rating
|
||
|
allowEmpty:null, // allow empty ratings?
|
||
|
emptyValue:'', // this is the expected value of the empty rating
|
||
|
showValues:false, // display rating values on the bars?
|
||
|
showSelectedRating:true, // append a div with a rating to the widget?
|
||
|
deselectable:true, // allow to deselect ratings?
|
||
|
reverse:false, // reverse the rating?
|
||
|
readonly:false, // make the rating ready-only?
|
||
|
fastClicks:true, // remove 300ms click delay on touch devices?
|
||
|
hoverState:true, // change state on hover?
|
||
|
silent:false, // supress callbacks when controlling ratings programatically
|
||
|
onSelect:function (value, text, event) {
|
||
|
}, // callback fired when a rating is selected
|
||
|
onClear:function (value, text) {
|
||
|
}, // callback fired when a rating is cleared
|
||
|
onDestroy:function (value, text) {
|
||
|
} // callback fired when a widget is destroyed
|
||
|
};
|
||
|
|
||
|
$.fn.barrating.BarRating = BarRating;
|
||
|
|
||
|
}));
|