1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: Slider form widget
5 //>>css: ../css/themes/default/jquery.mobile.theme.css, ../css/structure/jquery.mobile.forms.slider.css
7 define( [ "jquery", "./jquery.mobile.core", "./jquery.mobile.widget", "./jquery.mobile.forms.textinput", "./jquery.mobile.buttonMarkup" ], function( $ ) {
8 //>>excludeEnd("jqmBuildExclude");
9 ( function( $, undefined ) {
11 $.widget( "mobile.slider", $.mobile.widget, {
16 initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",
22 // TODO: Each of these should have comments explain what they're for
25 control = this.element,
27 parentTheme = $.mobile.getInheritedTheme( control, "c" ),
29 theme = this.options.theme || parentTheme,
31 trackTheme = this.options.trackTheme || parentTheme,
33 cType = control[ 0 ].nodeName.toLowerCase(),
35 selectClass = ( cType == "select" ) ? "ui-slider-switch" : "",
37 controlID = control.attr( "id" ),
39 labelID = controlID + "-label",
41 label = $( "[for='"+ controlID +"']" ).attr( "id", labelID ),
44 return cType == "input" ? parseFloat( control.val() ) : control[0].selectedIndex;
47 min = cType == "input" ? parseFloat( control.attr( "min" ) ) : 0,
49 max = cType == "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1,
51 step = window.parseFloat( control.attr( "step" ) || 1 ),
53 inlineClass = ( this.options.inline || control.jqmData("inline") == true ) ? " ui-slider-inline" : "",
55 miniClass = ( this.options.mini || control.jqmData("mini") ) ? " ui-slider-mini" : "",
58 domHandle = document.createElement('a'),
59 handle = $( domHandle ),
60 domSlider = document.createElement('div'),
61 slider = $( domSlider ),
63 valuebg = control.jqmData("highlight") && cType != "select" ? (function() {
64 var bg = document.createElement('div');
65 bg.className = 'ui-slider-bg ui-btn-active ui-btn-corner-all';
66 return $( bg ).prependTo( slider );
71 domHandle.setAttribute( 'href', "#" );
72 domSlider.setAttribute('role','application');
73 domSlider.className = ['ui-slider ',selectClass," ui-btn-down-",trackTheme,' ui-btn-corner-all', inlineClass, miniClass].join("");
74 domHandle.className = 'ui-slider-handle';
75 domSlider.appendChild(domHandle);
77 handle.buttonMarkup({ corners: true, theme: theme, shadow: true })
82 "aria-valuenow": val(),
83 "aria-valuetext": val(),
85 "aria-labelledby": labelID
98 if ( cType == "select" ) {
99 var wrapper = document.createElement('div');
100 wrapper.className = 'ui-slider-inneroffset';
102 for(var j = 0,length = domSlider.childNodes.length;j < length;j++){
103 wrapper.appendChild(domSlider.childNodes[j]);
106 domSlider.appendChild(wrapper);
108 // slider.wrapInner( "<div class='ui-slider-inneroffset'></div>" );
110 // make the handle move with a smooth transition
111 handle.addClass( "ui-slider-handle-snapping" );
113 options = control.find( "option" );
115 for(var i = 0, optionsCount = options.length; i < optionsCount; i++){
116 var side = !i ? "b":"a",
117 sliderTheme = !i ? " ui-btn-down-" + trackTheme :( " " + $.mobile.activeBtnClass ),
118 sliderLabel = document.createElement('div'),
119 sliderImg = document.createElement('span');
121 sliderImg.className = ['ui-slider-label ui-slider-label-',side,sliderTheme," ui-btn-corner-all"].join("");
122 sliderImg.setAttribute('role','img');
123 sliderImg.appendChild(document.createTextNode(options[i].innerHTML));
124 $(sliderImg).prependTo( slider );
127 self._labels = $( ".ui-slider-label", slider );
131 label.addClass( "ui-slider" );
133 // monitor the input for updated values
134 control.addClass( cType === "input" ? "ui-slider-input" : "ui-slider-switch" )
135 .change( function() {
136 // if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again
137 if (!self.mouseMoved) {
138 self.refresh( val(), true );
141 .keyup( function() { // necessary?
142 self.refresh( val(), true, true );
145 self.refresh( val(), true );
148 // prevent screen drag when slider activated
149 $( document ).bind( "vmousemove", function( event ) {
150 if ( self.dragging ) {
151 // self.mouseMoved must be updated before refresh() because it will be used in the control "change" event
152 self.mouseMoved = true;
154 if ( cType === "select" ) {
155 // make the handle move in sync with the mouse
156 handle.removeClass( "ui-slider-handle-snapping" );
159 self.refresh( event );
161 // only after refresh() you can calculate self.userModified
162 self.userModified = self.beforeStart !== control[0].selectedIndex;
167 slider.bind( "vmousedown", function( event ) {
168 self.dragging = true;
169 self.userModified = false;
170 self.mouseMoved = false;
172 if ( cType === "select" ) {
173 self.beforeStart = control[0].selectedIndex;
176 self.refresh( event );
179 .bind( "vclick", false );
181 slider.add( document )
182 .bind( "vmouseup", function() {
183 if ( self.dragging ) {
185 self.dragging = false;
187 if ( cType === "select") {
189 // make the handle move with a smooth transition
190 handle.addClass( "ui-slider-handle-snapping" );
192 if ( self.mouseMoved ) {
194 // this is a drag, change the value only if user dragged enough
195 if ( self.userModified ) {
196 self.refresh( self.beforeStart == 0 ? 1 : 0 );
199 self.refresh( self.beforeStart );
204 // this is just a click, change the value
205 self.refresh( self.beforeStart == 0 ? 1 : 0 );
210 self.mouseMoved = false;
216 slider.insertAfter( control );
218 // Only add focus class to toggle switch, sliders get it automatically from ui-btn
219 if( cType == 'select' ) {
222 slider.addClass( $.mobile.focusClass );
226 slider.removeClass( $.mobile.focusClass );
232 // NOTE force focus on handle
233 vmousedown: function() {
239 keydown: function( event ) {
242 if ( self.options.disabled ) {
246 // In all cases prevent the default and mark the handle as active
247 switch ( event.keyCode ) {
248 case $.mobile.keyCode.HOME:
249 case $.mobile.keyCode.END:
250 case $.mobile.keyCode.PAGE_UP:
251 case $.mobile.keyCode.PAGE_DOWN:
252 case $.mobile.keyCode.UP:
253 case $.mobile.keyCode.RIGHT:
254 case $.mobile.keyCode.DOWN:
255 case $.mobile.keyCode.LEFT:
256 event.preventDefault();
258 if ( !self._keySliding ) {
259 self._keySliding = true;
260 $( this ).addClass( "ui-state-active" );
265 // move the slider according to the keypress
266 switch ( event.keyCode ) {
267 case $.mobile.keyCode.HOME:
270 case $.mobile.keyCode.END:
273 case $.mobile.keyCode.PAGE_UP:
274 case $.mobile.keyCode.UP:
275 case $.mobile.keyCode.RIGHT:
276 self.refresh( index + step );
278 case $.mobile.keyCode.PAGE_DOWN:
279 case $.mobile.keyCode.DOWN:
280 case $.mobile.keyCode.LEFT:
281 self.refresh( index - step );
284 }, // remove active mark
286 keyup: function( event ) {
287 if ( self._keySliding ) {
288 self._keySliding = false;
289 $( this ).removeClass( "ui-state-active" );
294 this.refresh(undefined, undefined, true);
297 refresh: function( val, isfromControl, preventInputUpdate ) {
299 if ( this.options.disabled || this.element.attr('disabled')) {
303 var control = this.element, percent,
304 cType = control[0].nodeName.toLowerCase(),
305 min = cType === "input" ? parseFloat( control.attr( "min" ) ) : 0,
306 max = cType === "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length - 1,
307 step = (cType === "input" && parseFloat( control.attr( "step" ) ) > 0) ? parseFloat(control.attr("step")) : 1;
309 if ( typeof val === "object" ) {
311 // a slight tolerance helped get to the ends of the slider
313 if ( !this.dragging ||
314 data.pageX < this.slider.offset().left - tol ||
315 data.pageX > this.slider.offset().left + this.slider.width() + tol ) {
318 percent = Math.round( ( ( data.pageX - this.slider.offset().left ) / this.slider.width() ) * 100 );
321 val = cType === "input" ? parseFloat( control.val() || 0 ) : control[0].selectedIndex;
323 percent = ( parseFloat( val ) - min ) / ( max - min ) * 100;
326 if ( isNaN( percent ) ) {
334 if ( percent > 100 ) {
338 var newval = ( percent / 100 ) * ( max - min ) + min;
340 //from jQuery UI slider, the following source will round to the nearest step
341 var valModStep = ( newval - min ) % step;
342 var alignValue = newval - valModStep;
344 if ( Math.abs( valModStep ) * 2 >= step ) {
345 alignValue += ( valModStep > 0 ) ? step : ( -step );
347 // Since JavaScript has problems with large floats, round
348 // the final value to 5 digits after the decimal point (see jQueryUI: #4124)
349 newval = parseFloat( alignValue.toFixed(5) );
351 if ( newval < min ) {
355 if ( newval > max ) {
359 this.handle.css( "left", percent + "%" );
361 "aria-valuenow": cType === "input" ? newval : control.find( "option" ).eq( newval ).attr( "value" ),
362 "aria-valuetext": cType === "input" ? newval : control.find( "option" ).eq( newval ).getEncodedText(),
363 title: cType === "input" ? newval : control.find( "option" ).eq( newval ).getEncodedText()
365 this.valuebg && this.valuebg.css( "width", percent + "%" );
367 // drag the label widths
368 if ( this._labels ) {
369 var handlePercent = this.handle.width() / this.slider.width() * 100,
370 aPercent = percent && handlePercent + ( 100 - handlePercent ) * percent / 100,
371 bPercent = percent === 100 ? 0 : Math.min( handlePercent + 100 - aPercent, 100 );
373 this._labels.each(function(){
374 var ab = $(this).is( ".ui-slider-label-a" );
375 $( this ).width( ( ab ? aPercent : bPercent ) + "%" );
379 if ( !preventInputUpdate ) {
380 var valueChanged = false;
382 // update control"s value
383 if ( cType === "input" ) {
384 valueChanged = control.val() !== newval;
385 control.val( newval );
387 valueChanged = control[ 0 ].selectedIndex !== newval;
388 control[ 0 ].selectedIndex = newval;
390 if ( !isfromControl && valueChanged ) {
391 control.trigger( "change" );
397 this.element.attr( "disabled", false );
398 this.slider.removeClass( "ui-disabled" ).attr( "aria-disabled", false );
399 return this._setOption( "disabled", false );
402 disable: function() {
403 this.element.attr( "disabled", true );
404 this.slider.addClass( "ui-disabled" ).attr( "aria-disabled", true );
405 return this._setOption( "disabled", true );
410 //auto self-init widgets
411 $( document ).bind( "pagecreate create", function( e ){
412 $.mobile.slider.prototype.enhanceWithin( e.target, true );
416 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
418 //>>excludeEnd("jqmBuildExclude");