| //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| //>>description: Slider form widget |
| //>>label: Slider |
| //>>group: Forms |
| //>>css.structure: ../css/structure/jquery.mobile.forms.slider.css |
| //>>css.theme: ../css/themes/default/jquery.mobile.theme.css |
| |
| define( [ "jquery", "../../jquery.mobile.core", "../../jquery.mobile.widget", "./textinput", "../../jquery.mobile.buttonMarkup" ], function( jQuery ) { |
| //>>excludeEnd("jqmBuildExclude"); |
| (function( $, undefined ) { |
| |
| $.widget( "mobile.slider", $.mobile.widget, { |
| widgetEventPrefix: "slide", |
| |
| options: { |
| theme: null, |
| trackTheme: null, |
| disabled: false, |
| initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')", |
| mini: false |
| }, |
| |
| _create: function() { |
| |
| // TODO: Each of these should have comments explain what they're for |
| var self = this, |
| |
| control = this.element, |
| |
| parentTheme = $.mobile.getInheritedTheme( control, "c" ), |
| |
| theme = this.options.theme || parentTheme, |
| |
| trackTheme = this.options.trackTheme || parentTheme, |
| |
| cType = control[ 0 ].nodeName.toLowerCase(), |
| |
| selectClass = ( cType === "select" ) ? "ui-slider-switch" : "", |
| |
| controlID = control.attr( "id" ), |
| |
| $label = $( "[for='" + controlID + "']" ), |
| |
| labelID = $label.attr( "id" ) || controlID + "-label", |
| |
| label = $label.attr( "id", labelID ), |
| |
| val = function() { |
| return cType === "input" ? parseFloat( control.val() ) : control[0].selectedIndex; |
| }, |
| |
| min = cType === "input" ? parseFloat( control.attr( "min" ) ) : 0, |
| |
| max = cType === "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1, |
| |
| step = window.parseFloat( control.attr( "step" ) || 1 ), |
| |
| inlineClass = ( this.options.inline || $.mobile.getAttrFixed( control[0], "data-" + $.mobile.ns + "inline" ) === true ) ? " ui-slider-inline" : "", |
| |
| miniClass = ( this.options.mini || $.mobile.getAttrFixed( control[0], "data-" + $.mobile.ns + "min" ) ) ? " ui-slider-mini" : "", |
| |
| |
| domHandle = document.createElement( 'a' ), |
| handle = $( domHandle ), |
| domSlider = document.createElement( 'div' ), |
| slider = $( domSlider ), |
| |
| valuebg = $.mobile.getAttrFixed( control[0], "data-" + $.mobile.ns + "highlight" ) !== false && cType !== "select" ? (function() { |
| var bg = document.createElement('div'); |
| bg.className = 'ui-slider-bg ' + $.mobile.activeBtnClass + ' ui-btn-corner-all'; |
| return $( bg ).prependTo( slider ); |
| })() : false, |
| |
| options; |
| |
| this._type = cType; |
| |
| domHandle.setAttribute( 'href', "#" ); |
| domSlider.setAttribute('role','application'); |
| domSlider.className = ['ui-slider ',selectClass," ui-btn-down-",trackTheme,' ui-btn-corner-all', inlineClass, miniClass].join( "" ); |
| domHandle.className = 'ui-slider-handle'; |
| domSlider.appendChild( domHandle ); |
| if ( $( control ).find( "option" ).length && $( control ).find( "option" ).text() === "" ) { |
| $( domSlider ).addClass( "ui-toggle-switch" ); |
| } |
| if( val() === "1" ) { |
| $( domHandle ).addClass( "ui-toggle-on" ); |
| } else { |
| $( domHandle ).addClass( "ui-toggle-off" ); |
| } |
| handle.buttonMarkup({ corners: true, theme: theme, shadow: true }) |
| .attr({ |
| "role": "slider", |
| "aria-valuemin": min, |
| "aria-valuemax": max, |
| "aria-valuenow": val(), |
| "aria-valuetext": val(), |
| "title": val(), |
| "aria-labelledby": labelID |
| }); |
| |
| $.extend( this, { |
| slider: slider, |
| handle: handle, |
| valuebg: valuebg, |
| dragging: false, |
| beforeStart: null, |
| userModified: false, |
| mouseMoved: false |
| }); |
| |
| if ( cType === "select" ) { |
| var wrapper = document.createElement('div'); |
| wrapper.className = 'ui-slider-inneroffset'; |
| |
| for ( var j = 0,length = domSlider.childNodes.length;j < length;j++ ) { |
| wrapper.appendChild( domSlider.childNodes[j] ); |
| } |
| |
| domSlider.appendChild( wrapper ); |
| |
| // slider.wrapInner( "<div class='ui-slider-inneroffset'></div>" ); |
| |
| // make the handle move with a smooth transition |
| handle.addClass( "ui-slider-handle-snapping" ); |
| |
| options = control.find( "option" ); |
| |
| for ( var i = 0, optionsCount = options.length; i < optionsCount; i++ ) { |
| var side = !i ? "b" : "a", |
| sliderTheme = !i ? " ui-btn-down-" + trackTheme : ( " " + $.mobile.activeBtnClass ), |
| sliderLabel = document.createElement( 'div' ), |
| sliderImg = document.createElement( 'span' ); |
| |
| sliderImg.className = ['ui-slider-label ui-slider-label-',side,sliderTheme," ui-btn-corner-all"].join( "" ); |
| sliderImg.setAttribute('role','img'); |
| sliderImg.appendChild( document.createTextNode( options[i].innerHTML ) ); |
| $( sliderImg ).html() ? $( sliderImg ).html( $( sliderImg ).text() ) : $( sliderImg ).html(); |
| $(sliderImg).prependTo( slider ); |
| } |
| |
| self._labels = $( ".ui-slider-label", slider ); |
| |
| } |
| |
| label.addClass( "ui-slider" ); |
| |
| // monitor the input for updated values |
| control.addClass( cType === "input" ? "ui-slider-input" : "ui-slider-switch" ) |
| .change(function() { |
| // if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again |
| if ( !self.mouseMoved ) { |
| self.refresh( val(), true ); |
| } |
| }) |
| .keyup(function() { // necessary? |
| self.refresh( val(), true, true ); |
| }) |
| .blur(function() { |
| self.refresh( val(), true ); |
| }); |
| |
| this._preventDocumentDrag = function( event ) { |
| // NOTE: we don't do this in refresh because we still want to |
| // support programmatic alteration of disabled inputs |
| var et = $(event.target); |
| if ( self.dragging && !self.options.disabled && ( ( et.parents( ".ui-slider" ).is( ".ui-toggle-switch" ) && et.parents( ".ui-slider-handle" ).is( ".ui-btn-hover-s" ) ) || ( !$( self.element ).siblings( ".ui-slider" ).is( ".ui-toggle-switch"))) ) { |
| |
| // self.mouseMoved must be updated before refresh() because it will be used in the control "change" event |
| self.mouseMoved = true; |
| |
| if ( cType === "select" ) { |
| // make the handle move in sync with the mouse |
| handle.removeClass( "ui-slider-handle-snapping" ); |
| } |
| |
| self.refresh( event ); |
| |
| // only after refresh() you can calculate self.userModified |
| self.userModified = self.beforeStart !== control[0].selectedIndex; |
| return false; |
| } |
| } |
| |
| this._on( $.mobile.$document, { "vmousemove": this._preventDocumentDrag }); |
| |
| // it appears the clicking the up and down buttons in chrome on |
| // range/number inputs doesn't trigger a change until the field is |
| // blurred. Here we check thif the value has changed and refresh |
| control.bind( "vmouseup", $.proxy( self._checkedRefresh, self)); |
| |
| slider.bind( "vmousedown", function( event ) { |
| // NOTE: we don't do this in refresh because we still want to |
| // support programmatic alteration of disabled inputs |
| if ( self.options.disabled ) { |
| return false; |
| } |
| |
| self.dragging = true; |
| self.userModified = false; |
| self.mouseMoved = false; |
| |
| if ( cType === "select" ) { |
| self.beforeStart = control[0].selectedIndex; |
| } |
| |
| self.refresh( event ); |
| self._trigger( "start" ); |
| return false; |
| }) |
| .bind( "vclick", false ); |
| |
| this._sliderMouseUp = function() { |
| if ( self.dragging ) { |
| self.dragging = false; |
| |
| if ( cType === "select") { |
| // make the handle move with a smooth transition |
| handle.addClass( "ui-slider-handle-snapping" ); |
| |
| if ( self.mouseMoved ) { |
| // this is a drag, change the value only if user dragged enough |
| if ( self.userModified ) { |
| self.refresh( self.beforeStart === 0 ? 1 : 0 ); |
| } |
| else { |
| self.refresh( self.beforeStart ); |
| } |
| } |
| else { |
| // this is just a click, change the value |
| self.refresh( self.beforeStart === 0 ? 1 : 0 ); |
| } |
| } |
| |
| self.mouseMoved = false; |
| self._trigger( "stop" ); |
| return false; |
| } |
| }; |
| |
| this._on( slider.add( document ), { "vmouseup": this._sliderMouseUp }); |
| slider.insertAfter( control ); |
| |
| // Only add focus class to toggle switch, sliders get it automatically from ui-btn |
| if ( cType === 'select' ) { |
| this.handle.bind({ |
| focus: function() { |
| slider.addClass( $.mobile.focusClass ); |
| }, |
| |
| blur: function() { |
| slider.removeClass( $.mobile.focusClass ); |
| } |
| }); |
| } |
| |
| this.handle.bind({ |
| // NOTE force focus on handle |
| vmousedown: function() { |
| $( this ).focus(); |
| }, |
| |
| vclick: false, |
| |
| keydown: function( event ) { |
| var index = val(); |
| |
| if ( self.options.disabled ) { |
| return; |
| } |
| |
| // In all cases prevent the default and mark the handle as active |
| switch ( event.keyCode ) { |
| case $.mobile.keyCode.HOME: |
| case $.mobile.keyCode.END: |
| case $.mobile.keyCode.PAGE_UP: |
| case $.mobile.keyCode.PAGE_DOWN: |
| case $.mobile.keyCode.UP: |
| case $.mobile.keyCode.RIGHT: |
| case $.mobile.keyCode.DOWN: |
| case $.mobile.keyCode.LEFT: |
| event.preventDefault(); |
| |
| if ( !self._keySliding ) { |
| self._keySliding = true; |
| $( this ).addClass( "ui-state-active" ); |
| } |
| break; |
| } |
| |
| // move the slider according to the keypress |
| switch ( event.keyCode ) { |
| case $.mobile.keyCode.HOME: |
| self.refresh( min ); |
| break; |
| case $.mobile.keyCode.END: |
| self.refresh( max ); |
| break; |
| case $.mobile.keyCode.PAGE_UP: |
| case $.mobile.keyCode.UP: |
| case $.mobile.keyCode.RIGHT: |
| self.refresh( index + step ); |
| break; |
| case $.mobile.keyCode.PAGE_DOWN: |
| case $.mobile.keyCode.DOWN: |
| case $.mobile.keyCode.LEFT: |
| self.refresh( index - step ); |
| break; |
| } |
| }, // remove active mark |
| |
| keyup: function( event ) { |
| if ( self._keySliding ) { |
| self._keySliding = false; |
| $( this ).removeClass( "ui-state-active" ); |
| } |
| } |
| }); |
| |
| this.refresh( undefined, undefined, true ); |
| }, |
| |
| _checkedRefresh: function() { |
| if( this.value != this._value() ){ |
| this.refresh( this._value() ); |
| } |
| }, |
| |
| _value: function() { |
| return this._type === "input" ? |
| parseFloat( this.element.val() ) : this.element[0].selectedIndex; |
| }, |
| |
| refresh: function( val, isfromControl, preventInputUpdate ) { |
| |
| // NOTE: we don't return here because we want to support programmatic |
| // alteration of the input value, which should still update the slider |
| if ( this.options.disabled || this.element.attr('disabled')) { |
| this.disable(); |
| } |
| |
| // set the stored value for comparison later |
| this.value = this._value(); |
| |
| var control = this.element, percent, |
| cType = control[0].nodeName.toLowerCase(), |
| min = cType === "input" ? parseFloat( control.attr( "min" ) ) : 0, |
| max = cType === "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length - 1, |
| step = ( cType === "input" && parseFloat( control.attr( "step" ) ) > 0 ) ? parseFloat( control.attr( "step" ) ) : 1; |
| |
| if ( typeof val === "object" ) { |
| var data = val, |
| // a slight tolerance helped get to the ends of the slider |
| tol = 8; |
| if ( !this.dragging || |
| data.pageX < this.slider.offset().left - tol || |
| data.pageX > this.slider.offset().left + this.slider.width() + tol ) { |
| return; |
| } |
| percent = Math.round( ( ( data.pageX - this.slider.offset().left ) / this.slider.width() ) * 100 ); |
| } else { |
| if ( val == null ) { |
| val = cType === "input" ? parseFloat( control.val() || 0 ) : control[0].selectedIndex; |
| } |
| percent = ( parseFloat( val ) - min ) / ( max - min ) * 100; |
| } |
| |
| if ( isNaN( percent ) ) { |
| return; |
| } |
| |
| if ( percent < 0 ) { |
| percent = 0; |
| } |
| |
| if ( percent > 100 ) { |
| percent = 100; |
| } |
| |
| var newval = ( percent / 100 ) * ( max - min ) + min; |
| |
| //from jQuery UI slider, the following source will round to the nearest step |
| var valModStep = ( newval - min ) % step; |
| var alignValue = newval - valModStep; |
| |
| if ( Math.abs( valModStep ) * 2 >= step ) { |
| alignValue += ( valModStep > 0 ) ? step : ( -step ); |
| } |
| // Since JavaScript has problems with large floats, round |
| // the final value to 5 digits after the decimal point (see jQueryUI: #4124) |
| newval = parseFloat( alignValue.toFixed(5) ); |
| |
| if ( newval < min ) { |
| newval = min; |
| } |
| |
| if ( newval > max ) { |
| newval = max; |
| } |
| |
| this.handle.css( "left", percent + "%" ); |
| this.handle.attr( { |
| "aria-valuenow": cType === "input" ? newval : control.find( "option" ).eq( newval ).attr( "value" ), |
| "aria-valuetext": cType === "input" ? newval : control.find( "option" ).eq( newval ).getEncodedText(), |
| title: cType === "input" ? newval : control.find( "option" ).eq( newval ).getEncodedText() |
| }); |
| |
| if( percent === 100 && this.handle.hasClass( "ui-slider-handle-snapping" ) ) { |
| this.handle.removeClass( "ui-toggle-off" ); |
| this.handle.addClass( "ui-toggle-on" ); |
| } else if ( percent === 0 && this.handle.hasClass( "ui-slider-handle-snapping" ) ) { |
| this.handle.removeClass( "ui-toggle-on" ); |
| this.handle.addClass( "ui-toggle-off" ); |
| } |
| |
| if ( this.valuebg ) { |
| this.valuebg.css( "width", percent + "%" ); |
| } |
| |
| // drag the label widths |
| if ( this._labels ) { |
| var handlePercent = this.handle.width() / this.slider.width() * 100, |
| aPercent = percent && handlePercent + ( 100 - handlePercent ) * percent / 100, |
| bPercent = percent === 100 ? 0 : Math.min( handlePercent + 100 - aPercent, 100 ); |
| |
| this._labels.each(function() { |
| var ab = $( this ).is( ".ui-slider-label-a" ); |
| $( this ).width( ( ab ? aPercent : bPercent ) + "%" ); |
| }); |
| } |
| |
| if ( !preventInputUpdate ) { |
| var valueChanged = false; |
| |
| // update control"s value |
| if ( cType === "input" ) { |
| valueChanged = control.val() !== newval; |
| control.val( newval ); |
| } else { |
| valueChanged = control[ 0 ].selectedIndex !== newval; |
| control[ 0 ].selectedIndex = newval; |
| } |
| if ( !isfromControl && valueChanged ) { |
| control.trigger( "change" ); |
| } |
| } |
| }, |
| |
| enable: function() { |
| this.element.attr( "disabled", false ); |
| this.slider.removeClass( "ui-disabled" ).attr( "aria-disabled", false ); |
| return this._setOption( "disabled", false ); |
| }, |
| |
| disable: function() { |
| this.element.attr( "disabled", true ); |
| this.slider.addClass( "ui-disabled" ).attr( "aria-disabled", true ); |
| return this._setOption( "disabled", true ); |
| } |
| |
| }); |
| |
| //auto self-init widgets |
| $.mobile.$document.bind( "pagecreate create", function( e ) { |
| $.mobile.slider.prototype.enhanceWithin( e.target, true ); |
| }); |
| |
| })( jQuery ); |
| //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); |
| }); |
| //>>excludeEnd("jqmBuildExclude"); |