rateyo.js 34 KB

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