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.
585 lines
19 KiB
585 lines
19 KiB
/** |
|
* 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; |
|
|
|
}));
|
|
|