Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/editor/plugins/abstractdialogplugin.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
15
/**
16
* @fileoverview An abstract superclass for TrogEdit dialog plugins. Each
17
* Trogedit dialog has its own plugin.
18
*
19
* @author [email protected] (Nick Santos)
20
*/
21
22
goog.provide('goog.editor.plugins.AbstractDialogPlugin');
23
goog.provide('goog.editor.plugins.AbstractDialogPlugin.EventType');
24
25
goog.require('goog.dom');
26
goog.require('goog.dom.Range');
27
goog.require('goog.editor.Field');
28
goog.require('goog.editor.Plugin');
29
goog.require('goog.editor.range');
30
goog.require('goog.events');
31
goog.require('goog.ui.editor.AbstractDialog');
32
33
34
// *** Public interface ***************************************************** //
35
36
37
38
/**
39
* An abstract superclass for a Trogedit plugin that creates exactly one
40
* dialog. By default dialogs are not reused -- each time execCommand is called,
41
* a new instance of the dialog object is created (and the old one disposed of).
42
* To enable reusing of the dialog object, subclasses should call
43
* setReuseDialog() after calling the superclass constructor.
44
* @param {string} command The command that this plugin handles.
45
* @constructor
46
* @extends {goog.editor.Plugin}
47
*/
48
goog.editor.plugins.AbstractDialogPlugin = function(command) {
49
goog.editor.plugins.AbstractDialogPlugin.base(this, 'constructor');
50
51
/**
52
* The command that this plugin handles.
53
* @private {string}
54
*/
55
this.command_ = command;
56
57
/** @private {function()} */
58
this.restoreScrollPosition_ = function() {};
59
60
/**
61
* The current dialog that was created and opened by this plugin.
62
* @private {?goog.ui.editor.AbstractDialog}
63
*/
64
this.dialog_ = null;
65
66
/**
67
* Whether this plugin should reuse the same instance of the dialog each time
68
* execCommand is called or create a new one.
69
* @private {boolean}
70
*/
71
this.reuseDialog_ = false;
72
73
/**
74
* Mutex to prevent recursive calls to disposeDialog_.
75
* @private {boolean}
76
*/
77
this.isDisposingDialog_ = false;
78
79
/**
80
* SavedRange representing the selection before the dialog was opened.
81
* @private {?goog.dom.SavedRange}
82
*/
83
this.savedRange_ = null;
84
};
85
goog.inherits(goog.editor.plugins.AbstractDialogPlugin, goog.editor.Plugin);
86
87
88
/** @override */
89
goog.editor.plugins.AbstractDialogPlugin.prototype.isSupportedCommand =
90
function(command) {
91
return command == this.command_;
92
};
93
94
95
/**
96
* Handles execCommand. Dialog plugins don't make any changes when they open a
97
* dialog, just when the dialog closes (because only modal dialogs are
98
* supported). Hence this method does not dispatch the change events that the
99
* superclass method does.
100
* @param {string} command The command to execute.
101
* @param {...*} var_args Any additional parameters needed to
102
* execute the command.
103
* @return {*} The result of the execCommand, if any.
104
* @override
105
*/
106
goog.editor.plugins.AbstractDialogPlugin.prototype.execCommand = function(
107
command, var_args) {
108
return this.execCommandInternal.apply(this, arguments);
109
};
110
111
112
// *** Events *************************************************************** //
113
114
115
/**
116
* Event type constants for events the dialog plugins fire.
117
* @enum {string}
118
*/
119
goog.editor.plugins.AbstractDialogPlugin.EventType = {
120
// This event is fired when a dialog has been opened.
121
OPENED: 'dialogOpened',
122
// This event is fired when a dialog has been closed.
123
CLOSED: 'dialogClosed'
124
};
125
126
127
// *** Protected interface ************************************************** //
128
129
130
/**
131
* Creates a new instance of this plugin's dialog. Must be overridden by
132
* subclasses.
133
* Implementations should expect that the editor is inactive and cannot be
134
* focused, nor will its caret position (or selection) be determinable until
135
* after the dialogs goog.ui.PopupBase.EventType.HIDE event has been handled.
136
* @param {!goog.dom.DomHelper} dialogDomHelper The dom helper to be used to
137
* create the dialog.
138
* @param {*=} opt_arg The dialog specific argument. Concrete subclasses should
139
* declare a specific type.
140
* @return {goog.ui.editor.AbstractDialog} The newly created dialog.
141
* @protected
142
*/
143
goog.editor.plugins.AbstractDialogPlugin.prototype.createDialog =
144
goog.abstractMethod;
145
146
147
/**
148
* Returns the current dialog that was created and opened by this plugin.
149
* @return {goog.ui.editor.AbstractDialog} The current dialog that was created
150
* and opened by this plugin.
151
* @protected
152
*/
153
goog.editor.plugins.AbstractDialogPlugin.prototype.getDialog = function() {
154
return this.dialog_;
155
};
156
157
158
/**
159
* Sets whether this plugin should reuse the same instance of the dialog each
160
* time execCommand is called or create a new one. This is intended for use by
161
* subclasses only, hence protected.
162
* @param {boolean} reuse Whether to reuse the dialog.
163
* @protected
164
*/
165
goog.editor.plugins.AbstractDialogPlugin.prototype.setReuseDialog = function(
166
reuse) {
167
this.reuseDialog_ = reuse;
168
};
169
170
171
/**
172
* Handles execCommand by opening the dialog. Dispatches
173
* {@link goog.editor.plugins.AbstractDialogPlugin.EventType.OPENED} after the
174
* dialog is shown.
175
* @param {string} command The command to execute.
176
* @param {*=} opt_arg The dialog specific argument. Should be the same as
177
* {@link createDialog}.
178
* @return {*} Always returns true, indicating the dialog was shown.
179
* @protected
180
* @override
181
*/
182
goog.editor.plugins.AbstractDialogPlugin.prototype.execCommandInternal =
183
function(command, opt_arg) {
184
// If this plugin should not reuse dialog instances, first dispose of the
185
// previous dialog.
186
if (!this.reuseDialog_) {
187
this.disposeDialog_();
188
}
189
// If there is no dialog yet (or we aren't reusing the previous one), create
190
// one.
191
if (!this.dialog_) {
192
this.dialog_ = this.createDialog(
193
// TODO(user): Add Field.getAppDomHelper. (Note dom helper will
194
// need to be updated if setAppWindow is called by clients.)
195
goog.dom.getDomHelper(this.getFieldObject().getAppWindow()), opt_arg);
196
}
197
198
// Since we're opening a dialog, we need to clear the selection because the
199
// focus will be going to the dialog, and if we leave an selection in the
200
// editor while another selection is active in the dialog as the user is
201
// typing, some browsers will screw up the original selection. But first we
202
// save it so we can restore it when the dialog closes.
203
// getRange may return null if there is no selection in the field.
204
var tempRange = this.getFieldObject().getRange();
205
// saveUsingDom() did not work as well as saveUsingNormalizedCarets(),
206
// not sure why.
207
208
this.restoreScrollPosition_ = this.saveScrollPosition();
209
this.savedRange_ =
210
tempRange && goog.editor.range.saveUsingNormalizedCarets(tempRange);
211
goog.dom.Range.clearSelection(
212
this.getFieldObject().getEditableDomHelper().getWindow());
213
214
// Listen for the dialog closing so we can clean up.
215
goog.events.listenOnce(
216
this.dialog_, goog.ui.editor.AbstractDialog.EventType.AFTER_HIDE,
217
this.handleAfterHide, false, this);
218
219
this.getFieldObject().setModalMode(true);
220
this.dialog_.show();
221
this.dispatchEvent(goog.editor.plugins.AbstractDialogPlugin.EventType.OPENED);
222
223
// Since the selection has left the document, dispatch a selection
224
// change event.
225
this.getFieldObject().dispatchSelectionChangeEvent();
226
227
return true;
228
};
229
230
231
/**
232
* Cleans up after the dialog has closed, including restoring the selection to
233
* what it was before the dialog was opened. If a subclass modifies the editable
234
* field's content such that the original selection is no longer valid (usually
235
* the case when the user clicks OK, and sometimes also on Cancel), it is that
236
* subclass' responsibility to place the selection in the desired place during
237
* the OK or Cancel (or other) handler. In that case, this method will leave the
238
* selection in place.
239
* @param {goog.events.Event} e The AFTER_HIDE event object.
240
* @protected
241
*/
242
goog.editor.plugins.AbstractDialogPlugin.prototype.handleAfterHide = function(
243
e) {
244
this.getFieldObject().setModalMode(false);
245
this.restoreOriginalSelection();
246
this.restoreScrollPosition_();
247
248
if (!this.reuseDialog_) {
249
this.disposeDialog_();
250
}
251
252
this.dispatchEvent(goog.editor.plugins.AbstractDialogPlugin.EventType.CLOSED);
253
254
// Since the selection has returned to the document, dispatch a selection
255
// change event.
256
this.getFieldObject().dispatchSelectionChangeEvent();
257
258
// When the dialog closes due to pressing enter or escape, that happens on the
259
// keydown event. But the browser will still fire a keyup event after that,
260
// which is caught by the editable field and causes it to try to fire a
261
// selection change event. To avoid that, we "debounce" the selection change
262
// event, meaning the editable field will not fire that event if the keyup
263
// that caused it immediately after this dialog was hidden ("immediately"
264
// means a small number of milliseconds defined by the editable field).
265
this.getFieldObject().debounceEvent(
266
goog.editor.Field.EventType.SELECTIONCHANGE);
267
};
268
269
270
/**
271
* Restores the selection in the editable field to what it was before the dialog
272
* was opened. This is not guaranteed to work if the contents of the field
273
* have changed.
274
* @protected
275
*/
276
goog.editor.plugins.AbstractDialogPlugin.prototype.restoreOriginalSelection =
277
function() {
278
this.getFieldObject().restoreSavedRange(this.savedRange_);
279
this.savedRange_ = null;
280
};
281
282
283
/**
284
* Cleans up the structure used to save the original selection before the dialog
285
* was opened. Should be used by subclasses that don't restore the original
286
* selection via restoreOriginalSelection.
287
* @protected
288
*/
289
goog.editor.plugins.AbstractDialogPlugin.prototype.disposeOriginalSelection =
290
function() {
291
if (this.savedRange_) {
292
this.savedRange_.dispose();
293
this.savedRange_ = null;
294
}
295
};
296
297
298
/** @override */
299
goog.editor.plugins.AbstractDialogPlugin.prototype.disposeInternal =
300
function() {
301
this.disposeDialog_();
302
goog.editor.plugins.AbstractDialogPlugin.base(this, 'disposeInternal');
303
};
304
305
306
// *** Private implementation *********************************************** //
307
308
309
/**
310
* Disposes of the dialog if needed. It is this abstract class' responsibility
311
* to dispose of the dialog. The "if needed" refers to the fact this method
312
* might be called twice (nested calls, not sequential) in the dispose flow, so
313
* if the dialog was already disposed once it should not be disposed again.
314
* @private
315
*/
316
goog.editor.plugins.AbstractDialogPlugin.prototype.disposeDialog_ = function() {
317
// Wrap disposing the dialog in a mutex. Otherwise disposing it would cause it
318
// to get hidden (if it is still open) and fire AFTER_HIDE, which in
319
// turn would cause the dialog to be disposed again (closure only flags an
320
// object as disposed after the dispose call chain completes, so it doesn't
321
// prevent recursive dispose calls).
322
if (this.dialog_ && !this.isDisposingDialog_) {
323
this.isDisposingDialog_ = true;
324
this.dialog_.dispose();
325
this.dialog_ = null;
326
this.isDisposingDialog_ = false;
327
}
328
};
329
330