star-rating.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /*!
  2. * @copyright © Kartik Visweswaran, Krajee.com, 2014
  3. * @version 2.5.0
  4. *
  5. * A simple yet powerful JQuery star rating plugin that allows rendering
  6. * fractional star ratings and supports Right to Left (RTL) input.
  7. *
  8. * For more JQuery plugins visit http://plugins.krajee.com
  9. * For more Yii related demos visit http://demos.krajee.com
  10. */
  11. (function ($) {
  12. var DEFAULT_MIN = 0;
  13. var DEFAULT_MAX = 5;
  14. var DEFAULT_STEP = 0.5;
  15. var isEmpty = function (value, trim) {
  16. return typeof value === 'undefined' || value === null || value === undefined || value == []
  17. || value === '' || trim && $.trim(value) === '';
  18. };
  19. var validateAttr = function ($input, vattr, options) {
  20. var chk = isEmpty($input.data(vattr)) ? $input.attr(vattr) : $input.data(vattr);
  21. if (chk) {
  22. return chk;
  23. }
  24. return options[vattr];
  25. };
  26. var getDecimalPlaces = function (num) {
  27. var match = ('' + num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
  28. if (!match) {
  29. return 0;
  30. }
  31. return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0));
  32. };
  33. var applyPrecision = function (val, precision) {
  34. return parseFloat(val.toFixed(precision));
  35. };
  36. // Rating public class definition
  37. var Rating = function (element, options) {
  38. this.$element = $(element);
  39. this.init(options);
  40. };
  41. Rating.prototype = {
  42. constructor: Rating,
  43. _parseAttr: function (vattr, options) {
  44. var self = this, $input = self.$element;
  45. if ($input.attr('type') === 'range' || $input.attr('type') === 'number') {
  46. var val = validateAttr($input, vattr, options);
  47. var chk = DEFAULT_STEP;
  48. if (vattr === 'min') {
  49. chk = DEFAULT_MIN;
  50. }
  51. else if (vattr === 'max') {
  52. chk = DEFAULT_MAX;
  53. }
  54. else if (vattr === 'step') {
  55. chk = DEFAULT_STEP;
  56. }
  57. var final = isEmpty(val) ? chk : val;
  58. return parseFloat(final);
  59. }
  60. return parseFloat(options[vattr]);
  61. },
  62. /**
  63. * Listens to events
  64. */
  65. listen: function () {
  66. var self = this;
  67. self.$rating.on("click", function (e) {
  68. if (!self.inactive) {
  69. w = e.pageX - self.$rating.offset().left;
  70. self.setStars(w);
  71. self.$element.trigger('change');
  72. self.$element.trigger('rating.change', [self.$element.val(), self.$caption.html()]);
  73. }
  74. });
  75. self.$clear.on("click", function (e) {
  76. if (!self.inactive) {
  77. self.clear();
  78. }
  79. });
  80. $(self.$element[0].form).on("reset", function (e) {
  81. if (!self.inactive) {
  82. self.reset();
  83. }
  84. });
  85. },
  86. initSlider: function (options) {
  87. var self = this;
  88. if (isEmpty(self.$element.val())) {
  89. self.$element.val(0);
  90. }
  91. self.initialValue = self.$element.val();
  92. self.min = (typeof options.min !== 'undefined') ? options.min : self._parseAttr('min', options);
  93. self.max = (typeof options.max !== 'undefined') ? options.max : self._parseAttr('max', options);
  94. self.step = (typeof options.step !== 'undefined') ? options.step : self._parseAttr('step', options);
  95. if (isNaN(self.min) || isEmpty(self.min)) {
  96. self.min = DEFAULT_MIN;
  97. }
  98. if (isNaN(self.max) || isEmpty(self.max)) {
  99. self.max = DEFAULT_MAX;
  100. }
  101. if (isNaN(self.step) || isEmpty(self.step) || self.step == 0) {
  102. self.step = DEFAULT_STEP;
  103. }
  104. self.diff = self.max - self.min;
  105. },
  106. /**
  107. * Initializes the plugin
  108. */
  109. init: function (options) {
  110. var self = this;
  111. self.options = options;
  112. self.initSlider(options);
  113. self.checkDisabled();
  114. $element = self.$element;
  115. self.containerClass = options.containerClass;
  116. self.glyphicon = options.glyphicon;
  117. var defaultStar = (self.glyphicon) ? '\ue006' : '\u2605';
  118. self.symbol = isEmpty(options.symbol) ? defaultStar : options.symbol;
  119. self.rtl = options.rtl || self.$element.attr('dir');
  120. if (self.rtl) {
  121. self.$element.attr('dir', 'rtl');
  122. }
  123. self.showClear = options.showClear;
  124. self.showCaption = options.showCaption;
  125. self.size = options.size;
  126. self.stars = options.stars;
  127. self.defaultCaption = options.defaultCaption;
  128. self.starCaptions = options.starCaptions;
  129. self.starCaptionClasses = options.starCaptionClasses;
  130. self.clearButton = options.clearButton;
  131. self.clearButtonTitle = options.clearButtonTitle;
  132. self.clearButtonBaseClass = !isEmpty(options.clearButtonBaseClass) ? options.clearButtonBaseClass : 'clear-rating';
  133. self.clearButtonActiveClass = !isEmpty(options.clearButtonActiveClass) ? options.clearButtonActiveClass : 'clear-rating-active';
  134. self.clearCaption = options.clearCaption;
  135. self.clearCaptionClass = options.clearCaptionClass;
  136. self.clearValue = options.clearValue;
  137. self.$element.removeClass('form-control').addClass('form-control');
  138. self.$clearElement = isEmpty(options.clearElement) ? null : $(options.clearElement);
  139. self.$captionElement = isEmpty(options.captionElement) ? null : $(options.captionElement);
  140. if (typeof self.$rating == 'undefined' && typeof self.$container == 'undefined') {
  141. self.$rating = $(document.createElement("div")).html('<div class="rating-stars"></div>');
  142. self.$container = $(document.createElement("div"));
  143. self.$container.before(self.$rating);
  144. self.$container.append(self.$rating);
  145. self.$element.before(self.$container).appendTo(self.$rating);
  146. }
  147. self.$stars = self.$rating.find('.rating-stars');
  148. self.generateRating();
  149. self.$clear = !isEmpty(self.$clearElement) ? self.$clearElement : self.$container.find('.' + self.clearButtonBaseClass);
  150. self.$caption = !isEmpty(self.$captionElement) ? self.$captionElement : self.$container.find(".caption");
  151. self.setStars();
  152. self.$element.hide();
  153. self.listen();
  154. if (self.showClear) {
  155. self.$clear.attr({"class": self.getClearClass()});
  156. }
  157. },
  158. checkDisabled: function () {
  159. var self = this;
  160. self.disabled = validateAttr(self.$element, 'disabled', self.options);
  161. self.readonly = validateAttr(self.$element, 'readonly', self.options);
  162. self.inactive = (self.disabled || self.readonly);
  163. },
  164. getClearClass: function () {
  165. return this.clearButtonBaseClass + ' ' + ((this.inactive) ? '' : this.clearButtonActiveClass);
  166. },
  167. generateRating: function () {
  168. var self = this, clear = self.renderClear(), caption = self.renderCaption(),
  169. css = (self.rtl) ? 'rating-container-rtl' : 'rating-container',
  170. stars = self.getStars();
  171. css += (self.glyphicon) ? ((self.symbol == '\ue006') ? ' rating-gly-star' : ' rating-gly') : ' rating-uni';
  172. self.$rating.attr('class', css);
  173. self.$rating.attr('data-content', stars);
  174. self.$stars.attr('data-content', stars);
  175. var css = self.rtl ? 'star-rating-rtl' : 'star-rating';
  176. self.$container.attr('class', css + ' rating-' + self.size);
  177. if (self.inactive) {
  178. self.$container.removeClass('rating-active').addClass('rating-disabled');
  179. }
  180. else {
  181. self.$container.removeClass('rating-disabled').addClass('rating-active');
  182. }
  183. if (typeof self.$caption == 'undefined' && typeof self.$clear == 'undefined') {
  184. if (self.rtl) {
  185. self.$container.prepend(caption).append(clear);
  186. }
  187. else {
  188. self.$container.prepend(clear).append(caption);
  189. }
  190. }
  191. if (!isEmpty(self.containerClass)) {
  192. self.$container.removeClass(self.containerClass).addClass(self.containerClass);
  193. }
  194. },
  195. getStars: function () {
  196. var self = this, numStars = self.stars, stars = '';
  197. for (var i = 1; i <= numStars; i++) {
  198. stars += self.symbol;
  199. }
  200. return stars;
  201. },
  202. renderClear: function () {
  203. var self = this;
  204. if (!self.showClear) {
  205. return '';
  206. }
  207. var css = self.getClearClass();
  208. if (!isEmpty(self.$clearElement)) {
  209. self.$clearElement.removeClass(css).addClass(css).attr({"title": self.clearButtonTitle});
  210. self.$clearElement.html(self.clearButton);
  211. return '';
  212. }
  213. return '<div class="' + css + '" title="' + self.clearButtonTitle + '">' + self.clearButton + '</div>';
  214. },
  215. renderCaption: function () {
  216. var self = this, val = self.$element.val();
  217. if (!self.showCaption) {
  218. return '';
  219. }
  220. var html = self.fetchCaption(val);
  221. if (!isEmpty(self.$captionElement)) {
  222. self.$captionElement.removeClass('caption').addClass('caption').attr({"title": self.clearCaption});
  223. self.$captionElement.html(html);
  224. return '';
  225. }
  226. return '<div class="caption">' + html + '</div>';
  227. },
  228. fetchCaption: function (rating) {
  229. var self = this;
  230. var val = parseFloat(rating);
  231. var css = isEmpty(self.starCaptionClasses[val]) ? self.clearCaptionClass : self.starCaptionClasses[val];
  232. var cap = !isEmpty(self.starCaptions[val]) ? self.starCaptions[val] : self.defaultCaption.replace(/\{rating\}/g, val);
  233. var caption = (val == self.clearValue) ? self.clearCaption : cap;
  234. return '<span class="' + css + '">' + caption + '</span>';
  235. },
  236. getValueFromPosition: function (pos) {
  237. var self = this, precision = getDecimalPlaces(self.step),
  238. percentage, val, maxWidth = self.$rating.width();
  239. percentage = (pos / maxWidth);
  240. val = (self.min + Math.ceil(self.diff * percentage / self.step) * self.step);
  241. if (val < self.min) {
  242. val = self.min;
  243. }
  244. else if (val > self.max) {
  245. val = self.max;
  246. }
  247. val = applyPrecision(parseFloat(val), precision);
  248. if (self.rtl) {
  249. val = self.max - val;
  250. }
  251. return val;
  252. },
  253. setStars: function (pos) {
  254. var self = this, min = self.min, max = self.max, step = self.step,
  255. val = arguments.length ? self.getValueFromPosition(pos) : (isEmpty(self.$element.val()) ? 0 : self.$element.val()),
  256. width = 0, maxWidth = self.$rating.width(), caption = self.fetchCaption(val);
  257. width = (val - min) / max * 100;
  258. if (self.rtl) {
  259. width = 100 - width;
  260. }
  261. self.$element.val(val);
  262. width += '%';
  263. self.$stars.css('width', width);
  264. self.$caption.html(caption);
  265. },
  266. clear: function () {
  267. var self = this;
  268. var title = '<span class="' + self.clearCaptionClass + '">' + self.clearCaption + '</span>';
  269. self.$stars.removeClass('rated');
  270. if (!self.inactive) {
  271. self.$caption.html(title);
  272. }
  273. self.$element.val(self.clearValue);
  274. self.setStars();
  275. self.$element.trigger('rating.clear');
  276. },
  277. reset: function () {
  278. var self = this;
  279. self.$element.val(self.initialValue);
  280. self.setStars();
  281. self.$element.trigger('rating.reset');
  282. },
  283. update: function (val) {
  284. if (arguments.length > 0) {
  285. var self = this;
  286. self.$element.val(val);
  287. self.setStars();
  288. }
  289. },
  290. refresh: function (options) {
  291. var self = this;
  292. if (arguments.length) {
  293. var cap = '';
  294. self.init($.extend(self.options, options));
  295. if (self.showClear) {
  296. self.$clear.show();
  297. }
  298. else {
  299. self.$clear.hide();
  300. }
  301. if (self.showCaption) {
  302. self.$caption.show();
  303. }
  304. else {
  305. self.$caption.hide();
  306. }
  307. }
  308. }
  309. };
  310. //Star rating plugin definition
  311. $.fn.rating = function (option) {
  312. var args = Array.apply(null, arguments);
  313. args.shift();
  314. return this.each(function () {
  315. var $this = $(this),
  316. data = $this.data('rating'),
  317. options = typeof option === 'object' && option;
  318. if (!data) {
  319. $this.data('rating', (data = new Rating(this, $.extend({}, $.fn.rating.defaults, options, $(this).data()))));
  320. }
  321. if (typeof option === 'string') {
  322. data[option].apply(data, args);
  323. }
  324. });
  325. };
  326. $.fn.rating.defaults = {
  327. stars: 5,
  328. glyphicon: true,
  329. symbol: null,
  330. disabled: false,
  331. readonly: false,
  332. rtl: false,
  333. size: 'md',
  334. showClear: true,
  335. showCaption: true,
  336. defaultCaption: '{rating} Stars',
  337. starCaptions: {
  338. 0.5: '差到极点了',
  339. 1: '非常差',
  340. 1.5: '不太好',
  341. 2: '不太好',
  342. 2.5: '感觉一般般',
  343. 3: '感觉还凑合',
  344. 3.5: '感觉还凑合',
  345. 4: '我挺满意的',
  346. 4.5: '总体不错',
  347. 5: '超级棒棒棒'
  348. },
  349. starCaptionClasses: {
  350. 0.5: 'label label-default',
  351. 1: 'label label-default',
  352. 1.5: 'label label-warning',
  353. 2: 'label label-warning',
  354. 2.5: 'label label-info',
  355. 3: 'label label-info',
  356. 3.5: 'label label-primary',
  357. 4: 'label label-primary',
  358. 4.5: 'label label-danger',
  359. 5: 'label label-danger'
  360. },
  361. clearButton: '<i class="glyphicon glyphicon-minus-sign"></i>',
  362. clearButtonTitle: 'Clear',
  363. clearButtonBaseClass: 'clear-rating',
  364. clearButtonActiveClass: 'clear-rating-active',
  365. clearCaption: '没有选择评分',
  366. clearCaptionClass: 'label label-default',
  367. clearValue: 0,
  368. captionElement: null,
  369. clearElement: null,
  370. containerClass: null
  371. };
  372. /**
  373. * Convert automatically number inputs with class 'rating'
  374. * into the star rating control.
  375. */
  376. $(document).ready(function () {
  377. var $input = $('input.rating'), count = Object.keys($input).length;
  378. if (count > 0) {
  379. $input.rating();
  380. }
  381. });
  382. }(jQuery));