4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../resources/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> * Slider which supports vertical or horizontal orientation, keyboard adjustments, configurable snapping, axis clicking
20 * and animation. Can be added as an item to any container.
22 * Sliders can be created with more than one thumb handle by passing an array of values instead of a single one:
25 * Ext.create('Ext.slider.Multi', {
27 * values: [25, 50, 75],
32 * // this defaults to true, setting to false allows the thumbs to pass each other
33 * constrainThumbs: false,
34 * renderTo: Ext.getBody()
37 Ext.define('Ext.slider.Multi', {
38 extend: 'Ext.form.field.Base',
39 alias: 'widget.multislider',
40 alternateClassName: 'Ext.slider.MultiSlider',
48 'Ext.layout.component.field.Slider'
51 // note: {id} here is really {inputId}, but {cmpId} is available
53 '<div id="{id}" class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
54 '<div id="{cmpId}-endEl" class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
55 '<div id="{cmpId}-innerEl" class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
56 '<a id="{cmpId}-focusEl" class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
66 <span id='Ext-slider-Multi-cfg-value'> /**
67 </span> * @cfg {Number} value
68 * A value with which to initialize the slider. Defaults to minValue. Setting this will only result in the creation
69 * of a single slider thumb; if you want multiple thumbs then use the {@link #values} config instead.
72 <span id='Ext-slider-Multi-cfg-values'> /**
73 </span> * @cfg {Number[]} values
74 * Array of Number values with which to initalize the slider. A separate slider thumb will be created for each value
75 * in this array. This will take precedence over the single {@link #value} config.
78 <span id='Ext-slider-Multi-cfg-vertical'> /**
79 </span> * @cfg {Boolean} vertical
80 * Orient the Slider vertically rather than horizontally.
84 <span id='Ext-slider-Multi-cfg-minValue'> /**
85 </span> * @cfg {Number} minValue
86 * The minimum value for the Slider.
90 <span id='Ext-slider-Multi-cfg-maxValue'> /**
91 </span> * @cfg {Number} maxValue
92 * The maximum value for the Slider.
96 <span id='Ext-slider-Multi-cfg-decimalPrecision'> /**
97 </span> * @cfg {Number/Boolean} decimalPrecision The number of decimal places to which to round the Slider's value.
99 * To disable rounding, configure as **false**.
103 <span id='Ext-slider-Multi-cfg-keyIncrement'> /**
104 </span> * @cfg {Number} keyIncrement
105 * How many units to change the Slider when adjusting with keyboard navigation. If the increment
106 * config is larger, it will be used instead.
110 <span id='Ext-slider-Multi-cfg-increment'> /**
111 </span> * @cfg {Number} increment
112 * How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
116 <span id='Ext-slider-Multi-property-clickRange'> /**
118 * @property {Number[]} clickRange
119 * 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],
120 * 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'
121 * 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
125 <span id='Ext-slider-Multi-cfg-clickToChange'> /**
126 </span> * @cfg {Boolean} clickToChange
127 * Determines whether or not clicking on the Slider axis will change the slider.
129 clickToChange : true,
131 <span id='Ext-slider-Multi-cfg-animate'> /**
132 </span> * @cfg {Boolean} animate
133 * Turn on or off animation.
137 <span id='Ext-slider-Multi-property-dragging'> /**
138 </span> * @property {Boolean} dragging
139 * True while the thumb is in a drag operation
143 <span id='Ext-slider-Multi-cfg-constrainThumbs'> /**
144 </span> * @cfg {Boolean} constrainThumbs
145 * True to disallow thumbs from overlapping one another.
147 constrainThumbs: true,
149 componentLayout: 'sliderfield',
151 <span id='Ext-slider-Multi-cfg-useTips'> /**
152 </span> * @cfg {Boolean} useTips
153 * True to use an Ext.slider.Tip to display tips for the value.
157 <span id='Ext-slider-Multi-cfg-tipText'> /**
158 </span> * @cfg {Function} tipText
159 * A function used to display custom text for the slider tip. Defaults to null, which will use the default on the
167 initValue: function() {
169 extValue = Ext.value,
170 // Fallback for initial values: values config -> value config -> minValue config -> 0
171 values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
175 // Store for use in dirty check
176 me.originalValue = values;
178 // Add a thumb for each value
179 for (; i < len; i++) {
180 me.addThumb(values[i]);
185 initComponent : function() {
190 <span id='Ext-slider-Multi-property-thumbs'> /**
191 </span> * @property {Array} thumbs
192 * Array containing references to each thumb
196 me.keyIncrement = Math.max(me.increment, me.keyIncrement);
199 <span id='Ext-slider-Multi-event-beforechange'> /**
200 </span> * @event beforechange
201 * Fires before the slider value is changed. By returning false from an event handler, you can cancel the
202 * event and prevent the slider from changing.
203 * @param {Ext.slider.Multi} slider The slider
204 * @param {Number} newValue The new value which the slider is being changed to.
205 * @param {Number} oldValue The old value which the slider was previously.
209 <span id='Ext-slider-Multi-event-change'> /**
210 </span> * @event change
211 * Fires when the slider value is changed.
212 * @param {Ext.slider.Multi} slider The slider
213 * @param {Number} newValue The new value which the slider has been changed to.
214 * @param {Ext.slider.Thumb} thumb The thumb that was changed
218 <span id='Ext-slider-Multi-event-changecomplete'> /**
219 </span> * @event changecomplete
220 * Fires when the slider value is changed by the user and any drag operations have completed.
221 * @param {Ext.slider.Multi} slider The slider
222 * @param {Number} newValue The new value which the slider has been changed to.
223 * @param {Ext.slider.Thumb} thumb The thumb that was changed
227 <span id='Ext-slider-Multi-event-dragstart'> /**
228 </span> * @event dragstart
229 * Fires after a drag operation has started.
230 * @param {Ext.slider.Multi} slider The slider
231 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
235 <span id='Ext-slider-Multi-event-drag'> /**
236 </span> * @event drag
237 * Fires continuously during the drag operation while the mouse is moving.
238 * @param {Ext.slider.Multi} slider The slider
239 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
243 <span id='Ext-slider-Multi-event-dragend'> /**
244 </span> * @event dragend
245 * Fires after the drag operation has completed.
246 * @param {Ext.slider.Multi} slider The slider
247 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
253 Ext.apply(me, Ext.slider.Multi.Vertical);
258 // only can use it if it exists.
260 tipPlug = me.tipText ? {getText: me.tipText} : {};
261 me.plugins = me.plugins || [];
262 Ext.each(me.plugins, function(plug){
263 if (plug.isSliderTip) {
269 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
274 <span id='Ext-slider-Multi-method-addThumb'> /**
275 </span> * Creates a new thumb and adds it to the slider
276 * @param {Number} value The initial value to set on the thumb. Defaults to 0
277 * @return {Ext.slider.Thumb} The thumb
279 addThumb: function(value) {
281 thumb = Ext.create('Ext.slider.Thumb', {
284 index : me.thumbs.length,
285 constrain: me.constrainThumbs
287 me.thumbs.push(thumb);
289 //render the thumb now if needed
297 <span id='Ext-slider-Multi-method-promoteThumb'> /**
299 * Moves the given thumb above all other by increasing its z-index. This is called when as drag
300 * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
301 * required when the thumbs are stacked on top of each other at one of the ends of the slider's
302 * range, which can result in the user not being able to move any of them.
303 * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
305 promoteThumb: function(topThumb) {
306 var thumbs = this.thumbs,
310 for (i = 0; i < ln; i++) {
313 if (thumb == topThumb) {
314 thumb.bringToFront();
322 onRender : function() {
329 Ext.applyIf(me.subTplData, {
330 vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
331 minValue: me.minValue,
332 maxValue: me.maxValue,
336 me.addChildEls('endEl', 'innerEl', 'focusEl');
338 me.callParent(arguments);
341 for (; i < len; i++) {
345 //calculate the size of half a thumb
346 thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
347 me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
351 <span id='Ext-slider-Multi-method-onChange'> /**
352 </span> * Utility method to set the value of the field when the slider changes.
353 * @param {Object} slider The slider object.
354 * @param {Object} v The new value.
357 onChange : function(slider, v) {
358 this.setValue(v, undefined, true);
361 <span id='Ext-slider-Multi-method-initEvents'> /**
363 * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
365 initEvents : function() {
370 mousedown: me.onMouseDown,
371 keydown : me.onKeyDown,
375 me.focusEl.swallowEvent("click", true);
378 <span id='Ext-slider-Multi-method-onMouseDown'> /**
380 * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
381 * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
382 * @param {Ext.EventObject} e The click event
384 onMouseDown : function(e) {
386 thumbClicked = false,
396 //see if the click was on any of the thumbs
397 for (; i < len; i++) {
398 thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
401 if (me.clickToChange && !thumbClicked) {
402 local = me.innerEl.translatePoints(e.getXY());
403 me.onClickChange(local);
408 <span id='Ext-slider-Multi-method-onClickChange'> /**
410 * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
411 * Only changes the value if the click was within this.clickRange.
412 * @param {Object} local Object containing top and left values for the click event.
414 onClickChange : function(local) {
418 if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
419 //find the nearest thumb to the click event
420 thumb = me.getNearest(local, 'left');
421 if (!thumb.disabled) {
423 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
428 <span id='Ext-slider-Multi-method-getNearest'> /**
430 * Returns the nearest thumb to a click event, along with its distance
431 * @param {Object} local Object containing top and left values from a click event
432 * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
433 * @return {Object} The closest thumb object and its distance from the click event
435 getNearest: function(local, prop) {
437 localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
438 clickValue = me.reverseValue(localValue),
439 nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
449 for (; i < len; i++) {
450 thumb = me.thumbs[i];
452 dist = Math.abs(value - clickValue);
454 if (Math.abs(dist <= nearestDistance)) {
457 nearestDistance = dist;
463 <span id='Ext-slider-Multi-method-onKeyDown'> /**
465 * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
466 * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
467 * @param {Ext.EventObject} e The Event object
469 onKeyDown : function(e) {
471 * The behaviour for keyboard handling with multiple thumbs is currently undefined.
472 * There's no real sane default for it, so leave it like this until we come up
473 * with a better way of doing it.
479 if(me.disabled || me.thumbs.length !== 1) {
489 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
490 me.setValue(0, val, undefined, true);
495 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
496 me.setValue(0, val, undefined, true);
504 afterRender : function() {
512 me.callParent(arguments);
514 for (; i < len; i++) {
517 if (thumb.value !== undefined) {
518 v = me.normalizeValue(thumb.value);
519 if (v !== thumb.value) {
520 // delete this.value;
521 me.setValue(i, v, false);
523 thumb.move(me.translateValue(v), false);
529 <span id='Ext-slider-Multi-method-getRatio'> /**
531 * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
533 * @return {Number} The ratio of pixels to mapped values
535 getRatio : function() {
536 var w = this.innerEl.getWidth(),
537 v = this.maxValue - this.minValue;
538 return v === 0 ? w : (w/v);
541 <span id='Ext-slider-Multi-method-normalizeValue'> /**
543 * Returns a snapped, constrained value when given a desired value
544 * @param {Number} value Raw number value
545 * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
547 normalizeValue : function(v) {
550 v = Ext.Number.snap(v, this.increment, this.minValue, this.maxValue);
551 v = Ext.util.Format.round(v, me.decimalPrecision);
552 v = Ext.Number.constrain(v, me.minValue, me.maxValue);
556 <span id='Ext-slider-Multi-method-setMinValue'> /**
557 </span> * Sets the minimum value for the slider instance. If the current value is less than the minimum value, the current
558 * value will be changed.
559 * @param {Number} val The new minimum value
561 setMinValue : function(val) {
570 me.inputEl.dom.setAttribute('aria-valuemin', val);
573 for (; i < len; ++i) {
575 t.value = t.value < val ? val : t.value;
580 <span id='Ext-slider-Multi-method-setMaxValue'> /**
581 </span> * Sets the maximum value for the slider instance. If the current value is more than the maximum value, the current
582 * value will be changed.
583 * @param {Number} val The new maximum value
585 setMaxValue : function(val) {
594 me.inputEl.dom.setAttribute('aria-valuemax', val);
597 for (; i < len; ++i) {
599 t.value = t.value > val ? val : t.value;
604 <span id='Ext-slider-Multi-method-setValue'> /**
605 </span> * Programmatically sets the value of the Slider. Ensures that the value is constrained within the minValue and
607 * @param {Number} index Index of the thumb to move
608 * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
609 * @param {Boolean} [animate=true] Turn on or off animation
611 setValue : function(index, value, animate, changeComplete) {
613 thumb = me.thumbs[index];
615 // ensures value is contstrained and snapped
616 value = me.normalizeValue(value);
618 if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
621 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
622 // Perhaps this should go on each thumb element rather than the outer element.
624 'aria-valuenow': value,
625 'aria-valuetext': value
628 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
630 me.fireEvent('change', me, value, thumb);
631 if (changeComplete) {
632 me.fireEvent('changecomplete', me, value, thumb);
638 <span id='Ext-slider-Multi-method-translateValue'> /**
641 translateValue : function(v) {
642 var ratio = this.getRatio();
643 return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
646 <span id='Ext-slider-Multi-method-reverseValue'> /**
648 * Given a pixel location along the slider, returns the mapped slider value for that pixel.
649 * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
651 * @param {Number} pos The position along the slider to return a mapped value for
652 * @return {Number} The mapped value for the given position
654 reverseValue : function(pos) {
655 var ratio = this.getRatio();
656 return (pos + (this.minValue * ratio)) / ratio;
661 this.focusEl.focus(10);
665 onDisable: function() {
676 for (; i < len; i++) {
683 //IE breaks when using overflow visible and opacity other than 1.
684 //Create a place holder for the thumb and display it.
688 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
690 if (!me.thumbHolder) {
691 me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
694 me.thumbHolder.show().setXY(xy);
700 onEnable: function() {
710 for (; i < len; i++) {
717 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
719 if (me.thumbHolder) {
720 me.thumbHolder.hide();
729 <span id='Ext-slider-Multi-method-syncThumbs'> /**
730 </span> * Synchronizes thumbs position to the proper proportion of the total component width based on the current slider
731 * {@link #value}. This will be called automatically when the Slider is resized by a layout, but if it is rendered
732 * auto width, this method can be called from another resize handler to sync the Slider if necessary.
734 syncThumbs : function() {
736 var thumbs = this.thumbs,
737 length = thumbs.length,
740 for (; i < length; i++) {
741 thumbs[i].move(this.translateValue(thumbs[i].value));
746 <span id='Ext-slider-Multi-method-getValue'> /**
747 </span> * Returns the current value of the slider
748 * @param {Number} index The index of the thumb to return a value for
749 * @return {Number/Number[]} The current value of the slider at the given index, or an array of all thumb values if
752 getValue : function(index) {
753 return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
756 <span id='Ext-slider-Multi-method-getValues'> /**
757 </span> * Returns an array of values - one for the location of each thumb
758 * @return {Number[]} The set of thumb values
760 getValues: function() {
763 thumbs = this.thumbs,
766 for (; i < len; i++) {
767 values.push(thumbs[i].value);
773 getSubmitValue: function() {
775 return (me.disabled || !me.submitValue) ? null : me.getValue();
781 Array.forEach(Array.from(me.originalValue), function(val, i) {
785 // delete here so we reset back to the original state
790 beforeDestroy : function() {
793 Ext.destroy(me.innerEl, me.endEl, me.focusEl);
794 Ext.each(me.thumbs, function(thumb) {
802 // Method overrides to support slider with vertical orientation
804 getRatio : function() {
805 var h = this.innerEl.getHeight(),
806 v = this.maxValue - this.minValue;
810 onClickChange : function(local) {
812 thumb, index, bottom;
814 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
815 thumb = me.getNearest(local, 'top');
816 if (!thumb.disabled) {
818 bottom = me.reverseValue(me.innerEl.getHeight() - local.top);
820 me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);