progressbar.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Implementation of a progress bar.
  16. *
  17. * @author arv@google.com (Erik Arvidsson)
  18. * @see ../demos/progressbar.html
  19. */
  20. goog.provide('goog.ui.ProgressBar');
  21. goog.provide('goog.ui.ProgressBar.Orientation');
  22. goog.require('goog.a11y.aria');
  23. goog.require('goog.asserts');
  24. goog.require('goog.dom');
  25. goog.require('goog.dom.TagName');
  26. goog.require('goog.dom.classlist');
  27. goog.require('goog.events');
  28. goog.require('goog.events.EventType');
  29. goog.require('goog.ui.Component');
  30. goog.require('goog.ui.RangeModel');
  31. goog.require('goog.userAgent');
  32. /**
  33. * This creates a progress bar object.
  34. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
  35. * @constructor
  36. * @extends {goog.ui.Component}
  37. */
  38. goog.ui.ProgressBar = function(opt_domHelper) {
  39. goog.ui.Component.call(this, opt_domHelper);
  40. /** @type {?HTMLDivElement} */
  41. this.thumbElement_;
  42. /**
  43. * The underlying data model for the progress bar.
  44. * @type {goog.ui.RangeModel}
  45. * @private
  46. */
  47. this.rangeModel_ = new goog.ui.RangeModel;
  48. goog.events.listen(
  49. this.rangeModel_, goog.ui.Component.EventType.CHANGE, this.handleChange_,
  50. false, this);
  51. };
  52. goog.inherits(goog.ui.ProgressBar, goog.ui.Component);
  53. goog.tagUnsealableClass(goog.ui.ProgressBar);
  54. /**
  55. * Enum for representing the orientation of the progress bar.
  56. *
  57. * @enum {string}
  58. */
  59. goog.ui.ProgressBar.Orientation = {
  60. VERTICAL: 'vertical',
  61. HORIZONTAL: 'horizontal'
  62. };
  63. /**
  64. * Map from progress bar orientation to CSS class names.
  65. * @type {!Object<string, string>}
  66. * @private
  67. */
  68. goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_ = {};
  69. goog.ui.ProgressBar
  70. .ORIENTATION_TO_CSS_NAME_[goog.ui.ProgressBar.Orientation.VERTICAL] =
  71. goog.getCssName('progress-bar-vertical');
  72. goog.ui.ProgressBar
  73. .ORIENTATION_TO_CSS_NAME_[goog.ui.ProgressBar.Orientation.HORIZONTAL] =
  74. goog.getCssName('progress-bar-horizontal');
  75. /**
  76. * Creates the DOM nodes needed for the progress bar
  77. * @override
  78. */
  79. goog.ui.ProgressBar.prototype.createDom = function() {
  80. this.thumbElement_ = this.createThumb_();
  81. this.setElementInternal(this.getDomHelper().createDom(
  82. goog.dom.TagName.DIV,
  83. goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_[this.orientation_],
  84. this.thumbElement_));
  85. this.setValueState_();
  86. this.setMinimumState_();
  87. this.setMaximumState_();
  88. };
  89. /** @override */
  90. goog.ui.ProgressBar.prototype.enterDocument = function() {
  91. goog.ui.ProgressBar.superClass_.enterDocument.call(this);
  92. this.attachEvents_();
  93. this.updateUi_();
  94. var element = this.getElement();
  95. goog.asserts.assert(element, 'The progress bar DOM element cannot be null.');
  96. // state live = polite will notify the user of updates,
  97. // but will not interrupt ongoing feedback
  98. goog.a11y.aria.setRole(element, 'progressbar');
  99. goog.a11y.aria.setState(element, 'live', 'polite');
  100. };
  101. /** @override */
  102. goog.ui.ProgressBar.prototype.exitDocument = function() {
  103. goog.ui.ProgressBar.superClass_.exitDocument.call(this);
  104. this.detachEvents_();
  105. };
  106. /**
  107. * This creates the thumb element.
  108. * @private
  109. * @return {HTMLDivElement} The created thumb element.
  110. */
  111. goog.ui.ProgressBar.prototype.createThumb_ = function() {
  112. return this.getDomHelper().createDom(
  113. goog.dom.TagName.DIV, goog.getCssName('progress-bar-thumb'));
  114. };
  115. /**
  116. * Adds the initial event listeners to the element.
  117. * @private
  118. */
  119. goog.ui.ProgressBar.prototype.attachEvents_ = function() {
  120. if (goog.userAgent.IE && goog.userAgent.VERSION < 7) {
  121. goog.events.listen(
  122. this.getElement(), goog.events.EventType.RESIZE, this.updateUi_, false,
  123. this);
  124. }
  125. };
  126. /**
  127. * Removes the event listeners added by attachEvents_.
  128. * @private
  129. */
  130. goog.ui.ProgressBar.prototype.detachEvents_ = function() {
  131. if (goog.userAgent.IE && goog.userAgent.VERSION < 7) {
  132. goog.events.unlisten(
  133. this.getElement(), goog.events.EventType.RESIZE, this.updateUi_, false,
  134. this);
  135. }
  136. };
  137. /**
  138. * Decorates an existing HTML DIV element as a progress bar input. If the
  139. * element contains a child with a class name of 'progress-bar-thumb' that will
  140. * be used as the thumb.
  141. * @param {Element} element The HTML element to decorate.
  142. * @override
  143. */
  144. goog.ui.ProgressBar.prototype.decorateInternal = function(element) {
  145. goog.ui.ProgressBar.superClass_.decorateInternal.call(this, element);
  146. goog.dom.classlist.add(
  147. goog.asserts.assert(this.getElement()),
  148. goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_[this.orientation_]);
  149. // find thumb
  150. var thumb = goog.dom.getElementsByTagNameAndClass(
  151. null, goog.getCssName('progress-bar-thumb'), this.getElement())[0];
  152. if (!thumb) {
  153. thumb = this.createThumb_();
  154. this.getElement().appendChild(thumb);
  155. }
  156. this.thumbElement_ = /** @type {!HTMLDivElement} */ (thumb);
  157. };
  158. /**
  159. * @return {number} The value.
  160. */
  161. goog.ui.ProgressBar.prototype.getValue = function() {
  162. return this.rangeModel_.getValue();
  163. };
  164. /**
  165. * Sets the value
  166. * @param {number} v The value.
  167. */
  168. goog.ui.ProgressBar.prototype.setValue = function(v) {
  169. this.rangeModel_.setValue(v);
  170. if (this.getElement()) {
  171. this.setValueState_();
  172. }
  173. };
  174. /**
  175. * Sets the state for a11y of the current value.
  176. * @private
  177. */
  178. goog.ui.ProgressBar.prototype.setValueState_ = function() {
  179. var element = this.getElement();
  180. goog.asserts.assert(element, 'The progress bar DOM element cannot be null.');
  181. goog.a11y.aria.setState(element, 'valuenow', this.getValue());
  182. };
  183. /**
  184. * @return {number} The minimum value.
  185. */
  186. goog.ui.ProgressBar.prototype.getMinimum = function() {
  187. return this.rangeModel_.getMinimum();
  188. };
  189. /**
  190. * Sets the minimum number
  191. * @param {number} v The minimum value.
  192. */
  193. goog.ui.ProgressBar.prototype.setMinimum = function(v) {
  194. this.rangeModel_.setMinimum(v);
  195. if (this.getElement()) {
  196. this.setMinimumState_();
  197. }
  198. };
  199. /**
  200. * Sets the state for a11y of the minimum value.
  201. * @private
  202. */
  203. goog.ui.ProgressBar.prototype.setMinimumState_ = function() {
  204. var element = this.getElement();
  205. goog.asserts.assert(element, 'The progress bar DOM element cannot be null.');
  206. goog.a11y.aria.setState(element, 'valuemin', this.getMinimum());
  207. };
  208. /**
  209. * @return {number} The maximum value.
  210. */
  211. goog.ui.ProgressBar.prototype.getMaximum = function() {
  212. return this.rangeModel_.getMaximum();
  213. };
  214. /**
  215. * Sets the maximum number
  216. * @param {number} v The maximum value.
  217. */
  218. goog.ui.ProgressBar.prototype.setMaximum = function(v) {
  219. this.rangeModel_.setMaximum(v);
  220. if (this.getElement()) {
  221. this.setMaximumState_();
  222. }
  223. };
  224. /**
  225. * Sets the state for a11y of the maximum valiue.
  226. * @private
  227. */
  228. goog.ui.ProgressBar.prototype.setMaximumState_ = function() {
  229. var element = this.getElement();
  230. goog.asserts.assert(element, 'The progress bar DOM element cannot be null.');
  231. goog.a11y.aria.setState(element, 'valuemax', this.getMaximum());
  232. };
  233. /**
  234. *
  235. * @type {goog.ui.ProgressBar.Orientation}
  236. * @private
  237. */
  238. goog.ui.ProgressBar.prototype.orientation_ =
  239. goog.ui.ProgressBar.Orientation.HORIZONTAL;
  240. /**
  241. * Call back when the internal range model changes
  242. * @param {goog.events.Event} e The event object.
  243. * @private
  244. */
  245. goog.ui.ProgressBar.prototype.handleChange_ = function(e) {
  246. this.updateUi_();
  247. this.dispatchEvent(goog.ui.Component.EventType.CHANGE);
  248. };
  249. /**
  250. * This is called when we need to update the size of the thumb. This happens
  251. * when first created as well as when the value and the orientation changes.
  252. * @private
  253. */
  254. goog.ui.ProgressBar.prototype.updateUi_ = function() {
  255. if (this.thumbElement_) {
  256. var min = this.getMinimum();
  257. var max = this.getMaximum();
  258. var val = this.getValue();
  259. var ratio = (val - min) / (max - min);
  260. var size = Math.round(ratio * 100);
  261. if (this.orientation_ == goog.ui.ProgressBar.Orientation.VERTICAL) {
  262. // Note(arv): IE up to version 6 has some serious computation bugs when
  263. // using percentages or bottom. We therefore first set the height to
  264. // 100% and measure that and base the top and height on that size instead.
  265. if (goog.userAgent.IE && goog.userAgent.VERSION < 7) {
  266. this.thumbElement_.style.top = '0';
  267. this.thumbElement_.style.height = '100%';
  268. var h = this.thumbElement_.offsetHeight;
  269. var bottom = Math.round(ratio * h);
  270. this.thumbElement_.style.top = h - bottom + 'px';
  271. this.thumbElement_.style.height = bottom + 'px';
  272. } else {
  273. this.thumbElement_.style.top = (100 - size) + '%';
  274. this.thumbElement_.style.height = size + '%';
  275. }
  276. } else {
  277. this.thumbElement_.style.width = size + '%';
  278. }
  279. }
  280. };
  281. /**
  282. * This is called when we need to setup the UI sizes and positions. This
  283. * happens when we create the element and when we change the orientation.
  284. * @private
  285. */
  286. goog.ui.ProgressBar.prototype.initializeUi_ = function() {
  287. var tStyle = this.thumbElement_.style;
  288. if (this.orientation_ == goog.ui.ProgressBar.Orientation.VERTICAL) {
  289. tStyle.left = '0';
  290. tStyle.width = '100%';
  291. } else {
  292. tStyle.top = tStyle.left = '0';
  293. tStyle.height = '100%';
  294. }
  295. };
  296. /**
  297. * Changes the orientation
  298. * @param {goog.ui.ProgressBar.Orientation} orient The orientation.
  299. */
  300. goog.ui.ProgressBar.prototype.setOrientation = function(orient) {
  301. if (this.orientation_ != orient) {
  302. var oldCss =
  303. goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_[this.orientation_];
  304. var newCss = goog.ui.ProgressBar.ORIENTATION_TO_CSS_NAME_[orient];
  305. this.orientation_ = orient;
  306. // Update the DOM
  307. var element = this.getElement();
  308. if (element) {
  309. goog.dom.classlist.swap(element, oldCss, newCss);
  310. this.initializeUi_();
  311. this.updateUi_();
  312. }
  313. }
  314. };
  315. /**
  316. * @return {goog.ui.ProgressBar.Orientation} The orientation of the
  317. * progress bar.
  318. */
  319. goog.ui.ProgressBar.prototype.getOrientation = function() {
  320. return this.orientation_;
  321. };
  322. /** @override */
  323. goog.ui.ProgressBar.prototype.disposeInternal = function() {
  324. this.detachEvents_();
  325. goog.ui.ProgressBar.superClass_.disposeInternal.call(this);
  326. this.thumbElement_ = null;
  327. this.rangeModel_.dispose();
  328. };
  329. /**
  330. * @return {?number} The step value used to determine how to round the value.
  331. */
  332. goog.ui.ProgressBar.prototype.getStep = function() {
  333. return this.rangeModel_.getStep();
  334. };
  335. /**
  336. * Sets the step value. The step value is used to determine how to round the
  337. * value.
  338. * @param {?number} step The step size.
  339. */
  340. goog.ui.ProgressBar.prototype.setStep = function(step) {
  341. this.rangeModel_.setStep(step);
  342. };