2 Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
8 if(!dojo._hasResource["dijit.form.HorizontalSlider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9 dojo._hasResource["dijit.form.HorizontalSlider"] = true;
10 dojo.provide("dijit.form.HorizontalSlider");
11 dojo.require("dijit.form._FormWidget");
12 dojo.require("dijit._Container");
13 dojo.require("dojo.dnd.move");
14 dojo.require("dijit.form.Button");
15 dojo.require("dojo.number");
19 "dijit.form.HorizontalSlider",
20 [dijit.form._FormValueWidget, dijit._Container],
23 // A form widget that allows one to select a value with a horizontally draggable handle
25 templateString: dojo.cache("dijit.form", "templates/HorizontalSlider.html", "<table class=\"dijit dijitReset dijitSlider dijitSliderH\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"none\" dojoAttachEvent=\"onkeypress:_onKeyPress,onkeyup:_onKeyUp\"\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td dojoAttachPoint=\"topDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationT dijitSliderDecorationH\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\"\n\t\t\t><div class=\"dijitSliderDecrementIconH\" style=\"display:none\" dojoAttachPoint=\"decrementButton\"><span class=\"dijitSliderButtonInner\">-</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderLeftBumper\" dojoAttachEvent=\"onmousedown:_onClkDecBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><input dojoAttachPoint=\"valueNode\" type=\"hidden\" ${!nameAttrSetting}\n\t\t\t/><div class=\"dijitReset dijitSliderBarContainerH\" role=\"presentation\" dojoAttachPoint=\"sliderBarContainer\"\n\t\t\t\t><div role=\"presentation\" dojoAttachPoint=\"progressBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderProgressBar dijitSliderProgressBarH\" dojoAttachEvent=\"onmousedown:_onBarClick\"\n\t\t\t\t\t><div class=\"dijitSliderMoveable dijitSliderMoveableH\"\n\t\t\t\t\t\t><div dojoAttachPoint=\"sliderHandle,focusNode\" class=\"dijitSliderImageHandle dijitSliderImageHandleH\" dojoAttachEvent=\"onmousedown:_onHandleClick\" role=\"slider\" valuemin=\"${minimum}\" valuemax=\"${maximum}\"></div\n\t\t\t\t\t></div\n\t\t\t\t></div\n\t\t\t\t><div role=\"presentation\" dojoAttachPoint=\"remainingBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderRemainingBar dijitSliderRemainingBarH\" dojoAttachEvent=\"onmousedown:_onBarClick\"></div\n\t\t\t></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderRightBumper\" dojoAttachEvent=\"onmousedown:_onClkIncBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\"\n\t\t\t><div class=\"dijitSliderIncrementIconH\" style=\"display:none\" dojoAttachPoint=\"incrementButton\"><span class=\"dijitSliderButtonInner\">+</span></div\n\t\t></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td dojoAttachPoint=\"containerNode,bottomDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationB dijitSliderDecorationH\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n></table>\n"),
27 // Overrides FormValueWidget.value to indicate numeric value
30 // showButtons: [const] Boolean
31 // Show increment/decrement buttons at the ends of the slider?
34 // minimum:: [const] Integer
35 // The minimum value the slider can be set to.
38 // maximum: [const] Integer
39 // The maximum value the slider can be set to.
42 // discreteValues: Integer
43 // If specified, indicates that the slider handle has only 'discreteValues' possible positions,
44 // and that after dragging the handle, it will snap to the nearest possible position.
45 // Thus, the slider has only 'discreteValues' possible values.
47 // For example, if minimum=10, maxiumum=30, and discreteValues=3, then the slider handle has
48 // three possible positions, representing values 10, 20, or 30.
50 // If discreteValues is not specified or if it's value is higher than the number of pixels
51 // in the slider bar, then the slider handle can be moved freely, and the slider's value will be
52 // computed/reported based on pixel position (in this case it will likely be fractional,
53 // such as 123.456789).
54 discreteValues: Infinity,
56 // pageIncrement: Integer
57 // If discreteValues is also specified, this indicates the amount of clicks (ie, snap positions)
58 // that the slider handle is moved via pageup/pagedown keys.
59 // If discreteValues is not specified, it indicates the number of pixels.
62 // clickSelect: Boolean
63 // If clicking the slider bar changes the value or not
66 // slideDuration: Number
67 // The time in ms to take to animate the slider handle from 0% to 100%,
68 // when clicking the slider bar to make the handle move.
69 slideDuration: dijit.defaultDuration,
71 // Flag to _Templated (TODO: why is this here? I see no widgets in the template.)
72 widgetsInTemplate: true,
74 attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
78 baseClass: "dijitSlider",
80 // Apply CSS classes to up/down arrows and handle per mouse state
82 incrementButton: "dijitSliderIncrementButton",
83 decrementButton: "dijitSliderDecrementButton",
84 focusNode: "dijitSliderThumb"
87 _mousePixelCoord: "pageX",
89 _startingPixelCoord: "x",
90 _startingPixelCount: "l",
91 _handleOffsetCoord: "left",
92 _progressPixelSize: "width",
94 _onKeyUp: function(/*Event*/ e){
95 if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; }
96 this._setValueAttr(this.value, true);
99 _onKeyPress: function(/*Event*/ e){
100 if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; }
101 switch(e.charOrCode){
103 this._setValueAttr(this.minimum, false);
106 this._setValueAttr(this.maximum, false);
108 // this._descending === false: if ascending vertical (min on top)
109 // (this._descending || this.isLeftToRight()): if left-to-right horizontal or descending vertical
110 case ((this._descending || this.isLeftToRight()) ? dojo.keys.RIGHT_ARROW : dojo.keys.LEFT_ARROW):
111 case (this._descending === false ? dojo.keys.DOWN_ARROW : dojo.keys.UP_ARROW):
112 case (this._descending === false ? dojo.keys.PAGE_DOWN : dojo.keys.PAGE_UP):
115 case ((this._descending || this.isLeftToRight()) ? dojo.keys.LEFT_ARROW : dojo.keys.RIGHT_ARROW):
116 case (this._descending === false ? dojo.keys.UP_ARROW : dojo.keys.DOWN_ARROW):
117 case (this._descending === false ? dojo.keys.PAGE_UP : dojo.keys.PAGE_DOWN):
126 _onHandleClick: function(e){
127 if(this.disabled || this.readOnly){ return; }
129 // make sure you get focus when dragging the handle
130 // (but don't do on IE because it causes a flicker on mouse up (due to blur then focus)
131 dijit.focus(this.sliderHandle);
136 _isReversed: function(){
138 // Returns true if direction is from right to left
140 // protected extension
141 return !this.isLeftToRight();
144 _onBarClick: function(e){
145 if(this.disabled || this.readOnly || !this.clickSelect){ return; }
146 dijit.focus(this.sliderHandle);
148 var abspos = dojo.position(this.sliderBarContainer, true);
149 var pixelValue = e[this._mousePixelCoord] - abspos[this._startingPixelCoord];
150 this._setPixelValue(this._isReversed() ? (abspos[this._pixelCount] - pixelValue) : pixelValue, abspos[this._pixelCount], true);
151 this._movable.onMouseDown(e);
154 _setPixelValue: function(/*Number*/ pixelValue, /*Number*/ maxPixels, /*Boolean?*/ priorityChange){
155 if(this.disabled || this.readOnly){ return; }
156 pixelValue = pixelValue < 0 ? 0 : maxPixels < pixelValue ? maxPixels : pixelValue;
157 var count = this.discreteValues;
158 if(count <= 1 || count == Infinity){ count = maxPixels; }
160 var pixelsPerValue = maxPixels / count;
161 var wholeIncrements = Math.round(pixelValue / pixelsPerValue);
162 this._setValueAttr((this.maximum-this.minimum)*wholeIncrements/count + this.minimum, priorityChange);
165 _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){
167 // Hook so set('value', value) works.
168 this._set("value", value);
169 this.valueNode.value = value;
170 dijit.setWaiState(this.focusNode, "valuenow", value);
171 this.inherited(arguments);
172 var percent = (value - this.minimum) / (this.maximum - this.minimum);
173 var progressBar = (this._descending === false) ? this.remainingBar : this.progressBar;
174 var remainingBar = (this._descending === false) ? this.progressBar : this.remainingBar;
175 if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){
176 this._inProgressAnim.stop(true);
178 if(priorityChange && this.slideDuration > 0 && progressBar.style[this._progressPixelSize]){
179 // animate the slider
182 var start = parseFloat(progressBar.style[this._progressPixelSize]);
183 var duration = this.slideDuration * (percent-start/100);
184 if(duration == 0){ return; }
185 if(duration < 0){ duration = 0 - duration; }
186 props[this._progressPixelSize] = { start: start, end: percent*100, units:"%" };
187 this._inProgressAnim = dojo.animateProperty({ node: progressBar, duration: duration,
188 onAnimate: function(v){ remainingBar.style[_this._progressPixelSize] = (100-parseFloat(v[_this._progressPixelSize])) + "%"; },
189 onEnd: function(){ delete _this._inProgressAnim; },
192 this._inProgressAnim.play();
194 progressBar.style[this._progressPixelSize] = (percent*100) + "%";
195 remainingBar.style[this._progressPixelSize] = ((1-percent)*100) + "%";
199 _bumpValue: function(signedChange, /*Boolean?*/ priorityChange){
200 if(this.disabled || this.readOnly){ return; }
201 var s = dojo.getComputedStyle(this.sliderBarContainer);
202 var c = dojo._getContentBox(this.sliderBarContainer, s);
203 var count = this.discreteValues;
204 if(count <= 1 || count == Infinity){ count = c[this._pixelCount]; }
206 var value = (this.value - this.minimum) * count / (this.maximum - this.minimum) + signedChange;
207 if(value < 0){ value = 0; }
208 if(value > count){ value = count; }
209 value = value * (this.maximum - this.minimum) / count + this.minimum;
210 this._setValueAttr(value, priorityChange);
213 _onClkBumper: function(val){
214 if(this.disabled || this.readOnly || !this.clickSelect){ return; }
215 this._setValueAttr(val, true);
218 _onClkIncBumper: function(){
219 this._onClkBumper(this._descending === false ? this.minimum : this.maximum);
222 _onClkDecBumper: function(){
223 this._onClkBumper(this._descending === false ? this.maximum : this.minimum);
226 decrement: function(/*Event*/ e){
231 this._bumpValue(e.charOrCode == dojo.keys.PAGE_DOWN ? -this.pageIncrement : -1);
234 increment: function(/*Event*/ e){
239 this._bumpValue(e.charOrCode == dojo.keys.PAGE_UP ? this.pageIncrement : 1);
242 _mouseWheeled: function(/*Event*/ evt){
244 // Event handler for mousewheel where supported
246 var janky = !dojo.isMozilla;
247 var scroll = evt[(janky ? "wheelDelta" : "detail")] * (janky ? 1 : -1);
248 this._bumpValue(scroll < 0 ? -1 : 1, true); // negative scroll acts like a decrement
252 if(this._started){ return; }
254 dojo.forEach(this.getChildren(), function(child){
255 if(this[child.container] != this.containerNode){
256 this[child.container].appendChild(child.domNode);
260 this.inherited(arguments);
263 _typematicCallback: function(/*Number*/ count, /*Object*/ button, /*Event*/ e){
265 this._setValueAttr(this.value, true);
267 this[(button == (this._descending? this.incrementButton : this.decrementButton)) ? "decrement" : "increment"](e);
271 buildRendering: function(){
272 this.inherited(arguments);
273 if(this.showButtons){
274 this.incrementButton.style.display="";
275 this.decrementButton.style.display="";
278 // find any associated label element and add to slider focusnode.
279 var label = dojo.query('label[for="'+this.id+'"]');
281 label[0].id = (this.id+"_label");
282 dijit.setWaiState(this.focusNode, "labelledby", label[0].id);
285 dijit.setWaiState(this.focusNode, "valuemin", this.minimum);
286 dijit.setWaiState(this.focusNode, "valuemax", this.maximum);
289 postCreate: function(){
290 this.inherited(arguments);
292 if(this.showButtons){
293 this._connects.push(dijit.typematic.addMouseListener(
294 this.decrementButton, this, "_typematicCallback", 25, 500));
295 this._connects.push(dijit.typematic.addMouseListener(
296 this.incrementButton, this, "_typematicCallback", 25, 500));
298 this.connect(this.domNode, !dojo.isMozilla ? "onmousewheel" : "DOMMouseScroll", "_mouseWheeled");
300 // define a custom constructor for a SliderMover that points back to me
301 var mover = dojo.declare(dijit.form._SliderMover, {
304 this._movable = new dojo.dnd.Moveable(this.sliderHandle, {mover: mover});
306 this._layoutHackIE7();
310 this._movable.destroy();
311 if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){
312 this._inProgressAnim.stop(true);
314 this._supportingWidgets = dijit.findWidgets(this.domNode); // tells destroy about pseudo-child widgets (ruler/labels)
315 this.inherited(arguments);
319 dojo.declare("dijit.form._SliderMover",
322 onMouseMove: function(e){
323 var widget = this.widget;
324 var abspos = widget._abspos;
326 abspos = widget._abspos = dojo.position(widget.sliderBarContainer, true);
327 widget._setPixelValue_ = dojo.hitch(widget, "_setPixelValue");
328 widget._isReversed_ = widget._isReversed();
330 var coordEvent = e.touches ? e.touches[0] : e, // if multitouch take first touch for coords
331 pixelValue = coordEvent[widget._mousePixelCoord] - abspos[widget._startingPixelCoord];
332 widget._setPixelValue_(widget._isReversed_ ? (abspos[widget._pixelCount]-pixelValue) : pixelValue, abspos[widget._pixelCount], false);
335 destroy: function(e){
336 dojo.dnd.Mover.prototype.destroy.apply(this, arguments);
337 var widget = this.widget;
338 widget._abspos = null;
339 widget._setValueAttr(widget.value, true);