1 /* 2 * jQuery UI Slider 1.8.16 3 * 4 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) 5 * Dual licensed under the MIT or GPL Version 2 licenses. 6 * http://jquery.org/license 7 * 8 * http://docs.jquery.com/UI/Slider 9 * 10 * Depends: 11 * jquery.ui.core.js 12 * jquery.ui.mouse.js 13 * jquery.ui.widget.js 14 */ 15 (function( $, undefined ) { 16 17 // number of pages in a slider 18 // (how many times can you page up/down to go through the whole range) 19 var numPages = 5; 20 21 $.widget( "ui.slider", $.ui.mouse, { 22 23 widgetEventPrefix: "slide", 24 25 options: { 26 animate: false, 27 distance: 0, 28 max: 100, 29 min: 0, 30 orientation: "horizontal", 31 range: false, 32 step: 1, 33 value: 0, 34 values: null 35 }, 36 37 _create: function() { 38 var self = this, 39 o = this.options, 40 existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ), 41 handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>", 42 handleCount = ( o.values && o.values.length ) || 1, 43 handles = []; 44 45 this._keySliding = false; 46 this._mouseSliding = false; 47 this._animateOff = true; 48 this._handleIndex = null; 49 this._detectOrientation(); 50 this._mouseInit(); 51 52 this.element 53 .addClass( "ui-slider" + 54 " ui-slider-" + this.orientation + 55 " ui-widget" + 56 " ui-widget-content" + 57 " ui-corner-all" + 58 ( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) ); 59 60 this.range = $([]); 61 62 if ( o.range ) { 63 if ( o.range === true ) { 64 if ( !o.values ) { 65 o.values = [ this._valueMin(), this._valueMin() ]; 66 } 67 if ( o.values.length && o.values.length !== 2 ) { 68 o.values = [ o.values[0], o.values[0] ]; 69 } 70 } 71 72 this.range = $( "<div></div>" ) 73 .appendTo( this.element ) 74 .addClass( "ui-slider-range" + 75 // note: this isn't the most fittingly semantic framework class for this element, 76 // but worked best visually with a variety of themes 77 " ui-widget-header" + 78 ( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) ); 79 } 80 81 for ( var i = existingHandles.length; i < handleCount; i += 1 ) { 82 handles.push( handle ); 83 } 84 85 this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( self.element ) ); 86 87 this.handle = this.handles.eq( 0 ); 88 89 this.handles.add( this.range ).filter( "a" ) 90 .click(function( event ) { 91 event.preventDefault(); 92 }) 93 .hover(function() { 94 if ( !o.disabled ) { 95 $( this ).addClass( "ui-state-hover" ); 96 } 97 }, function() { 98 $( this ).removeClass( "ui-state-hover" ); 99 }) 100 .focus(function() { 101 if ( !o.disabled ) { 102 $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" ); 103 $( this ).addClass( "ui-state-focus" ); 104 } else { 105 $( this ).blur(); 106 } 107 }) 108 .blur(function() { 109 $( this ).removeClass( "ui-state-focus" ); 110 }); 111 112 this.handles.each(function( i ) { 113 $( this ).data( "index.ui-slider-handle", i ); 114 }); 115 116 this.handles 117 .keydown(function( event ) { 118 var ret = true, 119 index = $( this ).data( "index.ui-slider-handle" ), 120 allowed, 121 curVal, 122 newVal, 123 step; 124 125 if ( self.options.disabled ) { 126 return; 127 } 128 129 switch ( event.keyCode ) { 130 case $.ui.keyCode.HOME: 131 case $.ui.keyCode.END: 132 case $.ui.keyCode.PAGE_UP: 133 case $.ui.keyCode.PAGE_DOWN: 134 case $.ui.keyCode.UP: 135 case $.ui.keyCode.RIGHT: 136 case $.ui.keyCode.DOWN: 137 case $.ui.keyCode.LEFT: 138 ret = false; 139 if ( !self._keySliding ) { 140 self._keySliding = true; 141 $( this ).addClass( "ui-state-active" ); 142 allowed = self._start( event, index ); 143 if ( allowed === false ) { 144 return; 145 } 146 } 147 break; 148 } 149 150 step = self.options.step; 151 if ( self.options.values && self.options.values.length ) { 152 curVal = newVal = self.values( index ); 153 } else { 154 curVal = newVal = self.value(); 155 } 156 157 switch ( event.keyCode ) { 158 case $.ui.keyCode.HOME: 159 newVal = self._valueMin(); 160 break; 161 case $.ui.keyCode.END: 162 newVal = self._valueMax(); 163 break; 164 case $.ui.keyCode.PAGE_UP: 165 newVal = self._trimAlignValue( curVal + ( (self._valueMax() - self._valueMin()) / numPages ) ); 166 break; 167 case $.ui.keyCode.PAGE_DOWN: 168 newVal = self._trimAlignValue( curVal - ( (self._valueMax() - self._valueMin()) / numPages ) ); 169 break; 170 case $.ui.keyCode.UP: 171 case $.ui.keyCode.RIGHT: 172 if ( curVal === self._valueMax() ) { 173 return; 174 } 175 newVal = self._trimAlignValue( curVal + step ); 176 break; 177 case $.ui.keyCode.DOWN: 178 case $.ui.keyCode.LEFT: 179 if ( curVal === self._valueMin() ) { 180 return; 181 } 182 newVal = self._trimAlignValue( curVal - step ); 183 break; 184 } 185 186 self._slide( event, index, newVal ); 187 188 return ret; 189 190 }) 191 .keyup(function( event ) { 192 var index = $( this ).data( "index.ui-slider-handle" ); 193 194 if ( self._keySliding ) { 195 self._keySliding = false; 196 self._stop( event, index ); 197 self._change( event, index ); 198 $( this ).removeClass( "ui-state-active" ); 199 } 200 201 }); 202 203 this._refreshValue(); 204 205 this._animateOff = false; 206 }, 207 208 destroy: function() { 209 this.handles.remove(); 210 this.range.remove(); 211 212 this.element 213 .removeClass( "ui-slider" + 214 " ui-slider-horizontal" + 215 " ui-slider-vertical" + 216 " ui-slider-disabled" + 217 " ui-widget" + 218 " ui-widget-content" + 219 " ui-corner-all" ) 220 .removeData( "slider" ) 221 .unbind( ".slider" ); 222 223 this._mouseDestroy(); 224 225 return this; 226 }, 227 228 _mouseCapture: function( event ) { 229 var o = this.options, 230 position, 231 normValue, 232 distance, 233 closestHandle, 234 self, 235 index, 236 allowed, 237 offset, 238 mouseOverHandle; 239 240 if ( o.disabled ) { 241 return false; 242 } 243 244 this.elementSize = { 245 width: this.element.outerWidth(), 246 height: this.element.outerHeight() 247 }; 248 this.elementOffset = this.element.offset(); 249 250 position = { x: event.pageX, y: event.pageY }; 251 normValue = this._normValueFromMouse( position ); 252 distance = this._valueMax() - this._valueMin() + 1; 253 self = this; 254 this.handles.each(function( i ) { 255 var thisDistance = Math.abs( normValue - self.values(i) ); 256 if ( distance > thisDistance ) { 257 distance = thisDistance; 258 closestHandle = $( this ); 259 index = i; 260 } 261 }); 262 263 // workaround for bug #3736 (if both handles of a range are at 0, 264 // the first is always used as the one with least distance, 265 // and moving it is obviously prevented by preventing negative ranges) 266 if( o.range === true && this.values(1) === o.min ) { 267 index += 1; 268 closestHandle = $( this.handles[index] ); 269 } 270 271 allowed = this._start( event, index ); 272 if ( allowed === false ) { 273 return false; 274 } 275 this._mouseSliding = true; 276 277 self._handleIndex = index; 278 279 closestHandle 280 .addClass( "ui-state-active" ) 281 .focus(); 282 283 offset = closestHandle.offset(); 284 mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" ); 285 this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : { 286 left: event.pageX - offset.left - ( closestHandle.width() / 2 ), 287 top: event.pageY - offset.top - 288 ( closestHandle.height() / 2 ) - 289 ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) - 290 ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) + 291 ( parseInt( closestHandle.css("marginTop"), 10 ) || 0) 292 }; 293 294 if ( !this.handles.hasClass( "ui-state-hover" ) ) { 295 this._slide( event, index, normValue ); 296 } 297 this._animateOff = true; 298 return true; 299 }, 300 301 _mouseStart: function( event ) { 302 return true; 303 }, 304 305 _mouseDrag: function( event ) { 306 var position = { x: event.pageX, y: event.pageY }, 307 normValue = this._normValueFromMouse( position ); 308 309 this._slide( event, this._handleIndex, normValue ); 310 311 return false; 312 }, 313 314 _mouseStop: function( event ) { 315 this.handles.removeClass( "ui-state-active" ); 316 this._mouseSliding = false; 317 318 this._stop( event, this._handleIndex ); 319 this._change( event, this._handleIndex ); 320 321 this._handleIndex = null; 322 this._clickOffset = null; 323 this._animateOff = false; 324 325 return false; 326 }, 327 328 _detectOrientation: function() { 329 this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal"; 330 }, 331 332 _normValueFromMouse: function( position ) { 333 var pixelTotal, 334 pixelMouse, 335 percentMouse, 336 valueTotal, 337 valueMouse; 338 339 if ( this.orientation === "horizontal" ) { 340 pixelTotal = this.elementSize.width; 341 pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 ); 342 } else { 343 pixelTotal = this.elementSize.height; 344 pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 ); 345 } 346 347 percentMouse = ( pixelMouse / pixelTotal ); 348 if ( percentMouse > 1 ) { 349 percentMouse = 1; 350 } 351 if ( percentMouse < 0 ) { 352 percentMouse = 0; 353 } 354 if ( this.orientation === "vertical" ) { 355 percentMouse = 1 - percentMouse; 356 } 357 358 valueTotal = this._valueMax() - this._valueMin(); 359 valueMouse = this._valueMin() + percentMouse * valueTotal; 360 361 return this._trimAlignValue( valueMouse ); 362 }, 363 364 _start: function( event, index ) { 365 var uiHash = { 366 handle: this.handles[ index ], 367 value: this.value() 368 }; 369 if ( this.options.values && this.options.values.length ) { 370 uiHash.value = this.values( index ); 371 uiHash.values = this.values(); 372 } 373 return this._trigger( "start", event, uiHash ); 374 }, 375 376 _slide: function( event, index, newVal ) { 377 var otherVal, 378 newValues, 379 allowed; 380 381 if ( this.options.values && this.options.values.length ) { 382 otherVal = this.values( index ? 0 : 1 ); 383 384 if ( ( this.options.values.length === 2 && this.options.range === true ) && 385 ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) ) 386 ) { 387 newVal = otherVal; 388 } 389 390 if ( newVal !== this.values( index ) ) { 391 newValues = this.values(); 392 newValues[ index ] = newVal; 393 // A slide can be canceled by returning false from the slide callback 394 allowed = this._trigger( "slide", event, { 395 handle: this.handles[ index ], 396 value: newVal, 397 values: newValues 398 } ); 399 otherVal = this.values( index ? 0 : 1 ); 400 if ( allowed !== false ) { 401 this.values( index, newVal, true ); 402 } 403 } 404 } else { 405 if ( newVal !== this.value() ) { 406 // A slide can be canceled by returning false from the slide callback 407 allowed = this._trigger( "slide", event, { 408 handle: this.handles[ index ], 409 value: newVal 410 } ); 411 if ( allowed !== false ) { 412 this.value( newVal ); 413 } 414 } 415 } 416 }, 417 418 _stop: function( event, index ) { 419 var uiHash = { 420 handle: this.handles[ index ], 421 value: this.value() 422 }; 423 if ( this.options.values && this.options.values.length ) { 424 uiHash.value = this.values( index ); 425 uiHash.values = this.values(); 426 } 427 428 this._trigger( "stop", event, uiHash ); 429 }, 430 431 _change: function( event, index ) { 432 if ( !this._keySliding && !this._mouseSliding ) { 433 var uiHash = { 434 handle: this.handles[ index ], 435 value: this.value() 436 }; 437 if ( this.options.values && this.options.values.length ) { 438 uiHash.value = this.values( index ); 439 uiHash.values = this.values(); 440 } 441 442 this._trigger( "change", event, uiHash ); 443 } 444 }, 445 446 value: function( newValue ) { 447 if ( arguments.length ) { 448 this.options.value = this._trimAlignValue( newValue ); 449 this._refreshValue(); 450 this._change( null, 0 ); 451 return; 452 } 453 454 return this._value(); 455 }, 456 457 values: function( index, newValue ) { 458 var vals, 459 newValues, 460 i; 461 462 if ( arguments.length > 1 ) { 463 this.options.values[ index ] = this._trimAlignValue( newValue ); 464 this._refreshValue(); 465 this._change( null, index ); 466 return; 467 } 468 469 if ( arguments.length ) { 470 if ( $.isArray( arguments[ 0 ] ) ) { 471 vals = this.options.values; 472 newValues = arguments[ 0 ]; 473 for ( i = 0; i < vals.length; i += 1 ) { 474 vals[ i ] = this._trimAlignValue( newValues[ i ] ); 475 this._change( null, i ); 476 } 477 this._refreshValue(); 478 } else { 479 if ( this.options.values && this.options.values.length ) { 480 return this._values( index ); 481 } else { 482 return this.value(); 483 } 484 } 485 } else { 486 return this._values(); 487 } 488 }, 489 490 _setOption: function( key, value ) { 491 var i, 492 valsLength = 0; 493 494 if ( $.isArray( this.options.values ) ) { 495 valsLength = this.options.values.length; 496 } 497 498 $.Widget.prototype._setOption.apply( this, arguments ); 499 500 switch ( key ) { 501 case "disabled": 502 if ( value ) { 503 this.handles.filter( ".ui-state-focus" ).blur(); 504 this.handles.removeClass( "ui-state-hover" ); 505 this.handles.propAttr( "disabled", true ); 506 this.element.addClass( "ui-disabled" ); 507 } else { 508 this.handles.propAttr( "disabled", false ); 509 this.element.removeClass( "ui-disabled" ); 510 } 511 break; 512 case "orientation": 513 this._detectOrientation(); 514 this.element 515 .removeClass( "ui-slider-horizontal ui-slider-vertical" ) 516 .addClass( "ui-slider-" + this.orientation ); 517 this._refreshValue(); 518 break; 519 case "value": 520 this._animateOff = true; 521 this._refreshValue(); 522 this._change( null, 0 ); 523 this._animateOff = false; 524 break; 525 case "values": 526 this._animateOff = true; 527 this._refreshValue(); 528 for ( i = 0; i < valsLength; i += 1 ) { 529 this._change( null, i ); 530 } 531 this._animateOff = false; 532 break; 533 } 534 }, 535 536 //internal value getter 537 // _value() returns value trimmed by min and max, aligned by step 538 _value: function() { 539 var val = this.options.value; 540 val = this._trimAlignValue( val ); 541 542 return val; 543 }, 544 545 //internal values getter 546 // _values() returns array of values trimmed by min and max, aligned by step 547 // _values( index ) returns single value trimmed by min and max, aligned by step 548 _values: function( index ) { 549 var val, 550 vals, 551 i; 552 553 if ( arguments.length ) { 554 val = this.options.values[ index ]; 555 val = this._trimAlignValue( val ); 556 557 return val; 558 } else { 559 // .slice() creates a copy of the array 560 // this copy gets trimmed by min and max and then returned 561 vals = this.options.values.slice(); 562 for ( i = 0; i < vals.length; i+= 1) { 563 vals[ i ] = this._trimAlignValue( vals[ i ] ); 564 } 565 566 return vals; 567 } 568 }, 569 570 // returns the step-aligned value that val is closest to, between (inclusive) min and max 571 _trimAlignValue: function( val ) { 572 if ( val <= this._valueMin() ) { 573 return this._valueMin(); 574 } 575 if ( val >= this._valueMax() ) { 576 return this._valueMax(); 577 } 578 var step = ( this.options.step > 0 ) ? this.options.step : 1, 579 valModStep = (val - this._valueMin()) % step, 580 alignValue = val - valModStep; 581 582 if ( Math.abs(valModStep) * 2 >= step ) { 583 alignValue += ( valModStep > 0 ) ? step : ( -step ); 584 } 585 586 // Since JavaScript has problems with large floats, round 587 // the final value to 5 digits after the decimal point (see #4124) 588 return parseFloat( alignValue.toFixed(5) ); 589 }, 590 591 _valueMin: function() { 592 return this.options.min; 593 }, 594 595 _valueMax: function() { 596 return this.options.max; 597 }, 598 599 _refreshValue: function() { 600 var oRange = this.options.range, 601 o = this.options, 602 self = this, 603 animate = ( !this._animateOff ) ? o.animate : false, 604 valPercent, 605 _set = {}, 606 lastValPercent, 607 value, 608 valueMin, 609 valueMax; 610 611 if ( this.options.values && this.options.values.length ) { 612 this.handles.each(function( i, j ) { 613 valPercent = ( self.values(i) - self._valueMin() ) / ( self._valueMax() - self._valueMin() ) * 100; 614 _set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; 615 $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); 616 if ( self.options.range === true ) { 617 if ( self.orientation === "horizontal" ) { 618 if ( i === 0 ) { 619 self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate ); 620 } 621 if ( i === 1 ) { 622 self.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); 623 } 624 } else { 625 if ( i === 0 ) { 626 self.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate ); 627 } 628 if ( i === 1 ) { 629 self.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } ); 630 } 631 } 632 } 633 lastValPercent = valPercent; 634 }); 635 } else { 636 value = this.value(); 637 valueMin = this._valueMin(); 638 valueMax = this._valueMax(); 639 valPercent = ( valueMax !== valueMin ) ? 640 ( value - valueMin ) / ( valueMax - valueMin ) * 100 : 641 0; 642 _set[ self.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%"; 643 this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate ); 644 645 if ( oRange === "min" && this.orientation === "horizontal" ) { 646 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate ); 647 } 648 if ( oRange === "max" && this.orientation === "horizontal" ) { 649 this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); 650 } 651 if ( oRange === "min" && this.orientation === "vertical" ) { 652 this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate ); 653 } 654 if ( oRange === "max" && this.orientation === "vertical" ) { 655 this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } ); 656 } 657 } 658 } 659 660 }); 661 662 $.extend( $.ui.slider, { 663 version: "1.8.16" 664 }); 665 666 }(jQuery)); 667