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; 
 | 
						|
} |