hjg
2024-07-09 30304784e82d4bba24121328da8eb8490aec4f4f
提交 | 用户 | 时间
58d006 1 /* noUiSlider - refreshless.com/nouislider/ */
A 2 (function($, UNDEF){
3
4     $.fn.noUiSlider = function( options ){
5
6         var  namespace = '.nui'
7             // Create a shorthand for document event binding
8             ,all = $(document)
9             // Create a map of touch and mouse actions
10             ,actions = {
11                  start: 'mousedown' + namespace + ' touchstart' + namespace
12                 ,move: 'mousemove' + namespace + ' touchmove' + namespace
13                 ,end: 'mouseup' + namespace + ' touchend' + namespace
14             }
15             // Make a copy of the current val function.
16             ,$VAL = $.fn.val
17             // Define a set of standard HTML classes for
18             // the various structures noUiSlider uses.
19             ,clsList = [
20                  'noUi-base'        // 0
21                 ,'noUi-origin'        // 1
22                 ,'noUi-handle'        // 2
23                 ,'noUi-input'        // 3
24                 ,'noUi-active'        // 4
25                 ,'noUi-state-tap'    // 5
26                 ,'noUi-target'        // 6
27                 ,'-lower'            // 7
28                 ,'-upper'            // 8
29                 ,'noUi-connect'        // 9
30                 ,'noUi-vertical'    // 10
31                 ,'noUi-horizontal'    // 11
32                 ,'handles'            // 12
33                 ,'noUi-background'    // 13
34                 ,'noUi-z-index'        // 14
35             ]
36             // Define an extendible object with base classes for the various
37             // structure elements in the slider. These can be extended by simply
38             // pushing to the array, which reduces '.addClass()' calls.
39             ,stdCls = {
40                  base: [clsList[0], clsList[13]]
41                 ,origin: [clsList[1]]
42                 ,handle: [clsList[2]]
43             }
44             // The percentage object contains some well tested math to turn values
45             // to and from percentages. It can be a bit strange to wrap your head
46             // around the individual calls, but they'll do their job with all positive
47             // and negative input values.
48             ,percentage = {
49                  to: function ( range, value ) {
50                     value = range[0] < 0 ? value + Math.abs(range[0]) : value - range[0];
51                     return (value * 100) / this.len(range);
52                 }
53                 ,from: function ( range, value ) {
54                     return (value * 100) / this.len(range);
55                 }
56                 ,is: function ( range, value ) {
57                     return ((value * this.len(range)) / 100) + range[0];
58                 }
59                 ,len: function ( range ) {
60                     return (range[0] > range[1] ? range[0] - range[1] : range[1] - range[0]);
61                 }
62             };
63
64         // When the browser supports MsPointerEvents,
65         // Don't bind touch or mouse events. The touch events are
66         // currently only implemented by IE(10), but they are stable
67         // and convenient to use.
68         if ( window.navigator.msPointerEnabled ) {
69             actions = {
70                  start: 'MSPointerDown' + namespace
71                 ,move: 'MSPointerMove' + namespace
72                 ,end: 'MSPointerUp' + namespace
73             };
74         }
75
76         // Shorthand for stopping propagation on an object.
77         // Calling a function prevents having to define one inline.
78         function stopPropagation ( e ) {
79             e.stopPropagation();
80         }
81
82         // Test an array of objects, and calls them if they are a function.
83         function call ( f, scope, args ) {
84             $.each(f,function(i,q){
85                 if (typeof q === "function") {
86                     q.call(scope, args);
87                 }
88             });
89         }
90
91         // Test if there is anything that should prevent an event from being
92         // handled, such as a disabled state of a slider moving in the 'tap' event.
93         function blocked ( e ) {
94              return ( e.data.base.data('target').is('[class*="noUi-state-"], [disabled]') );
95         }
96
97         function fixEvent ( e, preventDefault ) {
98
99             // Required (in at the very least Chrome) to prevent
100             // scrolling and panning while attempting to slide.
101             // The tap event also depends on this.
102             if( preventDefault ) {
103                 e.preventDefault();
104             }
105
106             // Filter the event to register the type,
107             // which can be touch, mouse or pointer. Since noUiSlider 4
108             // so longer binds touch OR mouse, but rather touch AND mouse,
109             // offset changes need to be made on an event specific basis.
110             var  jQueryEvent = e
111                 ,touch = e.type.indexOf('touch') === 0
112                 ,mouse = e.type.indexOf('mouse') === 0
113                 ,pointer = e.type.indexOf('MSPointer') === 0
114                 ,x,y;
115
116             // Fetch the event where jQuery didn't make any modifications.
117             e = e.originalEvent;
118
119             if (touch) {
120                 // noUiSlider supports one movement at a time, for now.
121                 // It is therefore safe to select the first 'changedTouch'.
122                 x = e.changedTouches[0].pageX;
123                 y = e.changedTouches[0].pageY;
124             }
125             if (mouse) {
126
127                 // Polyfill the pageXOffset and pageYOffset
128                 // variables for IE7 and IE8;
129                 if(window.pageXOffset === UNDEF){
130                     window.pageXOffset = document.documentElement.scrollLeft;
131                     window.pageYOffset = document.documentElement.scrollTop;
132                 }
133
134                 x = e.clientX + window.pageXOffset;
135                 y = e.clientY + window.pageYOffset;
136             }
137             if (pointer) {
138                 x = e.pageX;
139                 y = e.pageY;
140             }
141
142             return { pass: jQueryEvent.data, e:e, x:x, y:y, t: [touch, mouse, pointer] };
143
144         }
145
146         function getPercentage( a ){
147             return parseFloat(this.style[a]);
148         }
149
150         function test ( o, set ){
151
152             // Checks whether a variable is numerical.
153             function num(e){
154                 return !isNaN(e) && isFinite(e);
155             }
156             // Checks whether a variable is a candidate to be a
157             // valid serialization target.
158             function ser(r){
159                 return ( r instanceof $ || typeof r === 'string' || r === false );
160             }
161
162
163     /**
164             These tests are structured with an item for every option available.
165             Every item contains an 'r' flag, which marks a required option, and
166             a 't' function, which in turn takes some arguments:
167             - a reference to options object
168             - the value for the option
169             - the option name (optional);
170             The testing function returns false when an error is detected,
171             or true when everything is OK. Every test also has an 'init'
172             method which appends the parent object to all children.
173     **/
174             var TESTS = {
175                 /*    Handles. Has default, can be 1 or 2;
176                  */
177                  "handles": {
178                      r: true
179                     ,t: function(o,q){
180                         q = parseInt(q, 10);
181                         return ( q === 1 || q === 2 );
182                     }
183                 }
184                 /*    Range.
185                  *    Must be an array of two numerical floats,
186                  *    which can't be identical.
187                  */
188                 ,"range": {
189                      r: true
190                     ,t: function(o,q,w){
191                         if(q.length!==2){
192                             return false;
193                         }
194                         // Reset the array to floats
195                         q = [parseFloat(q[0]),parseFloat(q[1])];
196                         // Test if those floats are numerical
197                         if(!num(q[0])||!num(q[1])){
198                             return false;
199                         }
200                         // When this test is run for range, the values can't
201                         // be identical.
202                         if(w==="range" && q[0] === q[1]){
203                             return false;
204                         }
205                         o[w]=q;
206                         return true;
207                     }
208                  }
209                 /*    Start.
210                  *    Must be an array of two numerical floats when handles = 2;
211                  *    Uses 'range' test.
212                  *    When handles = 1, a single float is also allowed.
213                  */
214                 ,"start": {
215                      r: true
216                     ,t: function(o,q,w){
217                         if(o.handles === 1){
218                             if($.isArray(q)){
219                                 q=q[0];
220                             }
221                             q = parseFloat(q);
222                             o.start = [q];
223                             return num(q);
224                         }
225                         return this.parent.range.t(o,q,w);
226                     }
227                 }
228                 /*    Connect.
229                  *    Must be true or false when handles = 2;
230                  *    Can use 'lower' and 'upper' when handles = 1.
231                  */
232                 ,"connect": {
233                      t: function(o,q){
234                         return (   q === true
235                                 || q === false
236                                 || ( q === 'lower' && o.handles === 1)
237                                 || ( q === 'upper' && o.handles === 1));
238                      }
239                 }
240                 /*    Connect.
241                  *    Will default to horizontal, not required.
242                  */
243                 ,"orientation": {
244                      t: function(o,q){
245                         return ( q === "horizontal" || q === "vertical" );
246                     }
247                 }
248                 /*    Margin.
249                  *    Must be a float, has a default value.
250                  */
251                 ,"margin": {
252                      r: true
253                     ,t: function(o,q,w){
254                         q = parseFloat(q);
255                         o[w]=q;
256                         return num(q);
257                     }
258                 }
259                 /*    Serialization.
260                  *    Required, but has default. Resolution option can be missing,
261                  *    'to' can't. Must be an array when using two handles, can
262                  *    be a singular value when using one handle.
263                  */
264                 ,"serialization": {
265                      r: true
266                     ,t: function(o,q){
267
268                         if(!q.resolution){
269                             o.serialization.resolution = 0.01;
270                         } else {
271                             switch(q.resolution){
272                                 case 1:
273                                 case 0.1:
274                                 case 0.01:
275                                 case 0.001:
276                                 case 0.0001:
277                                 case 0.00001:
278                                     break;
279                                 default:
280                                     return false;
281                             }
282                         }
283
284                         if(q.to){
285
286                             if(o.handles === 1){
287                                 // Wrap the value for one handle into an array;
288                                 if(!$.isArray(q.to)){
289                                     q.to = [q.to];
290                                 }
291                                 // Write back to the options object;
292                                 o.serialization.to = q.to;
293                                 // Run test for valid serialization target.
294                                 return ser(q.to[0]);
295                             }
296                             return (q.to.length === 2 && ser(q.to[0]) && ser(q.to[1]));
297
298                         }
299
300                         // If no 'to' option is specified,
301                         // the serialization option is invalid.
302                         return false;
303
304                     }
305                 }
306                 /*    Slide.
307                  *    Not required. Must be a function.
308                  */
309                 ,"slide": {
310                      t: function(o,q){
311                     return typeof q === "function";
312                     }
313                 }
314                 /*    Slide.
315                  *    Not required. Tested using the 'margin' function.
316                  */
317                 ,"step": {
318                      t: function(o,q,w){
319                         return this.parent.margin.t(o,q,w);
320                     }
321                 }
322                 /*    [init]
323                  *    Not an option test. Calling this method will return the
324                  *    parent object with some cross references that allow crawling
325                  *    the object upward, which normally isn't possible in Javascript.
326                  */
327                 ,"init": function(){
328                     var obj = this;
329                     $.each(obj,function(i,c){
330                         c.parent = obj;
331                     });
332                     delete this.init;
333                     return this;
334                 }
335             },
336
337             // Prepare a set of tests, by adding some internal reference
338             // values not available in native Javascript object implementation.
339             a = TESTS.init();
340
341             // Loop all provided tests;
342             // v is the option set, i is the index for the current test.
343             $.each(a, function( i, v ){
344
345                 // If the value is required but not set,
346                 // or if the test fails, throw an error.
347                 if((v.r && (!o[i] && o[i] !== 0)) || ((o[i] || o[i] === 0) && !v.t(o,o[i],i))){
348
349                     // For debugging purposes it might be very useful
350                     // to know what option caused the trouble.
351                     if(console&&console.log){
352                         console.log(
353                             "Slider:\t\t\t",    set,
354                             "\nOption:\t\t\t",    i,
355                             "\nValue:\t\t\t",    o[i]
356                         );
357                     }
358                     // Since 'error' will prevent further script execution,
359                     // log the error first.
360                     $.error("Error on noUiSlider initialisation.");
361                     return false;
362                 }
363
364             });
365
366         }
367
368         function closest( value, to ){
369             // Round a value to the closest 'to'.
370             // Used with the 'step' option.
371             return Math.round(value / to) * to;
372         }
373
374         function setHandle ( handle, to, forgive ) {
375
376             var  nui = handle.data('nui').options
377                 // Get the array of handles from the base.
378                 // Will be undefined at initialisation.
379                 ,handles = handle.data('nui').base.data(clsList[12])
380                 // Get some settings from the handle;
381                 ,style = handle.data('nui').style
382                 ,dec = handle.data('nui').decimals
383                 ,hLimit;
384
385             // Ignore the call if the handle won't move anyway.
386             if(to === handle[0].getPercentage(style)) {
387                 return false;
388             }
389
390             // Limit 'to' to 0 - 100
391             to = to < 0 ? 0 : to > 100 ? 100 : to;
392
393             // Handle the step option, or ignore it.
394             if( nui.step && !forgive ){
395                 to = closest( to, percentage.from(nui.range, nui.step));
396             }
397
398             // Stop handling this call if the handle won't step to a new value.
399             if(to === handle[0].getPercentage(style)) {
400                 return false;
401             }
402
403             // We're done if this is the only handle,
404             // if the handle bounce is trusted to the user
405             // or on initialisation when handles isn't defined yet.
406             if( handle.siblings('.' + clsList[1]).length && !forgive && handles ){
407
408                 // Otherwise, the handle should bounce,
409                 // and stop at the other handle.
410                 if ( handle.data('nui').number ) {
411                     hLimit = handles[0][0].getPercentage(style) + nui.margin;
412                     to = to < hLimit ? hLimit : to;
413                 } else {
414                     hLimit = handles[1][0].getPercentage(style) - nui.margin;
415                     to = to > hLimit ? hLimit : to;
416                 }
417
418                 // Stop handling this call if the handle can't move past another.
419                 if(to === handle[0].getPercentage(style)) {
420                     return false;
421                 }
422
423             }
424
425             // Fix for the z-index issue where the lower handle gets stuck
426             // below the upper one. Since this function is called for every
427             // movement, toggleClass cannot be used.
428             if(handle.data('nui').number === 0 && to > 95){
429                 handle.addClass(clsList[14]);
430             } else {
431                 handle.removeClass(clsList[14]);
432             }
433
434             // Set handle to new location
435             handle.css( style , to + '%');
436
437             // Write the value to the serialization object.
438             handle.data('store').val(percentage.is(nui.range, to).toFixed(dec));
439
440             return true;
441
442         }
443
444         function store ( handle, S ) {
445
446             var i = handle.data('nui').number;
447
448             if( S.to[i] instanceof $ ) {
449
450                 // Attach a change event to the supplied jQuery object,
451                 // which will just trigger the val function on the parent.
452                 // In some cases, the change event will not fire on select elements,
453                 // so listen to 'blur' too.
454                 return S.to[i].on('change'+namespace+' blur'+namespace, function(){
455                     var arr = [null, null];
456                     arr[i] = $(this).val();
457                     handle.data('nui').target.val(arr, true);
458                 });
459
460             }
461
462             if ( typeof S.to[i] === "string" ) {
463
464                 // Append a new object to the noUiSlider base,
465                 // prevent change events flowing upward.
466                 return $('<input type="hidden" class="'+clsList[3]+'" name="' + S.to[i] + '">')
467                     .appendTo(handle).change(stopPropagation);
468
469             }
470
471             if ( S.to[i] === false ) {
472
473                 // Create an object capable of handling all jQuery calls.
474                 return {
475                     // The value will be stored a data on the handle.
476                      val : function(a) {
477                         // Value function provides a getter and a setter.
478                         // Can't just test for !a, as a might be 0.
479                         if ( a === UNDEF ) {
480                             // Either set...
481                             return this.handleElement.data('nui-val');
482                         }
483                         // ... or return;
484                         this.handleElement.data('nui-val', a);
485                     }
486                     // The object could be mistaken for a jQuery object,
487                     // make sure that doesn't trigger any errors.
488                     ,hasClass: function(){
489                         return false;
490                     }
491                     // The val function needs access to the handle.
492                     ,handleElement: handle
493                 };
494             }
495
496         }
497
498         function move( event ) {
499
500             // This function is called often, keep it light.
501
502             event = fixEvent( event, true );
503
504             if(!event) {
505                 return;
506             }
507
508             var  base = event.pass.base
509                 ,style = base.data('style')
510             // Subtract the initial movement from the current event,
511             // while taking vertical sliders into account.
512                 ,proposal = event.x - event.pass.startEvent.x
513                 ,baseSize = style === 'left' ? base.width() : base.height();
514
515             // This loop prevents a long ternary for the proposal variable.
516             if(style === 'top') {
517                 proposal = event.y - event.pass.startEvent.y;
518             }
519
520             proposal = event.pass.position + ( ( proposal * 100 ) / baseSize );
521
522             setHandle( event.pass.handle, proposal );
523
524             // Trigger the 'slide' event, pass the target so that it is 'this'.
525             call(
526                  [ event.pass.base.data('options').slide ]
527                 ,event.pass.base.data('target')
528             );
529
530         }
531
532         function end ( event ) {
533
534             if ( blocked( event ) ) {
535                 return;
536             }
537
538             // Handle is no longer active;
539             event.data.handle.children().removeClass(clsList[4]);
540
541             // Unbind move and end events, to prevent
542             // them stacking up over and over;
543             all.off(actions.move);
544             all.off(actions.end);
545             $('body').off(namespace);
546
547             event.data.base.data('target').change();
548
549         }
550
551         function start ( event ) {
552
553             // When the slider is in a transitional state, stop.
554             // Also prevents interaction with disabled sliders.
555             if ( blocked( event ) ) {
556                 return;
557             }
558
559             event = fixEvent( event );
560
561             if(!event) {
562                 return;
563             }
564
565             var  handle = event.pass.handle
566                 ,position = handle[0].getPercentage( handle.data('nui').style );
567
568             handle.children().addClass('noUi-active');
569
570             // Attach the move event handler, while
571             // passing all relevant information along.
572             all.on(actions.move, {
573                  startEvent: event
574                 ,position: position
575                 ,base: event.pass.base
576                 ,handle: handle
577             }, move);
578
579             all.on(actions.end, { base: event.pass.base, handle: handle }, end);
580
581             // Prevent text selection when dragging the handles.
582             // This doesn't prevent the browser defaulting to the I like cursor.
583             $('body').on('selectstart' + namespace, function(){ return false; });
584
585         }
586
587         function selfEnd( event ) {
588             // Trigger the end handler. Supply correct data using a
589             // fake object that contains all required information;
590             end({ data: { base: event.data.base, handle: event.data.handle } });
591             // Stop propagation so that the tap handler doesn't interfere;
592             event.stopPropagation();
593         }
594
595         function tap ( event ) {
596
597             if ( blocked( event ) || event.data.base.find('.' + clsList[4]).length ) {
598                 return;
599             }
600
601             event = fixEvent( event );
602
603             // The event handler might have rejected this event.
604             if(!event) {
605                 return;
606             }
607
608             // Getting variables from the event is not required, but
609             // shortens other expressions and is far more convenient;
610             var  i, handle, hCenter, base = event.pass.base
611                 ,handles = event.pass.handles
612                 ,style = base.data('style')
613                 ,eventXY = event[style === 'left' ? 'x' : 'y']
614                 ,baseSize = style === 'left' ? base.width() : base.height()
615
616             // Create a standard set off offsets compensated with the
617             // scroll distance. When required, correct for scrolling.
618             // This is a bug, as far as I can see, in IE(10?).
619                 ,correction = {
620                      x: ( event.t[2] ? window.pageXOffset : 0 )
621                 }
622                 ,offset = {
623                      handles: []
624                     ,base: {
625                          left: base.offset().left - correction.x
626                         ,top: base.offset().top
627                     }
628                 };
629
630             // Loop handles and add data to the offset list.
631             for (i = 0; i < handles.length; i++ ) {
632                 offset.handles.push({
633                      left: handles[i].offset().left - correction.x
634                     ,top: handles[i].offset().top
635                 });
636             }
637
638             // Calculate the central point between the handles;
639             hCenter = handles.length === 1 ? 0 :
640                 (( offset.handles[0][style] + offset.handles[1][style] ) / 2 );
641
642             // If there is just one handle,
643             // or the lower handles in closest to the event,
644             // select the first handle. Otherwise, pick the second.
645             if ( handles.length === 1 || eventXY < hCenter ){
646                 handle = handles[0];
647             } else {
648                 handle = handles[1];
649             }
650
651             // Flag the slider as it is now in a transitional state.
652             // Transition takes 300 ms, so re-enable the slider afterwards.
653             base.addClass(clsList[5]);
654             setTimeout(function(){
655                 base.removeClass(clsList[5]);
656             }, 300);
657
658             // Calculate the new position for the handle and
659             // trigger the movement.
660             setHandle(
661                  handle
662                 ,(((eventXY - offset.base[style]) * 100) / baseSize)
663             );
664
665             // Trigger the 'slide' event, pass the target so that it is 'this'.
666             call(
667                  [ handle.data('nui').options.slide ]
668                 ,base.data('target')
669             );
670
671             base.data('target').change();
672
673         }
674
675         function create ( ) {
676
677             return this.each(function( index, target ){
678
679                 // Target is the wrapper that will receive all external
680                 // scripting interaction. It has no styling and serves no
681                 // other function.
682                 target = $(target);
683                 target.addClass(clsList[6]);
684
685                 // Base is the internal main 'bar'.
686                 var  i, style, decimals, handle
687                     ,base = $('<div/>').appendTo(target)
688                     ,handles = []
689                     ,cls = {
690                      base: stdCls.base
691                         ,origin: [
692                              stdCls.origin.concat([clsList[1] + clsList[7]])
693                             ,stdCls.origin.concat([clsList[1] + clsList[8]])
694                         ]
695                         ,handle: [
696                              stdCls.handle.concat([clsList[2] + clsList[7]])
697                             ,stdCls.handle.concat([clsList[2] + clsList[8]])
698                         ]
699                     };
700
701                 // Set defaults where applicable;
702                 options = $.extend({
703                      handles: 2
704                     ,margin: 0
705                     ,orientation: "horizontal"
706                 }, options) || {};
707
708                 // Set a default for serialization;
709                 if(!options.serialization){
710                     options.serialization = {
711                          to : [false, false]
712                         ,resolution : 0.01
713                     };
714                 }
715
716                 // Run all options through a testing mechanism to ensure correct
717                 // input. The test function will throw errors, so there is
718                 // no need to capture the result of this call. It should be noted
719                 // that options might get modified to be handled properly. E.g.
720                 // wrapping integers in arrays.
721                 test(options, target);
722
723                 // I can't type serialization any more, and it doesn't compress
724                 // very well, so shorten it.
725                 options.S = options.serialization;
726
727
728                 // INCOMPLETE
729                 if( options.connect ) {
730                     cls.origin[0].push(clsList[9]);
731                     if( options.connect === "lower" ){
732                         // Add some styling classes to the base;
733                         cls.base.push(clsList[9], clsList[9] + clsList[7]);
734                         // When using the option 'Lower', there is only one
735                         // handle, and thus only one origin.
736                         cls.origin[0].push(clsList[13]);
737                     } else {
738                         cls.base.push(clsList[9] + clsList[8]);
739                     }
740                 }
741
742                 // Parse the syntactic sugar that is the serialization
743                 // resolution option to a usable integer.
744                 style = options.orientation === 'vertical' ? 'top' : 'left';
745
746                 decimals = options.S.resolution.toString().split('.');
747
748                 // Checking for a string "1", since the resolution needs
749                 // to be cast to a string to split in on the period.
750                 decimals = decimals[0] === "1" ? 0 : decimals[1].length;
751
752                 // Add classes for horizontal and vertical sliders.
753                 // The horizontal class is provided for completeness,
754                 // as it isn't used in the default theme.
755                 if( options.orientation === "vertical" ){
756                     cls.base.push(clsList[10]);
757                 } else {
758                     cls.base.push(clsList[11]);
759                 }
760
761                 // Merge base classes with default;
762                 base.addClass(cls.base.join(" ")).data('target', target);
763
764                 for (i = 0; i < options.handles; i++ ) {
765
766                     handle = $('<div><div/></i>').appendTo(base);
767
768                     // Add all default and option-specific classes to the
769                     // origins and handles.
770                     handle.addClass(cls.origin[i].join(" "));
771                     handle.children().addClass(cls.handle[i].join(" "));
772
773                     // These events are only bound to the visual handle element,
774                     // not the 'real' origin element.
775                     handle.children()
776                         .on(actions.start, { base: base, handle: handle }, start)
777                         .on(actions.end, { base: base, handle: handle }, selfEnd);
778
779                     // Make sure every handle has access to all primary
780                     // variables. Can't uses jQuery's .data( obj ) structure
781                     // here, as 'store' needs some values from the 'nui' object.
782                     handle.data('nui', {
783                          target: target
784                         ,decimals: decimals
785                         ,options: options
786                         ,base: base
787                         ,style: style
788                         ,number: i
789                     }).data('store', store (
790                          handle
791                         ,options.S
792                     ));
793
794                     // Attach a function to the native DOM element,
795                     // since jQuery wont let me get the current value in percentages.
796                     handle[0].getPercentage = getPercentage;
797
798                     // Make handles loop-able
799                     handles.push(handle);
800
801                     // Set the handle to its initial position;
802                     setHandle(handle, percentage.to(options.range, options.start[i]));
803
804                 }
805
806                 // The base could use the handles too;
807                 base.data({
808                      options: options
809                     ,handles: handles
810                     ,style: style
811                 });
812
813                 // Add a downstream reference to the target as well.
814                 target.data({
815                      base: base
816                     ,handles: handles
817                 });
818
819                 // The tap event.
820                 base.on(actions.end, { base: base, handles: handles }, tap);
821
822             });
823
824         }
825
826         function val ( args, ignore ) {
827
828             // Setter
829             if( args !== UNDEF ){
830
831                 // If the val is to be set to a number, which is valid
832                 // when using a one-handle slider, wrap it in an array.
833                 if(!$.isArray(args)){
834                     args = [args];
835                 }
836
837                 // Setting is handled properly for each slider in the data set.
838                 return this.each(function(){
839
840                     $.each($(this).data(clsList[12]), function(i, handle){
841
842                         // The set request might want to ignore this handle.
843                         if( args[i] === null ) {
844                             return;
845                         }
846
847                         // Calculate a new position for the handle.
848                         var  value, current
849                             ,range = handle.data('nui').options.range
850                             ,to = percentage.to(
851                                      range
852                                     ,parseFloat(args[i])
853                                  ),
854
855                         // Set handle to new location, and make sure developer
856                         // input is always accepted. The ignore flag indicates
857                         // input from user facing elements.
858                         result = setHandle(handle, to, (ignore === true ? false : true));
859
860                         // If the value of the input doesn't match the slider,
861                         // reset it.
862                         if(!result){
863
864                             // Get the 'store' object, which can be an input element
865                             // or a wrapper arround a 'data' call.
866                             value = handle.data('store').val();
867                             current = percentage.is(range,
868                                         handle[0].getPercentage(handle.data('nui').style)
869                                     ).toFixed(handle.data('nui').decimals);
870
871                             // Sometimes the input is changed to a value the slider
872                             // has rejected. This can occur when using 'select' or
873                             // 'input[type="number"]' elements. In this case,
874                             // set the value back to the input.
875                             if(value !== current){
876                                 handle.data('store').val(current);
877                             }
878                         }
879
880                     });
881
882                 });
883
884             }
885
886             // Or, if the function was called without arguments,
887             // act as a 'getter';
888
889             var re = [];
890
891             // Loop the handles, and get the value from the input
892             // for every handle on its' own.
893             $.each($(this).data(clsList[12]), function(i, handle){
894                 re.push( handle.data('store').val() );
895             });
896
897             // If the slider has just one handle, return a single value.
898             // Otherwise, return an array.
899             return ( re.length === 1 ? re[0] : re) ;
900
901         }
902
903         // Overwrite the native jQuery val() function
904         // with a simple handler. noUiSlider will use the internal
905         // value method, anything else will use the standard method.
906         $.fn.val = function(){
907             return this.hasClass(clsList[6])
908                 ? val.apply(this, arguments)
909                 : $VAL.apply(this, arguments);
910         };
911
912         return create.apply(this, arguments);
913
914     };
915
916 }(jQuery));