toolbarcontroller.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. // Copyright 2008 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 A class for managing the editor toolbar.
  16. *
  17. * @author attila@google.com (Attila Bodis)
  18. * @see ../../demos/editor/editor.html
  19. */
  20. goog.provide('goog.ui.editor.ToolbarController');
  21. goog.require('goog.editor.Field');
  22. goog.require('goog.events.EventHandler');
  23. goog.require('goog.events.EventTarget');
  24. goog.require('goog.ui.Component');
  25. /**
  26. * A class for managing the editor toolbar. Acts as a bridge between
  27. * a {@link goog.editor.Field} and a {@link goog.ui.Toolbar}.
  28. *
  29. * The {@code toolbar} argument must be an instance of {@link goog.ui.Toolbar}
  30. * or a subclass. This class doesn't care how the toolbar was created. As
  31. * long as one or more controls hosted in the toolbar have IDs that match
  32. * built-in {@link goog.editor.Command}s, they will function as expected. It is
  33. * the caller's responsibility to ensure that the toolbar is already rendered
  34. * or that it decorates an existing element.
  35. *
  36. *
  37. * @param {!goog.editor.Field} field Editable field to be controlled by the
  38. * toolbar.
  39. * @param {!goog.ui.Toolbar} toolbar Toolbar to control the editable field.
  40. * @constructor
  41. * @extends {goog.events.EventTarget}
  42. */
  43. goog.ui.editor.ToolbarController = function(field, toolbar) {
  44. goog.events.EventTarget.call(this);
  45. /**
  46. * Event handler to listen for field events and user actions.
  47. * @type {!goog.events.EventHandler<!goog.ui.editor.ToolbarController>}
  48. * @private
  49. */
  50. this.handler_ = new goog.events.EventHandler(this);
  51. /**
  52. * The field instance controlled by the toolbar.
  53. * @type {!goog.editor.Field}
  54. * @private
  55. */
  56. this.field_ = field;
  57. /**
  58. * The toolbar that controls the field.
  59. * @type {!goog.ui.Toolbar}
  60. * @private
  61. */
  62. this.toolbar_ = toolbar;
  63. /**
  64. * Editing commands whose state is to be queried when updating the toolbar.
  65. * @type {!Array<string>}
  66. * @private
  67. */
  68. this.queryCommands_ = [];
  69. // Iterate over all buttons, and find those which correspond to
  70. // queryable commands. Add them to the list of commands to query on
  71. // each COMMAND_VALUE_CHANGE event.
  72. this.toolbar_.forEachChild(function(button) {
  73. if (button.queryable) {
  74. this.queryCommands_.push(this.getComponentId(button.getId()));
  75. }
  76. }, this);
  77. // Make sure the toolbar doesn't steal keyboard focus.
  78. this.toolbar_.setFocusable(false);
  79. // Hook up handlers that update the toolbar in response to field events,
  80. // and to execute editor commands in response to toolbar events.
  81. this.handler_
  82. .listen(
  83. this.field_, goog.editor.Field.EventType.COMMAND_VALUE_CHANGE,
  84. this.updateToolbar)
  85. .listen(
  86. this.toolbar_, goog.ui.Component.EventType.ACTION, this.handleAction);
  87. };
  88. goog.inherits(goog.ui.editor.ToolbarController, goog.events.EventTarget);
  89. /**
  90. * Returns the Closure component ID of the control that corresponds to the
  91. * given {@link goog.editor.Command} constant.
  92. * Subclasses may override this method if they want to use a custom mapping
  93. * scheme from commands to controls.
  94. * @param {string} command Editor command.
  95. * @return {string} Closure component ID of the corresponding toolbar
  96. * control, if any.
  97. * @protected
  98. */
  99. goog.ui.editor.ToolbarController.prototype.getComponentId = function(command) {
  100. // The default implementation assumes that the component ID is the same as
  101. // the command constant.
  102. return command;
  103. };
  104. /**
  105. * Returns the {@link goog.editor.Command} constant
  106. * that corresponds to the given Closure component ID. Subclasses may override
  107. * this method if they want to use a custom mapping scheme from controls to
  108. * commands.
  109. * @param {string} id Closure component ID of a toolbar control.
  110. * @return {string} Editor command or dialog constant corresponding to the
  111. * toolbar control, if any.
  112. * @protected
  113. */
  114. goog.ui.editor.ToolbarController.prototype.getCommand = function(id) {
  115. // The default implementation assumes that the component ID is the same as
  116. // the command constant.
  117. return id;
  118. };
  119. /**
  120. * Returns the event handler object for the editor toolbar. Useful for classes
  121. * that extend {@code goog.ui.editor.ToolbarController}.
  122. * @return {!goog.events.EventHandler<T>} The event handler object.
  123. * @protected
  124. * @this {T}
  125. * @template T
  126. */
  127. goog.ui.editor.ToolbarController.prototype.getHandler = function() {
  128. return this.handler_;
  129. };
  130. /**
  131. * Returns the field instance managed by the toolbar. Useful for
  132. * classes that extend {@code goog.ui.editor.ToolbarController}.
  133. * @return {!goog.editor.Field} The field managed by the toolbar.
  134. * @protected
  135. */
  136. goog.ui.editor.ToolbarController.prototype.getField = function() {
  137. return this.field_;
  138. };
  139. /**
  140. * Returns the toolbar UI component that manages the editor. Useful for
  141. * classes that extend {@code goog.ui.editor.ToolbarController}.
  142. * @return {!goog.ui.Toolbar} The toolbar UI component.
  143. */
  144. goog.ui.editor.ToolbarController.prototype.getToolbar = function() {
  145. return this.toolbar_;
  146. };
  147. /**
  148. * @return {boolean} Whether the toolbar is visible.
  149. */
  150. goog.ui.editor.ToolbarController.prototype.isVisible = function() {
  151. return this.toolbar_.isVisible();
  152. };
  153. /**
  154. * Shows or hides the toolbar.
  155. * @param {boolean} visible Whether to show or hide the toolbar.
  156. */
  157. goog.ui.editor.ToolbarController.prototype.setVisible = function(visible) {
  158. this.toolbar_.setVisible(visible);
  159. };
  160. /**
  161. * @return {boolean} Whether the toolbar is enabled.
  162. */
  163. goog.ui.editor.ToolbarController.prototype.isEnabled = function() {
  164. return this.toolbar_.isEnabled();
  165. };
  166. /**
  167. * Enables or disables the toolbar.
  168. * @param {boolean} enabled Whether to enable or disable the toolbar.
  169. */
  170. goog.ui.editor.ToolbarController.prototype.setEnabled = function(enabled) {
  171. this.toolbar_.setEnabled(enabled);
  172. };
  173. /**
  174. * Programmatically blurs the editor toolbar, un-highlighting the currently
  175. * highlighted item, and closing the currently open menu (if any).
  176. */
  177. goog.ui.editor.ToolbarController.prototype.blur = function() {
  178. // We can't just call this.toolbar_.getElement().blur(), because the toolbar
  179. // element itself isn't focusable, so goog.ui.Container#handleBlur isn't
  180. // registered to handle blur events.
  181. this.toolbar_.handleBlur(null);
  182. };
  183. /** @override */
  184. goog.ui.editor.ToolbarController.prototype.disposeInternal = function() {
  185. goog.ui.editor.ToolbarController.superClass_.disposeInternal.call(this);
  186. if (this.handler_) {
  187. this.handler_.dispose();
  188. delete this.handler_;
  189. }
  190. if (this.toolbar_) {
  191. this.toolbar_.dispose();
  192. delete this.toolbar_;
  193. }
  194. delete this.field_;
  195. delete this.queryCommands_;
  196. };
  197. /**
  198. * Updates the toolbar in response to editor events. Specifically, updates
  199. * button states based on {@code COMMAND_VALUE_CHANGE} events, reflecting the
  200. * effective formatting of the selection.
  201. * @param {goog.events.Event} e Editor event to handle.
  202. * @protected
  203. */
  204. goog.ui.editor.ToolbarController.prototype.updateToolbar = function(e) {
  205. if (!this.toolbar_.isEnabled() || !this.field_.isSelectionEditable() ||
  206. !this.dispatchEvent(goog.ui.Component.EventType.CHANGE)) {
  207. return;
  208. }
  209. var state;
  210. try {
  211. /** @type {Array<string>} */
  212. e.commands; // Added by dispatchEvent.
  213. // If the COMMAND_VALUE_CHANGE event specifies which commands changed
  214. // state, then we only need to update those ones, otherwise update all
  215. // commands.
  216. state = /** @type {Object} */ (
  217. this.field_.queryCommandValue(e.commands || this.queryCommands_));
  218. } catch (ex) {
  219. // TODO(attila): Find out when/why this happens.
  220. state = {};
  221. }
  222. this.updateToolbarFromState(state);
  223. };
  224. /**
  225. * Updates the toolbar to reflect a given state.
  226. * @param {Object} state Object mapping editor commands to values.
  227. */
  228. goog.ui.editor.ToolbarController.prototype.updateToolbarFromState = function(
  229. state) {
  230. for (var command in state) {
  231. var button = this.toolbar_.getChild(this.getComponentId(command));
  232. if (button) {
  233. var value = state[command];
  234. if (button.updateFromValue) {
  235. button.updateFromValue(value);
  236. } else {
  237. button.setChecked(!!value);
  238. }
  239. }
  240. }
  241. };
  242. /**
  243. * Handles {@code ACTION} events dispatched by toolbar buttons in response to
  244. * user actions by executing the corresponding field command.
  245. * @param {goog.events.Event} e Action event to handle.
  246. * @protected
  247. */
  248. goog.ui.editor.ToolbarController.prototype.handleAction = function(e) {
  249. var command = this.getCommand(e.target.getId());
  250. this.field_.execCommand(command, e.target.getValue());
  251. };