aboutsummaryrefslogtreecommitdiff
path: root/contexts/data/lib/closure-library/closure/goog/ui/editor/toolbarcontroller.js
blob: eed44a40ca7e8b5223b58512ab6c4d52389737f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview A class for managing the editor toolbar.
 *
 * @author attila@google.com (Attila Bodis)
 * @author jparent@google.com (Julie Parent)
 * @see ../../demos/editor/editor.html
 */

goog.provide('goog.ui.editor.ToolbarController');

goog.require('goog.editor.Field.EventType');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.ui.Component.EventType');



/**
 * A class for managing the editor toolbar.  Acts as a bridge between
 * a {@link goog.editor.Field} and a {@link goog.ui.Toolbar}.
 *
 * The {@code toolbar} argument must be an instance of {@link goog.ui.Toolbar}
 * or a subclass.  This class doesn't care how the toolbar was created.  As
 * long as one or more controls hosted  in the toolbar have IDs that match
 * built-in {@link goog.editor.Command}s, they will function as expected.  It is
 * the caller's responsibility to ensure that the toolbar is already rendered
 * or that it decorates an existing element.
 *
 *
 * @param {!goog.editor.Field} field Editable field to be controlled by the
 *     toolbar.
 * @param {!goog.ui.Toolbar} toolbar Toolbar to control the editable field.
 * @constructor
 * @extends {goog.events.EventTarget}
 */
goog.ui.editor.ToolbarController = function(field, toolbar) {
  goog.events.EventTarget.call(this);

  /**
   * Event handler to listen for field events and user actions.
   * @type {!goog.events.EventHandler}
   * @private
   */
  this.handler_ = new goog.events.EventHandler(this);

  /**
   * The field instance controlled by the toolbar.
   * @type {!goog.editor.Field}
   * @private
   */
  this.field_ = field;

  /**
   * The toolbar that controls the field.
   * @type {!goog.ui.Toolbar}
   * @private
   */
  this.toolbar_ = toolbar;

  /**
   * Editing commands whose state is to be queried when updating the toolbar.
   * @type {!Array.<string>}
   * @private
   */
  this.queryCommands_ = [];

  // Iterate over all buttons, and find those which correspond to
  // queryable commands. Add them to the list of commands to query on
  // each COMMAND_VALUE_CHANGE event.
  this.toolbar_.forEachChild(function(button) {
    if (button.queryable) {
      this.queryCommands_.push(this.getComponentId(button.getId()));
    }
  }, this);

  // Make sure the toolbar doesn't steal keyboard focus.
  this.toolbar_.setFocusable(false);

  // Hook up handlers that update the toolbar in response to field events,
  // and to execute editor commands in response to toolbar events.
  this.handler_.
      listen(this.field_, goog.editor.Field.EventType.COMMAND_VALUE_CHANGE,
          this.updateToolbar).
      listen(this.toolbar_, goog.ui.Component.EventType.ACTION,
          this.handleAction);
};
goog.inherits(goog.ui.editor.ToolbarController, goog.events.EventTarget);


/**
 * Returns the Closure component ID of the control that corresponds to the
 * given {@link goog.editor.Command} constant.
 * Subclasses may override this method if they want to use a custom mapping
 * scheme from commands to controls.
 * @param {string} command Editor command.
 * @return {string} Closure component ID of the corresponding toolbar
 *     control, if any.
 * @protected
 */
goog.ui.editor.ToolbarController.prototype.getComponentId = function(command) {
  // The default implementation assumes that the component ID is the same as
  // the command constant.
  return command;
};


/**
 * Returns the {@link goog.editor.Command} constant
 * that corresponds to the given Closure component ID.  Subclasses may override
 * this method if they want to use a custom mapping scheme from controls to
 * commands.
 * @param {string} id Closure component ID of a toolbar control.
 * @return {string} Editor command or dialog constant corresponding to the
 *     toolbar control, if any.
 * @protected
 */
goog.ui.editor.ToolbarController.prototype.getCommand = function(id) {
  // The default implementation assumes that the component ID is the same as
  // the command constant.
  return id;
};


/**
 * Returns the event handler object for the editor toolbar.  Useful for classes
 * that extend {@code goog.ui.editor.ToolbarController}.
 * @return {!goog.events.EventHandler} The event handler object.
 * @protected
 */
goog.ui.editor.ToolbarController.prototype.getHandler = function() {
  return this.handler_;
};


