123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- /*! rangeslider.js - v2.3.2 | (c) 2018 @andreruffert | MIT license | https://github.com/andreruffert/rangeslider.js */
- (function(factory) {
- 'use strict';
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(['jquery'], factory);
- } else if (typeof exports === 'object') {
- // CommonJS
- module.exports = factory(require('jquery'));
- } else {
- // Browser globals
- factory(jQuery);
- }
- }(function($) {
- 'use strict';
- // Polyfill Number.isNaN(value)
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
- Number.isNaN = Number.isNaN || function(value) {
- return typeof value === 'number' && value !== value;
- };
- /**
- * Range feature detection
- * @return {Boolean}
- */
- function supportsRange() {
- var input = document.createElement('input');
- input.setAttribute('type', 'range');
- return input.type !== 'text';
- }
- var pluginName = 'rangeslider',
- pluginIdentifier = 0,
- hasInputRangeSupport = supportsRange(),
- defaults = {
- polyfill: true,
- orientation: 'horizontal',
- rangeClass: 'rangeslider',
- disabledClass: 'rangeslider--disabled',
- activeClass: 'rangeslider--active',
- horizontalClass: 'rangeslider--horizontal',
- verticalClass: 'rangeslider--vertical',
- fillClass: 'rangeslider__fill',
- handleClass: 'rangeslider__handle',
- startEvent: ['mousedown', 'touchstart', 'pointerdown'],
- moveEvent: ['mousemove', 'touchmove', 'pointermove'],
- endEvent: ['mouseup', 'touchend', 'pointerup']
- },
- constants = {
- orientation: {
- horizontal: {
- dimension: 'width',
- direction: 'left',
- directionStyle: 'left',
- coordinate: 'x'
- },
- vertical: {
- dimension: 'height',
- direction: 'top',
- directionStyle: 'bottom',
- coordinate: 'y'
- }
- }
- };
- /**
- * Delays a function for the given number of milliseconds, and then calls
- * it with the arguments supplied.
- *
- * @param {Function} fn [description]
- * @param {Number} wait [description]
- * @return {Function}
- */
- function delay(fn, wait) {
- var args = Array.prototype.slice.call(arguments, 2);
- return setTimeout(function(){ return fn.apply(null, args); }, wait);
- }
- /**
- * Returns a debounced function that will make sure the given
- * function is not triggered too much.
- *
- * @param {Function} fn Function to debounce.
- * @param {Number} debounceDuration OPTIONAL. The amount of time in milliseconds for which we will debounce the function. (defaults to 100ms)
- * @return {Function}
- */
- function debounce(fn, debounceDuration) {
- debounceDuration = debounceDuration || 100;
- return function() {
- if (!fn.debouncing) {
- var args = Array.prototype.slice.apply(arguments);
- fn.lastReturnVal = fn.apply(window, args);
- fn.debouncing = true;
- }
- clearTimeout(fn.debounceTimeout);
- fn.debounceTimeout = setTimeout(function(){
- fn.debouncing = false;
- }, debounceDuration);
- return fn.lastReturnVal;
- };
- }
- /**
- * Check if a `element` is visible in the DOM
- *
- * @param {Element} element
- * @return {Boolean}
- */
- function isHidden(element) {
- return (
- element && (
- element.offsetWidth === 0 ||
- element.offsetHeight === 0 ||
- // Also Consider native `<details>` elements.
- element.open === false
- )
- );
- }
- /**
- * Get hidden parentNodes of an `element`
- *
- * @param {Element} element
- * @return {[type]}
- */
- function getHiddenParentNodes(element) {
- var parents = [],
- node = element.parentNode;
- while (isHidden(node)) {
- parents.push(node);
- node = node.parentNode;
- }
- return parents;
- }
- /**
- * Returns dimensions for an element even if it is not visible in the DOM.
- *
- * @param {Element} element
- * @param {String} key (e.g. offsetWidth …)
- * @return {Number}
- */
- function getDimension(element, key) {
- var hiddenParentNodes = getHiddenParentNodes(element),
- hiddenParentNodesLength = hiddenParentNodes.length,
- inlineStyle = [],
- dimension = element[key];
- // Used for native `<details>` elements
- function toggleOpenProperty(element) {
- if (typeof element.open !== 'undefined') {
- element.open = (element.open) ? false : true;
- }
- }
- if (hiddenParentNodesLength) {
- for (var i = 0; i < hiddenParentNodesLength; i++) {
- // Cache style attribute to restore it later.
- inlineStyle[i] = hiddenParentNodes[i].style.cssText;
- // visually hide
- if (hiddenParentNodes[i].style.setProperty) {
- hiddenParentNodes[i].style.setProperty('display', 'block', 'important');
- } else {
- hiddenParentNodes[i].style.cssText += ';display: block !important';
- }
- hiddenParentNodes[i].style.height = '0';
- hiddenParentNodes[i].style.overflow = 'hidden';
- hiddenParentNodes[i].style.visibility = 'hidden';
- toggleOpenProperty(hiddenParentNodes[i]);
- }
- // Update dimension
- dimension = element[key];
- for (var j = 0; j < hiddenParentNodesLength; j++) {
- // Restore the style attribute
- hiddenParentNodes[j].style.cssText = inlineStyle[j];
- toggleOpenProperty(hiddenParentNodes[j]);
- }
- }
- return dimension;
- }
- /**
- * Returns the parsed float or the default if it failed.
- *
- * @param {String} str
- * @param {Number} defaultValue
- * @return {Number}
- */
- function tryParseFloat(str, defaultValue) {
- var value = parseFloat(str);
- return Number.isNaN(value) ? defaultValue : value;
- }
- /**
- * Capitalize the first letter of string
- *
- * @param {String} str
- * @return {String}
- */
- function ucfirst(str) {
- return str.charAt(0).toUpperCase() + str.substr(1);
- }
- /**
- * Plugin
- * @param {String} element
- * @param {Object} options
- */
- function Plugin(element, options) {
- this.$window = $(window);
- this.$document = $(document);
- this.$element = $(element);
- this.options = $.extend( {}, defaults, options );
- this.polyfill = this.options.polyfill;
- this.orientation = this.$element[0].getAttribute('data-orientation') || this.options.orientation;
- this.onInit = this.options.onInit;
- this.onSlide = this.options.onSlide;
- this.onSlideEnd = this.options.onSlideEnd;
- this.DIMENSION = constants.orientation[this.orientation].dimension;
- this.DIRECTION = constants.orientation[this.orientation].direction;
- this.DIRECTION_STYLE = constants.orientation[this.orientation].directionStyle;
- this.COORDINATE = constants.orientation[this.orientation].coordinate;
- // Plugin should only be used as a polyfill
- if (this.polyfill) {
- // Input range support?
- if (hasInputRangeSupport) { return false; }
- }
- this.identifier = 'js-' + pluginName + '-' +(pluginIdentifier++);
- this.startEvent = this.options.startEvent.join('.' + this.identifier + ' ') + '.' + this.identifier;
- this.moveEvent = this.options.moveEvent.join('.' + this.identifier + ' ') + '.' + this.identifier;
- this.endEvent = this.options.endEvent.join('.' + this.identifier + ' ') + '.' + this.identifier;
- this.toFixed = (this.step + '').replace('.', '').length - 1;
- this.$fill = $('<div class="' + this.options.fillClass + '" />');
- this.$handle = $('<div class="' + this.options.handleClass + '" />');
- this.$range = $('<div class="' + this.options.rangeClass + ' ' + this.options[this.orientation + 'Class'] + '" id="' + this.identifier + '" />').insertAfter(this.$element).prepend(this.$fill, this.$handle);
- // visually hide the input
- this.$element.css({
- 'position': 'absolute',
- 'width': '1px',
- 'height': '1px',
- 'overflow': 'hidden',
- 'opacity': '0'
- });
- // Store context
- this.handleDown = $.proxy(this.handleDown, this);
- this.handleMove = $.proxy(this.handleMove, this);
- this.handleEnd = $.proxy(this.handleEnd, this);
- this.init();
- // Attach Events
- var _this = this;
- this.$window.on('resize.' + this.identifier, debounce(function() {
- // Simulate resizeEnd event.
- delay(function() { _this.update(false, false); }, 300);
- }, 20));
- this.$document.on(this.startEvent, '#' + this.identifier + ':not(.' + this.options.disabledClass + ')', this.handleDown);
- // Listen to programmatic value changes
- this.$element.on('change.' + this.identifier, function(e, data) {
- if (data && data.origin === _this.identifier) {
- return;
- }
- var value = e.target.value,
- pos = _this.getPositionFromValue(value);
- _this.setPosition(pos);
- });
- }
- Plugin.prototype.init = function() {
- this.update(true, false);
- if (this.onInit && typeof this.onInit === 'function') {
- this.onInit();
- }
- };
- Plugin.prototype.update = function(updateAttributes, triggerSlide) {
- updateAttributes = updateAttributes || false;
- if (updateAttributes) {
- this.min = tryParseFloat(this.$element[0].getAttribute('min'), 0);
- this.max = tryParseFloat(this.$element[0].getAttribute('max'), 100);
- this.value = tryParseFloat(this.$element[0].value, Math.round(this.min + (this.max-this.min)/2));
- this.step = tryParseFloat(this.$element[0].getAttribute('step'), 1);
- }
- this.handleDimension = getDimension(this.$handle[0], 'offset' + ucfirst(this.DIMENSION));
- this.rangeDimension = getDimension(this.$range[0], 'offset' + ucfirst(this.DIMENSION));
- this.maxHandlePos = this.rangeDimension - this.handleDimension;
- this.grabPos = this.handleDimension / 2;
- this.position = this.getPositionFromValue(this.value);
- // Consider disabled state
- if (this.$element[0].disabled) {
- this.$range.addClass(this.options.disabledClass);
- } else {
- this.$range.removeClass(this.options.disabledClass);
- }
- this.setPosition(this.position, triggerSlide);
- };
- Plugin.prototype.handleDown = function(e) {
- e.preventDefault();
- // Only respond to mouse main button clicks (usually the left button)
- if (e.button && e.button !== 0) { return; }
- this.$document.on(this.moveEvent, this.handleMove);
- this.$document.on(this.endEvent, this.handleEnd);
- // add active class because Firefox is ignoring
- // the handle:active pseudo selector because of `e.preventDefault();`
- this.$range.addClass(this.options.activeClass);
- // If we click on the handle don't set the new position
- if ((' ' + e.target.className + ' ').replace(/[\n\t]/g, ' ').indexOf(this.options.handleClass) > -1) {
- return;
- }
- var pos = this.getRelativePosition(e),
- rangePos = this.$range[0].getBoundingClientRect()[this.DIRECTION],
- handlePos = this.getPositionFromNode(this.$handle[0]) - rangePos,
- setPos = (this.orientation === 'vertical') ? (this.maxHandlePos - (pos - this.grabPos)) : (pos - this.grabPos);
- this.setPosition(setPos);
- if (pos >= handlePos && pos < handlePos + this.handleDimension) {
- this.grabPos = pos - handlePos;
- }
- };
- Plugin.prototype.handleMove = function(e) {
- e.preventDefault();
- var pos = this.getRelativePosition(e);
- var setPos = (this.orientation === 'vertical') ? (this.maxHandlePos - (pos - this.grabPos)) : (pos - this.grabPos);
- this.setPosition(setPos);
- };
- Plugin.prototype.handleEnd = function(e) {
- e.preventDefault();
- this.$document.off(this.moveEvent, this.handleMove);
- this.$document.off(this.endEvent, this.handleEnd);
- this.$range.removeClass(this.options.activeClass);
- // Ok we're done fire the change event
- this.$element.trigger('change', { origin: this.identifier });
- if (this.onSlideEnd && typeof this.onSlideEnd === 'function') {
- this.onSlideEnd(this.position, this.value);
- }
- };
- Plugin.prototype.cap = function(pos, min, max) {
- if (pos < min) { return min; }
- if (pos > max) { return max; }
- return pos;
- };
- Plugin.prototype.setPosition = function(pos, triggerSlide) {
- var value, newPos;
- if (triggerSlide === undefined) {
- triggerSlide = true;
- }
- // Snapping steps
- value = this.getValueFromPosition(this.cap(pos, 0, this.maxHandlePos));
- newPos = this.getPositionFromValue(value);
- // Update ui
- this.$fill[0].style[this.DIMENSION] = (newPos + this.grabPos) + 'px';
- this.$handle[0].style[this.DIRECTION_STYLE] = newPos + 'px';
- this.setValue(value);
- // Update globals
- this.position = newPos;
- this.value = value;
- if (triggerSlide && this.onSlide && typeof this.onSlide === 'function') {
- this.onSlide(newPos, value);
- }
- };
- // Returns element position relative to the parent
- Plugin.prototype.getPositionFromNode = function(node) {
- var i = 0;
- while (node !== null) {
- i += node.offsetLeft;
- node = node.offsetParent;
- }
- return i;
- };
- Plugin.prototype.getRelativePosition = function(e) {
- // Get the offset DIRECTION relative to the viewport
- var ucCoordinate = ucfirst(this.COORDINATE),
- rangePos = this.$range[0].getBoundingClientRect()[this.DIRECTION],
- pageCoordinate = 0;
- if (typeof e.originalEvent['client' + ucCoordinate] !== 'undefined') {
- pageCoordinate = e.originalEvent['client' + ucCoordinate];
- }
- else if (
- e.originalEvent.touches &&
- e.originalEvent.touches[0] &&
- typeof e.originalEvent.touches[0]['client' + ucCoordinate] !== 'undefined'
- ) {
- pageCoordinate = e.originalEvent.touches[0]['client' + ucCoordinate];
- }
- else if(e.currentPoint && typeof e.currentPoint[this.COORDINATE] !== 'undefined') {
- pageCoordinate = e.currentPoint[this.COORDINATE];
- }
- return pageCoordinate - rangePos;
- };
- Plugin.prototype.getPositionFromValue = function(value) {
- var percentage, pos;
- percentage = (value - this.min)/(this.max - this.min);
- pos = (!Number.isNaN(percentage)) ? percentage * this.maxHandlePos : 0;
- return pos;
- };
- Plugin.prototype.getValueFromPosition = function(pos) {
- var percentage, value;
- percentage = ((pos) / (this.maxHandlePos || 1));
- value = this.step * Math.round(percentage * (this.max - this.min) / this.step) + this.min;
- return Number((value).toFixed(this.toFixed));
- };
- Plugin.prototype.setValue = function(value) {
- if (value === this.value && this.$element[0].value !== '') {
- return;
- }
- // Set the new value and fire the `input` event
- this.$element
- .val(value)
- .trigger('input', { origin: this.identifier });
- };
- Plugin.prototype.destroy = function() {
- this.$document.off('.' + this.identifier);
- this.$window.off('.' + this.identifier);
- this.$element
- .off('.' + this.identifier)
- .removeAttr('style')
- .removeData('plugin_' + pluginName);
- // Remove the generated markup
- if (this.$range && this.$range.length) {
- this.$range[0].parentNode.removeChild(this.$range[0]);
- }
- };
- // A really lightweight plugin wrapper around the constructor,
- // preventing against multiple instantiations
- $.fn[pluginName] = function(options) {
- var args = Array.prototype.slice.call(arguments, 1);
- return this.each(function() {
- var $this = $(this),
- data = $this.data('plugin_' + pluginName);
- // Create a new instance.
- if (!data) {
- $this.data('plugin_' + pluginName, (data = new Plugin(this, options)));
- }
- // Make it possible to access methods from public.
- // e.g `$element.rangeslider('method');`
- if (typeof options === 'string') {
- data[options].apply(data, args);
- }
- });
- };
- return 'rangeslider.js is available in jQuery context e.g $(selector).rangeslider(options);';
- }));
|