Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/editor/plugin.js
2868 views
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
// All Rights Reserved.
15
16
/**
17
* @fileoverview Abstract API for TrogEdit plugins.
18
*
19
* @see ../demos/editor/editor.html
20
*/
21
22
goog.provide('goog.editor.Plugin');
23
24
// TODO(user): Remove the dependency on goog.editor.Command asap. Currently only
25
// needed for execCommand issues with links.
26
goog.require('goog.events.EventTarget');
27
goog.require('goog.functions');
28
goog.require('goog.log');
29
goog.require('goog.object');
30
goog.require('goog.reflect');
31
goog.require('goog.userAgent');
32
33
34
35
/**
36
* Abstract API for trogedit plugins.
37
* @constructor
38
* @extends {goog.events.EventTarget}
39
*/
40
goog.editor.Plugin = function() {
41
goog.events.EventTarget.call(this);
42
43
/**
44
* Whether this plugin is enabled for the registered field object.
45
* @type {boolean}
46
* @private
47
*/
48
this.enabled_ = this.activeOnUneditableFields();
49
50
/**
51
* The field object this plugin is attached to.
52
* @type {goog.editor.Field}
53
* @protected
54
* @deprecated Use goog.editor.Plugin.getFieldObject and
55
* goog.editor.Plugin.setFieldObject.
56
*/
57
this.fieldObject = null;
58
59
/**
60
* Indicates if this plugin should be automatically disposed when the
61
* registered field is disposed. This should be changed to false for
62
* plugins used as multi-field plugins.
63
* @type {boolean}
64
* @private
65
*/
66
this.autoDispose_ = true;
67
68
/**
69
* The logger for this plugin.
70
* @type {?goog.log.Logger}
71
* @protected
72
*/
73
this.logger = goog.log.getLogger('goog.editor.Plugin');
74
75
};
76
goog.inherits(goog.editor.Plugin, goog.events.EventTarget);
77
78
79
/**
80
* @return {goog.dom.DomHelper?} The dom helper object associated with the
81
* currently active field.
82
*/
83
goog.editor.Plugin.prototype.getFieldDomHelper = function() {
84
return this.getFieldObject() && this.getFieldObject().getEditableDomHelper();
85
};
86
87
88
/**
89
* Sets the field object for use with this plugin.
90
* @return {goog.editor.Field} The editable field object.
91
* @protected
92
* @suppress {deprecated} Until fieldObject can be made private.
93
*/
94
goog.editor.Plugin.prototype.getFieldObject = function() {
95
return this.fieldObject;
96
};
97
98
99
/**
100
* Sets the field object for use with this plugin.
101
* @param {goog.editor.Field} fieldObject The editable field object.
102
* @protected
103
* @suppress {deprecated} Until fieldObject can be made private.
104
*/
105
goog.editor.Plugin.prototype.setFieldObject = function(fieldObject) {
106
this.fieldObject = fieldObject;
107
};
108
109
110
/**
111
* Registers the field object for use with this plugin.
112
* @param {goog.editor.Field} fieldObject The editable field object.
113
*/
114
goog.editor.Plugin.prototype.registerFieldObject = function(fieldObject) {
115
this.setFieldObject(fieldObject);
116
};
117
118
119
/**
120
* Unregisters and disables this plugin for the current field object.
121
* @param {goog.editor.Field} fieldObj The field object. For single-field
122
* plugins, this parameter is ignored.
123
*/
124
goog.editor.Plugin.prototype.unregisterFieldObject = function(fieldObj) {
125
if (this.getFieldObject()) {
126
this.disable(this.getFieldObject());
127
this.setFieldObject(null);
128
}
129
};
130
131
132
/**
133
* Enables this plugin for the specified, registered field object. A field
134
* object should only be enabled when it is loaded.
135
* @param {goog.editor.Field} fieldObject The field object.
136
*/
137
goog.editor.Plugin.prototype.enable = function(fieldObject) {
138
if (this.getFieldObject() == fieldObject) {
139
this.enabled_ = true;
140
} else {
141
goog.log.error(
142
this.logger, 'Trying to enable an unregistered field with ' +
143
'this plugin.');
144
}
145
};
146
147
148
/**
149
* Disables this plugin for the specified, registered field object.
150
* @param {goog.editor.Field} fieldObject The field object.
151
*/
152
goog.editor.Plugin.prototype.disable = function(fieldObject) {
153
if (this.getFieldObject() == fieldObject) {
154
this.enabled_ = false;
155
} else {
156
goog.log.error(
157
this.logger, 'Trying to disable an unregistered field ' +
158
'with this plugin.');
159
}
160
};
161
162
163
/**
164
* Returns whether this plugin is enabled for the field object.
165
*
166
* @param {goog.editor.Field} fieldObject The field object.
167
* @return {boolean} Whether this plugin is enabled for the field object.
168
*/
169
goog.editor.Plugin.prototype.isEnabled = function(fieldObject) {
170
return this.getFieldObject() == fieldObject ? this.enabled_ : false;
171
};
172
173
174
/**
175
* Set if this plugin should automatically be disposed when the registered
176
* field is disposed.
177
* @param {boolean} autoDispose Whether to autoDispose.
178
*/
179
goog.editor.Plugin.prototype.setAutoDispose = function(autoDispose) {
180
this.autoDispose_ = autoDispose;
181
};
182
183
184
/**
185
* @return {boolean} Whether or not this plugin should automatically be disposed
186
* when it's registered field is disposed.
187
*/
188
goog.editor.Plugin.prototype.isAutoDispose = function() {
189
return this.autoDispose_;
190
};
191
192
193
/**
194
* @return {boolean} If true, field will not disable the command
195
* when the field becomes uneditable.
196
*/
197
goog.editor.Plugin.prototype.activeOnUneditableFields = goog.functions.FALSE;
198
199
200
/**
201
* @param {string} command The command to check.
202
* @return {boolean} If true, field will not dispatch change events
203
* for commands of this type. This is useful for "seamless" plugins like
204
* dialogs and lorem ipsum.
205
*/
206
goog.editor.Plugin.prototype.isSilentCommand = goog.functions.FALSE;
207
208
209
/** @override */
210
goog.editor.Plugin.prototype.disposeInternal = function() {
211
if (this.getFieldObject()) {
212
this.unregisterFieldObject(this.getFieldObject());
213
}
214
215
goog.editor.Plugin.superClass_.disposeInternal.call(this);
216
};
217
218
219
/**
220
* @return {string} The ID unique to this plugin class. Note that different
221
* instances off the plugin share the same classId.
222
*/
223
goog.editor.Plugin.prototype.getTrogClassId;
224
225
226
/**
227
* An enum of operations that plugins may support.
228
* @enum {number}
229
*/
230
goog.editor.Plugin.Op = {
231
KEYDOWN: 1,
232
KEYPRESS: 2,
233
KEYUP: 3,
234
SELECTION: 4,
235
SHORTCUT: 5,
236
EXEC_COMMAND: 6,
237
QUERY_COMMAND: 7,
238
PREPARE_CONTENTS_HTML: 8,
239
CLEAN_CONTENTS_HTML: 10,
240
CLEAN_CONTENTS_DOM: 11
241
};
242
243
244
/**
245
* A map from plugin operations to the names of the methods that
246
* invoke those operations.
247
*/
248
goog.editor.Plugin.OPCODE =
249
goog.object.transpose(goog.reflect.object(goog.editor.Plugin, {
250
handleKeyDown: goog.editor.Plugin.Op.KEYDOWN,
251
handleKeyPress: goog.editor.Plugin.Op.KEYPRESS,
252
handleKeyUp: goog.editor.Plugin.Op.KEYUP,
253
handleSelectionChange: goog.editor.Plugin.Op.SELECTION,
254
handleKeyboardShortcut: goog.editor.Plugin.Op.SHORTCUT,
255
execCommand: goog.editor.Plugin.Op.EXEC_COMMAND,
256
queryCommandValue: goog.editor.Plugin.Op.QUERY_COMMAND,
257
prepareContentsHtml: goog.editor.Plugin.Op.PREPARE_CONTENTS_HTML,
258
cleanContentsHtml: goog.editor.Plugin.Op.CLEAN_CONTENTS_HTML,
259
cleanContentsDom: goog.editor.Plugin.Op.CLEAN_CONTENTS_DOM
260
}));
261
262
263
/**
264
* A set of op codes that run even on disabled plugins.
265
*/
266
goog.editor.Plugin.IRREPRESSIBLE_OPS = goog.object.createSet(
267
goog.editor.Plugin.Op.PREPARE_CONTENTS_HTML,
268
goog.editor.Plugin.Op.CLEAN_CONTENTS_HTML,
269
goog.editor.Plugin.Op.CLEAN_CONTENTS_DOM);
270
271
272
/**
273
* Handles keydown. It is run before handleKeyboardShortcut and if it returns
274
* true handleKeyboardShortcut will not be called.
275
* @param {!goog.events.BrowserEvent} e The browser event.
276
* @return {boolean} Whether the event was handled and thus should *not* be
277
* propagated to other plugins or handleKeyboardShortcut.
278
*/
279
goog.editor.Plugin.prototype.handleKeyDown;
280
281
282
/**
283
* Handles keypress. It is run before handleKeyboardShortcut and if it returns
284
* true handleKeyboardShortcut will not be called.
285
* @param {!goog.events.BrowserEvent} e The browser event.
286
* @return {boolean} Whether the event was handled and thus should *not* be
287
* propagated to other plugins or handleKeyboardShortcut.
288
*/
289
goog.editor.Plugin.prototype.handleKeyPress;
290
291
292
/**
293
* Handles keyup.
294
* @param {!goog.events.BrowserEvent} e The browser event.
295
* @return {boolean} Whether the event was handled and thus should *not* be
296
* propagated to other plugins.
297
*/
298
goog.editor.Plugin.prototype.handleKeyUp;
299
300
301
/**
302
* Handles selection change.
303
* @param {!goog.events.BrowserEvent=} opt_e The browser event.
304
* @param {!Node=} opt_target The node the selection changed to.
305
* @return {boolean} Whether the event was handled and thus should *not* be
306
* propagated to other plugins.
307
*/
308
goog.editor.Plugin.prototype.handleSelectionChange;
309
310
311
/**
312
* Handles keyboard shortcuts. Preferred to using handleKey* as it will use
313
* the proper event based on browser and will be more performant. If
314
* handleKeyPress/handleKeyDown returns true, this will not be called. If the
315
* plugin handles the shortcut, it is responsible for dispatching appropriate
316
* events (change, selection change at the time of this comment). If the plugin
317
* calls execCommand on the editable field, then execCommand already takes care
318
* of dispatching events.
319
* NOTE: For performance reasons this is only called when any key is pressed
320
* in conjunction with ctrl/meta keys OR when a small subset of keys (defined
321
* in goog.editor.Field.POTENTIAL_SHORTCUT_KEYCODES_) are pressed without
322
* ctrl/meta keys. We specifically don't invoke it when altKey is pressed since
323
* alt key is used in many i8n UIs to enter certain characters.
324
* @param {!goog.events.BrowserEvent} e The browser event.
325
* @param {string} key The key pressed.
326
* @param {boolean} isModifierPressed Whether the ctrl/meta key was pressed or
327
* not.
328
* @return {boolean} Whether the event was handled and thus should *not* be
329
* propagated to other plugins. We also call preventDefault on the event if
330
* the return value is true.
331
*/
332
goog.editor.Plugin.prototype.handleKeyboardShortcut;
333
334
335
/**
336
* Handles execCommand. This default implementation handles dispatching
337
* BEFORECHANGE, CHANGE, and SELECTIONCHANGE events, and calls
338
* execCommandInternal to perform the actual command. Plugins that want to
339
* do their own event dispatching should override execCommand, otherwise
340
* it is preferred to only override execCommandInternal.
341
*
342
* This version of execCommand will only work for single field plugins.
343
* Multi-field plugins must override execCommand.
344
*
345
* @param {string} command The command to execute.
346
* @param {...*} var_args Any additional parameters needed to
347
* execute the command.
348
* @return {*} The result of the execCommand, if any.
349
*/
350
goog.editor.Plugin.prototype.execCommand = function(command, var_args) {
351
// TODO(user): Replace all uses of isSilentCommand with plugins that just
352
// override this base execCommand method.
353
var silent = this.isSilentCommand(command);
354
if (!silent) {
355
// Stop listening to mutation events in Firefox while text formatting
356
// is happening. This prevents us from trying to size the field in the
357
// middle of an execCommand, catching the field in a strange intermediary
358
// state where both replacement nodes and original nodes are appended to
359
// the dom. Note that change events get turned back on by
360
// fieldObj.dispatchChange.
361
if (goog.userAgent.GECKO) {
362
this.getFieldObject().stopChangeEvents(true, true);
363
}
364
365
this.getFieldObject().dispatchBeforeChange();
366
}
367
368
try {
369
var result = this.execCommandInternal.apply(this, arguments);
370
} finally {
371
// If the above execCommandInternal call throws an exception, we still need
372
// to turn change events back on (see http://b/issue?id=1471355).
373
// NOTE: If if you add to or change the methods called in this finally
374
// block, please add them as expected calls to the unit test function
375
// testExecCommandException().
376
if (!silent) {
377
// dispatchChange includes a call to startChangeEvents, which unwinds the
378
// call to stopChangeEvents made before the try block.
379
this.getFieldObject().dispatchChange();
380
this.getFieldObject().dispatchSelectionChangeEvent();
381
}
382
}
383
384
return result;
385
};
386
387
388
/**
389
* Handles execCommand. This default implementation does nothing, and is
390
* called by execCommand, which handles event dispatching. This method should
391
* be overriden by plugins that don't need to do their own event dispatching.
392
* If custom event dispatching is needed, execCommand shoul be overriden
393
* instead.
394
*
395
* @param {string} command The command to execute.
396
* @param {...*} var_args Any additional parameters needed to
397
* execute the command.
398
* @return {*} The result of the execCommand, if any.
399
* @protected
400
*/
401
goog.editor.Plugin.prototype.execCommandInternal;
402
403
404
/**
405
* Gets the state of this command if this plugin serves that command.
406
* @param {string} command The command to check.
407
* @return {*} The value of the command.
408
*/
409
goog.editor.Plugin.prototype.queryCommandValue;
410
411
412
/**
413
* Prepares the given HTML for editing. Strips out content that should not
414
* appear in an editor, and normalizes content as appropriate. The inverse
415
* of cleanContentsHtml.
416
*
417
* This op is invoked even on disabled plugins.
418
*
419
* @param {string} originalHtml The original HTML.
420
* @param {Object} styles A map of strings. If the plugin wants to add
421
* any styles to the field element, it should add them as key-value
422
* pairs to this object.
423
* @return {string} New HTML that's ok for editing.
424
*/
425
goog.editor.Plugin.prototype.prepareContentsHtml;
426
427
428
/**
429
* Cleans the contents of the node passed to it. The node contents are modified
430
* directly, and the modifications will subsequently be used, for operations
431
* such as saving the innerHTML of the editor etc. Since the plugins act on
432
* the DOM directly, this method can be very expensive.
433
*
434
* This op is invoked even on disabled plugins.
435
*
436
* @param {!Element} fieldCopy The copy of the editable field which
437
* needs to be cleaned up.
438
*/
439
goog.editor.Plugin.prototype.cleanContentsDom;
440
441
442
/**
443
* Cleans the html contents of Trogedit. Both cleanContentsDom and
444
* and cleanContentsHtml will be called on contents extracted from Trogedit.
445
* The inverse of prepareContentsHtml.
446
*
447
* This op is invoked even on disabled plugins.
448
*
449
* @param {string} originalHtml The trogedit HTML.
450
* @return {string} Cleaned-up HTML.
451
*/
452
goog.editor.Plugin.prototype.cleanContentsHtml;
453
454
455
/**
456
* Whether the string corresponds to a command this plugin handles.
457
* @param {string} command Command string to check.
458
* @return {boolean} Whether the plugin handles this type of command.
459
*/
460
goog.editor.Plugin.prototype.isSupportedCommand = function(command) {
461
return false;
462
};
463
464
465
/**
466
* Saves the field's scroll position. See b/7279077 for context.
467
* Currently only does anything in Edge, since all other browsers
468
* already seem to work correctly.
469
* @return {function()} A function to restore the current scroll position.
470
* @protected
471
*/
472
goog.editor.Plugin.prototype.saveScrollPosition = function() {
473
if (this.getFieldObject() && goog.userAgent.EDGE) {
474
var win = this.getFieldObject().getEditableDomHelper().getWindow();
475
return win.scrollTo.bind(win, win.scrollX, win.scrollY);
476
}
477
return function() {};
478
};
479
480