/**
 * Returns the field instance managed by the toolbar.  Useful for
 * classes that extend {@code goog.ui.editor.ToolbarController}.
 * @return {!goog.editor.Field} The field managed by the toolbar.
 * @protected
 */
goog.ui.editor.ToolbarController.prototype.getField = function() {
  return this.field_;
};


/**
 * Returns the toolbar UI component that manages the editor.  Useful for
 * classes that extend {@code goog.ui.editor.ToolbarController}.
 * @return {!goog.ui.Toolbar} The toolbar UI component.
 */
goog.ui.editor.ToolbarController.prototype.getToolbar = function() {
  return this.toolbar_;
};


/**
 * @return {boolean} Whether the toolbar is visible.
 */
goog.ui.editor.ToolbarController.prototype.isVisible = function() {
  return this.toolbar_.isVisible();
};


/**
 * Shows or hides the toolbar.
 * @param {boolean} visible Whether to show or hide the toolbar.
 */
goog.ui.editor.ToolbarController.prototype.setVisible = function(visible) {
  this.toolbar_.setVisible(visible);
};


/**
 * @return {boolean} Whether the toolbar is enabled.
 */
goog.ui.editor.ToolbarController.prototype.isEnabled = function() {
  return this.toolbar_.isEnabled();
};


/**
 * Enables or disables the toolbar.
 * @param {boolean} enabled Whether to enable or disable the toolbar.
 */
goog.ui.editor.ToolbarController.prototype.setEnabled = function(enabled) {
  this.toolbar_.setEnabled(enabled);
};


/**
 * Programmatically blurs the editor toolbar, un-highlighting the currently
 * highlighted item, and closing the currently open menu (if any).
 */
goog.ui.editor.ToolbarController.prototype.blur = function() {
  // We can't just call this.toolbar_.getElement().blur(), because the toolbar
  // element itself isn't focusable, so goog.ui.Container#handleBlur isn't
  // registered to handle blur events.
  this.toolbar_.handleBlur(null);
};


/** @override */
goog.ui.editor.ToolbarController.prototype.disposeInternal = function() {
  goog.ui.editor.ToolbarController.superClass_.disposeInternal.call(this);
  if (this.handler_) {
    this.handler_.dispose();
    delete this.handler_;
  }
  if (this.toolbar_) {
    this.toolbar_.dispose();
    delete this.toolbar_;
  }
  delete this.field_;
  delete this.queryCommands_;
};


/**
 * Updates the toolbar in response to editor events.  Specifically, updates
 * button states based on {@code COMMAND_VALUE_CHANGE} events, reflecting the
 * effective formatting of the selection.
 * @param {goog.events.Event} e Editor event to handle.
 * @protected
 */
goog.ui.editor.ToolbarController.prototype.updateToolbar = function(e) {
  if (!this.toolbar_.isEnabled() ||
      !this.dispatchEvent(goog.ui.Component.EventType.CHANGE)) {
    return;
  }

  var state;

  /** @preserveTry */
  try {
    /** @type {Array.<string>} */
    e.commands; // Added by dispatchEvent.

    // If the COMMAND_VALUE_CHANGE event specifies which commands changed
    // state, then we only need to update those ones, otherwise update all
    // commands.
    state = /** @type {Object} */ (
        this.field_.queryCommandValue(e.commands || this.queryCommands_));
  } catch (ex) {
    // TODO(attila): Find out when/why this happens.
    state = {};
  }

  this.updateToolbarFromState(state);
};


/**
 * Updates the toolbar to reflect a given state.
 * @param {Object} state Object mapping editor commands to values.
 */
goog.ui.editor.ToolbarController.prototype.updateToolbarFromState =
    function(state) {
  for (var command in state) {
    var button = this.toolbar_.getChild(this.getComponentId(command));
    if (button) {
      var value = state[command];
      if (button.updateFromValue) {
        button.updateFromValue(value);
      } else {
        button.setChecked(!!value);
      }
    }
  }
};


/**
 * Handles {@code ACTION} events dispatched by toolbar buttons in response to
 * user actions by executing the corresponding field command.
 * @param {goog.events.Event} e Action event to handle.
 * @protected
 */
goog.ui.editor.ToolbarController.prototype.handleAction = function(e) {
  var command = this.getCommand(e.target.getId());
  this.field_.execCommand(command, e.target.getValue());
};