Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 espaco 1
/**@preserve
2
$.fn.noUiSlider - WTFPL - refreshless.com/nouislider/ */
3
 
4
/*jslint browser: true */
5
/*jslint sub: true */
6
/*jslint white: true */
7
/*jslint continue: true */
8
/*jslint plusplus: true */
9
 
10
(function( $ ){
11
 
12
        'use strict';
13
 
14
        var
15
        // Cache the document selector;
16
        /** @const */
17
        doc = $(document),
18
        // Namespace for binding and unbinding slider events;
19
        /** @const */
20
        namespace = '.nui',
21
        // Determine the events to bind. IE11 implements pointerEvents without
22
        // a prefix, which breaks compatibility with the IE10 implementation.
23
        /** @const */
24
        actions = window.navigator['pointerEnabled'] ? {
25
                start: 'pointerdown',
26
                move: 'pointermove',
27
                end: 'pointerup'
28
        } : window.navigator['msPointerEnabled'] ? {
29
                start: 'MSPointerDown',
30
                move: 'MSPointerMove',
31
                end: 'MSPointerUp'
32
        } : {
33
                start: 'mousedown touchstart',
34
                move: 'mousemove touchmove',
35
                end: 'mouseup touchend'
36
        },
37
        // Re-usable list of classes;
38
        /** @const */
39
        Classes = [
40
/*  0 */  'noUi-target'
41
/*  1 */ ,'noUi-base'
42
/*  2 */ ,'noUi-origin'
43
/*  3 */ ,'noUi-handle'
44
/*  4 */ ,'noUi-horizontal'
45
/*  5 */ ,'noUi-vertical'
46
/*  6 */ ,'noUi-background'
47
/*  7 */ ,'noUi-connect'
48
/*  8 */ ,'noUi-ltr'
49
/*  9 */ ,'noUi-rtl'
50
/* 10 */ ,'noUi-dragable'
51
/* 11 */ ,''
52
/* 12 */ ,'noUi-state-drag'
53
/* 13 */ ,''
54
/* 14 */ ,'noUi-state-tap'
55
/* 15 */ ,'noUi-active'
56
/* 16 */ ,'noUi-extended'
57
/* 17 */ ,'noUi-stacking'
58
        ];
59
 
60
 
61
// General helpers
62
 
63
        // Limits a value to 0 - 100
64
        function limit ( a ) {
65
                return Math.max(Math.min(a, 100), 0);
66
        }
67
 
68
        // Round a value to the closest 'to'.
69
        function closest ( value, to ) {
70
                return Math.round(value / to) * to;
71
        }
72
 
73
        // Determine the size of a sub-range in relation to a full range.
74
        function subRangeRatio ( pa, pb ) {
75
                return (100 / (pb - pa));
76
        }
77
 
78
 
79
// Type validation
80
 
81
        // Checks whether a value is numerical.
82
        function isNumeric ( a ) {
83
                return typeof a === 'number' && !isNaN( a ) && isFinite( a );
84
        }
85
 
86
        // Wraps a variable as an array, if it isn't one yet.
87
        function asArray ( a ) {
88
                return $.isArray(a) ? a : [a];
89
        }
90
 
91
 
92
// Class handling
93
 
94
        // Sets a class and removes it after [duration] ms.
95
        function addClassFor ( element, className, duration ) {
96
                element.addClass(className);
97
                setTimeout(function(){
98
                        element.removeClass(className);
99
                }, duration);
100
        }
101
 
102
 
103
// Value calculation
104
 
105
        // (percentage) How many percent is this value of this range?
106
        function fromPercentage ( range, value ) {
107
                return (value * 100) / ( range[1] - range[0] );
108
        }
109
 
110
        // (percentage) Where is this value on this range?
111
        function toPercentage ( range, value ) {
112
                return fromPercentage( range, range[0] < 0 ?
113
                        value + Math.abs(range[0]) :
114
                                value - range[0] );
115
        }
116
 
117
        // (value) How much is this percentage on this range?
118
        function isPercentage ( range, value ) {
119
                return ((value * ( range[1] - range[0] )) / 100) + range[0];
120
        }
121
 
122
        // (percentage)
123
        function toStepping ( options, value ) {
124
 
125
                if ( value >= options.xVal.slice(-1)[0] ){
126
                        return 100;
127
                }
128
 
129
                var j = 1, va, vb, pa, pb;
130
                while ( value >= options.xVal[j] ){
131
                        j++;
132
                }
133
 
134
                va = options.xVal[j-1];
135
                vb = options.xVal[j];
136
                pa = options.xPct[j-1];
137
                pb = options.xPct[j];
138
 
139
                return pa + (toPercentage([va, vb], value) / subRangeRatio (pa, pb));
140
        }
141
 
142
        // (value)
143
        function fromStepping ( options, value ) {
144
 
145
                // There is no range group that fits 100
146
                if ( value >= 100 ){
147
                        return options.xVal.slice(-1)[0];
148
                }
149
 
150
                var j = 1, va, vb, pa, pb;
151
                while ( value >= options.xPct[j] ){
152
                        j++;
153
                }
154
 
155
                va = options.xVal[j-1];
156
                vb = options.xVal[j];
157
                pa = options.xPct[j-1];
158
                pb = options.xPct[j];
159
 
160
                return isPercentage([va, vb], (value - pa) * subRangeRatio (pa, pb));
161
        }
162
 
163
        // (percentage) Get the step that applies at a certain value.
164
        function getStep ( options, value ){
165
 
166
                var j = 1, a, b;
167
                while ( value >= options.xPct[j] ){
168
                        j++;
169
                }
170
 
171
                if ( options.snap ) {
172
 
173
                        a = options.xPct[j-1];
174
                        b = options.xPct[j];
175
 
176
                        if ((value - a) > ((b-a)/2)){
177
                                return b;
178
                        }
179
 
180
                        return a;
181
                }
182
 
183
                if ( !options.xSteps[j-1] ){
184
                        return value;
185
                }
186
 
187
                return options.xPct[j-1] + closest(
188
                        value - options.xPct[j-1],
189
                        options.xSteps[j-1]
190
                );
191
        }
192
 
193
 
194
// Event handling
195
 
196
        // Provide a clean event with standardized offset values.
197
        function fixEvent ( e ) {
198
 
199
                // Prevent scrolling and panning on touch events, while
200
                // attempting to slide. The tap event also depends on this.
201
                e.preventDefault();
202
 
203
                // Filter the event to register the type, which can be
204
                // touch, mouse or pointer. Offset changes need to be
205
                // made on an event specific basis.
206
                var  touch = e.type.indexOf('touch') === 0
207
                        ,mouse = e.type.indexOf('mouse') === 0
208
                        ,pointer = e.type.indexOf('pointer') === 0
209
                        ,x,y, event = e;
210
 
211
                // IE10 implemented pointer events with a prefix;
212
                if ( e.type.indexOf('MSPointer') === 0 ) {
213
                        pointer = true;
214
                }
215
 
216
                // Get the originalEvent, if the event has been wrapped
217
                // by jQuery. Zepto doesn't wrap the event.
218
                if ( e.originalEvent ) {
219
                        e = e.originalEvent;
220
                }
221
 
222
                if ( touch ) {
223
                        // noUiSlider supports one movement at a time,
224
                        // so we can select the first 'changedTouch'.
225
                        x = e.changedTouches[0].pageX;
226
                        y = e.changedTouches[0].pageY;
227
                }
228
 
229
                if ( mouse || pointer ) {
230
 
231
                        // Polyfill the pageXOffset and pageYOffset
232
                        // variables for IE7 and IE8;
233
                        if( !pointer && window.pageXOffset === undefined ){
234
                                window.pageXOffset = document.documentElement.scrollLeft;
235
                                window.pageYOffset = document.documentElement.scrollTop;
236
                        }
237
 
238
                        x = e.clientX + window.pageXOffset;
239
                        y = e.clientY + window.pageYOffset;
240
                }
241
 
242
                event.points = [x, y];
243
                event.cursor = mouse;
244
 
245
                return event;
246
        }
247
 
248
 
249
// Input validation
250
 
251
        function testStep ( parsed, entry ) {
252
 
253
                if ( !isNumeric( entry ) ) {
254
                        throw new Error("noUiSlider: 'step' is not numeric.");
255
                }
256
 
257
                // The step option can still be used to set stepping
258
                // for linear sliders. Overwritten if set in 'range'.
259
                parsed.xSteps[0] = entry;
260
        }
261
 
262
        function testRange ( parsed, entry ) {
263
 
264
                // Filter incorrect input.
265
                if ( typeof entry !== 'object' || $.isArray(entry) ) {
266
                        throw new Error("noUiSlider: 'range' is not an object.");
267
                }
268
 
269
                // Loop all entries.
270
                $.each( entry, function ( index, value ) {
271
 
272
                        var percentage;
273
 
274
                        // Wrap numerical input in an array.
275
                        if ( typeof value === "number" ) {
276
                                value = [value];
277
                        }
278
 
279
                        // Reject any invalid input.
280
                        if ( !$.isArray( value ) ){
281
                                throw new Error("noUiSlider: 'range' contains invalid value.");
282
                        }
283
 
284
                        // Covert min/max syntax to 0 and 100.
285
                        if ( index === 'min' ) {
286
                                percentage = 0;
287
                        } else if ( index === 'max' ) {
288
                                percentage = 100;
289
                        } else {
290
                                percentage = parseFloat( index );
291
                        }
292
 
293
                        // Check for correct input.
294
                        if ( !isNumeric( percentage ) || !isNumeric( value[0] ) ) {
295
                                throw new Error("noUiSlider: 'range' value isn't numeric.");
296
                        }
297
 
298
                        // Store values.
299
                        parsed.xPct.push( percentage );
300
                        parsed.xVal.push( value[0] );
301
 
302
                        // NaN will evaluate to false too, but to keep
303
                        // logging clear, set step explicitly. Make sure
304
                        // not to override the 'step' setting with false.
305
                        if ( !percentage ) {
306
                                if ( !isNaN( value[1] ) ) {
307
                                        parsed.xSteps[0] = value[1];
308
                                }
309
                        } else {
310
                                parsed.xSteps.push( isNaN(value[1]) ? false : value[1] );
311
                        }
312
                });
313
 
314
                $.each(parsed.xSteps, function(i,n){
315
 
316
                        // Ignore 'false' stepping.
317
                        if ( !n ) {
318
                                return true;
319
                        }
320
 
321
                        // Check if step fits. Not required, but this might serve some goal.
322
                        // !((parsed.xVal[i+1] - parsed.xVal[i]) % n);
323
 
324
                        // Factor to range ratio
325
                        parsed.xSteps[i] = fromPercentage([
326
                                 parsed.xVal[i]
327
                                ,parsed.xVal[i+1]
328
                        ], n) / subRangeRatio (
329
                                parsed.xPct[i],
330
                                parsed.xPct[i+1] );
331
                });
332
        }
333
 
334
        function testStart ( parsed, entry ) {
335
 
336
                if ( typeof entry === "number" ) {
337
                        entry = [entry];
338
                }
339
 
340
                // Validate input. Values aren't tested, the internal Link will do
341
                // that and provide a valid location.
342
                if ( !$.isArray( entry ) || !entry.length || entry.length > 2 ) {
343
                        throw new Error("noUiSlider: 'start' option is incorrect.");
344
                }
345
 
346
                // Store the number of handles.
347
                parsed.handles = entry.length;
348
 
349
                // When the slider is initialized, the .val method will
350
                // be called with the start options.
351
                parsed.start = entry;
352
        }
353
 
354
        function testSnap ( parsed, entry ) {
355
 
356
                // Enforce 100% stepping within subranges.
357
                parsed.snap = entry;
358
 
359
                if ( typeof entry !== 'boolean' ){
360
                        throw new Error("noUiSlider: 'snap' option must be a boolean.");
361
                }
362
        }
363
 
364
        function testConnect ( parsed, entry ) {
365
 
366
                if ( entry === 'lower' && parsed.handles === 1 ) {
367
                        parsed.connect = 1;
368
                } else if ( entry === 'upper' && parsed.handles === 1 ) {
369
                        parsed.connect = 2;
370
                } else if ( entry === true && parsed.handles === 2 ) {
371
                        parsed.connect = 3;
372
                } else if ( entry === false ) {
373
                        parsed.connect = 0;
374
                } else {
375
                        throw new Error("noUiSlider: 'connect' option doesn't match handle count.");
376
                }
377
        }
378
 
379
        function testOrientation ( parsed, entry ) {
380
 
381
                // Set orientation to an a numerical value for easy
382
                // array selection.
383
                switch ( entry ){
384
                  case 'horizontal':
385
                        parsed.ort = 0;
386
                        break;
387
                  case 'vertical':
388
                        parsed.ort = 1;
389
                        break;
390
                  default:
391
                        throw new Error("noUiSlider: 'orientation' option is invalid.");
392
                }
393
        }
394
 
395
        function testMargin ( parsed, entry ) {
396
 
397
                if ( parsed.xPct.length > 2 ) {
398
                        throw new Error("noUiSlider: 'margin' option is only supported on linear sliders.");
399
                }
400
 
401
                // Parse value to range and store. As xVal is checked
402
                // to be no bigger than 2, use it as range.
403
                parsed.margin = fromPercentage(parsed.xVal, entry);
404
 
405
                if ( !isNumeric(entry) ){
406
                        throw new Error("noUiSlider: 'margin' option must be numeric.");
407
                }
408
        }
409
 
410
        function testDirection ( parsed, entry ) {
411
 
412
                // Set direction as a numerical value for easy parsing.
413
                // Invert connection for RTL sliders, so that the proper
414
                // handles get the connect/background classes.
415
                switch ( entry ) {
416
                  case 'ltr':
417
                        parsed.dir = 0;
418
                        break;
419
                  case 'rtl':
420
                        parsed.dir = 1;
421
                        parsed.connect = [0,2,1,3][parsed.connect];
422
                        break;
423
                  default:
424
                        throw new Error("noUiSlider: 'direction' option was not recognized.");
425
                }
426
        }
427
 
428
        function testBehaviour ( parsed, entry ) {
429
 
430
                // Make sure the input is a string.
431
                if ( typeof entry !== 'string' ) {
432
                        throw new Error("noUiSlider: 'behaviour' must be a string containing options.");
433
                }
434
 
435
                // Check if the string contains any keywords.
436
                // None are required.
437
                var tap = entry.indexOf('tap') >= 0,
438
                        extend = entry.indexOf('extend') >= 0,
439
                        drag = entry.indexOf('drag') >= 0,
440
                        fixed = entry.indexOf('fixed') >= 0,
441
                        snap = entry.indexOf('snap') >= 0;
442
 
443
                parsed.events = {
444
                        tap: tap || snap,
445
                        extend: extend,
446
                        drag: drag,
447
                        fixed: fixed,
448
                        snap: snap
449
                };
450
        }
451
 
452
        function testSerialization ( parsed, entry, sliders ) {
453
 
454
                parsed.ser = [ entry['lower'], entry['upper'] ];
455
                parsed.formatting = entry['format'];
456
 
457
                $.each( parsed.ser, function( index, linkInstances ){
458
 
459
                        // Check if the provided option is an array.
460
                        if ( !$.isArray(linkInstances) ) {
461
                                throw new Error("noUiSlider: 'serialization."+(!index ? 'lower' : 'upper')+"' must be an array.");
462
                        }
463
 
464
                        $.each(linkInstances, function(){
465
 
466
                                // Check if entry is a Link.
467
                                if ( !(this instanceof $.Link) ) {
468
                                        throw new Error("noUiSlider: 'serialization."+(!index ? 'lower' : 'upper')+"' can only contain Link instances.");
469
                                }
470
 
471
                                // Assign properties.
472
                                this.setIndex ( index );
473
                                this.setObject( sliders );
474
                                this.setFormatting( entry['format'] );
475
                        });
476
                });
477
 
478
                // If the slider has two handles and is RTL,
479
                // reverse the serialization input. For one handle,
480
                // lower is still lower.
481
                if ( parsed.dir && parsed.handles > 1 ) {
482
                        parsed.ser.reverse();
483
                }
484
        }
485
 
486
        // Test all developer settings and parse to assumption-safe values.
487
        function test ( options, sliders ){
488
 
489
        /*      Every input option is tested and parsed. This'll prevent
490
                endless validation in internal methods. These tests are
491
                structured with an item for every option available. An
492
                option can be marked as required by setting the 'r' flag.
493
                The testing function is provided with three arguments:
494
                        - The provided value for the option;
495
                        - A reference to the options object;
496
                        - The name for the option;
497
 
498
                The testing function returns false when an error is detected,
499
                or true when everything is OK. It can also modify the option
500
                object, to make sure all values can be correctly looped elsewhere. */
501
 
502
                var parsed = {
503
                         xPct: []
504
                        ,xVal: []
505
                        ,xSteps: [ false ]
506
                        ,margin: 0
507
                }, tests;
508
 
509
                tests = {
510
                        'step': { r: false, t: testStep },
511
                        'range': { r: true, t: testRange },
512
                        'start': { r: true, t: testStart },
513
                        'snap': { r: false, t: testSnap },
514
                        'connect': { r: true, t: testConnect },
515
                        'orientation': { r: false, t: testOrientation },
516
                        'margin': { r: false, t: testMargin },
517
                        'direction': { r: true, t: testDirection },
518
                        'behaviour': { r: true, t: testBehaviour },
519
                        'serialization': { r: true, t: testSerialization }
520
                };
521
 
522
                // Set defaults where applicable.
523
                options = $.extend({
524
                        'connect': false,
525
                        'direction': 'ltr',
526
                        'behaviour': 'tap',
527
                        'orientation': 'horizontal'
528
                }, options);
529
 
530
                // Make sure the test for serialization runs.
531
                options['serialization'] = $.extend({
532
                         'lower': []
533
                        ,'upper': []
534
                        ,'format': {}
535
                }, options['serialization']);
536
 
537
                // Run all options through a testing mechanism to ensure correct
538
                // input. It should be noted that options might get modified to
539
                // be handled properly. E.g. wrapping integers in arrays.
540
                $.each( tests, function( name, test ){
541
 
542
                        if ( options[name] === undefined ) {
543
 
544
                                if ( test.r ) {
545
                                        throw new Error("noUiSlider: '" + name + "' is required.");
546
                                }
547
 
548
                                return true;
549
                        }
550
 
551
                        test.t( parsed, options[name], sliders );
552
                });
553
 
554
                // Pre-define the styles.
555
                parsed.style = parsed.ort ? 'top' : 'left';
556
 
557
                return parsed;
558
        }
559
 
560
 
561
// DOM additions
562
 
563
        // Append a handle to the base.
564
        function addHandle ( options, index ) {
565
 
566
                var handle = $('<div><div/></div>').addClass( Classes[2] ),
567
                        additions = [ '-lower', '-upper' ];
568
 
569
                if ( options.dir ) {
570
                        additions.reverse();
571
                }
572
 
573
                handle.children().addClass(
574
                        Classes[3] + " " + Classes[3]+additions[index]
575
                );
576
 
577
                return handle;
578
        }
579
 
580
        // Create a copy of an element-creating Link.
581
        function addElement ( handle, link ) {
582
 
583
                // If the Link requires creation of a new element,
584
                // create this element and return a new Link instance.
585
                if ( link.el ) {
586
 
587
                        link = new $.Link({
588
                                'target': $(link.el).clone().appendTo( handle ),
589
                                'method': link.method,
590
                                'format': link.formatting
591
                        }, true);
592
                }
593
 
594
                // Otherwise, return the reference.
595
                return link;
596
        }
597
 
598
        // Loop all links for a handle.
599
        function addElements ( elements, handle, formatting ) {
600
 
601
                var index, list = [], standard = new $.Link({}, true);
602
 
603
                // Use the Link interface to provide unified
604
                // formatting for the .val() method.
605
                standard.setFormatting(formatting);
606
 
607
                // The list now contains at least one element.
608
                list.push( standard );
609
 
610
                // Loop all links in either 'lower' or 'upper'.
611
                for ( index = 0; index < elements.length; index++ ) {
612
                        list.push(addElement(handle, elements[index]));
613
                }
614
 
615
                return list;
616
        }
617
 
618
        // Go over all Links and assign them to a handle.
619
        function addLinks ( options, handles ) {
620
 
621
                var index, links = [];
622
 
623
                // Copy the links into a new array, instead of modifying
624
                // the 'options.ser' list. This allows replacement of the invalid
625
                // '.el' Links, while the others are still passed by reference.
626
                for ( index = 0; index < options.handles; index++ ) {
627
 
628
                        // Append a new array.
629
                        links[index] = addElements(
630
                                options.ser[index],
631
                                handles[index].children(),
632
                                options.formatting
633
                        );
634
                }
635
 
636
                return links;
637
        }
638
 
639
        // Add the proper connection classes.
640
        function addConnection ( connect, target, handles ) {
641
 
642
                // Apply the required connection classes to the elements
643
                // that need them. Some classes are made up for several
644
                // segments listed in the class list, to allow easy
645
                // renaming and provide a minor compression benefit.
646
                switch ( connect ) {
647
                        case 1: target.addClass( Classes[7] );
648
                                        handles[0].addClass( Classes[6] );
649
                                        break;
650
                        case 3: handles[1].addClass( Classes[6] );
651
                                        /* falls through */
652
                        case 2: handles[0].addClass( Classes[7] );
653
                                        /* falls through */
654
                        case 0: target.addClass(Classes[6]);
655
                                        break;
656
                }
657
        }
658
 
659
        // Add handles and loop Link elements.
660
        function addHandles ( options, base ) {
661
 
662
                var index, handles = [];
663
 
664
                // Append handles.
665
                for ( index = 0; index < options.handles; index++ ) {
666
 
667
                        // Keep a list of all added handles.
668
                        handles.push( addHandle( options, index ).appendTo(base) );
669
                }
670
 
671
                return handles;
672
        }
673
 
674
        // Initialize a single slider.
675
        function addSlider ( options, target ) {
676
 
677
                // Apply classes and data to the target.
678
                target.addClass([
679
                        Classes[0],
680
                        Classes[8 + options.dir],
681
                        Classes[4 + options.ort]
682
                ].join(' '));
683
 
684
                return $('<div/>').appendTo(target).addClass( Classes[1] );
685
        }
686
 
687
 
688
// Slider scope
689
 
690
function closure ( target, options, originalOptions ){
691
 
692
// Internal variables
693
 
694
        // All variables local to 'closure' are marked $.
695
        var $Target = $(target),
696
                $Locations = [-1, -1],
697
                $Base,
698
                $Serialization,
699
                $Handles;
700
 
701
        // Shorthand for base dimensions.
702
        function baseSize ( ) {
703
                return $Base[['width', 'height'][options.ort]]();
704
        }
705
 
706
 
707
// External event handling
708
 
709
        function fireEvents ( events ) {
710
 
711
                // Use the external api to get the values.
712
                // Wrap the values in an array, as .trigger takes
713
                // only one additional argument.
714
                var index, values = [ $Target.val() ];
715
 
716
                for ( index = 0; index < events.length; index++ ){
717
                        $Target.trigger(events[index], values);
718
                }
719
        }
720
 
721
 
722
// Handle placement
723
 
724
        // Test suggested values and apply margin, step.
725
        function setHandle ( handle, to, delimit ) {
726
 
727
                var n = handle[0] !== $Handles[0][0] ? 1 : 0,
728
                        lower = $Locations[0] + options.margin,
729
                        upper = $Locations[1] - options.margin;
730
 
731
                // Don't delimit range dragging.
732
                if ( delimit && $Handles.length > 1 ) {
733
                        to = n ? Math.max( to, lower ) : Math.min( to, upper );
734
                }
735
 
736
                // Handle the step option.
737
                if ( to < 100 ){
738
                        to = getStep(options, to);
739
                }
740
 
741
                // Limit to 0/100 for .val input, trim anything beyond 7 digits, as
742
                // JavaScript has some issues in its floating point implementation.
743
                to = limit(parseFloat(to.toFixed(7)));
744
 
745
                // Return falsy if handle can't move. False for 0 or 100 limit,
746
                // '0' for limiting by another handle.
747
                if ( to === $Locations[n] ) {
748
                        if ( $Handles.length === 1 ) {
749
                                return false;
750
                        }
751
                        return ( to === lower || to === upper ) ? 0 : false;
752
                }
753
 
754
                // Set the handle to the new position.
755
                handle.css( options.style, to + '%' );
756
 
757
                // Force proper handle stacking
758
                if ( handle.is(':first-child') ) {
759
                        handle.toggleClass(Classes[17], to > 50 );
760
                }
761
 
762
                // Update locations.
763
                $Locations[n] = to;
764
 
765
                // Invert the value if this is a right-to-left slider.
766
                if ( options.dir ) {
767
                        to = 100 - to;
768
                }
769
 
770
                // Write values to serialization Links.
771
                // Convert the value to the correct relative representation.
772
                // Convert the value to the slider stepping/range.
773
                $($Serialization[n]).each(function(){
774
                        this.write( fromStepping( options, to ), handle.children(), $Target );
775
                });
776
 
777
                return true;
778
        }
779
 
780
        // Delimit proposed values for handle positions.
781
        function getPositions ( a, b, delimit ) {
782
 
783
                // Add movement to current position.
784
                var c = a + b[0], d = a + b[1];
785
 
786
                // Only alter the other position on drag,
787
                // not on standard sliding.
788
                if ( delimit ) {
789
                        if ( c < 0 ) {
790
                                d += Math.abs(c);
791
                        }
792
                        if ( d > 100 ) {
793
                                c -= ( d - 100 );
794
                        }
795
 
796
                        // Limit values to 0 and 100.
797
                        return [limit(c), limit(d)];
798
                }
799
 
800
                return [c,d];
801
        }
802
 
803
        // Handles movement by tapping.
804
        function jump ( handle, to, instant ) {
805
 
806
                if ( !instant ) {
807
                        // Flag the slider as it is now in a transitional state.
808
                        // Transition takes 300 ms, so re-enable the slider afterwards.
809
                        addClassFor( $Target, Classes[14], 300 );
810
                }
811
 
812
                // Move the handle to the new position.
813
                setHandle( handle, to, false );
814
 
815
                fireEvents(['slide', 'set', 'change']);
816
        }
817
 
818
 
819
// Events
820
 
821
        // Handler for attaching events trough a proxy.
822
        function attach ( events, element, callback, data ) {
823
 
824
                // Add the noUiSlider namespace to all events.
825
                events = events.replace( /\s/g, namespace + ' ' ) + namespace;
826
 
827
                // Bind a closure on the target.
828
                return element.on( events, function( e ){
829
 
830
                        // jQuery and Zepto handle unset attributes differently.
831
                        var disabled = $Target.attr('disabled');
832
                                disabled = !( disabled === undefined || disabled === null );
833
 
834
                        // Test if there is anything that should prevent an event
835
                        // from being handled, such as a disabled state or an active
836
                        // 'tap' transition.
837
                        if( $Target.hasClass( Classes[14] ) || disabled ) {
838
                                return false;
839
                        }
840
 
841
                        e = fixEvent(e);
842
                        e.calcPoint = e.points[ options.ort ];
843
 
844
                        // Call the event handler with the event [ and additional data ].
845
                        callback ( e, data );
846
                });
847
        }
848
 
849
        // Handle movement on document for handle and range drag.
850
        function move ( event, data ) {
851
 
852
                var handles = data.handles || $Handles, positions, state = false,
853
                        proposal = ((event.calcPoint - data.start) * 100) / baseSize(),
854
                        h = handles[0][0] !== $Handles[0][0] ? 1 : 0;
855
 
856
                // Calculate relative positions for the handles.
857
                positions = getPositions( proposal, data.positions, handles.length > 1);
858
 
859
                state = setHandle ( handles[0], positions[h], handles.length === 1 );
860
 
861
                if ( handles.length > 1 ) {
862
                        state = setHandle ( handles[1], positions[h?0:1], false ) || state;
863
                }
864
 
865
                // Fire the 'slide' event if any handle moved.
866
                if ( state ) {
867
                        fireEvents(['slide']);
868
                }
869
        }
870
 
871
        // Unbind move events on document, call callbacks.
872
        function end ( event ) {
873
 
874
                // The handle is no longer active, so remove the class.
875
                $('.' + Classes[15]).removeClass(Classes[15]);
876
 
877
                // Remove cursor styles and text-selection events bound to the body.
878
                if ( event.cursor ) {
879
                        $('body').css('cursor', '').off( namespace );
880
                }
881
 
882
                // Unbind the move and end events, which are added on 'start'.
883
                doc.off( namespace );
884
 
885
                // Remove dragging class.
886
                $Target.removeClass(Classes[12]);
887
 
888
                // Fire the change and set events.
889
                fireEvents(['set', 'change']);
890
        }
891
 
892
        // Bind move events on document.
893
        function start ( event, data ) {
894
 
895
                // Mark the handle as 'active' so it can be styled.
896
                if( data.handles.length === 1 ) {
897
                        data.handles[0].children().addClass(Classes[15]);
898
                }
899
 
900
                // A drag should never propagate up to the 'tap' event.
901
                event.stopPropagation();
902
 
903
                // Attach the move event.
904
                attach ( actions.move, doc, move, {
905
                        start: event.calcPoint,
906
                        handles: data.handles,
907
                        positions: [
908
                                $Locations[0],
909
                                $Locations[$Handles.length - 1]
910
                        ]
911
                });
912
 
913
                // Unbind all movement when the drag ends.
914
                attach ( actions.end, doc, end, null );
915
 
916
                // Text selection isn't an issue on touch devices,
917
                // so adding cursor styles can be skipped.
918
                if ( event.cursor ) {
919
 
920
                        // Prevent the 'I' cursor and extend the range-drag cursor.
921
                        $('body').css('cursor', $(event.target).css('cursor'));
922
 
923
                        // Mark the target with a dragging state.
924
                        if ( $Handles.length > 1 ) {
925
                                $Target.addClass(Classes[12]);
926
                        }
927
 
928
                        // Prevent text selection when dragging the handles.
929
                        $('body').on('selectstart' + namespace, false);
930
                }
931
        }
932
 
933
        // Move closest handle to tapped location.
934
        function tap ( event ) {
935
 
936
                var location = event.calcPoint, total = 0, to;
937
 
938
                // The tap event shouldn't propagate up and cause 'edge' to run.
939
                event.stopPropagation();
940
 
941
                // Add up the handle offsets.
942
                $.each( $Handles, function(){
943
                        total += this.offset()[ options.style ];
944
                });
945
 
946
                // Find the handle closest to the tapped position.
947
                total = ( location < total/2 || $Handles.length === 1 ) ? 0 : 1;
948
 
949
                location -= $Base.offset()[ options.style ];
950
 
951
                // Calculate the new position.
952
                to = ( location * 100 ) / baseSize();
953
 
954
                // Find the closest handle and calculate the tapped point.
955
                // The set handle to the new position.
956
                jump( $Handles[total], to, options.events.snap );
957
 
958
                if ( options.events.snap ) {
959
                        start(event, { handles: [$Handles[total]] });
960
                }
961
        }
962
 
963
        // Move handle to edges when target gets tapped.
964
        function edge ( event ) {
965
 
966
                var i = event.calcPoint < $Base.offset()[ options.style ],
967
                        to = i ? 0 : 100;
968
 
969
                i = i ? 0 : $Handles.length - 1;
970
 
971
                jump( $Handles[i], to, false );
972
        }
973
 
974
        // Attach events to several slider parts.
975
        function events ( behaviour ) {
976
 
977
                var i, drag;
978
 
979
                // Attach the standard drag event to the handles.
980
                if ( !behaviour.fixed ) {
981
 
982
                        for ( i = 0; i < $Handles.length; i++ ) {
983
 
984
                                // These events are only bound to the visual handle
985
                                // element, not the 'real' origin element.
986
                                attach ( actions.start, $Handles[i].children(), start, {
987
                                        handles: [ $Handles[i] ]
988
                                });
989
                        }
990
                }
991
 
992
                // Attach the tap event to the slider base.
993
                if ( behaviour.tap ) {
994
                        attach ( actions.start, $Base, tap, {
995
                                handles: $Handles
996
                        });
997
                }
998
 
999
                // Extend tapping behaviour to target
1000
                if ( behaviour.extend ) {
1001
 
1002
                        $Target.addClass( Classes[16] );
1003
 
1004
                        if ( behaviour.tap ) {
1005
                                attach ( actions.start, $Target, edge, {
1006
                                        handles: $Handles
1007
                                });
1008
                        }
1009
                }
1010
 
1011
                // Make the range dragable.
1012
                if ( behaviour.drag ){
1013
 
1014
                        drag = $Base.find( '.' + Classes[7] ).addClass( Classes[10] );
1015
 
1016
                        // When the range is fixed, the entire range can
1017
                        // be dragged by the handles. The handle in the first
1018
                        // origin will propagate the start event upward,
1019
                        // but it needs to be bound manually on the other.
1020
                        if ( behaviour.fixed ) {
1021
                                drag = drag.add($Base.children().not( drag ).children());
1022
                        }
1023
 
1024
                        attach ( actions.start, drag, start, {
1025
                                handles: $Handles
1026
                        });
1027
                }
1028
        }
1029
 
1030
 
1031
// Initialize slider
1032
 
1033
        // Throw an error if the slider was already initialized.
1034
        if ( $Target.hasClass(Classes[0]) ) {
1035
                throw new Error('Slider was already initialized.');
1036
        }
1037
 
1038
        // Create the base element, initialise HTML and set classes.
1039
        // Add handles and links.
1040
        $Base = addSlider( options, $Target );
1041
        $Handles = addHandles( options, $Base );
1042
        $Serialization = addLinks( options, $Handles );
1043
 
1044
        // Set the connect classes.
1045
        addConnection ( options.connect, $Target, $Handles );
1046
 
1047
        // Attach user events.
1048
        events( options.events );
1049
 
1050
 
1051
// Methods
1052
 
1053
        // Set the slider value.
1054
        /** @expose */
1055
        target.vSet = function ( ) {
1056
 
1057
                var args = Array.prototype.slice.call( arguments, 0 ),
1058
                        callback, link, update, animate,
1059
                        i, count, actual, to, values = asArray( args[0] );
1060
 
1061
                // Extract modifiers for value method.
1062
                if ( typeof args[1] === 'object' ) {
1063
                        callback = args[1]['set'];
1064
                        link = args[1]['link'];
1065
                        update = args[1]['update'];
1066
                        animate = args[1]['animate'];
1067
 
1068
                // Support the 'true' option.
1069
                } else if ( args[1] === true ) {
1070
                        callback = true;
1071
                }
1072
 
1073
                // The RTL settings is implemented by reversing the front-end,
1074
                // internal mechanisms are the same.
1075
                if ( options.dir && options.handles > 1 ) {
1076
                        values.reverse();
1077
                }
1078
 
1079
                // Animation is optional.
1080
                if ( animate ) {
1081
                        addClassFor( $Target, Classes[14], 300 );
1082
                }
1083
 
1084
                // Determine how often to set the handles.
1085
                count = $Handles.length > 1 ? 3 : 1;
1086
                if ( values.length === 1 ) {
1087
                        count = 1;
1088
                }
1089
 
1090
                // If there are multiple handles to be set run the setting
1091
                // mechanism twice for the first handle, to make sure it
1092
                // can be bounced of the second one properly.
1093
                for ( i = 0; i < count; i++ ) {
1094
 
1095
                        to = link || $Serialization[i%2][0];
1096
                        to = to.getValue( values[i%2] );
1097
 
1098
                        if ( to === false ) {
1099
                                continue;
1100
                        }
1101
 
1102
                        // Calculate the new handle position
1103
                        to = toStepping( options, to );
1104
 
1105
                        // Invert the value if this is a right-to-left slider.
1106
                        if ( options.dir ) {
1107
                                to = 100 - to;
1108
                        }
1109
 
1110
                        // Force delimitation.
1111
                        if ( setHandle( $Handles[i%2], to, true ) === true ) {
1112
                                continue;
1113
                        }
1114
 
1115
                        // Reset the input if it doesn't match the slider.
1116
                        $($Serialization[i%2]).each(function(index){
1117
 
1118
                                if (!index) {
1119
                                        actual = this.actual;
1120
                                        return true;
1121
                                }
1122
 
1123
                                this.write(
1124
                                        actual,
1125
                                        $Handles[i%2].children(),
1126
                                        $Target,
1127
                                        update
1128
                                );
1129
                        });
1130
                }
1131
 
1132
                // Optionally fire the 'set' event.
1133
                if( callback === true ) {
1134
                        fireEvents(['set']);
1135
                }
1136
 
1137
                return this;
1138
        };
1139
 
1140
        // Get the slider value.
1141
        /** @expose */
1142
        target.vGet = function ( ) {
1143
 
1144
                var i, retour = [];
1145
 
1146
                // Get the value from all handles.
1147
                for ( i = 0; i < options.handles; i++ ){
1148
                        retour[i] = $Serialization[i][0].saved;
1149
                }
1150
 
1151
                // If only one handle is used, return a single value.
1152
                if ( retour.length === 1 ){
1153
                        return retour[0];
1154
                }
1155
 
1156
                if ( options.dir ) {
1157
                        return retour.reverse();
1158
                }
1159
 
1160
                return retour;
1161
        };
1162
 
1163
        // Destroy the slider and unbind all events.
1164
        /** @expose */
1165
        target.destroy = function ( ) {
1166
 
1167
                // Loop all linked serialization objects and unbind all
1168
                // events in the noUiSlider namespace.
1169
                $.each($Serialization, function(){
1170
                        $.each(this, function(){
1171
                                // Won't remove 'change' when bound implicitly.
1172
                                if ( this.target ) {
1173
                                        this.target.off( namespace );
1174
                                }
1175
                        });
1176
                });
1177
 
1178
                // Unbind events on the slider, remove all classes and child elements.
1179
                $(this).off(namespace)
1180
                        .removeClass(Classes.join(' '))
1181
                        .empty();
1182
 
1183
                // Return the original options from the closure.
1184
                return originalOptions;
1185
        };
1186
 
1187
 
1188
// Value setting
1189
 
1190
        // Use the public value method to set the start values.
1191
        $Target.val( options.start );
1192
}
1193
 
1194
 
1195
// Access points
1196
 
1197
        // Run the standard initializer
1198
        function initialize ( originalOptions ) {
1199
 
1200
                // Throw error if group is empty.
1201
                if ( !this.length ){
1202
                        throw new Error("noUiSlider: Can't initialize slider on empty selection.");
1203
                }
1204
 
1205
                // Test the options once, not for every slider.
1206
                var options = test( originalOptions, this );
1207
 
1208
                // Loop all items, and provide a new closed-scope environment.
1209
                return this.each(function(){
1210
                        closure(this, options, originalOptions);
1211
                });
1212
        }
1213
 
1214
        // Destroy the slider, then re-enter initialization.
1215
        function rebuild ( options ) {
1216
 
1217
                return this.each(function(){
1218
 
1219
                        // Get the current values from the slider,
1220
                        // including the initialization options.
1221
                        var values = $(this).val(),
1222
                                originalOptions = this.destroy(),
1223
 
1224
                                // Extend the previous options with the newly provided ones.
1225
                                newOptions = $.extend( {}, originalOptions, options );
1226
 
1227
                        // Run the standard initializer.
1228
                        $(this).noUiSlider( newOptions );
1229
 
1230
                        // If the start option hasn't changed,
1231
                        // reset the previous values.
1232
                        if ( originalOptions.start === newOptions.start ) {
1233
                                $(this).val(values);
1234
                        }
1235
                });
1236
        }
1237
 
1238
 
1239
// Remap the serialization constructor for legacy support.
1240
        /** @expose */
1241
        $.noUiSlider = { 'Link': $.Link };
1242
 
1243
// Extend jQuery/Zepto with the noUiSlider method.
1244
        /** @expose */
1245
        $.fn.noUiSlider = function ( options, re ) {
1246
                return ( re ? rebuild : initialize ).call(this, options);
1247
        };
1248
 
1249
// Attach a classbased val handler.
1250
        $.classVal(Classes[0], 'vGet', 'vSet', false);
1251
 
1252
}( window['jQuery'] || window['Zepto'] ));