Subversion Repositories SmartDukaan

Rev

Rev 13695 | Details | Compare with Previous | Last modification | View Log | RSS feed

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