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