Revert "Export"
[framework/web/web-ui-fw.git] / libs / js / jquery-mobile-1.1.0 / js / jquery.mobile.forms.slider.js
1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: Slider form widget
3 //>>label: Slider
4 //>>group: Forms
5 //>>css: ../css/themes/default/jquery.mobile.theme.css, ../css/structure/jquery.mobile.forms.slider.css
6
7 define( [ "jquery", "./jquery.mobile.core", "./jquery.mobile.widget", "./jquery.mobile.forms.textinput", "./jquery.mobile.buttonMarkup" ], function( $ ) {
8 //>>excludeEnd("jqmBuildExclude");
9 ( function( $, undefined ) {
10
11 $.widget( "mobile.slider", $.mobile.widget, {
12         options: {
13                 theme: null,
14                 trackTheme: null,
15                 disabled: false,
16                 initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",
17                 mini: false
18         },
19
20         _create: function() {
21
22                 // TODO: Each of these should have comments explain what they're for
23                 var self = this,
24
25                         control = this.element,
26
27                         parentTheme = $.mobile.getInheritedTheme( control, "c" ),
28
29                         theme = this.options.theme || parentTheme,
30
31                         trackTheme = this.options.trackTheme || parentTheme,
32
33                         cType = control[ 0 ].nodeName.toLowerCase(),
34
35                         selectClass = ( cType == "select" ) ? "ui-slider-switch" : "",
36
37                         controlID = control.attr( "id" ),
38
39                         labelID = controlID + "-label",
40
41                         label = $( "[for='"+ controlID +"']" ).attr( "id", labelID ),
42
43                         val = function() {
44                                 return  cType == "input"  ? parseFloat( control.val() ) : control[0].selectedIndex;
45                         },
46
47                         min =  cType == "input" ? parseFloat( control.attr( "min" ) ) : 0,
48
49                         max =  cType == "input" ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1,
50
51                         step = window.parseFloat( control.attr( "step" ) || 1 ),
52
53                         inlineClass = ( this.options.inline || control.jqmData("inline") == true ) ? " ui-slider-inline" : "",
54
55                         miniClass = ( this.options.mini || control.jqmData("mini") ) ? " ui-slider-mini" : "",
56
57
58                         domHandle = document.createElement('a'),
59                         handle = $( domHandle ),
60                         domSlider = document.createElement('div'),
61                         slider = $( domSlider ),
62
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 );
67                         })() : false,
68
69                         options;
70
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);
76
77                 handle.buttonMarkup({ corners: true, theme: theme, shadow: true })
78                                 .attr({
79                                         "role": "slider",
80                                         "aria-valuemin": min,
81                                         "aria-valuemax": max,
82                                         "aria-valuenow": val(),
83                                         "aria-valuetext": val(),
84                                         "title": val(),
85                                         "aria-labelledby": labelID
86                                 });
87
88                 $.extend( this, {
89                         slider: slider,
90                         handle: handle,
91                         valuebg: valuebg,
92                         dragging: false,
93                         beforeStart: null,
94                         userModified: false,
95                         mouseMoved: false
96                 });
97
98                 if ( cType == "select" ) {
99                         var wrapper = document.createElement('div');
100                         wrapper.className = 'ui-slider-inneroffset';
101
102                         for(var j = 0,length = domSlider.childNodes.length;j < length;j++){
103                                 wrapper.appendChild(domSlider.childNodes[j]);
104                         }
105
106                         domSlider.appendChild(wrapper);
107
108                         // slider.wrapInner( "<div class='ui-slider-inneroffset'></div>" );
109
110                         // make the handle move with a smooth transition
111                         handle.addClass( "ui-slider-handle-snapping" );
112
113                         options = control.find( "option" );
114
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');
120
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 );
125                         }
126
127                         self._labels = $( ".ui-slider-label", slider );
128
129                 }
130
131                 label.addClass( "ui-slider" );
132
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 );
139                                 }
140                         })
141                         .keyup( function() { // necessary?
142                                 self.refresh( val(), true, true );
143                         })
144                         .blur( function() {
145                                 self.refresh( val(), true );
146                         });
147
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;
153
154                                 if ( cType === "select" ) {
155                                         // make the handle move in sync with the mouse
156                                         handle.removeClass( "ui-slider-handle-snapping" );
157                                 }
158
159                                 self.refresh( event );
160
161                                 // only after refresh() you can calculate self.userModified
162                                 self.userModified = self.beforeStart !== control[0].selectedIndex;
163                                 return false;
164                         }
165                 });
166
167                 slider.bind( "vmousedown", function( event ) {
168                         self.dragging = true;
169                         self.userModified = false;
170                         self.mouseMoved = false;
171
172                         if ( cType === "select" ) {
173                                 self.beforeStart = control[0].selectedIndex;
174                         }
175
176                         self.refresh( event );
177                         return false;
178                 })
179                 .bind( "vclick", false );
180
181                 slider.add( document )
182                         .bind( "vmouseup", function() {
183                                 if ( self.dragging ) {
184
185                                         self.dragging = false;
186
187                                         if ( cType === "select") {
188
189                                                 // make the handle move with a smooth transition
190                                                 handle.addClass( "ui-slider-handle-snapping" );
191
192                                                 if ( self.mouseMoved ) {
193
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 );
197                                                         }
198                                                         else {
199                                                                 self.refresh( self.beforeStart );
200                                                         }
201
202                                                 }
203                                                 else {
204                                                         // this is just a click, change the value
205                                                         self.refresh( self.beforeStart == 0 ? 1 : 0 );
206                                                 }
207
208                                         }
209
210                                         self.mouseMoved = false;
211
212                                         return false;
213                                 }
214                         });
215
216                 slider.insertAfter( control );
217
218                 // Only add focus class to toggle switch, sliders get it automatically from ui-btn
219                 if( cType == 'select' ) {
220                         this.handle.bind({
221                                 focus: function() {
222                                         slider.addClass( $.mobile.focusClass );
223                                 },
224
225                                 blur: function() {
226                                         slider.removeClass( $.mobile.focusClass );
227                                 }
228                         });
229                 }
230
231                 this.handle.bind({
232                         // NOTE force focus on handle
233                         vmousedown: function() {
234                                 $( this ).focus();
235                         },
236
237                         vclick: false,
238
239                         keydown: function( event ) {
240                                 var index = val();
241
242                                 if ( self.options.disabled ) {
243                                         return;
244                                 }
245
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();
257
258                                                 if ( !self._keySliding ) {
259                                                         self._keySliding = true;
260                                                         $( this ).addClass( "ui-state-active" );
261                                                 }
262                                                 break;
263                                 }
264
265                                 // move the slider according to the keypress
266                                 switch ( event.keyCode ) {
267                                         case $.mobile.keyCode.HOME:
268                                                 self.refresh( min );
269                                                 break;
270                                         case $.mobile.keyCode.END:
271                                                 self.refresh( max );
272                                                 break;
273                                         case $.mobile.keyCode.PAGE_UP:
274                                         case $.mobile.keyCode.UP:
275                                         case $.mobile.keyCode.RIGHT:
276                                                 self.refresh( index + step );
277                                                 break;
278                                         case $.mobile.keyCode.PAGE_DOWN:
279                                         case $.mobile.keyCode.DOWN:
280                                         case $.mobile.keyCode.LEFT:
281                                                 self.refresh( index - step );
282                                                 break;
283                                 }
284                         }, // remove active mark
285
286                         keyup: function( event ) {
287                                 if ( self._keySliding ) {
288                                         self._keySliding = false;
289                                         $( this ).removeClass( "ui-state-active" );
290                                 }
291                         }
292                         });
293
294                 this.refresh(undefined, undefined, true);
295         },
296
297         refresh: function( val, isfromControl, preventInputUpdate ) {
298
299                 if ( this.options.disabled || this.element.attr('disabled')) {
300                         this.disable();
301                 }
302
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;
308
309                 if ( typeof val === "object" ) {
310                         var data = val,
311                                 // a slight tolerance helped get to the ends of the slider
312                                 tol = 8;
313                         if ( !this.dragging ||
314                                         data.pageX < this.slider.offset().left - tol ||
315                                         data.pageX > this.slider.offset().left + this.slider.width() + tol ) {
316                                 return;
317                         }
318                         percent = Math.round( ( ( data.pageX - this.slider.offset().left ) / this.slider.width() ) * 100 );
319                 } else {
320                         if ( val == null ) {
321                                 val = cType === "input" ? parseFloat( control.val() || 0 ) : control[0].selectedIndex;
322                         }
323                         percent = ( parseFloat( val ) - min ) / ( max - min ) * 100;
324                 }
325
326                 if ( isNaN( percent ) ) {
327                         return;
328                 }
329
330                 if ( percent < 0 ) {
331                         percent = 0;
332                 }
333
334                 if ( percent > 100 ) {
335                         percent = 100;
336                 }
337
338                 var newval = ( percent / 100 ) * ( max - min ) + min;
339
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;
343
344                 if ( Math.abs( valModStep ) * 2 >= step ) {
345                         alignValue += ( valModStep > 0 ) ? step : ( -step );
346                 }
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) );
350
351                 if ( newval < min ) {
352                         newval = min;
353                 }
354
355                 if ( newval > max ) {
356                         newval = max;
357                 }
358
359                 this.handle.css( "left", percent + "%" );
360                 this.handle.attr( {
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()
364                         });
365                 this.valuebg && this.valuebg.css( "width", percent + "%" );
366
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 );
372
373                         this._labels.each(function(){
374                                 var ab = $(this).is( ".ui-slider-label-a" );
375                                 $( this ).width( ( ab ? aPercent : bPercent  ) + "%" );
376                         });
377                 }
378
379                 if ( !preventInputUpdate ) {
380                         var valueChanged = false;
381
382                         // update control"s value
383                         if ( cType === "input" ) {
384                                 valueChanged = control.val() !== newval;
385                                 control.val( newval );
386                         } else {
387                                 valueChanged = control[ 0 ].selectedIndex !== newval;
388                                 control[ 0 ].selectedIndex = newval;
389                         }
390                         if ( !isfromControl && valueChanged ) {
391                                 control.trigger( "change" );
392                         }
393                 }
394         },
395
396         enable: function() {
397                 this.element.attr( "disabled", false );
398                 this.slider.removeClass( "ui-disabled" ).attr( "aria-disabled", false );
399                 return this._setOption( "disabled", false );
400         },
401
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 );
406         }
407
408 });
409
410 //auto self-init widgets
411 $( document ).bind( "pagecreate create", function( e ){
412         $.mobile.slider.prototype.enhanceWithin( e.target, true );
413 });
414
415 })( jQuery );
416 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
417 });
418 //>>excludeEnd("jqmBuildExclude");