PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/closure/closure-library-20110323-r790/closure/goog/ui/checkbox.js

https://github.com/seanhodges/Closure-Sandbox
JavaScript | 329 lines | 151 code | 50 blank | 128 comment | 29 complexity | cc5f0eaf576101f00dc8b2119f96f357 MD5 | raw file
  1. // Copyright 2009 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 Tristate checkbox widget.
  16. *
  17. * @see ../demos/checkbox.html
  18. */
  19. goog.provide('goog.ui.Checkbox');
  20. goog.provide('goog.ui.Checkbox.State');
  21. goog.require('goog.array');
  22. goog.require('goog.dom.a11y');
  23. goog.require('goog.dom.a11y.Role');
  24. goog.require('goog.dom.a11y.State');
  25. goog.require('goog.dom.classes');
  26. goog.require('goog.events.EventType');
  27. goog.require('goog.events.KeyCodes');
  28. goog.require('goog.events.KeyHandler.EventType');
  29. goog.require('goog.object');
  30. goog.require('goog.ui.Component.EventType');
  31. goog.require('goog.ui.Control');
  32. goog.require('goog.ui.registry');
  33. /**
  34. * 3-state checkbox widget. Fires CHECK or UNCHECK events before toggled and
  35. * CHANGE event after toggled by user.
  36. * The checkbox can also be enabled/disabled and get focused and highlighted.
  37. *
  38. * @param {goog.ui.Checkbox.State=} opt_checked Checked state to set.
  39. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
  40. * document interaction.
  41. * @constructor
  42. * @extends {goog.ui.Control}
  43. */
  44. goog.ui.Checkbox = function(opt_checked, opt_domHelper) {
  45. var checkboxRenderer = goog.ui.ControlRenderer.getCustomRenderer(
  46. goog.ui.ControlRenderer, goog.ui.Checkbox.CSS_CLASS);
  47. goog.ui.Control.call(this, null, checkboxRenderer, opt_domHelper);
  48. // The checkbox maintains its own tri-state CHECKED state.
  49. // The control class maintains DISABLED and FOCUSED (which enable tab
  50. // navigation, and keyHandling with SPACE).
  51. this.setSupportedState(goog.ui.Component.State.ACTIVE, false);
  52. /**
  53. * Checked state of the checkbox.
  54. * @type {goog.ui.Checkbox.State}
  55. * @private
  56. */
  57. this.checked_ = goog.isDef(opt_checked) ?
  58. opt_checked : goog.ui.Checkbox.State.UNCHECKED;
  59. };
  60. goog.inherits(goog.ui.Checkbox, goog.ui.Control);
  61. /**
  62. * Possible checkbox states.
  63. * @enum {?boolean}
  64. */
  65. goog.ui.Checkbox.State = {
  66. CHECKED: true,
  67. UNCHECKED: false,
  68. UNDETERMINED: null
  69. };
  70. /**
  71. * CSS class for checkbox.
  72. * @type {string}
  73. */
  74. goog.ui.Checkbox.CSS_CLASS = goog.getCssName('goog-checkbox');
  75. /**
  76. * Checkbox CSS class names.
  77. * @enum {string}
  78. */
  79. goog.ui.Checkbox.Css = {
  80. CHECKED: goog.getCssName(goog.ui.Checkbox.CSS_CLASS, 'checked'),
  81. UNCHECKED: goog.getCssName(goog.ui.Checkbox.CSS_CLASS, 'unchecked'),
  82. UNDETERMINED: goog.getCssName(goog.ui.Checkbox.CSS_CLASS, 'undetermined')
  83. };
  84. /**
  85. * Map of component states to state-specific structural class names.
  86. * @type {Object}
  87. * @private
  88. */
  89. goog.ui.Checkbox.classByState_ = goog.object.create(
  90. goog.ui.Checkbox.State.CHECKED, goog.ui.Checkbox.Css.CHECKED,
  91. goog.ui.Checkbox.State.UNCHECKED, goog.ui.Checkbox.Css.UNCHECKED,
  92. goog.ui.Checkbox.State.UNDETERMINED, goog.ui.Checkbox.Css.UNDETERMINED);
  93. /**
  94. * Label element bound to the checkbox.
  95. * @type {Element}
  96. * @private
  97. */
  98. goog.ui.Checkbox.prototype.label_ = null;
  99. /**
  100. * @return {goog.ui.Checkbox.State} Checked state of the checkbox.
  101. */
  102. goog.ui.Checkbox.prototype.getChecked = function() {
  103. return this.checked_;
  104. };
  105. /**
  106. * @return {boolean} Whether the checkbox is checked.
  107. */
  108. goog.ui.Checkbox.prototype.isChecked = function() {
  109. return this.checked_ == goog.ui.Checkbox.State.CHECKED;
  110. };
  111. /**
  112. * @return {boolean} Whether the checkbox is not checked.
  113. */
  114. goog.ui.Checkbox.prototype.isUnchecked = function() {
  115. return this.checked_ == goog.ui.Checkbox.State.UNCHECKED;
  116. };
  117. /**
  118. * @return {boolean} Whether the checkbox is in partially checked state.
  119. */
  120. goog.ui.Checkbox.prototype.isUndetermined = function() {
  121. return this.checked_ == goog.ui.Checkbox.State.UNDETERMINED;
  122. };
  123. /**
  124. * Sets the checked state of the checkbox.
  125. * @param {goog.ui.Checkbox.State} checked The checked state to set.
  126. */
  127. goog.ui.Checkbox.prototype.setChecked = function(checked) {
  128. if (checked != this.checked_) {
  129. this.checked_ = checked;
  130. this.updateView();
  131. }
  132. };
  133. /**
  134. * Binds an HTML element to the checkbox which if clicked toggles the checkbox.
  135. * Behaves the same way as the 'label' HTML tag. The label element has to be the
  136. * direct or non-direct ancestor of the checkbox element because it will get the
  137. * focus when keyboard support is implemented.
  138. *
  139. * @param {Element} label The label control to set. If null, only the checkbox
  140. * reacts to clicks.
  141. */
  142. goog.ui.Checkbox.prototype.setLabel = function(label) {
  143. if (this.isInDocument()) {
  144. this.exitDocument();
  145. this.label_ = label;
  146. this.enterDocument();
  147. } else {
  148. this.label_ = label;
  149. }
  150. };
  151. /**
  152. * Toggles the checkbox. State transitions:
  153. * <ul>
  154. * <li>unchecked -> checked
  155. * <li>undetermined -> checked
  156. * <li>checked -> unchecked
  157. * </ul>
  158. */
  159. goog.ui.Checkbox.prototype.toggle = function() {
  160. this.checked_ = this.checked_ ? goog.ui.Checkbox.State.UNCHECKED :
  161. goog.ui.Checkbox.State.CHECKED;
  162. this.updateView();
  163. };
  164. /** @inheritDoc */
  165. goog.ui.Checkbox.prototype.createDom = function() {
  166. this.decorateInternal(this.getDomHelper().createElement('span'));
  167. };
  168. /** @inheritDoc */
  169. goog.ui.Checkbox.prototype.decorateInternal = function(element) {
  170. goog.ui.Checkbox.superClass_.decorateInternal.call(this, element);
  171. var classes = goog.dom.classes.get(element);
  172. // Update the checked state of the element based on its css classNames
  173. // with the following order: undetermined -> checked -> unchecked.
  174. if (goog.array.contains(classes, goog.ui.Checkbox.Css.UNDETERMINED)) {
  175. this.checked_ = goog.ui.Checkbox.State.UNDETERMINED;
  176. } else if (goog.array.contains(classes, goog.ui.Checkbox.Css.CHECKED)) {
  177. this.checked_ = goog.ui.Checkbox.State.CHECKED;
  178. } else if (goog.array.contains(classes, goog.ui.Checkbox.Css.UNCHECKED)) {
  179. this.checked_ = goog.ui.Checkbox.State.UNCHECKED;
  180. } else {
  181. this.updateView();
  182. }
  183. // Initialize ARIA role
  184. goog.dom.a11y.setRole(element, goog.dom.a11y.Role.CHECKBOX);
  185. };
  186. /** @inheritDoc */
  187. goog.ui.Checkbox.prototype.enterDocument = function() {
  188. goog.ui.Checkbox.superClass_.enterDocument.call(this);
  189. if (this.isHandleMouseEvents()) {
  190. this.getHandler().listen(this.label_ || this.getElement(),
  191. goog.events.EventType.CLICK, this.handleClickOrSpace_);
  192. }
  193. };
  194. /**
  195. * Updates the CSS class names after the checked state has changed.
  196. * Also updates the ARIA state.
  197. * @protected
  198. */
  199. goog.ui.Checkbox.prototype.updateView = function() {
  200. var el = this.getElement();
  201. if (el) {
  202. var classToAdd = goog.ui.Checkbox.classByState_[this.checked_];
  203. var elementClassNames = goog.dom.classes.get(el);
  204. if (goog.array.contains(elementClassNames, classToAdd)) {
  205. return;
  206. }
  207. var classesToAssign = [classToAdd];
  208. var checkStateClasses = goog.object.getValues(goog.ui.Checkbox.Css);
  209. goog.array.forEach(elementClassNames, function(name) {
  210. if (!goog.array.contains(checkStateClasses, name)) {
  211. classesToAssign.push(name);
  212. }
  213. });
  214. goog.dom.classes.set(el, classesToAssign.join(' '));
  215. goog.dom.a11y.setState(el, goog.dom.a11y.State.CHECKED,
  216. this.ariaStateFromCheckState_());
  217. }
  218. };
  219. /**
  220. * Gets the checkbox's ARIA (accessibility) state from its checked state.
  221. * @return {string} The value of goog.dom.a11y.state.PRESSED. Either 'true',
  222. * 'false', or 'mixed'.
  223. * @private
  224. */
  225. goog.ui.Checkbox.prototype.ariaStateFromCheckState_ = function() {
  226. if (this.checked_ == goog.ui.Checkbox.State.UNDETERMINED) {
  227. return 'mixed';
  228. } else if (this.checked_ == goog.ui.Checkbox.State.CHECKED) {
  229. return 'true';
  230. } else {
  231. return 'false';
  232. }
  233. };
  234. /**
  235. * Fix for tabindex not being updated so that disabled checkbox is not
  236. * focusable. In particular this fails in Chrome.
  237. * Note: in general tabIndex=-1 will prevent from keyboard focus but enables
  238. * mouse focus, however in this case the control class prevents mouse focus.
  239. * @inheritDoc
  240. */
  241. goog.ui.Checkbox.prototype.setEnabled = function(enabled) {
  242. goog.ui.Checkbox.superClass_.setEnabled.call(this, enabled);
  243. var el = this.getElement();
  244. if (el) {
  245. el.tabIndex = this.isEnabled() ? 0 : -1;
  246. }
  247. };
  248. /**
  249. * Handles the click event.
  250. * @param {!goog.events.BrowserEvent} e The event.
  251. * @private
  252. */
  253. goog.ui.Checkbox.prototype.handleClickOrSpace_ = function(e) {
  254. e.stopPropagation();
  255. var eventType = this.checked_ ? goog.ui.Component.EventType.UNCHECK :
  256. goog.ui.Component.EventType.CHECK;
  257. if (this.isEnabled() && this.dispatchEvent(eventType)) {
  258. this.toggle();
  259. this.dispatchEvent(goog.ui.Component.EventType.CHANGE);
  260. }
  261. };
  262. /**
  263. * @inheritDoc
  264. */
  265. goog.ui.Checkbox.prototype.handleKeyEventInternal = function(e) {
  266. if (e.keyCode == goog.events.KeyCodes.SPACE) {
  267. this.handleClickOrSpace_(e);
  268. }
  269. return false;
  270. };
  271. /**
  272. * Register this control so it can be created from markup.
  273. */
  274. // TODO(user): support setLabel from markup
  275. goog.ui.registry.setDecoratorByClassName(
  276. goog.ui.Checkbox.CSS_CLASS,
  277. function() {
  278. return new goog.ui.Checkbox();
  279. });