346 lines
7.9 KiB
JavaScript
346 lines
7.9 KiB
JavaScript
/*! 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;
|
|
} |