Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
5 lars 1
/*! nouislider - 8.0.2 - 2015-07-06 13:22:09 */
2
 
3
/*jslint browser: true */
4
/*jslint white: true */
5
 
6
(function (factory) {
7
 
8
    if ( typeof define === 'function' && define.amd ) {
9
 
10
        // AMD. Register as an anonymous module.
11
        define([], factory);
12
 
13
    } else if ( typeof exports === 'object' ) {
14
 
15
        var fs = require('fs');
16
 
17
        // Node/CommonJS
18
        module.exports = factory();
19
        module.exports.css = function () {
20
            return fs.readFileSync(__dirname + '/nouislider.min.css', 'utf8');
21
        };
22
 
23
    } else {
24
 
25
        // Browser globals
26
        window.noUiSlider = factory();
27
    }
28
 
29
}(function( ){
30
 
31
	'use strict';
32
 
33
 
34
	// Removes duplicates from an array.
35
	function unique(array) {
36
		return array.filter(function(a){
37
			return !this[a] ? this[a] = true : false;
38
		}, {});
39
	}
40
 
41
	// Round a value to the closest 'to'.
42
	function closest ( value, to ) {
43
		return Math.round(value / to) * to;
44
	}
45
 
46
	// Current position of an element relative to the document.
47
	function offset ( elem ) {
48
 
49
	var rect = elem.getBoundingClientRect(),
50
		doc = elem.ownerDocument,
51
		win = doc.defaultView || doc.parentWindow,
52
		docElem = doc.documentElement,
53
		xOff = win.pageXOffset;
54
 
55
		// getBoundingClientRect contains left scroll in Chrome on Android.
56
		// I haven't found a feature detection that proves this. Worst case
57
		// scenario on mis-match: the 'tap' feature on horizontal sliders breaks.
58
		if ( /webkit.*Chrome.*Mobile/i.test(navigator.userAgent) ) {
59
			xOff = 0;
60
		}
61
 
62
		return {
63
			top: rect.top + win.pageYOffset - docElem.clientTop,
64
			left: rect.left + xOff - docElem.clientLeft
65
		};
66
	}
67
 
68
	// Checks whether a value is numerical.
69
	function isNumeric ( a ) {
70
		return typeof a === 'number' && !isNaN( a ) && isFinite( a );
71
	}
72
 
73
	// Rounds a number to 7 supported decimals.
74
	function accurateNumber( number ) {
75
		var p = Math.pow(10, 7);
76
		return Number((Math.round(number*p)/p).toFixed(7));
77
	}
78
 
79
	// Sets a class and removes it after [duration] ms.
80
	function addClassFor ( element, className, duration ) {
81
		addClass(element, className);
82
		setTimeout(function(){
83
			removeClass(element, className);
84
		}, duration);
85
	}
86
 
87
	// Limits a value to 0 - 100
88
	function limit ( a ) {
89
		return Math.max(Math.min(a, 100), 0);
90
	}
91
 
92
	// Wraps a variable as an array, if it isn't one yet.
93
	function asArray ( a ) {
94
		return Array.isArray(a) ? a : [a];
95
	}
96
 
97
	// Counts decimals
98
	function countDecimals ( numStr ) {
99
		var pieces = numStr.split(".");
100
		return pieces.length > 1 ? pieces[1].length : 0;
101
	}
102
 
103
	// http://youmightnotneedjquery.com/#add_class
104
	function addClass ( el, className ) {
105
		if ( el.classList ) {
106
			el.classList.add(className);
107
		} else {
108
			el.className += ' ' + className;
109
		}
110
	}
111
 
112
	// http://youmightnotneedjquery.com/#remove_class
113
	function removeClass ( el, className ) {
114
		if ( el.classList ) {
115
			el.classList.remove(className);
116
		} else {
117
			el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
118
		}
119
	}
120
 
121
	// http://youmightnotneedjquery.com/#has_class
122
	function hasClass ( el, className ) {
123
		if ( el.classList ) {
124
			el.classList.contains(className);
125
		} else {
126
			new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className);
127
		}
128
	}
129
 
130
 
131
	var
132
	// Determine the events to bind. IE11 implements pointerEvents without
133
	// a prefix, which breaks compatibility with the IE10 implementation.
134
	/** @const */
135
	actions = window.navigator.pointerEnabled ? {
136
		start: 'pointerdown',
137
		move: 'pointermove',
138
		end: 'pointerup'
139
	} : window.navigator.msPointerEnabled ? {
140
		start: 'MSPointerDown',
141
		move: 'MSPointerMove',
142
		end: 'MSPointerUp'
143
	} : {
144
		start: 'mousedown touchstart',
145
		move: 'mousemove touchmove',
146
		end: 'mouseup touchend'
147
	},
148
	// Re-usable list of classes;
149
	/** @const */
150
	Classes = [
151
/*  0 */  'noUi-target'
152
/*  1 */ ,'noUi-base'
153
/*  2 */ ,'noUi-origin'
154
/*  3 */ ,'noUi-handle'
155
/*  4 */ ,'noUi-horizontal'
156
/*  5 */ ,'noUi-vertical'
157
/*  6 */ ,'noUi-background'
158
/*  7 */ ,'noUi-connect'
159
/*  8 */ ,'noUi-ltr'
160
/*  9 */ ,'noUi-rtl'
161
/* 10 */ ,'noUi-dragable'
162
/* 11 */ ,''
163
/* 12 */ ,'noUi-state-drag'
164
/* 13 */ ,''
165
/* 14 */ ,'noUi-state-tap'
166
/* 15 */ ,'noUi-active'
167
/* 16 */ ,''
168
/* 17 */ ,'noUi-stacking'
169
	];
170
 
171
 
172
// Value calculation
173
 
174
	// Determine the size of a sub-range in relation to a full range.
175
	function subRangeRatio ( pa, pb ) {
176
		return (100 / (pb - pa));
177
	}
178
 
179
	// (percentage) How many percent is this value of this range?
180
	function fromPercentage ( range, value ) {
181
		return (value * 100) / ( range[1] - range[0] );
182
	}
183
 
184
	// (percentage) Where is this value on this range?
185
	function toPercentage ( range, value ) {
186
		return fromPercentage( range, range[0] < 0 ?
187
			value + Math.abs(range[0]) :
188
				value - range[0] );
189
	}
190
 
191
	// (value) How much is this percentage on this range?
192
	function isPercentage ( range, value ) {
193
		return ((value * ( range[1] - range[0] )) / 100) + range[0];
194
	}
195
 
196
 
197
// Range conversion
198
 
199
	function getJ ( value, arr ) {
200
 
201
		var j = 1;
202
 
203
		while ( value >= arr[j] ){
204
			j += 1;
205
		}
206
 
207
		return j;
208
	}
209
 
210
	// (percentage) Input a value, find where, on a scale of 0-100, it applies.
211
	function toStepping ( xVal, xPct, value ) {
212
 
213
		if ( value >= xVal.slice(-1)[0] ){
214
			return 100;
215
		}
216
 
217
		var j = getJ( value, xVal ), va, vb, pa, pb;
218
 
219
		va = xVal[j-1];
220
		vb = xVal[j];
221
		pa = xPct[j-1];
222
		pb = xPct[j];
223
 
224
		return pa + (toPercentage([va, vb], value) / subRangeRatio (pa, pb));
225
	}
226
 
227
	// (value) Input a percentage, find where it is on the specified range.
228
	function fromStepping ( xVal, xPct, value ) {
229
 
230
		// There is no range group that fits 100
231
		if ( value >= 100 ){
232
			return xVal.slice(-1)[0];
233
		}
234
 
235
		var j = getJ( value, xPct ), va, vb, pa, pb;
236
 
237
		va = xVal[j-1];
238
		vb = xVal[j];
239
		pa = xPct[j-1];
240
		pb = xPct[j];
241
 
242
		return isPercentage([va, vb], (value - pa) * subRangeRatio (pa, pb));
243
	}
244
 
245
	// (percentage) Get the step that applies at a certain value.
246
	function getStep ( xPct, xSteps, snap, value ) {
247
 
248
		if ( value === 100 ) {
249
			return value;
250
		}
251
 
252
		var j = getJ( value, xPct ), a, b;
253
 
254
		// If 'snap' is set, steps are used as fixed points on the slider.
255
		if ( snap ) {
256
 
257
			a = xPct[j-1];
258
			b = xPct[j];
259
 
260
			// Find the closest position, a or b.
261
			if ((value - a) > ((b-a)/2)){
262
				return b;
263
			}
264
 
265
			return a;
266
		}
267
 
268
		if ( !xSteps[j-1] ){
269
			return value;
270
		}
271
 
272
		return xPct[j-1] + closest(
273
			value - xPct[j-1],
274
			xSteps[j-1]
275
		);
276
	}
277
 
278
 
279
// Entry parsing
280
 
281
	function handleEntryPoint ( index, value, that ) {
282
 
283
		var percentage;
284
 
285
		// Wrap numerical input in an array.
286
		if ( typeof value === "number" ) {
287
			value = [value];
288
		}
289
 
290
		// Reject any invalid input, by testing whether value is an array.
291
		if ( Object.prototype.toString.call( value ) !== '[object Array]' ){
292
			throw new Error("noUiSlider: 'range' contains invalid value.");
293
		}
294
 
295
		// Covert min/max syntax to 0 and 100.
296
		if ( index === 'min' ) {
297
			percentage = 0;
298
		} else if ( index === 'max' ) {
299
			percentage = 100;
300
		} else {
301
			percentage = parseFloat( index );
302
		}
303
 
304
		// Check for correct input.
305
		if ( !isNumeric( percentage ) || !isNumeric( value[0] ) ) {
306
			throw new Error("noUiSlider: 'range' value isn't numeric.");
307
		}
308
 
309
		// Store values.
310
		that.xPct.push( percentage );
311
		that.xVal.push( value[0] );
312
 
313
		// NaN will evaluate to false too, but to keep
314
		// logging clear, set step explicitly. Make sure
315
		// not to override the 'step' setting with false.
316
		if ( !percentage ) {
317
			if ( !isNaN( value[1] ) ) {
318
				that.xSteps[0] = value[1];
319
			}
320
		} else {
321
			that.xSteps.push( isNaN(value[1]) ? false : value[1] );
322
		}
323
	}
324
 
325
	function handleStepPoint ( i, n, that ) {
326
 
327
		// Ignore 'false' stepping.
328
		if ( !n ) {
329
			return true;
330
		}
331
 
332
		// Factor to range ratio
333
		that.xSteps[i] = fromPercentage([
334
			 that.xVal[i]
335
			,that.xVal[i+1]
336
		], n) / subRangeRatio (
337
			that.xPct[i],
338
			that.xPct[i+1] );
339
	}
340
 
341
 
342
// Interface
343
 
344
	// The interface to Spectrum handles all direction-based
345
	// conversions, so the above values are unaware.
346
 
347
	function Spectrum ( entry, snap, direction, singleStep ) {
348
 
349
		this.xPct = [];
350
		this.xVal = [];
351
		this.xSteps = [ singleStep || false ];
352
		this.xNumSteps = [ false ];
353
 
354
		this.snap = snap;
355
		this.direction = direction;
356
 
357
		var index, ordered = [ /* [0, 'min'], [1, '50%'], [2, 'max'] */ ];
358
 
359
		// Map the object keys to an array.
360
		for ( index in entry ) {
361
			if ( entry.hasOwnProperty(index) ) {
362
				ordered.push([entry[index], index]);
363
			}
364
		}
365
 
366
		// Sort all entries by value (numeric sort).
367
		ordered.sort(function(a, b) { return a[0] - b[0]; });
368
 
369
		// Convert all entries to subranges.
370
		for ( index = 0; index < ordered.length; index++ ) {
371
			handleEntryPoint(ordered[index][1], ordered[index][0], this);
372
		}
373
 
374
		// Store the actual step values.
375
		// xSteps is sorted in the same order as xPct and xVal.
376
		this.xNumSteps = this.xSteps.slice(0);
377
 
378
		// Convert all numeric steps to the percentage of the subrange they represent.
379
		for ( index = 0; index < this.xNumSteps.length; index++ ) {
380
			handleStepPoint(index, this.xNumSteps[index], this);
381
		}
382
	}
383
 
384
	Spectrum.prototype.getMargin = function ( value ) {
385
		return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false;
386
	};
387
 
388
	Spectrum.prototype.toStepping = function ( value ) {
389
 
390
		value = toStepping( this.xVal, this.xPct, value );
391
 
392
		// Invert the value if this is a right-to-left slider.
393
		if ( this.direction ) {
394
			value = 100 - value;
395
		}
396
 
397
		return value;
398
	};
399
 
400
	Spectrum.prototype.fromStepping = function ( value ) {
401
 
402
		// Invert the value if this is a right-to-left slider.
403
		if ( this.direction ) {
404
			value = 100 - value;
405
		}
406
 
407
		return accurateNumber(fromStepping( this.xVal, this.xPct, value ));
408
	};
409
 
410
	Spectrum.prototype.getStep = function ( value ) {
411
 
412
		// Find the proper step for rtl sliders by search in inverse direction.
413
		// Fixes issue #262.
414
		if ( this.direction ) {
415
			value = 100 - value;
416
		}
417
 
418
		value = getStep(this.xPct, this.xSteps, this.snap, value );
419
 
420
		if ( this.direction ) {
421
			value = 100 - value;
422
		}
423
 
424
		return value;
425
	};
426
 
427
	Spectrum.prototype.getApplicableStep = function ( value ) {
428
 
429
		// If the value is 100%, return the negative step twice.
430
		var j = getJ(value, this.xPct), offset = value === 100 ? 2 : 1;
431
		return [this.xNumSteps[j-2], this.xVal[j-offset], this.xNumSteps[j-offset]];
432
	};
433
 
434
	// Outside testing
435
	Spectrum.prototype.convert = function ( value ) {
436
		return this.getStep(this.toStepping(value));
437
	};
438
 
439
/*	Every input option is tested and parsed. This'll prevent
440
	endless validation in internal methods. These tests are
441
	structured with an item for every option available. An
442
	option can be marked as required by setting the 'r' flag.
443
	The testing function is provided with three arguments:
444
		- The provided value for the option;
445
		- A reference to the options object;
446
		- The name for the option;
447
 
448
	The testing function returns false when an error is detected,
449
	or true when everything is OK. It can also modify the option
450
	object, to make sure all values can be correctly looped elsewhere. */
451
 
452
	var defaultFormatter = { 'to': function( value ){
453
		return value.toFixed(2);
454
	}, 'from': Number };
455
 
456
	function testStep ( parsed, entry ) {
457
 
458
		if ( !isNumeric( entry ) ) {
459
			throw new Error("noUiSlider: 'step' is not numeric.");
460
		}
461
 
462
		// The step option can still be used to set stepping
463
		// for linear sliders. Overwritten if set in 'range'.
464
		parsed.singleStep = entry;
465
	}
466
 
467
	function testRange ( parsed, entry ) {
468
 
469
		// Filter incorrect input.
470
		if ( typeof entry !== 'object' || Array.isArray(entry) ) {
471
			throw new Error("noUiSlider: 'range' is not an object.");
472
		}
473
 
474
		// Catch missing start or end.
475
		if ( entry.min === undefined || entry.max === undefined ) {
476
			throw new Error("noUiSlider: Missing 'min' or 'max' in 'range'.");
477
		}
478
 
479
		parsed.spectrum = new Spectrum(entry, parsed.snap, parsed.dir, parsed.singleStep);
480
	}
481
 
482
	function testStart ( parsed, entry ) {
483
 
484
		entry = asArray(entry);
485
 
486
		// Validate input. Values aren't tested, as the public .val method
487
		// will always provide a valid location.
488
		if ( !Array.isArray( entry ) || !entry.length || entry.length > 2 ) {
489
			throw new Error("noUiSlider: 'start' option is incorrect.");
490
		}
491
 
492
		// Store the number of handles.
493
		parsed.handles = entry.length;
494
 
495
		// When the slider is initialized, the .val method will
496
		// be called with the start options.
497
		parsed.start = entry;
498
	}
499
 
500
	function testSnap ( parsed, entry ) {
501
 
502
		// Enforce 100% stepping within subranges.
503
		parsed.snap = entry;
504
 
505
		if ( typeof entry !== 'boolean' ){
506
			throw new Error("noUiSlider: 'snap' option must be a boolean.");
507
		}
508
	}
509
 
510
	function testAnimate ( parsed, entry ) {
511
 
512
		// Enforce 100% stepping within subranges.
513
		parsed.animate = entry;
514
 
515
		if ( typeof entry !== 'boolean' ){
516
			throw new Error("noUiSlider: 'animate' option must be a boolean.");
517
		}
518
	}
519
 
520
	function testConnect ( parsed, entry ) {
521
 
522
		if ( entry === 'lower' && parsed.handles === 1 ) {
523
			parsed.connect = 1;
524
		} else if ( entry === 'upper' && parsed.handles === 1 ) {
525
			parsed.connect = 2;
526
		} else if ( entry === true && parsed.handles === 2 ) {
527
			parsed.connect = 3;
528
		} else if ( entry === false ) {
529
			parsed.connect = 0;
530
		} else {
531
			throw new Error("noUiSlider: 'connect' option doesn't match handle count.");
532
		}
533
	}
534
 
535
	function testOrientation ( parsed, entry ) {
536
 
537
		// Set orientation to an a numerical value for easy
538
		// array selection.
539
		switch ( entry ){
540
		  case 'horizontal':
541
			parsed.ort = 0;
542
			break;
543
		  case 'vertical':
544
			parsed.ort = 1;
545
			break;
546
		  default:
547
			throw new Error("noUiSlider: 'orientation' option is invalid.");
548
		}
549
	}
550
 
551
	function testMargin ( parsed, entry ) {
552
 
553
		if ( !isNumeric(entry) ){
554
			throw new Error("noUiSlider: 'margin' option must be numeric.");
555
		}
556
 
557
		parsed.margin = parsed.spectrum.getMargin(entry);
558
 
559
		if ( !parsed.margin ) {
560
			throw new Error("noUiSlider: 'margin' option is only supported on linear sliders.");
561
		}
562
	}
563
 
564
	function testLimit ( parsed, entry ) {
565
 
566
		if ( !isNumeric(entry) ){
567
			throw new Error("noUiSlider: 'limit' option must be numeric.");
568
		}
569
 
570
		parsed.limit = parsed.spectrum.getMargin(entry);
571
 
572
		if ( !parsed.limit ) {
573
			throw new Error("noUiSlider: 'limit' option is only supported on linear sliders.");
574
		}
575
	}
576
 
577
	function testDirection ( parsed, entry ) {
578
 
579
		// Set direction as a numerical value for easy parsing.
580
		// Invert connection for RTL sliders, so that the proper
581
		// handles get the connect/background classes.
582
		switch ( entry ) {
583
		  case 'ltr':
584
			parsed.dir = 0;
585
			break;
586
		  case 'rtl':
587
			parsed.dir = 1;
588
			parsed.connect = [0,2,1,3][parsed.connect];
589
			break;
590
		  default:
591
			throw new Error("noUiSlider: 'direction' option was not recognized.");
592
		}
593
	}
594
 
595
	function testBehaviour ( parsed, entry ) {
596
 
597
		// Make sure the input is a string.
598
		if ( typeof entry !== 'string' ) {
599
			throw new Error("noUiSlider: 'behaviour' must be a string containing options.");
600
		}
601
 
602
		// Check if the string contains any keywords.
603
		// None are required.
604
		var tap = entry.indexOf('tap') >= 0,
605
			drag = entry.indexOf('drag') >= 0,
606
			fixed = entry.indexOf('fixed') >= 0,
607
			snap = entry.indexOf('snap') >= 0;
608
 
609
		parsed.events = {
610
			tap: tap || snap,
611
			drag: drag,
612
			fixed: fixed,
613
			snap: snap
614
		};
615
	}
616
 
617
	function testFormat ( parsed, entry ) {
618
 
619
		parsed.format = entry;
620
 
621
		// Any object with a to and from method is supported.
622
		if ( typeof entry.to === 'function' && typeof entry.from === 'function' ) {
623
			return true;
624
		}
625
 
626
		throw new Error( "noUiSlider: 'format' requires 'to' and 'from' methods.");
627
	}
628
 
629
	// Test all developer settings and parse to assumption-safe values.
630
	function testOptions ( options ) {
631
 
632
		var parsed = {
633
			margin: 0,
634
			limit: 0,
635
			animate: true,
636
			format: defaultFormatter
637
		}, tests;
638
 
639
		// Tests are executed in the order they are presented here.
640
		tests = {
641
			'step': { r: false, t: testStep },
642
			'start': { r: true, t: testStart },
643
			'connect': { r: true, t: testConnect },
644
			'direction': { r: true, t: testDirection },
645
			'snap': { r: false, t: testSnap },
646
			'animate': { r: false, t: testAnimate },
647
			'range': { r: true, t: testRange },
648
			'orientation': { r: false, t: testOrientation },
649
			'margin': { r: false, t: testMargin },
650
			'limit': { r: false, t: testLimit },
651
			'behaviour': { r: true, t: testBehaviour },
652
			'format': { r: false, t: testFormat }
653
		};
654
 
655
		var defaults = {
656
			'connect': false,
657
			'direction': 'ltr',
658
			'behaviour': 'tap',
659
			'orientation': 'horizontal'
660
		};
661
 
662
		// Set defaults where applicable.
663
		Object.keys(defaults).forEach(function ( name ) {
664
			if ( options[name] === undefined ) {
665
				options[name] = defaults[name];
666
			}
667
		});
668
 
669
		// Run all options through a testing mechanism to ensure correct
670
		// input. It should be noted that options might get modified to
671
		// be handled properly. E.g. wrapping integers in arrays.
672
		Object.keys(tests).forEach(function( name ){
673
 
674
			var test = tests[name];
675
 
676
			// If the option isn't set, but it is required, throw an error.
677
			if ( options[name] === undefined ) {
678
 
679
				if ( test.r ) {
680
					throw new Error("noUiSlider: '" + name + "' is required.");
681
				}
682
 
683
				return true;
684
			}
685
 
686
			test.t( parsed, options[name] );
687
		});
688
 
689
		// Forward pips options
690
		parsed.pips = options.pips;
691
 
692
		// Pre-define the styles.
693
		parsed.style = parsed.ort ? 'top' : 'left';
694
 
695
		return parsed;
696
	}
697
 
698
 
699
	// Delimit proposed values for handle positions.
700
	function getPositions ( a, b, delimit ) {
701
 
702
		// Add movement to current position.
703
		var c = a + b[0], d = a + b[1];
704
 
705
		// Only alter the other position on drag,
706
		// not on standard sliding.
707
		if ( delimit ) {
708
			if ( c < 0 ) {
709
				d += Math.abs(c);
710
			}
711
			if ( d > 100 ) {
712
				c -= ( d - 100 );
713
			}
714
 
715
			// Limit values to 0 and 100.
716
			return [limit(c), limit(d)];
717
		}
718
 
719
		return [c,d];
720
	}
721
 
722
	// Provide a clean event with standardized offset values.
723
	function fixEvent ( e ) {
724
 
725
		// Prevent scrolling and panning on touch events, while
726
		// attempting to slide. The tap event also depends on this.
727
		e.preventDefault();
728
 
729
		// Filter the event to register the type, which can be
730
		// touch, mouse or pointer. Offset changes need to be
731
		// made on an event specific basis.
732
		var touch = e.type.indexOf('touch') === 0,
733
			mouse = e.type.indexOf('mouse') === 0,
734
			pointer = e.type.indexOf('pointer') === 0,
735
			x,y, event = e;
736
 
737
		// IE10 implemented pointer events with a prefix;
738
		if ( e.type.indexOf('MSPointer') === 0 ) {
739
			pointer = true;
740
		}
741
 
742
		if ( touch ) {
743
			// noUiSlider supports one movement at a time,
744
			// so we can select the first 'changedTouch'.
745
			x = e.changedTouches[0].pageX;
746
			y = e.changedTouches[0].pageY;
747
		}
748
 
749
		if ( mouse || pointer ) {
750
			x = e.clientX + window.pageXOffset;
751
			y = e.clientY + window.pageYOffset;
752
		}
753
 
754
		event.points = [x, y];
755
		event.cursor = mouse || pointer; // Fix #435
756
 
757
		return event;
758
	}
759
 
760
	// Append a handle to the base.
761
	function addHandle ( direction, index ) {
762
 
763
		var origin = document.createElement('div'),
764
			handle = document.createElement('div'),
765
			additions = [ '-lower', '-upper' ];
766
 
767
		if ( direction ) {
768
			additions.reverse();
769
		}
770
 
771
		addClass(handle, Classes[3]);
772
		addClass(handle, Classes[3] + additions[index]);
773
 
774
		addClass(origin, Classes[2]);
775
		origin.appendChild(handle);
776
 
777
		return origin;
778
	}
779
 
780
	// Add the proper connection classes.
781
	function addConnection ( connect, target, handles ) {
782
 
783
		// Apply the required connection classes to the elements
784
		// that need them. Some classes are made up for several
785
		// segments listed in the class list, to allow easy
786
		// renaming and provide a minor compression benefit.
787
		switch ( connect ) {
788
			case 1:	addClass(target, Classes[7]);
789
					addClass(handles[0], Classes[6]);
790
					break;
791
			case 3: addClass(handles[1], Classes[6]);
792
					/* falls through */
793
			case 2: addClass(handles[0], Classes[7]);
794
					/* falls through */
795
			case 0: addClass(target, Classes[6]);
796
					break;
797
		}
798
	}
799
 
800
	// Add handles to the slider base.
801
	function addHandles ( nrHandles, direction, base ) {
802
 
803
		var index, handles = [];
804
 
805
		// Append handles.
806
		for ( index = 0; index < nrHandles; index += 1 ) {
807
 
808
			// Keep a list of all added handles.
809
			handles.push( base.appendChild(addHandle( direction, index )) );
810
		}
811
 
812
		return handles;
813
	}
814
 
815
	// Initialize a single slider.
816
	function addSlider ( direction, orientation, target ) {
817
 
818
		// Apply classes and data to the target.
819
		addClass(target, Classes[0]);
820
		addClass(target, Classes[8 + direction]);
821
		addClass(target, Classes[4 + orientation]);
822
 
823
		var div = document.createElement('div');
824
		addClass(div, Classes[1]);
825
		target.appendChild(div);
826
		return div;
827
	}
828
 
829
 
830
function closure ( target, options ){
831
 
832
	// All variables local to 'closure' are prefixed with 'scope_'
833
	var scope_Target = target,
834
		scope_Locations = [-1, -1],
835
		scope_Base,
836
		scope_Handles,
837
		scope_Spectrum = options.spectrum,
838
		scope_Values = [],
839
		scope_Events = {};
840
 
841
 
842
	function getGroup ( mode, values, stepped ) {
843
 
844
		// Use the range.
845
		if ( mode === 'range' || mode === 'steps' ) {
846
			return scope_Spectrum.xVal;
847
		}
848
 
849
		if ( mode === 'count' ) {
850
 
851
			// Divide 0 - 100 in 'count' parts.
852
			var spread = ( 100 / (values-1) ), v, i = 0;
853
			values = [];
854
 
855
			// List these parts and have them handled as 'positions'.
856
			while ((v=i++*spread) <= 100 ) {
857
				values.push(v);
858
			}
859
 
860
			mode = 'positions';
861
		}
862
 
863
		if ( mode === 'positions' ) {
864
 
865
			// Map all percentages to on-range values.
866
			return values.map(function( value ){
867
				return scope_Spectrum.fromStepping( stepped ? scope_Spectrum.getStep( value ) : value );
868
			});
869
		}
870
 
871
		if ( mode === 'values' ) {
872
 
873
			// If the value must be stepped, it needs to be converted to a percentage first.
874
			if ( stepped ) {
875
 
876
				return values.map(function( value ){
877
 
878
					// Convert to percentage, apply step, return to value.
879
					return scope_Spectrum.fromStepping( scope_Spectrum.getStep( scope_Spectrum.toStepping( value ) ) );
880
				});
881
 
882
			}
883
 
884
			// Otherwise, we can simply use the values.
885
			return values;
886
		}
887
	}
888
 
889
	function generateSpread ( density, mode, group ) {
890
 
891
		var originalSpectrumDirection = scope_Spectrum.direction,
892
			indexes = {},
893
			firstInRange = scope_Spectrum.xVal[0],
894
			lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length-1],
895
			ignoreFirst = false,
896
			ignoreLast = false,
897
			prevPct = 0;
898
 
899
		// This function loops the spectrum in an ltr linear fashion,
900
		// while the toStepping method is direction aware. Trick it into
901
		// believing it is ltr.
902
		scope_Spectrum.direction = 0;
903
 
904
		// Create a copy of the group, sort it and filter away all duplicates.
905
		group = unique(group.slice().sort(function(a, b){ return a - b; }));
906
 
907
		// Make sure the range starts with the first element.
908
		if ( group[0] !== firstInRange ) {
909
			group.unshift(firstInRange);
910
			ignoreFirst = true;
911
		}
912
 
913
		// Likewise for the last one.
914
		if ( group[group.length - 1] !== lastInRange ) {
915
			group.push(lastInRange);
916
			ignoreLast = true;
917
		}
918
 
919
		group.forEach(function ( current, index ) {
920
 
921
			// Get the current step and the lower + upper positions.
922
			var step, i, q,
923
				low = current,
924
				high = group[index+1],
925
				newPct, pctDifference, pctPos, type,
926
				steps, realSteps, stepsize;
927
 
928
			// When using 'steps' mode, use the provided steps.
929
			// Otherwise, we'll step on to the next subrange.
930
			if ( mode === 'steps' ) {
931
				step = scope_Spectrum.xNumSteps[ index ];
932
			}
933
 
934
			// Default to a 'full' step.
935
			if ( !step ) {
936
				step = high-low;
937
			}
938
 
939
			// Low can be 0, so test for false. If high is undefined,
940
			// we are at the last subrange. Index 0 is already handled.
941
			if ( low === false || high === undefined ) {
942
				return;
943
			}
944
 
945
			// Find all steps in the subrange.
946
			for ( i = low; i <= high; i += step ) {
947
 
948
				// Get the percentage value for the current step,
949
				// calculate the size for the subrange.
950
				newPct = scope_Spectrum.toStepping( i );
951
				pctDifference = newPct - prevPct;
952
 
953
				steps = pctDifference / density;
954
				realSteps = Math.round(steps);
955
 
956
				// This ratio represents the ammount of percentage-space a point indicates.
957
				// For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-devided.
958
				// Round the percentage offset to an even number, then divide by two
959
				// to spread the offset on both sides of the range.
960
				stepsize = pctDifference/realSteps;
961
 
962
				// Divide all points evenly, adding the correct number to this subrange.
963
				// Run up to <= so that 100% gets a point, event if ignoreLast is set.
964
				for ( q = 1; q <= realSteps; q += 1 ) {
965
 
966
					// The ratio between the rounded value and the actual size might be ~1% off.
967
					// Correct the percentage offset by the number of points
968
					// per subrange. density = 1 will result in 100 points on the
969
					// full range, 2 for 50, 4 for 25, etc.
970
					pctPos = prevPct + ( q * stepsize );
971
					indexes[pctPos.toFixed(5)] = ['x', 0];
972
				}
973
 
974
				// Determine the point type.
975
				type = (group.indexOf(i) > -1) ? 1 : ( mode === 'steps' ? 2 : 0 );
976
 
977
				// Enforce the 'ignoreFirst' option by overwriting the type for 0.
978
				if ( !index && ignoreFirst ) {
979
					type = 0;
980
				}
981
 
982
				if ( !(i === high && ignoreLast)) {
983
					// Mark the 'type' of this point. 0 = plain, 1 = real value, 2 = step value.
984
					indexes[newPct.toFixed(5)] = [i, type];
985
				}
986
 
987
				// Update the percentage count.
988
				prevPct = newPct;
989
			}
990
		});
991
 
992
		// Reset the spectrum.
993
		scope_Spectrum.direction = originalSpectrumDirection;
994
 
995
		return indexes;
996
	}
997
 
998
	function addMarking ( spread, filterFunc, formatter ) {
999
 
1000
		var style = ['horizontal', 'vertical'][options.ort],
1001
			element = document.createElement('div');
1002
 
1003
		addClass(element, 'noUi-pips');
1004
		addClass(element, 'noUi-pips-' + style);
1005
 
1006
		function getSize( type ){
1007
			return [ '-normal', '-large', '-sub' ][type];
1008
		}
1009
 
1010
		function getTags( offset, source, values ) {
1011
			return 'class="' + source + ' ' +
1012
				source + '-' + style + ' ' +
1013
				source + getSize(values[1]) +
1014
				'" style="' + options.style + ': ' + offset + '%"';
1015
		}
1016
 
1017
		function addSpread ( offset, values ){
1018
 
1019
			if ( scope_Spectrum.direction ) {
1020
				offset = 100 - offset;
1021
			}
1022
 
1023
			// Apply the filter function, if it is set.
1024
			values[1] = (values[1] && filterFunc) ? filterFunc(values[0], values[1]) : values[1];
1025
 
1026
			// Add a marker for every point
1027
			element.innerHTML += '<div ' + getTags(offset, 'noUi-marker', values) + '></div>';
1028
 
1029
			// Values are only appended for points marked '1' or '2'.
1030
			if ( values[1] ) {
1031
				element.innerHTML += '<div '+getTags(offset, 'noUi-value', values)+'>' + formatter.to(values[0]) + '</div>';
1032
			}
1033
		}
1034
 
1035
		// Append all points.
1036
		Object.keys(spread).forEach(function(a){
1037
			addSpread(a, spread[a]);
1038
		});
1039
 
1040
		return element;
1041
	}
1042
 
1043
	function pips ( grid ) {
1044
 
1045
	var mode = grid.mode,
1046
		density = grid.density || 1,
1047
		filter = grid.filter || false,
1048
		values = grid.values || false,
1049
		stepped = grid.stepped || false,
1050
		group = getGroup( mode, values, stepped ),
1051
		spread = generateSpread( density, mode, group ),
1052
		format = grid.format || {
1053
			to: Math.round
1054
		};
1055
 
1056
		return scope_Target.appendChild(addMarking(
1057
			spread,
1058
			filter,
1059
			format
1060
		));
1061
	}
1062
 
1063
 
1064
	// Shorthand for base dimensions.
1065
	function baseSize ( ) {
1066
		return scope_Base['offset' + ['Width', 'Height'][options.ort]];
1067
	}
1068
 
1069
	// External event handling
1070
	function fireEvent ( event, handleNumber ) {
1071
 
1072
		if ( handleNumber !== undefined ) {
1073
			handleNumber = Math.abs(handleNumber - options.dir);
1074
		}
1075
 
1076
		Object.keys(scope_Events).forEach(function( targetEvent ) {
1077
 
1078
			var eventType = targetEvent.split('.')[0];
1079
 
1080
			if ( event === eventType ) {
1081
				scope_Events[targetEvent].forEach(function( callback ) {
1082
					// .reverse is in place
1083
					// Return values as array, so arg_1[arg_2] is always valid.
1084
					callback( asArray(valueGet()), handleNumber, inSliderOrder(Array.prototype.slice.call(scope_Values)) );
1085
				});
1086
			}
1087
		});
1088
	}
1089
 
1090
	// Returns the input array, respecting the slider direction configuration.
1091
	function inSliderOrder ( values ) {
1092
 
1093
		// If only one handle is used, return a single value.
1094
		if ( values.length === 1 ){
1095
			return values[0];
1096
		}
1097
 
1098
		if ( options.dir ) {
1099
			return values.reverse();
1100
		}
1101
 
1102
		return values;
1103
	}
1104
 
1105
 
1106
	// Handler for attaching events trough a proxy.
1107
	function attach ( events, element, callback, data ) {
1108
 
1109
		// This function can be used to 'filter' events to the slider.
1110
		// element is a node, not a nodeList
1111
 
1112
		var method = function ( e ){
1113
 
1114
			if ( scope_Target.hasAttribute('disabled') ) {
1115
				return false;
1116
			}
1117
 
1118
			// Stop if an active 'tap' transition is taking place.
1119
			if ( hasClass(scope_Target, Classes[14]) ) {
1120
				return false;
1121
			}
1122
 
1123
			e = fixEvent(e);
1124
 
1125
			// Ignore right or middle clicks on start #454
1126
			if ( events === actions.start && e.buttons !== undefined && e.buttons > 1 ) {
1127
				return false;
1128
			}
1129
 
1130
			e.calcPoint = e.points[ options.ort ];
1131
 
1132
			// Call the event handler with the event [ and additional data ].
1133
			callback ( e, data );
1134
 
1135
		}, methods = [];
1136
 
1137
		// Bind a closure on the target for every event type.
1138
		events.split(' ').forEach(function( eventName ){
1139
			element.addEventListener(eventName, method, false);
1140
			methods.push([eventName, method]);
1141
		});
1142
 
1143
		return methods;
1144
	}
1145
 
1146
	// Handle movement on document for handle and range drag.
1147
	function move ( event, data ) {
1148
 
1149
		var handles = data.handles || scope_Handles, positions, state = false,
1150
			proposal = ((event.calcPoint - data.start) * 100) / baseSize(),
1151
			handleNumber = handles[0] === scope_Handles[0] ? 0 : 1, i;
1152
 
1153
		// Calculate relative positions for the handles.
1154
		positions = getPositions( proposal, data.positions, handles.length > 1);
1155
 
1156
		state = setHandle ( handles[0], positions[handleNumber], handles.length === 1 );
1157
 
1158
		if ( handles.length > 1 ) {
1159
 
1160
			state = setHandle ( handles[1], positions[handleNumber?0:1], false ) || state;
1161
 
1162
			if ( state ) {
1163
				// fire for both handles
1164
				for ( i = 0; i < data.handles.length; i++ ) {
1165
					fireEvent('slide', i);
1166
				}
1167
			}
1168
		} else if ( state ) {
1169
			// Fire for a single handle
1170
			fireEvent('slide', handleNumber);
1171
		}
1172
	}
1173
 
1174
	// Unbind move events on document, call callbacks.
1175
	function end ( event, data ) {
1176
 
1177
		// The handle is no longer active, so remove the class.
1178
		var active = scope_Base.getElementsByClassName(Classes[15]),
1179
			handleNumber = data.handles[0] === scope_Handles[0] ? 0 : 1;
1180
 
1181
		if ( active.length ) {
1182
			removeClass(active[0], Classes[15]);
1183
		}
1184
 
1185
		// Remove cursor styles and text-selection events bound to the body.
1186
		if ( event.cursor ) {
1187
			document.body.style.cursor = '';
1188
			document.body.removeEventListener('selectstart', document.body.noUiListener);
1189
		}
1190
 
1191
		var d = document.documentElement;
1192
 
1193
		// Unbind the move and end events, which are added on 'start'.
1194
		d.noUiListeners.forEach(function( c ) {
1195
			d.removeEventListener(c[0], c[1]);
1196
		});
1197
 
1198
		// Remove dragging class.
1199
		removeClass(scope_Target, Classes[12]);
1200
 
1201
		// Fire the change and set events.
1202
		fireEvent('set', handleNumber);
1203
		fireEvent('change', handleNumber);
1204
	}
1205
 
1206
	// Bind move events on document.
1207
	function start ( event, data ) {
1208
 
1209
		var d = document.documentElement;
1210
 
1211
		// Mark the handle as 'active' so it can be styled.
1212
		if ( data.handles.length === 1 ) {
1213
			addClass(data.handles[0].children[0], Classes[15]);
1214
 
1215
			// Support 'disabled' handles
1216
			if ( data.handles[0].hasAttribute('disabled') ) {
1217
				return false;
1218
			}
1219
		}
1220
 
1221
		// A drag should never propagate up to the 'tap' event.
1222
		event.stopPropagation();
1223
 
1224
		// Attach the move and end events.
1225
		var moveEvent = attach(actions.move, d, move, {
1226
			start: event.calcPoint,
1227
			handles: data.handles,
1228
			positions: [
1229
				scope_Locations[0],
1230
				scope_Locations[scope_Handles.length - 1]
1231
			]
1232
		}), endEvent = attach(actions.end, d, end, {
1233
			handles: data.handles
1234
		});
1235
 
1236
		d.noUiListeners = moveEvent.concat(endEvent);
1237
 
1238
		// Text selection isn't an issue on touch devices,
1239
		// so adding cursor styles can be skipped.
1240
		if ( event.cursor ) {
1241
 
1242
			// Prevent the 'I' cursor and extend the range-drag cursor.
1243
			document.body.style.cursor = getComputedStyle(event.target).cursor;
1244
 
1245
			// Mark the target with a dragging state.
1246
			if ( scope_Handles.length > 1 ) {
1247
				addClass(scope_Target, Classes[12]);
1248
			}
1249
 
1250
			var f = function(){
1251
				return false;
1252
			};
1253
 
1254
			document.body.noUiListener = f;
1255
 
1256
			// Prevent text selection when dragging the handles.
1257
			document.body.addEventListener('selectstart', f, false);
1258
		}
1259
	}
1260
 
1261
	// Move closest handle to tapped location.
1262
	function tap ( event ) {
1263
 
1264
		var location = event.calcPoint, total = 0, handleNumber, to;
1265
 
1266
		// The tap event shouldn't propagate up and cause 'edge' to run.
1267
		event.stopPropagation();
1268
 
1269
		// Add up the handle offsets.
1270
		scope_Handles.forEach(function(a){
1271
			total += offset(a)[ options.style ];
1272
		});
1273
 
1274
		// Find the handle closest to the tapped position.
1275
		handleNumber = ( location < total/2 || scope_Handles.length === 1 ) ? 0 : 1;
1276
 
1277
		location -= offset(scope_Base)[ options.style ];
1278
 
1279
		// Calculate the new position.
1280
		to = ( location * 100 ) / baseSize();
1281
 
1282
		if ( !options.events.snap ) {
1283
			// Flag the slider as it is now in a transitional state.
1284
			// Transition takes 300 ms, so re-enable the slider afterwards.
1285
			addClassFor( scope_Target, Classes[14], 300 );
1286
		}
1287
 
1288
		// Support 'disabled' handles
1289
		if ( scope_Handles[handleNumber].hasAttribute('disabled') ) {
1290
			return false;
1291
		}
1292
 
1293
		// Find the closest handle and calculate the tapped point.
1294
		// The set handle to the new position.
1295
		setHandle( scope_Handles[handleNumber], to );
1296
 
1297
		fireEvent('slide', handleNumber);
1298
		fireEvent('set', handleNumber);
1299
		fireEvent('change', handleNumber);
1300
 
1301
		if ( options.events.snap ) {
1302
			start(event, { handles: [scope_Handles[total]] });
1303
		}
1304
	}
1305
 
1306
	// Attach events to several slider parts.
1307
	function events ( behaviour ) {
1308
 
1309
		var i, drag;
1310
 
1311
		// Attach the standard drag event to the handles.
1312
		if ( !behaviour.fixed ) {
1313
 
1314
			for ( i = 0; i < scope_Handles.length; i += 1 ) {
1315
 
1316
				// These events are only bound to the visual handle
1317
				// element, not the 'real' origin element.
1318
				attach ( actions.start, scope_Handles[i].children[0], start, {
1319
					handles: [ scope_Handles[i] ]
1320
				});
1321
			}
1322
		}
1323
 
1324
		// Attach the tap event to the slider base.
1325
		if ( behaviour.tap ) {
1326
 
1327
			attach ( actions.start, scope_Base, tap, {
1328
				handles: scope_Handles
1329
			});
1330
		}
1331
 
1332
		// Make the range dragable.
1333
		if ( behaviour.drag ){
1334
 
1335
			drag = [scope_Base.getElementsByClassName( Classes[7] )[0]];
1336
			addClass(drag[0], Classes[10]);
1337
 
1338
			// When the range is fixed, the entire range can
1339
			// be dragged by the handles. The handle in the first
1340
			// origin will propagate the start event upward,
1341
			// but it needs to be bound manually on the other.
1342
			if ( behaviour.fixed ) {
1343
				drag.push(scope_Handles[(drag[0] === scope_Handles[0] ? 1 : 0)].children[0]);
1344
			}
1345
 
1346
			drag.forEach(function( element ) {
1347
				attach ( actions.start, element, start, {
1348
					handles: scope_Handles
1349
				});
1350
			});
1351
		}
1352
	}
1353
 
1354
 
1355
	// Test suggested values and apply margin, step.
1356
	function setHandle ( handle, to, noLimitOption ) {
1357
 
1358
		var trigger = handle !== scope_Handles[0] ? 1 : 0,
1359
			lowerMargin = scope_Locations[0] + options.margin,
1360
			upperMargin = scope_Locations[1] - options.margin,
1361
			lowerLimit = scope_Locations[0] + options.limit,
1362
			upperLimit = scope_Locations[1] - options.limit;
1363
 
1364
		// For sliders with multiple handles,
1365
		// limit movement to the other handle.
1366
		// Apply the margin option by adding it to the handle positions.
1367
		if ( scope_Handles.length > 1 ) {
1368
			to = trigger ? Math.max( to, lowerMargin ) : Math.min( to, upperMargin );
1369
		}
1370
 
1371
		// The limit option has the opposite effect, limiting handles to a
1372
		// maximum distance from another. Limit must be > 0, as otherwise
1373
		// handles would be unmoveable. 'noLimitOption' is set to 'false'
1374
		// for the .val() method, except for pass 4/4.
1375
		if ( noLimitOption !== false && options.limit && scope_Handles.length > 1 ) {
1376
			to = trigger ? Math.min ( to, lowerLimit ) : Math.max( to, upperLimit );
1377
		}
1378
 
1379
		// Handle the step option.
1380
		to = scope_Spectrum.getStep( to );
1381
 
1382
		// Limit to 0/100 for .val input, trim anything beyond 7 digits, as
1383
		// JavaScript has some issues in its floating point implementation.
1384
		to = limit(parseFloat(to.toFixed(7)));
1385
 
1386
		// Return false if handle can't move.
1387
		if ( to === scope_Locations[trigger] ) {
1388
			return false;
1389
		}
1390
 
1391
		// Set the handle to the new position.
1392
		handle.style[options.style] = to + '%';
1393
 
1394
		// Force proper handle stacking
1395
		if ( !handle.previousSibling ) {
1396
			removeClass(handle, Classes[17]);
1397
			if ( to > 50 ) {
1398
				addClass(handle, Classes[17]);
1399
			}
1400
		}
1401
 
1402
		// Update locations.
1403
		scope_Locations[trigger] = to;
1404
 
1405
		// Convert the value to the slider stepping/range.
1406
		scope_Values[trigger] = scope_Spectrum.fromStepping( to );
1407
 
1408
		fireEvent('update', trigger);
1409
 
1410
		return true;
1411
	}
1412
 
1413
	// Loop values from value method and apply them.
1414
	function setValues ( count, values ) {
1415
 
1416
		var i, trigger, to;
1417
 
1418
		// With the limit option, we'll need another limiting pass.
1419
		if ( options.limit ) {
1420
			count += 1;
1421
		}
1422
 
1423
		// If there are multiple handles to be set run the setting
1424
		// mechanism twice for the first handle, to make sure it
1425
		// can be bounced of the second one properly.
1426
		for ( i = 0; i < count; i += 1 ) {
1427
 
1428
			trigger = i%2;
1429
 
1430
			// Get the current argument from the array.
1431
			to = values[trigger];
1432
 
1433
			// Setting with null indicates an 'ignore'.
1434
			// Inputting 'false' is invalid.
1435
			if ( to !== null && to !== false ) {
1436
 
1437
				// If a formatted number was passed, attemt to decode it.
1438
				if ( typeof to === 'number' ) {
1439
					to = String(to);
1440
				}
1441
 
1442
				to = options.format.from( to );
1443
 
1444
				// Request an update for all links if the value was invalid.
1445
				// Do so too if setting the handle fails.
1446
				if ( to === false || isNaN(to) || setHandle( scope_Handles[trigger], scope_Spectrum.toStepping( to ), i === (3 - options.dir) ) === false ) {
1447
					fireEvent('update', trigger);
1448
				}
1449
			}
1450
		}
1451
	}
1452
 
1453
	// Set the slider value.
1454
	function valueSet ( input ) {
1455
 
1456
		var count, values = asArray( input ), i;
1457
 
1458
		// The RTL settings is implemented by reversing the front-end,
1459
		// internal mechanisms are the same.
1460
		if ( options.dir && options.handles > 1 ) {
1461
			values.reverse();
1462
		}
1463
 
1464
		// Animation is optional.
1465
		// Make sure the initial values where set before using animated placement.
1466
		if ( options.animate && scope_Locations[0] !== -1 ) {
1467
			addClassFor( scope_Target, Classes[14], 300 );
1468
		}
1469
 
1470
		// Determine how often to set the handles.
1471
		count = scope_Handles.length > 1 ? 3 : 1;
1472
 
1473
		if ( values.length === 1 ) {
1474
			count = 1;
1475
		}
1476
 
1477
		setValues ( count, values );
1478
 
1479
		// Fire the 'set' event for both handles.
1480
		for ( i = 0; i < scope_Handles.length; i++ ) {
1481
			fireEvent('set', i);
1482
		}
1483
	}
1484
 
1485
	// Get the slider value.
1486
	function valueGet ( ) {
1487
 
1488
		var i, retour = [];
1489
 
1490
		// Get the value from all handles.
1491
		for ( i = 0; i < options.handles; i += 1 ){
1492
			retour[i] = options.format.to( scope_Values[i] );
1493
		}
1494
 
1495
		return inSliderOrder( retour );
1496
	}
1497
 
1498
	// Removes classes from the root and empties it.
1499
	function destroy ( ) {
1500
		Classes.forEach(function(cls){
1501
			if ( !cls ) { return; } // Ignore empty classes
1502
			removeClass(scope_Target, cls);
1503
		});
1504
		scope_Target.innerHTML = '';
1505
		delete scope_Target.noUiSlider;
1506
	}
1507
 
1508
	// Get the current step size for the slider.
1509
	function getCurrentStep ( ) {
1510
 
1511
		// Check all locations, map them to their stepping point.
1512
		// Get the step point, then find it in the input list.
1513
		var retour = scope_Locations.map(function( location, index ){
1514
 
1515
			var step = scope_Spectrum.getApplicableStep( location ),
1516
 
1517
				// As per #391, the comparison for the decrement step can have some rounding issues.
1518
				// Round the value to the precision used in the step.
1519
				stepDecimals = countDecimals(String(step[2])),
1520
 
1521
				// Get the current numeric value
1522
				value = scope_Values[index],
1523
 
1524
				// To move the slider 'one step up', the current step value needs to be added.
1525
				// Use null if we are at the maximum slider value.
1526
				increment = location === 100 ? null : step[2],
1527
 
1528
				// Going 'one step down' might put the slider in a different sub-range, so we
1529
				// need to switch between the current or the previous step.
1530
				prev = Number((value - step[2]).toFixed(stepDecimals)),
1531
 
1532
				// If the value fits the step, return the current step value. Otherwise, use the
1533
				// previous step. Return null if the slider is at its minimum value.
1534
				decrement = location === 0 ? null : (prev >= step[1]) ? step[2] : (step[0] || false);
1535
 
1536
			return [decrement, increment];
1537
		});
1538
 
1539
		// Return values in the proper order.
1540
		return inSliderOrder( retour );
1541
	}
1542
 
1543
	// Attach an event to this slider, possibly including a namespace
1544
	function bindEvent ( namespacedEvent, callback ) {
1545
		scope_Events[namespacedEvent] = scope_Events[namespacedEvent] || [];
1546
		scope_Events[namespacedEvent].push(callback);
1547
 
1548
		// If the event bound is 'update,' fire it immediately for all handles.
1549
		if ( namespacedEvent.split('.')[0] === 'update' ) {
1550
			scope_Handles.forEach(function(a, index){
1551
				fireEvent('update', index);
1552
			});
1553
		}
1554
	}
1555
 
1556
	// Undo attachment of event
1557
	function removeEvent ( namespacedEvent ) {
1558
 
1559
		var event = namespacedEvent.split('.')[0],
1560
			namespace = namespacedEvent.substring(event.length);
1561
 
1562
		Object.keys(scope_Events).forEach(function( bind ){
1563
 
1564
			var tEvent = bind.split('.')[0],
1565
				tNamespace = bind.substring(tEvent.length);
1566
 
1567
			if ( (!event || event === tEvent) && (!namespace || namespace === tNamespace) ) {
1568
				delete scope_Events[bind];
1569
			}
1570
		});
1571
	}
1572
 
1573
 
1574
	// Throw an error if the slider was already initialized.
1575
	if ( scope_Target.noUiSlider ) {
1576
		throw new Error('Slider was already initialized.');
1577
	}
1578
 
1579
 
1580
	// Create the base element, initialise HTML and set classes.
1581
	// Add handles and links.
1582
	scope_Base = addSlider( options.dir, options.ort, scope_Target );
1583
	scope_Handles = addHandles( options.handles, options.dir, scope_Base );
1584
 
1585
	// Set the connect classes.
1586
	addConnection ( options.connect, scope_Target, scope_Handles );
1587
 
1588
	// Attach user events.
1589
	events( options.events );
1590
 
1591
	if ( options.pips ) {
1592
		pips(options.pips);
1593
	}
1594
 
1595
	return {
1596
		destroy: destroy,
1597
		steps: getCurrentStep,
1598
		on: bindEvent,
1599
		off: removeEvent,
1600
		get: valueGet,
1601
		set: valueSet
1602
	};
1603
 
1604
}
1605
 
1606
 
1607
	// Run the standard initializer
1608
	function initialize ( target, originalOptions ) {
1609
 
1610
		if ( !target.nodeName ) {
1611
			throw new Error('noUiSlider.create requires a single element.');
1612
		}
1613
 
1614
		// Test the options and create the slider environment;
1615
		var options = testOptions( originalOptions, target ),
1616
			slider = closure( target, options );
1617
 
1618
		// Use the public value method to set the start values.
1619
		slider.set(options.start);
1620
 
1621
		target.noUiSlider = slider;
1622
	}
1623
 
1624
	// Use an object instead of a function for future expansibility;
1625
	return {
1626
		create: initialize
1627
	};
1628
 
1629
}));