/*! rater-js. [c] 2018 Fredrik Olsson. MIT License */ let css = require('./style.css'); module.exports = function(options) { //private fields let showToolTip = true; if (typeof options.element === "undefined" || options.element === null) { throw new Error("element required"); } if (typeof options.showToolTip !== "undefined") { showToolTip = !!options.showToolTip; } if (typeof options.step !== "undefined") { if (options.step <= 0 || options.step > 1) { throw new Error("step must be a number between 0 and 1"); } } let elem = options.element; let reverse = options.reverse; let stars = options.max || 5; let starSize = options.starSize || 16; let step = options.step || 1; let onHover = options.onHover; let onLeave = options.onLeave; let rating = null; let myRating; elem.classList.add("star-rating"); let div = document.createElement("div"); div.classList.add("star-value"); if(reverse) { div.classList.add("rtl"); } div.style.backgroundSize = starSize + "px"; elem.appendChild(div); elem.style.width = starSize * stars + "px"; elem.style.height = starSize + "px"; elem.style.backgroundSize = starSize + "px"; let callback = options.rateCallback; let disabled = !!options.readOnly; let disableText; let isRating = false; let isBusyText = options.isBusyText; let currentRating; let ratingText; if (typeof options.disableText !== "undefined") { disableText = options.disableText; }else { disableText = "{rating}/{maxRating}"; } if (typeof options.ratingText !== "undefined") { ratingText = options.ratingText; }else { ratingText = "{rating}/{maxRating}"; } if (options.rating) { setRating(options.rating); }else { var dataRating = elem.dataset.rating; if (dataRating) { setRating( + dataRating); } } if ( ! rating) { elem.querySelector(".star-value").style.width = "0px"; } if (disabled) { disable(); } //private methods function onMouseMove(e) { onMove(e,false); } /** * Called by eventhandlers when mouse or touch events are triggered * @param {MouseEvent} e */ function onMove(e, isTouch) { if (disabled === true || isRating === true) { return; } let xCoor = null; let percent; let width = elem.offsetWidth; let parentOffset = elem.getBoundingClientRect(); if (reverse) { if(isTouch) { xCoor = e.changedTouches[0].pageX - parentOffset.left; } else { xCoor = e.pageX - window.scrollX - parentOffset.left; } let relXRtl = width - xCoor; let valueForDivision = width / 100; percent = relXRtl / valueForDivision; } else { if(isTouch) { xCoor = e.changedTouches[0].pageX - parentOffset.left; } else { xCoor = e.offsetX; } percent = xCoor / width * 100; } if (percent < 101) { if (step === 1) { currentRating = Math.ceil((percent / 100) * stars); }else { let rat = (percent / 100) * stars; for (let i = 0; ; i += step) { if (i >= rat) { currentRating = i; break; } } } //todo: check why this happens and fix if(currentRating > stars) { currentRating = stars; } elem.querySelector(".star-value").style.width = currentRating/stars * 100 + "%"; if (showToolTip) { let toolTip = ratingText.replace("{rating}", currentRating); toolTip = toolTip.replace("{maxRating}", stars); elem.setAttribute("title", toolTip); } if (typeof onHover === "function") { onHover(currentRating, rating); } } } /** * Called when mouse is released. This function will update the view with the rating. * @param {MouseEvent} e */ function onStarOut(e) { if (!rating) { elem.querySelector(".star-value").style.width = "0%"; elem.removeAttribute("data-rating"); }else { elem.querySelector(".star-value").style.width = rating/stars * 100 + "%"; elem.setAttribute("data-rating", rating); } if (typeof onLeave === "function") { onLeave(currentRating, rating); } } /** * Called when star is clicked. * @param {MouseEvent} e */ function onStarClick(e) { if (disabled === true) { return; } if (isRating === true) { return; } if (typeof callback !== "undefined") { isRating = true; myRating = currentRating; if (typeof isBusyText === "undefined") { elem.removeAttribute("title"); }else { elem.setAttribute("title", isBusyText); } elem.classList.add("is-busy"); callback.call(this, myRating, function() { if (disabled === false) { elem.removeAttribute("title"); } isRating = false; elem.classList.remove("is-busy"); }); } } /** * Disables the rater so that it's not possible to click the stars. */ function disable() { disabled = true; elem.classList.add("disabled"); if (showToolTip && !!disableText) { let toolTip = disableText.replace("{rating}", !!rating ? rating : 0); toolTip = toolTip.replace("{maxRating}", stars); elem.setAttribute("title", toolTip); }else { elem.removeAttribute("title"); } } /** * Enabled the rater so that it's possible to click the stars. */ function enable() { disabled = false; elem.removeAttribute("title"); elem.classList.remove("disabled"); } /** * Sets the rating */ function setRating(value) { if (typeof value === "undefined") { throw new Error("Value not set."); } if (value === null) { throw new Error("Value cannot be null."); } if (typeof value !== "number") { throw new Error("Value must be a number."); } if (value < 0 || value > stars) { throw new Error("Value too high. Please set a rating of " + stars + " or below."); } rating = value; elem.querySelector(".star-value").style.width = value/stars * 100 + "%"; elem.setAttribute("data-rating", value); } /** * Gets the rating */ function getRating() { return rating; } /** * Set the rating to a value to inducate it's not rated. */ function clear() { rating = null; elem.querySelector(".star-value").style.width = "0px"; elem.removeAttribute("title"); } /** * Remove event handlers. */ function dispose() { elem.removeEventListener("mousemove", onMouseMove); elem.removeEventListener("mouseleave", onStarOut); elem.removeEventListener("click", onStarClick); elem.removeEventListener("touchmove", handleMove, false); elem.removeEventListener("touchstart", handleStart, false); elem.removeEventListener("touchend", handleEnd, false); elem.removeEventListener("touchcancel", handleCancel, false); } elem.addEventListener("mousemove", onMouseMove); elem.addEventListener("mouseleave", onStarOut); let module = { setRating:setRating, getRating:getRating, disable:disable, enable:enable, clear:clear, dispose:dispose, get element() { return elem; } }; /** * Handles touchmove event. * @param {TouchEvent} e */ function handleMove(e) { e.preventDefault(); onMove(e, true); } /** * Handles touchstart event. * @param {TouchEvent} e */ function handleStart(e) { e.preventDefault(); onMove(e,true); } /** * Handles touchend event. * @param {TouchEvent} e */ function handleEnd(evt) { evt.preventDefault(); onMove(evt,true); onStarClick.call(module); } /** * Handles touchend event. * @param {TouchEvent} e */ function handleCancel(e) { e.preventDefault(); onStarOut(e); } elem.addEventListener("click", onStarClick.bind(module)); elem.addEventListener("touchmove", handleMove, false); elem.addEventListener("touchstart", handleStart, false); elem.addEventListener("touchend", handleEnd, false); elem.addEventListener("touchcancel", handleCancel, false); return module; }