4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
17 <body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-slider-Multi'>/**
19 </span> * @class Ext.slider.Multi
20 * @extends Ext.form.field.Base
21 * <p>Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis
22 * clicking and animation. Can be added as an item to any container. In addition,
23 * {@img Ext.slider.Multi/Ext.slider.Multi.png Ext.slider.Multi component}
24 * <p>Example usage:</p>
25 * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
27 Ext.create('Ext.slider.Multi', {
34 //this defaults to true, setting to false allows the thumbs to pass each other
35 {@link #constrainThumbs}: false,
36 renderTo: Ext.getBody()
40 Ext.define('Ext.slider.Multi', {
41 extend: 'Ext.form.field.Base',
42 alias: 'widget.multislider',
43 alternateClassName: 'Ext.slider.MultiSlider',
51 'Ext.layout.component.field.Slider'
55 '<div class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
56 '<div class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
57 '<div class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
58 '<a class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
68 <span id='Ext-slider-Multi-cfg-value'> /**
69 </span> * @cfg {Number} value
70 * A value with which to initialize the slider. Defaults to minValue. Setting this will only
71 * result in the creation of a single slider thumb; if you want multiple thumbs then use the
72 * {@link #values} config instead.
75 <span id='Ext-slider-Multi-cfg-values'> /**
76 </span> * @cfg {Array} values
77 * Array of Number values with which to initalize the slider. A separate slider thumb will be created for
78 * each value in this array. This will take precedence over the single {@link #value} config.
81 <span id='Ext-slider-Multi-cfg-vertical'> /**
82 </span> * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
85 <span id='Ext-slider-Multi-cfg-minValue'> /**
86 </span> * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
89 <span id='Ext-slider-Multi-cfg-maxValue'> /**
90 </span> * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
93 <span id='Ext-slider-Multi-cfg-decimalPrecision'> /**
94 </span> * @cfg {Number/Boolean} decimalPrecision.
95 * <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
96 * <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
99 <span id='Ext-slider-Multi-cfg-keyIncrement'> /**
100 </span> * @cfg {Number} keyIncrement How many units to change the Slider when adjusting with keyboard navigation. Defaults to 1. If the increment config is larger, it will be used instead.
103 <span id='Ext-slider-Multi-cfg-increment'> /**
104 </span> * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
108 <span id='Ext-slider-Multi-property-clickRange'> /**
110 * @property clickRange
112 * 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],
113 * 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'
114 * 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
118 <span id='Ext-slider-Multi-cfg-clickToChange'> /**
119 </span> * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
121 clickToChange : true,
122 <span id='Ext-slider-Multi-cfg-animate'> /**
123 </span> * @cfg {Boolean} animate Turn on or off animation. Defaults to true
127 <span id='Ext-slider-Multi-property-dragging'> /**
128 </span> * True while the thumb is in a drag operation
133 <span id='Ext-slider-Multi-cfg-constrainThumbs'> /**
134 </span> * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
136 constrainThumbs: true,
138 componentLayout: 'sliderfield',
140 <span id='Ext-slider-Multi-cfg-useTips'> /**
141 </span> * @cfg {Boolean} useTips
142 * True to use an Ext.slider.Tip to display tips for the value. Defaults to <tt>true</tt>.
146 <span id='Ext-slider-Multi-cfg-tipText'> /**
147 </span> * @cfg {Function} tipText
148 * A function used to display custom text for the slider tip. Defaults to <tt>null</tt>, which will
149 * use the default on the plugin.
156 initValue: function() {
158 extValue = Ext.value,
159 // Fallback for initial values: values config -> value config -> minValue config -> 0
160 values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
164 // Store for use in dirty check
165 me.originalValue = values;
167 // Add a thumb for each value
168 for (; i < len; i++) {
169 me.addThumb(values[i]);
174 initComponent : function() {
179 <span id='Ext-slider-Multi-property-thumbs'> /**
180 </span> * @property thumbs
182 * Array containing references to each thumb
186 me.keyIncrement = Math.max(me.increment, me.keyIncrement);
189 <span id='Ext-slider-Multi-event-beforechange'> /**
190 </span> * @event beforechange
191 * Fires before the slider value is changed. By returning false from an event handler,
192 * you can cancel the event and prevent the slider from changing.
193 * @param {Ext.slider.Multi} slider The slider
194 * @param {Number} newValue The new value which the slider is being changed to.
195 * @param {Number} oldValue The old value which the slider was previously.
199 <span id='Ext-slider-Multi-event-change'> /**
200 </span> * @event change
201 * Fires when the slider value is changed.
202 * @param {Ext.slider.Multi} slider The slider
203 * @param {Number} newValue The new value which the slider has been changed to.
204 * @param {Ext.slider.Thumb} thumb The thumb that was changed
208 <span id='Ext-slider-Multi-event-changecomplete'> /**
209 </span> * @event changecomplete
210 * Fires when the slider value is changed by the user and any drag operations have completed.
211 * @param {Ext.slider.Multi} slider The slider
212 * @param {Number} newValue The new value which the slider has been changed to.
213 * @param {Ext.slider.Thumb} thumb The thumb that was changed
217 <span id='Ext-slider-Multi-event-dragstart'> /**
218 </span> * @event dragstart
219 * Fires after a drag operation has started.
220 * @param {Ext.slider.Multi} slider The slider
221 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
225 <span id='Ext-slider-Multi-event-drag'> /**
226 </span> * @event drag
227 * Fires continuously during the drag operation while the mouse is moving.
228 * @param {Ext.slider.Multi} slider The slider
229 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
233 <span id='Ext-slider-Multi-event-dragend'> /**
234 </span> * @event dragend
235 * Fires after the drag operation has completed.
236 * @param {Ext.slider.Multi} slider The slider
237 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
243 Ext.apply(me, Ext.slider.Multi.Vertical);
248 // only can use it if it exists.
250 tipPlug = me.tipText ? {getText: me.tipText} : {};
251 me.plugins = me.plugins || [];
252 Ext.each(me.plugins, function(plug){
253 if (plug.isSliderTip) {
259 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
264 <span id='Ext-slider-Multi-method-addThumb'> /**
265 </span> * Creates a new thumb and adds it to the slider
266 * @param {Number} value The initial value to set on the thumb. Defaults to 0
267 * @return {Ext.slider.Thumb} The thumb
269 addThumb: function(value) {
271 thumb = Ext.create('Ext.slider.Thumb', {
274 index : me.thumbs.length,
275 constrain: me.constrainThumbs
277 me.thumbs.push(thumb);
279 //render the thumb now if needed
287 <span id='Ext-slider-Multi-method-promoteThumb'> /**
289 * Moves the given thumb above all other by increasing its z-index. This is called when as drag
290 * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
291 * required when the thumbs are stacked on top of each other at one of the ends of the slider's
292 * range, which can result in the user not being able to move any of them.
293 * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
295 promoteThumb: function(topThumb) {
296 var thumbs = this.thumbs,
300 for (i = 0; i < ln; i++) {
303 if (thumb == topThumb) {
304 thumb.bringToFront();
312 onRender : function() {
319 Ext.applyIf(me.subTplData, {
320 vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
321 minValue: me.minValue,
322 maxValue: me.maxValue,
326 Ext.applyIf(me.renderSelectors, {
327 endEl: '.' + Ext.baseCSSPrefix + 'slider-end',
328 innerEl: '.' + Ext.baseCSSPrefix + 'slider-inner',
329 focusEl: '.' + Ext.baseCSSPrefix + 'slider-focus'
332 me.callParent(arguments);
335 for (; i < len; i++) {
339 //calculate the size of half a thumb
340 thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
341 me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
345 <span id='Ext-slider-Multi-method-onChange'> /**
346 </span> * Utility method to set the value of the field when the slider changes.
347 * @param {Object} slider The slider object.
348 * @param {Object} v The new value.
351 onChange : function(slider, v) {
352 this.setValue(v, undefined, true);
355 <span id='Ext-slider-Multi-method-initEvents'> /**
357 * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
359 initEvents : function() {
364 mousedown: me.onMouseDown,
365 keydown : me.onKeyDown,
369 me.focusEl.swallowEvent("click", true);
372 <span id='Ext-slider-Multi-method-onMouseDown'> /**
374 * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
375 * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
376 * @param {Ext.EventObject} e The click event
378 onMouseDown : function(e) {
380 thumbClicked = false,
390 //see if the click was on any of the thumbs
391 for (; i < len; i++) {
392 thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
395 if (me.clickToChange && !thumbClicked) {
396 local = me.innerEl.translatePoints(e.getXY());
397 me.onClickChange(local);
402 <span id='Ext-slider-Multi-method-onClickChange'> /**
404 * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
405 * Only changes the value if the click was within this.clickRange.
406 * @param {Object} local Object containing top and left values for the click event.
408 onClickChange : function(local) {
412 if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
413 //find the nearest thumb to the click event
414 thumb = me.getNearest(local, 'left');
415 if (!thumb.disabled) {
417 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
422 <span id='Ext-slider-Multi-method-getNearest'> /**
424 * Returns the nearest thumb to a click event, along with its distance
425 * @param {Object} local Object containing top and left values from a click event
426 * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
427 * @return {Object} The closest thumb object and its distance from the click event
429 getNearest: function(local, prop) {
431 localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
432 clickValue = me.reverseValue(localValue),
433 nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
443 for (; i < len; i++) {
444 thumb = me.thumbs[i];
446 dist = Math.abs(value - clickValue);
448 if (Math.abs(dist <= nearestDistance)) {
451 nearestDistance = dist;
457 <span id='Ext-slider-Multi-method-onKeyDown'> /**
459 * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
460 * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
461 * @param {Ext.EventObject} e The Event object
463 onKeyDown : function(e) {
465 * The behaviour for keyboard handling with multiple thumbs is currently undefined.
466 * There's no real sane default for it, so leave it like this until we come up
467 * with a better way of doing it.
473 if(me.disabled || me.thumbs.length !== 1) {
483 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
484 me.setValue(0, val, undefined, true);
489 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
490 me.setValue(0, val, undefined, true);
498 afterRender : function() {
506 me.callParent(arguments);
508 for (; i < len; i++) {
511 if (thumb.value !== undefined) {
512 v = me.normalizeValue(thumb.value);
513 if (v !== thumb.value) {
514 // delete this.value;
515 me.setValue(i, v, false);
517 thumb.move(me.translateValue(v), false);
523 <span id='Ext-slider-Multi-method-getRatio'> /**
525 * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
527 * @return {Number} The ratio of pixels to mapped values
529 getRatio : function() {
530 var w = this.innerEl.getWidth(),
531 v = this.maxValue - this.minValue;
532 return v === 0 ? w : (w/v);
535 <span id='Ext-slider-Multi-method-normalizeValue'> /**
537 * Returns a snapped, constrained value when given a desired value
538 * @param {Number} value Raw number value
539 * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
541 normalizeValue : function(v) {
544 v = Ext.Number.snap(v, this.increment, this.minValue, this.maxValue);
545 v = Ext.util.Format.round(v, me.decimalPrecision);
546 v = Ext.Number.constrain(v, me.minValue, me.maxValue);
550 <span id='Ext-slider-Multi-method-setMinValue'> /**
551 </span> * Sets the minimum value for the slider instance. If the current value is less than the
552 * minimum value, the current value will be changed.
553 * @param {Number} val The new minimum value
555 setMinValue : function(val) {
563 me.inputEl.dom.setAttribute('aria-valuemin', val);
565 for (; i < len; ++i) {
567 t.value = t.value < val ? val : t.value;
572 <span id='Ext-slider-Multi-method-setMaxValue'> /**
573 </span> * Sets the maximum value for the slider instance. If the current value is more than the
574 * maximum value, the current value will be changed.
575 * @param {Number} val The new maximum value
577 setMaxValue : function(val) {
585 me.inputEl.dom.setAttribute('aria-valuemax', val);
587 for (; i < len; ++i) {
589 t.value = t.value > val ? val : t.value;
594 <span id='Ext-slider-Multi-method-setValue'> /**
595 </span> * Programmatically sets the value of the Slider. Ensures that the value is constrained within
596 * the minValue and maxValue.
597 * @param {Number} index Index of the thumb to move
598 * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
599 * @param {Boolean} animate Turn on or off animation, defaults to true
601 setValue : function(index, value, animate, changeComplete) {
603 thumb = me.thumbs[index];
605 // ensures value is contstrained and snapped
606 value = me.normalizeValue(value);
608 if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
611 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
612 // Perhaps this should go on each thumb element rather than the outer element.
614 'aria-valuenow': value,
615 'aria-valuetext': value
618 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
620 me.fireEvent('change', me, value, thumb);
621 if (changeComplete) {
622 me.fireEvent('changecomplete', me, value, thumb);
628 <span id='Ext-slider-Multi-method-translateValue'> /**
631 translateValue : function(v) {
632 var ratio = this.getRatio();
633 return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
636 <span id='Ext-slider-Multi-method-reverseValue'> /**
638 * Given a pixel location along the slider, returns the mapped slider value for that pixel.
639 * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
641 * @param {Number} pos The position along the slider to return a mapped value for
642 * @return {Number} The mapped value for the given position
644 reverseValue : function(pos) {
645 var ratio = this.getRatio();
646 return (pos + (this.minValue * ratio)) / ratio;
651 this.focusEl.focus(10);
655 onDisable: function() {
666 for (; i < len; i++) {
673 //IE breaks when using overflow visible and opacity other than 1.
674 //Create a place holder for the thumb and display it.
678 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
680 if (!me.thumbHolder) {
681 me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
684 me.thumbHolder.show().setXY(xy);
690 onEnable: function() {
700 for (; i < len; i++) {
707 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
709 if (me.thumbHolder) {
710 me.thumbHolder.hide();
719 <span id='Ext-slider-Multi-method-syncThumbs'> /**
720 </span> * Synchronizes thumbs position to the proper proportion of the total component width based
721 * on the current slider {@link #value}. This will be called automatically when the Slider
722 * is resized by a layout, but if it is rendered auto width, this method can be called from
723 * another resize handler to sync the Slider if necessary.
725 syncThumbs : function() {
727 var thumbs = this.thumbs,
728 length = thumbs.length,
731 for (; i < length; i++) {
732 thumbs[i].move(this.translateValue(thumbs[i].value));
737 <span id='Ext-slider-Multi-method-getValue'> /**
738 </span> * Returns the current value of the slider
739 * @param {Number} index The index of the thumb to return a value for
740 * @return {Number/Array} The current value of the slider at the given index, or an array of
741 * all thumb values if no index is given.
743 getValue : function(index) {
744 return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
747 <span id='Ext-slider-Multi-method-getValues'> /**
748 </span> * Returns an array of values - one for the location of each thumb
749 * @return {Array} The set of thumb values
751 getValues: function() {
754 thumbs = this.thumbs,
757 for (; i < len; i++) {
758 values.push(thumbs[i].value);
764 getSubmitValue: function() {
766 return (me.disabled || !me.submitValue) ? null : me.getValue();
772 Array.forEach(Array.from(me.originalValue), function(val, i) {
776 // delete here so we reset back to the original state
781 beforeDestroy : function() {
784 Ext.destroyMembers(me.innerEl, me.endEl, me.focusEl);
785 Ext.each(me.thumbs, function(thumb) {
793 // Method overrides to support slider with vertical orientation
795 getRatio : function() {
796 var h = this.innerEl.getHeight(),
797 v = this.maxValue - this.minValue;
801 onClickChange : function(local) {
803 thumb, index, bottom;
805 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
806 thumb = me.getNearest(local, 'top');
807 if (!thumb.disabled) {
809 bottom = me.reverseValue(me.innerEl.getHeight() - local.top);
811 me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);