]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/slider/Multi.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / slider / Multi.js
CommitLineData
6527f429
DM
1/**\r
2 * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking\r
3 * and animation. Can be added as an item to any container.\r
4 *\r
5 * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:\r
6 *\r
7 * @example\r
8 * Ext.create('Ext.slider.Multi', {\r
9 * width: 200,\r
10 * values: [25, 50, 75],\r
11 * increment: 5,\r
12 * minValue: 0,\r
13 * maxValue: 100,\r
14 *\r
15 * // this defaults to true, setting to false allows the thumbs to pass each other\r
16 * constrainThumbs: false,\r
17 * renderTo: Ext.getBody()\r
18 * });\r
19 */\r
20Ext.define('Ext.slider.Multi', {\r
21 extend: 'Ext.form.field.Base',\r
22 alias: 'widget.multislider',\r
23 alternateClassName: 'Ext.slider.MultiSlider',\r
24\r
25 requires: [\r
26 'Ext.slider.Thumb',\r
27 'Ext.slider.Tip',\r
28 'Ext.Number',\r
29 'Ext.util.Format',\r
30 'Ext.Template'\r
31 ],\r
32 \r
33 /**\r
34 * @cfg {Number} value\r
35 * A value with which to initialize the slider. Setting this will only result in the creation\r
36 * of a single slider thumb; if you want multiple thumbs then use the {@link #values} config instead.\r
37 *\r
38 * Defaults to #minValue.\r
39 */\r
40\r
41 /**\r
42 * @cfg {Number[]} values\r
43 * Array of Number values with which to initalize the slider. A separate slider thumb will be created for each value\r
44 * in this array. This will take precedence over the single {@link #value} config.\r
45 */\r
46\r
47 /**\r
48 * @cfg {Boolean} vertical\r
49 * Orient the Slider vertically rather than horizontally.\r
50 */\r
51 vertical: false,\r
52\r
53 /**\r
54 * @cfg {Number} minValue\r
55 * The minimum value for the Slider.\r
56 */\r
57 minValue: 0,\r
58\r
59 /**\r
60 * @cfg {Number} maxValue\r
61 * The maximum value for the Slider.\r
62 */\r
63 maxValue: 100,\r
64\r
65 /**\r
66 * @cfg {Number/Boolean} decimalPrecision The number of decimal places to which to round the Slider's value.\r
67 *\r
68 * To disable rounding, configure as **false**.\r
69 */\r
70 decimalPrecision: 0,\r
71\r
72 /**\r
73 * @cfg {Number} keyIncrement\r
74 * How many units to change the Slider when adjusting with keyboard navigation. If the increment\r
75 * config is larger, it will be used instead.\r
76 */\r
77 keyIncrement: 1,\r
78 \r
79 /**\r
80 * @cfg {Number} [pageSize=10]\r
81 * How many units to change the Slider when using PageUp and PageDown keys.\r
82 */\r
83 pageSize: 10,\r
84\r
85 /**\r
86 * @cfg {Number} increment\r
87 * How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.\r
88 */\r
89 increment: 0,\r
90\r
91 /**\r
92 * @cfg {Boolean} [zeroBasedSnapping=false]\r
93 * Set to `true` to calculate snap points based on {@link #increment}s from zero as opposed to\r
94 * from this Slider's {@link #minValue}.\r
95 *\r
96 * By Default, valid snap points are calculated starting {@link #increment}s from the {@link #minValue}\r
97 */\r
98\r
99 /**\r
100 * @private\r
101 * @property {Number[]} clickRange\r
102 * Determines whether or not a click to the slider component is considered to be a user request to change the value. Specified as an array of [top, bottom],\r
103 * the click event's 'top' property is compared to these numbers and the click only considered a change request if it falls within them. e.g. if the 'top'\r
104 * value of the click event is 4 or 16, the click is not considered a change request as it falls outside of the [5, 15] range\r
105 */\r
106 clickRange: [5,15],\r
107\r
108 /**\r
109 * @cfg {Boolean} clickToChange\r
110 * Determines whether or not clicking on the Slider axis will change the slider.\r
111 */\r
112 clickToChange : true,\r
113\r
114 /**\r
115 * @cfg {Object/Boolean} animate\r
116 * Turn on or off animation. May be an animation configuration object:\r
117 *\r
118 * animate: {\r
119 * duration: 3000,\r
120 * easing: 'easeIn'\r
121 * }\r
122 */\r
123 animate: true,\r
124\r
125 /**\r
126 * @property {Boolean} dragging\r
127 * True while the thumb is in a drag operation\r
128 */\r
129 dragging: false,\r
130\r
131 /**\r
132 * @cfg {Boolean} constrainThumbs\r
133 * True to disallow thumbs from overlapping one another.\r
134 */\r
135 constrainThumbs: true,\r
136\r
137 /**\r
138 * @cfg {Object/Boolean} useTips\r
139 * True to use an {@link Ext.slider.Tip} to display tips for the value. This option may also\r
140 * provide a configuration object for an {@link Ext.slider.Tip}.\r
141 */\r
142 useTips : true,\r
143\r
144 /**\r
145 * @cfg {Function} [tipText=undefined]\r
146 * A function used to display custom text for the slider tip.\r
147 *\r
148 * Defaults to null, which will use the default on the plugin.\r
149 *\r
150 * @cfg {Ext.slider.Thumb} tipText.thumb The Thumb that the Tip is attached to\r
151 * @cfg {String} tipText.return The text to display in the tip\r
152 */\r
153 tipText : null,\r
154 \r
155 /**\r
156 * @inheritdoc\r
157 */\r
158 defaultBindProperty: 'values',\r
159\r
160 /**\r
161 * @event beforechange\r
162 * Fires before the slider value is changed. By returning false from an event handler, you can cancel the\r
163 * event and prevent the slider from changing.\r
164 * @param {Ext.slider.Multi} slider The slider\r
165 * @param {Number} newValue The new value which the slider is being changed to.\r
166 * @param {Number} oldValue The old value which the slider was previously.\r
167 */\r
168\r
169 /**\r
170 * @event change\r
171 * Fires when the slider value is changed.\r
172 * @param {Ext.slider.Multi} slider The slider\r
173 * @param {Number} newValue The new value which the slider has been changed to.\r
174 * @param {Ext.slider.Thumb} thumb The thumb that was changed\r
175 */\r
176\r
177 /**\r
178 * @event changecomplete\r
179 * Fires when the slider value is changed by the user and any drag operations have completed.\r
180 * @param {Ext.slider.Multi} slider The slider\r
181 * @param {Number} newValue The new value which the slider has been changed to.\r
182 * @param {Ext.slider.Thumb} thumb The thumb that was changed\r
183 */\r
184\r
185 /**\r
186 * @event dragstart\r
187 * Fires after a drag operation has started.\r
188 * @param {Ext.slider.Multi} slider The slider\r
189 * @param {Ext.event.Event} e The event fired from Ext.dd.DragTracker\r
190 */\r
191\r
192 /**\r
193 * @event drag\r
194 * Fires continuously during the drag operation while the mouse is moving.\r
195 * @param {Ext.slider.Multi} slider The slider\r
196 * @param {Ext.event.Event} e The event fired from Ext.dd.DragTracker\r
197 */\r
198\r
199 /**\r
200 * @event dragend\r
201 * Fires after the drag operation has completed.\r
202 * @param {Ext.slider.Multi} slider The slider\r
203 * @param {Ext.event.Event} e The event fired from Ext.dd.DragTracker\r
204 */\r
205\r
206 ariaRole: 'slider',\r
207\r
208 focusable: true,\r
209 needArrowKeys: true,\r
210 tabIndex: 0,\r
211 \r
212 focusCls: 'slider-focus',\r
213\r
214 childEls: [\r
215 'endEl', 'innerEl'\r
216 ],\r
217\r
218 // note: {id} here is really {inputId}, but {cmpId} is available\r
219 fieldSubTpl: [\r
220 '<div id="{id}" data-ref="inputEl" {inputAttrTpl}',\r
221 ' class="', Ext.baseCSSPrefix, 'slider {fieldCls} {vertical}',\r
222 '{childElCls}"',\r
223 '<tpl if="tabIdx != null"> tabindex="{tabIdx}"</tpl>',\r
224 '<tpl foreach="inputElAriaAttributes"> {$}="{.}"</tpl>',\r
225 '>',\r
226 '<div id="{cmpId}-endEl" data-ref="endEl" class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',\r
227 '<div id="{cmpId}-innerEl" data-ref="innerEl" class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',\r
228 '{%this.renderThumbs(out, values)%}',\r
229 '</div>',\r
230 '</div>',\r
231 '</div>',\r
232 {\r
233 renderThumbs: function(out, values) {\r
234 var me = values.$comp,\r
235 i = 0,\r
236 thumbs = me.thumbs,\r
237 len = thumbs.length,\r
238 thumb,\r
239 thumbConfig;\r
240\r
241 for (; i < len; i++) {\r
242 thumb = thumbs[i];\r
243 thumbConfig = thumb.getElConfig();\r
244 thumbConfig.id = me.id + '-thumb-' + i;\r
245 Ext.DomHelper.generateMarkup(thumbConfig, out);\r
246 }\r
247 },\r
248 disableFormats: true\r
249 }\r
250 ],\r
251\r
252 horizontalProp: 'left',\r
253\r
254 initValue: function() {\r
255 var me = this,\r
256 extValueFrom = Ext.valueFrom,\r
257 // Fallback for initial values: values config -> value config -> minValue config -> 0\r
258 values = extValueFrom(me.values, [extValueFrom(me.value, extValueFrom(me.minValue, 0))]),\r
259 i = 0,\r
260 len = values.length;\r
261\r
262 // Store for use in dirty check\r
263 me.originalValue = values;\r
264\r
265 // Add a thumb for each value, enforcing configured constraints\r
266 for (; i < len; i++) {\r
267 me.addThumb(me.normalizeValue(values[i]));\r
268 }\r
269 },\r
270\r
271 initComponent: function() {\r
272 var me = this,\r
273 tipPlug,\r
274 hasTip,\r
275 p, pLen, plugins;\r
276\r
277 /**\r
278 * @property {Array} thumbs\r
279 * Array containing references to each thumb\r
280 */\r
281 me.thumbs = [];\r
282\r
283 me.keyIncrement = Math.max(me.increment, me.keyIncrement);\r
284\r
285 me.extraFieldBodyCls = Ext.baseCSSPrefix + 'slider-ct-' + (me.vertical ? 'vert' : 'horz');\r
286\r
287 me.callParent();\r
288\r
289 // only can use it if it exists.\r
290 if (me.useTips) {\r
291 if (Ext.isObject(me.useTips)) {\r
292 tipPlug = Ext.apply({}, me.useTips);\r
293 } else {\r
294 tipPlug = me.tipText ? {getText: me.tipText} : {};\r
295 }\r
296\r
297 plugins = me.plugins = me.plugins || [];\r
298 pLen = plugins.length;\r
299\r
300 for (p = 0; p < pLen; p++) {\r
301 if (plugins[p].isSliderTip) {\r
302 hasTip = true;\r
303 break;\r
304 }\r
305 }\r
306\r
307 if (!hasTip) {\r
308 me.plugins.push(new Ext.slider.Tip(tipPlug));\r
309 }\r
310 }\r
311 },\r
312\r
313 /**\r
314 * Creates a new thumb and adds it to the slider\r
315 * @param {Number} [value=0] The initial value to set on the thumb.\r
316 * @return {Ext.slider.Thumb} The thumb\r
317 */\r
318 addThumb: function(value) {\r
319 var me = this,\r
320 thumb = new Ext.slider.Thumb({\r
321 ownerCt : me,\r
322 value : value,\r
323 slider : me,\r
324 index : me.thumbs.length,\r
325 constrain : me.constrainThumbs,\r
326 disabled : !!me.readOnly\r
327 });\r
328\r
329 me.thumbs.push(thumb);\r
330\r
331 //render the thumb now if needed\r
332 if (me.rendered) {\r
333 thumb.render();\r
334 }\r
335\r
336 return thumb;\r
337 },\r
338\r
339 /**\r
340 * @private\r
341 * Moves the given thumb above all other by increasing its z-index. This is called when as drag\r
342 * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is\r
343 * required when the thumbs are stacked on top of each other at one of the ends of the slider's\r
344 * range, which can result in the user not being able to move any of them.\r
345 * @param {Ext.slider.Thumb} topThumb The thumb to move to the top\r
346 */\r
347 promoteThumb: function(topThumb) {\r
348 var thumbs = this.thumbStack || (this.thumbStack = Ext.Array.slice(this.thumbs)),\r
349 ln = thumbs.length,\r
350 zIndex = 10000, i;\r
351\r
352 // Move topthumb to position zero\r
353 if (thumbs[0] !== topThumb) {\r
354 Ext.Array.remove(thumbs, topThumb);\r
355 thumbs.unshift(topThumb);\r
356 }\r
357\r
358 // Then shuffle the zIndices\r
359 for (i = 0; i < ln; i++) {\r
360 thumbs[i].el.setStyle('zIndex', zIndex);\r
361 zIndex -= 1000;\r
362 }\r
363 },\r
364\r
365 getSubTplData : function(fieldData) {\r
366 var me = this,\r
367 data, ariaAttr;\r
368\r
369 data = Ext.apply(me.callParent([fieldData]), {\r
370 $comp: me,\r
371 vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',\r
372 minValue: me.minValue,\r
373 maxValue: me.maxValue,\r
374 value: me.value,\r
375 tabIdx: me.tabIndex,\r
376 childElCls: ''\r
377 });\r
378 \r
379 ariaAttr = data.inputElAriaAttributes;\r
380 \r
381 if (ariaAttr) {\r
382 ariaAttr['aria-orientation'] = me.vertical ? 'vertical' : 'horizontal';\r
383 ariaAttr['aria-valuemin'] = me.minValue;\r
384 ariaAttr['aria-valuemax'] = me.maxValue;\r
385 ariaAttr['aria-valuenow'] = me.value;\r
386 }\r
387 \r
388 return data;\r
389 },\r
390\r
391 onRender : function() {\r
392 var me = this,\r
393 thumbs = me.thumbs,\r
394 len = thumbs.length,\r
395 i = 0,\r
396 thumb;\r
397\r
398 me.callParent(arguments);\r
399\r
400 for (i = 0; i < len; i++) {\r
401 thumb = thumbs[i];\r
402 thumb.el = me.el.getById(me.id + '-thumb-' + i);\r
403 thumb.onRender();\r
404 }\r
405 },\r
406\r
407 /**\r
408 * @private\r
409 * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.\r
410 */\r
411 initEvents : function() {\r
412 var me = this;\r
413 \r
414 me.callParent();\r
415 \r
416 me.mon(me.el, {\r
417 scope : me,\r
418 mousedown: me.onMouseDown,\r
419 keydown : me.onKeyDown\r
420 });\r
421 },\r
422\r
423 onDragStart: Ext.emptyFn,\r
424 onDragEnd: Ext.emptyFn,\r
425\r
426 /**\r
427 * @private\r
428 * 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
429 * calculate how many pixels **from the slider origin** (left for horizontal Sliders and bottom for vertical Sliders) that point is.\r
430 *\r
431 * If the point is outside the range of the Slider's track, the return value is `undefined`\r
432 * @param {Number[]} xy The point to calculate the track point for\r
433 */\r
434 getTrackpoint : function(xy) {\r
435 var me = this,\r
436 vertical = me.vertical,\r
437 sliderTrack = me.innerEl,\r
438 trackLength, result,\r
439 positionProperty;\r
440\r
441 if (vertical) {\r
442 positionProperty = 'top';\r
443 trackLength = sliderTrack.getHeight();\r
444 } else {\r
445 positionProperty = me.horizontalProp;\r
446 trackLength = sliderTrack.getWidth();\r
447 }\r
448 xy = me.transformTrackPoints(sliderTrack.translatePoints(xy));\r
449 result = Ext.Number.constrain(xy[positionProperty], 0, trackLength);\r
450 return vertical ? trackLength - result : result;\r
451 },\r
452\r
453 transformTrackPoints: Ext.identityFn,\r
454 \r
455 // Base field checkChange method will fire 'change' event with signature common to all fields,\r
456 // but Slider fires the same event with different signature. Hence we disable checkChange here\r
457 // to avoid breakage.\r
458 checkChange: Ext.emptyFn,\r
459\r
460 /**\r
461 * @private\r
462 * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',\r
463 * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb\r
464 * @param {Ext.event.Event} e The click event\r
465 */\r
466 onMouseDown : function(e) {\r
467 var me = this,\r
468 thumbClicked = false,\r
469 i = 0,\r
470 thumbs = me.thumbs,\r
471 len = thumbs.length,\r
472 trackPoint;\r
473\r
474 if (me.disabled) {\r
475 return;\r
476 }\r
477\r
478 //see if the click was on any of the thumbs\r
479 for (; !thumbClicked && i < len; i++) {\r
480 thumbClicked = thumbClicked || e.target === thumbs[i].el.dom;\r
481 }\r
482\r
483 // Focus ourselves before setting the value. This allows other\r
484 // fields that have blur handlers (for example, date/number field)\r
485 // to take care of themselves first. This is important for\r
486 // databinding.\r
487 me.focus();\r
488\r
489 if (me.clickToChange && !thumbClicked) {\r
490 trackPoint = me.getTrackpoint(e.getXY());\r
491 if (trackPoint !== undefined) {\r
492 me.onClickChange(trackPoint);\r
493 }\r
494 }\r
495 },\r
496\r
497 /**\r
498 * @private\r
499 * Moves the thumb to the indicated position.\r
500 * Only changes the value if the click was within this.clickRange.\r
501 * @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
502 */\r
503 onClickChange : function(trackPoint) {\r
504 var me = this,\r
505 thumb, index;\r
506\r
507 // How far along the track *from the origin* was the click.\r
508 // If vertical, the origin is the bottom of the slider track.\r
509\r
510 //find the nearest thumb to the click event\r
511 thumb = me.getNearest(trackPoint);\r
512 if (!thumb.disabled) {\r
513 index = thumb.index;\r
514 me.setValue(index, Ext.util.Format.round(me.reversePixelValue(trackPoint), me.decimalPrecision), undefined, true);\r
515 }\r
516 },\r
517\r
518 /**\r
519 * @private\r
520 * Returns the nearest thumb to a click event, along with its distance\r
521 * @param {Number} trackPoint local pixel position along the Slider's axis to find the Thumb for\r
522 * @return {Object} The closest thumb object and its distance from the click event\r
523 */\r
524 getNearest: function(trackPoint) {\r
525 var me = this,\r
526 clickValue = me.reversePixelValue(trackPoint),\r
527 nearestDistance = me.getRange() + 5, //add a small fudge for the end of the slider\r
528 nearest = null,\r
529 thumbs = me.thumbs,\r
530 i = 0,\r
531 len = thumbs.length,\r
532 thumb,\r
533 value,\r
534 dist;\r
535\r
536 for (; i < len; i++) {\r
537 thumb = me.thumbs[i];\r
538 value = thumb.value;\r
539 dist = Math.abs(value - clickValue);\r
540\r
541 if (Math.abs(dist) <= nearestDistance) {\r
542 // this makes sure that thumbs will stay in order\r
543 if (nearest && nearest.value == value && value > clickValue && thumb.index > nearest.index) {\r
544 continue;\r
545 }\r
546 nearest = thumb;\r
547 nearestDistance = dist;\r
548 }\r
549 }\r
550 return nearest;\r
551 },\r
552\r
553 /**\r
554 * @private\r
555 * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right\r
556 * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction\r
557 * @param {Ext.event.Event} e The Event object\r
558 */\r
559 onKeyDown: function(e) {\r
560 var me = this,\r
561 ariaDom = me.ariaEl.dom,\r
562 k, val;\r
563 \r
564 k = e.getKey();\r
565\r
566 /*\r
567 * The behaviour for keyboard handling with multiple thumbs is currently undefined.\r
568 * There's no real sane default for it, so leave it like this until we come up\r
569 * with a better way of doing it.\r
570 */\r
571 if (me.disabled || me.thumbs.length !== 1) {\r
572 // Must not mingle with the Tab key!\r
573 if (k !== e.TAB) {\r
574 e.preventDefault();\r
575 }\r
576 \r
577 return;\r
578 }\r
579\r
580 switch (k) {\r
581 case e.UP:\r
582 case e.RIGHT:\r
583 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;\r
584 break;\r
585 \r
586 case e.DOWN:\r
587 case e.LEFT:\r
588 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;\r
589 break;\r
590 \r
591 case e.HOME:\r
592 val = me.minValue;\r
593 break;\r
594 \r
595 case e.END:\r
596 val = me.maxValue;\r
597 break;\r
598 \r
599 case e.PAGE_UP:\r
600 val = me.getValue(0) + me.pageSize;\r
601 break;\r
602 \r
603 case e.PAGE_DOWN:\r
604 val = me.getValue(0) - me.pageSize;\r
605 break;\r
606 }\r
607 \r
608 if (val !== undefined) {\r
609 e.stopEvent();\r
610 \r
611 val = me.normalizeValue(val);\r
612 \r
613 me.setValue(0, val, undefined, true);\r
614 \r
615 if (ariaDom) {\r
616 ariaDom.setAttribute('aria-valuenow', val);\r
617 }\r
618 }\r
619 },\r
620\r
621 /**\r
622 * @private\r
623 * Returns a snapped, constrained value when given a desired value\r
624 * @param {Number} value Raw number value\r
625 * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values\r
626 */\r
627 normalizeValue : function(v) {\r
628 var me = this,\r
629 snapFn = me.zeroBasedSnapping ? 'snap' : 'snapInRange';\r
630\r
631 v = Ext.Number[snapFn](v, me.increment, me.minValue, me.maxValue);\r
632 v = Ext.util.Format.round(v, me.decimalPrecision);\r
633 v = Ext.Number.constrain(v, me.minValue, me.maxValue);\r
634 return v;\r
635 },\r
636\r
637 /**\r
638 * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current\r
639 * value will be changed.\r
640 * @param {Number} val The new minimum value\r
641 */\r
642 setMinValue: function(val) {\r
643 var me = this,\r
644 thumbs = me.thumbs,\r
645 len = thumbs.length,\r
646 ariaDom = me.ariaEl.dom,\r
647 thumb, i;\r
648\r
649 me.minValue = val;\r
650\r
651 for (i = 0; i < len; ++i) {\r
652 thumb = thumbs[i];\r
653 if (thumb.value < val) {\r
654 me.setValue(i, val, false);\r
655 }\r
656 }\r
657 \r
658 if (ariaDom) {\r
659 ariaDom.setAttribute('aria-valuemin', val);\r
660 }\r
661 \r
662 me.syncThumbs();\r
663 },\r
664\r
665 /**\r
666 * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current\r
667 * value will be changed.\r
668 * @param {Number} val The new maximum value\r
669 */\r
670 setMaxValue: function(val) {\r
671 var me = this,\r
672 thumbs = me.thumbs,\r
673 len = thumbs.length,\r
674 ariaDom = me.ariaEl.dom,\r
675 thumb, i;\r
676\r
677 me.maxValue = val;\r
678\r
679 for (i = 0; i < len; ++i) {\r
680 thumb = thumbs[i];\r
681 if (thumb.value > val) {\r
682 me.setValue(i, val, false);\r
683 }\r
684 }\r
685 \r
686 if (ariaDom) {\r
687 ariaDom.setAttribute('aria-valuemax', val);\r
688 }\r
689 \r
690 me.syncThumbs();\r
691 },\r
692\r
693 /**\r
694 * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and\r
695 * maxValue.\r
696 *\r
697 * Setting the second slider's value without animation:\r
698 *\r
699 * mySlider.setValue(1, 50, false);\r
700 *\r
701 * Setting multiple values with animation:\r
702 *\r
703 * mySlider.setValue([20, 40, 60], true);\r
704 *\r
705 * @param {Number/Number[]} index Index of the thumb to move. Alternatively, it can be an array of values to set\r
706 * for each thumb in the slider.\r
707 * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)\r
708 * @param {Object/Boolean} [animate] `false` to not animate. `true` to use the default animation. This may also be an\r
709 * animate configuration object, see {@link #cfg-animate}. If this configuration is omitted, the {@link #cfg-animate} configuration\r
710 * will be used.\r
711 * @return {Ext.slider.Multi} this\r
712 */\r
713 setValue : function(index, value, animate, changeComplete) {\r
714 var me = this,\r
715 thumbs = me.thumbs,\r
716 ariaDom = me.ariaEl.dom,\r
717 thumb, len, i, values;\r
718\r
719 if (Ext.isArray(index)) {\r
720 values = index;\r
721 animate = value;\r
722\r
723 for (i = 0, len = values.length; i < len; ++i) {\r
724 thumb = thumbs[i];\r
725 if (thumb) {\r
726 me.setValue(i, values[i], animate);\r
727 }\r
728 }\r
729 return me;\r
730 }\r
731\r
732 thumb = me.thumbs[index];\r
733 // ensures value is contstrained and snapped\r
734 value = me.normalizeValue(value);\r
735\r
736 if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {\r
737 thumb.value = value;\r
738 if (me.rendered) {\r
739 if (Ext.isDefined(animate)) {\r
740 animate = animate === false ? false : animate;\r
741 } else {\r
742 animate = me.animate;\r
743 }\r
744 thumb.move(me.calculateThumbPosition(value), animate);\r
745 \r
746 // At this moment we can only handle one thumb wrt ARIA\r
747 if (index === 0 && ariaDom) {\r
748 ariaDom.setAttribute('aria-valuenow', value);\r
749 }\r
750\r
751 me.fireEvent('change', me, value, thumb);\r
752 me.checkDirty();\r
753\r
754 if (changeComplete) {\r
755 me.fireEvent('changecomplete', me, value, thumb);\r
756 }\r
757 }\r
758 }\r
759 return me;\r
760 },\r
761\r
762 /**\r
763 * @private\r
764 * Given a value within this Slider's range, calculates a Thumb's percentage CSS position to map that value.\r
765 */\r
766 calculateThumbPosition : function(v) {\r
767 var me = this,\r
768 minValue = me.minValue,\r
769 pos = (v - minValue) / me.getRange() * 100;\r
770\r
771 if (isNaN(pos)) {\r
772 pos = 0;\r
773 }\r
774\r
775 return pos;\r
776 },\r
777\r
778 /**\r
779 * @private\r
780 * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,\r
781 * the ratio is 2\r
782 * @return {Number} The ratio of pixels to mapped values\r
783 */\r
784 getRatio : function() {\r
785 var me = this,\r
786 innerEl = me.innerEl,\r
787 trackLength = me.vertical ? innerEl.getHeight() : innerEl.getWidth(),\r
788 valueRange = me.getRange();\r
789\r
790 return valueRange === 0 ? trackLength : (trackLength / valueRange);\r
791 },\r
792\r
793 getRange: function(){\r
794 return this.maxValue - this.minValue;\r
795 },\r
796\r
797 /**\r
798 * @private\r
799 * Given a pixel location along the slider, returns the mapped slider value for that pixel.\r
800 * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reversePixelValue(50)\r
801 * returns 200\r
802 * @param {Number} pos The position along the slider to return a mapped value for\r
803 * @return {Number} The mapped value for the given position\r
804 */\r
805 reversePixelValue : function(pos) {\r
806 return this.minValue + (pos / this.getRatio());\r
807 },\r
808\r
809 /**\r
810 * @private\r
811 * Given a Thumb's percentage position along the slider, returns the mapped slider value for that pixel.\r
812 * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reversePercentageValue(25)\r
813 * returns 200\r
814 * @param {Number} pos The percentage along the slider track to return a mapped value for\r
815 * @return {Number} The mapped value for the given position\r
816 */\r
817 reversePercentageValue : function(pos) {\r
818 return this.minValue + this.getRange() * (pos / 100);\r
819 },\r
820\r
821 onDisable: function() {\r
822 var me = this,\r
823 i = 0,\r
824 thumbs = me.thumbs,\r
825 len = thumbs.length,\r
826 thumb,\r
827 el,\r
828 xy;\r
829\r
830 me.callParent();\r
831\r
832 for (; i < len; i++) {\r
833 thumb = thumbs[i];\r
834 el = thumb.el;\r
835\r
836 thumb.disable();\r
837\r
838 if(Ext.isIE) {\r
839 //IE breaks when using overflow visible and opacity other than 1.\r
840 //Create a place holder for the thumb and display it.\r
841 xy = el.getXY();\r
842 el.hide();\r
843\r
844 me.innerEl.addCls(me.disabledCls).dom.disabled = true;\r
845\r
846 if (!me.thumbHolder) {\r
847 me.thumbHolder = me.endEl.createChild({\r
848 role: 'presentation',\r
849 cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls\r
850 });\r
851 }\r
852\r
853 me.thumbHolder.show().setXY(xy);\r
854 }\r
855 }\r
856 },\r
857\r
858 onEnable: function() {\r
859 var me = this,\r
860 i = 0,\r
861 thumbs = me.thumbs,\r
862 len = thumbs.length,\r
863 thumb,\r
864 el;\r
865\r
866 this.callParent();\r
867\r
868 for (; i < len; i++) {\r
869 thumb = thumbs[i];\r
870 el = thumb.el;\r
871\r
872 thumb.enable();\r
873\r
874 if (Ext.isIE) {\r
875 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;\r
876\r
877 if (me.thumbHolder) {\r
878 me.thumbHolder.hide();\r
879 }\r
880\r
881 el.show();\r
882 me.syncThumbs();\r
883 }\r
884 }\r
885 },\r
886\r
887 /**\r
888 * Synchronizes thumbs position to the proper proportion of the total component width based on the current slider\r
889 * {@link #value}. This will be called automatically when the Slider is resized by a layout, but if it is rendered\r
890 * auto width, this method can be called from another resize handler to sync the Slider if necessary.\r
891 */\r
892 syncThumbs : function() {\r
893 if (this.rendered) {\r
894 var thumbs = this.thumbs,\r
895 length = thumbs.length,\r
896 i = 0;\r
897\r
898 for (; i < length; i++) {\r
899 thumbs[i].move(this.calculateThumbPosition(thumbs[i].value));\r
900 }\r
901 }\r
902 },\r
903\r
904 /**\r
905 * Returns the current value of the slider\r
906 * @param {Number} index The index of the thumb to return a value for\r
907 * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if\r
908 * no index is given.\r
909 */\r
910 getValue : function(index) {\r
911 return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();\r
912 },\r
913\r
914 /**\r
915 * Returns an array of values - one for the location of each thumb\r
916 * @return {Number[]} The set of thumb values\r
917 */\r
918 getValues: function() {\r
919 var values = [],\r
920 i = 0,\r
921 thumbs = this.thumbs,\r
922 len = thumbs.length;\r
923\r
924 for (; i < len; i++) {\r
925 values.push(thumbs[i].value);\r
926 }\r
927\r
928 return values;\r
929 },\r
930\r
931 getSubmitValue: function() {\r
932 var me = this;\r
933 return (me.disabled || !me.submitValue) ? null : me.getValue();\r
934 },\r
935\r
936 reset: function() {\r
937 var me = this,\r
938 arr = [].concat(me.originalValue),\r
939 a = 0,\r
940 aLen = arr.length,\r
941 val;\r
942\r
943 for (; a < aLen; a++) {\r
944 val = arr[a];\r
945\r
946 me.setValue(a, val);\r
947 }\r
948\r
949 me.clearInvalid();\r
950 // delete here so we reset back to the original state\r
951 delete me.wasValid;\r
952 },\r
953\r
954 setReadOnly: function(readOnly){\r
955 var me = this,\r
956 thumbs = me.thumbs,\r
957 len = thumbs.length,\r
958 i = 0;\r
959\r
960 me.callParent(arguments);\r
961 readOnly = me.readOnly;\r
962\r
963 for (; i < len; ++i) {\r
964 if (readOnly) {\r
965 thumbs[i].disable();\r
966 } else {\r
967 thumbs[i].enable();\r
968 }\r
969\r
970 }\r
971\r
972 },\r
973\r
974 beforeDestroy: function() {\r
975 var me = this,\r
976 thumbs = me.thumbs,\r
977 t = 0,\r
978 tLen = thumbs.length,\r
979 thumb;\r
980\r
981 if (me.rendered) {\r
982 for (; t < tLen; t++) {\r
983 thumb = thumbs[t];\r
984\r
985 Ext.destroy(thumb);\r
986 }\r
987 }\r
988\r
989 me.callParent();\r
990 }\r
991});\r