Path: blob/trunk/third_party/closure/goog/editor/plugins/abstractdialogplugin.js
2868 views
// Copyright 2008 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview An abstract superclass for TrogEdit dialog plugins. Each16* Trogedit dialog has its own plugin.17*18* @author [email protected] (Nick Santos)19*/2021goog.provide('goog.editor.plugins.AbstractDialogPlugin');22goog.provide('goog.editor.plugins.AbstractDialogPlugin.EventType');2324goog.require('goog.dom');25goog.require('goog.dom.Range');26goog.require('goog.editor.Field');27goog.require('goog.editor.Plugin');28goog.require('goog.editor.range');29goog.require('goog.events');30goog.require('goog.ui.editor.AbstractDialog');313233// *** Public interface ***************************************************** //34353637/**38* An abstract superclass for a Trogedit plugin that creates exactly one39* dialog. By default dialogs are not reused -- each time execCommand is called,40* a new instance of the dialog object is created (and the old one disposed of).41* To enable reusing of the dialog object, subclasses should call42* setReuseDialog() after calling the superclass constructor.43* @param {string} command The command that this plugin handles.44* @constructor45* @extends {goog.editor.Plugin}46*/47goog.editor.plugins.AbstractDialogPlugin = function(command) {48goog.editor.plugins.AbstractDialogPlugin.base(this, 'constructor');4950/**51* The command that this plugin handles.52* @private {string}53*/54this.command_ = command;5556/** @private {function()} */57this.restoreScrollPosition_ = function() {};5859/**60* The current dialog that was created and opened by this plugin.61* @private {?goog.ui.editor.AbstractDialog}62*/63this.dialog_ = null;6465/**66* Whether this plugin should reuse the same instance of the dialog each time67* execCommand is called or create a new one.68* @private {boolean}69*/70this.reuseDialog_ = false;7172/**73* Mutex to prevent recursive calls to disposeDialog_.74* @private {boolean}75*/76this.isDisposingDialog_ = false;7778/**79* SavedRange representing the selection before the dialog was opened.80* @private {?goog.dom.SavedRange}81*/82this.savedRange_ = null;83};84goog.inherits(goog.editor.plugins.AbstractDialogPlugin, goog.editor.Plugin);858687/** @override */88goog.editor.plugins.AbstractDialogPlugin.prototype.isSupportedCommand =89function(command) {90return command == this.command_;91};929394/**95* Handles execCommand. Dialog plugins don't make any changes when they open a96* dialog, just when the dialog closes (because only modal dialogs are97* supported). Hence this method does not dispatch the change events that the98* superclass method does.99* @param {string} command The command to execute.100* @param {...*} var_args Any additional parameters needed to101* execute the command.102* @return {*} The result of the execCommand, if any.103* @override104*/105goog.editor.plugins.AbstractDialogPlugin.prototype.execCommand = function(106command, var_args) {107return this.execCommandInternal.apply(this, arguments);108};109110111// *** Events *************************************************************** //112113114/**115* Event type constants for events the dialog plugins fire.116* @enum {string}117*/118goog.editor.plugins.AbstractDialogPlugin.EventType = {119// This event is fired when a dialog has been opened.120OPENED: 'dialogOpened',121// This event is fired when a dialog has been closed.122CLOSED: 'dialogClosed'123};124125126// *** Protected interface ************************************************** //127128129/**130* Creates a new instance of this plugin's dialog. Must be overridden by131* subclasses.132* Implementations should expect that the editor is inactive and cannot be133* focused, nor will its caret position (or selection) be determinable until134* after the dialogs goog.ui.PopupBase.EventType.HIDE event has been handled.135* @param {!goog.dom.DomHelper} dialogDomHelper The dom helper to be used to136* create the dialog.137* @param {*=} opt_arg The dialog specific argument. Concrete subclasses should138* declare a specific type.139* @return {goog.ui.editor.AbstractDialog} The newly created dialog.140* @protected141*/142goog.editor.plugins.AbstractDialogPlugin.prototype.createDialog =143goog.abstractMethod;144145146/**147* Returns the current dialog that was created and opened by this plugin.148* @return {goog.ui.editor.AbstractDialog} The current dialog that was created149* and opened by this plugin.150* @protected151*/152goog.editor.plugins.AbstractDialogPlugin.prototype.getDialog = function() {153return this.dialog_;154};155156157/**158* Sets whether this plugin should reuse the same instance of the dialog each159* time execCommand is called or create a new one. This is intended for use by160* subclasses only, hence protected.161* @param {boolean} reuse Whether to reuse the dialog.162* @protected163*/164goog.editor.plugins.AbstractDialogPlugin.prototype.setReuseDialog = function(165reuse) {166this.reuseDialog_ = reuse;167};168169170/**171* Handles execCommand by opening the dialog. Dispatches172* {@link goog.editor.plugins.AbstractDialogPlugin.EventType.OPENED} after the173* dialog is shown.174* @param {string} command The command to execute.175* @param {*=} opt_arg The dialog specific argument. Should be the same as176* {@link createDialog}.177* @return {*} Always returns true, indicating the dialog was shown.178* @protected179* @override180*/181goog.editor.plugins.AbstractDialogPlugin.prototype.execCommandInternal =182function(command, opt_arg) {183// If this plugin should not reuse dialog instances, first dispose of the184// previous dialog.185if (!this.reuseDialog_) {186this.disposeDialog_();187}188// If there is no dialog yet (or we aren't reusing the previous one), create189// one.190if (!this.dialog_) {191this.dialog_ = this.createDialog(192// TODO(user): Add Field.getAppDomHelper. (Note dom helper will193// need to be updated if setAppWindow is called by clients.)194goog.dom.getDomHelper(this.getFieldObject().getAppWindow()), opt_arg);195}196197// Since we're opening a dialog, we need to clear the selection because the198// focus will be going to the dialog, and if we leave an selection in the199// editor while another selection is active in the dialog as the user is200// typing, some browsers will screw up the original selection. But first we201// save it so we can restore it when the dialog closes.202// getRange may return null if there is no selection in the field.203var tempRange = this.getFieldObject().getRange();204// saveUsingDom() did not work as well as saveUsingNormalizedCarets(),205// not sure why.206207this.restoreScrollPosition_ = this.saveScrollPosition();208this.savedRange_ =209tempRange && goog.editor.range.saveUsingNormalizedCarets(tempRange);210goog.dom.Range.clearSelection(211this.getFieldObject().getEditableDomHelper().getWindow());212213// Listen for the dialog closing so we can clean up.214goog.events.listenOnce(215this.dialog_, goog.ui.editor.AbstractDialog.EventType.AFTER_HIDE,216this.handleAfterHide, false, this);217218this.getFieldObject().setModalMode(true);219this.dialog_.show();220this.dispatchEvent(goog.editor.plugins.AbstractDialogPlugin.EventType.OPENED);221222// Since the selection has left the document, dispatch a selection223// change event.224this.getFieldObject().dispatchSelectionChangeEvent();225226return true;227};228229230/**231* Cleans up after the dialog has closed, including restoring the selection to232* what it was before the dialog was opened. If a subclass modifies the editable233* field's content such that the original selection is no longer valid (usually234* the case when the user clicks OK, and sometimes also on Cancel), it is that235* subclass' responsibility to place the selection in the desired place during236* the OK or Cancel (or other) handler. In that case, this method will leave the237* selection in place.238* @param {goog.events.Event} e The AFTER_HIDE event object.239* @protected240*/241goog.editor.plugins.AbstractDialogPlugin.prototype.handleAfterHide = function(242e) {243this.getFieldObject().setModalMode(false);244this.restoreOriginalSelection();245this.restoreScrollPosition_();246247if (!this.reuseDialog_) {248this.disposeDialog_();249}250251this.dispatchEvent(goog.editor.plugins.AbstractDialogPlugin.EventType.CLOSED);252253// Since the selection has returned to the document, dispatch a selection254// change event.255this.getFieldObject().dispatchSelectionChangeEvent();256257// When the dialog closes due to pressing enter or escape, that happens on the258// keydown event. But the browser will still fire a keyup event after that,259// which is caught by the editable field and causes it to try to fire a260// selection change event. To avoid that, we "debounce" the selection change261// event, meaning the editable field will not fire that event if the keyup262// that caused it immediately after this dialog was hidden ("immediately"263// means a small number of milliseconds defined by the editable field).264this.getFieldObject().debounceEvent(265goog.editor.Field.EventType.SELECTIONCHANGE);266};267268269/**270* Restores the selection in the editable field to what it was before the dialog271* was opened. This is not guaranteed to work if the contents of the field272* have changed.273* @protected274*/275goog.editor.plugins.AbstractDialogPlugin.prototype.restoreOriginalSelection =276function() {277this.getFieldObject().restoreSavedRange(this.savedRange_);278this.savedRange_ = null;279};280281282/**283* Cleans up the structure used to save the original selection before the dialog284* was opened. Should be used by subclasses that don't restore the original285* selection via restoreOriginalSelection.286* @protected287*/288goog.editor.plugins.AbstractDialogPlugin.prototype.disposeOriginalSelection =289function() {290if (this.savedRange_) {291this.savedRange_.dispose();292this.savedRange_ = null;293}294};295296297/** @override */298goog.editor.plugins.AbstractDialogPlugin.prototype.disposeInternal =299function() {300this.disposeDialog_();301goog.editor.plugins.AbstractDialogPlugin.base(this, 'disposeInternal');302};303304305// *** Private implementation *********************************************** //306307308/**309* Disposes of the dialog if needed. It is this abstract class' responsibility310* to dispose of the dialog. The "if needed" refers to the fact this method311* might be called twice (nested calls, not sequential) in the dispose flow, so312* if the dialog was already disposed once it should not be disposed again.313* @private314*/315goog.editor.plugins.AbstractDialogPlugin.prototype.disposeDialog_ = function() {316// Wrap disposing the dialog in a mutex. Otherwise disposing it would cause it317// to get hidden (if it is still open) and fire AFTER_HIDE, which in318// turn would cause the dialog to be disposed again (closure only flags an319// object as disposed after the dispose call chain completes, so it doesn't320// prevent recursive dispose calls).321if (this.dialog_ && !this.isDisposingDialog_) {322this.isDisposingDialog_ = true;323this.dialog_.dispose();324this.dialog_ = null;325this.isDisposingDialog_ = false;326}327};328329330