HorizontalSlider.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. Copyright (c) 2004-2012, 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
  5. */
  6. if(!dojo._hasResource["dijit.form.HorizontalSlider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit.form.HorizontalSlider"] = true;
  8. dojo.provide("dijit.form.HorizontalSlider");
  9. dojo.require("dijit.form._FormWidget");
  10. dojo.require("dijit._Container");
  11. dojo.require("dojo.dnd.move");
  12. dojo.require("dijit.form.Button");
  13. dojo.require("dojo.number");
  14. dojo.declare(
  15. "dijit.form.HorizontalSlider",
  16. [dijit.form._FormValueWidget, dijit._Container],
  17. {
  18. // summary:
  19. // A form widget that allows one to select a value with a horizontally draggable handle
  20. 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\trole=\"presentation\"\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"),
  21. // Overrides FormValueWidget.value to indicate numeric value
  22. value: 0,
  23. // showButtons: [const] Boolean
  24. // Show increment/decrement buttons at the ends of the slider?
  25. showButtons: true,
  26. // minimum:: [const] Integer
  27. // The minimum value the slider can be set to.
  28. minimum: 0,
  29. // maximum: [const] Integer
  30. // The maximum value the slider can be set to.
  31. maximum: 100,
  32. // discreteValues: Integer
  33. // If specified, indicates that the slider handle has only 'discreteValues' possible positions,
  34. // and that after dragging the handle, it will snap to the nearest possible position.
  35. // Thus, the slider has only 'discreteValues' possible values.
  36. //
  37. // For example, if minimum=10, maxiumum=30, and discreteValues=3, then the slider handle has
  38. // three possible positions, representing values 10, 20, or 30.
  39. //
  40. // If discreteValues is not specified or if it's value is higher than the number of pixels
  41. // in the slider bar, then the slider handle can be moved freely, and the slider's value will be
  42. // computed/reported based on pixel position (in this case it will likely be fractional,
  43. // such as 123.456789).
  44. discreteValues: Infinity,
  45. // pageIncrement: Integer
  46. // If discreteValues is also specified, this indicates the amount of clicks (ie, snap positions)
  47. // that the slider handle is moved via pageup/pagedown keys.
  48. // If discreteValues is not specified, it indicates the number of pixels.
  49. pageIncrement: 2,
  50. // clickSelect: Boolean
  51. // If clicking the slider bar changes the value or not
  52. clickSelect: true,
  53. // slideDuration: Number
  54. // The time in ms to take to animate the slider handle from 0% to 100%,
  55. // when clicking the slider bar to make the handle move.
  56. slideDuration: dijit.defaultDuration,
  57. // Flag to _Templated (TODO: why is this here? I see no widgets in the template.)
  58. widgetsInTemplate: true,
  59. attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, {
  60. id: ""
  61. }),
  62. baseClass: "dijitSlider",
  63. // Apply CSS classes to up/down arrows and handle per mouse state
  64. cssStateNodes: {
  65. incrementButton: "dijitSliderIncrementButton",
  66. decrementButton: "dijitSliderDecrementButton",
  67. focusNode: "dijitSliderThumb"
  68. },
  69. _mousePixelCoord: "pageX",
  70. _pixelCount: "w",
  71. _startingPixelCoord: "x",
  72. _startingPixelCount: "l",
  73. _handleOffsetCoord: "left",
  74. _progressPixelSize: "width",
  75. _onKeyUp: function(/*Event*/ e){
  76. if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; }
  77. this._setValueAttr(this.value, true);
  78. },
  79. _onKeyPress: function(/*Event*/ e){
  80. if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; }
  81. switch(e.charOrCode){
  82. case dojo.keys.HOME:
  83. this._setValueAttr(this.minimum, false);
  84. break;
  85. case dojo.keys.END:
  86. this._setValueAttr(this.maximum, false);
  87. break;
  88. // this._descending === false: if ascending vertical (min on top)
  89. // (this._descending || this.isLeftToRight()): if left-to-right horizontal or descending vertical
  90. case ((this._descending || this.isLeftToRight()) ? dojo.keys.RIGHT_ARROW : dojo.keys.LEFT_ARROW):
  91. case (this._descending === false ? dojo.keys.DOWN_ARROW : dojo.keys.UP_ARROW):
  92. case (this._descending === false ? dojo.keys.PAGE_DOWN : dojo.keys.PAGE_UP):
  93. this.increment(e);
  94. break;
  95. case ((this._descending || this.isLeftToRight()) ? dojo.keys.LEFT_ARROW : dojo.keys.RIGHT_ARROW):
  96. case (this._descending === false ? dojo.keys.UP_ARROW : dojo.keys.DOWN_ARROW):
  97. case (this._descending === false ? dojo.keys.PAGE_UP : dojo.keys.PAGE_DOWN):
  98. this.decrement(e);
  99. break;
  100. default:
  101. return;
  102. }
  103. dojo.stopEvent(e);
  104. },
  105. _onHandleClick: function(e){
  106. if(this.disabled || this.readOnly){ return; }
  107. if(!dojo.isIE){
  108. // make sure you get focus when dragging the handle
  109. // (but don't do on IE because it causes a flicker on mouse up (due to blur then focus)
  110. dijit.focus(this.sliderHandle);
  111. }
  112. dojo.stopEvent(e);
  113. },
  114. _isReversed: function(){
  115. // summary:
  116. // Returns true if direction is from right to left
  117. // tags:
  118. // protected extension
  119. return !this.isLeftToRight();
  120. },
  121. _onBarClick: function(e){
  122. if(this.disabled || this.readOnly || !this.clickSelect){ return; }
  123. dijit.focus(this.sliderHandle);
  124. dojo.stopEvent(e);
  125. var abspos = dojo.position(this.sliderBarContainer, true);
  126. var pixelValue = e[this._mousePixelCoord] - abspos[this._startingPixelCoord];
  127. this._setPixelValue(this._isReversed() ? (abspos[this._pixelCount] - pixelValue) : pixelValue, abspos[this._pixelCount], true);
  128. this._movable.onMouseDown(e);
  129. },
  130. _setPixelValue: function(/*Number*/ pixelValue, /*Number*/ maxPixels, /*Boolean?*/ priorityChange){
  131. if(this.disabled || this.readOnly){ return; }
  132. pixelValue = pixelValue < 0 ? 0 : maxPixels < pixelValue ? maxPixels : pixelValue;
  133. var count = this.discreteValues;
  134. if(count <= 1 || count == Infinity){ count = maxPixels; }
  135. count--;
  136. var pixelsPerValue = maxPixels / count;
  137. var wholeIncrements = Math.round(pixelValue / pixelsPerValue);
  138. this._setValueAttr((this.maximum-this.minimum)*wholeIncrements/count + this.minimum, priorityChange);
  139. },
  140. _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){
  141. // summary:
  142. // Hook so set('value', value) works.
  143. this._set("value", value);
  144. this.valueNode.value = value;
  145. dijit.setWaiState(this.focusNode, "valuenow", value);
  146. this.inherited(arguments);
  147. var percent = (value - this.minimum) / (this.maximum - this.minimum);
  148. var progressBar = (this._descending === false) ? this.remainingBar : this.progressBar;
  149. var remainingBar = (this._descending === false) ? this.progressBar : this.remainingBar;
  150. if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){
  151. this._inProgressAnim.stop(true);
  152. }
  153. if(priorityChange && this.slideDuration > 0 && progressBar.style[this._progressPixelSize]){
  154. // animate the slider
  155. var _this = this;
  156. var props = {};
  157. var start = parseFloat(progressBar.style[this._progressPixelSize]);
  158. var duration = this.slideDuration * (percent-start/100);
  159. if(duration == 0){ return; }
  160. if(duration < 0){ duration = 0 - duration; }
  161. props[this._progressPixelSize] = { start: start, end: percent*100, units:"%" };
  162. this._inProgressAnim = dojo.animateProperty({ node: progressBar, duration: duration,
  163. onAnimate: function(v){ remainingBar.style[_this._progressPixelSize] = (100-parseFloat(v[_this._progressPixelSize])) + "%"; },
  164. onEnd: function(){ delete _this._inProgressAnim; },
  165. properties: props
  166. })
  167. this._inProgressAnim.play();
  168. }else{
  169. progressBar.style[this._progressPixelSize] = (percent*100) + "%";
  170. remainingBar.style[this._progressPixelSize] = ((1-percent)*100) + "%";
  171. }
  172. },
  173. _bumpValue: function(signedChange, /*Boolean?*/ priorityChange){
  174. if(this.disabled || this.readOnly){ return; }
  175. var s = dojo.getComputedStyle(this.sliderBarContainer);
  176. var c = dojo._getContentBox(this.sliderBarContainer, s);
  177. var count = this.discreteValues;
  178. if(count <= 1 || count == Infinity){ count = c[this._pixelCount]; }
  179. count--;
  180. var value = (this.value - this.minimum) * count / (this.maximum - this.minimum) + signedChange;
  181. if(value < 0){ value = 0; }
  182. if(value > count){ value = count; }
  183. value = value * (this.maximum - this.minimum) / count + this.minimum;
  184. this._setValueAttr(value, priorityChange);
  185. },
  186. _onClkBumper: function(val){
  187. if(this.disabled || this.readOnly || !this.clickSelect){ return; }
  188. this._setValueAttr(val, true);
  189. },
  190. _onClkIncBumper: function(){
  191. this._onClkBumper(this._descending === false ? this.minimum : this.maximum);
  192. },
  193. _onClkDecBumper: function(){
  194. this._onClkBumper(this._descending === false ? this.maximum : this.minimum);
  195. },
  196. decrement: function(/*Event*/ e){
  197. // summary:
  198. // Decrement slider
  199. // tags:
  200. // private
  201. this._bumpValue(e.charOrCode == dojo.keys.PAGE_DOWN ? -this.pageIncrement : -1);
  202. },
  203. increment: function(/*Event*/ e){
  204. // summary:
  205. // Increment slider
  206. // tags:
  207. // private
  208. this._bumpValue(e.charOrCode == dojo.keys.PAGE_UP ? this.pageIncrement : 1);
  209. },
  210. _mouseWheeled: function(/*Event*/ evt){
  211. // summary:
  212. // Event handler for mousewheel where supported
  213. dojo.stopEvent(evt);
  214. var janky = !dojo.isMozilla;
  215. var scroll = evt[(janky ? "wheelDelta" : "detail")] * (janky ? 1 : -1);
  216. this._bumpValue(scroll < 0 ? -1 : 1, true); // negative scroll acts like a decrement
  217. },
  218. startup: function(){
  219. if(this._started){ return; }
  220. dojo.forEach(this.getChildren(), function(child){
  221. if(this[child.container] != this.containerNode){
  222. this[child.container].appendChild(child.domNode);
  223. }
  224. }, this);
  225. this.inherited(arguments);
  226. },
  227. _typematicCallback: function(/*Number*/ count, /*Object*/ button, /*Event*/ e){
  228. if(count == -1){
  229. this._setValueAttr(this.value, true);
  230. }else{
  231. this[(button == (this._descending? this.incrementButton : this.decrementButton)) ? "decrement" : "increment"](e);
  232. }
  233. },
  234. buildRendering: function(){
  235. this.inherited(arguments);
  236. if(this.showButtons){
  237. this.incrementButton.style.display="";
  238. this.decrementButton.style.display="";
  239. }
  240. // find any associated label element and add to slider focusnode.
  241. var label = dojo.query('label[for="'+this.id+'"]');
  242. if(label.length){
  243. label[0].id = (this.id+"_label");
  244. dijit.setWaiState(this.focusNode, "labelledby", label[0].id);
  245. }
  246. dijit.setWaiState(this.focusNode, "valuemin", this.minimum);
  247. dijit.setWaiState(this.focusNode, "valuemax", this.maximum);
  248. },
  249. postCreate: function(){
  250. this.inherited(arguments);
  251. if(this.showButtons){
  252. this._connects.push(dijit.typematic.addMouseListener(
  253. this.decrementButton, this, "_typematicCallback", 25, 500));
  254. this._connects.push(dijit.typematic.addMouseListener(
  255. this.incrementButton, this, "_typematicCallback", 25, 500));
  256. }
  257. this.connect(this.domNode, !dojo.isMozilla ? "onmousewheel" : "DOMMouseScroll", "_mouseWheeled");
  258. // define a custom constructor for a SliderMover that points back to me
  259. var mover = dojo.declare(dijit.form._SliderMover, {
  260. widget: this
  261. });
  262. this._movable = new dojo.dnd.Moveable(this.sliderHandle, {mover: mover});
  263. this._layoutHackIE7();
  264. },
  265. destroy: function(){
  266. this._movable.destroy();
  267. if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){
  268. this._inProgressAnim.stop(true);
  269. }
  270. this._supportingWidgets = dijit.findWidgets(this.domNode); // tells destroy about pseudo-child widgets (ruler/labels)
  271. this.inherited(arguments);
  272. }
  273. });
  274. dojo.declare("dijit.form._SliderMover",
  275. dojo.dnd.Mover,
  276. {
  277. onMouseMove: function(e){
  278. var widget = this.widget;
  279. var abspos = widget._abspos;
  280. if(!abspos){
  281. abspos = widget._abspos = dojo.position(widget.sliderBarContainer, true);
  282. widget._setPixelValue_ = dojo.hitch(widget, "_setPixelValue");
  283. widget._isReversed_ = widget._isReversed();
  284. }
  285. var coordEvent = e.touches ? e.touches[0] : e, // if multitouch take first touch for coords
  286. pixelValue = coordEvent[widget._mousePixelCoord] - abspos[widget._startingPixelCoord];
  287. widget._setPixelValue_(widget._isReversed_ ? (abspos[widget._pixelCount]-pixelValue) : pixelValue, abspos[widget._pixelCount], false);
  288. },
  289. destroy: function(e){
  290. dojo.dnd.Mover.prototype.destroy.apply(this, arguments);
  291. var widget = this.widget;
  292. widget._abspos = null;
  293. widget._setValueAttr(widget.value, true);
  294. }
  295. });
  296. }