API Docs for: 3.10.3
Show:

File: dial/js/Dial.js

  1. /**
  2. * Create a circular dial value range input visualized as a draggable handle on a
  3. * background element.
  4. *
  5. * @module dial
  6. */
  7. var supportsVML = false;
  8. //testVMLNode;
  9.  
  10. if (Y.UA.ie && Y.UA.ie < 9){
  11. supportsVML = true;
  12. }
  13.  
  14. var Lang = Y.Lang,
  15. Widget = Y.Widget,
  16. Node = Y.Node;
  17.  
  18. /**
  19. * Create a dial to represent an input control capable of representing a
  20. * series of intermediate states based on the position of the Dial's handle.
  21. * These states are typically aligned to a value algorithm whereby the angle of the handle's
  22. * position corresponds to a given value.
  23. *
  24. * @class Dial
  25. * @extends Widget
  26. * @param config {Object} Configuration object
  27. * @constructor
  28. */
  29. function Dial(config) {
  30. Dial.superclass.constructor.apply(this, arguments);
  31. }
  32.  
  33. // Y.Dial static properties
  34.  
  35. /**
  36. * The identity of the widget.
  37. *
  38. * @property NAME
  39. * @type String
  40. * @default 'dial'
  41. * @readOnly
  42. * @protected
  43. * @static
  44. */
  45. Dial.NAME = "dial";
  46.  
  47. /**
  48. * Static property used to define the default attribute configuration of
  49. * the Widget.
  50. *
  51. * @property ATTRS
  52. * @type {Object}
  53. * @protected
  54. * @static
  55. */
  56. Dial.ATTRS = {
  57.  
  58. /**
  59. * minimum value allowed
  60. *
  61. * @attribute min
  62. * @type {Number}
  63. * @default -220
  64. */
  65. min : {
  66. value:-220
  67. },
  68.  
  69. /**
  70. * maximum value allowed
  71. *
  72. * @attribute max
  73. * @type {Number}
  74. * @default 220
  75. */
  76. max : {
  77. value:220
  78. },
  79.  
  80. /**
  81. * diameter of the circular background object.
  82. * Other objects scale accordingly.
  83. * Set this only before rendering.
  84. *
  85. * @attribute diameter
  86. * @type {Number} the number of px in diameter
  87. * @default 100
  88. * @writeOnce
  89. */
  90. diameter : {
  91. value:100
  92. },
  93.  
  94. /**
  95. * diameter of the handle object which users drag to change the value.
  96. * Dial sets the pixel dimension of the handle equal to handleDiameter * diameter.
  97. * Set this only before rendering.
  98. *
  99. * @attribute handleDiameter
  100. * @type {Number}
  101. * @default 0.2
  102. * @writeOnce
  103. */
  104. handleDiameter : {
  105. value:0.2
  106. },
  107.  
  108. /**
  109. * diameter of the marker object which follows the angle of the handle during value changes.
  110. * Dial sets the pixel dimension of the marker equal to markerDiameter * diameter.
  111. * Set this only before rendering.
  112. *
  113. * @attribute markerDiameter
  114. * @type {Number}
  115. * @default 0.1
  116. * @writeOnce
  117. */
  118. markerDiameter : {
  119. value:0.1
  120. },
  121.  
  122. /**
  123. * diameter of the center button object.
  124. * Dial sets the pixel dimension of the centerButton equal to centerButtonDiameter * diameter.
  125. * Set this only before rendering.
  126. *
  127. * @attribute centerButtonDiameter
  128. * @type {Number}
  129. * @default 0.1
  130. * @writeOnce
  131. */
  132. centerButtonDiameter : {
  133. value:0.5
  134. },
  135.  
  136. /**
  137. * initial value of the Dial
  138. *
  139. * @attribute value
  140. * @type {Number}
  141. * @default 0
  142. */
  143. value : {
  144. value:0,
  145. validator: function(val) {
  146. return this._validateValue(val);
  147. }
  148. },
  149.  
  150. /**
  151. * amount to increment/decrement the dial value
  152. * when the arrow up/down/left/right keys are pressed
  153. *
  154. * @attribute minorStep
  155. * @type {Number}
  156. * @default 1
  157. */
  158. minorStep : {
  159. value:1
  160. },
  161.  
  162. /**
  163. * amount to increment/decrement the dial value
  164. * when the page up/down keys are pressed
  165. *
  166. * @attribute majorStep
  167. * @type {Number}
  168. * @default 10
  169. */
  170. majorStep : {
  171. value:10
  172. },
  173.  
  174. /**
  175. * number of value increments in one 360 degree revolution
  176. * of the handle around the dial
  177. *
  178. * @attribute stepsPerRevolution
  179. * @type {Number}
  180. * @default 100
  181. */
  182. stepsPerRevolution : {
  183. value:100
  184. },
  185.  
  186. /**
  187. * number of decimal places of accuracy in the value
  188. *
  189. * @attribute decimalPlaces
  190. * @type {Number}
  191. * @default 0
  192. */
  193. decimalPlaces : {
  194. value:0
  195. },
  196.  
  197. /**
  198. * visible strings for the dial UI. This attribute is
  199. * defined by the base Widget class but has an empty value. The
  200. * Dial is simply providing a default value for the attribute.
  201. * Gets localized strings in the current language
  202. *
  203. * @attribute strings
  204. * @type {Object} the values are HTML strings
  205. * @default {label: 'My label', resetStr: 'Reset', tooltipHandle: 'Drag to set value'}
  206. */
  207. strings: {
  208. valueFn: function () {
  209. return Y.Intl.get('dial');
  210. }
  211. },
  212.  
  213. /**
  214. * distance from the center of the dial to the
  215. * center of the marker and handle, when at rest.
  216. * The value is a percent of the radius of the dial.
  217. *
  218. * @attribute handleDistance
  219. * @type {number}
  220. * @default 0.75
  221. */
  222. handleDistance:{
  223. value:0.75
  224. }
  225.  
  226. };
  227.  
  228. /**
  229. * returns a properly formed yui class name
  230. *
  231. * @method
  232. * @param {String} string to be appended at the end of class name
  233. * @return
  234. * @private
  235. */
  236. function makeClassName(str) {
  237. return Y.ClassNameManager.getClassName(Dial.NAME, str);
  238. }
  239.  
  240. /** array of static constants used to identify the classname applied to the Dial DOM objects
  241. *
  242. * @property CSS_CLASSES
  243. * @type {Array}
  244. * @private
  245. * @static
  246. */
  247. Dial.CSS_CLASSES = {
  248. label : makeClassName("label"),
  249. labelString : makeClassName("label-string"),
  250. valueString : makeClassName("value-string"),
  251. northMark : makeClassName("north-mark"),
  252. ring : makeClassName('ring'),
  253. ringVml : makeClassName('ring-vml'),
  254. marker : makeClassName("marker"),
  255. markerVml : makeClassName("marker-vml"),
  256. markerMaxMin : makeClassName("marker-max-min"),
  257. centerButton : makeClassName("center-button"),
  258. centerButtonVml : makeClassName('center-button-vml'),
  259. resetString : makeClassName("reset-string"),
  260. handle : makeClassName("handle"),
  261. handleVml : makeClassName("handle-vml"),
  262. hidden : makeClassName("hidden"),
  263. dragging : Y.ClassNameManager.getClassName("dd-dragging")
  264. };
  265.  
  266. /* Static constants used to define the markup templates used to create Dial DOM elements */
  267.  
  268.  
  269. /**
  270. * template that will contain the Dial's label.
  271. *
  272. * @property LABEL_TEMPLATE
  273. * @type {HTML}
  274. * @default &lt;div class="[...-label]">&lt;span id="" class="[...-label-string]">{label}&lt;/span>&lt;span class="[...-value-string]">&lt;/span>&lt;/div>
  275. * @protected
  276. */
  277.  
  278. Dial.LABEL_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.label + '"><span id="" class="' + Dial.CSS_CLASSES.labelString + '">{label}</span><span class="' + Dial.CSS_CLASSES.valueString + '"></span></div>';
  279.  
  280. if(supportsVML === false){
  281. /**
  282. * template that will contain the Dial's background ring.
  283. *
  284. * @property RING_TEMPLATE
  285. * @type {HTML}
  286. * @default &lt;div class="[...-ring]">&lt;div class="[...-northMark]">&lt;/div>&lt;/div>
  287. * @protected
  288. */
  289. Dial.RING_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.ring + '"><div class="' + Dial.CSS_CLASSES.northMark + '"></div></div>';
  290.  
  291. /**
  292. * template that will contain the Dial's current angle marker.
  293. *
  294. * @property MARKER_TEMPLATE
  295. * @type {HTML}
  296. * @default &lt;div class="[...-marker] [...-marker-hidden]">&lt;div class="[...-markerUser]">&lt;/div>&lt;/div>
  297. * @protected
  298. */
  299. Dial.MARKER_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.marker + ' ' + Dial.CSS_CLASSES.hidden + '"></div>';
  300.  
  301. /**
  302. * template that will contain the Dial's center button.
  303. *
  304. * @property CENTER_BUTTON_TEMPLATE
  305. * @type {HTML}
  306. * @default &lt;div class="[...-centerButton]">&lt;div class="[...-resetString]">' + Y.Lang.sub('{resetStr}', Dial.ATTRS.strings.value) + '&lt;/div>&lt;/div>
  307. * @protected
  308. */
  309. Dial.CENTER_BUTTON_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.centerButton + '"><div class="' + Dial.CSS_CLASSES.resetString + ' ' + Dial.CSS_CLASSES.hidden + '">{resetStr}</div></div>';
  310.  
  311. /**
  312. * template that will contain the Dial's handle.
  313. *
  314. * @property HANDLE_TEMPLATE
  315. * @type {HTML}
  316. * @default &lt;div class="[...-handle]">&lt;div class="[...-handleUser]" aria-labelledby="" aria-valuetext="" aria-valuemax="" aria-valuemin="" aria-valuenow="" role="slider" tabindex="0">&lt;/div>&lt;/div>';// title="{tooltipHandle}"
  317. * @protected
  318. */
  319. Dial.HANDLE_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.handle + '" aria-labelledby="" aria-valuetext="" aria-valuemax="" aria-valuemin="" aria-valuenow="" role="slider" tabindex="0" title="{tooltipHandle}">';
  320.  
  321. }else{ // VML case
  322. Dial.RING_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.ring + ' ' + Dial.CSS_CLASSES.ringVml + '">'+
  323. '<div class="' + Dial.CSS_CLASSES.northMark + '"></div>'+
  324. '<v:oval strokecolor="#ceccc0" strokeweight="1px"><v:fill type=gradient color="#8B8A7F" color2="#EDEDEB" angle="45"/></v:oval>'+
  325. '</div>'+
  326. '';
  327. Dial.MARKER_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.markerVml + ' ' + Dial.CSS_CLASSES.hidden + '">'+
  328. '<v:oval stroked="false">'+
  329. '<v:fill opacity="20%" color="#000"/>'+
  330. '</v:oval>'+
  331. '</div>'+
  332. '';
  333. Dial.CENTER_BUTTON_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.centerButton + ' ' + Dial.CSS_CLASSES.centerButtonVml + '">'+
  334. '<v:oval strokecolor="#ceccc0" strokeweight="1px">'+
  335. '<v:fill type=gradient color="#C7C5B9" color2="#fefcf6" colors="35% #d9d7cb, 65% #fefcf6" angle="45"/>'+
  336. '<v:shadow on="True" color="#000" opacity="10%" offset="2px, 2px"/>'+
  337. '</v:oval>'+
  338. '<div class="' + Dial.CSS_CLASSES.resetString + ' ' + Dial.CSS_CLASSES.hidden + '">{resetStr}</div>'+
  339. '</div>'+
  340. '';
  341. Dial.HANDLE_TEMPLATE = '<div class="' + Dial.CSS_CLASSES.handleVml + '" aria-labelledby="" aria-valuetext="" aria-valuemax="" aria-valuemin="" aria-valuenow="" role="slider" tabindex="0" title="{tooltipHandle}">'+
  342. '<v:oval stroked="false">'+
  343. '<v:fill opacity="20%" color="#6C3A3A"/>'+
  344. '</v:oval>'+
  345. '</div>'+
  346. '';
  347. }
  348.  
  349. /* Dial extends the base Widget class */
  350. Y.extend(Dial, Widget, {
  351.  
  352. /**
  353. * creates the DOM structure for the Dial.
  354. *
  355. * @method renderUI
  356. * @protected
  357. */
  358. renderUI : function() {
  359. this._renderLabel();
  360. this._renderRing();
  361. this._renderMarker();
  362. this._renderCenterButton();
  363. this._renderHandle();
  364.  
  365. // object handles
  366. this.contentBox = this.get("contentBox");
  367.  
  368. // constants
  369. this._originalValue = this.get('value');
  370. this._minValue = this.get('min'); // saves doing a .get many times, but we need to remember to update this if/when we allow changing min or max after instantiation
  371. this._maxValue = this.get('max');
  372. this._stepsPerRevolution = this.get('stepsPerRevolution');
  373. this._minTimesWrapped = (Math.floor(this._minValue / this._stepsPerRevolution - 1));
  374. this._maxTimesWrapped = (Math.floor(this._maxValue / this._stepsPerRevolution + 1));
  375.  
  376. // variables
  377. this._timesWrapped = 0;
  378. this._angle = this._getAngleFromValue(this.get('value'));
  379. this._prevAng = this._angle;
  380.  
  381. // init
  382. this._setTimesWrappedFromValue(this._originalValue);
  383. this._handleNode.set('aria-valuemin', this._minValue);
  384. this._handleNode.set('aria-valuemax', this._maxValue);
  385. },
  386.  
  387. /**
  388. * Sets -webkit-border-radius to 50% of width/height of the ring, handle, marker, and center-button.
  389. * This is needed for iOS 3.x.
  390. * The objects render square if the radius is > 50% of the width/height
  391. * @method _setBorderRadius
  392. * @private
  393. */
  394. _setBorderRadius : function(){
  395. this._ringNode.setStyles({'WebkitBorderRadius':this._ringNodeRadius + 'px',
  396. 'MozBorderRadius':this._ringNodeRadius + 'px',
  397. 'borderRadius':this._ringNodeRadius + 'px'
  398. });
  399. this._handleNode.setStyles({'WebkitBorderRadius':this._handleNodeRadius + 'px',
  400. 'MozBorderRadius':this._handleNodeRadius + 'px',
  401. 'borderRadius':this._handleNodeRadius + 'px'
  402. });
  403. this._markerNode.setStyles({'WebkitBorderRadius':this._markerNodeRadius + 'px',
  404. 'MozBorderRadius':this._markerNodeRadius + 'px',
  405. 'borderRadius':this._markerNodeRadius + 'px'
  406. });
  407. this._centerButtonNode.setStyles({'WebkitBorderRadius':this._centerButtonNodeRadius + 'px',
  408. 'MozBorderRadius':this._centerButtonNodeRadius + 'px',
  409. 'borderRadius':this._centerButtonNodeRadius + 'px'
  410. });
  411. },
  412.  
  413. /**
  414. * Handles the mouseenter on the centerButton
  415. *
  416. * @method _handleCenterButtonEnter
  417. * @protected
  418. */
  419. _handleCenterButtonEnter : function(){
  420. this._resetString.removeClass(Dial.CSS_CLASSES.hidden);
  421. },
  422.  
  423. /**
  424. * Handles the mouseleave on the centerButton
  425. *
  426. * @method _handleCenterButtonLeave
  427. * @protected
  428. */
  429. _handleCenterButtonLeave : function(){
  430. this._resetString.addClass(Dial.CSS_CLASSES.hidden);
  431. },
  432.  
  433. /**
  434. * Creates the Y.DD.Drag instance used for the handle movement and
  435. * binds Dial interaction to the configured value model.
  436. *
  437. * @method bindUI
  438. * @protected
  439. */
  440. bindUI : function() {
  441.  
  442. this.after("valueChange", this._afterValueChange);
  443.  
  444. var boundingBox = this.get("boundingBox"),
  445. // Looking for a key event which will fire continously across browsers while the key is held down.
  446. keyEvent = (!Y.UA.opera) ? "down:" : "press:",
  447. // 38, 40 = arrow up/down, 33, 34 = page up/down, 35 , 36 = end/home
  448. keyEventSpec = keyEvent + "38,40,33,34,35,36",
  449. // 37 , 39 = arrow left/right
  450. keyLeftRightSpec = keyEvent + "37,39",
  451. // 37 , 39 = arrow left/right + meta (command/apple key) for mac
  452. keyLeftRightSpecMeta = keyEvent + "37+meta,39+meta",
  453. Drag = Y.DD.Drag;
  454.  
  455. Y.on("key", Y.bind(this._onDirectionKey, this), boundingBox, keyEventSpec);
  456. Y.on("key", Y.bind(this._onLeftRightKey, this), boundingBox, keyLeftRightSpec);
  457. boundingBox.on("key", this._onLeftRightKeyMeta, keyLeftRightSpecMeta, this);
  458.  
  459. Y.on('mouseenter', Y.bind(this._handleCenterButtonEnter, this), this._centerButtonNode);
  460. Y.on('mouseleave', Y.bind(this._handleCenterButtonLeave, this), this._centerButtonNode);
  461. // Needed to replace mousedown/up with gesturemovestart/end to make behavior on touch devices work the same.
  462. Y.on('gesturemovestart', Y.bind(this._resetDial, this), this._centerButtonNode); //[#2530441]
  463. Y.on('gesturemoveend', Y.bind(this._handleCenterButtonMouseup, this), this._centerButtonNode);
  464.  
  465.  
  466. Y.on(Drag.START_EVENT, Y.bind(this._handleHandleMousedown, this), this._handleNode);
  467. Y.on(Drag.START_EVENT, Y.bind(this._handleMousedown, this), this._ringNode); // [#2530766]
  468.  
  469. //TODO: Can this be merged this into the drag:end event listener to avoid another registration?
  470. Y.on('gesturemoveend', Y.bind(this._handleRingMouseup, this), this._ringNode);
  471.  
  472. this._dd1 = new Drag({ //// [#2530206] changed global this._dd1 from just var dd1 = new Y.DD.drag so
  473. node: this._handleNode,
  474. on : {
  475. 'drag:drag' : Y.bind(this._handleDrag, this),
  476. 'drag:start' : Y.bind(this._handleDragStart, this),
  477. 'drag:end' : Y.bind(this._handleDragEnd, this) //,
  478. }
  479. });
  480. Y.bind(this._dd1.addHandle(this._ringNode), this); // [#2530206] added the ring as a handle to the dd1 (the dd of the handleNode)
  481. },
  482.  
  483. /**
  484. * Sets _timesWrapped based on Dial value
  485. * to net integer revolutions the user dragged the handle around the Dial
  486. *
  487. * @method _setTimesWrappedFromValue
  488. * @param val {Number} current value of the Dial
  489. * @private
  490. */
  491. _setTimesWrappedFromValue : function(val){
  492. if(val % this._stepsPerRevolution === 0){
  493. this._timesWrapped = (val / this._stepsPerRevolution);
  494. }else{
  495. this._timesWrapped = Math.floor(val / this._stepsPerRevolution);
  496. }
  497. },
  498.  
  499. /**
  500. * gets the angle of the line from the center of the Dial to the center of the handle
  501. *
  502. * @method _getAngleFromHandleCenter
  503. * @param handleCenterX {number}
  504. * @param handleCenterY {number}
  505. * @return ang {number} the angle
  506. * @protected
  507. */
  508. _getAngleFromHandleCenter : function(handleCenterX, handleCenterY){
  509. var ang = Math.atan( (this._dialCenterY - handleCenterY) / (this._dialCenterX - handleCenterX) ) * (180 / Math.PI);
  510. ang = ((this._dialCenterX - handleCenterX) < 0) ? ang + 90 : ang + 90 + 180; // Compensate for neg angles from Math.atan
  511. return ang;
  512. },
  513.  
  514. /**
  515. * calculates the XY of the center of the dial relative to the ring node.
  516. * This is needed for calculating the angle of the handle
  517. *
  518. * @method _calculateDialCenter
  519. * @protected
  520. */
  521. _calculateDialCenter : function(){ // #2531111 value, and marker don't track handle when dial position changes on page (resize when inline)
  522. this._dialCenterX = this._ringNode.get('offsetWidth') / 2;
  523. this._dialCenterY = this._ringNode.get('offsetHeight') / 2;
  524. },
  525.  
  526. /**
  527. * Handles the mouseup on the ring
  528. *
  529. * @method _handleRingMouseup
  530. * @protected
  531. */
  532. _handleRingMouseup : function(){
  533. this._handleNode.focus(); // need to re-focus on the handle so keyboard is accessible [#2530206]
  534. },
  535.  
  536. /**
  537. * Handles the mouseup on the centerButton
  538. *
  539. * @method _handleCenterButtonMouseup
  540. * @protected
  541. */
  542. _handleCenterButtonMouseup : function(){
  543. this._handleNode.focus(); // need to re-focus on the handle so keyboard is accessible [#2530206]
  544. },
  545.  
  546. /**
  547. * Handles the mousedown on the handle
  548. *
  549. * @method _handleHandleMousedown
  550. * @protected
  551. */
  552. _handleHandleMousedown : function(){
  553. this._handleNode.focus(); // need to re-focus on the handle so keyboard is accessible [#2530206]
  554. // this is better done here instead of on _handleDragEnd
  555. // because we should make the keyboard accessible after a click of the handle
  556. },
  557.  
  558. /**
  559. * handles the user dragging the handle around the Dial, gets the angle,
  560. * checks for wrapping around top center.
  561. * Sets the new value of the Dial
  562. *
  563. * @method _handleDrag
  564. * @param e {DOMEvent} the drag event object
  565. * @protected
  566. */
  567. _handleDrag : function(e){
  568. var handleCenterX,
  569. handleCenterY,
  570. ang,
  571. newValue;
  572.  
  573. // The event was emitted from drag:drag of handle.
  574. // The center of the handle is top left position of the handle node + radius of handle.
  575. // This is different than a mousedown on the ring.
  576. handleCenterX = (parseInt(this._handleNode.getStyle('left'),10) + this._handleNodeRadius);
  577. handleCenterY = (parseInt(this._handleNode.getStyle('top'),10) + this._handleNodeRadius);
  578. ang = this._getAngleFromHandleCenter(handleCenterX, handleCenterY);
  579.  
  580. // check for need to set timesWrapped
  581. if((this._prevAng > 270) && (ang < 90)){ // If wrapping, clockwise
  582. if(this._timesWrapped < this._maxTimesWrapped){
  583. this._timesWrapped = (this._timesWrapped + 1);
  584. }
  585. }else if((this._prevAng < 90) && (ang > 270)){ // if un-wrapping, counter-clockwise
  586. if(this._timesWrapped > this._minTimesWrapped){
  587. this._timesWrapped = (this._timesWrapped - 1);
  588. }
  589. }
  590. newValue = this._getValueFromAngle(ang); // This function needs the current _timesWrapped value. That's why it comes after the _timesWrapped code above
  591.  
  592. // If you've gone past max more than one full revolution, we decrement the _timesWrapped value
  593. // This gives the effect of a ratchet mechanism.
  594. // It feels like you are never more than one revolution past max
  595. // The effect is the same for min, only in reverse.
  596. // We can't reset the _timesWrapped to the max or min here.
  597. // If we did, the next (continuous) drag would reset the value incorrectly.
  598. if(newValue > (this._maxValue + this._stepsPerRevolution) ){
  599. this._timesWrapped --;
  600. }else if(newValue < (this._minValue - this._stepsPerRevolution) ){
  601. this._timesWrapped ++;
  602. }
  603. this._prevAng = ang; // need to keep the previous angle in order to check for wrapping on the next drag, click, or keypress
  604.  
  605. this._handleValuesBeyondMinMax(e, newValue);
  606. },
  607.  
  608. /**
  609. * handles a mousedown or gesturemovestart event on the ring node
  610. *
  611. * @method _handleMousedown
  612. * @param e {DOMEvent} the event object
  613. * @private
  614. */
  615. _handleMousedown : function(e){ // #2530306
  616.  
  617. if (this._ringNode.compareTo(e.target)) {
  618. var minAng = this._getAngleFromValue(this._minValue),
  619. maxAng = this._getAngleFromValue(this._maxValue),
  620. newValue, oppositeMidRangeAngle,
  621. handleCenterX, handleCenterY,
  622. ang;
  623.  
  624.  
  625.  
  626. // The event was emitted from mousedown on the ring node,
  627. // so the center of the handle should be the XY of mousedown.
  628. if(Y.UA.ios){ // ios adds the scrollLeft and top onto clientX and Y in a native click
  629. handleCenterX = (e.clientX - this._ringNode.getX());
  630. handleCenterY = (e.clientY - this._ringNode.getY());
  631. }else{
  632. handleCenterX = (e.clientX + Y.one('document').get('scrollLeft') - this._ringNode.getX());
  633. handleCenterY = (e.clientY + Y.one('document').get('scrollTop') - this._ringNode.getY());
  634. }
  635. ang = this._getAngleFromHandleCenter(handleCenterX, handleCenterY);
  636.  
  637. /* ///////////////////////////////////////////////////////////////////////////////////////////////////////
  638. * The next sections of logic
  639. * set this._timesWrapped in the different cases of value range
  640. * and value range position,
  641. * then the Dial value is set at the end of this method
  642. */ ///////////////////////////////////////////////////////////////////////////////////////////////////////
  643.  
  644.  
  645. ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  646. if(this._maxValue - this._minValue > this._stepsPerRevolution){
  647.  
  648. // Case: range min-to-max is greater than stepsPerRevolution (one revolution)
  649.  
  650. // This checks the shortest way around the dial between the prevAng and this ang.
  651. if(Math.abs(this._prevAng - ang) > 180){ // this crossed a wrapping
  652.  
  653. // Only change the _timesWrapped if it's between minTimesWrapped and maxTimesWrapped
  654. if((this._timesWrapped > this._minTimesWrapped) &&
  655. (this._timesWrapped < this._maxTimesWrapped)
  656. ){
  657. // this checks which direction, clock wise or CCW and incr or decr _timesWrapped
  658. this._timesWrapped = ((this._prevAng - ang) > 0) ? (this._timesWrapped + 1) : (this._timesWrapped - 1);
  659. }
  660. // special case of getting un-stuck from a min value case
  661. // where timesWrapped is minTimesWrapped but new ang won't trigger a cross wrap boundry
  662. // because prevAng is set to 0 or > 0
  663. }else if(
  664. (this._timesWrapped === this._minTimesWrapped) &&
  665. (ang - this._prevAng < 180)
  666. ){
  667. this._timesWrapped ++;
  668. } //it didn't cross a wrapping boundary
  669.  
  670. } /////////////////////////////////////////////////////////////////////////////////////////////////////////
  671. else if(this._maxValue - this._minValue === this._stepsPerRevolution){
  672. // Case: range min-to-max === stepsPerRevolution (one revolution)
  673. // This means min and max will be at same angle
  674. // This does not mean they are at "north"
  675.  
  676. if(ang < minAng){ // if mousedown angle is < minAng (and maxAng, because they're the same)
  677. // The only way it can be, is if min and max are not at north
  678. this._timesWrapped = 1;
  679. }else{
  680. this._timesWrapped = 0;
  681. }
  682.  
  683. } //////////////////////////////////////////////////////////////////////////////////////////////////////////
  684. else if(minAng > maxAng){
  685. // Case: range includes the wrap point (north)
  686. // Because of "else if"...
  687. // range is < stepsPerRevolution
  688.  
  689. if(
  690. (this._prevAng >= minAng) && // if prev angle was greater than angle of min and...
  691. (ang <= (minAng + maxAng) / 2) // the angle of this click is less than
  692. // the angle opposite the mid-range angle, then...
  693. ){
  694. this._timesWrapped ++;
  695. }else if(
  696. (this._prevAng <= maxAng) &&
  697. // if prev angle is < max angle and...
  698.  
  699. (ang > (minAng + maxAng) / 2)
  700. // the angle of this click is greater than,
  701. // the angle opposite the mid-range angle and...
  702.  
  703. ){
  704. this._timesWrapped --;
  705. }
  706.  
  707. } ////////////////////////////////////////////////////////////////////////////////////////////////////
  708. else{
  709. // "else" Case: min-to-max range doesn't include the wrap point
  710. // Because of "else if"...
  711. // range is still < stepsPerRevolution
  712.  
  713. if ((ang < minAng) || (ang > maxAng)){ // angle is out of range
  714. oppositeMidRangeAngle = (((minAng + maxAng) / 2) + 180) % 360;
  715. // This is the bisection of the min-to-max range + 180. (opposite the bisection)
  716.  
  717. if(oppositeMidRangeAngle > 180){
  718. newValue = ((maxAng < ang) && (ang < oppositeMidRangeAngle)) ? this.get('max') : this.get('min');
  719. }else{ //oppositeMidRangeAngle <= 180
  720. newValue = ((minAng > ang) && (ang > oppositeMidRangeAngle)) ? this.get('min') : this.get('max');
  721. }
  722. this._prevAng = this._getAngleFromValue(newValue);
  723. this.set('value', newValue);
  724. this._setTimesWrappedFromValue(newValue);
  725. return;
  726. }
  727. }
  728.  
  729. // Now that _timesWrapped is set, set newValue .......................................................................
  730. newValue = this._getValueFromAngle(ang); // This function needs the correct, current _timesWrapped value.
  731.  
  732.  
  733. /* updating _prevAng (previous angle)
  734. * When past min or max, _prevAng is set to the angle of min or max
  735. * Don't do this in a drag method, or it will affect wrapping,
  736. * causing the marker to stick at min, when min is 0 degrees (north)
  737. * #2532878
  738. */
  739. if (newValue > this._maxValue) {
  740. this._prevAng = this._getAngleFromValue(this._maxValue); // #2530766 need for mousedown on the ring; causes prob for drag
  741. } else if (newValue < this._minValue) {
  742. this._prevAng = this._getAngleFromValue(this._minValue);
  743. } else {
  744. this._prevAng = ang;
  745. }
  746.  
  747. this._handleValuesBeyondMinMax(e, newValue);
  748. }
  749. },
  750.  
  751. /**
  752. * handles the case where the value is less than min or greater than max
  753. * This is used both when handle is dragged and when the ring is clicked
  754. *
  755. * @method _handleValuesBeyondMinMax
  756. * @param e {DOMEvent} the event object
  757. * @param newValue {number} current value of the dial
  758. * @protected
  759. */
  760. _handleValuesBeyondMinMax : function(e, newValue){ // #2530306
  761. // If _getValueFromAngle() is passed 0, it increments the _timesWrapped value.
  762. // handle hitting max and min and going beyond, stops at max or min
  763. if((newValue >= this._minValue) && (newValue <= this._maxValue)) {
  764. this.set('value', newValue);
  765. // [#2530206] transfer the mousedown event from the _ringNode to the _handleNode drag, so we can mousedown, then continue dragging
  766. if(e.currentTarget === this._ringNode){
  767. // Delegate to DD's natural behavior
  768. this._dd1._handleMouseDownEvent(e);
  769. }
  770. } else if (newValue > this._maxValue) {
  771. this.set('value', this._maxValue);
  772. } else if (newValue < this._minValue) {
  773. this.set('value', this._minValue);
  774. }
  775. },
  776.  
  777. /**
  778. * handles the user starting to drag the handle around the Dial
  779. *
  780. * @method _handleDragStart
  781. * @param e {DOMEvent} the drag event object
  782. * @protected
  783. */
  784. _handleDragStart : function(e){
  785. this._markerNode.removeClass(Dial.CSS_CLASSES.hidden);
  786. },
  787.  
  788. /*
  789. * When handle is handleDragEnd, this animates the return to the fixed dial
  790. */
  791.  
  792. /**
  793. * handles the end of a user dragging the handle, animates the handle returning to
  794. * resting position.
  795. *
  796. * @method _handleDragEnd
  797. * @protected
  798. */
  799. _handleDragEnd : function(){
  800. var node = this._handleNode;
  801. node.transition({
  802. duration: 0.08, // seconds
  803. easing: 'ease-in',
  804. left: this._setNodeToFixedRadius(this._handleNode, true)[0] + 'px',
  805. top: this._setNodeToFixedRadius(this._handleNode, true)[1] + 'px'
  806. }, Y.bind(function(){
  807. var value = this.get('value');
  808. //[#2530206] only hide marker if not at max or min
  809. // more persistant user visibility of when the dial is at max or min
  810. if((value > this._minValue) && (value < this._maxValue)){
  811. this._markerNode.addClass(Dial.CSS_CLASSES.hidden);
  812. }else{
  813. this._setTimesWrappedFromValue(value); //#2530766 secondary bug when drag past max + cross wrapping boundry
  814. this._prevAng = this._getAngleFromValue(value); //#2530766 secondary bug when drag past max + cross wrapping boundry
  815. }
  816. }, this)
  817. );
  818. },
  819.  
  820. /**
  821. * returns the XY of the fixed position, handleDistance, from the center of the Dial (resting position).
  822. * The XY also represents the angle related to the current value.
  823. * If typeArray is true, [X,Y] is returned.
  824. * If typeArray is false, the XY of the obj node passed in is set.
  825. *
  826. * @method _setNodeToFixedRadius
  827. * @param obj {Node}
  828. * @param typeArray {Boolean} true returns an array [X,Y]
  829. * @protected
  830. * @return {Array} an array of [XY] is optionally returned
  831. */
  832. _setNodeToFixedRadius : function(obj, typeArray){
  833. var thisAngle = (this._angle - 90),
  834. rad = (Math.PI / 180),
  835. newY = Math.round(Math.sin(thisAngle * rad) * this._handleDistance),
  836. newX = Math.round(Math.cos(thisAngle * rad) * this._handleDistance),
  837. dia = obj.get('offsetWidth'); //Ticket #2529852
  838.  
  839. newY = newY - (dia * 0.5);
  840. newX = newX - (dia * 0.5);
  841. if(typeArray){ // just need the style for css transform left and top to animate the handle drag:end
  842. return [(this._ringNodeRadius + newX), (this._ringNodeRadius + newY)];
  843. }else{
  844. obj.setStyle('left', (this._ringNodeRadius + newX) + 'px');
  845. obj.setStyle('top', (this._ringNodeRadius + newY) + 'px');
  846. }
  847. },
  848.  
  849. /**
  850. * Synchronizes the DOM state with the attribute settings.
  851. *
  852. * @method syncUI
  853. */
  854. syncUI : function() {
  855. // Make the marker and the resetString display so their placement and borderRadius can be calculated, then hide them again.
  856. // We would have used visibility:hidden in the css of this class,
  857. // but IE8 VML never returns to visible after applying visibility:hidden then removing it.
  858. this._setSizes();
  859. this._calculateDialCenter(); // #2531111 initialize center of dial
  860. this._setBorderRadius();
  861. this._uiSetValue(this.get("value"));
  862. this._markerNode.addClass(Dial.CSS_CLASSES.hidden);
  863. this._resetString.addClass(Dial.CSS_CLASSES.hidden);
  864. },
  865.  
  866. /**
  867. * sets the sizes of ring, center-button, marker, handle, and VML ovals in pixels.
  868. * Needed only because some IE versions
  869. * ignore CSS percent sizes/offsets.
  870. * so these must be set in pixels.
  871. * Normally these are set in % of the ring.
  872. *
  873. * @method _setSizes
  874. * @protected
  875. */
  876. _setSizes : function(){
  877. var dia = this.get('diameter'),
  878. offset, offsetResetX, offsetResetY,
  879. setSize = function(node, dia, percent){
  880. var suffix = 'px';
  881. node.getElementsByTagName('oval').setStyle('width', (dia * percent) + suffix);
  882. node.getElementsByTagName('oval').setStyle('height', (dia * percent) + suffix);
  883. node.setStyle('width', (dia * percent) + suffix);
  884. node.setStyle('height', (dia * percent) + suffix);
  885. };
  886. setSize(this._ringNode, dia, 1.0);
  887. setSize(this._handleNode, dia, this.get('handleDiameter'));
  888. setSize(this._markerNode, dia, this.get('markerDiameter'));
  889. setSize(this._centerButtonNode, dia, this.get('centerButtonDiameter'));
  890.  
  891. // Set these (used for trig) this way instead of relative to dia,
  892. // in case they have borders, have images etc.
  893. this._ringNodeRadius = this._ringNode.get('offsetWidth') * 0.5;
  894. this._handleNodeRadius = this._handleNode.get('offsetWidth') * 0.5;
  895. this._markerNodeRadius = this._markerNode.get('offsetWidth') * 0.5;
  896. this._centerButtonNodeRadius = this._centerButtonNode.get('offsetWidth') * 0.5;
  897. this._handleDistance = this._ringNodeRadius * this.get('handleDistance');
  898. // place the centerButton
  899. offset = (this._ringNodeRadius - this._centerButtonNodeRadius);
  900. this._centerButtonNode.setStyle('left', offset + 'px');
  901. this._centerButtonNode.setStyle('top', offset + 'px');
  902. /*
  903. Place the resetString
  904. This seems like it should be able to be done with CSS,
  905. But since there is also a VML oval in IE that is absolute positioned,
  906. The resetString ends up behind the VML oval.
  907. */
  908. offsetResetX = (this._centerButtonNodeRadius - (this._resetString.get('offsetWidth') * 0.5));
  909. offsetResetY = (this._centerButtonNodeRadius - (this._resetString.get('offsetHeight') * 0.5));
  910. this._resetString.setStyles({'left':offsetResetX + 'px', 'top':offsetResetY + 'px'});
  911. },
  912.  
  913.  
  914. /**
  915. * renders the DOM object for the Dial's label
  916. *
  917. * @method _renderLabel
  918. * @protected
  919. */
  920. _renderLabel : function() {
  921. var contentBox = this.get("contentBox"),
  922. label = contentBox.one("." + Dial.CSS_CLASSES.label);
  923. if (!label) {
  924. label = Node.create(Y.Lang.sub(Dial.LABEL_TEMPLATE, this.get('strings')));
  925. contentBox.append(label);
  926. }
  927. this._labelNode = label;
  928. this._valueStringNode = this._labelNode.one("." + Dial.CSS_CLASSES.valueString);
  929. },
  930.  
  931. /**
  932. * renders the DOM object for the Dial's background ring
  933. *
  934. * @method _renderRing
  935. * @protected
  936. */
  937. _renderRing : function() {
  938. var contentBox = this.get("contentBox"),
  939. ring = contentBox.one("." + Dial.CSS_CLASSES.ring);
  940. if (!ring) {
  941. ring = contentBox.appendChild(Dial.RING_TEMPLATE);
  942. ring.setStyles({width:this.get('diameter') + 'px', height:this.get('diameter') + 'px'});
  943. }
  944. this._ringNode = ring;
  945. },
  946.  
  947. /**
  948. * renders the DOM object for the Dial's background marker which
  949. * tracks the angle of the user dragging the handle
  950. *
  951. * @method _renderMarker
  952. * @protected
  953. */
  954. _renderMarker : function() {
  955. var contentBox = this.get("contentBox"),
  956. marker = contentBox.one("." + Dial.CSS_CLASSES.marker);
  957. if (!marker) {
  958. marker = contentBox.one('.' + Dial.CSS_CLASSES.ring).appendChild(Dial.MARKER_TEMPLATE);
  959. }
  960. this._markerNode = marker;
  961. },
  962.  
  963. /**
  964. * renders the DOM object for the Dial's center
  965. *
  966. * @method _renderCenterButton
  967. * @protected
  968. */
  969. _renderCenterButton : function() {
  970. var contentBox = this.get("contentBox"),
  971. centerButton = contentBox.one("." + Dial.CSS_CLASSES.centerButton);
  972. if (!centerButton) {
  973. centerButton = Node.create(Y.Lang.sub(Dial.CENTER_BUTTON_TEMPLATE, this.get('strings')));
  974. contentBox.one('.' + Dial.CSS_CLASSES.ring).append(centerButton);
  975. }
  976. this._centerButtonNode = centerButton;
  977. this._resetString = this._centerButtonNode.one('.' + Dial.CSS_CLASSES.resetString);
  978. },
  979.  
  980. /**
  981. * renders the DOM object for the Dial's user draggable handle
  982. *
  983. * @method _renderHandle
  984. * @protected
  985. */
  986. _renderHandle : function() {
  987. var labelId = Dial.CSS_CLASSES.label + Y.guid(), //get this unique id once then use for handle and label for ARIA
  988. contentBox = this.get("contentBox"),
  989. handle = contentBox.one("." + Dial.CSS_CLASSES.handle);
  990. if (!handle) {
  991. handle = Node.create(Y.Lang.sub(Dial.HANDLE_TEMPLATE, this.get('strings')));
  992. handle.setAttribute('aria-labelledby', labelId); // get unique id for specifying a label & handle for ARIA
  993. this._labelNode.one('.' + Dial.CSS_CLASSES.labelString).setAttribute('id', labelId); // When handle gets focus, screen reader will include label text when reading the value.
  994. contentBox.one('.' + Dial.CSS_CLASSES.ring).append(handle);
  995. }
  996. this._handleNode = handle;
  997. },
  998.  
  999. /**
  1000. * sets the visible UI label HTML string
  1001. *
  1002. * @method _setLabelString
  1003. * @param str {HTML}
  1004. * @protected
  1005. * @deprecated Use DialObjName.set('strings',{'label':'My new label'}); before DialObjName.render();
  1006.  
  1007. */
  1008. _setLabelString : function(str) {
  1009. this.get("contentBox").one("." + Dial.CSS_CLASSES.labelString).setHTML(str);
  1010. },
  1011.  
  1012. /**
  1013. * sets the visible UI label HTML string
  1014. *
  1015. * @method _setResetString
  1016. * @param str {HTML}
  1017. * @protected
  1018. * @deprecated Use DialObjName.set('strings',{'resetStr':'My new reset string'}); before DialObjName.render();
  1019. */
  1020. _setResetString : function(str) {
  1021. this.get("contentBox").one("." + Dial.CSS_CLASSES.resetString).setHTML(str);
  1022. // this._setXYResetString(); // This used to recenter the string in the button. Done with CSS now. Method has been removed.
  1023. // this._resetString.setHTML(''); //We no longer show/hide the reset string with setHTML but by addClass and removeClass .yui3-dial-reset-string-hidden
  1024. },
  1025.  
  1026. /**
  1027. * sets the tooltip HTML string in the Dial's handle
  1028. *
  1029. * @method _setTooltipString
  1030. * @param str {HTML}
  1031. * @protected
  1032. * @deprecated Use DialObjName.set('strings',{'tooltipHandle':'My new tooltip'}); before DialObjName.render();
  1033. */
  1034. _setTooltipString : function(str) {
  1035. this._handleNode.set('title', str);
  1036. },
  1037.  
  1038. /**
  1039. * sets the Dial's value in response to key events.
  1040. * Left and right keys are in a separate method
  1041. * in case an implementation wants to increment values
  1042. * but needs left and right arrow keys for other purposes.
  1043. *
  1044. * @method _onDirectionKey
  1045. * @param e {Event} the key event
  1046. * @protected
  1047. */
  1048. _onDirectionKey : function(e) {
  1049. e.preventDefault();
  1050. switch (e.charCode) {
  1051. case 38: // up
  1052. this._incrMinor();
  1053. break;
  1054. case 40: // down
  1055. this._decrMinor();
  1056. break;
  1057. case 36: // home
  1058. this._setToMin();
  1059. break;
  1060. case 35: // end
  1061. this._setToMax();
  1062. break;
  1063. case 33: // page up
  1064. this._incrMajor();
  1065. break;
  1066. case 34: // page down
  1067. this._decrMajor();
  1068. break;
  1069. }
  1070. },
  1071.  
  1072. /**
  1073. * sets the Dial's value in response to left or right key events
  1074. *
  1075. * @method _onLeftRightKey
  1076. * @param e {Event} the key event
  1077. * @protected
  1078. */
  1079. _onLeftRightKey : function(e) {
  1080. e.preventDefault();
  1081. switch (e.charCode) {
  1082. case 37: // left
  1083. this._decrMinor();
  1084. break;
  1085. case 39: // right
  1086. this._incrMinor();
  1087. break;
  1088. }
  1089. },
  1090.  
  1091. /**
  1092. * sets the Dial's value in response to left or right key events when a meta (mac command/apple) key is also pressed
  1093. *
  1094. * @method _onLeftRightKeyMeta
  1095. * @param e {Event} the key event
  1096. * @protected
  1097. */
  1098. _onLeftRightKeyMeta : function(e) {
  1099. e.preventDefault();
  1100. switch (e.charCode) {
  1101. case 37: // left + meta
  1102. this._setToMin();
  1103. break;
  1104. case 39: // right + meta
  1105. this._setToMax();
  1106. break;
  1107. }
  1108. },
  1109.  
  1110. /**
  1111. * increments Dial value by a minor increment
  1112. *
  1113. * @method _incrMinor
  1114. * @protected
  1115. */
  1116. _incrMinor : function(){
  1117. var newVal = (this.get('value') + this.get("minorStep"));
  1118. newVal = Math.min(newVal, this.get("max"));
  1119. // [#2530045] .toFixed returns a string.
  1120. // Dial's value needs a number. -0 makes it a number, but removes trailing zeros.
  1121. // Added toFixed(...) again in _uiSetValue where content of yui3-dial-value-string is set.
  1122. // Removing the toFixed here, loses the feature of "snap-to" when for example, stepsPerRevolution is 10 and decimalPlaces is 0.
  1123. this.set('value', newVal.toFixed(this.get('decimalPlaces')) - 0);
  1124. },
  1125.  
  1126. /**
  1127. * decrements Dial value by a minor increment
  1128. *
  1129. * @method _decrMinor
  1130. * @protected
  1131. */
  1132. _decrMinor : function(){
  1133. var newVal = (this.get('value') - this.get("minorStep"));
  1134. newVal = Math.max(newVal, this.get("min"));
  1135. this.set('value', newVal.toFixed(this.get('decimalPlaces')) - 0);
  1136. },
  1137.  
  1138. /**
  1139. * increments Dial value by a major increment
  1140. *
  1141. * @method _incrMajor
  1142. * @protected
  1143. */
  1144. _incrMajor : function(){
  1145. var newVal = (this.get('value') + this.get("majorStep"));
  1146. newVal = Math.min(newVal, this.get("max"));
  1147. this.set('value', newVal.toFixed(this.get('decimalPlaces')) - 0);
  1148. },
  1149.  
  1150. /**
  1151. * decrements Dial value by a major increment
  1152. *
  1153. * @method _decrMajor
  1154. * @protected
  1155. */
  1156. _decrMajor : function(){
  1157. var newVal = (this.get('value') - this.get("majorStep"));
  1158. newVal = Math.max(newVal, this.get("min"));
  1159. this.set('value', newVal.toFixed(this.get('decimalPlaces')) - 0);
  1160. },
  1161.  
  1162. /**
  1163. * sets Dial value to dial's max attr
  1164. *
  1165. * @method _setToMax
  1166. * @protected
  1167. */
  1168. _setToMax : function(){
  1169. this.set('value', this.get("max"));
  1170. },
  1171.  
  1172. /**
  1173. * sets Dial value to dial's min attr
  1174. *
  1175. * @method _setToMin
  1176. * @protected
  1177. */
  1178. _setToMin : function(){
  1179. this.set('value', this.get("min"));
  1180. },
  1181.  
  1182. /**
  1183. * resets Dial value to the orignal initial value.
  1184. *
  1185. * @method _resetDial
  1186. * @protected
  1187. */
  1188. _resetDial : function(e){
  1189. if(e){
  1190. e.stopPropagation(); //[#2530206] need to add so mousedown doesn't propagate to ring and move the handle
  1191. }
  1192. this.set('value', this._originalValue);
  1193. this._resetString.addClass(Dial.CSS_CLASSES.hidden); //[#2530441]
  1194. this._handleNode.focus();
  1195. },
  1196.  
  1197. /**
  1198. * returns the handle angle associated with the current value of the Dial.
  1199. * Returns a number between 0 and 360.
  1200. *
  1201. * @method _getAngleFromValue
  1202. * @param newVal {Number} the current value of the Dial
  1203. * @return {Number} the angle associated with the current Dial value
  1204. * @protected
  1205. */
  1206. _getAngleFromValue : function(newVal){
  1207. var nonWrappedPartOfValue = newVal % this._stepsPerRevolution,
  1208. angleFromValue = nonWrappedPartOfValue / this._stepsPerRevolution * 360;
  1209. return (angleFromValue < 0) ? (angleFromValue + 360) : angleFromValue;
  1210. },
  1211.  
  1212. /**
  1213. * returns the value of the Dial calculated from the current handle angle
  1214. *
  1215. * @method _getValueFromAngle
  1216. * @param angle {Number} the current angle of the Dial's handle
  1217. * @return {Number} the current Dial value corresponding to the handle position
  1218. * @protected
  1219. */
  1220. _getValueFromAngle : function(angle){
  1221. if(angle < 0){
  1222. angle = (360 + angle);
  1223. }else if(angle === 0){
  1224. angle = 360;
  1225. }
  1226. var value = (angle / 360) * this._stepsPerRevolution;
  1227. value = (value + (this._timesWrapped * this._stepsPerRevolution));
  1228. //return Math.round(value * 100) / 100;
  1229. return value.toFixed(this.get('decimalPlaces')) - 0;
  1230. },
  1231.  
  1232. /**
  1233. * calls the method to update the UI whenever the Dial value changes
  1234. *
  1235. * @method _afterValueChange
  1236. * @param e {Event}
  1237. * @protected
  1238. */
  1239. _afterValueChange : function(e) {
  1240. this._uiSetValue(e.newVal);
  1241. },
  1242.  
  1243. /**
  1244. * Changes a value to have the correct decimal places per the attribute decimalPlaces
  1245. *
  1246. * @method _valueToDecimalPlaces
  1247. * @param val {Number} a raw value to set to the Dial
  1248. * @return {Number} the input val changed to have the correct decimal places
  1249. * @protected
  1250. */
  1251. _valueToDecimalPlaces : function(val) { // [#2530206] cleaned up and better user feedback of when it's max or min.
  1252.  
  1253. },
  1254.  
  1255. /**
  1256. * Updates the UI display value of the Dial to reflect
  1257. * the value passed in.
  1258. * Makes all other needed UI display changes
  1259. *
  1260. * @method _uiSetValue
  1261. * @param val {Number} value of the Dial
  1262. * @protected
  1263. */
  1264. _uiSetValue : function(val) { // [#2530206] cleaned up and better user feedback of when it's max or min.
  1265. this._angle = this._getAngleFromValue(val);
  1266. if(this._handleNode.hasClass(Dial.CSS_CLASSES.dragging) === false){
  1267. this._setTimesWrappedFromValue(val);
  1268. this._setNodeToFixedRadius(this._handleNode, false);
  1269. this._prevAng = this._getAngleFromValue(this.get('value'));
  1270. }
  1271. this._valueStringNode.setHTML(val.toFixed(this.get('decimalPlaces'))); // [#2530045]
  1272. this._handleNode.set('aria-valuenow', val);
  1273. this._handleNode.set('aria-valuetext', val);
  1274. this._setNodeToFixedRadius(this._markerNode, false);
  1275. if((val === this._maxValue) || (val === this._minValue)){
  1276. this._markerNode.addClass(Dial.CSS_CLASSES.markerMaxMin);
  1277. if(supportsVML === true){
  1278. this._markerNode.getElementsByTagName('fill').set('color', '#AB3232');
  1279. }
  1280. this._markerNode.removeClass(Dial.CSS_CLASSES.hidden);
  1281. }else{ // not max or min
  1282. if(supportsVML === true){
  1283. this._markerNode.getElementsByTagName('fill').set('color', '#000');
  1284. }
  1285. this._markerNode.removeClass(Dial.CSS_CLASSES.markerMaxMin);
  1286. if(this._handleNode.hasClass(Dial.CSS_CLASSES.dragging) === false){ // if not max || min, and not dragging handle, hide the marker
  1287. this._markerNode.addClass(Dial.CSS_CLASSES.hidden);
  1288. }
  1289. }
  1290. },
  1291.  
  1292. /**
  1293. * value attribute default validator. Verifies that
  1294. * the value being set lies between the min/max value
  1295. *
  1296. * @method _validateValue
  1297. * @param val {Number} value of the Dial
  1298. * @protected
  1299. */
  1300. _validateValue: function(val) {
  1301. var min = this.get("min"),
  1302. max = this.get("max");
  1303. return (Lang.isNumber(val) && val >= min && val <= max);
  1304. }
  1305. });
  1306. Y.Dial = Dial;
  1307.