]> git.proxmox.com Git - extjs.git/blame - extjs/modern/modern/src/slider/Slider.js
add extjs 6.0.1 sources
[extjs.git] / extjs / modern / modern / src / slider / Slider.js
CommitLineData
6527f429
DM
1/**\r
2 * Utility class used by Ext.field.Slider.\r
3 * @private\r
4 */\r
5Ext.define('Ext.slider.Slider', {\r
6 extend: 'Ext.Container',\r
7 xtype: 'slider',\r
8\r
9 requires: [\r
10 'Ext.slider.Thumb',\r
11 'Ext.fx.easing.EaseOut'\r
12 ],\r
13\r
14 /**\r
15 * @event change\r
16 * Fires when the value changes\r
17 * @param {Ext.slider.Slider} this\r
18 * @param {Ext.slider.Thumb} thumb The thumb being changed\r
19 * @param {Number} newValue The new value\r
20 * @param {Number} oldValue The old value\r
21 */\r
22\r
23 /**\r
24 * @event dragstart\r
25 * Fires when the slider thumb starts a drag\r
26 * @param {Ext.slider.Slider} this\r
27 * @param {Ext.slider.Thumb} thumb The thumb being dragged\r
28 * @param {Array} value The start value\r
29 * @param {Ext.EventObject} e\r
30 */\r
31\r
32 /**\r
33 * @event drag\r
34 * Fires when the slider thumb starts a drag\r
35 * @param {Ext.slider.Slider} this\r
36 * @param {Ext.slider.Thumb} thumb The thumb being dragged\r
37 * @param {Ext.EventObject} e\r
38 */\r
39\r
40 /**\r
41 * @event dragend\r
42 * Fires when the slider thumb starts a drag\r
43 * @param {Ext.slider.Slider} this\r
44 * @param {Ext.slider.Thumb} thumb The thumb being dragged\r
45 * @param {Array} value The end value\r
46 * @param {Ext.EventObject} e\r
47 */\r
48 config: {\r
49 baseCls: 'x-slider',\r
50\r
51 /**\r
52 * @cfg {Object} thumbConfig The config object to factory {@link Ext.slider.Thumb} instances\r
53 * @accessor\r
54 */\r
55 thumbConfig: {\r
56 draggable: {\r
57 translatable: {\r
58 easingX: {\r
59 duration: 300,\r
60 type: 'ease-out'\r
61 }\r
62 }\r
63 }\r
64 },\r
65\r
66 /**\r
67 * @cfg {Number} increment The increment by which to snap each thumb when its value changes. Any thumb movement\r
68 * will be snapped to the nearest value that is a multiple of the increment (e.g. if increment is 10 and the user\r
69 * tries to move the thumb to 67, it will be snapped to 70 instead)\r
70 * @accessor\r
71 */\r
72 increment : 1,\r
73\r
74 /**\r
75 * @cfg {Number/Number[]} value The value(s) of this slider's thumbs. If you pass\r
76 * a number, it will assume you have just 1 thumb.\r
77 * @accessor\r
78 */\r
79 value: 0,\r
80\r
81 /**\r
82 * @cfg {Number} minValue The lowest value any thumb on this slider can be set to.\r
83 * @accessor\r
84 */\r
85 minValue: 0,\r
86\r
87 /**\r
88 * @cfg {Number} maxValue The highest value any thumb on this slider can be set to.\r
89 * @accessor\r
90 */\r
91 maxValue: 100,\r
92\r
93 /**\r
94 * @cfg {Boolean} allowThumbsOverlapping Whether or not to allow multiple thumbs to overlap each other.\r
95 * Setting this to true guarantees the ability to select every possible value in between {@link #minValue}\r
96 * and {@link #maxValue} that satisfies {@link #increment}\r
97 * @accessor\r
98 */\r
99 allowThumbsOverlapping: false,\r
100\r
101 /**\r
102 * @cfg {Boolean/Object} animation\r
103 * The animation to use when moving the slider. Possible properties are:\r
104 *\r
105 * - duration\r
106 * - easingX\r
107 * - easingY\r
108 *\r
109 * @accessor\r
110 */\r
111 animation: true,\r
112\r
113 /**\r
114 * Will make this field read only, meaning it cannot be changed with used interaction.\r
115 * @cfg {Boolean} readOnly\r
116 * @accessor\r
117 */\r
118 readOnly: false\r
119 },\r
120\r
121 /**\r
122 * @cfg {Number/Number[]} values Alias to {@link #value}\r
123 */\r
124\r
125 elementWidth: 0,\r
126\r
127 offsetValueRatio: 0,\r
128\r
129 activeThumb: null,\r
130\r
131 constructor: function(config) {\r
132 config = config || {};\r
133\r
134 if (config.hasOwnProperty('values')) {\r
135 config.value = config.values;\r
136 }\r
137\r
138 this.callParent([config]);\r
139 },\r
140\r
141 /**\r
142 * @private\r
143 */\r
144 initialize: function() {\r
145 var element = this.element,\r
146 thumb;\r
147\r
148 this.callParent();\r
149\r
150 element.on({\r
151 scope: this,\r
152 tap: 'onTap',\r
153 resize: 'onResize'\r
154 });\r
155\r
156 this.on({\r
157 scope: this,\r
158 delegate: '> thumb',\r
159 tap: 'onTap',\r
160 beforedragstart: 'onThumbBeforeDragStart',\r
161 dragstart: 'onThumbDragStart',\r
162 drag: 'onThumbDrag',\r
163 dragend: 'onThumbDragEnd'\r
164 });\r
165\r
166 thumb = this.getThumb(0);\r
167 if (thumb) {\r
168 thumb.on('resize', 'onThumbResize', this);\r
169 }\r
170 },\r
171\r
172 /**\r
173 * @private\r
174 */\r
175 factoryThumb: function() {\r
176 return Ext.factory(this.getThumbConfig(), Ext.slider.Thumb);\r
177 },\r
178\r
179 /**\r
180 * Returns the Thumb instances bound to this Slider\r
181 * @return {Ext.slider.Thumb[]} The thumb instances\r
182 */\r
183 getThumbs: function() {\r
184 return this.innerItems;\r
185 },\r
186\r
187 /**\r
188 * Returns the Thumb instance bound to this Slider\r
189 * @param {Number} [index=0] The index of Thumb to return.\r
190 * @return {Ext.slider.Thumb} The thumb instance\r
191 */\r
192 getThumb: function(index) {\r
193 if (typeof index != 'number') {\r
194 index = 0;\r
195 }\r
196\r
197 return this.innerItems[index];\r
198 },\r
199\r
200 refreshOffsetValueRatio: function() {\r
201 var me = this,\r
202 valueRange = me.getMaxValue() - me.getMinValue(),\r
203 trackWidth = me.elementWidth - me.thumbWidth;\r
204\r
205 me.offsetValueRatio = valueRange === 0 ? 0 : trackWidth / valueRange;\r
206 },\r
207\r
208 onThumbResize: function(){\r
209 var thumb = this.getThumb(0);\r
210 if (thumb) {\r
211 this.thumbWidth = thumb.getElementWidth();\r
212 }\r
213 this.refresh();\r
214 },\r
215\r
216 onResize: function(element, info) {\r
217 this.elementWidth = info.width;\r
218 this.refresh();\r
219 },\r
220\r
221 refresh: function() {\r
222 this.refreshing = true;\r
223 this.refreshValue();\r
224 this.refreshing = false;\r
225 },\r
226\r
227 setActiveThumb: function(thumb) {\r
228 var oldActiveThumb = this.activeThumb;\r
229\r
230 if (oldActiveThumb && oldActiveThumb !== thumb) {\r
231 oldActiveThumb.setZIndex(null);\r
232 }\r
233\r
234 this.activeThumb = thumb;\r
235 thumb.setZIndex(2);\r
236\r
237 return this;\r
238 },\r
239\r
240 onThumbBeforeDragStart: function(thumb, e) {\r
241 if (this.offsetValueRatio === 0 || e.absDeltaX <= e.absDeltaY || this.getReadOnly()) {\r
242 return false;\r
243 }\r
244 },\r
245\r
246 onThumbDragStart: function(thumb, e) {\r
247 var me = this;\r
248\r
249 me.refreshAllThumbConstraints();\r
250\r
251 e.stopPropagation();\r
252\r
253 if (me.getAllowThumbsOverlapping()) {\r
254 me.setActiveThumb(thumb);\r
255 }\r
256\r
257 me.dragStartValue = me.getValue()[me.getThumbIndex(thumb)];\r
258 me.fireEvent('dragstart', me, thumb, me.dragStartValue, e);\r
259 },\r
260\r
261 onThumbDrag: function(thumb, e, offsetX) {\r
262 var me = this,\r
263 index = me.getThumbIndex(thumb),\r
264 offsetValueRatio = me.offsetValueRatio,\r
265 constrainedValue = me.constrainValue(me.getMinValue() + offsetX / offsetValueRatio);\r
266\r
267 e.stopPropagation();\r
268\r
269 me.setIndexValue(index, constrainedValue);\r
270\r
271 me.fireEvent('drag', me, thumb, me.getValue(), e);\r
272\r
273 return false;\r
274 },\r
275\r
276 setIndexValue: function(index, value, animation) {\r
277 var me = this,\r
278 thumb = me.getThumb(index),\r
279 values = me.getValue(),\r
280 minValue = me.getMinValue(),\r
281 offsetValueRatio = me.offsetValueRatio,\r
282 increment = me.getIncrement(),\r
283 draggable = thumb.getDraggable();\r
284\r
285 draggable.setOffset((value - minValue) * offsetValueRatio, null, animation);\r
286\r
287 values[index] = minValue + Math.round((draggable.offset.x / offsetValueRatio) / increment) * increment;\r
288 },\r
289\r
290 onThumbDragEnd: function(thumb, e) {\r
291 var me = this,\r
292 index = me.getThumbIndex(thumb),\r
293 newValue = me.getValue()[index],\r
294 oldValue = me.dragStartValue;\r
295\r
296 me.snapThumbPosition(thumb, newValue);\r
297 me.fireEvent('dragend', me, thumb, me.getValue(), e);\r
298 if (oldValue !== newValue) {\r
299 me.fireEvent('change', me, thumb, newValue, oldValue);\r
300 }\r
301 },\r
302\r
303 getThumbIndex: function(thumb) {\r
304 return this.getThumbs().indexOf(thumb);\r
305 },\r
306\r
307 refreshThumbConstraints: function(thumb) {\r
308 var allowThumbsOverlapping = this.getAllowThumbsOverlapping(),\r
309 offsetX = thumb.getDraggable().getOffset().x,\r
310 thumbs = this.getThumbs(),\r
311 index = this.getThumbIndex(thumb),\r
312 previousThumb = thumbs[index - 1],\r
313 nextThumb = thumbs[index + 1],\r
314 thumbWidth = this.thumbWidth;\r
315\r
316 if (previousThumb) {\r
317 previousThumb.getDraggable().addExtraConstraint({\r
318 max: {\r
319 x: offsetX - ((allowThumbsOverlapping) ? 0 : thumbWidth)\r
320 }\r
321 });\r
322 }\r
323\r
324 if (nextThumb) {\r
325 nextThumb.getDraggable().addExtraConstraint({\r
326 min: {\r
327 x: offsetX + ((allowThumbsOverlapping) ? 0 : thumbWidth)\r
328 }\r
329 });\r
330 }\r
331 },\r
332\r
333 /**\r
334 * @private\r
335 */\r
336 onTap: function(e) {\r
337 var me = this,\r
338 element = me.element,\r
339 minDistance = Infinity,\r
340 i, absDistance, testValue, closestIndex, oldValue, thumb,\r
341 ln, values, value, offset, elementX, targetElement, touchPointX;\r
342\r
343 if (me.offsetValueRatio === 0 || me.isDisabled() || me.getReadOnly()) {\r
344 return;\r
345 }\r
346\r
347 targetElement = Ext.get(e.target);\r
348\r
349 if (!targetElement || (Ext.browser.engineName == 'WebKit' && targetElement.hasCls('x-thumb'))) {\r
350 return;\r
351 }\r
352\r
353 touchPointX = e.touch.point.x;\r
354 elementX = element.getX();\r
355 offset = touchPointX - elementX - (me.thumbWidth / 2);\r
356 value = me.constrainValue(me.getMinValue() + offset / me.offsetValueRatio);\r
357 values = me.getValue();\r
358 ln = values.length;\r
359\r
360 if (ln === 1) {\r
361 closestIndex = 0;\r
362 } else {\r
363 for (i = 0; i < ln; i++) {\r
364 testValue = values[i];\r
365 absDistance = Math.abs(testValue - value);\r
366\r
367 if (absDistance < minDistance) {\r
368 minDistance = absDistance;\r
369 closestIndex = i;\r
370 }\r
371 }\r
372 }\r
373\r
374 oldValue = values[closestIndex];\r
375 thumb = me.getThumb(closestIndex);\r
376\r
377 me.setIndexValue(closestIndex, value, me.getAnimation());\r
378 me.refreshThumbConstraints(thumb);\r
379\r
380 if (oldValue !== value) {\r
381 me.fireEvent('change', me, thumb, value, oldValue);\r
382 }\r
383 },\r
384\r
385 /**\r
386 * @private\r
387 */\r
388 updateThumbs: function(newThumbs) {\r
389 this.add(newThumbs);\r
390 },\r
391\r
392 applyValue: function(value, oldValue) {\r
393 var values = Ext.Array.from(value || 0),\r
394 filteredValues = [],\r
395 previousFilteredValue = this.getMinValue(),\r
396 filteredValue, i, ln;\r
397\r
398 for (i = 0,ln = values.length; i < ln; i++) {\r
399 filteredValue = this.constrainValue(values[i]);\r
400\r
401 if (filteredValue < previousFilteredValue) {\r
402 //<debug>\r
403 Ext.Logger.warn("Invalid values of '"+Ext.encode(values)+"', values at smaller indexes must " +\r
404 "be smaller than or equal to values at greater indexes");\r
405 //</debug>\r
406 filteredValue = previousFilteredValue;\r
407 }\r
408\r
409 filteredValues.push(filteredValue);\r
410\r
411 previousFilteredValue = filteredValue;\r
412 }\r
413\r
414 if (!this.refreshing && oldValue) {\r
415 if (Ext.Array.equals(value, oldValue)) {\r
416 filteredValues = undefined;\r
417 }\r
418 }\r
419\r
420 return filteredValues;\r
421 },\r
422\r
423 /**\r
424 * Updates the sliders thumbs with their new value(s)\r
425 */\r
426 updateValue: function(newValue, oldValue) {\r
427 var me = this,\r
428 thumbs = me.getThumbs(),\r
429 len = newValue.length,\r
430 i;\r
431\r
432 me.setThumbsCount(len);\r
433\r
434 for (i = 0; i < len; i++) {\r
435 me.snapThumbPosition(thumbs[i], newValue[i]);\r
436 }\r
437 },\r
438\r
439 /**\r
440 * @private\r
441 */\r
442 refreshValue: function() {\r
443 this.refreshOffsetValueRatio();\r
444\r
445 this.setValue(this.getValue());\r
446 },\r
447\r
448 /**\r
449 * @private\r
450 * Takes a desired value of a thumb and returns the nearest snap value. e.g if minValue = 0, maxValue = 100, increment = 10 and we\r
451 * pass a value of 67 here, the returned value will be 70. The returned number is constrained within {@link #minValue} and {@link #maxValue},\r
452 * so in the above example 68 would be returned if {@link #maxValue} was set to 68.\r
453 * @param {Number} value The value to snap\r
454 * @return {Number} The snapped value\r
455 */\r
456 constrainValue: function(value) {\r
457 var me = this,\r
458 minValue = me.getMinValue(),\r
459 maxValue = me.getMaxValue(),\r
460 increment = me.getIncrement(),\r
461 remainder;\r
462\r
463 value = parseFloat(value);\r
464\r
465 if (isNaN(value)) {\r
466 value = minValue;\r
467 }\r
468\r
469 remainder = (value - minValue) % increment;\r
470 value -= remainder;\r
471\r
472 if (Math.abs(remainder) >= (increment / 2)) {\r
473 value += (remainder > 0) ? increment : -increment;\r
474 }\r
475\r
476 value = Math.max(minValue, value);\r
477 value = Math.min(maxValue, value);\r
478\r
479 return value;\r
480 },\r
481\r
482 setThumbsCount: function(count) {\r
483 var thumbs = this.getThumbs(),\r
484 thumbsCount = thumbs.length,\r
485 i, ln, thumb;\r
486\r
487 if (thumbsCount > count) {\r
488 for (i = 0,ln = thumbsCount - count; i < ln; i++) {\r
489 thumb = thumbs[thumbs.length - 1];\r
490 thumb.destroy();\r
491 }\r
492 }\r
493 else if (thumbsCount < count) {\r
494 for (i = 0,ln = count - thumbsCount; i < ln; i++) {\r
495 this.add(this.factoryThumb());\r
496 }\r
497 }\r
498\r
499 return this;\r
500 },\r
501\r
502 /**\r
503 * Convenience method. Calls {@link #setValue}.\r
504 */\r
505 setValues: function(value) {\r
506 this.setValue(value);\r
507 },\r
508\r
509 /**\r
510 * Convenience method. Calls {@link #getValue}.\r
511 * @return {Object}\r
512 */\r
513 getValues: function() {\r
514 return this.getValue();\r
515 },\r
516\r
517 /**\r
518 * Sets the {@link #increment} configuration.\r
519 * @param {Number} increment\r
520 * @return {Number}\r
521 */\r
522 applyIncrement: function(increment) {\r
523 if (increment === 0) {\r
524 increment = 1;\r
525 }\r
526\r
527 return Math.abs(increment);\r
528 },\r
529\r
530 /**\r
531 * @private\r
532 */\r
533 updateAllowThumbsOverlapping: function(newValue, oldValue) {\r
534 if (typeof oldValue != 'undefined') {\r
535 this.refreshValue();\r
536 }\r
537 },\r
538\r
539 /**\r
540 * @private\r
541 */\r
542 updateMinValue: function(newValue, oldValue) {\r
543 if (typeof oldValue != 'undefined') {\r
544 this.refreshValue();\r
545 }\r
546 },\r
547\r
548 /**\r
549 * @private\r
550 */\r
551 updateMaxValue: function(newValue, oldValue) {\r
552 if (typeof oldValue != 'undefined') {\r
553 this.refreshValue();\r
554 }\r
555 },\r
556\r
557 /**\r
558 * @private\r
559 */\r
560 updateIncrement: function(newValue, oldValue) {\r
561 if (typeof oldValue != 'undefined') {\r
562 this.refreshValue();\r
563 }\r
564 },\r
565\r
566 updateDisabled: function(disabled) {\r
567 this.callParent(arguments);\r
568\r
569 var items = this.getItems().items,\r
570 ln = items.length,\r
571 i;\r
572\r
573 for (i = 0; i < ln; i++) {\r
574 items[i].setDisabled(disabled);\r
575 }\r
576 },\r
577\r
578 privates: {\r
579 refreshAllThumbConstraints: function() {\r
580 var thumbs = this.getThumbs(),\r
581 len = thumbs.length,\r
582 i;\r
583\r
584 for (i = 0; i < len; i++) {\r
585 this.refreshThumbConstraints(thumbs[i]);\r
586 }\r
587 },\r
588\r
589 snapThumbPosition: function(thumb, value) {\r
590 var ratio = this.offsetValueRatio,\r
591 offset;\r
592\r
593 if (isFinite(ratio)) {\r
594 offset = Ext.Number.correctFloat((value - this.getMinValue()) * ratio)\r
595 thumb.getDraggable().setExtraConstraint(null).setOffset(offset);\r
596 }\r
597 }\r
598 }\r
599});\r