nouislider-rails 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1650 @@
1
+ /*! $.noUiSlider - WTFPL - refreshless.com/nouislider/ */
2
+
3
+ /*jslint browser: true */
4
+ /*jslint devel: true */
5
+ /*jslint continue: true */
6
+ /*jslint plusplus: true */
7
+ /*jslint sub: true */
8
+ /*jslint white: true */
9
+
10
+ // ==ClosureCompiler==
11
+ // @externs_url http://refreshless.com/externs/jquery-1.8.js
12
+ // @compilation_level ADVANCED_OPTIMIZATIONS
13
+ // @warning_level VERBOSE
14
+ // ==/ClosureCompiler==
15
+
16
+ (function( $ ){
17
+
18
+ 'use strict';
19
+
20
+ var
21
+ // Cache the document selector;
22
+ /** @const */ doc = $(document),
23
+ // Namespace for binding and unbinding slider events;
24
+ /** @const */ namespace = '.nui',
25
+ // Copy of the current value function;
26
+ /** @const */ $val = $.fn.val,
27
+ // Determine the events to bind. IE11 implements pointerEvents without
28
+ // a prefix, which breaks compatibility with the IE10 implementation.
29
+ /** @const */ actions = window.navigator.pointerEnabled ? {
30
+ start: 'pointerdown',
31
+ move: 'pointermove',
32
+ end: 'pointerup'
33
+ } : window.navigator.msPointerEnabled ? {
34
+ start: 'MSPointerDown',
35
+ move: 'MSPointerMove',
36
+ end: 'MSPointerUp'
37
+ } : {
38
+ start: 'mousedown touchstart',
39
+ move: 'mousemove touchmove',
40
+ end: 'mouseup touchend'
41
+ },
42
+ // Re-usable list of classes;
43
+ /** @const */ Classes = [
44
+ /* 0 */ 'noUi-target'
45
+ /* 1 */ ,'noUi-base'
46
+ /* 2 */ ,'noUi-origin'
47
+ /* 3 */ ,'noUi-handle'
48
+ /* 4 */ ,'noUi-horizontal'
49
+ /* 5 */ ,'noUi-vertical'
50
+ /* 6 */ ,'noUi-background'
51
+ /* 7 */ ,'noUi-connect'
52
+ /* 8 */ ,'noUi-ltr'
53
+ /* 9 */ ,'noUi-rtl'
54
+ /* 10 */ ,'noUi-dragable'
55
+ /* 11 */ ,''
56
+ /* 12 */ ,'noUi-state-drag'
57
+ /* 13 */ ,''
58
+ /* 14 */ ,'noUi-state-tap'
59
+ /* 15 */ ,'noUi-active'
60
+ /* 16 */ ,'noUi-extended'
61
+ /* 17 */ ,'noUi-stacking'
62
+ ],
63
+ /** @const */ Formatting = [
64
+ /* 0 */ 'decimals'
65
+ /* 1 */ ,'mark'
66
+ /* 2 */ ,'thousand'
67
+ /* 3 */ ,'prefix'
68
+ /* 4 */ ,'postfix'
69
+ /* 5 */ ,'encoder'
70
+ /* 6 */ ,'decoder'
71
+ /* 7 */ ,'negative'
72
+ /* 8 */ ,'negativeBefore'
73
+ ],
74
+ /** @const */ FormatDefaults = [
75
+ /* 0 */ 2
76
+ /* 1 */ ,'.'
77
+ /* 2 */ ,''
78
+ /* 3 */ ,''
79
+ /* 4 */ ,''
80
+ /* 5 */ ,function(a){ return a; }
81
+ /* 6 */ ,function(a){ return a; }
82
+ /* 7 */ ,'-'
83
+ /* 8 */ ,''
84
+ ];
85
+
86
+
87
+ // Error handling
88
+
89
+ function throwError( message ){
90
+ throw new RangeError('noUiSlider: ' + message);
91
+ }
92
+
93
+ // Throw an error if formatting options are incompatible.
94
+ function throwEqualError( F, a, b ) {
95
+ if ( (F[a] || F[b]) && (F[a] === F[b]) ) {
96
+ throwError("(Link) '"+a+"' can't match '"+b+"'.'");
97
+ }
98
+ }
99
+
100
+
101
+ // General helpers
102
+
103
+ // Limits a value to 0 - 100
104
+ function limit ( a ) {
105
+ return Math.max(Math.min(a, 100), 0);
106
+ }
107
+
108
+ // Round a value to the closest 'to'.
109
+ function closest ( value, to ) {
110
+ return Math.round(value / to) * to;
111
+ }
112
+
113
+ // Determine the size of a sub-range in relation to a full range.
114
+ function subRangeRatio ( pa, pb ) {
115
+ return (100 / (pb - pa));
116
+ }
117
+
118
+
119
+ // Type validation
120
+
121
+ function typeMatch ( a, b ) {
122
+ return (typeof a) === (typeof b);
123
+ }
124
+
125
+ // Test in an object is an instance of jQuery or Zepto.
126
+ function isInstance ( a ) {
127
+ return a instanceof $ || ( $['zepto'] && $['zepto']['isZ'](a) );
128
+ }
129
+
130
+ // Checks whether a value is numerical.
131
+ function isNumeric ( a ) {
132
+ return typeof a === 'number' && !isNaN( a ) && isFinite( a );
133
+ }
134
+
135
+ // Wraps a variable as an array, if it isn't one yet.
136
+ function asArray ( a ) {
137
+ return $.isArray(a) ? a : [a];
138
+ }
139
+
140
+
141
+ // Class handling
142
+
143
+ // Sets a class and removes it after [duration] ms.
144
+ function addClassFor ( element, className, duration ) {
145
+ element.addClass(className);
146
+ setTimeout(function(){
147
+ element.removeClass(className);
148
+ }, duration);
149
+ }
150
+
151
+ // Tests if element has a class, adds it if not. Returns original state.
152
+ function getsClass ( element, className ) {
153
+
154
+ var has = element.hasClass(className);
155
+
156
+ if ( !has ) {
157
+ element.addClass( className );
158
+ }
159
+
160
+ return has;
161
+ }
162
+
163
+
164
+ // Value calculation
165
+
166
+ // (percentage) How many percent is this value of this range?
167
+ function fromPercentage ( range, value ) {
168
+ return (value * 100) / ( range[1] - range[0] );
169
+ }
170
+
171
+ // (percentage) Where is this value on this range?
172
+ function toPercentage ( range, value ) {
173
+ return fromPercentage( range, range[0] < 0 ?
174
+ value + Math.abs(range[0]) :
175
+ value - range[0] );
176
+ }
177
+
178
+ // (value) How much is this percentage on this range?
179
+ function isPercentage ( range, value ) {
180
+ return ((value * ( range[1] - range[0] )) / 100) + range[0];
181
+ }
182
+
183
+ // (percentage)
184
+ function toStepping ( options, value ) {
185
+
186
+ if ( value >= options.xVal.slice(-1)[0] ){
187
+ return 100;
188
+ }
189
+
190
+ var j = 1, va, vb, pa, pb;
191
+ while ( value >= options.xVal[j] ){
192
+ j++;
193
+ }
194
+
195
+ va = options.xVal[j-1];
196
+ vb = options.xVal[j];
197
+ pa = options.xPct[j-1];
198
+ pb = options.xPct[j];
199
+
200
+ return pa + (toPercentage([va, vb], value) / subRangeRatio (pa, pb));
201
+ }
202
+
203
+ // (value)
204
+ function fromStepping ( options, value ) {
205
+
206
+ // There is no range group that fits 100
207
+ if ( value >= 100 ){
208
+ return options.xVal.slice(-1)[0];
209
+ }
210
+
211
+ var j = 1, va, vb, pa, pb;
212
+ while ( value >= options.xPct[j] ){
213
+ j++;
214
+ }
215
+
216
+ va = options.xVal[j-1];
217
+ vb = options.xVal[j];
218
+ pa = options.xPct[j-1];
219
+ pb = options.xPct[j];
220
+
221
+ return isPercentage([va, vb], (value - pa) * subRangeRatio (pa, pb));
222
+ }
223
+
224
+ // (percentage) Get the step that applies at a certain value.
225
+ function getStep ( options, value ){
226
+
227
+ var j = 1, a, b;
228
+ while ( value >= options.xPct[j] ){
229
+ j++;
230
+ }
231
+
232
+ if ( options.snap ) {
233
+
234
+ a = options.xPct[j-1];
235
+ b = options.xPct[j];
236
+
237
+ if ((value - a) > ((b-a)/2)){
238
+ return b;
239
+ }
240
+
241
+ return a;
242
+ }
243
+
244
+ if ( !options.xSteps[j-1] ){
245
+ return value;
246
+ }
247
+
248
+ return options.xPct[j-1] + closest(
249
+ value - options.xPct[j-1],
250
+ options.xSteps[j-1]
251
+ );
252
+ }
253
+
254
+
255
+ // Event handling
256
+
257
+ // Provide a clean event with standardized offset values.
258
+ function fixEvent ( e ) {
259
+
260
+ // Prevent scrolling and panning on touch events, while
261
+ // attempting to slide. The tap event also depends on this.
262
+ e.preventDefault();
263
+
264
+ // Filter the event to register the type, which can be
265
+ // touch, mouse or pointer. Offset changes need to be
266
+ // made on an event specific basis.
267
+ var touch = e.type.indexOf('touch') === 0
268
+ ,mouse = e.type.indexOf('mouse') === 0
269
+ ,pointer = e.type.indexOf('pointer') === 0
270
+ ,x,y, event = e;
271
+
272
+ // IE10 implemented pointer events with a prefix;
273
+ if ( e.type.indexOf('MSPointer') === 0 ) {
274
+ pointer = true;
275
+ }
276
+
277
+ // Get the originalEvent, if the event has been wrapped
278
+ // by jQuery. Zepto doesn't wrap the event.
279
+ if ( e.originalEvent ) {
280
+ e = e.originalEvent;
281
+ }
282
+
283
+ if ( touch ) {
284
+ // noUiSlider supports one movement at a time,
285
+ // so we can select the first 'changedTouch'.
286
+ x = e.changedTouches[0].pageX;
287
+ y = e.changedTouches[0].pageY;
288
+ }
289
+
290
+ if ( mouse || pointer ) {
291
+
292
+ // Polyfill the pageXOffset and pageYOffset
293
+ // variables for IE7 and IE8;
294
+ if( !pointer && window.pageXOffset === undefined ){
295
+ window.pageXOffset = document.documentElement.scrollLeft;
296
+ window.pageYOffset = document.documentElement.scrollTop;
297
+ }
298
+
299
+ x = e.clientX + window.pageXOffset;
300
+ y = e.clientY + window.pageYOffset;
301
+ }
302
+
303
+ event.points = [x, y];
304
+ event.cursor = mouse;
305
+
306
+ return event;
307
+ }
308
+
309
+
310
+ // Organize formatting in an object.
311
+
312
+ /** @constructor */
313
+ function Format( options ){
314
+
315
+ // If no settings where provided, the defaults will be loaded.
316
+ if ( options === undefined ){
317
+ options = {};
318
+ }
319
+
320
+ if ( typeof options !== 'object' ){
321
+ throwError("(Format) 'format' option must be an object.");
322
+ }
323
+
324
+ var settings = {};
325
+
326
+ // Copy all values into a new object.
327
+ $(Formatting).each(function(i, val){
328
+
329
+ if ( options[val] === undefined ){
330
+
331
+ settings[val] = FormatDefaults[i];
332
+
333
+ // When we aren't loading defaults, validate the entry.
334
+ } else if ( typeMatch(options[val], FormatDefaults[i]) ) {
335
+
336
+ // Support for up to 7 decimals.
337
+ // More can't be guaranteed due to floating point issues.
338
+ if ( val === 'decimals' ){
339
+ if ( options[val] < 0 || options[val] > 7 ){
340
+ throwError("(Format) 'format.decimals' option must be between 0 and 7.");
341
+ }
342
+ }
343
+
344
+ settings[val] = options[val];
345
+
346
+ // If the value isn't valid, emit an error.
347
+ } else {
348
+ throwError("(Format) 'format."+val+"' must be a " + typeof FormatDefaults[i] + ".");
349
+ }
350
+ });
351
+
352
+ // Some values can't be extracted from a
353
+ // string if certain combinations are present.
354
+ throwEqualError(settings, 'mark', 'thousand');
355
+ throwEqualError(settings, 'prefix', 'negative');
356
+ throwEqualError(settings, 'prefix', 'negativeBefore');
357
+
358
+ this.settings = settings;
359
+ }
360
+
361
+ // Shorthand for internal value get
362
+ Format.prototype.v = function ( a ) {
363
+ return this.settings[a];
364
+ };
365
+
366
+ Format.prototype.to = function ( number ) {
367
+
368
+ function reverse ( a ) {
369
+ return a.split('').reverse().join('');
370
+ }
371
+
372
+ number = this.v('encoder')( number );
373
+
374
+ var negative = '', preNegative = '', base = '', mark = '';
375
+
376
+ if ( number < 0 ) {
377
+ negative = this.v('negative');
378
+ preNegative = this.v('negativeBefore');
379
+ }
380
+
381
+ // Round to proper decimal count
382
+ number = Math.abs(number).toFixed( this.v('decimals') ).toString();
383
+ number = number.split('.');
384
+
385
+ // Rounding away decimals might cause a value of -0
386
+ // when using very small ranges. Remove those cases.
387
+ if ( parseFloat(number) === 0 ) {
388
+ number[0] = '0';
389
+ }
390
+
391
+ // Group numbers in sets of three.
392
+ if ( this.v('thousand') ) {
393
+ base = reverse(number[0]).match(/.{1,3}/g);
394
+ base = reverse(base.join(reverse( this.v('thousand') )));
395
+ } else {
396
+ base = number[0];
397
+ }
398
+
399
+ // Ignore the decimal separator if decimals are set to 0.
400
+ if ( this.v('mark') && number.length > 1 ) {
401
+ mark = this.v('mark') + number[1];
402
+ }
403
+
404
+ // Return the finalized formatted number.
405
+ return preNegative +
406
+ this.v('prefix') +
407
+ negative +
408
+ base +
409
+ mark +
410
+ this.v('postfix');
411
+ };
412
+
413
+ Format.prototype.from = function ( input ) {
414
+
415
+ function esc(s){
416
+ return s.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&');
417
+ }
418
+
419
+ var isNeg;
420
+ // The set request might want to ignore this handle.
421
+ // Test for 'undefined' too, as a two-handle slider
422
+ // can still be set with an integer.
423
+ if( input === null || input === undefined ) {
424
+ return false;
425
+ }
426
+
427
+ // Remove formatting and set period for float parsing.
428
+ input = input.toString();
429
+
430
+ // Replace the preNegative indicator.
431
+ isNeg = input.replace(new RegExp('^' + esc( this.v('negativeBefore') )), '');
432
+
433
+ // Check if the value changed by removing the negativeBefore symbol.
434
+ if( input !== isNeg ) {
435
+ input = isNeg;
436
+ isNeg = '-';
437
+ } else {
438
+ isNeg = '';
439
+ }
440
+
441
+ // If prefix is set and the number is actually prefixed.
442
+ input = input.replace(new RegExp('^'+esc( this.v('prefix') )), '');
443
+
444
+ // Only replace if a negative sign is set.
445
+ if ( this.v['negative'] ) {
446
+
447
+ // Reset isNeg to prevent double '-' insertion.
448
+ isNeg = '';
449
+
450
+ // Reset the negative sign to '-'
451
+ input = input.replace(new RegExp('^'+esc( this.v('negative') )), '-');
452
+ }
453
+
454
+ // Clean the input string
455
+ input = input
456
+ // If postfix is set and the number is postfixed.
457
+ .replace( new RegExp(esc( this.v('postfix') ) + '$'), '')
458
+ // Remove the separator every three digits.
459
+ .replace( new RegExp(esc( this.v('thousand') ), 'g'), '')
460
+ // Set the decimal separator back to period.
461
+ .replace( this.v('mark'), '.');
462
+
463
+ // Run the user defined decoder. Returns input by default.
464
+ input = this.v('decoder')( parseFloat( isNeg + input ) );
465
+
466
+ // Ignore invalid input
467
+ if (isNaN( input )) {
468
+ return false;
469
+ }
470
+
471
+ return input;
472
+ };
473
+
474
+
475
+ // Serialization target
476
+
477
+ /** @constructor */
478
+ function Link( entry, update ){
479
+
480
+ // Make sure Link isn't called as a function, in which case
481
+ // the 'this' scope would be the window.
482
+ if ( !(this instanceof Link) ) {
483
+ throw new Error( "Link: " +
484
+ "Don't use Link as a function. " +
485
+ "Use the 'new' keyword.");
486
+ }
487
+
488
+ if ( !entry ) {
489
+ throw new RangeError("Link: missing parameters.");
490
+ }
491
+
492
+ // Write all formatting to this object.
493
+ // No validation needed, as we'll merge these with the parent
494
+ // format options first.
495
+ this.formatting = entry['format'] || {};
496
+
497
+ // Store the update option.
498
+ this.update = !update;
499
+
500
+ // In IE < 9, .bind() isn't available, need this link in .change().
501
+ var that = this,
502
+
503
+ // Get values from the input.
504
+ target = entry['target'] || function(){},
505
+ method = entry['method'],
506
+
507
+ // Find the type of this link.
508
+ isTooltip = ( typeof target === 'string' && target.indexOf('-tooltip-') === 0 ),
509
+ isHidden = ( typeof target === 'string' && target.indexOf('-') !== 0 ),
510
+ isMethod = ( typeof target === 'function' ),
511
+ is$ = ( isInstance(target) ),
512
+ isInput = ( is$ && target.is('input, select, textarea') ),
513
+ methodIsFunction = ( is$ && typeof method === 'function' ),
514
+ methodIsName = ( is$ && typeof method === 'string' && target[method] );
515
+
516
+ // If target is a string, a new hidden input will be created.
517
+ if ( isTooltip ) {
518
+
519
+ // By default, use the 'html' method.
520
+ this.method = method || 'html';
521
+
522
+ // Use jQuery to create the element
523
+ this.el = $( target.replace('-tooltip-', '') || '<div/>' )[0];
524
+
525
+ return;
526
+ }
527
+
528
+ // If the string doesn't begin with '-', which is reserved, add a new hidden input.
529
+ if ( isHidden ) {
530
+
531
+ this.method = 'val';
532
+
533
+ this.el = document.createElement('input');
534
+ this.el.name = target;
535
+ this.el.type = 'hidden';
536
+
537
+ return;
538
+ }
539
+
540
+ // The target can also be a function, which will be called.
541
+ if ( isMethod ) {
542
+ this.target = false;
543
+ this.method = target;
544
+ return;
545
+ }
546
+
547
+ // If the target is and $ element.
548
+ if ( is$ ) {
549
+
550
+ // The method must exist on the element.
551
+ if ( method && ( methodIsFunction || methodIsName ) ) {
552
+ this.target = target;
553
+ this.method = method;
554
+ return;
555
+ }
556
+
557
+ // If a jQuery/Zepto input element is provided, but no method is set,
558
+ // the element can assume it needs to respond to 'change'...
559
+ if ( !method && isInput ) {
560
+
561
+ // Default to .val if this is an input element.
562
+ this.method = 'val';
563
+ this.target = target;
564
+
565
+ // Set the slider to a new value on change.
566
+ this.target.on('change', function( e ){
567
+
568
+ // Returns null array.
569
+ function at(a,b,c){
570
+ return [c?a:b, c?b:a];
571
+ }
572
+
573
+ var output = at(null, $(e.target).val(), that.N);
574
+
575
+ that.obj.val(output, { 'link': that });
576
+ });
577
+
578
+ return;
579
+ }
580
+
581
+ // ... or not.
582
+ if ( !method && !isInput ) {
583
+
584
+ // Default arbitrarily to 'html'.
585
+ this.method = 'html';
586
+ this.target = target;
587
+
588
+ return;
589
+ }
590
+ }
591
+
592
+ throw new RangeError("Link: Invalid Link.");
593
+ }
594
+
595
+ // Provides external items with the slider value.
596
+ Link.prototype.write = function ( options, value, handle, slider, update ) {
597
+
598
+ // Don't synchronize this Link.
599
+ if ( this.update && update === false ) {
600
+ return;
601
+ }
602
+
603
+ // Convert the value to the slider stepping/range.
604
+ value = fromStepping( options, value );
605
+
606
+ // Format values for display.
607
+ value = this.format( value );
608
+
609
+ // Store the numerical value.
610
+ this.saved = value;
611
+
612
+ // Branch between serialization to a function or an object.
613
+ if ( typeof this.method === 'function' ) {
614
+ // When target is undefined, the target was a function.
615
+ // In that case, provided the slider as the calling scope.
616
+ // Use [0] to get the DOM element, not the $ instance.
617
+ this.method.call( this.target[0] || slider[0], value, handle, slider );
618
+ } else {
619
+ this.target[ this.method ]( value, handle, slider );
620
+ }
621
+ };
622
+
623
+ // Parses slider value to user defined display.
624
+ Link.prototype.format = function ( a ) {
625
+ return this.formatting.to(a);
626
+ };
627
+
628
+ // Converts a formatted value back to a real number.
629
+ Link.prototype.valueOf = function ( a ) {
630
+ return this.formatting.from(a);
631
+ };
632
+
633
+
634
+ // Input validation
635
+
636
+ function testStep ( parsed, entry ) {
637
+
638
+ if ( !isNumeric( entry ) ) {
639
+ throwError("'step' is not numeric.");
640
+ }
641
+
642
+ // The step option can still be used to set stepping
643
+ // for linear sliders. Overwritten if set in 'range'.
644
+ parsed.xSteps[0] = entry;
645
+ }
646
+
647
+ function testRange ( parsed, entry ) {
648
+
649
+ // Filter incorrect input.
650
+ if ( typeof entry !== 'object' || $.isArray(entry) ) {
651
+ throwError("'range' is not an object.");
652
+ }
653
+
654
+ // Loop all entries.
655
+ $.each( entry, function ( index, value ) {
656
+
657
+ var percentage;
658
+
659
+ // Wrap numerical input in an array.
660
+ if ( typeof value === "number" ) {
661
+ value = [value];
662
+ }
663
+
664
+ // Reject any invalid input.
665
+ if ( !$.isArray( value ) ){
666
+ throwError("'range' contains invalid value.");
667
+ }
668
+
669
+ // Covert min/max syntax to 0 and 100.
670
+ if ( index === 'min' ) {
671
+ percentage = 0;
672
+ } else if ( index === 'max' ) {
673
+ percentage = 100;
674
+ } else {
675
+ percentage = parseFloat( index );
676
+ }
677
+
678
+ // Check for correct input.
679
+ if ( !isNumeric( percentage ) || !isNumeric( value[0] ) ) {
680
+ throwError("'range' value isn't numeric.");
681
+ }
682
+
683
+ // Store values.
684
+ parsed.xPct.push( percentage );
685
+ parsed.xVal.push( value[0] );
686
+
687
+ // NaN will evaluate to false too, but to keep
688
+ // logging clear, set step explicitly. Make sure
689
+ // not to override the 'step' setting with false.
690
+ if ( !percentage ) {
691
+ if ( !isNaN( value[1] ) ) {
692
+ parsed.xSteps[0] = value[1];
693
+ }
694
+ } else {
695
+ parsed.xSteps.push( isNaN(value[1]) ? false : value[1] );
696
+ }
697
+ });
698
+
699
+ $.each(parsed.xSteps, function(i,n){
700
+
701
+ // Ignore 'false' stepping.
702
+ if ( !n ) {
703
+ return true;
704
+ }
705
+
706
+ // Check if step fits. Not required, but this might serve some goal.
707
+ // !((parsed.xVal[i+1] - parsed.xVal[i]) % n);
708
+
709
+ // Factor to range ratio
710
+ parsed.xSteps[i] = fromPercentage([
711
+ parsed.xVal[i]
712
+ ,parsed.xVal[i+1]
713
+ ], n) / subRangeRatio (
714
+ parsed.xPct[i],
715
+ parsed.xPct[i+1] );
716
+ });
717
+ }
718
+
719
+ function testStart ( parsed, entry ) {
720
+
721
+ if ( typeof entry === "number" ) {
722
+ entry = [entry];
723
+ }
724
+
725
+ // Validate input. Values aren't tested, the internal Link will do
726
+ // that and provide a valid location.
727
+ if ( !$.isArray( entry ) || !entry.length || entry.length > 2 ) {
728
+ throwError("'start' option is incorrect.");
729
+ }
730
+
731
+ // Store the number of handles.
732
+ parsed.handles = entry.length;
733
+
734
+ // When the slider is initialized, the .val method will
735
+ // be called with the start options.
736
+ parsed.start = entry;
737
+ }
738
+
739
+ function testSnap ( parsed, entry ) {
740
+
741
+ // Enforce 100% stepping within subranges.
742
+ parsed.snap = entry;
743
+
744
+ if ( typeof entry !== 'boolean' ){
745
+ throwError("'snap' option must be a boolean.");
746
+ }
747
+ }
748
+
749
+ function testConnect ( parsed, entry ) {
750
+
751
+ if ( entry === 'lower' && parsed.handles === 1 ) {
752
+ parsed.connect = 1;
753
+ } else if ( entry === 'upper' && parsed.handles === 1 ) {
754
+ parsed.connect = 2;
755
+ } else if ( entry === true && parsed.handles === 2 ) {
756
+ parsed.connect = 3;
757
+ } else if ( entry === false ) {
758
+ parsed.connect = 0;
759
+ } else {
760
+ throwError("'connect' option was doesn't match handle count.");
761
+ }
762
+ }
763
+
764
+ function testOrientation ( parsed, entry ) {
765
+
766
+ // Set orientation to an a numerical value for easy
767
+ // array selection.
768
+ switch ( entry ){
769
+ case 'horizontal':
770
+ parsed.ort = 0;
771
+ break;
772
+ case 'vertical':
773
+ parsed.ort = 1;
774
+ break;
775
+ default:
776
+ throwError("'orientation' option is invalid.");
777
+ }
778
+ }
779
+
780
+ function testMargin ( parsed, entry ) {
781
+
782
+ if ( parsed.xPct.length > 2 ) {
783
+ throwError("'margin' option is only supported on linear sliders.");
784
+ }
785
+
786
+ // Parse value to range and store. As xVal is checked
787
+ // to be no bigger than 2, use it as range.
788
+ parsed.margin = fromPercentage(parsed.xVal, entry);
789
+
790
+ if ( !isNumeric(entry) ){
791
+ throwError("'margin' option must be numeric.");
792
+ }
793
+ }
794
+
795
+ function testDirection ( parsed, entry ) {
796
+
797
+ // Set direction as a numerical value for easy parsing.
798
+ // Invert connection for RTL sliders, so that the proper
799
+ // handles get the connect/background classes.
800
+ switch ( entry ) {
801
+ case 'ltr':
802
+ parsed.dir = 0;
803
+ break;
804
+ case 'rtl':
805
+ parsed.dir = 1;
806
+ parsed.connect = [0,2,1,3][parsed.connect];
807
+ break;
808
+ default:
809
+ throwError("'direction' option was not recognized.");
810
+ }
811
+ }
812
+
813
+ function testBehaviour ( parsed, entry ) {
814
+
815
+ // Make sure the input is a string.
816
+ if ( typeof entry !== 'string' ) {
817
+ throwError("'behaviour' must be a string containing options.");
818
+ }
819
+
820
+ // Check if the string contains any keywords.
821
+ // None are required.
822
+ var tap = entry.indexOf('tap') >= 0,
823
+ extend = entry.indexOf('extend') >= 0,
824
+ drag = entry.indexOf('drag') >= 0,
825
+ fixed = entry.indexOf('fixed') >= 0,
826
+ snap = entry.indexOf('snap') >= 0;
827
+
828
+ parsed.events = {
829
+ tap: tap || snap,
830
+ extend: extend,
831
+ drag: drag,
832
+ fixed: fixed,
833
+ snap: snap
834
+ };
835
+ }
836
+
837
+ function testSerialization ( parsed, entry, sliders ) {
838
+
839
+ parsed.ser = [ entry['lower'], entry['upper'] ];
840
+ parsed.formatting = new Format( entry['format'] );
841
+
842
+ $.each( parsed.ser, function( i, a ){
843
+
844
+ // Check if the provided option is an array.
845
+ if ( !$.isArray(a) ) {
846
+ throwError("'serialization."+(!i?'lower':'upper')+"' must be an array.");
847
+ }
848
+
849
+ $.each(a, function(){
850
+
851
+ // Check if entry is a Link.
852
+ if ( !(this instanceof Link) ) {
853
+ throwError("'serialization."+(!i?'lower':'upper')+"' can only contain Link instances.");
854
+ }
855
+
856
+ // Assign other properties.
857
+ this.N = i;
858
+ this.obj = sliders;
859
+ this.scope = this.scope || sliders;
860
+
861
+ // Run internal validator.
862
+ this.formatting = new Format($.extend({}
863
+ ,entry['format']
864
+ ,this.formatting
865
+ ));
866
+ });
867
+ });
868
+
869
+ // If the slider has two handles and is RTL,
870
+ // reverse the serialization input. For one handle,
871
+ // lower is still lower.
872
+ if ( parsed.dir && parsed.handles > 1 ) {
873
+ parsed.ser.reverse();
874
+ }
875
+ }
876
+
877
+ // Test all developer settings and parse to assumption-safe values.
878
+ function test ( options, sliders ){
879
+
880
+ /* Every input option is tested and parsed. This'll prevent
881
+ endless validation in internal methods. These tests are
882
+ structured with an item for every option available. An
883
+ option can be marked as required by setting the 'r' flag.
884
+ The testing function is provided with three arguments:
885
+ - The provided value for the option;
886
+ - A reference to the options object;
887
+ - The name for the option;
888
+
889
+ The testing function returns false when an error is detected,
890
+ or true when everything is OK. It can also modify the option
891
+ object, to make sure all values can be correctly looped elsewhere. */
892
+
893
+ var parsed = {
894
+ xPct: []
895
+ ,xVal: []
896
+ ,xSteps: [ false ]
897
+ ,margin: 0
898
+ }, tests;
899
+
900
+ tests = {
901
+ 'step': { r: false, t: testStep },
902
+ 'range': { r: true, t: testRange },
903
+ 'start': { r: true, t: testStart },
904
+ 'snap': { r: false, t: testSnap },
905
+ 'connect': { r: true, t: testConnect },
906
+ 'orientation': { r: false, t: testOrientation },
907
+ 'margin': { r: false, t: testMargin },
908
+ 'direction': { r: true, t: testDirection },
909
+ 'behaviour': { r: true, t: testBehaviour },
910
+ 'serialization': { r: true, t: testSerialization }
911
+ };
912
+
913
+ // Set defaults where applicable.
914
+ options = $.extend({
915
+ 'connect': false
916
+ ,'direction': 'ltr'
917
+ ,'behaviour': 'tap'
918
+ ,'orientation': 'horizontal'
919
+ }, options);
920
+
921
+ // Make sure the test for serialization runs.
922
+ options['serialization'] = $.extend({
923
+ 'lower': []
924
+ ,'upper': []
925
+ ,'format': {}
926
+ }, options['serialization']);
927
+
928
+ // Run all options through a testing mechanism to ensure correct
929
+ // input. It should be noted that options might get modified to
930
+ // be handled properly. E.g. wrapping integers in arrays.
931
+ $.each( tests, function( name, test ){
932
+
933
+ if ( options[name] === undefined ) {
934
+ if ( test.r ) {
935
+ throwError("'" + name + "' is required.");
936
+ } else {
937
+ return true;
938
+ }
939
+ }
940
+
941
+ test.t( parsed, options[name], sliders );
942
+ });
943
+
944
+ // Pre-define the styles.
945
+ parsed.style = parsed.ort ? 'top' : 'left';
946
+
947
+ return parsed;
948
+ }
949
+
950
+
951
+ // DOM additions
952
+
953
+ // Append a handle to the base.
954
+ function addHandle ( options, index ) {
955
+
956
+ var handle = $('<div><div/></div>').addClass( Classes[2] ),
957
+ additions = [ '-lower', '-upper' ];
958
+
959
+ if ( options.dir ) {
960
+ additions.reverse();
961
+ }
962
+
963
+ handle.children().addClass(
964
+ Classes[3] + " " + Classes[3]+additions[index]
965
+ );
966
+
967
+ return handle;
968
+ }
969
+
970
+ // Create a copy of an element-creating Link.
971
+ function addElement ( handle, link ) {
972
+
973
+ // If the Link requires creation of a new element,
974
+ // create this element and return a new Link instance.
975
+ if ( link.el ) {
976
+ link = new Link({
977
+ 'target': $(link.el).clone().appendTo( handle ),
978
+ 'method': link.method,
979
+ 'format': link.formatting
980
+ }, true);
981
+ }
982
+
983
+ // Otherwise, return the reference.
984
+ return link;
985
+ }
986
+
987
+ // Loop all links for a handle.
988
+ function addElements ( elements, handle, formatting ) {
989
+
990
+ var index, list = [];
991
+
992
+ // Use the Link interface to provide unified
993
+ // formatting for the .val() method.
994
+ list.push(
995
+ new Link({
996
+ 'format': formatting
997
+ }, true)
998
+ );
999
+
1000
+ // Loop all links in either 'lower' or 'upper'.
1001
+ for ( index = 0; index < elements.length; index++ ) {
1002
+ list.push(addElement(handle, elements[index]));
1003
+ }
1004
+
1005
+ return list;
1006
+ }
1007
+
1008
+ // Go over all Links and assign them to a handle.
1009
+ function addLinks ( options, handles ) {
1010
+
1011
+ var index, links = [];
1012
+
1013
+ // Copy the links into a new array, instead of modifying
1014
+ // the 'options.ser' list. This allows replacement of the invalid
1015
+ // '.el' Links, while the others are still passed by reference.
1016
+ for ( index = 0; index < options.handles; index++ ) {
1017
+
1018
+ // Append a new array.
1019
+ links[index] = addElements(
1020
+ options.ser[index],
1021
+ handles[index].children(),
1022
+ options.formatting
1023
+ );
1024
+ }
1025
+
1026
+ return links;
1027
+ }
1028
+
1029
+ // Add the proper connection classes.
1030
+ function addConnection ( connect, target, handles ) {
1031
+
1032
+ // Apply the required connection classes to the elements
1033
+ // that need them. Some classes are made up for several
1034
+ // segments listed in the class list, to allow easy
1035
+ // renaming and provide a minor compression benefit.
1036
+ switch ( connect ) {
1037
+ case 1: target.addClass( Classes[7] );
1038
+ handles[0].addClass( Classes[6] );
1039
+ break;
1040
+ case 3: handles[1].addClass( Classes[6] );
1041
+ /* falls through */
1042
+ case 2: handles[0].addClass( Classes[7] );
1043
+ /* falls through */
1044
+ case 0: target.addClass(Classes[6]);
1045
+ break;
1046
+ }
1047
+ }
1048
+
1049
+ // Add handles and loop Link elements.
1050
+ function addHandles ( options, base ) {
1051
+
1052
+ var index, handles = [];
1053
+
1054
+ // Append handles.
1055
+ for ( index = 0; index < options.handles; index++ ) {
1056
+
1057
+ // Keep a list of all added handles.
1058
+ handles.push( addHandle( options, index ).appendTo(base) );
1059
+ }
1060
+
1061
+ return handles;
1062
+ }
1063
+
1064
+ // Initialize a single slider.
1065
+ function addSlider ( options, target ) {
1066
+
1067
+ // Apply classes and data to the target.
1068
+ target.addClass([
1069
+ Classes[0],
1070
+ Classes[8 + options.dir],
1071
+ Classes[4 + options.ort]
1072
+ ].join(' '));
1073
+
1074
+ return $('<div/>').appendTo(target).addClass( Classes[1] );
1075
+ }
1076
+
1077
+
1078
+ // Slider scope
1079
+
1080
+ function closure ( target, options, originalOptions ){
1081
+
1082
+ // Internal variables
1083
+
1084
+ // All variables local to 'closure' are marked $.
1085
+ var $Target = $(target),
1086
+ $Locations = [-1, -1],
1087
+ $Base,
1088
+ $Serialization,
1089
+ $Handles;
1090
+
1091
+ // Shorthand for base dimensions.
1092
+ function baseSize ( ) {
1093
+ return $Base[['width', 'height'][options.ort]]();
1094
+ }
1095
+
1096
+
1097
+ // External event handling
1098
+
1099
+ function fireEvents ( events ) {
1100
+
1101
+ // Use the external api to get the values.
1102
+ // Wrap the values in an array, as .trigger takes
1103
+ // only one additional argument.
1104
+ var index, values = [ $Target.val() ];
1105
+
1106
+ for ( index = 0; index < events.length; index++ ){
1107
+ $Target.trigger(events[index], values);
1108
+ }
1109
+ }
1110
+
1111
+
1112
+ // Handle placement
1113
+
1114
+ // Test suggested values and apply margin, step.
1115
+ function setHandle ( handle, to, delimit ) {
1116
+
1117
+ var n = handle[0] !== $Handles[0][0] ? 1 : 0,
1118
+ lower = $Locations[0] + options.margin,
1119
+ upper = $Locations[1] - options.margin;
1120
+
1121
+ // Don't delimit range dragging.
1122
+ if ( delimit && $Handles.length > 1 ) {
1123
+ to = n ? Math.max( to, lower ) : Math.min( to, upper );
1124
+ }
1125
+
1126
+ // Handle the step option.
1127
+ if ( to < 100 ){
1128
+ to = getStep(options, to);
1129
+ }
1130
+
1131
+ // Limit to 0/100 for .val input, trim anything beyond 7 digits, as
1132
+ // JavaScript has some issues in its floating point implementation.
1133
+ to = limit(parseFloat(to.toFixed(7)));
1134
+
1135
+ // Return falsy if handle can't move. False for 0 or 100 limit,
1136
+ // '0' for limiting by another handle.
1137
+ if ( to === $Locations[n] ) {
1138
+ if ( $Handles.length === 1 ) {
1139
+ return false;
1140
+ }
1141
+ return ( to === lower || to === upper ) ? 0 : false;
1142
+ }
1143
+
1144
+ // Set the handle to the new position.
1145
+ handle.css( options.style, to + '%' );
1146
+
1147
+ // Force proper handle stacking
1148
+ if ( handle.is(':first-child') ) {
1149
+ handle.toggleClass(Classes[17], to > 50 );
1150
+ }
1151
+
1152
+ // Update locations.
1153
+ $Locations[n] = to;
1154
+
1155
+ // Invert the value if this is a right-to-left slider.
1156
+ if ( options.dir ) {
1157
+ to = 100 - to;
1158
+ }
1159
+
1160
+ // Write values to serialization Links.
1161
+ // Convert the value to the correct relative representation.
1162
+ $($Serialization[n]).each(function(){
1163
+ this.write( options, to, handle.children(), $Target );
1164
+ });
1165
+
1166
+ return true;
1167
+ }
1168
+
1169
+ // Delimit proposed values for handle positions.
1170
+ function getPositions ( a, b, delimit ) {
1171
+
1172
+ // Add movement to current position.
1173
+ var c = a + b[0], d = a + b[1];
1174
+
1175
+ // Only alter the other position on drag,
1176
+ // not on standard sliding.
1177
+ if ( delimit ) {
1178
+ if ( c < 0 ) {
1179
+ d += Math.abs(c);
1180
+ }
1181
+ if ( d > 100 ) {
1182
+ c -= ( d - 100 );
1183
+ }
1184
+
1185
+ // Limit values to 0 and 100.
1186
+ return [limit(c), limit(d)];
1187
+ }
1188
+
1189
+ return [c,d];
1190
+ }
1191
+
1192
+ // Handles movement by tapping.
1193
+ function jump ( handle, to, instant ) {
1194
+
1195
+ if ( !instant ) {
1196
+ // Flag the slider as it is now in a transitional state.
1197
+ // Transition takes 300 ms, so re-enable the slider afterwards.
1198
+ addClassFor( $Target, Classes[14], 300 );
1199
+ }
1200
+
1201
+ // Move the handle to the new position.
1202
+ setHandle( handle, to, false );
1203
+
1204
+ fireEvents(['slide', 'set', 'change']);
1205
+ }
1206
+
1207
+
1208
+ // Events
1209
+
1210
+ // Handler for attaching events trough a proxy.
1211
+ function attach ( events, element, callback, data ) {
1212
+
1213
+ // Add the noUiSlider namespace to all events.
1214
+ events = events.replace( /\s/g, namespace + ' ' ) + namespace;
1215
+
1216
+ // Bind a closure on the target.
1217
+ return element.on( events, function( e ){
1218
+
1219
+ // jQuery and Zepto handle unset attributes differently.
1220
+ var disabled = $Target.attr('disabled');
1221
+ disabled = !( disabled === undefined || disabled === null );
1222
+
1223
+ // Test if there is anything that should prevent an event
1224
+ // from being handled, such as a disabled state or an active
1225
+ // 'tap' transition.
1226
+ if( $Target.hasClass( Classes[14] ) || disabled ) {
1227
+ return false;
1228
+ }
1229
+
1230
+ e = fixEvent(e);
1231
+ e.calcPoint = e.points[ options.ort ];
1232
+
1233
+ // Call the event handler with the event [ and additional data ].
1234
+ callback ( e, data );
1235
+ });
1236
+ }
1237
+
1238
+ // Handle movement on document for handle and range drag.
1239
+ function move ( event, data ) {
1240
+
1241
+ var handles = data.handles || $Handles, positions, state = false,
1242
+ proposal = ((event.calcPoint - data.start) * 100) / baseSize(),
1243
+ h = handles[0][0] !== $Handles[0][0] ? 1 : 0;
1244
+
1245
+ // Calculate relative positions for the handles.
1246
+ positions = getPositions( proposal, data.positions, handles.length > 1);
1247
+
1248
+ state = setHandle ( handles[0], positions[h], handles.length === 1 );
1249
+
1250
+ if ( handles.length > 1 ) {
1251
+ state = setHandle ( handles[1], positions[h?0:1], false ) || state;
1252
+ }
1253
+
1254
+ // Fire the 'slide' event if any handle moved.
1255
+ if ( state ) {
1256
+ fireEvents(['slide']);
1257
+ }
1258
+ }
1259
+
1260
+ // Unbind move events on document, call callbacks.
1261
+ function end ( event ) {
1262
+
1263
+ // The handle is no longer active, so remove the class.
1264
+ $('.' + Classes[15]).removeClass(Classes[15]);
1265
+
1266
+ // Remove cursor styles and text-selection events bound to the body.
1267
+ if ( event.cursor ) {
1268
+ $('body').css('cursor', '').off( namespace );
1269
+ }
1270
+
1271
+ // Unbind the move and end events, which are added on 'start'.
1272
+ doc.off( namespace );
1273
+
1274
+ // Remove dragging class.
1275
+ $Target.removeClass(Classes[12]);
1276
+
1277
+ // Fire the change and set events.
1278
+ fireEvents(['set', 'change']);
1279
+ }
1280
+
1281
+ // Bind move events on document.
1282
+ function start ( event, data ) {
1283
+
1284
+ // Mark the handle as 'active' so it can be styled.
1285
+ if( data.handles.length === 1 ) {
1286
+ data.handles[0].children().addClass(Classes[15]);
1287
+ }
1288
+
1289
+ // A drag should never propagate up to the 'tap' event.
1290
+ event.stopPropagation();
1291
+
1292
+ // Attach the move event.
1293
+ attach ( actions.move, doc, move, {
1294
+ start: event.calcPoint,
1295
+ handles: data.handles,
1296
+ positions: [
1297
+ $Locations[0],
1298
+ $Locations[$Handles.length - 1]
1299
+ ]
1300
+ });
1301
+
1302
+ // Unbind all movement when the drag ends.
1303
+ attach ( actions.end, doc, end, null );
1304
+
1305
+ // Text selection isn't an issue on touch devices,
1306
+ // so adding cursor styles can be skipped.
1307
+ if ( event.cursor ) {
1308
+
1309
+ // Prevent the 'I' cursor and extend the range-drag cursor.
1310
+ $('body').css('cursor', $(event.target).css('cursor'));
1311
+
1312
+ // Mark the target with a dragging state.
1313
+ if ( $Handles.length > 1 ) {
1314
+ $Target.addClass(Classes[12]);
1315
+ }
1316
+
1317
+ // Prevent text selection when dragging the handles.
1318
+ $('body').on('selectstart' + namespace, false);
1319
+ }
1320
+ }
1321
+
1322
+ // Move closest handle to tapped location.
1323
+ function tap ( event ) {
1324
+
1325
+ var location = event.calcPoint, total = 0, to;
1326
+
1327
+ // The tap event shouldn't propagate up and cause 'edge' to run.
1328
+ event.stopPropagation();
1329
+
1330
+ // Add up the handle offsets.
1331
+ $.each( $Handles, function(){
1332
+ total += this.offset()[ options.style ];
1333
+ });
1334
+
1335
+ // Find the handle closest to the tapped position.
1336
+ total = ( location < total/2 || $Handles.length === 1 ) ? 0 : 1;
1337
+
1338
+ location -= $Base.offset()[ options.style ];
1339
+
1340
+ // Calculate the new position.
1341
+ to = ( location * 100 ) / baseSize();
1342
+
1343
+ // Find the closest handle and calculate the tapped point.
1344
+ // The set handle to the new position.
1345
+ jump( $Handles[total], to, options.events.snap );
1346
+
1347
+ if ( options.events.snap ) {
1348
+ start(event, { handles: [$Handles[total]] });
1349
+ }
1350
+ }
1351
+
1352
+ // Move handle to edges when target gets tapped.
1353
+ function edge ( event ) {
1354
+
1355
+ var i = event.calcPoint < $Base.offset()[ options.style ],
1356
+ to = i ? 0 : 100;
1357
+
1358
+ i = i ? 0 : $Handles.length - 1;
1359
+
1360
+ jump( $Handles[i], to, false );
1361
+ }
1362
+
1363
+ // Attach events to several slider parts.
1364
+ function events ( behaviour ) {
1365
+
1366
+ var i, drag;
1367
+
1368
+ // Attach the standard drag event to the handles.
1369
+ if ( !behaviour.fixed ) {
1370
+
1371
+ for ( i = 0; i < $Handles.length; i++ ) {
1372
+
1373
+ // These events are only bound to the visual handle
1374
+ // element, not the 'real' origin element.
1375
+ attach ( actions.start, $Handles[i].children(), start, {
1376
+ handles: [ $Handles[i] ]
1377
+ });
1378
+ }
1379
+ }
1380
+
1381
+ // Attach the tap event to the slider base.
1382
+ if ( behaviour.tap ) {
1383
+ attach ( actions.start, $Base, tap, {
1384
+ handles: $Handles
1385
+ });
1386
+ }
1387
+
1388
+ // Extend tapping behaviour to target
1389
+ if ( behaviour.extend ) {
1390
+
1391
+ $Target.addClass( Classes[16] );
1392
+
1393
+ if ( behaviour.tap ) {
1394
+ attach ( actions.start, $Target, edge, {
1395
+ handles: $Handles
1396
+ });
1397
+ }
1398
+ }
1399
+
1400
+ // Make the range dragable.
1401
+ if ( behaviour.drag ){
1402
+
1403
+ drag = $Base.find( '.' + Classes[7] ).addClass( Classes[10] );
1404
+
1405
+ // When the range is fixed, the entire range can
1406
+ // be dragged by the handles. The handle in the first
1407
+ // origin will propagate the start event upward,
1408
+ // but it needs to be bound manually on the other.
1409
+ if ( behaviour.fixed ) {
1410
+ drag = drag.add($Base.children().not( drag ).children());
1411
+ }
1412
+
1413
+ attach ( actions.start, drag, start, {
1414
+ handles: $Handles
1415
+ });
1416
+ }
1417
+ }
1418
+
1419
+
1420
+ // Initialize slider
1421
+
1422
+ // Throw an error if the slider was already initialized.
1423
+ if ( !$Target.is(':empty') ) {
1424
+ throw new Error('Slider was already initialized.');
1425
+ }
1426
+
1427
+ // Create the base element, initialise HTML and set classes.
1428
+ // Add handles and links.
1429
+ $Base = addSlider( options, $Target );
1430
+ $Handles = addHandles( options, $Base );
1431
+ $Serialization = addLinks( options, $Handles );
1432
+
1433
+ // Set the connect classes.
1434
+ addConnection ( options.connect, $Target, $Handles );
1435
+
1436
+ // Attach user events.
1437
+ events( options.events );
1438
+
1439
+
1440
+ // Methods
1441
+
1442
+ // Set the slider value.
1443
+ target.vSet = function ( values, callback, link, update, animate ){
1444
+
1445
+ var i, to;
1446
+
1447
+ // The RTL settings is implemented by reversing the front-end,
1448
+ // internal mechanisms are the same.
1449
+ if ( options.dir && options.handles > 1 ) {
1450
+ values.reverse();
1451
+ }
1452
+
1453
+ // Animation is optional.
1454
+ if ( animate ) {
1455
+ addClassFor( $Target, Classes[14], 300 );
1456
+ }
1457
+
1458
+ // If there are multiple handles to be set run the setting
1459
+ // mechanism twice for the first handle, to make sure it
1460
+ // can be bounced of the second one properly.
1461
+ for ( i = 0; i < ( $Handles.length > 1 ? 3 : 1 ); i++ ) {
1462
+
1463
+ to = link || $Serialization[i%2][0];
1464
+ to = to.valueOf( values[i%2] );
1465
+
1466
+ if ( to === false ) {
1467
+ continue;
1468
+ }
1469
+
1470
+ // Calculate the new handle position
1471
+ to = toStepping( options, to );
1472
+
1473
+ // Invert the value if this is a right-to-left slider.
1474
+ if ( options.dir ) {
1475
+ to = 100 - to;
1476
+ }
1477
+
1478
+ // Force delimitation.
1479
+ if ( setHandle( $Handles[i%2], to, true ) === true ) {
1480
+ continue;
1481
+ }
1482
+
1483
+ // Reset the input if it doesn't match the slider.
1484
+ $($Serialization[i%2]).each(function(){
1485
+ this.write(
1486
+ options,
1487
+ $Locations[i%2],
1488
+ $Handles[i%2].children(),
1489
+ $Target,
1490
+ update
1491
+ );
1492
+ });
1493
+ }
1494
+
1495
+ // Optionally fire the 'set' event.
1496
+ if( callback === true ) {
1497
+ fireEvents(['set']);
1498
+ }
1499
+
1500
+ return this;
1501
+ };
1502
+
1503
+ // Get the slider value.
1504
+ target.vGet = function ( ){
1505
+
1506
+ var i, retour = [];
1507
+
1508
+ // Get the value from all handles.
1509
+ for ( i = 0; i < options.handles; i++ ){
1510
+ retour[i] = $Serialization[i][0].saved;
1511
+ }
1512
+
1513
+ // If only one handle is used, return a single value.
1514
+ if ( retour.length === 1 ){
1515
+ return retour[0];
1516
+ }
1517
+
1518
+ if ( options.dir && options.handles > 1 ) {
1519
+ return retour.reverse();
1520
+ }
1521
+
1522
+ return retour;
1523
+ };
1524
+
1525
+ // Destroy the slider and unbind all events.
1526
+ target.destroy = function ( ){
1527
+
1528
+ // Loop all linked serialization objects and unbind all
1529
+ // events in the noUiSlider namespace.
1530
+ $.each($Serialization, function(){
1531
+ $.each(this, function(){
1532
+ // Won't remove 'change' when bound implicitly.
1533
+ if ( this.target ) {
1534
+ this.target.off( namespace );
1535
+ }
1536
+ });
1537
+ });
1538
+
1539
+ // Unbind events on the slider, remove all classes and child elements.
1540
+ $(this).off(namespace)
1541
+ .removeClass(Classes.join(' '))
1542
+ .empty();
1543
+
1544
+ // Return the original options from the closure.
1545
+ return originalOptions;
1546
+ };
1547
+
1548
+
1549
+ // Value setting
1550
+
1551
+ // Use the public value method to set the start values.
1552
+ $Target.val( options.start );
1553
+ }
1554
+
1555
+
1556
+ // Access points
1557
+
1558
+ // Run the standard initializer
1559
+ function initialize ( originalOptions ) {
1560
+
1561
+ // Throw error if group is empty.
1562
+ if ( !this.length ){
1563
+ throwError("Can't initialize slider on empty selection.");
1564
+ }
1565
+
1566
+ // Test the options once, not for every slider.
1567
+ var options = test( originalOptions, this );
1568
+
1569
+ // Loop all items, and provide a new closed-scope environment.
1570
+ return this.each(function(){
1571
+ closure(this, options, originalOptions);
1572
+ });
1573
+ }
1574
+
1575
+ // Destroy the slider, then re-enter initialization.
1576
+ function rebuild ( options ) {
1577
+
1578
+ return this.each(function(){
1579
+
1580
+ // Get the current values from the slider,
1581
+ // including the initialization options.
1582
+ var values = $(this).val(),
1583
+ originalOptions = this.destroy(),
1584
+
1585
+ // Extend the previous options with the newly provided ones.
1586
+ newOptions = $.extend( {}, originalOptions, options );
1587
+
1588
+ // Run the standard initializer.
1589
+ $(this).noUiSlider( newOptions );
1590
+
1591
+ // If the start option hasn't changed,
1592
+ // reset the previous values.
1593
+ if ( originalOptions.start === newOptions.start ) {
1594
+ $(this).val(values);
1595
+ }
1596
+ });
1597
+ }
1598
+
1599
+
1600
+ // Expose serialization constructor.
1601
+ /** @expose */
1602
+ $.noUiSlider = { 'Link': Link };
1603
+
1604
+ // Extend jQuery/Zepto with the noUiSlider method.
1605
+ /** @expose */
1606
+ $.fn.noUiSlider = function ( options, re ) {
1607
+ return ( re ? rebuild : initialize ).call(this, options);
1608
+ };
1609
+
1610
+ $.fn.val = function ( ) {
1611
+
1612
+ // Convert the function arguments to an array.
1613
+ var args = Array.prototype.slice.call( arguments, 0 ),
1614
+ set, link, update, animate;
1615
+
1616
+ // Test if there are arguments, and if not, call the 'get' method.
1617
+ if ( !args.length ) {
1618
+
1619
+ // Determine whether to use the native val method.
1620
+ if ( this.hasClass(Classes[0]) ) {
1621
+ return this[0].vGet();
1622
+ }
1623
+
1624
+ return $val.apply( this );
1625
+ }
1626
+
1627
+ // Extract modifiers for value method.
1628
+ if ( typeof args[1] === 'object' ) {
1629
+ set = args[1]['set'];
1630
+ link = args[1]['link'];
1631
+ update = args[1]['update'];
1632
+ animate = args[1]['animate'];
1633
+
1634
+ // Support the 'true' option.
1635
+ } else if ( args[1] === true ) {
1636
+ set = true;
1637
+ }
1638
+
1639
+ // Loop all individual items, and handle setting appropriately.
1640
+ return this.each(function(){
1641
+
1642
+ if ( $(this).hasClass(Classes[0]) ) {
1643
+ this.vSet( asArray(args[0]), set, link, update, animate );
1644
+ } else {
1645
+ $val.apply( $(this), args );
1646
+ }
1647
+ });
1648
+ };
1649
+
1650
+ }( window['jQuery'] || window['Zepto'] ));