//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); //>>description: Slider form widget //>>label: Slider define( [ "jquery", "./jquery.mobile.core", "./jquery.mobile.widget", "./jquery.mobile.forms.textinput", "./jquery.mobile.buttonMarkup" ], function( $ ) { //>>excludeEnd("jqmBuildExclude"); ( function( $, undefined ) { $.widget( "mobile.slider", $.mobile.widget, { options: { theme: null, trackTheme: null, disabled: false, initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')" }, _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" ), labelID = controlID + "-label", label = $( "[for='"+ controlID +"']" ).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 ), slider = $( "
" ), handle = $( "" ) .appendTo( slider ) .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 }), options; $.extend( this, { slider: slider, handle: handle, dragging: false, beforeStart: null, userModified: false, mouseMoved: false }); if ( cType == "select" ) { slider.wrapInner( "" ); // make the handle move with a smooth transition handle.addClass( "ui-slider-handle-snapping" ); options = control.find( "option" ); control.find( "option" ).each(function( i ) { var side = !i ? "b":"a", corners = !i ? "right" :"left", theme = !i ? " ui-btn-down-" + trackTheme :( " " + $.mobile.activeBtnClass ); $( "" ) .prependTo( slider ); $( "" ) .prependTo( handle ); }); } 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 ); }); // prevent screen drag when slider activated $( document ).bind( "vmousemove", function( event ) { if ( self.dragging ) { // 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; } }); slider.bind( "vmousedown", function( event ) { self.dragging = true; self.userModified = false; self.mouseMoved = false; if ( cType === "select" ) { self.beforeStart = control[0].selectedIndex; } self.refresh( event ); return false; }); slider.add( document ) .bind( "vmouseup", 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; return false; } }); 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); }, refresh: function( val, isfromControl, preventInputUpdate ) { if ( this.options.disabled || this.element.attr('disabled')) { this.disable(); } 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() }); // add/remove classes for flip toggle switch if ( cType === "select" ) { if ( newval === 0 ) { this.slider.addClass( "ui-slider-switch-a" ) .removeClass( "ui-slider-switch-b" ); } else { this.slider.addClass( "ui-slider-switch-b" ) .removeClass( "ui-slider-switch-a" ); } } 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 $( document ).bind( "pagecreate create", function( e ){ $.mobile.slider.prototype.enhanceWithin( e.target ); }); })( jQuery ); //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude); }); //>>excludeEnd("jqmBuildExclude");