dijit
Version:
Dijit provides a complete collection of user interface controls based on Dojo, giving you the power to create web applications that are highly optimized for usability, performance, internationalization, accessibility, but above all deliver an incredible u
391 lines (343 loc) • 13.1 kB
JavaScript
define([
"dojo/_base/array", // array.forEach
"dojo/_base/declare", // declare
"dojo/dnd/move",
"dojo/_base/fx", // fx.animateProperty
"dojo/dom-geometry", // domGeometry.position
"dojo/dom-style", // domStyle.getComputedStyle
"dojo/keys", // keys.DOWN_ARROW keys.END keys.HOME keys.LEFT_ARROW keys.PAGE_DOWN keys.PAGE_UP keys.RIGHT_ARROW keys.UP_ARROW
"dojo/_base/lang", // lang.hitch
"dojo/sniff", // has("ie") has("mozilla")
"dojo/dnd/Moveable", // Moveable
"dojo/dnd/Mover", // Mover Mover.prototype.destroy.apply
"dojo/query", // query
"dojo/mouse", // mouse.wheel
"dojo/on",
"../_base/manager", // defaultDuration
"../focus", // focus.focus()
"../typematic",
"./Button",
"./_FormValueWidget",
"../_Container",
"dojo/text!./templates/HorizontalSlider.html"
], function(array, declare, move, fx, domGeometry, domStyle, keys, lang, has, Moveable, Mover, query, mouse, on,
manager, focus, typematic, Button, _FormValueWidget, _Container, template){
// module:
// dijit/form/HorizontalSlider
var _SliderMover = declare("dijit.form._SliderMover", Mover, {
onMouseMove: function(e){
var widget = this.widget;
var abspos = widget._abspos;
if(!abspos){
abspos = widget._abspos = domGeometry.position(widget.sliderBarContainer, true);
widget._setPixelValue_ = lang.hitch(widget, "_setPixelValue");
widget._isReversed_ = widget._isReversed();
}
var pixelValue = e[widget._mousePixelCoord] - abspos[widget._startingPixelCoord];
widget._setPixelValue_(widget._isReversed_ ? (abspos[widget._pixelCount] - pixelValue) : pixelValue, abspos[widget._pixelCount], false);
},
destroy: function(e){
Mover.prototype.destroy.apply(this, arguments);
var widget = this.widget;
widget._abspos = null;
widget._setValueAttr(widget.value, true);
}
});
var HorizontalSlider = declare("dijit.form.HorizontalSlider", [_FormValueWidget, _Container], {
// summary:
// A form widget that allows one to select a value with a horizontally draggable handle
templateString: template,
// Overrides FormValueWidget.value to indicate numeric value
value: 0,
// showButtons: [const] Boolean
// Show increment/decrement buttons at the ends of the slider?
showButtons: true,
// minimum: [const] Integer
// The minimum value the slider can be set to.
minimum: 0,
// maximum: [const] Integer
// The maximum value the slider can be set to.
maximum: 100,
// discreteValues: Integer
// If specified, indicates that the slider handle has only 'discreteValues' possible positions,
// and that after dragging the handle, it will snap to the nearest possible position.
// Thus, the slider has only 'discreteValues' possible values.
//
// For example, if minimum=10, maxiumum=30, and discreteValues=3, then the slider handle has
// three possible positions, representing values 10, 20, or 30.
//
// If discreteValues is not specified or if it's value is higher than the number of pixels
// in the slider bar, then the slider handle can be moved freely, and the slider's value will be
// computed/reported based on pixel position (in this case it will likely be fractional,
// such as 123.456789).
discreteValues: Infinity,
// pageIncrement: Integer
// If discreteValues is also specified, this indicates the amount of clicks (ie, snap positions)
// that the slider handle is moved via pageup/pagedown keys.
// If discreteValues is not specified, it indicates the number of pixels.
pageIncrement: 2,
// clickSelect: Boolean
// If clicking the slider bar changes the value or not
clickSelect: true,
// slideDuration: Number
// The time in ms to take to animate the slider handle from 0% to 100%,
// when clicking the slider bar to make the handle move.
slideDuration: manager.defaultDuration,
// Map widget attributes to DOMNode attributes.
_setIdAttr: "", // Override _FormWidget which sends id to focusNode
_setNameAttr: "valueNode", // Override default behavior to send to focusNode
baseClass: "dijitSlider",
// Apply CSS classes to up/down arrows and handle per mouse state
cssStateNodes: {
incrementButton: "dijitSliderIncrementButton",
decrementButton: "dijitSliderDecrementButton",
focusNode: "dijitSliderThumb"
},
_mousePixelCoord: "pageX",
_pixelCount: "w",
_startingPixelCoord: "x",
_handleOffsetCoord: "left",
_progressPixelSize: "width",
_onKeyUp: function(/*Event*/ e){
if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){
return;
}
this._setValueAttr(this.value, true);
},
_onKeyDown: function(/*Event*/ e){
if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){
return;
}
switch(e.keyCode){
case keys.HOME:
this._setValueAttr(this.minimum, false);
break;
case keys.END:
this._setValueAttr(this.maximum, false);
break;
// this._descending === false: if ascending vertical (min on top)
// (this._descending || this.isLeftToRight()): if left-to-right horizontal or descending vertical
case ((this._descending || this.isLeftToRight()) ? keys.RIGHT_ARROW : keys.LEFT_ARROW):
case (this._descending === false ? keys.DOWN_ARROW : keys.UP_ARROW):
case (this._descending === false ? keys.PAGE_DOWN : keys.PAGE_UP):
this.increment(e);
break;
case ((this._descending || this.isLeftToRight()) ? keys.LEFT_ARROW : keys.RIGHT_ARROW):
case (this._descending === false ? keys.UP_ARROW : keys.DOWN_ARROW):
case (this._descending === false ? keys.PAGE_UP : keys.PAGE_DOWN):
this.decrement(e);
break;
default:
return;
}
e.stopPropagation();
e.preventDefault();
},
_onHandleClick: function(e){
if(this.disabled || this.readOnly){
return;
}
if(!has("ie")){
// make sure you get focus when dragging the handle
// (but don't do on IE because it causes a flicker on mouse up (due to blur then focus)
focus.focus(this.sliderHandle);
}
e.stopPropagation();
e.preventDefault();
},
_isReversed: function(){
// summary:
// Returns true if direction is from right to left
// tags:
// protected extension
return !this.isLeftToRight();
},
_onBarClick: function(e){
if(this.disabled || this.readOnly || !this.clickSelect){
return;
}
focus.focus(this.sliderHandle);
e.stopPropagation();
e.preventDefault();
var abspos = domGeometry.position(this.sliderBarContainer, true);
var pixelValue = e[this._mousePixelCoord] - abspos[this._startingPixelCoord];
this._setPixelValue(this._isReversed() ? (abspos[this._pixelCount] - pixelValue) : pixelValue, abspos[this._pixelCount], true);
this._movable.onMouseDown(e);
},
_setPixelValue: function(/*Number*/ pixelValue, /*Number*/ maxPixels, /*Boolean?*/ priorityChange){
if(this.disabled || this.readOnly){
return;
}
var count = this.discreteValues;
if(count <= 1 || count == Infinity){
count = maxPixels;
}
count--;
var pixelsPerValue = maxPixels / count;
var wholeIncrements = Math.round(pixelValue / pixelsPerValue);
this._setValueAttr(Math.max(Math.min((this.maximum - this.minimum) * wholeIncrements / count + this.minimum, this.maximum), this.minimum), priorityChange);
},
_setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){
// summary:
// Hook so set('value', value) works.
this._set("value", value);
this.valueNode.value = value;
this.focusNode.setAttribute("aria-valuenow", value);
this.inherited(arguments);
var percent = this.maximum > this.minimum ? ((value - this.minimum) / (this.maximum - this.minimum)) : 0;
var progressBar = (this._descending === false) ? this.remainingBar : this.progressBar;
var remainingBar = (this._descending === false) ? this.progressBar : this.remainingBar;
if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){
this._inProgressAnim.stop(true);
}
if(priorityChange && this.slideDuration > 0 && progressBar.style[this._progressPixelSize]){
// animate the slider
var _this = this;
var props = {};
var start = parseFloat(progressBar.style[this._progressPixelSize]);
var duration = this.slideDuration * (percent - start / 100);
if(duration == 0){
return;
}
if(duration < 0){
duration = 0 - duration;
}
props[this._progressPixelSize] = { start: start, end: percent * 100, units: "%" };
this._inProgressAnim = fx.animateProperty({ node: progressBar, duration: duration,
onAnimate: function(v){
remainingBar.style[_this._progressPixelSize] = (100 - parseFloat(v[_this._progressPixelSize])) + "%";
},
onEnd: function(){
delete _this._inProgressAnim;
},
properties: props
});
this._inProgressAnim.play();
}else{
progressBar.style[this._progressPixelSize] = (percent * 100) + "%";
remainingBar.style[this._progressPixelSize] = ((1 - percent) * 100) + "%";
}
},
_bumpValue: function(signedChange, /*Boolean?*/ priorityChange){
if(this.disabled || this.readOnly || (this.maximum <= this.minimum)){
return;
}
var s = domStyle.getComputedStyle(this.sliderBarContainer);
var c = domGeometry.getContentBox(this.sliderBarContainer, s);
var count = this.discreteValues;
if(count <= 1 || count == Infinity){
count = c[this._pixelCount];
}
count--;
// the division is imprecise so the expression has to be rounded to avoid long floating numbers
var value = Math.round((this.value - this.minimum) * count / (this.maximum - this.minimum)) + signedChange;
if(value < 0){
value = 0;
}
if(value > count){
value = count;
}
value = value * (this.maximum - this.minimum) / count + this.minimum;
this._setValueAttr(value, priorityChange);
},
_onClkBumper: function(val){
if(this.disabled || this.readOnly || !this.clickSelect){
return;
}
this._setValueAttr(val, true);
},
_onClkIncBumper: function(){
this._onClkBumper(this._descending === false ? this.minimum : this.maximum);
},
_onClkDecBumper: function(){
this._onClkBumper(this._descending === false ? this.maximum : this.minimum);
},
decrement: function(/*Event*/ e){
// summary:
// Decrement slider
// tags:
// private
this._bumpValue(e.keyCode == keys.PAGE_DOWN ? -this.pageIncrement : -1);
},
increment: function(/*Event*/ e){
// summary:
// Increment slider
// tags:
// private
this._bumpValue(e.keyCode == keys.PAGE_UP ? this.pageIncrement : 1);
},
_mouseWheeled: function(/*Event*/ evt){
// summary:
// Event handler for mousewheel where supported
if(!this.focused){
// If use is scrolling over page and we happen to get the mouse wheel event, just ignore it.
return;
}
evt.stopPropagation();
evt.preventDefault();
this._bumpValue(evt.wheelDelta < 0 ? -1 : 1, true); // negative scroll acts like a decrement
},
startup: function(){
if(this._started){
return;
}
array.forEach(this.getChildren(), function(child){
if(this[child.container] != this.containerNode){
this[child.container].appendChild(child.domNode);
}
}, this);
this.inherited(arguments);
},
_typematicCallback: function(/*Number*/ count, /*Object*/ button, /*Event*/ e){
if(count == -1){
this._setValueAttr(this.value, true);
}else{
this[(button == (this._descending ? this.incrementButton : this.decrementButton)) ? "decrement" : "increment"](e);
}
},
buildRendering: function(){
this.inherited(arguments);
if(this.showButtons){
this.incrementButton.style.display = "";
this.decrementButton.style.display = "";
}
// find any associated label element and add to slider focusnode.
var label = query('label[for="' + this.id + '"]');
if(label.length){
if(!label[0].id){
label[0].id = this.id + "_label";
}
this.focusNode.setAttribute("aria-labelledby", label[0].id);
}
this.focusNode.setAttribute("aria-valuemin", this.minimum);
this.focusNode.setAttribute("aria-valuemax", this.maximum);
},
postCreate: function(){
this.inherited(arguments);
if(this.showButtons){
this.own(
typematic.addMouseListener(this.decrementButton, this, "_typematicCallback", 25, 500),
typematic.addMouseListener(this.incrementButton, this, "_typematicCallback", 25, 500)
);
}
this.own(
on(this.domNode, mouse.wheel, lang.hitch(this, "_mouseWheeled"))
);
// define a custom constructor for a SliderMover that points back to me
var mover = declare(_SliderMover, {
widget: this
});
this._movable = new Moveable(this.sliderHandle, {mover: mover});
this._layoutHackIE7();
},
destroy: function(){
this._movable.destroy();
if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){
this._inProgressAnim.stop(true);
}
this.inherited(arguments);
}
});
HorizontalSlider._Mover = _SliderMover; // for monkey patching
return HorizontalSlider;
});