]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * Utility class used by Ext.field.Slider.\r | |
3 | * @private\r | |
4 | */\r | |
5 | Ext.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 |