]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/slider/Widget.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / slider / Widget.js
CommitLineData
6527f429
DM
1/**\r
2 * A Widget-based implementation of a slider.\r
3 * @since 5.0.0\r
4 */\r
5Ext.define('Ext.slider.Widget', {\r
6 extend: 'Ext.Widget',\r
7 alias: 'widget.sliderwidget',\r
8\r
9 // Required to pull in the styles\r
10 requires: [\r
11 'Ext.slider.Multi'\r
12 ], \r
13\r
14 cachedConfig: {\r
15 /**\r
16 * @cfg {Boolean} vertical\r
17 * Orients the slider vertically rather than horizontally.\r
18 */\r
19 vertical: false\r
20 },\r
21\r
22 config: {\r
23 /**\r
24 * @cfg {Boolean} clickToChange\r
25 * Determines whether or not clicking on the Slider axis will change the slider.\r
26 */\r
27 clickToChange: true,\r
28\r
29 ui: 'widget',\r
30\r
31 /**\r
32 * @cfg {Number/Number[]} value\r
33 * One more values for the position of the slider's thumb(s).\r
34 */\r
35 value: 0,\r
36 \r
37 /**\r
38 * @cfg {Number} minValue\r
39 * The minimum value for any slider thumb.\r
40 */\r
41 minValue: 0,\r
42 \r
43 /**\r
44 * @cfg {Number} maxValue\r
45 * The maximum value for any slider thumb.\r
46 */\r
47 maxValue: 100,\r
48\r
49 /**\r
50 * @cfg {Boolean} [publishOnComplete=true]\r
51 * This controls when the value of the slider is published to the `ViewModel`. By\r
52 * default this is done only when the thumb is released (the change is complete). To\r
53 * cause this to happen on every change of the thumb position, specify `false`. This\r
54 * setting is `true` by default for improved performance on slower devices (such as\r
55 * older browsers or tablets).\r
56 */\r
57 publishOnComplete: true,\r
58\r
59 /**\r
60 * @cfg {Object} twoWayBindable\r
61 * This object is a map of config property names holding a `true` if changes to\r
62 * that config should written back to its binding. Most commonly this is used to\r
63 * indicate that the `value` config should be monitored and changes written back\r
64 * to the bound value.\r
65 */\r
66 twoWayBindable: {\r
67 value: 1\r
68 }\r
69 },\r
70\r
71 decimalPrecision: 0,\r
72\r
73 defaultBindProperty: 'value',\r
74\r
75 element: {\r
76 reference: 'element',\r
77 cls: Ext.baseCSSPrefix + 'slider',\r
78 listeners: {\r
79 mousedown: 'onMouseDown',\r
80 dragstart: 'cancelDrag',\r
81 drag: 'cancelDrag',\r
82 dragend: 'cancelDrag'\r
83 },\r
84 children: [{\r
85 reference: 'endEl',\r
86 cls: Ext.baseCSSPrefix + 'slider-end',\r
87 children: [{\r
88 reference: 'innerEl',\r
89 cls: Ext.baseCSSPrefix + 'slider-inner'\r
90 }]\r
91 }]\r
92 },\r
93\r
94 thumbCls: Ext.baseCSSPrefix + 'slider-thumb',\r
95\r
96 horizontalProp: 'left',\r
97\r
98 // This property is set to false onMouseDown and deleted onMouseUp. It is used only\r
99 // by applyValue when it passes the animate parameter to setThumbValue.\r
100 animateOnSetValue: undefined,\r
101\r
102 applyValue: function(value) {\r
103 var me = this,\r
104 animate = me.animateOnSetValue,\r
105 i, len;\r
106\r
107 if (Ext.isArray(value)) {\r
108 value = Ext.Array.from(value);\r
109 for (i = 0, len = value.length; i < len; ++i) {\r
110 me.setThumbValue(i, value[i] = me.normalizeValue(value[i]), animate, true);\r
111 }\r
112 } else {\r
113 value = me.normalizeValue(value);\r
114 me.setThumbValue(0, value, animate, true);\r
115 }\r
116 return value;\r
117 },\r
118\r
119 updateVertical: function(vertical, oldVertical) {\r
120 this.element.removeCls(Ext.baseCSSPrefix + 'slider-' + (oldVertical ? 'vert' : 'horz'));\r
121 this.element.addCls( Ext.baseCSSPrefix + 'slider-' + (vertical ? 'vert' : 'horz'));\r
122 },\r
123\r
124 updateHeight: function(height, oldHeight) {\r
125 this.callParent([height, oldHeight]);\r
126 this.endEl.dom.style.height = this.innerEl.dom.style.height = '100%';\r
127 },\r
128\r
129 cancelDrag: function(e) {\r
130 // prevent the touch scroller from scrolling when the slider is being dragged\r
131 e.stopPropagation();\r
132 },\r
133\r
134 getThumb: function(ordinal) {\r
135 var me = this,\r
136 thumbConfig,\r
137 result = (me.thumbs || (me.thumbs = []))[ordinal];\r
138 \r
139 if (!result) {\r
140 thumbConfig = {\r
141 cls: me.thumbCls,\r
142 style: {}\r
143 };\r
144 thumbConfig['data-thumbIndex'] = ordinal;\r
145 result = me.thumbs[ordinal] = me.innerEl.createChild(thumbConfig);\r
146 }\r
147 return result;\r
148 },\r
149\r
150 getThumbPositionStyle: function() {\r
151 return this.getVertical() ? 'bottom' : (this.rtl && Ext.rtl ? 'right' : 'left');\r
152 },\r
153\r
154// // TODO: RTL\r
155// getRenderTree: function() {\r
156// var me = this,\r
157// rtl = me.rtl;\r
158//\r
159// if (rtl && Ext.rtl) {\r
160// me.baseCls += ' ' + (Ext.rtl.util.Renderable.prototype._rtlCls);\r
161// me.horizontalProp = 'right';\r
162// } else if (rtl === false) {\r
163// me.addCls(Ext.rtl.util.Renderable.prototype._ltrCls);\r
164// }\r
165//\r
166// return me.callParent();\r
167// },\r
168\r
169 update: function() {\r
170 var me = this,\r
171 values = me.getValue(),\r
172 len = values.length,\r
173 i;\r
174\r
175 for (i = 0; i < len; i++) {\r
176 this.thumbs[i].dom.style[me.getThumbPositionStyle()] = me.calculateThumbPosition(values[i]) + '%';\r
177 }\r
178 },\r
179\r
180 onMouseDown: function(e) {\r
181 var me = this,\r
182 thumb,\r
183 trackPoint = e.getXY(),\r
184 delta;\r
185\r
186 if (!me.disabled && e.button === 0) {\r
187 // Stop any selection caused by mousedown + mousemove\r
188 Ext.getDoc().on({\r
189 scope: me,\r
190 capture: true,\r
191 selectstart: me.stopSelect\r
192 });\r
193\r
194 thumb = e.getTarget('.' + me.thumbCls, null, true);\r
195\r
196 if (thumb) {\r
197 me.animateOnSetValue = false;\r
198\r
199 me.promoteThumb(thumb);\r
200 me.captureMouse(me.onMouseMove, me.onMouseUp, [thumb], 1);\r
201 delta = me.pointerOffset = thumb.getXY();\r
202\r
203 // Work out the delta of the pointer from the dead centre of the thumb.\r
204 // Slider.getTrackPoint positions the centre of the slider at the reported\r
205 // pointer position, so we have to correct for that in getValueFromTracker.\r
206 delta[0] += Math.floor(thumb.getWidth() / 2) - trackPoint[0];\r
207 delta[1] += Math.floor(thumb.getHeight() / 2) - trackPoint[1];\r
208 } else {\r
209 if (me.getClickToChange()) {\r
210 trackPoint = me.getTrackpoint(trackPoint);\r
211 if (trackPoint != null) {\r
212 me.onClickChange(trackPoint);\r
213 }\r
214 }\r
215 }\r
216 }\r
217 },\r
218\r
219 /**\r
220 * @private\r
221 * Moves the thumb to the indicated position.\r
222 * Only changes the value if the click was within this.clickRange.\r
223 * @param {Number} trackPoint local pixel offset **from the origin** (left for horizontal and bottom for vertical) along the Slider's axis at which the click event occured.\r
224 */\r
225 onClickChange : function(trackPoint) {\r
226 var me = this,\r
227 thumb, index, value;\r
228\r
229 // How far along the track *from the origin* was the click.\r
230 // If vertical, the origin is the bottom of the slider track.\r
231\r
232 //find the nearest thumb to the click event\r
233 thumb = me.getNearest(trackPoint);\r
234 index = parseInt(thumb.getAttribute('data-thumbIndex'), 10);\r
235 value = Ext.util.Format.round(me.reversePixelValue(trackPoint), me.decimalPrecision);\r
236 if (index) {\r
237 me.setThumbValue(index, value, undefined, true);\r
238 } else {\r
239 me.setValue(value);\r
240 }\r
241 },\r
242\r
243 /**\r
244 * @private\r
245 * Returns the nearest thumb to a click event, along with its distance\r
246 * @param {Number} trackPoint local pixel position along the Slider's axis to find the Thumb for\r
247 * @return {Object} The closest thumb object and its distance from the click event\r
248 */\r
249 getNearest: function(trackPoint) {\r
250 var me = this,\r
251 clickValue = me.reversePixelValue(trackPoint),\r
252 nearestDistance = me.getRange() + 5, //add a small fudge for the end of the slider\r
253 nearest = null,\r
254 thumbs = me.thumbs,\r
255 i = 0,\r
256 len = thumbs.length,\r
257 thumb,\r
258 value,\r
259 dist;\r
260\r
261 for (; i < len; i++) {\r
262 thumb = thumbs[i];\r
263 value = me.reversePercentageValue(parseInt(thumb.dom.style[me.getThumbPositionStyle()], 10));\r
264 dist = Math.abs(value - clickValue);\r
265\r
266 if (Math.abs(dist) <= nearestDistance) {\r
267 nearest = thumb;\r
268 nearestDistance = dist;\r
269 }\r
270 }\r
271 return nearest;\r
272 },\r
273\r
274 /**\r
275 * @private\r
276 * Moves the given thumb above all other by increasing its z-index. This is called when as drag\r
277 * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is\r
278 * required when the thumbs are stacked on top of each other at one of the ends of the slider's\r
279 * range, which can result in the user not being able to move any of them.\r
280 * @param {Ext.slider.Thumb} topThumb The thumb to move to the top\r
281 */\r
282 promoteThumb: function(topThumb) {\r
283 var thumbs = this.thumbStack || (this.thumbStack = Ext.Array.slice(this.thumbs)),\r
284 ln = thumbs.length,\r
285 zIndex = 10000, i;\r
286\r
287 // Move topthumb to position zero\r
288 if (thumbs[0] !== topThumb) {\r
289 Ext.Array.remove(thumbs, topThumb);\r
290 thumbs.unshift(topThumb);\r
291 }\r
292\r
293 // Then shuffle the zIndices\r
294 for (i = 0; i < ln; i++) {\r
295 thumbs[i].el.setStyle('zIndex', zIndex);\r
296 zIndex -= 1000;\r
297 }\r
298 },\r
299\r
300 doMouseMove: function (e, thumb, changeComplete) {\r
301 var me = this,\r
302 trackerXY = e.getXY(),\r
303 newValue, thumbIndex, trackPoint;\r
304\r
305 trackerXY[0] += me.pointerOffset[0];\r
306 trackerXY[1] += me.pointerOffset[1];\r
307 trackPoint = me.getTrackpoint(trackerXY);\r
308\r
309 // If dragged out of range, value will be undefined\r
310 if (trackPoint) {\r
311 newValue = me.reversePixelValue(trackPoint);\r
312 thumbIndex = parseInt(thumb.getAttribute('data-thumbIndex'), 10);\r
313 if (thumbIndex || (!changeComplete && me.getPublishOnComplete())) {\r
314 me.setThumbValue(thumbIndex, newValue, false, changeComplete);\r
315 } else {\r
316 me.setValue(newValue);\r
317 }\r
318 }\r
319 },\r
320\r
321 onMouseMove: function(e, thumb) {\r
322 this.doMouseMove(e, thumb, false);\r
323 },\r
324\r
325 onMouseUp: function(e, thumb) {\r
326 var me = this;\r
327 \r
328 me.doMouseMove(e, thumb, true);\r
329 Ext.getDoc().un({\r
330 scope: me,\r
331 capture: true,\r
332 selectstart: me.stopSelect\r
333 });\r
334 delete me.animateOnSetValue; // expose "undefined" on prototype\r
335 },\r
336\r
337 stopSelect : function(e) {\r
338 e.stopEvent();\r
339 return false;\r
340 },\r
341\r
342 /**\r
343 * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and\r
344 * maxValue.\r
345 *\r
346 * Setting a single value:\r
347 * // Set the second slider value, don't animate\r
348 * mySlider.setThumbValue(1, 50, false);\r
349 *\r
350 * Setting multiple values at once\r
351 * // Set 3 thumb values, animate\r
352 * mySlider.setThumbValue([20, 40, 60], true);\r
353 *\r
354 * @param {Number/Number[]} index Index of the thumb to move. Alternatively, it can be an array of values to set\r
355 * for each thumb in the slider.\r
356 * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)\r
357 * @param {Boolean} [animate=true] Turn on or off animation\r
358 * @return {Ext.slider.Multi} this\r
359 */\r
360 setThumbValue : function(index, value, animate, changeComplete) {\r
361 var me = this,\r
362 thumb, thumbValue, len, i, values;\r
363\r
364 if (Ext.isArray(index)) {\r
365 values = index;\r
366 animate = value;\r
367\r
368 for (i = 0, len = values.length; i < len; ++i) {\r
369 me.setThumbValue(i, values[i], animate, changeComplete);\r
370 }\r
371 return me;\r
372 }\r
373\r
374 thumb = me.getThumb(index);\r
375 thumbValue = me.reversePercentageValue(parseInt(thumb.dom.style[me.getThumbPositionStyle()], 10));\r
376\r
377 // ensures value is contstrained and snapped\r
378 value = me.normalizeValue(value);\r
379\r
380 if (value !== thumbValue && me.fireEvent('beforechange', me, value, thumbValue, thumb) !== false) {\r
381 if (me.element.dom) {\r
382 // TODO this only handles a single value; need a solution for exposing multiple values to aria.\r
383 // Perhaps this should go on each thumb element rather than the outer element.\r
384 me.element.set({\r
385 'aria-valuenow': value,\r
386 'aria-valuetext': value\r
387 });\r
388\r
389 me.moveThumb(thumb, me.calculateThumbPosition(value), Ext.isDefined(animate) ? animate !== false : me.animate);\r
390 me.fireEvent('change', me, value, thumb);\r
391 }\r
392 }\r
393 return me;\r
394 },\r
395\r
396 /**\r
397 * Returns the current value of the slider\r
398 * @param {Number} index The index of the thumb to return a value for\r
399 * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if\r
400 * no index is given.\r
401 */\r
402 getValue : function(index) {\r
403 var me = this,\r
404 value;\r
405\r
406 if (Ext.isNumber(index)) {\r
407 value = me.thumbs[index].dom.style[me.getThumbPositionStyle()];\r
408 value = me.reversePercentageValue(parseInt(value, 10));\r
409 } else {\r
410 value = me.getValues();\r
411 if (value.length === 1) {\r
412 value = value[0];\r
413 }\r
414 }\r
415\r
416 return value;\r
417 },\r
418\r
419 /**\r
420 * Returns an array of values - one for the location of each thumb\r
421 * @return {Number[]} The set of thumb values\r
422 */\r
423 getValues: function() {\r
424 var me = this,\r
425 values = [],\r
426 i = 0,\r
427 thumbs = me.thumbs,\r
428 len = thumbs.length;\r
429\r
430 for (; i < len; i++) {\r
431 values.push(me.reversePercentageValue(parseInt(me.thumbs[i].dom.style[me.getThumbPositionStyle()], 10)));\r
432 }\r
433 return values;\r
434 },\r
435\r
436 /**\r
437 * @private\r
438 * move the thumb\r
439 */\r
440 moveThumb: function(thumb, v, animate) {\r
441 var me = this,\r
442 styleProp = me.getThumbPositionStyle(),\r
443 to,\r
444 from;\r
445\r
446 v += '%';\r
447\r
448 if (!animate) {\r
449 thumb.dom.style[styleProp] = v;\r
450 } else {\r
451 to = {};\r
452 to[styleProp] = v;\r
453\r
454 if (!Ext.supports.GetPositionPercentage) {\r
455 from = {};\r
456 from[styleProp] = thumb.dom.style[styleProp];\r
457 }\r
458\r
459 new Ext.fx.Anim({ // jshint ignore:line\r
460 target: thumb,\r
461 duration: 350,\r
462 from: from,\r
463 to: to\r
464 });\r
465 }\r
466 },\r
467\r
468 /**\r
469 * @private\r
470 * Returns a snapped, constrained value when given a desired value\r
471 * @param {Number} value Raw number value\r
472 * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values\r
473 */\r
474 normalizeValue : function(v) {\r
475 var me = this,\r
476 snapFn = me.zeroBasedSnapping ? 'snap' : 'snapInRange';\r
477\r
478 v = Ext.Number[snapFn](v, me.increment, me.minValue, me.maxValue);\r
479 v = Ext.util.Format.round(v, me.decimalPrecision);\r
480 v = Ext.Number.constrain(v, me.minValue, me.maxValue);\r
481 return v;\r
482 },\r
483\r
484 /**\r
485 * @private\r
486 * Given an `[x, y]` position within the slider's track (Points outside the slider's track are coerced to either the minimum or maximum value),\r
487 * calculate how many pixels **from the slider origin** (left for horizontal Sliders and bottom for vertical Sliders) that point is.\r
488 *\r
489 * If the point is outside the range of the Slider's track, the return value is `undefined`\r
490 * @param {Number[]} xy The point to calculate the track point for\r
491 */\r
492 getTrackpoint : function(xy) {\r
493 var me = this,\r
494 vertical = me.getVertical(),\r
495 sliderTrack = me.innerEl,\r
496 trackLength, result,\r
497 positionProperty;\r
498\r
499 if (vertical) {\r
500 positionProperty = 'top';\r
501 trackLength = sliderTrack.getHeight();\r
502 } else {\r
503 positionProperty = 'left';\r
504 trackLength = sliderTrack.getWidth();\r
505 }\r
506 xy = me.transformTrackPoints(sliderTrack.translatePoints(xy));\r
507 result = Ext.Number.constrain(xy[positionProperty], 0, trackLength);\r
508 return vertical ? trackLength - result : result;\r
509 },\r
510\r
511 transformTrackPoints: Ext.identityFn,\r
512\r
513 /**\r
514 * @private\r
515 * Given a value within this Slider's range, calculates a Thumb's percentage CSS position to map that value.\r
516 */\r
517 calculateThumbPosition : function(v) {\r
518 var me = this,\r
519 pos = (v - me.getMinValue()) / me.getRange() * 100;\r
520\r
521 if (isNaN(pos)) {\r
522 pos = 0;\r
523 }\r
524\r
525 return pos;\r
526 },\r
527\r
528 /**\r
529 * @private\r
530 * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,\r
531 * the ratio is 2\r
532 * @return {Number} The ratio of pixels to mapped values\r
533 */\r
534 getRatio : function() {\r
535 var me = this,\r
536 innerEl = me.innerEl,\r
537 trackLength = me.getVertical() ? innerEl.getHeight() : innerEl.getWidth(),\r
538 valueRange = me.getRange();\r
539\r
540 return valueRange === 0 ? trackLength : (trackLength / valueRange);\r
541 },\r
542\r
543 getRange: function() {\r
544 return this.getMaxValue() - this.getMinValue();\r
545 },\r
546\r
547 /**\r
548 * @private\r
549 * Given a pixel location along the slider, returns the mapped slider value for that pixel.\r
550 * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reversePixelValue(50)\r
551 * returns 200\r
552 * @param {Number} pos The position along the slider to return a mapped value for\r
553 * @return {Number} The mapped value for the given position\r
554 */\r
555 reversePixelValue : function(pos) {\r
556 return this.getMinValue() + (pos / this.getRatio());\r
557 },\r
558\r
559 /**\r
560 * @private\r
561 * Given a Thumb's percentage position along the slider, returns the mapped slider value for that pixel.\r
562 * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reversePercentageValue(25)\r
563 * returns 200\r
564 * @param {Number} pos The percentage along the slider track to return a mapped value for\r
565 * @return {Number} The mapped value for the given position\r
566 */\r
567 reversePercentageValue : function(pos) {\r
568 return this.getMinValue() + this.getRange() * (pos / 100);\r
569 },\r
570\r
571 captureMouse: function(onMouseMove, onMouseUp, args, appendArgs) {\r
572 var me = this,\r
573 onMouseupWrap,\r
574 listeners;\r
575\r
576 onMouseMove = onMouseMove && Ext.Function.bind(onMouseMove, me, args, appendArgs);\r
577 onMouseUp = onMouseUp && Ext.Function.bind(onMouseUp, me, args, appendArgs);\r
578 onMouseupWrap = function() {\r
579 Ext.getDoc().un(listeners);\r
580 if (onMouseUp) {\r
581 onMouseUp.apply(me, arguments);\r
582 }\r
583 };\r
584 listeners = {\r
585 mousemove: onMouseMove,\r
586 mouseup: onMouseupWrap\r
587 };\r
588\r
589 // Funnel mousemove events and the final mouseup event back into the gadget\r
590 Ext.getDoc().on(listeners);\r
591 }\r
592});\r