comparison query_builder/bootstrap/bootstrap-slider/js/bootstrap-slider.js @ 25:f82512502b31

Initial commit for query builder, still need to tailor for ISMI purposes
author alistair
date Mon, 23 Nov 2015 02:03:51 -0500
parents
children
comparison
equal deleted inserted replaced
24:eb0be386736f 25:f82512502b31
1 /*! =========================================================
2 * bootstrap-slider.js
3 *
4 * Maintainers:
5 * Kyle Kemp
6 * - Twitter: @seiyria
7 * - Github: seiyria
8 * Rohit Kalkur
9 * - Twitter: @Rovolutionary
10 * - Github: rovolution
11 *
12 * =========================================================
13 *
14 * Licensed under the Apache License, Version 2.0 (the "License");
15 * you may not use this file except in compliance with the License.
16 * You may obtain a copy of the License at
17 *
18 * http://www.apache.org/licenses/LICENSE-2.0
19 *
20 * Unless required by applicable law or agreed to in writing, software
21 * distributed under the License is distributed on an "AS IS" BASIS,
22 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 * See the License for the specific language governing permissions and
24 * limitations under the License.
25 * ========================================================= */
26
27
28 /**
29 * Bridget makes jQuery widgets
30 * v1.0.1
31 * MIT license
32 */
33
34 (function(root, factory) {
35 if(typeof define === "function" && define.amd) {
36 define(["jquery"], factory);
37 }
38 else if(typeof module === "object" && module.exports) {
39 var jQuery;
40 try {
41 jQuery = require("jquery");
42 }
43 catch (err) {
44 jQuery = null;
45 }
46 module.exports = factory(jQuery);
47 }
48 else {
49 root.Slider = factory(root.jQuery);
50 }
51 }(this, function($) {
52 // Reference to Slider constructor
53 var Slider;
54
55
56 (function( $ ) {
57
58 'use strict';
59
60 // -------------------------- utils -------------------------- //
61
62 var slice = Array.prototype.slice;
63
64 function noop() {}
65
66 // -------------------------- definition -------------------------- //
67
68 function defineBridget( $ ) {
69
70 // bail if no jQuery
71 if ( !$ ) {
72 return;
73 }
74
75 // -------------------------- addOptionMethod -------------------------- //
76
77 /**
78 * adds option method -> $().plugin('option', {...})
79 * @param {Function} PluginClass - constructor class
80 */
81 function addOptionMethod( PluginClass ) {
82 // don't overwrite original option method
83 if ( PluginClass.prototype.option ) {
84 return;
85 }
86
87 // option setter
88 PluginClass.prototype.option = function( opts ) {
89 // bail out if not an object
90 if ( !$.isPlainObject( opts ) ){
91 return;
92 }
93 this.options = $.extend( true, this.options, opts );
94 };
95 }
96
97
98 // -------------------------- plugin bridge -------------------------- //
99
100 // helper function for logging errors
101 // $.error breaks jQuery chaining
102 var logError = typeof console === 'undefined' ? noop :
103 function( message ) {
104 console.error( message );
105 };
106
107 /**
108 * jQuery plugin bridge, access methods like $elem.plugin('method')
109 * @param {String} namespace - plugin name
110 * @param {Function} PluginClass - constructor class
111 */
112 function bridge( namespace, PluginClass ) {
113 // add to jQuery fn namespace
114 $.fn[ namespace ] = function( options ) {
115 if ( typeof options === 'string' ) {
116 // call plugin method when first argument is a string
117 // get arguments for method
118 var args = slice.call( arguments, 1 );
119
120 for ( var i=0, len = this.length; i < len; i++ ) {
121 var elem = this[i];
122 var instance = $.data( elem, namespace );
123 if ( !instance ) {
124 logError( "cannot call methods on " + namespace + " prior to initialization; " +
125 "attempted to call '" + options + "'" );
126 continue;
127 }
128 if ( !$.isFunction( instance[options] ) || options.charAt(0) === '_' ) {
129 logError( "no such method '" + options + "' for " + namespace + " instance" );
130 continue;
131 }
132
133 // trigger method with arguments
134 var returnValue = instance[ options ].apply( instance, args);
135
136 // break look and return first value if provided
137 if ( returnValue !== undefined && returnValue !== instance) {
138 return returnValue;
139 }
140 }
141 // return this if no return value
142 return this;
143 } else {
144 var objects = this.map( function() {
145 var instance = $.data( this, namespace );
146 if ( instance ) {
147 // apply options & init
148 instance.option( options );
149 instance._init();
150 } else {
151 // initialize new instance
152 instance = new PluginClass( this, options );
153 $.data( this, namespace, instance );
154 }
155 return $(this);
156 });
157
158 if(!objects || objects.length > 1) {
159 return objects;
160 } else {
161 return objects[0];
162 }
163 }
164 };
165
166 }
167
168 // -------------------------- bridget -------------------------- //
169
170 /**
171 * converts a Prototypical class into a proper jQuery plugin
172 * the class must have a ._init method
173 * @param {String} namespace - plugin name, used in $().pluginName
174 * @param {Function} PluginClass - constructor class
175 */
176 $.bridget = function( namespace, PluginClass ) {
177 addOptionMethod( PluginClass );
178 bridge( namespace, PluginClass );
179 };
180
181 return $.bridget;
182
183 }
184
185 // get jquery from browser global
186 defineBridget( $ );
187
188 })( $ );
189
190
191 /*************************************************
192
193 BOOTSTRAP-SLIDER SOURCE CODE
194
195 **************************************************/
196
197 (function($) {
198
199 var ErrorMsgs = {
200 formatInvalidInputErrorMsg : function(input) {
201 return "Invalid input value '" + input + "' passed in";
202 },
203 callingContextNotSliderInstance : "Calling context element does not have instance of Slider bound to it. Check your code to make sure the JQuery object returned from the call to the slider() initializer is calling the method"
204 };
205
206 var SliderScale = {
207 linear: {
208 toValue: function(percentage) {
209 var rawValue = percentage/100 * (this.options.max - this.options.min);
210 if (this.options.ticks_positions.length > 0) {
211 var minv, maxv, minp, maxp = 0;
212 for (var i = 0; i < this.options.ticks_positions.length; i++) {
213 if (percentage <= this.options.ticks_positions[i]) {
214 minv = (i > 0) ? this.options.ticks[i-1] : 0;
215 minp = (i > 0) ? this.options.ticks_positions[i-1] : 0;
216 maxv = this.options.ticks[i];
217 maxp = this.options.ticks_positions[i];
218
219 break;
220 }
221 }
222 if (i > 0) {
223 var partialPercentage = (percentage - minp) / (maxp - minp);
224 rawValue = minv + partialPercentage * (maxv - minv);
225 }
226 }
227
228 var value = this.options.min + Math.round(rawValue / this.options.step) * this.options.step;
229 if (value < this.options.min) {
230 return this.options.min;
231 } else if (value > this.options.max) {
232 return this.options.max;
233 } else {
234 return value;
235 }
236 },
237 toPercentage: function(value) {
238 if (this.options.max === this.options.min) {
239 return 0;
240 }
241
242 if (this.options.ticks_positions.length > 0) {
243 var minv, maxv, minp, maxp = 0;
244 for (var i = 0; i < this.options.ticks.length; i++) {
245 if (value <= this.options.ticks[i]) {
246 minv = (i > 0) ? this.options.ticks[i-1] : 0;
247 minp = (i > 0) ? this.options.ticks_positions[i-1] : 0;
248 maxv = this.options.ticks[i];
249 maxp = this.options.ticks_positions[i];
250
251 break;
252 }
253 }
254 if (i > 0) {
255 var partialPercentage = (value - minv) / (maxv - minv);
256 return minp + partialPercentage * (maxp - minp);
257 }
258 }
259
260 return 100 * (value - this.options.min) / (this.options.max - this.options.min);
261 }
262 },
263
264 logarithmic: {
265 /* Based on http://stackoverflow.com/questions/846221/logarithmic-slider */
266 toValue: function(percentage) {
267 var min = (this.options.min === 0) ? 0 : Math.log(this.options.min);
268 var max = Math.log(this.options.max);
269 var value = Math.exp(min + (max - min) * percentage / 100);
270 value = this.options.min + Math.round((value - this.options.min) / this.options.step) * this.options.step;
271 /* Rounding to the nearest step could exceed the min or
272 * max, so clip to those values. */
273 if (value < this.options.min) {
274 return this.options.min;
275 } else if (value > this.options.max) {
276 return this.options.max;
277 } else {
278 return value;
279 }
280 },
281 toPercentage: function(value) {
282 if (this.options.max === this.options.min) {
283 return 0;
284 } else {
285 var max = Math.log(this.options.max);
286 var min = this.options.min === 0 ? 0 : Math.log(this.options.min);
287 var v = value === 0 ? 0 : Math.log(value);
288 return 100 * (v - min) / (max - min);
289 }
290 }
291 }
292 };
293
294
295 /*************************************************
296
297 CONSTRUCTOR
298
299 **************************************************/
300 Slider = function(element, options) {
301 createNewSlider.call(this, element, options);
302 return this;
303 };
304
305 function createNewSlider(element, options) {
306
307 /*
308 The internal state object is used to store data about the current 'state' of slider.
309
310 This includes values such as the `value`, `enabled`, etc...
311 */
312 this._state = {
313 value: null,
314 enabled: null,
315 offset: null,
316 size: null,
317 percentage: null,
318 inDrag: false,
319 over: false
320 };
321
322
323 if(typeof element === "string") {
324 this.element = document.querySelector(element);
325 } else if(element instanceof HTMLElement) {
326 this.element = element;
327 }
328
329 /*************************************************
330
331 Process Options
332
333 **************************************************/
334 options = options ? options : {};
335 var optionTypes = Object.keys(this.defaultOptions);
336
337 for(var i = 0; i < optionTypes.length; i++) {
338 var optName = optionTypes[i];
339
340 // First check if an option was passed in via the constructor
341 var val = options[optName];
342 // If no data attrib, then check data atrributes
343 val = (typeof val !== 'undefined') ? val : getDataAttrib(this.element, optName);
344 // Finally, if nothing was specified, use the defaults
345 val = (val !== null) ? val : this.defaultOptions[optName];
346
347 // Set all options on the instance of the Slider
348 if(!this.options) {
349 this.options = {};
350 }
351 this.options[optName] = val;
352 }
353
354 /*
355 Validate `tooltip_position` against 'orientation`
356 - if `tooltip_position` is incompatible with orientation, swith it to a default compatible with specified `orientation`
357 -- default for "vertical" -> "right"
358 -- default for "horizontal" -> "left"
359 */
360 if(this.options.orientation === "vertical" && (this.options.tooltip_position === "top" || this.options.tooltip_position === "bottom")) {
361
362 this.options.tooltip_position = "right";
363
364 }
365 else if(this.options.orientation === "horizontal" && (this.options.tooltip_position === "left" || this.options.tooltip_position === "right")) {
366
367 this.options.tooltip_position = "top";
368
369 }
370
371 function getDataAttrib(element, optName) {
372 var dataName = "data-slider-" + optName.replace(/_/g, '-');
373 var dataValString = element.getAttribute(dataName);
374
375 try {
376 return JSON.parse(dataValString);
377 }
378 catch(err) {
379 return dataValString;
380 }
381 }
382
383 /*************************************************
384
385 Create Markup
386
387 **************************************************/
388
389 var origWidth = this.element.style.width;
390 var updateSlider = false;
391 var parent = this.element.parentNode;
392 var sliderTrackSelection;
393 var sliderTrackLow, sliderTrackHigh;
394 var sliderMinHandle;
395 var sliderMaxHandle;
396
397 if (this.sliderElem) {
398 updateSlider = true;
399 } else {
400 /* Create elements needed for slider */
401 this.sliderElem = document.createElement("div");
402 this.sliderElem.className = "slider";
403
404 /* Create slider track elements */
405 var sliderTrack = document.createElement("div");
406 sliderTrack.className = "slider-track";
407
408 sliderTrackLow = document.createElement("div");
409 sliderTrackLow.className = "slider-track-low";
410
411 sliderTrackSelection = document.createElement("div");
412 sliderTrackSelection.className = "slider-selection";
413
414 sliderTrackHigh = document.createElement("div");
415 sliderTrackHigh.className = "slider-track-high";
416
417 sliderMinHandle = document.createElement("div");
418 sliderMinHandle.className = "slider-handle min-slider-handle";
419
420 sliderMaxHandle = document.createElement("div");
421 sliderMaxHandle.className = "slider-handle max-slider-handle";
422
423 sliderTrack.appendChild(sliderTrackLow);
424 sliderTrack.appendChild(sliderTrackSelection);
425 sliderTrack.appendChild(sliderTrackHigh);
426
427 /* Create ticks */
428 this.ticks = [];
429 if (Array.isArray(this.options.ticks) && this.options.ticks.length > 0) {
430 for (i = 0; i < this.options.ticks.length; i++) {
431 var tick = document.createElement('div');
432 tick.className = 'slider-tick';
433
434 this.ticks.push(tick);
435 sliderTrack.appendChild(tick);
436 }
437
438 sliderTrackSelection.className += " tick-slider-selection";
439 }
440
441 sliderTrack.appendChild(sliderMinHandle);
442 sliderTrack.appendChild(sliderMaxHandle);
443
444 this.tickLabels = [];
445 if (Array.isArray(this.options.ticks_labels) && this.options.ticks_labels.length > 0) {
446 this.tickLabelContainer = document.createElement('div');
447 this.tickLabelContainer.className = 'slider-tick-label-container';
448
449 for (i = 0; i < this.options.ticks_labels.length; i++) {
450 var label = document.createElement('div');
451 var noTickPositionsSpecified = this.options.ticks_positions.length === 0;
452 var tickLabelsIndex = (this.options.reversed && noTickPositionsSpecified) ? (this.options.ticks_labels.length - (i + 1)) : i;
453 label.className = 'slider-tick-label';
454 label.innerHTML = this.options.ticks_labels[tickLabelsIndex];
455
456 this.tickLabels.push(label);
457 this.tickLabelContainer.appendChild(label);
458 }
459 }
460
461
462 var createAndAppendTooltipSubElements = function(tooltipElem) {
463 var arrow = document.createElement("div");
464 arrow.className = "tooltip-arrow";
465
466 var inner = document.createElement("div");
467 inner.className = "tooltip-inner";
468
469 tooltipElem.appendChild(arrow);
470 tooltipElem.appendChild(inner);
471
472 };
473
474 /* Create tooltip elements */
475 var sliderTooltip = document.createElement("div");
476 sliderTooltip.className = "tooltip tooltip-main";
477 createAndAppendTooltipSubElements(sliderTooltip);
478
479 var sliderTooltipMin = document.createElement("div");
480 sliderTooltipMin.className = "tooltip tooltip-min";
481 createAndAppendTooltipSubElements(sliderTooltipMin);
482
483 var sliderTooltipMax = document.createElement("div");
484 sliderTooltipMax.className = "tooltip tooltip-max";
485 createAndAppendTooltipSubElements(sliderTooltipMax);
486
487
488 /* Append components to sliderElem */
489 this.sliderElem.appendChild(sliderTrack);
490 this.sliderElem.appendChild(sliderTooltip);
491 this.sliderElem.appendChild(sliderTooltipMin);
492 this.sliderElem.appendChild(sliderTooltipMax);
493
494 if (this.tickLabelContainer) {
495 this.sliderElem.appendChild(this.tickLabelContainer);
496 }
497
498 /* Append slider element to parent container, right before the original <input> element */
499 parent.insertBefore(this.sliderElem, this.element);
500
501 /* Hide original <input> element */
502 this.element.style.display = "none";
503 }
504 /* If JQuery exists, cache JQ references */
505 if($) {
506 this.$element = $(this.element);
507 this.$sliderElem = $(this.sliderElem);
508 }
509
510 /*************************************************
511
512 Setup
513
514 **************************************************/
515 this.eventToCallbackMap = {};
516 this.sliderElem.id = this.options.id;
517
518 this.touchCapable = 'ontouchstart' in window || (window.DocumentTouch && document instanceof window.DocumentTouch);
519
520 this.tooltip = this.sliderElem.querySelector('.tooltip-main');
521 this.tooltipInner = this.tooltip.querySelector('.tooltip-inner');
522
523 this.tooltip_min = this.sliderElem.querySelector('.tooltip-min');
524 this.tooltipInner_min = this.tooltip_min.querySelector('.tooltip-inner');
525
526 this.tooltip_max = this.sliderElem.querySelector('.tooltip-max');
527 this.tooltipInner_max= this.tooltip_max.querySelector('.tooltip-inner');
528
529 if (SliderScale[this.options.scale]) {
530 this.options.scale = SliderScale[this.options.scale];
531 }
532
533 if (updateSlider === true) {
534 // Reset classes
535 this._removeClass(this.sliderElem, 'slider-horizontal');
536 this._removeClass(this.sliderElem, 'slider-vertical');
537 this._removeClass(this.tooltip, 'hide');
538 this._removeClass(this.tooltip_min, 'hide');
539 this._removeClass(this.tooltip_max, 'hide');
540
541 // Undo existing inline styles for track
542 ["left", "top", "width", "height"].forEach(function(prop) {
543 this._removeProperty(this.trackLow, prop);
544 this._removeProperty(this.trackSelection, prop);
545 this._removeProperty(this.trackHigh, prop);
546 }, this);
547
548 // Undo inline styles on handles
549 [this.handle1, this.handle2].forEach(function(handle) {
550 this._removeProperty(handle, 'left');
551 this._removeProperty(handle, 'top');
552 }, this);
553
554 // Undo inline styles and classes on tooltips
555 [this.tooltip, this.tooltip_min, this.tooltip_max].forEach(function(tooltip) {
556 this._removeProperty(tooltip, 'left');
557 this._removeProperty(tooltip, 'top');
558 this._removeProperty(tooltip, 'margin-left');
559 this._removeProperty(tooltip, 'margin-top');
560
561 this._removeClass(tooltip, 'right');
562 this._removeClass(tooltip, 'top');
563 }, this);
564 }
565
566 if(this.options.orientation === 'vertical') {
567 this._addClass(this.sliderElem,'slider-vertical');
568 this.stylePos = 'top';
569 this.mousePos = 'pageY';
570 this.sizePos = 'offsetHeight';
571 } else {
572 this._addClass(this.sliderElem, 'slider-horizontal');
573 this.sliderElem.style.width = origWidth;
574 this.options.orientation = 'horizontal';
575 this.stylePos = 'left';
576 this.mousePos = 'pageX';
577 this.sizePos = 'offsetWidth';
578
579 }
580 this._setTooltipPosition();
581 /* In case ticks are specified, overwrite the min and max bounds */
582 if (Array.isArray(this.options.ticks) && this.options.ticks.length > 0) {
583 this.options.max = Math.max.apply(Math, this.options.ticks);
584 this.options.min = Math.min.apply(Math, this.options.ticks);
585 }
586
587 if (Array.isArray(this.options.value)) {
588 this.options.range = true;
589 this._state.value = this.options.value;
590 }
591 else if (this.options.range) {
592 // User wants a range, but value is not an array
593 this._state.value = [this.options.value, this.options.max];
594 }
595 else {
596 this._state.value = this.options.value;
597 }
598
599 this.trackLow = sliderTrackLow || this.trackLow;
600 this.trackSelection = sliderTrackSelection || this.trackSelection;
601 this.trackHigh = sliderTrackHigh || this.trackHigh;
602
603 if (this.options.selection === 'none') {
604 this._addClass(this.trackLow, 'hide');
605 this._addClass(this.trackSelection, 'hide');
606 this._addClass(this.trackHigh, 'hide');
607 }
608
609 this.handle1 = sliderMinHandle || this.handle1;
610 this.handle2 = sliderMaxHandle || this.handle2;
611
612 if (updateSlider === true) {
613 // Reset classes
614 this._removeClass(this.handle1, 'round triangle');
615 this._removeClass(this.handle2, 'round triangle hide');
616
617 for (i = 0; i < this.ticks.length; i++) {
618 this._removeClass(this.ticks[i], 'round triangle hide');
619 }
620 }
621
622 var availableHandleModifiers = ['round', 'triangle', 'custom'];
623 var isValidHandleType = availableHandleModifiers.indexOf(this.options.handle) !== -1;
624 if (isValidHandleType) {
625 this._addClass(this.handle1, this.options.handle);
626 this._addClass(this.handle2, this.options.handle);
627
628 for (i = 0; i < this.ticks.length; i++) {
629 this._addClass(this.ticks[i], this.options.handle);
630 }
631 }
632
633 this._state.offset = this._offset(this.sliderElem);
634 this._state.size = this.sliderElem[this.sizePos];
635 this.setValue(this._state.value);
636
637 /******************************************
638
639 Bind Event Listeners
640
641 ******************************************/
642
643 // Bind keyboard handlers
644 this.handle1Keydown = this._keydown.bind(this, 0);
645 this.handle1.addEventListener("keydown", this.handle1Keydown, false);
646
647 this.handle2Keydown = this._keydown.bind(this, 1);
648 this.handle2.addEventListener("keydown", this.handle2Keydown, false);
649
650 this.mousedown = this._mousedown.bind(this);
651 if (this.touchCapable) {
652 // Bind touch handlers
653 this.sliderElem.addEventListener("touchstart", this.mousedown, false);
654 }
655 this.sliderElem.addEventListener("mousedown", this.mousedown, false);
656
657
658 // Bind tooltip-related handlers
659 if(this.options.tooltip === 'hide') {
660 this._addClass(this.tooltip, 'hide');
661 this._addClass(this.tooltip_min, 'hide');
662 this._addClass(this.tooltip_max, 'hide');
663 }
664 else if(this.options.tooltip === 'always') {
665 this._showTooltip();
666 this._alwaysShowTooltip = true;
667 }
668 else {
669 this.showTooltip = this._showTooltip.bind(this);
670 this.hideTooltip = this._hideTooltip.bind(this);
671
672 this.sliderElem.addEventListener("mouseenter", this.showTooltip, false);
673 this.sliderElem.addEventListener("mouseleave", this.hideTooltip, false);
674
675 this.handle1.addEventListener("focus", this.showTooltip, false);
676 this.handle1.addEventListener("blur", this.hideTooltip, false);
677
678 this.handle2.addEventListener("focus", this.showTooltip, false);
679 this.handle2.addEventListener("blur", this.hideTooltip, false);
680 }
681
682 if(this.options.enabled) {
683 this.enable();
684 } else {
685 this.disable();
686 }
687 }
688
689
690
691 /*************************************************
692
693 INSTANCE PROPERTIES/METHODS
694
695 - Any methods bound to the prototype are considered
696 part of the plugin's `public` interface
697
698 **************************************************/
699 Slider.prototype = {
700 _init: function() {}, // NOTE: Must exist to support bridget
701
702 constructor: Slider,
703
704 defaultOptions: {
705 id: "",
706 min: 0,
707 max: 10,
708 step: 1,
709 precision: 0,
710 orientation: 'horizontal',
711 value: 5,
712 range: false,
713 selection: 'before',
714 tooltip: 'show',
715 tooltip_split: false,
716 handle: 'round',
717 reversed: false,
718 enabled: true,
719 formatter: function(val) {
720 if (Array.isArray(val)) {
721 return val[0] + " : " + val[1];
722 } else {
723 return val;
724 }
725 },
726 natural_arrow_keys: false,
727 ticks: [],
728 ticks_positions: [],
729 ticks_labels: [],
730 ticks_snap_bounds: 0,
731 scale: 'linear',
732 focus: false,
733 tooltip_position: null
734 },
735
736 getElement: function() {
737 return this.sliderElem;
738 },
739
740 getValue: function() {
741 if (this.options.range) {
742 return this._state.value;
743 }
744 else {
745 return this._state.value[0];
746 }
747 },
748
749 setValue: function(val, triggerSlideEvent, triggerChangeEvent) {
750 if (!val) {
751 val = 0;
752 }
753 var oldValue = this.getValue();
754 this._state.value = this._validateInputValue(val);
755 var applyPrecision = this._applyPrecision.bind(this);
756
757 if (this.options.range) {
758 this._state.value[0] = applyPrecision(this._state.value[0]);
759 this._state.value[1] = applyPrecision(this._state.value[1]);
760
761 this._state.value[0] = Math.max(this.options.min, Math.min(this.options.max, this._state.value[0]));
762 this._state.value[1] = Math.max(this.options.min, Math.min(this.options.max, this._state.value[1]));
763 }
764 else {
765 this._state.value = applyPrecision(this._state.value);
766 this._state.value = [ Math.max(this.options.min, Math.min(this.options.max, this._state.value))];
767 this._addClass(this.handle2, 'hide');
768 if (this.options.selection === 'after') {
769 this._state.value[1] = this.options.max;
770 } else {
771 this._state.value[1] = this.options.min;
772 }
773 }
774
775 if (this.options.max > this.options.min) {
776 this._state.percentage = [
777 this._toPercentage(this._state.value[0]),
778 this._toPercentage(this._state.value[1]),
779 this.options.step * 100 / (this.options.max - this.options.min)
780 ];
781 } else {
782 this._state.percentage = [0, 0, 100];
783 }
784
785 this._layout();
786 var newValue = this.options.range ? this._state.value : this._state.value[0];
787
788 if(triggerSlideEvent === true) {
789 this._trigger('slide', newValue);
790 }
791 if( (oldValue !== newValue) && (triggerChangeEvent === true) ) {
792 this._trigger('change', {
793 oldValue: oldValue,
794 newValue: newValue
795 });
796 }
797 this._setDataVal(newValue);
798
799 return this;
800 },
801
802 destroy: function(){
803 // Remove event handlers on slider elements
804 this._removeSliderEventHandlers();
805
806 // Remove the slider from the DOM
807 this.sliderElem.parentNode.removeChild(this.sliderElem);
808 /* Show original <input> element */
809 this.element.style.display = "";
810
811 // Clear out custom event bindings
812 this._cleanUpEventCallbacksMap();
813
814 // Remove data values
815 this.element.removeAttribute("data");
816
817 // Remove JQuery handlers/data
818 if($) {
819 this._unbindJQueryEventHandlers();
820 this.$element.removeData('slider');
821 }
822 },
823
824 disable: function() {
825 this._state.enabled = false;
826 this.handle1.removeAttribute("tabindex");
827 this.handle2.removeAttribute("tabindex");
828 this._addClass(this.sliderElem, 'slider-disabled');
829 this._trigger('slideDisabled');
830
831 return this;
832 },
833
834 enable: function() {
835 this._state.enabled = true;
836 this.handle1.setAttribute("tabindex", 0);
837 this.handle2.setAttribute("tabindex", 0);
838 this._removeClass(this.sliderElem, 'slider-disabled');
839 this._trigger('slideEnabled');
840
841 return this;
842 },
843
844 toggle: function() {
845 if(this._state.enabled) {
846 this.disable();
847 } else {
848 this.enable();
849 }
850 return this;
851 },
852
853 isEnabled: function() {
854 return this._state.enabled;
855 },
856
857 on: function(evt, callback) {
858 this._bindNonQueryEventHandler(evt, callback);
859 return this;
860 },
861
862 off: function(evt, callback) {
863 if($) {
864 this.$element.off(evt, callback);
865 this.$sliderElem.off(evt, callback);
866 } else {
867 this._unbindNonQueryEventHandler(evt, callback);
868 }
869 },
870
871 getAttribute: function(attribute) {
872 if(attribute) {
873 return this.options[attribute];
874 } else {
875 return this.options;
876 }
877 },
878
879 setAttribute: function(attribute, value) {
880 this.options[attribute] = value;
881 return this;
882 },
883
884 refresh: function() {
885 this._removeSliderEventHandlers();
886 createNewSlider.call(this, this.element, this.options);
887 if($) {
888 // Bind new instance of slider to the element
889 $.data(this.element, 'slider', this);
890 }
891 return this;
892 },
893
894 relayout: function() {
895 this._layout();
896 return this;
897 },
898
899 /******************************+
900
901 HELPERS
902
903 - Any method that is not part of the public interface.
904 - Place it underneath this comment block and write its signature like so:
905
906 _fnName : function() {...}
907
908 ********************************/
909 _removeSliderEventHandlers: function() {
910 // Remove keydown event listeners
911 this.handle1.removeEventListener("keydown", this.handle1Keydown, false);
912 this.handle2.removeEventListener("keydown", this.handle2Keydown, false);
913
914 if (this.showTooltip) {
915 this.handle1.removeEventListener("focus", this.showTooltip, false);
916 this.handle2.removeEventListener("focus", this.showTooltip, false);
917 }
918 if (this.hideTooltip) {
919 this.handle1.removeEventListener("blur", this.hideTooltip, false);
920 this.handle2.removeEventListener("blur", this.hideTooltip, false);
921 }
922
923 // Remove event listeners from sliderElem
924 if (this.showTooltip) {
925 this.sliderElem.removeEventListener("mouseenter", this.showTooltip, false);
926 }
927 if (this.hideTooltip) {
928 this.sliderElem.removeEventListener("mouseleave", this.hideTooltip, false);
929 }
930 this.sliderElem.removeEventListener("touchstart", this.mousedown, false);
931 this.sliderElem.removeEventListener("mousedown", this.mousedown, false);
932 },
933 _bindNonQueryEventHandler: function(evt, callback) {
934 if(this.eventToCallbackMap[evt] === undefined) {
935 this.eventToCallbackMap[evt] = [];
936 }
937 this.eventToCallbackMap[evt].push(callback);
938 },
939 _unbindNonQueryEventHandler: function(evt, callback) {
940 var callbacks = this.eventToCallbackMap[evt];
941 if(callbacks !== undefined) {
942 for (var i = 0; i < callbacks.length; i++) {
943 if (callbacks[i] === callback) {
944 callbacks.splice(i, 1);
945 break;
946 }
947 }
948 }
949 },
950 _cleanUpEventCallbacksMap: function() {
951 var eventNames = Object.keys(this.eventToCallbackMap);
952 for(var i = 0; i < eventNames.length; i++) {
953 var eventName = eventNames[i];
954 this.eventToCallbackMap[eventName] = null;
955 }
956 },
957 _showTooltip: function() {
958 if (this.options.tooltip_split === false ){
959 this._addClass(this.tooltip, 'in');
960 this.tooltip_min.style.display = 'none';
961 this.tooltip_max.style.display = 'none';
962 } else {
963 this._addClass(this.tooltip_min, 'in');
964 this._addClass(this.tooltip_max, 'in');
965 this.tooltip.style.display = 'none';
966 }
967 this._state.over = true;
968 },
969 _hideTooltip: function() {
970 if (this._state.inDrag === false && this.alwaysShowTooltip !== true) {
971 this._removeClass(this.tooltip, 'in');
972 this._removeClass(this.tooltip_min, 'in');
973 this._removeClass(this.tooltip_max, 'in');
974 }
975 this._state.over = false;
976 },
977 _layout: function() {
978 var positionPercentages;
979
980 if(this.options.reversed) {
981 positionPercentages = [ 100 - this._state.percentage[0], this.options.range ? 100 - this._state.percentage[1] : this._state.percentage[1]];
982 }
983 else {
984 positionPercentages = [ this._state.percentage[0], this._state.percentage[1] ];
985 }
986
987 this.handle1.style[this.stylePos] = positionPercentages[0]+'%';
988 this.handle2.style[this.stylePos] = positionPercentages[1]+'%';
989
990 /* Position ticks and labels */
991 if (Array.isArray(this.options.ticks) && this.options.ticks.length > 0) {
992
993 var styleSize = this.options.orientation === 'vertical' ? 'height' : 'width';
994 var styleMargin = this.options.orientation === 'vertical' ? 'marginTop' : 'marginLeft';
995 var labelSize = this._state.size / (this.options.ticks.length - 1);
996
997 if (this.tickLabelContainer) {
998 var extraMargin = 0;
999 if (this.options.ticks_positions.length === 0) {
1000 if (this.options.orientation !== 'vertical') {
1001 this.tickLabelContainer.style[styleMargin] = -labelSize/2 + 'px';
1002 }
1003
1004 extraMargin = this.tickLabelContainer.offsetHeight;
1005 } else {
1006 /* Chidren are position absolute, calculate height by finding the max offsetHeight of a child */
1007 for (i = 0 ; i < this.tickLabelContainer.childNodes.length; i++) {
1008 if (this.tickLabelContainer.childNodes[i].offsetHeight > extraMargin) {
1009 extraMargin = this.tickLabelContainer.childNodes[i].offsetHeight;
1010 }
1011 }
1012 }
1013 if (this.options.orientation === 'horizontal') {
1014 this.sliderElem.style.marginBottom = extraMargin + 'px';
1015 }
1016 }
1017 for (var i = 0; i < this.options.ticks.length; i++) {
1018
1019 var percentage = this.options.ticks_positions[i] || this._toPercentage(this.options.ticks[i]);
1020
1021 if (this.options.reversed) {
1022 percentage = 100 - percentage;
1023 }
1024
1025 this.ticks[i].style[this.stylePos] = percentage + '%';
1026
1027 /* Set class labels to denote whether ticks are in the selection */
1028 this._removeClass(this.ticks[i], 'in-selection');
1029 if (!this.options.range) {
1030 if (this.options.selection === 'after' && percentage >= positionPercentages[0]){
1031 this._addClass(this.ticks[i], 'in-selection');
1032 } else if (this.options.selection === 'before' && percentage <= positionPercentages[0]) {
1033 this._addClass(this.ticks[i], 'in-selection');
1034 }
1035 } else if (percentage >= positionPercentages[0] && percentage <= positionPercentages[1]) {
1036 this._addClass(this.ticks[i], 'in-selection');
1037 }
1038
1039 if (this.tickLabels[i]) {
1040 this.tickLabels[i].style[styleSize] = labelSize + 'px';
1041
1042 if (this.options.orientation !== 'vertical' && this.options.ticks_positions[i] !== undefined) {
1043 this.tickLabels[i].style.position = 'absolute';
1044 this.tickLabels[i].style[this.stylePos] = percentage + '%';
1045 this.tickLabels[i].style[styleMargin] = -labelSize/2 + 'px';
1046 } else if (this.options.orientation === 'vertical') {
1047 this.tickLabels[i].style['marginLeft'] = this.sliderElem.offsetWidth + 'px';
1048 this.tickLabelContainer.style['marginTop'] = this.sliderElem.offsetWidth / 2 * -1 + 'px';
1049 }
1050 }
1051 }
1052 }
1053
1054 var formattedTooltipVal;
1055
1056 if (this.options.range) {
1057 formattedTooltipVal = this.options.formatter(this._state.value);
1058 this._setText(this.tooltipInner, formattedTooltipVal);
1059 this.tooltip.style[this.stylePos] = (positionPercentages[1] + positionPercentages[0])/2 + '%';
1060
1061 if (this.options.orientation === 'vertical') {
1062 this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px');
1063 } else {
1064 this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px');
1065 }
1066
1067 if (this.options.orientation === 'vertical') {
1068 this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px');
1069 } else {
1070 this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px');
1071 }
1072
1073 var innerTooltipMinText = this.options.formatter(this._state.value[0]);
1074 this._setText(this.tooltipInner_min, innerTooltipMinText);
1075
1076 var innerTooltipMaxText = this.options.formatter(this._state.value[1]);
1077 this._setText(this.tooltipInner_max, innerTooltipMaxText);
1078
1079 this.tooltip_min.style[this.stylePos] = positionPercentages[0] + '%';
1080
1081 if (this.options.orientation === 'vertical') {
1082 this._css(this.tooltip_min, 'margin-top', -this.tooltip_min.offsetHeight / 2 + 'px');
1083 } else {
1084 this._css(this.tooltip_min, 'margin-left', -this.tooltip_min.offsetWidth / 2 + 'px');
1085 }
1086
1087 this.tooltip_max.style[this.stylePos] = positionPercentages[1] + '%';
1088
1089 if (this.options.orientation === 'vertical') {
1090 this._css(this.tooltip_max, 'margin-top', -this.tooltip_max.offsetHeight / 2 + 'px');
1091 } else {
1092 this._css(this.tooltip_max, 'margin-left', -this.tooltip_max.offsetWidth / 2 + 'px');
1093 }
1094 } else {
1095 formattedTooltipVal = this.options.formatter(this._state.value[0]);
1096 this._setText(this.tooltipInner, formattedTooltipVal);
1097
1098 this.tooltip.style[this.stylePos] = positionPercentages[0] + '%';
1099 if (this.options.orientation === 'vertical') {
1100 this._css(this.tooltip, 'margin-top', -this.tooltip.offsetHeight / 2 + 'px');
1101 } else {
1102 this._css(this.tooltip, 'margin-left', -this.tooltip.offsetWidth / 2 + 'px');
1103 }
1104 }
1105
1106 if (this.options.orientation === 'vertical') {
1107 this.trackLow.style.top = '0';
1108 this.trackLow.style.height = Math.min(positionPercentages[0], positionPercentages[1]) +'%';
1109
1110 this.trackSelection.style.top = Math.min(positionPercentages[0], positionPercentages[1]) +'%';
1111 this.trackSelection.style.height = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%';
1112
1113 this.trackHigh.style.bottom = '0';
1114 this.trackHigh.style.height = (100 - Math.min(positionPercentages[0], positionPercentages[1]) - Math.abs(positionPercentages[0] - positionPercentages[1])) +'%';
1115 }
1116 else {
1117 this.trackLow.style.left = '0';
1118 this.trackLow.style.width = Math.min(positionPercentages[0], positionPercentages[1]) +'%';
1119
1120 this.trackSelection.style.left = Math.min(positionPercentages[0], positionPercentages[1]) +'%';
1121 this.trackSelection.style.width = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%';
1122
1123 this.trackHigh.style.right = '0';
1124 this.trackHigh.style.width = (100 - Math.min(positionPercentages[0], positionPercentages[1]) - Math.abs(positionPercentages[0] - positionPercentages[1])) +'%';
1125
1126 var offset_min = this.tooltip_min.getBoundingClientRect();
1127 var offset_max = this.tooltip_max.getBoundingClientRect();
1128
1129 if (offset_min.right > offset_max.left) {
1130 this._removeClass(this.tooltip_max, 'top');
1131 this._addClass(this.tooltip_max, 'bottom');
1132 this.tooltip_max.style.top = 18 + 'px';
1133 } else {
1134 this._removeClass(this.tooltip_max, 'bottom');
1135 this._addClass(this.tooltip_max, 'top');
1136 this.tooltip_max.style.top = this.tooltip_min.style.top;
1137 }
1138 }
1139 },
1140 _removeProperty: function(element, prop) {
1141 if (element.style.removeProperty) {
1142 element.style.removeProperty(prop);
1143 } else {
1144 element.style.removeAttribute(prop);
1145 }
1146 },
1147 _mousedown: function(ev) {
1148 if(!this._state.enabled) {
1149 return false;
1150 }
1151
1152 this._state.offset = this._offset(this.sliderElem);
1153 this._state.size = this.sliderElem[this.sizePos];
1154
1155 var percentage = this._getPercentage(ev);
1156
1157 if (this.options.range) {
1158 var diff1 = Math.abs(this._state.percentage[0] - percentage);
1159 var diff2 = Math.abs(this._state.percentage[1] - percentage);
1160 this._state.dragged = (diff1 < diff2) ? 0 : 1;
1161 } else {
1162 this._state.dragged = 0;
1163 }
1164
1165 this._state.percentage[this._state.dragged] = percentage;
1166 this._layout();
1167
1168 if (this.touchCapable) {
1169 document.removeEventListener("touchmove", this.mousemove, false);
1170 document.removeEventListener("touchend", this.mouseup, false);
1171 }
1172
1173 if(this.mousemove){
1174 document.removeEventListener("mousemove", this.mousemove, false);
1175 }
1176 if(this.mouseup){
1177 document.removeEventListener("mouseup", this.mouseup, false);
1178 }
1179
1180 this.mousemove = this._mousemove.bind(this);
1181 this.mouseup = this._mouseup.bind(this);
1182
1183 if (this.touchCapable) {
1184 // Touch: Bind touch events:
1185 document.addEventListener("touchmove", this.mousemove, false);
1186 document.addEventListener("touchend", this.mouseup, false);
1187 }
1188 // Bind mouse events:
1189 document.addEventListener("mousemove", this.mousemove, false);
1190 document.addEventListener("mouseup", this.mouseup, false);
1191
1192 this._state.inDrag = true;
1193 var newValue = this._calculateValue();
1194
1195 this._trigger('slideStart', newValue);
1196
1197 this._setDataVal(newValue);
1198 this.setValue(newValue, false, true);
1199
1200 this._pauseEvent(ev);
1201
1202 if (this.options.focus) {
1203 this._triggerFocusOnHandle(this._state.dragged);
1204 }
1205
1206 return true;
1207 },
1208 _triggerFocusOnHandle: function(handleIdx) {
1209 if(handleIdx === 0) {
1210 this.handle1.focus();
1211 }
1212 if(handleIdx === 1) {
1213 this.handle2.focus();
1214 }
1215 },
1216 _keydown: function(handleIdx, ev) {
1217 if(!this._state.enabled) {
1218 return false;
1219 }
1220
1221 var dir;
1222 switch (ev.keyCode) {
1223 case 37: // left
1224 case 40: // down
1225 dir = -1;
1226 break;
1227 case 39: // right
1228 case 38: // up
1229 dir = 1;
1230 break;
1231 }
1232 if (!dir) {
1233 return;
1234 }
1235
1236 // use natural arrow keys instead of from min to max
1237 if (this.options.natural_arrow_keys) {
1238 var ifVerticalAndNotReversed = (this.options.orientation === 'vertical' && !this.options.reversed);
1239 var ifHorizontalAndReversed = (this.options.orientation === 'horizontal' && this.options.reversed);
1240
1241 if (ifVerticalAndNotReversed || ifHorizontalAndReversed) {
1242 dir = -dir;
1243 }
1244 }
1245
1246 var val = this._state.value[handleIdx] + dir * this.options.step;
1247 if (this.options.range) {
1248 val = [ (!handleIdx) ? val : this._state.value[0],
1249 ( handleIdx) ? val : this._state.value[1]];
1250 }
1251
1252 this._trigger('slideStart', val);
1253 this._setDataVal(val);
1254 this.setValue(val, true, true);
1255
1256 this._setDataVal(val);
1257 this._trigger('slideStop', val);
1258 this._layout();
1259
1260 this._pauseEvent(ev);
1261
1262 return false;
1263 },
1264 _pauseEvent: function(ev) {
1265 if(ev.stopPropagation) {
1266 ev.stopPropagation();
1267 }
1268 if(ev.preventDefault) {
1269 ev.preventDefault();
1270 }
1271 ev.cancelBubble=true;
1272 ev.returnValue=false;
1273 },
1274 _mousemove: function(ev) {
1275 if(!this._state.enabled) {
1276 return false;
1277 }
1278
1279 var percentage = this._getPercentage(ev);
1280 this._adjustPercentageForRangeSliders(percentage);
1281 this._state.percentage[this._state.dragged] = percentage;
1282 this._layout();
1283
1284 var val = this._calculateValue(true);
1285 this.setValue(val, true, true);
1286
1287 return false;
1288 },
1289 _adjustPercentageForRangeSliders: function(percentage) {
1290 if (this.options.range) {
1291 var precision = this._getNumDigitsAfterDecimalPlace(percentage);
1292 precision = precision ? precision - 1 : 0;
1293 var percentageWithAdjustedPrecision = this._applyToFixedAndParseFloat(percentage, precision);
1294 if (this._state.dragged === 0 && this._applyToFixedAndParseFloat(this._state.percentage[1], precision) < percentageWithAdjustedPrecision) {
1295 this._state.percentage[0] = this._state.percentage[1];
1296 this._state.dragged = 1;
1297 } else if (this._state.dragged === 1 && this._applyToFixedAndParseFloat(this._state.percentage[0], precision) > percentageWithAdjustedPrecision) {
1298 this._state.percentage[1] = this._state.percentage[0];
1299 this._state.dragged = 0;
1300 }
1301 }
1302 },
1303 _mouseup: function() {
1304 if(!this._state.enabled) {
1305 return false;
1306 }
1307 if (this.touchCapable) {
1308 // Touch: Unbind touch event handlers:
1309 document.removeEventListener("touchmove", this.mousemove, false);
1310 document.removeEventListener("touchend", this.mouseup, false);
1311 }
1312 // Unbind mouse event handlers:
1313 document.removeEventListener("mousemove", this.mousemove, false);
1314 document.removeEventListener("mouseup", this.mouseup, false);
1315
1316 this._state.inDrag = false;
1317 if (this._state.over === false) {
1318 this._hideTooltip();
1319 }
1320 var val = this._calculateValue(true);
1321
1322 this._layout();
1323 this._setDataVal(val);
1324 this._trigger('slideStop', val);
1325
1326 return false;
1327 },
1328 _calculateValue: function(snapToClosestTick) {
1329 var val;
1330 if (this.options.range) {
1331 val = [this.options.min,this.options.max];
1332 if (this._state.percentage[0] !== 0){
1333 val[0] = this._toValue(this._state.percentage[0]);
1334 val[0] = this._applyPrecision(val[0]);
1335 }
1336 if (this._state.percentage[1] !== 100){
1337 val[1] = this._toValue(this._state.percentage[1]);
1338 val[1] = this._applyPrecision(val[1]);
1339 }
1340 } else {
1341 val = this._toValue(this._state.percentage[0]);
1342 val = parseFloat(val);
1343 val = this._applyPrecision(val);
1344 }
1345
1346 if (snapToClosestTick) {
1347 var min = [val, Infinity];
1348 for (var i = 0; i < this.options.ticks.length; i++) {
1349 var diff = Math.abs(this.options.ticks[i] - val);
1350 if (diff <= min[1]) {
1351 min = [this.options.ticks[i], diff];
1352 }
1353 }
1354 if (min[1] <= this.options.ticks_snap_bounds) {
1355 return min[0];
1356 }
1357 }
1358
1359 return val;
1360 },
1361 _applyPrecision: function(val) {
1362 var precision = this.options.precision || this._getNumDigitsAfterDecimalPlace(this.options.step);
1363 return this._applyToFixedAndParseFloat(val, precision);
1364 },
1365 _getNumDigitsAfterDecimalPlace: function(num) {
1366 var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
1367 if (!match) { return 0; }
1368 return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0));
1369 },
1370 _applyToFixedAndParseFloat: function(num, toFixedInput) {
1371 var truncatedNum = num.toFixed(toFixedInput);
1372 return parseFloat(truncatedNum);
1373 },
1374 /*
1375 Credits to Mike Samuel for the following method!
1376 Source: http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number
1377 */
1378 _getPercentage: function(ev) {
1379 if (this.touchCapable && (ev.type === 'touchstart' || ev.type === 'touchmove')) {
1380 ev = ev.touches[0];
1381 }
1382
1383 var eventPosition = ev[this.mousePos];
1384 var sliderOffset = this._state.offset[this.stylePos];
1385 var distanceToSlide = eventPosition - sliderOffset;
1386 // Calculate what percent of the length the slider handle has slid
1387 var percentage = (distanceToSlide / this._state.size) * 100;
1388 percentage = Math.round(percentage / this._state.percentage[2]) * this._state.percentage[2];
1389 if (this.options.reversed) {
1390 percentage = 100 - percentage;
1391 }
1392
1393 // Make sure the percent is within the bounds of the slider.
1394 // 0% corresponds to the 'min' value of the slide
1395 // 100% corresponds to the 'max' value of the slide
1396 return Math.max(0, Math.min(100, percentage));
1397 },
1398 _validateInputValue: function(val) {
1399 if (typeof val === 'number') {
1400 return val;
1401 } else if (Array.isArray(val)) {
1402 this._validateArray(val);
1403 return val;
1404 } else {
1405 throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(val) );
1406 }
1407 },
1408 _validateArray: function(val) {
1409 for(var i = 0; i < val.length; i++) {
1410 var input = val[i];
1411 if (typeof input !== 'number') { throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(input) ); }
1412 }
1413 },
1414 _setDataVal: function(val) {
1415 this.element.setAttribute('data-value', val);
1416 this.element.setAttribute('value', val);
1417 this.element.value = val;
1418 },
1419 _trigger: function(evt, val) {
1420 val = (val || val === 0) ? val : undefined;
1421
1422 var callbackFnArray = this.eventToCallbackMap[evt];
1423 if(callbackFnArray && callbackFnArray.length) {
1424 for(var i = 0; i < callbackFnArray.length; i++) {
1425 var callbackFn = callbackFnArray[i];
1426 callbackFn(val);
1427 }
1428 }
1429
1430 /* If JQuery exists, trigger JQuery events */
1431 if($) {
1432 this._triggerJQueryEvent(evt, val);
1433 }
1434 },
1435 _triggerJQueryEvent: function(evt, val) {
1436 var eventData = {
1437 type: evt,
1438 value: val
1439 };
1440 this.$element.trigger(eventData);
1441 this.$sliderElem.trigger(eventData);
1442 },
1443 _unbindJQueryEventHandlers: function() {
1444 this.$element.off();
1445 this.$sliderElem.off();
1446 },
1447 _setText: function(element, text) {
1448 if(typeof element.innerText !== "undefined") {
1449 element.innerText = text;
1450 } else if(typeof element.textContent !== "undefined") {
1451 element.textContent = text;
1452 }
1453 },
1454 _removeClass: function(element, classString) {
1455 var classes = classString.split(" ");
1456 var newClasses = element.className;
1457
1458 for(var i = 0; i < classes.length; i++) {
1459 var classTag = classes[i];
1460 var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)");
1461 newClasses = newClasses.replace(regex, " ");
1462 }
1463
1464 element.className = newClasses.trim();
1465 },
1466 _addClass: function(element, classString) {
1467 var classes = classString.split(" ");
1468 var newClasses = element.className;
1469
1470 for(var i = 0; i < classes.length; i++) {
1471 var classTag = classes[i];
1472 var regex = new RegExp("(?:\\s|^)" + classTag + "(?:\\s|$)");
1473 var ifClassExists = regex.test(newClasses);
1474
1475 if(!ifClassExists) {
1476 newClasses += " " + classTag;
1477 }
1478 }
1479
1480 element.className = newClasses.trim();
1481 },
1482 _offsetLeft: function(obj){
1483 return obj.getBoundingClientRect().left;
1484 },
1485 _offsetTop: function(obj){
1486 var offsetTop = obj.offsetTop;
1487 while((obj = obj.offsetParent) && !isNaN(obj.offsetTop)){
1488 offsetTop += obj.offsetTop;
1489 }
1490 return offsetTop;
1491 },
1492 _offset: function (obj) {
1493 return {
1494 left: this._offsetLeft(obj),
1495 top: this._offsetTop(obj)
1496 };
1497 },
1498 _css: function(elementRef, styleName, value) {
1499 if ($) {
1500 $.style(elementRef, styleName, value);
1501 } else {
1502 var style = styleName.replace(/^-ms-/, "ms-").replace(/-([\da-z])/gi, function (all, letter) {
1503 return letter.toUpperCase();
1504 });
1505 elementRef.style[style] = value;
1506 }
1507 },
1508 _toValue: function(percentage) {
1509 return this.options.scale.toValue.apply(this, [percentage]);
1510 },
1511 _toPercentage: function(value) {
1512 return this.options.scale.toPercentage.apply(this, [value]);
1513 },
1514 _setTooltipPosition: function(){
1515 var tooltips = [this.tooltip, this.tooltip_min, this.tooltip_max];
1516 if (this.options.orientation === 'vertical'){
1517 var tooltipPos = this.options.tooltip_position || 'right';
1518 var oppositeSide = (tooltipPos === 'left') ? 'right' : 'left';
1519 tooltips.forEach(function(tooltip){
1520 this._addClass(tooltip, tooltipPos);
1521 tooltip.style[oppositeSide] = '100%';
1522 }.bind(this));
1523 } else if(this.options.tooltip_position === 'bottom') {
1524 tooltips.forEach(function(tooltip){
1525 this._addClass(tooltip, 'bottom');
1526 tooltip.style.top = 22 + 'px';
1527 }.bind(this));
1528 } else {
1529 tooltips.forEach(function(tooltip){
1530 this._addClass(tooltip, 'top');
1531 tooltip.style.top = -this.tooltip.outerHeight - 14 + 'px';
1532 }.bind(this));
1533 }
1534 }
1535 };
1536
1537 /*********************************
1538
1539 Attach to global namespace
1540
1541 *********************************/
1542 if($) {
1543 var namespace = $.fn.slider ? 'bootstrapSlider' : 'slider';
1544 $.bridget(namespace, Slider);
1545 }
1546
1547 })( $ );
1548
1549 return Slider;
1550 }));