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()
41 Ext.define('Ext.slider.Multi', {
42 extend: 'Ext.form.field.Base',
43 alias: 'widget.multislider',
44 alternateClassName: 'Ext.slider.MultiSlider',
52 'Ext.layout.component.field.Slider'
56 '<div class="' + Ext.baseCSSPrefix + 'slider {fieldCls} {vertical}" aria-valuemin="{minValue}" aria-valuemax="{maxValue}" aria-valuenow="{value}" aria-valuetext="{value}">',
57 '<div class="' + Ext.baseCSSPrefix + 'slider-end" role="presentation">',
58 '<div class="' + Ext.baseCSSPrefix + 'slider-inner" role="presentation">',
59 '<a class="' + Ext.baseCSSPrefix + 'slider-focus" href="#" tabIndex="-1" hidefocus="on" role="presentation"></a>',
69 <span id='Ext-slider-Multi-cfg-value'> /**
70 </span> * @cfg {Number} value
71 * A value with which to initialize the slider. Defaults to minValue. Setting this will only
72 * result in the creation of a single slider thumb; if you want multiple thumbs then use the
73 * {@link #values} config instead.
76 <span id='Ext-slider-Multi-cfg-values'> /**
77 </span> * @cfg {Array} values
78 * Array of Number values with which to initalize the slider. A separate slider thumb will be created for
79 * each value in this array. This will take precedence over the single {@link #value} config.
82 <span id='Ext-slider-Multi-cfg-vertical'> /**
83 </span> * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
86 <span id='Ext-slider-Multi-cfg-minValue'> /**
87 </span> * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
90 <span id='Ext-slider-Multi-cfg-maxValue'> /**
91 </span> * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
94 <span id='Ext-slider-Multi-cfg-decimalPrecision'> /**
95 </span> * @cfg {Number/Boolean} decimalPrecision.
96 * <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
97 * <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
100 <span id='Ext-slider-Multi-cfg-keyIncrement'> /**
101 </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.
104 <span id='Ext-slider-Multi-cfg-increment'> /**
105 </span> * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
109 <span id='Ext-slider-Multi-property-clickRange'> /**
111 * @property clickRange
113 * 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],
114 * 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'
115 * 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
119 <span id='Ext-slider-Multi-cfg-clickToChange'> /**
120 </span> * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
122 clickToChange : true,
123 <span id='Ext-slider-Multi-cfg-animate'> /**
124 </span> * @cfg {Boolean} animate Turn on or off animation. Defaults to true
128 <span id='Ext-slider-Multi-property-dragging'> /**
129 </span> * True while the thumb is in a drag operation
134 <span id='Ext-slider-Multi-cfg-constrainThumbs'> /**
135 </span> * @cfg {Boolean} constrainThumbs True to disallow thumbs from overlapping one another. Defaults to true
137 constrainThumbs: true,
139 componentLayout: 'sliderfield',
141 <span id='Ext-slider-Multi-cfg-useTips'> /**
142 </span> * @cfg {Boolean} useTips
143 * True to use an Ext.slider.Tip to display tips for the value. Defaults to <tt>true</tt>.
147 <span id='Ext-slider-Multi-cfg-tipText'> /**
148 </span> * @cfg {Function} tipText
149 * A function used to display custom text for the slider tip. Defaults to <tt>null</tt>, which will
150 * use the default on the plugin.
157 initValue: function() {
159 extValue = Ext.value,
160 // Fallback for initial values: values config -> value config -> minValue config -> 0
161 values = extValue(me.values, [extValue(me.value, extValue(me.minValue, 0))]),
165 // Store for use in dirty check
166 me.originalValue = values;
168 // Add a thumb for each value
169 for (; i < len; i++) {
170 me.addThumb(values[i]);
175 initComponent : function() {
180 <span id='Ext-slider-Multi-property-thumbs'> /**
181 </span> * @property thumbs
183 * Array containing references to each thumb
187 me.keyIncrement = Math.max(me.increment, me.keyIncrement);
190 <span id='Ext-slider-Multi-event-beforechange'> /**
191 </span> * @event beforechange
192 * Fires before the slider value is changed. By returning false from an event handler,
193 * you can cancel the event and prevent the slider from changing.
194 * @param {Ext.slider.Multi} slider The slider
195 * @param {Number} newValue The new value which the slider is being changed to.
196 * @param {Number} oldValue The old value which the slider was previously.
200 <span id='Ext-slider-Multi-event-change'> /**
201 </span> * @event change
202 * Fires when the slider value is changed.
203 * @param {Ext.slider.Multi} slider The slider
204 * @param {Number} newValue The new value which the slider has been changed to.
205 * @param {Ext.slider.Thumb} thumb The thumb that was changed
209 <span id='Ext-slider-Multi-event-changecomplete'> /**
210 </span> * @event changecomplete
211 * Fires when the slider value is changed by the user and any drag operations have completed.
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-dragstart'> /**
219 </span> * @event dragstart
220 * Fires after a drag operation has started.
221 * @param {Ext.slider.Multi} slider The slider
222 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
226 <span id='Ext-slider-Multi-event-drag'> /**
227 </span> * @event drag
228 * Fires continuously during the drag operation while the mouse is moving.
229 * @param {Ext.slider.Multi} slider The slider
230 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
234 <span id='Ext-slider-Multi-event-dragend'> /**
235 </span> * @event dragend
236 * Fires after the drag operation has completed.
237 * @param {Ext.slider.Multi} slider The slider
238 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
244 Ext.apply(me, Ext.slider.Multi.Vertical);
249 // only can use it if it exists.
251 tipPlug = me.tipText ? {getText: me.tipText} : {};
252 me.plugins = me.plugins || [];
253 Ext.each(me.plugins, function(plug){
254 if (plug.isSliderTip) {
260 me.plugins.push(Ext.create('Ext.slider.Tip', tipPlug));
265 <span id='Ext-slider-Multi-method-addThumb'> /**
266 </span> * Creates a new thumb and adds it to the slider
267 * @param {Number} value The initial value to set on the thumb. Defaults to 0
268 * @return {Ext.slider.Thumb} The thumb
270 addThumb: function(value) {
272 thumb = Ext.create('Ext.slider.Thumb', {
275 index : me.thumbs.length,
276 constrain: me.constrainThumbs
278 me.thumbs.push(thumb);
280 //render the thumb now if needed
288 <span id='Ext-slider-Multi-method-promoteThumb'> /**
290 * Moves the given thumb above all other by increasing its z-index. This is called when as drag
291 * any thumb, so that the thumb that was just dragged is always at the highest z-index. This is
292 * required when the thumbs are stacked on top of each other at one of the ends of the slider's
293 * range, which can result in the user not being able to move any of them.
294 * @param {Ext.slider.Thumb} topThumb The thumb to move to the top
296 promoteThumb: function(topThumb) {
297 var thumbs = this.thumbs,
301 for (i = 0; i < ln; i++) {
304 if (thumb == topThumb) {
305 thumb.bringToFront();
313 onRender : function() {
320 Ext.applyIf(me.subTplData, {
321 vertical: me.vertical ? Ext.baseCSSPrefix + 'slider-vert' : Ext.baseCSSPrefix + 'slider-horz',
322 minValue: me.minValue,
323 maxValue: me.maxValue,
327 Ext.applyIf(me.renderSelectors, {
328 endEl: '.' + Ext.baseCSSPrefix + 'slider-end',
329 innerEl: '.' + Ext.baseCSSPrefix + 'slider-inner',
330 focusEl: '.' + Ext.baseCSSPrefix + 'slider-focus'
333 me.callParent(arguments);
336 for (; i < len; i++) {
340 //calculate the size of half a thumb
341 thumb = me.innerEl.down('.' + Ext.baseCSSPrefix + 'slider-thumb');
342 me.halfThumb = (me.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
346 <span id='Ext-slider-Multi-method-onChange'> /**
347 </span> * Utility method to set the value of the field when the slider changes.
348 * @param {Object} slider The slider object.
349 * @param {Object} v The new value.
352 onChange : function(slider, v) {
353 this.setValue(v, undefined, true);
356 <span id='Ext-slider-Multi-method-initEvents'> /**
358 * Adds keyboard and mouse listeners on this.el. Ignores click events on the internal focus element.
360 initEvents : function() {
365 mousedown: me.onMouseDown,
366 keydown : me.onKeyDown,
370 me.focusEl.swallowEvent("click", true);
373 <span id='Ext-slider-Multi-method-onMouseDown'> /**
375 * Mousedown handler for the slider. If the clickToChange is enabled and the click was not on the draggable 'thumb',
376 * this calculates the new value of the slider and tells the implementation (Horizontal or Vertical) to move the thumb
377 * @param {Ext.EventObject} e The click event
379 onMouseDown : function(e) {
381 thumbClicked = false,
391 //see if the click was on any of the thumbs
392 for (; i < len; i++) {
393 thumbClicked = thumbClicked || e.target == thumbs[i].el.dom;
396 if (me.clickToChange && !thumbClicked) {
397 local = me.innerEl.translatePoints(e.getXY());
398 me.onClickChange(local);
403 <span id='Ext-slider-Multi-method-onClickChange'> /**
405 * Moves the thumb to the indicated position. Note that a Vertical implementation is provided in Ext.slider.Multi.Vertical.
406 * Only changes the value if the click was within this.clickRange.
407 * @param {Object} local Object containing top and left values for the click event.
409 onClickChange : function(local) {
413 if (local.top > me.clickRange[0] && local.top < me.clickRange[1]) {
414 //find the nearest thumb to the click event
415 thumb = me.getNearest(local, 'left');
416 if (!thumb.disabled) {
418 me.setValue(index, Ext.util.Format.round(me.reverseValue(local.left), me.decimalPrecision), undefined, true);
423 <span id='Ext-slider-Multi-method-getNearest'> /**
425 * Returns the nearest thumb to a click event, along with its distance
426 * @param {Object} local Object containing top and left values from a click event
427 * @param {String} prop The property of local to compare on. Use 'left' for horizontal sliders, 'top' for vertical ones
428 * @return {Object} The closest thumb object and its distance from the click event
430 getNearest: function(local, prop) {
432 localValue = prop == 'top' ? me.innerEl.getHeight() - local[prop] : local[prop],
433 clickValue = me.reverseValue(localValue),
434 nearestDistance = (me.maxValue - me.minValue) + 5, //add a small fudge for the end of the slider
444 for (; i < len; i++) {
445 thumb = me.thumbs[i];
447 dist = Math.abs(value - clickValue);
449 if (Math.abs(dist <= nearestDistance)) {
452 nearestDistance = dist;
458 <span id='Ext-slider-Multi-method-onKeyDown'> /**
460 * Handler for any keypresses captured by the slider. If the key is UP or RIGHT, the thumb is moved along to the right
461 * by this.keyIncrement. If DOWN or LEFT it is moved left. Pressing CTRL moves the slider to the end in either direction
462 * @param {Ext.EventObject} e The Event object
464 onKeyDown : function(e) {
466 * The behaviour for keyboard handling with multiple thumbs is currently undefined.
467 * There's no real sane default for it, so leave it like this until we come up
468 * with a better way of doing it.
474 if(me.disabled || me.thumbs.length !== 1) {
484 val = e.ctrlKey ? me.maxValue : me.getValue(0) + me.keyIncrement;
485 me.setValue(0, val, undefined, true);
490 val = e.ctrlKey ? me.minValue : me.getValue(0) - me.keyIncrement;
491 me.setValue(0, val, undefined, true);
498 <span id='Ext-slider-Multi-method-doSnap'> /**
500 * If using snapping, this takes a desired new value and returns the closest snapped
502 * @param {Number} value The unsnapped value
503 * @return {Number} The value of the nearest snap target
505 doSnap : function(value) {
506 var newValue = value,
507 inc = this.increment,
510 if (!(inc && value)) {
516 if (m * 2 >= inc) {
518 } else if (m * 2 < -inc) {
522 return Ext.Number.constrain(newValue, this.minValue, this.maxValue);
526 afterRender : function() {
534 me.callParent(arguments);
536 for (; i < len; i++) {
539 if (thumb.value !== undefined) {
540 v = me.normalizeValue(thumb.value);
541 if (v !== thumb.value) {
542 // delete this.value;
543 me.setValue(i, v, false);
545 thumb.move(me.translateValue(v), false);
551 <span id='Ext-slider-Multi-method-getRatio'> /**
553 * Returns the ratio of pixels to mapped values. e.g. if the slider is 200px wide and maxValue - minValue is 100,
555 * @return {Number} The ratio of pixels to mapped values
557 getRatio : function() {
558 var w = this.innerEl.getWidth(),
559 v = this.maxValue - this.minValue;
560 return v === 0 ? w : (w/v);
563 <span id='Ext-slider-Multi-method-normalizeValue'> /**
565 * Returns a snapped, constrained value when given a desired value
566 * @param {Number} value Raw number value
567 * @return {Number} The raw value rounded to the correct d.p. and constrained within the set max and min values
569 normalizeValue : function(v) {
573 v = Ext.util.Format.round(v, me.decimalPrecision);
574 v = Ext.Number.constrain(v, me.minValue, me.maxValue);
578 <span id='Ext-slider-Multi-method-setMinValue'> /**
579 </span> * Sets the minimum value for the slider instance. If the current value is less than the
580 * minimum value, the current value will be changed.
581 * @param {Number} val The new minimum value
583 setMinValue : function(val) {
591 me.inputEl.dom.setAttribute('aria-valuemin', val);
593 for (; i < len; ++i) {
595 t.value = t.value < val ? val : t.value;
600 <span id='Ext-slider-Multi-method-setMaxValue'> /**
601 </span> * Sets the maximum value for the slider instance. If the current value is more than the
602 * maximum value, the current value will be changed.
603 * @param {Number} val The new maximum value
605 setMaxValue : function(val) {
613 me.inputEl.dom.setAttribute('aria-valuemax', val);
615 for (; i < len; ++i) {
617 t.value = t.value > val ? val : t.value;
622 <span id='Ext-slider-Multi-method-setValue'> /**
623 </span> * Programmatically sets the value of the Slider. Ensures that the value is constrained within
624 * the minValue and maxValue.
625 * @param {Number} index Index of the thumb to move
626 * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
627 * @param {Boolean} animate Turn on or off animation, defaults to true
629 setValue : function(index, value, animate, changeComplete) {
631 thumb = me.thumbs[index];
633 // ensures value is contstrained and snapped
634 value = me.normalizeValue(value);
636 if (value !== thumb.value && me.fireEvent('beforechange', me, value, thumb.value, thumb) !== false) {
639 // TODO this only handles a single value; need a solution for exposing multiple values to aria.
640 // Perhaps this should go on each thumb element rather than the outer element.
642 'aria-valuenow': value,
643 'aria-valuetext': value
646 thumb.move(me.translateValue(value), Ext.isDefined(animate) ? animate !== false : me.animate);
648 me.fireEvent('change', me, value, thumb);
649 if (changeComplete) {
650 me.fireEvent('changecomplete', me, value, thumb);
656 <span id='Ext-slider-Multi-method-translateValue'> /**
659 translateValue : function(v) {
660 var ratio = this.getRatio();
661 return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
664 <span id='Ext-slider-Multi-method-reverseValue'> /**
666 * Given a pixel location along the slider, returns the mapped slider value for that pixel.
667 * E.g. if we have a slider 200px wide with minValue = 100 and maxValue = 500, reverseValue(50)
669 * @param {Number} pos The position along the slider to return a mapped value for
670 * @return {Number} The mapped value for the given position
672 reverseValue : function(pos) {
673 var ratio = this.getRatio();
674 return (pos + (this.minValue * ratio)) / ratio;
679 this.focusEl.focus(10);
683 onDisable: function() {
694 for (; i < len; i++) {
701 //IE breaks when using overflow visible and opacity other than 1.
702 //Create a place holder for the thumb and display it.
706 me.innerEl.addCls(me.disabledCls).dom.disabled = true;
708 if (!me.thumbHolder) {
709 me.thumbHolder = me.endEl.createChild({cls: Ext.baseCSSPrefix + 'slider-thumb ' + me.disabledCls});
712 me.thumbHolder.show().setXY(xy);
718 onEnable: function() {
728 for (; i < len; i++) {
735 me.innerEl.removeCls(me.disabledCls).dom.disabled = false;
737 if (me.thumbHolder) {
738 me.thumbHolder.hide();
747 <span id='Ext-slider-Multi-method-syncThumbs'> /**
748 </span> * Synchronizes thumbs position to the proper proportion of the total component width based
749 * on the current slider {@link #value}. This will be called automatically when the Slider
750 * is resized by a layout, but if it is rendered auto width, this method can be called from
751 * another resize handler to sync the Slider if necessary.
753 syncThumbs : function() {
755 var thumbs = this.thumbs,
756 length = thumbs.length,
759 for (; i < length; i++) {
760 thumbs[i].move(this.translateValue(thumbs[i].value));
765 <span id='Ext-slider-Multi-method-getValue'> /**
766 </span> * Returns the current value of the slider
767 * @param {Number} index The index of the thumb to return a value for
768 * @return {Number/Array} The current value of the slider at the given index, or an array of
769 * all thumb values if no index is given.
771 getValue : function(index) {
772 return Ext.isNumber(index) ? this.thumbs[index].value : this.getValues();
775 <span id='Ext-slider-Multi-method-getValues'> /**
776 </span> * Returns an array of values - one for the location of each thumb
777 * @return {Array} The set of thumb values
779 getValues: function() {
782 thumbs = this.thumbs,
785 for (; i < len; i++) {
786 values.push(thumbs[i].value);
792 getSubmitValue: function() {
794 return (me.disabled || !me.submitValue) ? null : me.getValue();
800 Array.forEach(Array.from(me.originalValue), function(val, i) {
804 // delete here so we reset back to the original state
809 beforeDestroy : function() {
812 Ext.destroyMembers(me.innerEl, me.endEl, me.focusEl);
813 Ext.each(me.thumbs, function(thumb) {
821 // Method overrides to support slider with vertical orientation
823 getRatio : function() {
824 var h = this.innerEl.getHeight(),
825 v = this.maxValue - this.minValue;
829 onClickChange : function(local) {
831 thumb, index, bottom;
833 if (local.left > me.clickRange[0] && local.left < me.clickRange[1]) {
834 thumb = me.getNearest(local, 'top');
835 if (!thumb.disabled) {
837 bottom = me.reverseValue(me.innerEl.getHeight() - local.top);
839 me.setValue(index, Ext.util.Format.round(me.minValue + bottom, me.decimalPrecision), undefined, true);