rateyo.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167
  1. 'use strict';
  2. function _typeof(obj) {
  3. "@babel/helpers - typeof";
  4. if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
  5. _typeof = function (obj) {
  6. return typeof obj;
  7. };
  8. } else {
  9. _typeof = function (obj) {
  10. return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
  11. };
  12. }
  13. return _typeof(obj);
  14. }
  15. function _defineProperty(obj, key, value) {
  16. if (key in obj) {
  17. Object.defineProperty(obj, key, {
  18. value: value,
  19. enumerable: true,
  20. configurable: true,
  21. writable: true
  22. });
  23. } else {
  24. obj[key] = value;
  25. }
  26. return obj;
  27. }
  28. function ownKeys(object, enumerableOnly) {
  29. var keys = Object.keys(object);
  30. if (Object.getOwnPropertySymbols) {
  31. var symbols = Object.getOwnPropertySymbols(object);
  32. if (enumerableOnly) symbols = symbols.filter(function (sym) {
  33. return Object.getOwnPropertyDescriptor(object, sym).enumerable;
  34. });
  35. keys.push.apply(keys, symbols);
  36. }
  37. return keys;
  38. }
  39. function _objectSpread2(target) {
  40. for (var i = 1; i < arguments.length; i++) {
  41. var source = arguments[i] != null ? arguments[i] : {};
  42. if (i % 2) {
  43. ownKeys(Object(source), true).forEach(function (key) {
  44. _defineProperty(target, key, source[key]);
  45. });
  46. } else if (Object.getOwnPropertyDescriptors) {
  47. Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
  48. } else {
  49. ownKeys(Object(source)).forEach(function (key) {
  50. Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
  51. });
  52. }
  53. }
  54. return target;
  55. }
  56. // The basic svg string required to generate stars
  57. var BASICSTAR = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<svg version=\"1.1\"" + "xmlns=\"http://www.w3.org/2000/svg\"" + "viewBox=\"0 12.705 512 486.59\"" + "x=\"0px\" y=\"0px\"" + "xml:space=\"preserve\">" + "<polygon " + "points=\"256.814,12.705 317.205,198.566" + " 512.631,198.566 354.529,313.435 " + "414.918,499.295 256.814,384.427 " + "98.713,499.295 159.102,313.435 " + "1,198.566 196.426,198.566 \"/>" + "</svg>"; // The Default values of different options available in the Plugin
  58. var DEFAULTS = {
  59. starWidth: "32px",
  60. normalFill: "gray",
  61. ratedFill: "#f39c12",
  62. numStars: 5,
  63. maxValue: 5,
  64. precision: 1,
  65. rating: 0,
  66. fullStar: false,
  67. halfStar: false,
  68. hover: true,
  69. readOnly: false,
  70. spacing: "0px",
  71. rtl: false,
  72. multiColor: null,
  73. onInit: null,
  74. onChange: null,
  75. onSet: null,
  76. starSvg: null
  77. }; //Default colors for multi-color rating
  78. var MULTICOLOR_OPTIONS = {
  79. startColor: "#c0392b",
  80. //red
  81. endColor: "#f1c40f" //yellow
  82. };
  83. // http://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
  84. function isMobileBrowser() {
  85. var check = false;
  86. (function (a) {
  87. if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true;
  88. })(navigator.userAgent || navigator.vendor || window.opera);
  89. return check;
  90. }
  91. function checkPrecision(value, minValue, maxValue) {
  92. /*
  93. * This function removes the unnecessary precision, at Min and Max Values
  94. */
  95. // Its like comparing 0.0 with 0, which is true
  96. if (value === minValue) {
  97. value = minValue;
  98. } else if (value === maxValue) {
  99. value = maxValue;
  100. }
  101. return value;
  102. }
  103. function checkBounds(value, minValue, maxValue) {
  104. /*
  105. * Check if the value is between min and max values, if not, throw an error
  106. */
  107. var isValid = value >= minValue && value <= maxValue;
  108. if (!isValid) {
  109. throw Error("Invalid Rating, expected value between " + minValue + " and " + maxValue);
  110. }
  111. return value;
  112. }
  113. function isType(value, type) {
  114. return _typeof(value) === type;
  115. }
  116. function isDefined(value) {
  117. // Better way to check if a variable is defined or not
  118. return typeof value !== "undefined";
  119. }
  120. var isNumber = function isNumber(input) {
  121. return isType(input, "number");
  122. };
  123. var isString = function isString(input) {
  124. return isType(input, "string");
  125. };
  126. var isFunction = function isFunction(input) {
  127. return isType(input, "function");
  128. };
  129. var hexRegex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;
  130. function hexToRGB(hex) {
  131. /*
  132. * Extracts and returns the Red, Blue, Green Channel values,
  133. * in the form of decimals
  134. */
  135. if (!hexRegex.test(hex)) {
  136. return null;
  137. }
  138. var hexValues = hexRegex.exec(hex),
  139. r = parseInt(hexValues[1], 16),
  140. g = parseInt(hexValues[2], 16),
  141. b = parseInt(hexValues[3], 16);
  142. return {
  143. r: r,
  144. g: g,
  145. b: b
  146. };
  147. }
  148. function getChannelValue(startVal, endVal, percent) {
  149. /*
  150. * Returns a value between `startVal` and `endVal` based on the percent
  151. */
  152. var newVal = (endVal - startVal) * (percent / 100);
  153. newVal = Math.round(startVal + newVal).toString(16);
  154. if (newVal.length === 1) {
  155. newVal = "0" + newVal;
  156. }
  157. return newVal;
  158. }
  159. function getColor(startColor, endColor, percent) {
  160. /*
  161. * Given the percentage( `percent` ) of `endColor` to be mixed
  162. * with the `startColor`, returns the mixed color.
  163. * Colors should be only in Hex Format
  164. */
  165. if (!startColor || !endColor) {
  166. return null;
  167. }
  168. percent = isDefined(percent) ? percent : 0;
  169. startColor = hexToRGB(startColor);
  170. endColor = hexToRGB(endColor);
  171. var r = getChannelValue(startColor.r, endColor.r, percent),
  172. b = getChannelValue(startColor.b, endColor.b, percent),
  173. g = getChannelValue(startColor.g, endColor.g, percent);
  174. return "#" + r + g + b;
  175. }
  176. var eventObjectMap = {};
  177. function getEventObject(event) {
  178. return eventObjectMap[event] || (eventObjectMap[event] = new String(event));
  179. }
  180. var handlerProxyMap = new WeakMap();
  181. function proxy(node, fn, event) {
  182. event = getEventObject(event);
  183. var eventHandlerMap = handlerProxyMap.get(node);
  184. if (!eventHandlerMap) {
  185. handlerProxyMap.set(node, eventHandlerMap = new WeakMap());
  186. }
  187. var handlerMap = eventHandlerMap.get(event);
  188. if (!handlerMap) {
  189. eventHandlerMap.set(event, handlerMap = new Map());
  190. }
  191. var handler = handlerMap.get(fn);
  192. if (handler) {
  193. return handler;
  194. }
  195. function proxy(e) {
  196. var data = e.detail;
  197. fn.call(node, e, data);
  198. }
  199. handlerMap.set(fn, proxy);
  200. return proxy;
  201. }
  202. proxy.get = function getOriginalFunction(node, fn, event) {
  203. event = getEventObject(event);
  204. var eventHandlerMap = handlerProxyMap.get(node);
  205. if (!eventHandlerMap) {
  206. return fn;
  207. }
  208. var handlerMap = eventHandlerMap.get(event);
  209. if (!handlerMap) {
  210. return fn;
  211. }
  212. return handlerMap.get(fn) || fn;
  213. };
  214. var Event = isFunction(window.Event) ? window.Event : function Event(event) {
  215. var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  216. var _params$bubbles = params.bubbles,
  217. bubbles = _params$bubbles === void 0 ? false : _params$bubbles,
  218. _params$cancelable = params.cancelable,
  219. cancelable = _params$cancelable === void 0 ? false : _params$cancelable;
  220. var evt = document.createEvent("Event");
  221. evt.initEvent(event, bubbles, cancelable);
  222. return evt;
  223. };
  224. var CustomEvent = isFunction(window.CustomEvent) ? window.CustomEvent : (CustomEvent.prototype = Object.create(Event.prototype), CustomEvent);
  225. var events = {
  226. trigger: function trigger(event, detail) {
  227. var eventProps = {
  228. bubbles: true
  229. };
  230. if (!isDefined(detail)) {
  231. this.node.dispatchEvent(new Event(event, eventProps));
  232. } else {
  233. this.node.dispatchEvent(new CustomEvent(event, _objectSpread2({
  234. detail: detail
  235. }, eventProps)));
  236. }
  237. return this;
  238. },
  239. on: function on(event, handler) {
  240. this.node.addEventListener(event, proxy(this.node, handler, event));
  241. return this;
  242. },
  243. off: function off(event, handler) {
  244. this.node.removeEventListener(event, proxy.get(this.node, handler, event));
  245. return this;
  246. }
  247. };
  248. var rateyoAttrRegex = /^rateyo(.+)$/;
  249. function classList(node, operation, input) {
  250. var className = node.className.trim();
  251. var classes = className && className.split(/\s/) || [],
  252. classMap = {};
  253. classes = classes.reduce(function (result, item, index) {
  254. if (!classMap.hasOwnProperty(item)) {
  255. result.push(item);
  256. classMap[item] = index;
  257. }
  258. return result;
  259. }, []);
  260. if (operation === classList.add) {
  261. if (classMap.hasOwnProperty(input)) {
  262. return;
  263. }
  264. classes.push(input);
  265. } else if (operation === classList.remove) {
  266. if (!classMap.hasOwnProperty(input)) {
  267. return;
  268. }
  269. classes.splice(classMap[input], 1);
  270. }
  271. node.className = classes.join(" ");
  272. }
  273. classList.add = "add";
  274. classList.remove = "remove";
  275. function El(node) {
  276. this.node = node;
  277. }
  278. El.prototype = {
  279. empty: function empty() {
  280. this.node.innerHTML = "";
  281. return this;
  282. },
  283. addClass: function addClass(className) {
  284. classList(this.node, classList.add, className);
  285. return this;
  286. },
  287. removeClass: function removeClass(className) {
  288. classList(this.node, classList.remove, className);
  289. return this;
  290. },
  291. appendTo: function appendTo(parent) {
  292. (El.isEl(parent) ? parent.node : parent).appendChild(this.node);
  293. return this;
  294. },
  295. css: function css(styleAttribute, value) {
  296. this.node.style[styleAttribute] = value;
  297. return this;
  298. },
  299. width: function width(_width) {
  300. if (!isDefined(_width)) {
  301. return this.node.getBoundingClientRect().width;
  302. }
  303. this.css("width", _width + (isNumber(_width) ? "px" : ""));
  304. },
  305. find: function find(selector) {
  306. return $(this.node.querySelectorAll(selector));
  307. },
  308. attr: function attr(attrObj) {
  309. for (var attrName in attrObj) {
  310. if (attrObj.hasOwnProperty(attrName)) {
  311. this.node.setAttribute(attrName, attrObj[attrName]);
  312. }
  313. }
  314. return this;
  315. },
  316. removeAttr: function removeAttr(attributeName) {
  317. this.node.removeAttribute(attributeName);
  318. return this;
  319. },
  320. children: function children() {
  321. return $(this.node.childNodes);
  322. },
  323. remove: function remove() {
  324. return this.node.remove();
  325. },
  326. offset: function offset() {
  327. var _this$node$getBoundin = this.node.getBoundingClientRect(),
  328. top = _this$node$getBoundin.top,
  329. left = _this$node$getBoundin.left,
  330. bottom = _this$node$getBoundin.bottom,
  331. right = _this$node$getBoundin.right;
  332. return {
  333. top: top,
  334. left: left,
  335. bottom: bottom,
  336. right: right
  337. };
  338. },
  339. dataAttrOptions: function dataAttrOptions() {
  340. var dataset = this.node.dataset;
  341. return Object.keys(dataset).reduce(function (options, attr) {
  342. var match = attr.match(rateyoAttrRegex);
  343. if (!match) {
  344. return options;
  345. }
  346. var rateyoAttr = match[1],
  347. option = rateyoAttr[0].toLowerCase() + rateyoAttr.slice(1);
  348. options[option] = dataset[attr];
  349. return options;
  350. }, {});
  351. }
  352. };
  353. El.prototype = _objectSpread2(_objectSpread2({}, El.prototype), events);
  354. El.isEl = function (node) {
  355. return node instanceof El;
  356. };
  357. function Collection(nodeList) {
  358. var _this = this;
  359. this.collection = [];
  360. Array.prototype.forEach.call(nodeList, function (node) {
  361. _this.collection.push(new El(node));
  362. });
  363. }
  364. Collection.isCollection = function (input) {
  365. return input instanceof Collection;
  366. };
  367. Collection.prototype = {};
  368. var _loop = function _loop(method) {
  369. if (!El.prototype.hasOwnProperty(method)) {
  370. return "continue";
  371. }
  372. function proxy() {
  373. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
  374. args[_key] = arguments[_key];
  375. }
  376. this.collection.forEach(function (el) {
  377. return El.isEl(el) && el[method].apply(el, args);
  378. });
  379. return this;
  380. }
  381. Collection.prototype[method] = proxy;
  382. };
  383. for (var method in El.prototype) {
  384. var _ret = _loop(method);
  385. if (_ret === "continue") continue;
  386. }
  387. var parser = new DOMParser();
  388. function parseHTML(html) {
  389. return parser.parseFromString(html.trim(), "text/html").body.childNodes;
  390. }
  391. function $(node) {
  392. node = isString(node) && parseHTML(node) || node;
  393. if (El.isEl(node) || Collection.isCollection(node)) {
  394. return node;
  395. }
  396. if (node instanceof NodeList) {
  397. return new Collection(node);
  398. }
  399. return new El(node);
  400. }
  401. $.El = El;
  402. var instanceMap = new WeakMap();
  403. function RateYo(node) {
  404. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  405. if (!(this instanceof RateYo)) {
  406. return new RateYo(node, options);
  407. }
  408. if (instanceMap.has(node)) {
  409. return instanceMap.get(node);
  410. }
  411. var that = this;
  412. this.node = node;
  413. var $node = $(node);
  414. options = _objectSpread2(_objectSpread2(_objectSpread2({}, DEFAULTS), options), $node.dataAttrOptions()); // Remove any stuff that is present inside the container, and add the plugin class
  415. $node.empty().addClass("jq-ry-container");
  416. /*
  417. * Basically the plugin displays the rating using two rows of stars lying one above
  418. * the other, the row that is on the top represents the actual rating, and the one
  419. * behind acts just like a background.
  420. *
  421. * `$groupWrapper`: is an element that wraps both the rows
  422. * `$normalGroup`: is the container for row of stars thats behind and
  423. * acts as background
  424. * `$ratedGroup`: is the container for row of stars that display the actual rating.
  425. *
  426. * The rating is displayed by adjusting the width of `$ratedGroup`
  427. */
  428. var $groupWrapper = $(document.createElement("div")).addClass("jq-ry-group-wrapper").appendTo($node);
  429. var $normalGroup = $(document.createElement("div")).addClass("jq-ry-normal-group").addClass("jq-ry-group").appendTo($groupWrapper);
  430. var $ratedGroup = $(document.createElement("div")).addClass("jq-ry-rated-group").addClass("jq-ry-group").appendTo($groupWrapper);
  431. /*
  432. * Variable `step`: store the value of the rating for each star
  433. * eg: if `maxValue` is 5 and `numStars` is 5, value of each star
  434. * is 1.
  435. * Variable `starWidth`: stores the decimal value of width of star in units of px
  436. * Variable `percentOfStar`: stores the percentage of width each star takes w.r.t
  437. * the container
  438. * Variable `spacing`: stores the decimal value of the spacing between stars
  439. * in the units of px
  440. * Variable `percentOfSpacing`: stores the percentage of width of the spacing
  441. * between stars w.r.t the container
  442. */
  443. var step,
  444. starWidth,
  445. percentOfStar,
  446. spacing,
  447. percentOfSpacing,
  448. containerWidth,
  449. minValue = 0;
  450. /*
  451. * `currentRating` contains rating that is being displayed at the latest point of
  452. * time.
  453. *
  454. * When ever you hover over the plugin UI, the rating value changes
  455. * according to the place where you point the cursor, currentRating contains
  456. * the current value of rating that is being shown in the UI
  457. */
  458. var currentRating = options.rating; // A flag to store if the plugin is already being displayed in the UI
  459. var isInitialized = false;
  460. function showRating(ratingVal) {
  461. /*
  462. * The function is responsible for displaying the rating by changing
  463. * the width of `$ratedGroup`
  464. */
  465. if (!isDefined(ratingVal)) {
  466. ratingVal = options.rating;
  467. } // Storing the value that is being shown in `currentRating`.
  468. currentRating = ratingVal;
  469. var numStarsToShow = ratingVal / step; // calculating the percentage of width of $ratedGroup with respect to its parent
  470. var percent = numStarsToShow * percentOfStar;
  471. if (numStarsToShow > 1) {
  472. // adding the percentage of space that is taken by the gap the stars
  473. percent += (Math.ceil(numStarsToShow) - 1) * percentOfSpacing;
  474. }
  475. setRatedFill(options.ratedFill);
  476. percent = options.rtl ? 100 - percent : percent;
  477. if (percent < 0) {
  478. percent = 0;
  479. } else if (percent > 100) {
  480. percent = 100;
  481. }
  482. $ratedGroup.css("width", percent + "%");
  483. }
  484. function setContainerWidth() {
  485. /*
  486. * Set the width of the `this.node` based on the width of each star and
  487. * the space between them
  488. */
  489. containerWidth = starWidth * options.numStars + spacing * (options.numStars - 1);
  490. percentOfStar = starWidth / containerWidth * 100;
  491. percentOfSpacing = spacing / containerWidth * 100;
  492. $node.width(containerWidth);
  493. showRating();
  494. }
  495. function setStarWidth(newWidth) {
  496. /*
  497. * Set the width and height of each SVG star, called whenever one changes the
  498. * `starWidth` option
  499. */
  500. // The width and height of the star should be the same
  501. var starHeight = options.starWidth = newWidth;
  502. starWidth = window.parseFloat(options.starWidth.replace("px", ""));
  503. $normalGroup.find("svg").attr({
  504. width: options.starWidth,
  505. height: starHeight
  506. });
  507. $ratedGroup.find("svg").attr({
  508. width: options.starWidth,
  509. height: starHeight
  510. });
  511. setContainerWidth();
  512. return $node;
  513. }
  514. function setSpacing(newSpacing) {
  515. /*
  516. * Set spacing between the SVG stars, called whenever one changes
  517. * the `spacing` option
  518. */
  519. options.spacing = newSpacing;
  520. spacing = parseFloat(options.spacing.replace("px", ""));
  521. $normalGroup.find("svg:not(:first-child)").css("margin-left", newSpacing);
  522. $ratedGroup.find("svg:not(:first-child)").css("margin-left", newSpacing);
  523. setContainerWidth();
  524. return $node;
  525. }
  526. function setNormalFill(newFill) {
  527. /*
  528. * Set the background fill of the Stars, called whenever one changes the
  529. * `normalFill` option
  530. */
  531. options.normalFill = newFill;
  532. var $svgs = (options.rtl ? $ratedGroup : $normalGroup).find("svg");
  533. $svgs.attr({
  534. fill: options.normalFill
  535. });
  536. return $node;
  537. }
  538. /*
  539. * Store the recent `ratedFill` option in a variable
  540. * so that if multiColor is unset, we can use the perviously set `ratedFill`
  541. * from this variable
  542. */
  543. var ratedFill = options.ratedFill;
  544. function setRatedFill(newFill) {
  545. /*
  546. * Set ratedFill of the stars, called when one changes the `ratedFill` option
  547. */
  548. /*
  549. * If `multiColor` option is set, `newFill` variable is dynamically set
  550. * based on the rating, what ever set as parameter will be discarded
  551. */
  552. if (options.multiColor) {
  553. var ratingDiff = currentRating - minValue,
  554. percentCovered = ratingDiff / options.maxValue * 100;
  555. var colorOpts = options.multiColor || {},
  556. startColor = colorOpts.startColor || MULTICOLOR_OPTIONS.startColor,
  557. endColor = colorOpts.endColor || MULTICOLOR_OPTIONS.endColor;
  558. newFill = getColor(startColor, endColor, percentCovered);
  559. } else {
  560. ratedFill = newFill;
  561. }
  562. options.ratedFill = newFill;
  563. var $svgs = (options.rtl ? $normalGroup : $ratedGroup).find("svg");
  564. $svgs.attr({
  565. fill: options.ratedFill
  566. });
  567. return $node;
  568. }
  569. function setRtl(newValue) {
  570. newValue = !!newValue;
  571. options.rtl = newValue;
  572. setNormalFill(options.normalFill);
  573. showRating();
  574. }
  575. function setMultiColor(colorOptions) {
  576. /*
  577. * called whenever one changes the `multiColor` option
  578. */
  579. options.multiColor = colorOptions; // set the recently set `ratedFill` option, if multiColor Options are unset
  580. setRatedFill(colorOptions ? colorOptions : ratedFill);
  581. }
  582. function setNumStars(newValue) {
  583. /*
  584. * Set the number of stars to use to display the rating, called whenever one
  585. * changes the `numStars` option
  586. */
  587. options.numStars = newValue;
  588. step = options.maxValue / options.numStars;
  589. $normalGroup.empty();
  590. $ratedGroup.empty();
  591. for (var i = 0; i < options.numStars; i++) {
  592. $(options.starSvg || BASICSTAR).appendTo($normalGroup);
  593. $(options.starSvg || BASICSTAR).appendTo($ratedGroup);
  594. }
  595. setStarWidth(options.starWidth);
  596. setNormalFill(options.normalFill);
  597. setSpacing(options.spacing);
  598. showRating();
  599. return $node;
  600. }
  601. function setMaxValue(newValue) {
  602. /*
  603. * set the Maximum Value of rating to be allowed, called whenever
  604. * one changes the `maxValue` option
  605. */
  606. options.maxValue = newValue;
  607. step = options.maxValue / options.numStars;
  608. if (options.rating > newValue) {
  609. setRating(newValue);
  610. }
  611. showRating();
  612. return $node;
  613. }
  614. function setPrecision(newValue) {
  615. /*
  616. * Set the precision of the rating value, called if one changes the
  617. * `precision` option
  618. */
  619. options.precision = newValue;
  620. setRating(options.rating);
  621. return $node;
  622. }
  623. function setHalfStar(newValue) {
  624. /*
  625. * This function will be called if one changes the `halfStar` option
  626. */
  627. options.halfStar = newValue;
  628. return $node;
  629. }
  630. function setFullStar(newValue) {
  631. /*
  632. * This function will be called if one changes the `fullStar` option
  633. */
  634. options.fullStar = newValue;
  635. return $node;
  636. }
  637. function round(value) {
  638. /*
  639. * Rounds the value of rating if `halfStar` or `fullStar` options are chosen
  640. */
  641. var remainder = value % step,
  642. halfStep = step / 2,
  643. isHalfStar = options.halfStar,
  644. isFullStar = options.fullStar;
  645. if (!isFullStar && !isHalfStar) {
  646. return value;
  647. }
  648. if (isFullStar || isHalfStar && remainder > halfStep) {
  649. value += step - remainder;
  650. } else {
  651. value = value - remainder;
  652. if (remainder > 0) {
  653. value += halfStep;
  654. }
  655. }
  656. return value;
  657. }
  658. function calculateRating(e) {
  659. /*
  660. * Calculates and returns the rating based on the position of cursor w.r.t the
  661. * plugin container
  662. */
  663. var position = $normalGroup.offset(),
  664. nodeStartX = position.left,
  665. nodeEndX = nodeStartX + $normalGroup.width();
  666. var maxValue = options.maxValue; // The x-coordinate(position) of the mouse pointer w.r.t page
  667. var pageX = e.pageX;
  668. var calculatedRating = 0; // If the mouse pointer is to the left of the container
  669. if (pageX < nodeStartX) {
  670. calculatedRating = minValue;
  671. } else if (pageX > nodeEndX) {
  672. // If the mouse pointer is right of the container
  673. calculatedRating = maxValue;
  674. } else {
  675. // If the mouse pointer is inside the continer
  676. /*
  677. * The fraction of width covered by the pointer w.r.t to the total width
  678. * of the container.
  679. */
  680. var calcPrcnt = (pageX - nodeStartX) / (nodeEndX - nodeStartX);
  681. if (spacing > 0) {
  682. /*
  683. * If there is spacing between stars, take the percentage of width covered
  684. * and subtract the percentage of width covered by stars and spacing, to find
  685. * how many stars are covered, the number of stars covered is the rating
  686. *
  687. * TODO: I strongly feel that this logic can be improved!, Please help!
  688. */
  689. calcPrcnt *= 100;
  690. var remPrcnt = calcPrcnt;
  691. while (remPrcnt > 0) {
  692. if (remPrcnt > percentOfStar) {
  693. calculatedRating += step;
  694. remPrcnt -= percentOfStar + percentOfSpacing;
  695. } else {
  696. calculatedRating += remPrcnt / percentOfStar * step;
  697. remPrcnt = 0;
  698. }
  699. }
  700. } else {
  701. /*
  702. * If there is not spacing between stars, the fraction of width covered per
  703. * `maxValue` is the rating
  704. */
  705. calculatedRating = calcPrcnt * options.maxValue;
  706. } // Round the rating if `halfStar` or `fullStar` options are chosen
  707. calculatedRating = round(calculatedRating);
  708. }
  709. if (options.rtl) {
  710. calculatedRating = maxValue - calculatedRating;
  711. }
  712. return parseFloat(calculatedRating);
  713. }
  714. function setReadOnly(newValue) {
  715. /*
  716. * UnBinds mouse event handlers, called when whenever one changes the
  717. * `readOnly` option
  718. */
  719. options.readOnly = newValue;
  720. $node.attr({
  721. "readonly": true
  722. });
  723. unbindEvents();
  724. if (!newValue) {
  725. $node.removeAttr("readonly");
  726. bindEvents();
  727. }
  728. return $node;
  729. }
  730. function setRating(newValue) {
  731. /*
  732. * Sets the rating of the Plugin, Called when option `rating` is changed
  733. * or, when `rating` method is called
  734. */
  735. var rating = newValue;
  736. var maxValue = options.maxValue;
  737. if (typeof rating === "string") {
  738. // If rating is given in percentage, maxValue should be 100
  739. if (rating[rating.length - 1] === "%") {
  740. rating = rating.substr(0, rating.length - 1);
  741. maxValue = 100;
  742. setMaxValue(maxValue);
  743. }
  744. rating = parseFloat(rating);
  745. }
  746. checkBounds(rating, minValue, maxValue);
  747. rating = parseFloat(rating.toFixed(options.precision));
  748. checkPrecision(parseFloat(rating), minValue, maxValue);
  749. options.rating = rating;
  750. showRating();
  751. if (isInitialized) {
  752. $node.trigger("rateyo.set", {
  753. rating: rating
  754. });
  755. }
  756. return $node;
  757. }
  758. function setOnInit(method) {
  759. /*
  760. * set what method to be called on Initialization
  761. */
  762. options.onInit = method;
  763. return $node;
  764. }
  765. function setOnSet(method) {
  766. /*
  767. * set what method to be called when rating is set
  768. */
  769. options.onSet = method;
  770. return $node;
  771. }
  772. function setOnChange(method) {
  773. /*
  774. * set what method to be called rating in the UI is changed
  775. */
  776. options.onChange = method;
  777. return $node;
  778. }
  779. this.rating = function (newValue) {
  780. /*
  781. * rating getter/setter
  782. */
  783. if (!isDefined(newValue)) {
  784. return options.rating;
  785. }
  786. setRating(newValue);
  787. return $node;
  788. };
  789. this.destroy = function () {
  790. /*
  791. * Removes the Rating UI by clearing the content, and removing the custom classes
  792. */
  793. if (!options.readOnly) {
  794. unbindEvents();
  795. }
  796. instanceMap["delete"](node);
  797. $node.removeClass("jq-ry-container").children().remove();
  798. return $node;
  799. };
  800. this.method = function (methodName) {
  801. /*
  802. * Method to call the methods of RateYo Instance
  803. */
  804. if (!methodName) {
  805. throw Error("Method name not specified!");
  806. }
  807. if (!isDefined(this[methodName])) {
  808. throw Error("Method " + methodName + " doesn't exist!");
  809. }
  810. var args = Array.prototype.slice.apply(arguments, []),
  811. params = args.slice(1),
  812. method = this[methodName];
  813. return method.apply(this, params);
  814. };
  815. this.option = function (optionName, param) {
  816. /*
  817. * Method to get/set Options
  818. */
  819. if (!isDefined(optionName)) {
  820. return options;
  821. }
  822. var method;
  823. switch (optionName) {
  824. case "starWidth":
  825. method = setStarWidth;
  826. break;
  827. case "numStars":
  828. method = setNumStars;
  829. break;
  830. case "normalFill":
  831. method = setNormalFill;
  832. break;
  833. case "ratedFill":
  834. method = setRatedFill;
  835. break;
  836. case "multiColor":
  837. method = setMultiColor;
  838. break;
  839. case "maxValue":
  840. method = setMaxValue;
  841. break;
  842. case "precision":
  843. method = setPrecision;
  844. break;
  845. case "rating":
  846. method = setRating;
  847. break;
  848. case "halfStar":
  849. method = setHalfStar;
  850. break;
  851. case "fullStar":
  852. method = setFullStar;
  853. break;
  854. case "readOnly":
  855. method = setReadOnly;
  856. break;
  857. case "spacing":
  858. method = setSpacing;
  859. break;
  860. case "rtl":
  861. method = setRtl;
  862. break;
  863. case "onInit":
  864. method = setOnInit;
  865. break;
  866. case "onSet":
  867. method = setOnSet;
  868. break;
  869. case "onChange":
  870. method = setOnChange;
  871. break;
  872. default:
  873. throw Error("No such option as " + optionName);
  874. }
  875. return isDefined(param) ? method(param) : options[optionName];
  876. };
  877. function onMouseEnter(e) {
  878. if (!options.hover) {
  879. return;
  880. }
  881. /*
  882. * If the Mouse Pointer is inside the container, calculate and show the rating
  883. * in UI
  884. */
  885. var rating = calculateRating(e).toFixed(options.precision);
  886. var maxValue = options.maxValue;
  887. rating = checkPrecision(parseFloat(rating), minValue, maxValue);
  888. showRating(rating);
  889. $node.trigger("rateyo.change", {
  890. rating: rating
  891. });
  892. }
  893. function onMouseLeave() {
  894. if (isMobileBrowser() || !options.hover) {
  895. return;
  896. }
  897. /*
  898. * If mouse leaves, revert the rating in UI to previously set rating,
  899. * when empty value is passed to showRating, it will take the previously set
  900. * rating
  901. */
  902. showRating();
  903. $node.trigger("rateyo.change", {
  904. rating: options.rating
  905. });
  906. }
  907. function onMouseClick(e) {
  908. /*
  909. * On clicking the mouse inside the container, calculate and set the rating
  910. */
  911. var resultantRating = calculateRating(e).toFixed(options.precision);
  912. resultantRating = parseFloat(resultantRating);
  913. that.rating(resultantRating);
  914. }
  915. function onInit(e, data) {
  916. if (options.onInit && typeof options.onInit === "function") {
  917. options.onInit.apply(this, [data.rating, that]);
  918. }
  919. }
  920. function onChange(e, data) {
  921. if (options.onChange && typeof options.onChange === "function") {
  922. options.onChange.apply(this, [data.rating, that]);
  923. }
  924. }
  925. function onSet(e, data) {
  926. if (options.onSet && typeof options.onSet === "function") {
  927. options.onSet.apply(this, [data.rating, that]);
  928. }
  929. }
  930. function bindEvents() {
  931. $node.on("mousemove", onMouseEnter).on("mouseenter", onMouseEnter).on("mouseleave", onMouseLeave).on("click", onMouseClick).on("rateyo.init", onInit).on("rateyo.change", onChange).on("rateyo.set", onSet);
  932. }
  933. function unbindEvents() {
  934. $node.off("mousemove", onMouseEnter).off("mouseenter", onMouseEnter).off("mouseleave", onMouseLeave).off("click", onMouseClick).off("rateyo.init", onInit).off("rateyo.change", onChange).off("rateyo.set", onSet);
  935. }
  936. setNumStars(options.numStars);
  937. setReadOnly(options.readOnly);
  938. if (options.rtl) {
  939. setRtl(options.rtl);
  940. }
  941. instanceMap.set(node, this);
  942. this.rating(options.rating, true);
  943. isInitialized = true;
  944. $node.trigger("rateyo.init", {
  945. rating: options.rating
  946. });
  947. }
  948. Object.defineProperty(RateYo.prototype, "on", {
  949. value: function on(eventName, handler) {
  950. $(this.node).on(eventName, handler);
  951. return this;
  952. }
  953. });
  954. Object.defineProperty(RateYo.prototype, "off", {
  955. value: function off(eventName, handler) {
  956. $(this.node).off(eventName, handler);
  957. return this;
  958. }
  959. });
  960. Object.defineProperty(RateYo, "has", {
  961. value: function has(node) {
  962. return instanceMap.has(node);
  963. }
  964. });
  965. Object.defineProperty(RateYo, "get", {
  966. value: function get(node) {
  967. return instanceMap.get(node);
  968. }
  969. });
  970. Object.defineProperty(RateYo, "_$", {
  971. get: function get() {
  972. return $;
  973. }
  974. });
  975. module.exports = RateYo;