]>
Commit | Line | Data |
---|---|---|
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 | |
20 | Ext.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 |