Adding mapbox-gl branch
This commit is contained in:
@@ -0,0 +1,444 @@
|
||||
// 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 Wrapper around {@link goog.ui.Dialog}, to provide
|
||||
* dialogs that are smarter about interacting with a rich text editor.
|
||||
*
|
||||
* @author nicksantos@google.com (Nick Santos)
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.editor.AbstractDialog');
|
||||
goog.provide('goog.ui.editor.AbstractDialog.Builder');
|
||||
goog.provide('goog.ui.editor.AbstractDialog.EventType');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.ui.Dialog');
|
||||
goog.require('goog.ui.PopupBase');
|
||||
|
||||
|
||||
// *** Public interface ***************************************************** //
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates an object that represents a dialog box.
|
||||
* @param {goog.dom.DomHelper} domHelper DomHelper to be used to create the
|
||||
* dialog's dom structure.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog = function(domHelper) {
|
||||
goog.events.EventTarget.call(this);
|
||||
this.dom = domHelper;
|
||||
};
|
||||
goog.inherits(goog.ui.editor.AbstractDialog, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Causes the dialog box to appear, centered on the screen. Lazily creates the
|
||||
* dialog if needed.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.show = function() {
|
||||
// Lazily create the wrapped dialog to be shown.
|
||||
if (!this.dialogInternal_) {
|
||||
this.dialogInternal_ = this.createDialogControl();
|
||||
this.dialogInternal_.listen(goog.ui.PopupBase.EventType.HIDE,
|
||||
this.handleAfterHide_, false, this);
|
||||
}
|
||||
|
||||
this.dialogInternal_.setVisible(true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hides the dialog, causing AFTER_HIDE to fire.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.hide = function() {
|
||||
if (this.dialogInternal_) {
|
||||
// This eventually fires the wrapped dialog's AFTER_HIDE event, calling our
|
||||
// handleAfterHide_().
|
||||
this.dialogInternal_.setVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the dialog is open.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.isOpen = function() {
|
||||
return !!this.dialogInternal_ && this.dialogInternal_.isVisible();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Runs the handler registered on the OK button event and closes the dialog if
|
||||
* that handler succeeds.
|
||||
* This is useful in cases such as double-clicking an item in the dialog is
|
||||
* equivalent to selecting it and clicking the default button.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.processOkAndClose = function() {
|
||||
// Fake an OK event from the wrapped dialog control.
|
||||
var evt = new goog.ui.Dialog.Event(goog.ui.Dialog.DefaultButtonKeys.OK, null);
|
||||
if (this.handleOk(evt)) {
|
||||
// handleOk calls dispatchEvent, so if any listener calls preventDefault it
|
||||
// will return false and we won't hide the dialog.
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// *** Dialog events ******************************************************** //
|
||||
|
||||
|
||||
/**
|
||||
* Event type constants for events the dialog fires.
|
||||
* @enum {string}
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.EventType = {
|
||||
// This event is fired after the dialog is hidden, no matter if it was closed
|
||||
// via OK or Cancel or is being disposed without being hidden first.
|
||||
AFTER_HIDE: 'afterhide',
|
||||
// Either the cancel or OK events can be canceled via preventDefault or by
|
||||
// returning false from their handlers to stop the dialog from closing.
|
||||
CANCEL: 'cancel',
|
||||
OK: 'ok'
|
||||
};
|
||||
|
||||
|
||||
// *** Inner helper class *************************************************** //
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A builder class for the dialog control. All methods except build return this.
|
||||
* @param {goog.ui.editor.AbstractDialog} editorDialog Editor dialog object
|
||||
* that will wrap the wrapped dialog object this builder will create.
|
||||
* @constructor
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder = function(editorDialog) {
|
||||
// We require the editor dialog to be passed in so that the builder can set up
|
||||
// ok/cancel listeners by default, making it easier for most dialogs.
|
||||
this.editorDialog_ = editorDialog;
|
||||
this.wrappedDialog_ = new goog.ui.Dialog('', true, this.editorDialog_.dom);
|
||||
this.buttonSet_ = new goog.ui.Dialog.ButtonSet(this.editorDialog_.dom);
|
||||
this.buttonHandlers_ = {};
|
||||
this.addClassName(goog.getCssName('tr-dialog'));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the title of the dialog.
|
||||
* @param {string} title Title HTML (escaped).
|
||||
* @return {!goog.ui.editor.AbstractDialog.Builder} This.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.setTitle = function(title) {
|
||||
this.wrappedDialog_.setTitle(title);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds an OK button to the dialog. Clicking this button will cause {@link
|
||||
* handleOk} to run, subsequently dispatching an OK event.
|
||||
* @param {string=} opt_label The caption for the button, if not "OK".
|
||||
* @return {!goog.ui.editor.AbstractDialog.Builder} This.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.addOkButton =
|
||||
function(opt_label) {
|
||||
var key = goog.ui.Dialog.DefaultButtonKeys.OK;
|
||||
/** @desc Label for an OK button in an editor dialog. */
|
||||
var MSG_TR_DIALOG_OK = goog.getMsg('OK');
|
||||
// True means this is the default/OK button.
|
||||
this.buttonSet_.set(key, opt_label || MSG_TR_DIALOG_OK, true);
|
||||
this.buttonHandlers_[key] = goog.bind(this.editorDialog_.handleOk,
|
||||
this.editorDialog_);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a Cancel button to the dialog. Clicking this button will cause {@link
|
||||
* handleCancel} to run, subsequently dispatching a CANCEL event.
|
||||
* @param {string=} opt_label The caption for the button, if not "Cancel".
|
||||
* @return {!goog.ui.editor.AbstractDialog.Builder} This.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.addCancelButton =
|
||||
function(opt_label) {
|
||||
var key = goog.ui.Dialog.DefaultButtonKeys.CANCEL;
|
||||
/** @desc Label for a cancel button in an editor dialog. */
|
||||
var MSG_TR_DIALOG_CANCEL = goog.getMsg('Cancel');
|
||||
// False means it's not the OK button, true means it's the Cancel button.
|
||||
this.buttonSet_.set(key, opt_label || MSG_TR_DIALOG_CANCEL, false, true);
|
||||
this.buttonHandlers_[key] = goog.bind(this.editorDialog_.handleCancel,
|
||||
this.editorDialog_);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a custom button to the dialog.
|
||||
* @param {string} label The caption for the button.
|
||||
* @param {function(goog.ui.Dialog.EventType):*} handler Function called when
|
||||
* the button is clicked. It is recommended that this function be a method
|
||||
* in the concrete subclass of AbstractDialog using this Builder, and that
|
||||
* it dispatch an event (see {@link handleOk}).
|
||||
* @param {string=} opt_buttonId Identifier to be used to access the button when
|
||||
* calling AbstractDialog.getButtonElement().
|
||||
* @return {!goog.ui.editor.AbstractDialog.Builder} This.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.addButton =
|
||||
function(label, handler, opt_buttonId) {
|
||||
// We don't care what the key is, just that we can match the button with the
|
||||
// handler function later.
|
||||
var key = opt_buttonId || goog.string.createUniqueString();
|
||||
this.buttonSet_.set(key, label);
|
||||
this.buttonHandlers_[key] = handler;
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Puts a CSS class on the dialog's main element.
|
||||
* @param {string} className The class to add.
|
||||
* @return {!goog.ui.editor.AbstractDialog.Builder} This.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.addClassName =
|
||||
function(className) {
|
||||
goog.dom.classlist.add(
|
||||
goog.asserts.assert(this.wrappedDialog_.getDialogElement()), className);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the content element of the dialog.
|
||||
* @param {Element} contentElem An element for the main body.
|
||||
* @return {!goog.ui.editor.AbstractDialog.Builder} This.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.setContent =
|
||||
function(contentElem) {
|
||||
goog.dom.appendChild(this.wrappedDialog_.getContentElement(), contentElem);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Builds the wrapped dialog control. May only be called once, after which
|
||||
* no more methods may be called on this builder.
|
||||
* @return {!goog.ui.Dialog} The wrapped dialog control.
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.build = function() {
|
||||
if (this.buttonSet_.isEmpty()) {
|
||||
// If caller didn't set any buttons, add an OK and Cancel button by default.
|
||||
this.addOkButton();
|
||||
this.addCancelButton();
|
||||
}
|
||||
this.wrappedDialog_.setButtonSet(this.buttonSet_);
|
||||
|
||||
var handlers = this.buttonHandlers_;
|
||||
this.buttonHandlers_ = null;
|
||||
this.wrappedDialog_.listen(goog.ui.Dialog.EventType.SELECT,
|
||||
// Listen for the SELECT event, which means a button was clicked, and
|
||||
// call the handler associated with that button via the key property.
|
||||
function(e) {
|
||||
if (handlers[e.key]) {
|
||||
return handlers[e.key](e);
|
||||
}
|
||||
});
|
||||
|
||||
// All editor dialogs are modal.
|
||||
this.wrappedDialog_.setModal(true);
|
||||
|
||||
var dialog = this.wrappedDialog_;
|
||||
this.wrappedDialog_ = null;
|
||||
return dialog;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Editor dialog that will wrap the wrapped dialog this builder will create.
|
||||
* @type {goog.ui.editor.AbstractDialog}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.editorDialog_;
|
||||
|
||||
|
||||
/**
|
||||
* wrapped dialog control being built by this builder.
|
||||
* @type {goog.ui.Dialog}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.wrappedDialog_;
|
||||
|
||||
|
||||
/**
|
||||
* Set of buttons to be added to the wrapped dialog control.
|
||||
* @type {goog.ui.Dialog.ButtonSet}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.buttonSet_;
|
||||
|
||||
|
||||
/**
|
||||
* Map from keys that will be returned in the wrapped dialog SELECT events to
|
||||
* handler functions to be called to handle those events.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.Builder.prototype.buttonHandlers_;
|
||||
|
||||
|
||||
// *** Protected interface ************************************************** //
|
||||
|
||||
|
||||
/**
|
||||
* The DOM helper for the parent document.
|
||||
* @type {goog.dom.DomHelper}
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.dom;
|
||||
|
||||
|
||||
/**
|
||||
* Creates and returns the goog.ui.Dialog control that is being wrapped
|
||||
* by this object.
|
||||
* @return {!goog.ui.Dialog} Created Dialog control.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.createDialogControl =
|
||||
goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the HTML Button element for the OK button in this dialog.
|
||||
* @return {Element} The button element if found, else null.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.getOkButtonElement = function() {
|
||||
return this.getButtonElement(goog.ui.Dialog.DefaultButtonKeys.OK);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the HTML Button element for the Cancel button in this dialog.
|
||||
* @return {Element} The button element if found, else null.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.getCancelButtonElement = function() {
|
||||
return this.getButtonElement(goog.ui.Dialog.DefaultButtonKeys.CANCEL);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the HTML Button element for the button added to this dialog with
|
||||
* the given button id.
|
||||
* @param {string} buttonId The id of the button to get.
|
||||
* @return {Element} The button element if found, else null.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.getButtonElement = function(buttonId) {
|
||||
return this.dialogInternal_.getButtonSet().getButton(buttonId);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates and returns the event object to be used when dispatching the OK
|
||||
* event to listeners, or returns null to prevent the dialog from closing.
|
||||
* Subclasses should override this to return their own subclass of
|
||||
* goog.events.Event that includes all data a plugin would need from the dialog.
|
||||
* @param {goog.events.Event} e The event object dispatched by the wrapped
|
||||
* dialog.
|
||||
* @return {goog.events.Event} The event object to be used when dispatching the
|
||||
* OK event to listeners.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.createOkEvent = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Handles the event dispatched by the wrapped dialog control when the user
|
||||
* clicks the OK button. Attempts to create the OK event object and dispatches
|
||||
* it if successful.
|
||||
* @param {goog.ui.Dialog.Event} e wrapped dialog OK event object.
|
||||
* @return {boolean} Whether the default action (closing the dialog) should
|
||||
* still be executed. This will be false if the OK event could not be
|
||||
* created to be dispatched, or if any listener to that event returs false
|
||||
* or calls preventDefault.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.handleOk = function(e) {
|
||||
var eventObj = this.createOkEvent(e);
|
||||
if (eventObj) {
|
||||
return this.dispatchEvent(eventObj);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the event dispatched by the wrapped dialog control when the user
|
||||
* clicks the Cancel button. Simply dispatches a CANCEL event.
|
||||
* @return {boolean} Returns false if any of the handlers called prefentDefault
|
||||
* on the event or returned false themselves.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.handleCancel = function() {
|
||||
return this.dispatchEvent(goog.ui.editor.AbstractDialog.EventType.CANCEL);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the dialog. If the dialog is open, it will be hidden and
|
||||
* AFTER_HIDE will be dispatched.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.disposeInternal = function() {
|
||||
if (this.dialogInternal_) {
|
||||
this.hide();
|
||||
|
||||
this.dialogInternal_.dispose();
|
||||
this.dialogInternal_ = null;
|
||||
}
|
||||
|
||||
goog.ui.editor.AbstractDialog.superClass_.disposeInternal.call(this);
|
||||
};
|
||||
|
||||
|
||||
// *** Private implementation *********************************************** //
|
||||
|
||||
|
||||
/**
|
||||
* The wrapped dialog widget.
|
||||
* @type {goog.ui.Dialog}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.dialogInternal_;
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up after the dialog is hidden and fires the AFTER_HIDE event. Should
|
||||
* be a listener for the wrapped dialog's AFTER_HIDE event.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.AbstractDialog.prototype.handleAfterHide_ = function() {
|
||||
this.dispatchEvent(goog.ui.editor.AbstractDialog.EventType.AFTER_HIDE);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
All Rights Reserved.
|
||||
|
||||
@author nicksantos@google.com (Nick Santos)
|
||||
@author marcosalmeida@google.com (Marcos Almeida)
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.ui.editor.AbstractDialog
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.ui.editor.AbstractDialogTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,483 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.ui.editor.AbstractDialogTest');
|
||||
goog.setTestOnly('goog.ui.editor.AbstractDialogTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.DomHelper');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.KeyCodes');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.events');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.mockmatchers.ArgumentMatcher');
|
||||
goog.require('goog.ui.editor.AbstractDialog');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
function shouldRunTests() {
|
||||
// Test disabled in IE7 due to flakiness. See b/4269021.
|
||||
return !(goog.userAgent.IE && goog.userAgent.isVersionOrHigher('7'));
|
||||
}
|
||||
|
||||
var dialog;
|
||||
var builder;
|
||||
|
||||
var mockCtrl;
|
||||
var mockAfterHideHandler;
|
||||
var mockOkHandler;
|
||||
var mockCancelHandler;
|
||||
var mockCustomButtonHandler;
|
||||
|
||||
var CUSTOM_EVENT = 'customEvent';
|
||||
var CUSTOM_BUTTON_ID = 'customButton';
|
||||
|
||||
|
||||
function setUp() {
|
||||
mockCtrl = new goog.testing.MockControl();
|
||||
mockAfterHideHandler = mockCtrl.createLooseMock(goog.events.EventHandler);
|
||||
mockOkHandler = mockCtrl.createLooseMock(goog.events.EventHandler);
|
||||
mockCancelHandler = mockCtrl.createLooseMock(goog.events.EventHandler);
|
||||
mockCustomButtonHandler = mockCtrl.createLooseMock(goog.events.EventHandler);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
if (dialog) {
|
||||
mockAfterHideHandler.$setIgnoreUnexpectedCalls(true);
|
||||
dialog.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the mock event handler to expect an AFTER_HIDE event.
|
||||
*/
|
||||
function expectAfterHide() {
|
||||
mockAfterHideHandler.handleEvent(
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(function(arg) {
|
||||
return arg.type ==
|
||||
goog.ui.editor.AbstractDialog.EventType.AFTER_HIDE;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the mock event handler to expect an OK event.
|
||||
*/
|
||||
function expectOk() {
|
||||
mockOkHandler.handleEvent(new goog.testing.mockmatchers.ArgumentMatcher(
|
||||
function(arg) {
|
||||
return arg.type == goog.ui.editor.AbstractDialog.EventType.OK;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the mock event handler to expect an OK event and to call
|
||||
* preventDefault when handling it.
|
||||
*/
|
||||
function expectOkPreventDefault() {
|
||||
expectOk();
|
||||
mockOkHandler.$does(function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the mock event handler to expect an OK event and to return false
|
||||
* when handling it.
|
||||
*/
|
||||
function expectOkReturnFalse() {
|
||||
expectOk();
|
||||
mockOkHandler.$returns(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the mock event handler to expect a CANCEL event.
|
||||
*/
|
||||
function expectCancel() {
|
||||
mockCancelHandler.handleEvent(new goog.testing.mockmatchers.ArgumentMatcher(
|
||||
function(arg) {
|
||||
return arg.type == goog.ui.editor.AbstractDialog.EventType.CANCEL;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the mock event handler to expect a custom button event.
|
||||
*/
|
||||
function expectCustomButton() {
|
||||
mockCustomButtonHandler.handleEvent(
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(function(arg) {
|
||||
return arg.type == CUSTOM_EVENT;
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to create the dialog being tested in each test. Since NewDialog is
|
||||
* abstract, needs to add a concrete version of any abstract methods. Also
|
||||
* creates up the global builder variable which should be set up after the call
|
||||
* to this method.
|
||||
* @return {goog.ui.editor.AbstractDialog} The dialog.
|
||||
*/
|
||||
function createTestDialog() {
|
||||
var dialog = new goog.ui.editor.AbstractDialog(new goog.dom.DomHelper());
|
||||
builder = new goog.ui.editor.AbstractDialog.Builder(dialog);
|
||||
dialog.createDialogControl = function() {
|
||||
return builder.build();
|
||||
};
|
||||
dialog.createOkEvent = function(e) {
|
||||
return new goog.events.Event(goog.ui.editor.AbstractDialog.EventType.OK);
|
||||
};
|
||||
dialog.addEventListener(goog.ui.editor.AbstractDialog.EventType.AFTER_HIDE,
|
||||
mockAfterHideHandler);
|
||||
dialog.addEventListener(goog.ui.editor.AbstractDialog.EventType.OK,
|
||||
mockOkHandler);
|
||||
dialog.addEventListener(goog.ui.editor.AbstractDialog.EventType.CANCEL,
|
||||
mockCancelHandler);
|
||||
dialog.addEventListener(CUSTOM_EVENT, mockCustomButtonHandler);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the given dialog is open.
|
||||
* @param {string} msg Message to be printed in case of failure.
|
||||
* @param {goog.ui.editor.AbstractDialog} dialog Dialog to be tested.
|
||||
*/
|
||||
function assertOpen(msg, dialog) {
|
||||
assertTrue(msg + ' [AbstractDialog.isOpen()]', dialog && dialog.isOpen());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asserts that the given dialog is closed.
|
||||
* @param {string} msg Message to be printed in case of failure.
|
||||
* @param {goog.ui.editor.AbstractDialog} dialog Dialog to be tested.
|
||||
*/
|
||||
function assertNotOpen(msg, dialog) {
|
||||
assertFalse(msg + ' [AbstractDialog.isOpen()]', dialog && dialog.isOpen());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that if you create a dialog and hide it without having shown it, no
|
||||
* errors occur.
|
||||
*/
|
||||
function testCreateAndHide() {
|
||||
dialog = createTestDialog();
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
assertNotOpen('Dialog should not be open after creation', dialog);
|
||||
dialog.hide();
|
||||
assertNotOpen('Dialog should not be open after hide()', dialog);
|
||||
|
||||
mockCtrl.$verifyAll(); // Verifies AFTER_HIDE was not dispatched.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that when you show and hide a dialog the flags indicating open are
|
||||
* correct and the AFTER_HIDE event is dispatched (and no errors happen).
|
||||
*/
|
||||
function testShowAndHide() {
|
||||
dialog = createTestDialog();
|
||||
expectAfterHide(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
assertNotOpen('Dialog should not be open before show()', dialog);
|
||||
dialog.show();
|
||||
assertOpen('Dialog should be open after show()', dialog);
|
||||
dialog.hide();
|
||||
assertNotOpen('Dialog should not be open after hide()', dialog);
|
||||
|
||||
mockCtrl.$verifyAll(); // Verifies AFTER_HIDE was dispatched.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that when you show and dispose a dialog (without hiding it first) the
|
||||
* flags indicating open are correct and the AFTER_HIDE event is dispatched (and
|
||||
* no errors happen).
|
||||
*/
|
||||
function testShowAndDispose() {
|
||||
dialog = createTestDialog();
|
||||
expectAfterHide(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
assertNotOpen('Dialog should not be open before show()', dialog);
|
||||
dialog.show();
|
||||
assertOpen('Dialog should be open after show()', dialog);
|
||||
dialog.dispose();
|
||||
assertNotOpen('Dialog should not be open after dispose()', dialog);
|
||||
|
||||
mockCtrl.$verifyAll(); // Verifies AFTER_HIDE was dispatched.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that when you dispose a dialog (without ever showing it first) the
|
||||
* flags indicating open are correct and the AFTER_HIDE event is never
|
||||
* dispatched (and no errors happen).
|
||||
*/
|
||||
function testDisposeWithoutShow() {
|
||||
dialog = createTestDialog();
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
assertNotOpen('Dialog should not be open before dispose()', dialog);
|
||||
dialog.dispose();
|
||||
assertNotOpen('Dialog should not be open after dispose()', dialog);
|
||||
|
||||
mockCtrl.$verifyAll(); // Verifies AFTER_HIDE was NOT dispatched.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that labels set in the builder can be found in the resulting dialog's
|
||||
* HTML.
|
||||
*/
|
||||
function testBasicLayout() {
|
||||
dialog = createTestDialog();
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
// create some dialog content
|
||||
var content = goog.dom.createDom('div', null, 'The Content');
|
||||
builder.setTitle('The Title')
|
||||
.setContent(content)
|
||||
.addOkButton('The OK Button')
|
||||
.addCancelButton()
|
||||
.addButton('The Apply Button', goog.nullFunction)
|
||||
.addClassName('myClassName');
|
||||
dialog.show();
|
||||
|
||||
var dialogElem = dialog.dialogInternal_.getElement();
|
||||
var html = dialogElem.innerHTML;
|
||||
// TODO(user): This is really insufficient. If the title and content
|
||||
// were swapped this test would still pass!
|
||||
assertContains('Dialog html should contain title', '>The Title<', html);
|
||||
assertContains('Dialog html should contain content', '>The Content<', html);
|
||||
assertContains('Dialog html should contain custom OK button label',
|
||||
'>The OK Button<',
|
||||
html);
|
||||
assertContains('Dialog html should contain default Cancel button label',
|
||||
'>Cancel<',
|
||||
html);
|
||||
assertContains('Dialog html should contain custom button label',
|
||||
'>The Apply Button<',
|
||||
html);
|
||||
assertTrue('Dialog should have default Closure class',
|
||||
goog.dom.classlist.contains(dialogElem, 'modal-dialog'));
|
||||
assertTrue('Dialog should have our custom class',
|
||||
goog.dom.classlist.contains(dialogElem, 'myClassName'));
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that clicking the OK button dispatches the OK event and closes the
|
||||
* dialog (dispatching the AFTER_HIDE event too).
|
||||
*/
|
||||
function testOk() {
|
||||
dialog = createTestDialog();
|
||||
expectOk(dialog);
|
||||
expectAfterHide(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
dialog.show();
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
assertNotOpen('Dialog should not be open after clicking OK', dialog);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that hitting the enter key dispatches the OK event and closes the
|
||||
* dialog (dispatching the AFTER_HIDE event too).
|
||||
*/
|
||||
function testEnter() {
|
||||
dialog = createTestDialog();
|
||||
expectOk(dialog);
|
||||
expectAfterHide(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
dialog.show();
|
||||
goog.testing.events.fireKeySequence(dialog.dialogInternal_.getElement(),
|
||||
goog.events.KeyCodes.ENTER);
|
||||
assertNotOpen('Dialog should not be open after hitting enter', dialog);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that clicking the Cancel button dispatches the CANCEL event and closes
|
||||
* the dialog (dispatching the AFTER_HIDE event too).
|
||||
*/
|
||||
function testCancel() {
|
||||
dialog = createTestDialog();
|
||||
expectCancel(dialog);
|
||||
expectAfterHide(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
builder.addCancelButton('My Cancel Button');
|
||||
|
||||
dialog.show();
|
||||
goog.testing.events.fireClickSequence(dialog.getCancelButtonElement());
|
||||
assertNotOpen('Dialog should not be open after clicking Cancel', dialog);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that hitting the escape key dispatches the CANCEL event and closes
|
||||
* the dialog (dispatching the AFTER_HIDE event too).
|
||||
*/
|
||||
function testEscape() {
|
||||
dialog = createTestDialog();
|
||||
expectCancel(dialog);
|
||||
expectAfterHide(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
dialog.show();
|
||||
goog.testing.events.fireKeySequence(dialog.dialogInternal_.getElement(),
|
||||
goog.events.KeyCodes.ESC);
|
||||
assertNotOpen('Dialog should not be open after hitting escape', dialog);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that clicking the custom button dispatches the custom event and closes
|
||||
* the dialog (dispatching the AFTER_HIDE event too).
|
||||
*/
|
||||
function testCustomButton() {
|
||||
dialog = createTestDialog();
|
||||
expectCustomButton(dialog);
|
||||
expectAfterHide(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
builder.addButton('My Custom Button',
|
||||
function() {
|
||||
dialog.dispatchEvent(CUSTOM_EVENT);
|
||||
},
|
||||
CUSTOM_BUTTON_ID);
|
||||
|
||||
dialog.show();
|
||||
goog.testing.events.fireClickSequence(
|
||||
dialog.getButtonElement(CUSTOM_BUTTON_ID));
|
||||
assertNotOpen('Dialog should not be open after clicking custom button',
|
||||
dialog);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that if the OK handler calls preventDefault, the dialog doesn't close.
|
||||
*/
|
||||
function testOkPreventDefault() {
|
||||
dialog = createTestDialog();
|
||||
expectOkPreventDefault(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
dialog.show();
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
assertOpen('Dialog should not be closed because preventDefault was called',
|
||||
dialog);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that if the OK handler returns false, the dialog doesn't close.
|
||||
*/
|
||||
function testOkReturnFalse() {
|
||||
dialog = createTestDialog();
|
||||
expectOkReturnFalse(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
dialog.show();
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
assertOpen('Dialog should not be closed because handler returned false',
|
||||
dialog);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that if creating the OK event fails, no event is dispatched and the
|
||||
* dialog doesn't close.
|
||||
*/
|
||||
function testCreateOkEventFail() {
|
||||
dialog = createTestDialog();
|
||||
dialog.createOkEvent = function() { // Override our mock createOkEvent.
|
||||
return null;
|
||||
};
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
dialog.show();
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
assertOpen('Dialog should not be closed because OK event creation failed',
|
||||
dialog);
|
||||
|
||||
mockCtrl.$verifyAll(); // Verifies that no event was dispatched.
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that processOkAndClose() dispatches the OK event and closes the
|
||||
* dialog (dispatching the AFTER_HIDE event too).
|
||||
*/
|
||||
function testProcessOkAndClose() {
|
||||
dialog = createTestDialog();
|
||||
expectOk(dialog);
|
||||
expectAfterHide(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
dialog.show();
|
||||
dialog.processOkAndClose();
|
||||
assertNotOpen('Dialog should not be open after processOkAndClose()', dialog);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that if the OK handler triggered by processOkAndClose calls
|
||||
* preventDefault, the dialog doesn't close (in the old implementation this
|
||||
* failed due to not great design, so this is sort of a regression test).
|
||||
*/
|
||||
function testProcessOkAndClosePreventDefault() {
|
||||
dialog = createTestDialog();
|
||||
expectOkPreventDefault(dialog);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
dialog.show();
|
||||
dialog.processOkAndClose();
|
||||
assertOpen('Dialog should not be closed because preventDefault was called',
|
||||
dialog);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
// Copyright 2005 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 Bubble component - handles display, hiding, etc. of the
|
||||
* actual bubble UI.
|
||||
*
|
||||
* This is used exclusively by code within the editor package, and should not
|
||||
* be used directly.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.editor.Bubble');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.dom.ViewportSizeMonitor');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.editor.style');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.math.Box');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.positioning');
|
||||
goog.require('goog.positioning.Corner');
|
||||
goog.require('goog.positioning.Overflow');
|
||||
goog.require('goog.positioning.OverflowStatus');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.ui.Component');
|
||||
goog.require('goog.ui.PopupBase');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Property bubble UI element.
|
||||
* @param {Element} parent The parent element for this bubble.
|
||||
* @param {number} zIndex The z index to draw the bubble at.
|
||||
* @constructor
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.ui.editor.Bubble = function(parent, zIndex) {
|
||||
goog.ui.editor.Bubble.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* Dom helper for the document the bubble should be shown in.
|
||||
* @type {!goog.dom.DomHelper}
|
||||
* @private
|
||||
*/
|
||||
this.dom_ = goog.dom.getDomHelper(parent);
|
||||
|
||||
/**
|
||||
* Event handler for this bubble.
|
||||
* @type {goog.events.EventHandler<!goog.ui.editor.Bubble>}
|
||||
* @private
|
||||
*/
|
||||
this.eventHandler_ = new goog.events.EventHandler(this);
|
||||
|
||||
/**
|
||||
* Object that monitors the application window for size changes.
|
||||
* @type {goog.dom.ViewportSizeMonitor}
|
||||
* @private
|
||||
*/
|
||||
this.viewPortSizeMonitor_ = new goog.dom.ViewportSizeMonitor(
|
||||
this.dom_.getWindow());
|
||||
|
||||
/**
|
||||
* Maps panel ids to panels.
|
||||
* @type {Object<goog.ui.editor.Bubble.Panel_>}
|
||||
* @private
|
||||
*/
|
||||
this.panels_ = {};
|
||||
|
||||
/**
|
||||
* Container element for the entire bubble. This may contain elements related
|
||||
* to look and feel or styling of the bubble.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.bubbleContainer_ =
|
||||
this.dom_.createDom(goog.dom.TagName.DIV,
|
||||
{'className': goog.ui.editor.Bubble.BUBBLE_CLASSNAME});
|
||||
|
||||
goog.style.setElementShown(this.bubbleContainer_, false);
|
||||
goog.dom.appendChild(parent, this.bubbleContainer_);
|
||||
goog.style.setStyle(this.bubbleContainer_, 'zIndex', zIndex);
|
||||
|
||||
/**
|
||||
* Container element for the bubble panels - this should be some inner element
|
||||
* within (or equal to) bubbleContainer.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.bubbleContents_ = this.createBubbleDom(this.dom_, this.bubbleContainer_);
|
||||
|
||||
/**
|
||||
* Element showing the close box.
|
||||
* @type {!Element}
|
||||
* @private
|
||||
*/
|
||||
this.closeBox_ = this.dom_.createDom(goog.dom.TagName.DIV, {
|
||||
'className': goog.getCssName('tr_bubble_closebox'),
|
||||
'innerHTML': ' '
|
||||
});
|
||||
this.bubbleContents_.appendChild(this.closeBox_);
|
||||
|
||||
// We make bubbles unselectable so that clicking on them does not steal focus
|
||||
// or move the cursor away from the element the bubble is attached to.
|
||||
goog.editor.style.makeUnselectable(this.bubbleContainer_, this.eventHandler_);
|
||||
|
||||
/**
|
||||
* Popup that controls showing and hiding the bubble at the appropriate
|
||||
* position.
|
||||
* @type {goog.ui.PopupBase}
|
||||
* @private
|
||||
*/
|
||||
this.popup_ = new goog.ui.PopupBase(this.bubbleContainer_);
|
||||
};
|
||||
goog.inherits(goog.ui.editor.Bubble, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* The css class name of the bubble container element.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.ui.editor.Bubble.BUBBLE_CLASSNAME = goog.getCssName('tr_bubble');
|
||||
|
||||
|
||||
/**
|
||||
* Creates and adds DOM for the bubble UI to the given container. This default
|
||||
* implementation just returns the container itself.
|
||||
* @param {!goog.dom.DomHelper} dom DOM helper to use.
|
||||
* @param {!Element} container Element to add the new elements to.
|
||||
* @return {!Element} The element where bubble content should be added.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.createBubbleDom = function(dom, container) {
|
||||
return container;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A logger for goog.ui.editor.Bubble.
|
||||
* @type {goog.log.Logger}
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.logger =
|
||||
goog.log.getLogger('goog.ui.editor.Bubble');
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.editor.Bubble.prototype.disposeInternal = function() {
|
||||
goog.ui.editor.Bubble.base(this, 'disposeInternal');
|
||||
|
||||
goog.dom.removeNode(this.bubbleContainer_);
|
||||
this.bubbleContainer_ = null;
|
||||
|
||||
this.eventHandler_.dispose();
|
||||
this.eventHandler_ = null;
|
||||
|
||||
this.viewPortSizeMonitor_.dispose();
|
||||
this.viewPortSizeMonitor_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Element} The element that where the bubble's contents go.
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.getContentElement = function() {
|
||||
return this.bubbleContents_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Element} The element that contains the bubble.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.getContainerElement = function() {
|
||||
return this.bubbleContainer_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.events.EventHandler<T>} The event handler.
|
||||
* @protected
|
||||
* @this T
|
||||
* @template T
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.getEventHandler = function() {
|
||||
return this.eventHandler_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles user resizing of window.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.handleWindowResize_ = function() {
|
||||
if (this.isVisible()) {
|
||||
this.reposition();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether the bubble dismisses itself when the user clicks outside of it.
|
||||
* @param {boolean} autoHide Whether to autohide on an external click.
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.setAutoHide = function(autoHide) {
|
||||
this.popup_.setAutoHide(autoHide);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether there is already a panel of the given type.
|
||||
* @param {string} type Type of panel to check.
|
||||
* @return {boolean} Whether there is already a panel of the given type.
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.hasPanelOfType = function(type) {
|
||||
return goog.object.some(this.panels_, function(panel) {
|
||||
return panel.type == type;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a panel to the bubble.
|
||||
* @param {string} type The type of bubble panel this is. Should usually be
|
||||
* the same as the tagName of the targetElement. This ensures multiple
|
||||
* bubble panels don't appear for the same element.
|
||||
* @param {string} title The title of the panel.
|
||||
* @param {Element} targetElement The target element of the bubble.
|
||||
* @param {function(Element): void} contentFn Function that when called with
|
||||
* a container element, will add relevant panel content to it.
|
||||
* @param {boolean=} opt_preferTopPosition Whether to prefer placing the bubble
|
||||
* above the element instead of below it. Defaults to preferring below.
|
||||
* If any panel prefers the top position, the top position is used.
|
||||
* @return {string} The id of the panel.
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.addPanel = function(type, title, targetElement,
|
||||
contentFn, opt_preferTopPosition) {
|
||||
var id = goog.string.createUniqueString();
|
||||
var panel = new goog.ui.editor.Bubble.Panel_(this.dom_, id, type, title,
|
||||
targetElement, !opt_preferTopPosition);
|
||||
this.panels_[id] = panel;
|
||||
|
||||
// Insert the panel in string order of type. Technically we could use binary
|
||||
// search here but n is really small (probably 0 - 2) so it's not worth it.
|
||||
// The last child of bubbleContents_ is the close box so we take care not
|
||||
// to treat it as a panel element, and we also ensure it stays as the last
|
||||
// element. The intention here is not to create any artificial order, but
|
||||
// just to ensure that it is always consistent.
|
||||
var nextElement;
|
||||
for (var i = 0, len = this.bubbleContents_.childNodes.length - 1; i < len;
|
||||
i++) {
|
||||
var otherChild = this.bubbleContents_.childNodes[i];
|
||||
var otherPanel = this.panels_[otherChild.id];
|
||||
if (otherPanel.type > type) {
|
||||
nextElement = otherChild;
|
||||
break;
|
||||
}
|
||||
}
|
||||
goog.dom.insertSiblingBefore(panel.element,
|
||||
nextElement || this.bubbleContents_.lastChild);
|
||||
|
||||
contentFn(panel.getContentElement());
|
||||
goog.editor.style.makeUnselectable(panel.element, this.eventHandler_);
|
||||
|
||||
var numPanels = goog.object.getCount(this.panels_);
|
||||
if (numPanels == 1) {
|
||||
this.openBubble_();
|
||||
} else if (numPanels == 2) {
|
||||
goog.dom.classlist.add(
|
||||
goog.asserts.assert(this.bubbleContainer_),
|
||||
goog.getCssName('tr_multi_bubble'));
|
||||
}
|
||||
this.reposition();
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the panel with the given id.
|
||||
* @param {string} id The id of the panel.
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.removePanel = function(id) {
|
||||
var panel = this.panels_[id];
|
||||
goog.dom.removeNode(panel.element);
|
||||
delete this.panels_[id];
|
||||
|
||||
var numPanels = goog.object.getCount(this.panels_);
|
||||
if (numPanels <= 1) {
|
||||
goog.dom.classlist.remove(
|
||||
goog.asserts.assert(this.bubbleContainer_),
|
||||
goog.getCssName('tr_multi_bubble'));
|
||||
}
|
||||
|
||||
if (numPanels == 0) {
|
||||
this.closeBubble_();
|
||||
} else {
|
||||
this.reposition();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Opens the bubble.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.openBubble_ = function() {
|
||||
this.eventHandler_.
|
||||
listen(this.closeBox_, goog.events.EventType.CLICK,
|
||||
this.closeBubble_).
|
||||
listen(this.viewPortSizeMonitor_,
|
||||
goog.events.EventType.RESIZE, this.handleWindowResize_).
|
||||
listen(this.popup_, goog.ui.PopupBase.EventType.HIDE,
|
||||
this.handlePopupHide);
|
||||
|
||||
this.popup_.setVisible(true);
|
||||
this.reposition();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Closes the bubble.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.closeBubble_ = function() {
|
||||
this.popup_.setVisible(false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the popup's hide event by removing all panels and dispatching a
|
||||
* HIDE event.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.handlePopupHide = function() {
|
||||
// Remove the panel elements.
|
||||
for (var panelId in this.panels_) {
|
||||
goog.dom.removeNode(this.panels_[panelId].element);
|
||||
}
|
||||
|
||||
// Update the state to reflect no panels.
|
||||
this.panels_ = {};
|
||||
goog.dom.classlist.remove(
|
||||
goog.asserts.assert(this.bubbleContainer_),
|
||||
goog.getCssName('tr_multi_bubble'));
|
||||
|
||||
this.eventHandler_.removeAll();
|
||||
this.dispatchEvent(goog.ui.Component.EventType.HIDE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the visibility of the bubble.
|
||||
* @return {boolean} True if visible false if not.
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.isVisible = function() {
|
||||
return this.popup_.isVisible();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The vertical clearance in pixels between the bottom of the targetElement
|
||||
* and the edge of the bubble.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.Bubble.VERTICAL_CLEARANCE_ = goog.userAgent.IE ? 4 : 2;
|
||||
|
||||
|
||||
/**
|
||||
* Bubble's margin box to be passed to goog.positioning.
|
||||
* @type {goog.math.Box}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.Bubble.MARGIN_BOX_ = new goog.math.Box(
|
||||
goog.ui.editor.Bubble.VERTICAL_CLEARANCE_, 0,
|
||||
goog.ui.editor.Bubble.VERTICAL_CLEARANCE_, 0);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the margin box.
|
||||
* @return {goog.math.Box}
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.getMarginBox = function() {
|
||||
return goog.ui.editor.Bubble.MARGIN_BOX_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Positions and displays this bubble below its targetElement. Assumes that
|
||||
* the bubbleContainer is already contained in the document object it applies
|
||||
* to.
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.reposition = function() {
|
||||
var targetElement = null;
|
||||
var preferBottomPosition = true;
|
||||
for (var panelId in this.panels_) {
|
||||
var panel = this.panels_[panelId];
|
||||
// We don't care which targetElement we get, so we just take the last one.
|
||||
targetElement = panel.targetElement;
|
||||
preferBottomPosition = preferBottomPosition && panel.preferBottomPosition;
|
||||
}
|
||||
var status = goog.positioning.OverflowStatus.FAILED;
|
||||
|
||||
// Fix for bug when bubbleContainer and targetElement have
|
||||
// opposite directionality, the bubble should anchor to the END of
|
||||
// the targetElement instead of START.
|
||||
var reverseLayout = (goog.style.isRightToLeft(this.bubbleContainer_) !=
|
||||
goog.style.isRightToLeft(targetElement));
|
||||
|
||||
// Try to put the bubble at the bottom of the target unless the plugin has
|
||||
// requested otherwise.
|
||||
if (preferBottomPosition) {
|
||||
status = this.positionAtAnchor_(reverseLayout ?
|
||||
goog.positioning.Corner.BOTTOM_END :
|
||||
goog.positioning.Corner.BOTTOM_START,
|
||||
goog.positioning.Corner.TOP_START,
|
||||
goog.positioning.Overflow.ADJUST_X | goog.positioning.Overflow.FAIL_Y);
|
||||
}
|
||||
|
||||
if (status & goog.positioning.OverflowStatus.FAILED) {
|
||||
// Try to put it at the top of the target if there is not enough
|
||||
// space at the bottom.
|
||||
status = this.positionAtAnchor_(reverseLayout ?
|
||||
goog.positioning.Corner.TOP_END : goog.positioning.Corner.TOP_START,
|
||||
goog.positioning.Corner.BOTTOM_START,
|
||||
goog.positioning.Overflow.ADJUST_X | goog.positioning.Overflow.FAIL_Y);
|
||||
}
|
||||
|
||||
if (status & goog.positioning.OverflowStatus.FAILED) {
|
||||
// Put it at the bottom again with adjustment if there is no
|
||||
// enough space at the top.
|
||||
status = this.positionAtAnchor_(reverseLayout ?
|
||||
goog.positioning.Corner.BOTTOM_END :
|
||||
goog.positioning.Corner.BOTTOM_START,
|
||||
goog.positioning.Corner.TOP_START,
|
||||
goog.positioning.Overflow.ADJUST_X |
|
||||
goog.positioning.Overflow.ADJUST_Y);
|
||||
if (status & goog.positioning.OverflowStatus.FAILED) {
|
||||
goog.log.warning(this.logger,
|
||||
'reposition(): positionAtAnchor() failed with ' + status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A helper for reposition() - positions the bubble in regards to the position
|
||||
* of the elements the bubble is attached to.
|
||||
* @param {goog.positioning.Corner} targetCorner The corner of
|
||||
* the target element.
|
||||
* @param {goog.positioning.Corner} bubbleCorner The corner of the bubble.
|
||||
* @param {number} overflow Overflow handling mode bitmap,
|
||||
* {@see goog.positioning.Overflow}.
|
||||
* @return {number} Status bitmap, {@see goog.positioning.OverflowStatus}.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.positionAtAnchor_ = function(
|
||||
targetCorner, bubbleCorner, overflow) {
|
||||
var targetElement = null;
|
||||
for (var panelId in this.panels_) {
|
||||
// For now, we use the outermost element. This assumes the multiple
|
||||
// elements this panel is showing for contain each other - in the event
|
||||
// that is not generally the case this may need to be updated to pick
|
||||
// the lowest or highest element depending on targetCorner.
|
||||
var candidate = this.panels_[panelId].targetElement;
|
||||
if (!targetElement || goog.dom.contains(candidate, targetElement)) {
|
||||
targetElement = this.panels_[panelId].targetElement;
|
||||
}
|
||||
}
|
||||
return goog.positioning.positionAtAnchor(
|
||||
targetElement, targetCorner, this.bubbleContainer_,
|
||||
bubbleCorner, null, this.getMarginBox(), overflow,
|
||||
null, this.getViewportBox());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the viewport box to use when positioning the bubble.
|
||||
* @return {goog.math.Box}
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.editor.Bubble.prototype.getViewportBox = goog.functions.NULL;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Private class used to describe a bubble panel.
|
||||
* @param {goog.dom.DomHelper} dom DOM helper used to create the panel.
|
||||
* @param {string} id ID of the panel.
|
||||
* @param {string} type Type of the panel.
|
||||
* @param {string} title Title of the panel.
|
||||
* @param {Element} targetElement Element the panel is showing for.
|
||||
* @param {boolean} preferBottomPosition Whether this panel prefers to show
|
||||
* below the target element.
|
||||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.Bubble.Panel_ = function(dom, id, type, title, targetElement,
|
||||
preferBottomPosition) {
|
||||
/**
|
||||
* The type of bubble panel.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = type;
|
||||
|
||||
/**
|
||||
* The target element of this bubble panel.
|
||||
* @type {Element}
|
||||
*/
|
||||
this.targetElement = targetElement;
|
||||
|
||||
/**
|
||||
* Whether the panel prefers to be placed below the target element.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.preferBottomPosition = preferBottomPosition;
|
||||
|
||||
/**
|
||||
* The element containing this panel.
|
||||
*/
|
||||
this.element = dom.createDom(goog.dom.TagName.DIV,
|
||||
{className: goog.getCssName('tr_bubble_panel'), id: id},
|
||||
dom.createDom(goog.dom.TagName.DIV,
|
||||
{className: goog.getCssName('tr_bubble_panel_title')},
|
||||
title ? title + ':' : ''), // TODO(robbyw): Does this work in bidi?
|
||||
dom.createDom(goog.dom.TagName.DIV,
|
||||
{className: goog.getCssName('tr_bubble_panel_content')}));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Element} The element in the panel where content should go.
|
||||
*/
|
||||
goog.ui.editor.Bubble.Panel_.prototype.getContentElement = function() {
|
||||
return /** @type {Element} */ (this.element.lastChild);
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
All Rights Reserved.
|
||||
|
||||
@author tildahl@google.com (Michael Tildahl)
|
||||
@author robbyw@google.com (Robby Walker)
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.ui.editor.Bubble
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.ui.editor.BubbleTest');
|
||||
</script>
|
||||
<link rel="stylesheet" type="text/css" href="../../css/editor/bubble.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="field"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,255 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.ui.editor.BubbleTest');
|
||||
goog.setTestOnly('goog.ui.editor.BubbleTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.positioning.Corner');
|
||||
goog.require('goog.positioning.OverflowStatus');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.testing.editor.TestHelper');
|
||||
goog.require('goog.testing.events');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.ui.Component');
|
||||
goog.require('goog.ui.editor.Bubble');
|
||||
|
||||
var testHelper;
|
||||
var fieldDiv;
|
||||
var bubble;
|
||||
var link;
|
||||
var link2;
|
||||
var panelId;
|
||||
|
||||
function setUpPage() {
|
||||
fieldDiv = goog.dom.getElement('field');
|
||||
var viewportSize = goog.dom.getViewportSize();
|
||||
// Some tests depends on enough size of viewport.
|
||||
if (viewportSize.width < 600 || viewportSize.height < 440) {
|
||||
window.moveTo(0, 0);
|
||||
window.resizeTo(640, 480);
|
||||
}
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
testHelper = new goog.testing.editor.TestHelper(fieldDiv);
|
||||
testHelper.setUpEditableElement();
|
||||
|
||||
bubble = new goog.ui.editor.Bubble(document.body, 999);
|
||||
|
||||
fieldDiv.innerHTML = '<a href="http://www.google.com">Google</a>' +
|
||||
'<a href="http://www.google.com">Google2</a>';
|
||||
link = fieldDiv.firstChild;
|
||||
link2 = fieldDiv.lastChild;
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
goog.style.setStyle(document.body, 'direction', 'ltr');
|
||||
goog.style.setStyle(document.getElementById('field'), 'position', 'static');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
if (panelId) {
|
||||
bubble.removePanel(panelId);
|
||||
}
|
||||
testHelper.tearDownEditableElement();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a helper function for setting up the target element with a
|
||||
* given direction.
|
||||
*
|
||||
* @param {string} dir The direction of the target element, 'ltr' or 'rtl'.
|
||||
* @param {boolean=} opt_preferTopPosition Whether to prefer placing the bubble
|
||||
* above the element instead of below it. Defaults to preferring below.
|
||||
*/
|
||||
function prepareTargetWithGivenDirection(dir, opt_preferTopPosition) {
|
||||
goog.style.setStyle(document.body, 'direction', dir);
|
||||
|
||||
fieldDiv.style.direction = dir;
|
||||
fieldDiv.innerHTML = '<a href="http://www.google.com">Google</a>';
|
||||
link = fieldDiv.firstChild;
|
||||
|
||||
panelId = bubble.addPanel('A', 'Link', link, function(el) {
|
||||
el.innerHTML = '<div style="border:1px solid blue;">B</div>';
|
||||
}, opt_preferTopPosition);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a helper function for getting the expected position of the bubble.
|
||||
* (align to the right or the left of the target element). Align left by
|
||||
* default and align right if opt_alignRight is true. The expected Y is
|
||||
* unaffected by alignment.
|
||||
*
|
||||
* @param {boolean=} opt_alignRight Sets the expected alignment to be right.
|
||||
*/
|
||||
function getExpectedBubblePositionWithGivenAlignment(opt_alignRight) {
|
||||
var targetPosition = goog.style.getFramedPageOffset(link, window);
|
||||
var targetWidth = link.offsetWidth;
|
||||
var bubbleSize = goog.style.getSize(bubble.bubbleContainer_);
|
||||
var expectedBubbleX = opt_alignRight ?
|
||||
targetPosition.x + targetWidth - bubbleSize.width : targetPosition.x;
|
||||
var expectedBubbleY = link.offsetHeight + targetPosition.y +
|
||||
goog.ui.editor.Bubble.VERTICAL_CLEARANCE_;
|
||||
|
||||
return {
|
||||
x: expectedBubbleX,
|
||||
y: expectedBubbleY
|
||||
};
|
||||
}
|
||||
|
||||
function testCreateBubbleWithLinkPanel() {
|
||||
var id = goog.string.createUniqueString();
|
||||
panelId = bubble.addPanel('A', 'Link', link, function(container) {
|
||||
container.innerHTML = '<span id="' + id + '">Test</span>';
|
||||
});
|
||||
assertNotNull('Bubble should be created', bubble.bubbleContents_);
|
||||
assertNotNull('Added element should be present', goog.dom.getElement(id));
|
||||
assertTrue('Bubble should be visible', bubble.isVisible());
|
||||
}
|
||||
|
||||
function testCloseBubble() {
|
||||
testCreateBubbleWithLinkPanel();
|
||||
|
||||
var count = 0;
|
||||
goog.events.listen(bubble, goog.ui.Component.EventType.HIDE, function() {
|
||||
count++;
|
||||
});
|
||||
|
||||
bubble.removePanel(panelId);
|
||||
panelId = null;
|
||||
|
||||
assertFalse('Bubble should not be visible', bubble.isVisible());
|
||||
assertEquals('Hide event should be dispatched', 1, count);
|
||||
}
|
||||
|
||||
function testCloseBox() {
|
||||
testCreateBubbleWithLinkPanel();
|
||||
|
||||
var count = 0;
|
||||
goog.events.listen(bubble, goog.ui.Component.EventType.HIDE, function() {
|
||||
count++;
|
||||
});
|
||||
|
||||
var closeBox = goog.dom.getElementsByTagNameAndClass(
|
||||
goog.dom.TagName.DIV, 'tr_bubble_closebox', bubble.bubbleContainer_)[0];
|
||||
goog.testing.events.fireClickSequence(closeBox);
|
||||
panelId = null;
|
||||
|
||||
assertFalse('Bubble should not be visible', bubble.isVisible());
|
||||
assertEquals('Hide event should be dispatched', 1, count);
|
||||
}
|
||||
|
||||
function testViewPortSizeMonitorEvent() {
|
||||
testCreateBubbleWithLinkPanel();
|
||||
|
||||
var numCalled = 0;
|
||||
bubble.reposition = function() {
|
||||
numCalled++;
|
||||
};
|
||||
|
||||
assertNotUndefined('viewPortSizeMonitor_ should not be undefined',
|
||||
bubble.viewPortSizeMonitor_);
|
||||
bubble.viewPortSizeMonitor_.dispatchEvent(goog.events.EventType.RESIZE);
|
||||
|
||||
assertEquals('reposition not called', 1, numCalled);
|
||||
}
|
||||
|
||||
function testBubblePositionPreferTop() {
|
||||
called = false;
|
||||
bubble.positionAtAnchor_ = function(targetCorner, bubbleCorner, overflow) {
|
||||
called = true;
|
||||
|
||||
// Assert that the bubble is positioned below the target.
|
||||
assertEquals(goog.positioning.Corner.TOP_START, targetCorner);
|
||||
assertEquals(goog.positioning.Corner.BOTTOM_START, bubbleCorner);
|
||||
|
||||
return goog.positioning.OverflowStatus.NONE;
|
||||
};
|
||||
prepareTargetWithGivenDirection('ltr', true);
|
||||
assertTrue(called);
|
||||
}
|
||||
|
||||
function testBubblePosition() {
|
||||
panelId = bubble.addPanel('A', 'Link', link, goog.nullFunction);
|
||||
var CLEARANCE = goog.ui.editor.Bubble.VERTICAL_CLEARANCE_;
|
||||
var bubbleContainer = bubble.bubbleContainer_;
|
||||
|
||||
// The field is at a normal place, alomost the top of the viewport, and
|
||||
// there is enough space at the bottom of the field.
|
||||
var targetPos = goog.style.getFramedPageOffset(link, window);
|
||||
var targetSize = goog.style.getSize(link);
|
||||
var pos = goog.style.getFramedPageOffset(bubbleContainer);
|
||||
assertEquals(targetPos.y + targetSize.height + CLEARANCE, pos.y);
|
||||
assertEquals(targetPos.x, pos.x);
|
||||
|
||||
// Move the target to the bottom of the viewport.
|
||||
var field = document.getElementById('field');
|
||||
var fieldPos = goog.style.getFramedPageOffset(field, window);
|
||||
fieldPos.y += bubble.dom_.getViewportSize().height -
|
||||
(targetPos.y + targetSize.height);
|
||||
goog.style.setStyle(field, 'position', 'absolute');
|
||||
goog.style.setPosition(field, fieldPos);
|
||||
bubble.reposition();
|
||||
var bubbleSize = goog.style.getSize(bubbleContainer);
|
||||
targetPosition = goog.style.getFramedPageOffset(link, window);
|
||||
pos = goog.style.getFramedPageOffset(bubbleContainer);
|
||||
assertEquals(targetPosition.y - CLEARANCE - bubbleSize.height, pos.y);
|
||||
}
|
||||
|
||||
function testBubblePositionRightAligned() {
|
||||
prepareTargetWithGivenDirection('rtl');
|
||||
|
||||
var expectedPos = getExpectedBubblePositionWithGivenAlignment(true);
|
||||
var pos = goog.style.getFramedPageOffset(bubble.bubbleContainer_);
|
||||
assertRoughlyEquals(expectedPos.x, pos.x, 0.1);
|
||||
assertRoughlyEquals(expectedPos.y, pos.y, 0.1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test for bug 1955511, the bubble should align to the right side
|
||||
* of the target element when the bubble is RTL, regardless of the
|
||||
* target element's directionality.
|
||||
*/
|
||||
function testBubblePositionLeftToRight() {
|
||||
goog.style.setStyle(bubble.bubbleContainer_, 'direction', 'ltr');
|
||||
prepareTargetWithGivenDirection('rtl');
|
||||
|
||||
var expectedPos = getExpectedBubblePositionWithGivenAlignment();
|
||||
var pos = goog.style.getFramedPageOffset(bubble.bubbleContainer_);
|
||||
assertRoughlyEquals(expectedPos.x, pos.x, 0.1);
|
||||
assertRoughlyEquals(expectedPos.y, pos.y, 0.1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test for bug 1955511, the bubble should align to the left side
|
||||
* of the target element when the bubble is LTR, regardless of the
|
||||
* target element's directionality.
|
||||
*/
|
||||
function testBubblePositionRightToLeft() {
|
||||
goog.style.setStyle(bubble.bubbleContainer_, 'direction', 'rtl');
|
||||
prepareTargetWithGivenDirection('ltr');
|
||||
|
||||
var expectedPos = getExpectedBubblePositionWithGivenAlignment(true);
|
||||
var pos = goog.style.getFramedPageOffset(bubble.bubbleContainer_);
|
||||
assertEquals(expectedPos.x, pos.x);
|
||||
assertEquals(expectedPos.y, pos.y);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
goog.ui.editor.LinkDialog Tests
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.ui.editor.LinkDialogTest');
|
||||
</script>
|
||||
<link rel="stylesheet" href="../../css/dialog.css" />
|
||||
<link rel="stylesheet" href="../../css/linkbutton.css" />
|
||||
<link rel="stylesheet" href="../../css/editor/dialog.css" />
|
||||
<link rel="stylesheet" href="../../css/editor/linkdialog.css" />
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="appWindowIframe" src="javascript:''">
|
||||
</iframe>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,666 @@
|
||||
// Copyright 2010 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.
|
||||
|
||||
goog.provide('goog.ui.editor.LinkDialogTest');
|
||||
goog.setTestOnly('goog.ui.editor.LinkDialogTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.DomHelper');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.editor.BrowserFeature');
|
||||
goog.require('goog.editor.Link');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.events.KeyCodes');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.dom');
|
||||
goog.require('goog.testing.events');
|
||||
goog.require('goog.testing.events.Event');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.mockmatchers');
|
||||
goog.require('goog.testing.mockmatchers.ArgumentMatcher');
|
||||
goog.require('goog.ui.editor.AbstractDialog');
|
||||
goog.require('goog.ui.editor.LinkDialog');
|
||||
goog.require('goog.ui.editor.messages');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
var dialog;
|
||||
var mockCtrl;
|
||||
var mockLink;
|
||||
var mockOkHandler;
|
||||
var mockGetViewportSize;
|
||||
var mockWindowOpen;
|
||||
var isNew;
|
||||
var anchorElem;
|
||||
var stubs = new goog.testing.PropertyReplacer();
|
||||
|
||||
var ANCHOR_TEXT = 'anchor text';
|
||||
var ANCHOR_URL = 'http://www.google.com/';
|
||||
var ANCHOR_EMAIL = 'm@r.cos';
|
||||
var ANCHOR_MAILTO = 'mailto:' + ANCHOR_EMAIL;
|
||||
|
||||
function setUp() {
|
||||
anchorElem = goog.dom.createElement(goog.dom.TagName.A);
|
||||
goog.dom.appendChild(goog.dom.getDocument().body, anchorElem);
|
||||
|
||||
mockCtrl = new goog.testing.MockControl();
|
||||
mockLink = mockCtrl.createLooseMock(goog.editor.Link);
|
||||
mockOkHandler = mockCtrl.createLooseMock(goog.events.EventHandler);
|
||||
|
||||
isNew = false;
|
||||
mockLink.isNew();
|
||||
mockLink.$anyTimes();
|
||||
mockLink.$does(function() {
|
||||
return isNew;
|
||||
});
|
||||
mockLink.getCurrentText();
|
||||
mockLink.$anyTimes();
|
||||
mockLink.$does(function() {
|
||||
return anchorElem.innerHTML;
|
||||
});
|
||||
mockLink.setTextAndUrl(goog.testing.mockmatchers.isString,
|
||||
goog.testing.mockmatchers.isString);
|
||||
mockLink.$anyTimes();
|
||||
mockLink.$does(function(text, url) {
|
||||
anchorElem.innerHTML = text;
|
||||
anchorElem.href = url;
|
||||
});
|
||||
mockLink.$registerArgumentListVerifier('placeCursorRightOf', function() {
|
||||
return true;
|
||||
});
|
||||
mockLink.placeCursorRightOf(goog.testing.mockmatchers.iBoolean);
|
||||
mockLink.$anyTimes();
|
||||
mockLink.getAnchor();
|
||||
mockLink.$anyTimes();
|
||||
mockLink.$returns(anchorElem);
|
||||
|
||||
mockWindowOpen = mockCtrl.createFunctionMock('open');
|
||||
stubs.set(window, 'open', mockWindowOpen);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
dialog.dispose();
|
||||
goog.dom.removeNode(anchorElem);
|
||||
stubs.reset();
|
||||
}
|
||||
|
||||
function setUpAnchor(href, text, opt_isNew, opt_target, opt_rel) {
|
||||
anchorElem.href = href;
|
||||
anchorElem.innerHTML = text;
|
||||
isNew = !!opt_isNew;
|
||||
if (opt_target) {
|
||||
anchorElem.target = opt_target;
|
||||
}
|
||||
if (opt_rel) {
|
||||
anchorElem.rel = opt_rel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates and shows the dialog to be tested.
|
||||
* @param {Document=} opt_document Document to render the dialog into.
|
||||
* Defaults to the main window's document.
|
||||
* @param {boolean=} opt_openInNewWindow Whether the open in new window
|
||||
* checkbox should be shown.
|
||||
* @param {boolean=} opt_noFollow Whether rel=nofollow checkbox should be
|
||||
* shown.
|
||||
*/
|
||||
function createAndShow(opt_document, opt_openInNewWindow, opt_noFollow) {
|
||||
dialog = new goog.ui.editor.LinkDialog(new goog.dom.DomHelper(opt_document),
|
||||
mockLink);
|
||||
if (opt_openInNewWindow) {
|
||||
dialog.showOpenLinkInNewWindow(false);
|
||||
}
|
||||
if (opt_noFollow) {
|
||||
dialog.showRelNoFollow();
|
||||
}
|
||||
dialog.addEventListener(goog.ui.editor.AbstractDialog.EventType.OK,
|
||||
mockOkHandler);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the mock event handler to expect an OK event with the given text
|
||||
* and url.
|
||||
*/
|
||||
function expectOk(linkText, linkUrl, opt_openInNewWindow, opt_noFollow) {
|
||||
mockOkHandler.handleEvent(new goog.testing.mockmatchers.ArgumentMatcher(
|
||||
function(arg) {
|
||||
return arg.type == goog.ui.editor.AbstractDialog.EventType.OK &&
|
||||
arg.linkText == linkText &&
|
||||
arg.linkUrl == linkUrl &&
|
||||
arg.openInNewWindow == !!opt_openInNewWindow &&
|
||||
arg.noFollow == !!opt_noFollow;
|
||||
},
|
||||
'{linkText: ' + linkText + ', linkUrl: ' + linkUrl +
|
||||
', openInNewWindow: ' + opt_openInNewWindow +
|
||||
', noFollow: ' + opt_noFollow + '}'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if we should use active element in our tests.
|
||||
* @return {boolean} .
|
||||
*/
|
||||
function useActiveElement() {
|
||||
return goog.editor.BrowserFeature.HAS_ACTIVE_ELEMENT ||
|
||||
goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher(9);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that when you show the dialog for a new link, you can switch
|
||||
* to the URL view.
|
||||
* @param {Document=} opt_document Document to render the dialog into.
|
||||
* Defaults to the main window's document.
|
||||
*/
|
||||
function testShowNewLinkSwitchToUrl(opt_document) {
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor('', '', true); // Must be done before creating the dialog.
|
||||
createAndShow(opt_document);
|
||||
|
||||
var webRadio = dialog.dom.getElement(
|
||||
goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB).firstChild;
|
||||
var emailRadio = dialog.dom.getElement(
|
||||
goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB).firstChild;
|
||||
assertTrue('Web Radio Button selected', webRadio.checked);
|
||||
assertFalse('Email Radio Button selected', emailRadio.checked);
|
||||
if (useActiveElement()) {
|
||||
assertEquals('Focus should be on url input',
|
||||
getUrlInput(),
|
||||
dialog.dom.getActiveElement());
|
||||
}
|
||||
|
||||
emailRadio.click();
|
||||
assertFalse('Web Radio Button selected', webRadio.checked);
|
||||
assertTrue('Email Radio Button selected', emailRadio.checked);
|
||||
if (useActiveElement()) {
|
||||
assertEquals('Focus should be on url input',
|
||||
getEmailInput(),
|
||||
dialog.dom.getActiveElement());
|
||||
}
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that when you show the dialog for a new link, the input fields are
|
||||
* empty, the web tab is selected and focus is in the url input field.
|
||||
* @param {Document=} opt_document Document to render the dialog into.
|
||||
* Defaults to the main window's document.
|
||||
*/
|
||||
function testShowForNewLink(opt_document) {
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor('', '', true); // Must be done before creating the dialog.
|
||||
createAndShow(opt_document);
|
||||
|
||||
assertEquals('Display text input field should be empty',
|
||||
'',
|
||||
getDisplayInputText());
|
||||
assertEquals('Url input field should be empty',
|
||||
'',
|
||||
getUrlInputText());
|
||||
assertEquals('On the web tab should be selected',
|
||||
goog.ui.editor.LinkDialog.Id_.ON_WEB,
|
||||
dialog.curTabId_);
|
||||
if (useActiveElement()) {
|
||||
assertEquals('Focus should be on url input',
|
||||
getUrlInput(),
|
||||
dialog.dom.getActiveElement());
|
||||
}
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fakes that the mock field is using an iframe and does the same test as
|
||||
* testShowForNewLink().
|
||||
*/
|
||||
function testShowForNewLinkWithDiffAppWindow() {
|
||||
testShowForNewLink(goog.dom.getElement('appWindowIframe').contentDocument);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that when you show the dialog for a url link, the input fields are
|
||||
* filled in, the web tab is selected and focus is in the url input field.
|
||||
*/
|
||||
function testShowForUrlLink() {
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor(ANCHOR_URL, ANCHOR_TEXT);
|
||||
createAndShow();
|
||||
|
||||
assertEquals('Display text input field should be filled in',
|
||||
ANCHOR_TEXT,
|
||||
getDisplayInputText());
|
||||
assertEquals('Url input field should be filled in',
|
||||
ANCHOR_URL,
|
||||
getUrlInputText());
|
||||
assertEquals('On the web tab should be selected',
|
||||
goog.ui.editor.LinkDialog.Id_.ON_WEB,
|
||||
dialog.curTabId_);
|
||||
if (useActiveElement()) {
|
||||
assertEquals('Focus should be on url input',
|
||||
getUrlInput(),
|
||||
dialog.dom.getActiveElement());
|
||||
}
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that when you show the dialog for a mailto link, the input fields are
|
||||
* filled in, the email tab is selected and focus is in the email input field.
|
||||
*/
|
||||
function testShowForMailtoLink() {
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor(ANCHOR_MAILTO, ANCHOR_TEXT);
|
||||
createAndShow();
|
||||
|
||||
assertEquals('Display text input field should be filled in',
|
||||
ANCHOR_TEXT,
|
||||
getDisplayInputText());
|
||||
assertEquals('Email input field should be filled in',
|
||||
ANCHOR_EMAIL, // The 'mailto:' is not in the input!
|
||||
getEmailInputText());
|
||||
assertEquals('Email tab should be selected',
|
||||
goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS,
|
||||
dialog.curTabId_);
|
||||
if (useActiveElement()) {
|
||||
assertEquals('Focus should be on email input',
|
||||
getEmailInput(),
|
||||
dialog.dom.getActiveElement());
|
||||
}
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that the display text is autogenerated from the url input in the
|
||||
* right situations (and not generated when appropriate too).
|
||||
*/
|
||||
function testAutogeneration() {
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor('', '', true);
|
||||
createAndShow();
|
||||
|
||||
// Simulate typing a url when everything is empty, should autogen.
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
assertEquals('Display text should have been autogenerated',
|
||||
ANCHOR_URL,
|
||||
getDisplayInputText());
|
||||
|
||||
// Simulate typing text when url is set, afterwards should not autogen.
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setUrlInputText(ANCHOR_MAILTO);
|
||||
assertNotEquals('Display text should not have been autogenerated',
|
||||
ANCHOR_MAILTO,
|
||||
getDisplayInputText());
|
||||
assertEquals('Display text should have remained the same',
|
||||
ANCHOR_TEXT,
|
||||
getDisplayInputText());
|
||||
|
||||
// Simulate typing text equal to existing url, afterwards should autogen.
|
||||
setDisplayInputText(ANCHOR_MAILTO);
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
assertEquals('Display text should have been autogenerated',
|
||||
ANCHOR_URL,
|
||||
getDisplayInputText());
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that the display text is not autogenerated from the url input in all
|
||||
* situations when the autogeneration feature is turned off.
|
||||
*/
|
||||
function testAutogenerationOff() {
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
setUpAnchor('', '', true);
|
||||
createAndShow();
|
||||
|
||||
// Disable the autogen feature
|
||||
dialog.setAutogenFeatureEnabled(false);
|
||||
|
||||
// Simulate typing a url when everything is empty, should not autogen.
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
assertEquals('Display text should not have been autogenerated',
|
||||
'',
|
||||
getDisplayInputText());
|
||||
|
||||
// Simulate typing text when url is set, afterwards should not autogen.
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setUrlInputText(ANCHOR_MAILTO);
|
||||
assertNotEquals('Display text should not have been autogenerated',
|
||||
ANCHOR_MAILTO,
|
||||
getDisplayInputText());
|
||||
assertEquals('Display text should have remained the same',
|
||||
ANCHOR_TEXT,
|
||||
getDisplayInputText());
|
||||
|
||||
// Simulate typing text equal to existing url, afterwards should not
|
||||
// autogen.
|
||||
setDisplayInputText(ANCHOR_MAILTO);
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
assertEquals('Display text should not have been autogenerated',
|
||||
ANCHOR_MAILTO,
|
||||
getDisplayInputText());
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that clicking OK with the url tab selected dispatches an event with
|
||||
* the proper link data.
|
||||
*/
|
||||
function testOkForUrl() {
|
||||
expectOk(ANCHOR_TEXT, ANCHOR_URL);
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor('', '', true);
|
||||
createAndShow();
|
||||
|
||||
dialog.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that clicking OK with the url tab selected but with an email address
|
||||
* in the url field dispatches an event with the proper link data.
|
||||
*/
|
||||
function testOkForUrlWithEmail() {
|
||||
expectOk(ANCHOR_TEXT, ANCHOR_MAILTO);
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor('', '', true);
|
||||
createAndShow();
|
||||
|
||||
dialog.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setUrlInputText(ANCHOR_EMAIL);
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that clicking OK with the email tab selected dispatches an event with
|
||||
* the proper link data.
|
||||
*/
|
||||
function testOkForEmail() {
|
||||
expectOk(ANCHOR_TEXT, ANCHOR_MAILTO);
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor('', '', true);
|
||||
createAndShow();
|
||||
|
||||
dialog.tabPane_.setSelectedTabId(
|
||||
goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_TAB);
|
||||
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setEmailInputText(ANCHOR_EMAIL);
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
function testOpenLinkInNewWindowNewLink() {
|
||||
expectOk(ANCHOR_TEXT, ANCHOR_URL, true);
|
||||
expectOk(ANCHOR_TEXT, ANCHOR_URL, false);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
setUpAnchor('', '', true);
|
||||
createAndShow(undefined, true);
|
||||
dialog.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
|
||||
assertFalse('"Open in new window" should start unchecked',
|
||||
getOpenInNewWindowCheckboxChecked());
|
||||
setOpenInNewWindowCheckboxChecked(true);
|
||||
assertTrue('"Open in new window" should have gotten checked',
|
||||
getOpenInNewWindowCheckboxChecked());
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
|
||||
// Reopen same dialog
|
||||
dialog.show();
|
||||
dialog.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
|
||||
assertTrue('"Open in new window" should remember it was checked',
|
||||
getOpenInNewWindowCheckboxChecked());
|
||||
setOpenInNewWindowCheckboxChecked(false);
|
||||
assertFalse('"Open in new window" should have gotten unchecked',
|
||||
getOpenInNewWindowCheckboxChecked());
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
}
|
||||
|
||||
function testOpenLinkInNewWindowExistingLink() {
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
// Edit an existing link that already opens in a new window.
|
||||
setUpAnchor('', '', false, '_blank');
|
||||
createAndShow(undefined, true);
|
||||
dialog.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
|
||||
assertTrue('"Open in new window" should start checked for existing link',
|
||||
getOpenInNewWindowCheckboxChecked());
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
function testRelNoFollowNewLink() {
|
||||
expectOk(ANCHOR_TEXT, ANCHOR_URL, null, true);
|
||||
expectOk(ANCHOR_TEXT, ANCHOR_URL, null, false);
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
setUpAnchor('', '', true, true);
|
||||
createAndShow(null, null, true);
|
||||
dialog.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
assertFalse('rel=nofollow should start unchecked',
|
||||
dialog.relNoFollowCheckbox_.checked);
|
||||
|
||||
// Check rel=nofollow and close the dialog.
|
||||
dialog.relNoFollowCheckbox_.checked = true;
|
||||
goog.testing.events.fireClickSequence(dialog.getOkButtonElement());
|
||||
|
||||
// Reopen the same dialog.
|
||||
anchorElem.rel = 'foo nofollow bar';
|
||||
dialog.show();
|
||||
dialog.tabPane_.setSelectedTabId(goog.ui.editor.LinkDialog.Id_.ON_WEB_TAB);
|
||||
setDisplayInputText(ANCHOR_TEXT);
|
||||
setUrlInputText(ANCHOR_URL);
|
||||
assertTrue('rel=nofollow should start checked when reopening the dialog',
|
||||
dialog.relNoFollowCheckbox_.checked);
|
||||
}
|
||||
|
||||
function testRelNoFollowExistingLink() {
|
||||
mockCtrl.$replayAll();
|
||||
|
||||
setUpAnchor('', '', null, null, 'foo nofollow bar');
|
||||
createAndShow(null, null, true);
|
||||
assertTrue('rel=nofollow should start checked for existing link',
|
||||
dialog.relNoFollowCheckbox_.checked);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that clicking on the test button opens a new window with the correct
|
||||
* options.
|
||||
*/
|
||||
function testWebTestButton() {
|
||||
if (goog.userAgent.GECKO) {
|
||||
// TODO(robbyw): Figure out why this is flaky and fix it.
|
||||
return;
|
||||
}
|
||||
|
||||
var width, height;
|
||||
mockWindowOpen(ANCHOR_URL, '_blank',
|
||||
new goog.testing.mockmatchers.ArgumentMatcher(function(str) {
|
||||
return str == 'width=' + width + ',height=' + height +
|
||||
',toolbar=1,scrollbars=1,location=1,statusbar=0,' +
|
||||
'menubar=1,resizable=1';
|
||||
}, '3rd arg: (string) window.open() options'));
|
||||
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor(ANCHOR_URL, ANCHOR_TEXT);
|
||||
createAndShow();
|
||||
|
||||
// Measure viewport after opening dialog because that might cause scrollbars
|
||||
// to appear and reduce the viewport size.
|
||||
var size = goog.dom.getViewportSize(window);
|
||||
width = Math.max(size.width - 50, 50);
|
||||
height = Math.max(size.height - 50, 50);
|
||||
|
||||
var testLink = goog.testing.dom.findTextNode(
|
||||
goog.ui.editor.messages.MSG_TEST_THIS_LINK,
|
||||
dialog.dialogInternal_.getElement());
|
||||
goog.testing.events.fireClickSequence(testLink.parentNode);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that clicking on the test button does not open a new window when
|
||||
* the event is canceled.
|
||||
*/
|
||||
function testWebTestButtonPreventDefault() {
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor(ANCHOR_URL, ANCHOR_TEXT);
|
||||
createAndShow();
|
||||
|
||||
goog.events.listen(dialog,
|
||||
goog.ui.editor.LinkDialog.EventType.BEFORE_TEST_LINK,
|
||||
function(e) {
|
||||
assertEquals(e.url, ANCHOR_URL);
|
||||
e.preventDefault();
|
||||
});
|
||||
var testLink = goog.testing.dom.findTextNode(
|
||||
goog.ui.editor.messages.MSG_TEST_THIS_LINK,
|
||||
dialog.dialogInternal_.getElement());
|
||||
goog.testing.events.fireClickSequence(testLink.parentNode);
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test that the setTextToDisplayVisible() correctly works.
|
||||
* options.
|
||||
*/
|
||||
function testSetTextToDisplayVisible() {
|
||||
mockCtrl.$replayAll();
|
||||
setUpAnchor('', '', true);
|
||||
createAndShow();
|
||||
|
||||
assertNotEquals('none',
|
||||
goog.style.getStyle(dialog.textToDisplayDiv_, 'display'));
|
||||
dialog.setTextToDisplayVisible(false);
|
||||
assertEquals('none',
|
||||
goog.style.getStyle(dialog.textToDisplayDiv_, 'display'));
|
||||
dialog.setTextToDisplayVisible(true);
|
||||
assertNotEquals('none',
|
||||
goog.style.getStyle(dialog.textToDisplayDiv_, 'display'));
|
||||
|
||||
mockCtrl.$verifyAll();
|
||||
}
|
||||
|
||||
function getDisplayInput() {
|
||||
return dialog.dom.getElement(goog.ui.editor.LinkDialog.Id_.TEXT_TO_DISPLAY);
|
||||
}
|
||||
|
||||
function getDisplayInputText() {
|
||||
return getDisplayInput().value;
|
||||
}
|
||||
|
||||
function setDisplayInputText(text) {
|
||||
var textInput = getDisplayInput();
|
||||
textInput.value = text;
|
||||
// Fire event so that dialog behaves like when user types.
|
||||
fireInputEvent(textInput, goog.events.KeyCodes.M);
|
||||
}
|
||||
|
||||
function getUrlInput() {
|
||||
var elt = dialog.dom.getElement(goog.ui.editor.LinkDialog.Id_.ON_WEB_INPUT);
|
||||
assertNotNullNorUndefined('UrlInput must be found', elt);
|
||||
return elt;
|
||||
}
|
||||
|
||||
function getUrlInputText() {
|
||||
return getUrlInput().value;
|
||||
}
|
||||
|
||||
function setUrlInputText(text) {
|
||||
var urlInput = getUrlInput();
|
||||
urlInput.value = text;
|
||||
// Fire event so that dialog behaves like when user types.
|
||||
fireInputEvent(dialog.urlInputHandler_, goog.events.KeyCodes.M);
|
||||
}
|
||||
|
||||
function getEmailInput() {
|
||||
var elt = dialog.dom.getElement(
|
||||
goog.ui.editor.LinkDialog.Id_.EMAIL_ADDRESS_INPUT);
|
||||
assertNotNullNorUndefined('EmailInput must be found', elt);
|
||||
return elt;
|
||||
}
|
||||
|
||||
function getEmailInputText() {
|
||||
return getEmailInput().value;
|
||||
}
|
||||
|
||||
function setEmailInputText(text) {
|
||||
var emailInput = getEmailInput();
|
||||
emailInput.value = text;
|
||||
// Fire event so that dialog behaves like when user types.
|
||||
fireInputEvent(dialog.emailInputHandler_, goog.events.KeyCodes.M);
|
||||
}
|
||||
|
||||
function getOpenInNewWindowCheckboxChecked() {
|
||||
return dialog.openInNewWindowCheckbox_.checked;
|
||||
}
|
||||
|
||||
function setOpenInNewWindowCheckboxChecked(checked) {
|
||||
dialog.openInNewWindowCheckbox_.checked = checked;
|
||||
}
|
||||
|
||||
function fireInputEvent(input, keyCode) {
|
||||
var inputEvent = new goog.testing.events.Event(goog.events.EventType.INPUT,
|
||||
input);
|
||||
inputEvent.keyCode = keyCode;
|
||||
inputEvent.charCode = keyCode;
|
||||
goog.testing.events.fireBrowserEvent(inputEvent);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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 Messages common to Editor UI components.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.editor.messages');
|
||||
|
||||
goog.require('goog.html.uncheckedconversions');
|
||||
goog.require('goog.string.Const');
|
||||
|
||||
|
||||
/** @desc Link button / bubble caption. */
|
||||
goog.ui.editor.messages.MSG_LINK_CAPTION = goog.getMsg('Link');
|
||||
|
||||
|
||||
/** @desc Title for the dialog that edits a link. */
|
||||
goog.ui.editor.messages.MSG_EDIT_LINK = goog.getMsg('Edit Link');
|
||||
|
||||
|
||||
/** @desc Prompt the user for the text of the link they've written. */
|
||||
goog.ui.editor.messages.MSG_TEXT_TO_DISPLAY = goog.getMsg('Text to display:');
|
||||
|
||||
|
||||
/** @desc Prompt the user for the URL of the link they've created. */
|
||||
goog.ui.editor.messages.MSG_LINK_TO = goog.getMsg('Link to:');
|
||||
|
||||
|
||||
/** @desc Prompt the user to type a web address for their link. */
|
||||
goog.ui.editor.messages.MSG_ON_THE_WEB = goog.getMsg('Web address');
|
||||
|
||||
|
||||
/** @desc More details on what linking to a web address involves.. */
|
||||
goog.ui.editor.messages.MSG_ON_THE_WEB_TIP = goog.getMsg(
|
||||
'Link to a page or file somewhere else on the web');
|
||||
|
||||
|
||||
/**
|
||||
* @desc Text for a button that allows the user to test the link that
|
||||
* they created.
|
||||
*/
|
||||
goog.ui.editor.messages.MSG_TEST_THIS_LINK = goog.getMsg('Test this link');
|
||||
|
||||
|
||||
/**
|
||||
* @desc Explanation for how to create a link with the link-editing dialog.
|
||||
*/
|
||||
goog.ui.editor.messages.MSG_TR_LINK_EXPLANATION = goog.getMsg(
|
||||
'{$startBold}Not sure what to put in the box?{$endBold} ' +
|
||||
'First, find the page on the web that you want to ' +
|
||||
'link to. (A {$searchEngineLink}search engine{$endLink} ' +
|
||||
'might be useful.) Then, copy the web address from ' +
|
||||
"the box in your browser's address bar, and paste it into " +
|
||||
'the box above.',
|
||||
{'startBold': '<b>',
|
||||
'endBold': '</b>',
|
||||
'searchEngineLink': "<a href='http://www.google.com/' target='_new'>",
|
||||
'endLink': '</a>'});
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.html.SafeHtml} SafeHtml version of MSG_TR_LINK_EXPLANATION.
|
||||
*/
|
||||
goog.ui.editor.messages.getTrLinkExplanationSafeHtml = function() {
|
||||
return goog.html.uncheckedconversions.
|
||||
safeHtmlFromStringKnownToSatisfyTypeContract(
|
||||
goog.string.Const.from('Parameterless translation'),
|
||||
goog.ui.editor.messages.MSG_TR_LINK_EXPLANATION);
|
||||
};
|
||||
|
||||
|
||||
/** @desc Prompt for the URL of a link that the user is creating. */
|
||||
goog.ui.editor.messages.MSG_WHAT_URL = goog.getMsg(
|
||||
'To what URL should this link go?');
|
||||
|
||||
|
||||
/**
|
||||
* @desc Prompt for an email address, so that the user can create a link
|
||||
* that sends an email.
|
||||
*/
|
||||
goog.ui.editor.messages.MSG_EMAIL_ADDRESS = goog.getMsg('Email address');
|
||||
|
||||
|
||||
/**
|
||||
* @desc Explanation of the prompt for an email address in a link.
|
||||
*/
|
||||
goog.ui.editor.messages.MSG_EMAIL_ADDRESS_TIP = goog.getMsg(
|
||||
'Link to an email address');
|
||||
|
||||
|
||||
/** @desc Error message when the user enters an invalid email address. */
|
||||
goog.ui.editor.messages.MSG_INVALID_EMAIL = goog.getMsg(
|
||||
'Invalid email address');
|
||||
|
||||
|
||||
/**
|
||||
* @desc When the user creates a mailto link, asks them what email
|
||||
* address clicking on this link will send mail to.
|
||||
*/
|
||||
goog.ui.editor.messages.MSG_WHAT_EMAIL = goog.getMsg(
|
||||
'To what email address should this link?');
|
||||
|
||||
|
||||
/**
|
||||
* @desc Warning about the dangers of creating links with email
|
||||
* addresses in them.
|
||||
*/
|
||||
goog.ui.editor.messages.MSG_EMAIL_EXPLANATION = goog.getMsg(
|
||||
'{$preb}Be careful.{$postb} ' +
|
||||
'Remember that any time you include an email address on a web page, ' +
|
||||
'nasty spammers can find it too.', {'preb': '<b>', 'postb': '</b>'});
|
||||
|
||||
|
||||
/**
|
||||
* @return {!goog.html.SafeHtml} SafeHtml version of MSG_EMAIL_EXPLANATION.
|
||||
*/
|
||||
goog.ui.editor.messages.getEmailExplanationSafeHtml = function() {
|
||||
return goog.html.uncheckedconversions.
|
||||
safeHtmlFromStringKnownToSatisfyTypeContract(
|
||||
goog.string.Const.from('Parameterless translation'),
|
||||
goog.ui.editor.messages.MSG_EMAIL_EXPLANATION);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @desc Label for the checkbox that allows the user to specify what when this
|
||||
* link is clicked, it should be opened in a new window.
|
||||
*/
|
||||
goog.ui.editor.messages.MSG_OPEN_IN_NEW_WINDOW = goog.getMsg(
|
||||
'Open this link in a new window');
|
||||
|
||||
|
||||
/** @desc Image bubble caption. */
|
||||
goog.ui.editor.messages.MSG_IMAGE_CAPTION = goog.getMsg('Image');
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright 2010 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 Tabbed pane with style and functionality specific to
|
||||
* Editor dialogs.
|
||||
*
|
||||
* @author robbyw@google.com (Robby Walker)
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.editor.TabPane');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.dom.classlist');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.ui.Component');
|
||||
goog.require('goog.ui.Control');
|
||||
goog.require('goog.ui.Tab');
|
||||
goog.require('goog.ui.TabBar');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new Editor-style tab pane.
|
||||
* @param {goog.dom.DomHelper} dom The dom helper for the window to create this
|
||||
* tab pane in.
|
||||
* @param {string=} opt_caption Optional caption of the tab pane.
|
||||
* @constructor
|
||||
* @extends {goog.ui.Component}
|
||||
* @final
|
||||
*/
|
||||
goog.ui.editor.TabPane = function(dom, opt_caption) {
|
||||
goog.ui.editor.TabPane.base(this, 'constructor', dom);
|
||||
|
||||
/**
|
||||
* The event handler used to register events.
|
||||
* @type {goog.events.EventHandler<!goog.ui.editor.TabPane>}
|
||||
* @private
|
||||
*/
|
||||
this.eventHandler_ = new goog.events.EventHandler(this);
|
||||
this.registerDisposable(this.eventHandler_);
|
||||
|
||||
/**
|
||||
* The tab bar used to render the tabs.
|
||||
* @type {goog.ui.TabBar}
|
||||
* @private
|
||||
*/
|
||||
this.tabBar_ = new goog.ui.TabBar(goog.ui.TabBar.Location.START,
|
||||
undefined, this.dom_);
|
||||
this.tabBar_.setFocusable(false);
|
||||
|
||||
/**
|
||||
* The content element.
|
||||
* @private
|
||||
*/
|
||||
this.tabContent_ = this.dom_.createDom(goog.dom.TagName.DIV,
|
||||
{className: goog.getCssName('goog-tab-content')});
|
||||
|
||||
/**
|
||||
* The currently selected radio button.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.selectedRadio_ = null;
|
||||
|
||||
/**
|
||||
* The currently visible tab content.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.visibleContent_ = null;
|
||||
|
||||
|
||||
// Add the caption as the first element in the tab bar.
|
||||
if (opt_caption) {
|
||||
var captionControl = new goog.ui.Control(opt_caption, undefined,
|
||||
this.dom_);
|
||||
captionControl.addClassName(goog.getCssName('tr-tabpane-caption'));
|
||||
captionControl.setEnabled(false);
|
||||
this.tabBar_.addChild(captionControl, true);
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.ui.editor.TabPane, goog.ui.Component);
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} The ID of the content element for the current tab.
|
||||
*/
|
||||
goog.ui.editor.TabPane.prototype.getCurrentTabId = function() {
|
||||
return this.tabBar_.getSelectedTab().getId();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Selects the tab with the given id.
|
||||
* @param {string} id Id of the tab to select.
|
||||
*/
|
||||
goog.ui.editor.TabPane.prototype.setSelectedTabId = function(id) {
|
||||
this.tabBar_.setSelectedTab(this.tabBar_.getChild(id));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a tab to the tab pane.
|
||||
* @param {string} id The id of the tab to add.
|
||||
* @param {string} caption The caption of the tab.
|
||||
* @param {string} tooltip The tooltip for the tab.
|
||||
* @param {string} groupName for the radio button group.
|
||||
* @param {Element} content The content element to show when this tab is
|
||||
* selected.
|
||||
*/
|
||||
goog.ui.editor.TabPane.prototype.addTab = function(id, caption, tooltip,
|
||||
groupName, content) {
|
||||
var radio = this.dom_.createDom(goog.dom.TagName.INPUT,
|
||||
{
|
||||
name: groupName,
|
||||
type: 'radio'
|
||||
});
|
||||
|
||||
var tab = new goog.ui.Tab([radio, this.dom_.createTextNode(caption)],
|
||||
undefined, this.dom_);
|
||||
tab.setId(id);
|
||||
tab.setTooltip(tooltip);
|
||||
this.tabBar_.addChild(tab, true);
|
||||
|
||||
// When you navigate the radio buttons with TAB and then the Arrow keys on
|
||||
// Chrome and FF, you get a CLICK event on them, and the radio button
|
||||
// is selected. You don't get a SELECT at all. We listen for SELECT
|
||||
// nonetheless because it's possible that some browser will issue only
|
||||
// SELECT.
|
||||
this.eventHandler_.listen(radio,
|
||||
[goog.events.EventType.SELECT, goog.events.EventType.CLICK],
|
||||
goog.bind(this.tabBar_.setSelectedTab, this.tabBar_, tab));
|
||||
|
||||
content.id = id + '-tab';
|
||||
this.tabContent_.appendChild(content);
|
||||
goog.style.setElementShown(content, false);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.editor.TabPane.prototype.enterDocument = function() {
|
||||
goog.ui.editor.TabPane.base(this, 'enterDocument');
|
||||
|
||||
// Get the root element and add a class name to it.
|
||||
var root = this.getElement();
|
||||
goog.asserts.assert(root);
|
||||
goog.dom.classlist.add(root, goog.getCssName('tr-tabpane'));
|
||||
|
||||
// Add the tabs.
|
||||
this.addChild(this.tabBar_, true);
|
||||
this.eventHandler_.listen(this.tabBar_, goog.ui.Component.EventType.SELECT,
|
||||
this.handleTabSelect_);
|
||||
|
||||
// Add the tab content.
|
||||
root.appendChild(this.tabContent_);
|
||||
|
||||
// Add an element to clear the tab float.
|
||||
root.appendChild(
|
||||
this.dom_.createDom(goog.dom.TagName.DIV,
|
||||
{className: goog.getCssName('goog-tab-bar-clear')}));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles a tab change.
|
||||
* @param {goog.events.Event} e The browser change event.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.TabPane.prototype.handleTabSelect_ = function(e) {
|
||||
var tab = /** @type {goog.ui.Tab} */ (e.target);
|
||||
|
||||
// Show the tab content.
|
||||
if (this.visibleContent_) {
|
||||
goog.style.setElementShown(this.visibleContent_, false);
|
||||
}
|
||||
this.visibleContent_ = this.dom_.getElement(tab.getId() + '-tab');
|
||||
goog.style.setElementShown(this.visibleContent_, true);
|
||||
|
||||
// Select the appropriate radio button (and deselect the current one).
|
||||
if (this.selectedRadio_) {
|
||||
this.selectedRadio_.checked = false;
|
||||
}
|
||||
this.selectedRadio_ = tab.getElement().getElementsByTagName(
|
||||
goog.dom.TagName.INPUT)[0];
|
||||
this.selectedRadio_.checked = true;
|
||||
};
|
||||
@@ -0,0 +1,296 @@
|
||||
// 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)
|
||||
* @see ../../demos/editor/editor.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.editor.ToolbarController');
|
||||
|
||||
goog.require('goog.editor.Field');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.ui.Component');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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<!goog.ui.editor.ToolbarController>}
|
||||
* @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<T>} The event handler object.
|
||||
* @protected
|
||||
* @this T
|
||||
* @template T
|
||||
*/
|
||||
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());
|
||||
};
|
||||
@@ -0,0 +1,439 @@
|
||||
// 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 Generic factory functions for creating the building blocks for
|
||||
* an editor toolbar.
|
||||
*
|
||||
* @author attila@google.com (Attila Bodis)
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.editor.ToolbarFactory');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.string.Unicode');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.ui.Component');
|
||||
goog.require('goog.ui.Container');
|
||||
goog.require('goog.ui.Option');
|
||||
goog.require('goog.ui.Toolbar');
|
||||
goog.require('goog.ui.ToolbarButton');
|
||||
goog.require('goog.ui.ToolbarColorMenuButton');
|
||||
goog.require('goog.ui.ToolbarMenuButton');
|
||||
goog.require('goog.ui.ToolbarRenderer');
|
||||
goog.require('goog.ui.ToolbarSelect');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Takes a font spec (e.g. "Arial, Helvetica, sans-serif") and returns the
|
||||
* primary font name, normalized to lowercase (e.g. "arial").
|
||||
* @param {string} fontSpec Font specification.
|
||||
* @return {string} The primary font name, in lowercase.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.getPrimaryFont = function(fontSpec) {
|
||||
var i = fontSpec.indexOf(',');
|
||||
var fontName = (i != -1 ? fontSpec.substring(0, i) : fontSpec).toLowerCase();
|
||||
// Strip leading/trailing quotes from the font name (bug 1050118).
|
||||
return goog.string.stripQuotes(fontName, '"\'');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bulk-adds fonts to the given font menu button. The argument must be an
|
||||
* array of font descriptor objects, each of which must have the following
|
||||
* attributes:
|
||||
* <ul>
|
||||
* <li>{@code caption} - Caption to show in the font menu (e.g. 'Tahoma')
|
||||
* <li>{@code value} - Value for the corresponding 'font-family' CSS style
|
||||
* (e.g. 'Tahoma, Arial, sans-serif')
|
||||
* </ul>
|
||||
* @param {!goog.ui.Select} button Font menu button.
|
||||
* @param {!Array<{caption: string, value: string}>} fonts Array of
|
||||
* font descriptors.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.addFonts = function(button, fonts) {
|
||||
goog.array.forEach(fonts, function(font) {
|
||||
goog.ui.editor.ToolbarFactory.addFont(button, font.caption, font.value);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a menu item to the given font menu button. The first font listed in
|
||||
* the {@code value} argument is considered the font ID, so adding two items
|
||||
* whose CSS style starts with the same font may lead to unpredictable results.
|
||||
* @param {!goog.ui.Select} button Font menu button.
|
||||
* @param {string} caption Caption to show for the font menu.
|
||||
* @param {string} value Value for the corresponding 'font-family' CSS style.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.addFont = function(button, caption, value) {
|
||||
// The font ID is the first font listed in the CSS style, normalized to
|
||||
// lowercase.
|
||||
var id = goog.ui.editor.ToolbarFactory.getPrimaryFont(value);
|
||||
|
||||
// Construct the option, and add it to the button.
|
||||
var option = new goog.ui.Option(caption, value, button.getDomHelper());
|
||||
option.setId(id);
|
||||
button.addItem(option);
|
||||
|
||||
// Captions are shown in their own font.
|
||||
option.getContentElement().style.fontFamily = value;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bulk-adds font sizes to the given font size menu button. The argument must
|
||||
* be an array of font size descriptor objects, each of which must have the
|
||||
* following attributes:
|
||||
* <ul>
|
||||
* <li>{@code caption} - Caption to show in the font size menu (e.g. 'Huge')
|
||||
* <li>{@code value} - Value for the corresponding HTML font size (e.g. 6)
|
||||
* </ul>
|
||||
* @param {!goog.ui.Select} button Font size menu button.
|
||||
* @param {!Array<{caption: string, value:number}>} sizes Array of font
|
||||
* size descriptors.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.addFontSizes = function(button, sizes) {
|
||||
goog.array.forEach(sizes, function(size) {
|
||||
goog.ui.editor.ToolbarFactory.addFontSize(button, size.caption, size.value);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a menu item to the given font size menu button. The {@code value}
|
||||
* argument must be a legacy HTML font size in the 0-7 range.
|
||||
* @param {!goog.ui.Select} button Font size menu button.
|
||||
* @param {string} caption Caption to show in the font size menu.
|
||||
* @param {number} value Value for the corresponding HTML font size.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.addFontSize = function(button, caption, value) {
|
||||
// Construct the option, and add it to the button.
|
||||
var option = new goog.ui.Option(caption, value, button.getDomHelper());
|
||||
button.addItem(option);
|
||||
|
||||
// Adjust the font size of the menu item and the height of the checkbox
|
||||
// element after they've been rendered by addItem(). Captions are shown in
|
||||
// the corresponding font size, and lining up the checkbox is tricky.
|
||||
var content = option.getContentElement();
|
||||
content.style.fontSize =
|
||||
goog.ui.editor.ToolbarFactory.getPxFromLegacySize(value) + 'px';
|
||||
content.firstChild.style.height = '1.1em';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a legacy font size specification into an equivalent pixel size.
|
||||
* For example, {@code <font size="6">} is {@code font-size: 32px;}, etc.
|
||||
* @param {number} fontSize Legacy font size spec in the 0-7 range.
|
||||
* @return {number} Equivalent pixel size.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.getPxFromLegacySize = function(fontSize) {
|
||||
return goog.ui.editor.ToolbarFactory.LEGACY_SIZE_TO_PX_MAP_[fontSize] || 10;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a pixel font size specification into an equivalent legacy size.
|
||||
* For example, {@code font-size: 32px;} is {@code <font size="6">}, etc.
|
||||
* If the given pixel size doesn't exactly match one of the legacy sizes, -1 is
|
||||
* returned.
|
||||
* @param {number} px Pixel font size.
|
||||
* @return {number} Equivalent legacy size spec in the 0-7 range, or -1 if none
|
||||
* exists.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.getLegacySizeFromPx = function(px) {
|
||||
// Use lastIndexOf to get the largest legacy size matching the pixel size
|
||||
// (most notably returning 1 instead of 0 for 10px).
|
||||
return goog.array.lastIndexOf(
|
||||
goog.ui.editor.ToolbarFactory.LEGACY_SIZE_TO_PX_MAP_, px);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Map of legacy font sizes (0-7) to equivalent pixel sizes.
|
||||
* @type {!Array<number>}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.LEGACY_SIZE_TO_PX_MAP_ =
|
||||
[10, 10, 13, 16, 18, 24, 32, 48];
|
||||
|
||||
|
||||
/**
|
||||
* Bulk-adds format options to the given "Format block" menu button. The
|
||||
* argument must be an array of format option descriptor objects, each of
|
||||
* which must have the following attributes:
|
||||
* <ul>
|
||||
* <li>{@code caption} - Caption to show in the menu (e.g. 'Minor heading')
|
||||
* <li>{@code command} - Corresponding {@link goog.dom.TagName} (e.g.
|
||||
* 'H4')
|
||||
* </ul>
|
||||
* @param {!goog.ui.Select} button "Format block" menu button.
|
||||
* @param {!Array<{caption: string, command: goog.dom.TagName}>} formats Array
|
||||
* of format option descriptors.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.addFormatOptions = function(button, formats) {
|
||||
goog.array.forEach(formats, function(format) {
|
||||
goog.ui.editor.ToolbarFactory.addFormatOption(button, format.caption,
|
||||
format.command);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds a menu item to the given "Format block" menu button.
|
||||
* @param {!goog.ui.Select} button "Format block" menu button.
|
||||
* @param {string} caption Caption to show in the menu.
|
||||
* @param {goog.dom.TagName} tag Corresponding block format tag.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.addFormatOption = function(button, caption, tag) {
|
||||
// Construct the option, and add it to the button.
|
||||
// TODO(attila): Create boring but functional menu item for now...
|
||||
var buttonDom = button.getDomHelper();
|
||||
var option = new goog.ui.Option(buttonDom.createDom(goog.dom.TagName.DIV,
|
||||
null, caption), tag, buttonDom);
|
||||
option.setId(tag);
|
||||
button.addItem(option);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link goog.ui.Toolbar} containing the specified set of
|
||||
* toolbar buttons, and renders it into the given parent element. Each
|
||||
* item in the {@code items} array must a {@link goog.ui.Control}.
|
||||
* @param {!Array<goog.ui.Control>} items Toolbar items; each must
|
||||
* be a {@link goog.ui.Control}.
|
||||
* @param {!Element} elem Toolbar parent element.
|
||||
* @param {boolean=} opt_isRightToLeft Whether the editor chrome is
|
||||
* right-to-left; defaults to the directionality of the toolbar parent
|
||||
* element.
|
||||
* @return {!goog.ui.Toolbar} Editor toolbar, rendered into the given parent
|
||||
* element.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.makeToolbar = function(items, elem,
|
||||
opt_isRightToLeft) {
|
||||
var domHelper = goog.dom.getDomHelper(elem);
|
||||
|
||||
// Create an empty horizontal toolbar using the default renderer.
|
||||
var toolbar = new goog.ui.Toolbar(goog.ui.ToolbarRenderer.getInstance(),
|
||||
goog.ui.Container.Orientation.HORIZONTAL, domHelper);
|
||||
|
||||
// Optimization: Explicitly test for the directionality of the parent
|
||||
// element here, so we can set it for both the toolbar and its children,
|
||||
// saving a lot of expensive calls to goog.style.isRightToLeft() during
|
||||
// rendering.
|
||||
var isRightToLeft = opt_isRightToLeft || goog.style.isRightToLeft(elem);
|
||||
toolbar.setRightToLeft(isRightToLeft);
|
||||
|
||||
// Optimization: Set the toolbar to non-focusable before it is rendered,
|
||||
// to avoid creating unnecessary keyboard event handler objects.
|
||||
toolbar.setFocusable(false);
|
||||
|
||||
for (var i = 0, button; button = items[i]; i++) {
|
||||
// Optimization: Set the button to non-focusable before it is rendered,
|
||||
// to avoid creating unnecessary keyboard event handler objects. Also set
|
||||
// the directionality of the button explicitly, to avoid expensive calls
|
||||
// to goog.style.isRightToLeft() during rendering.
|
||||
button.setSupportedState(goog.ui.Component.State.FOCUSED, false);
|
||||
button.setRightToLeft(isRightToLeft);
|
||||
toolbar.addChild(button, true);
|
||||
}
|
||||
|
||||
toolbar.render(elem);
|
||||
return toolbar;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a toolbar button with the given ID, tooltip, and caption. Applies
|
||||
* any custom CSS class names to the button's caption element.
|
||||
* @param {string} id Button ID; must equal a {@link goog.editor.Command} for
|
||||
* built-in buttons, anything else for custom buttons.
|
||||
* @param {string} tooltip Tooltip to be shown on hover.
|
||||
* @param {goog.ui.ControlContent} caption Button caption.
|
||||
* @param {string=} opt_classNames CSS class name(s) to apply to the caption
|
||||
* element.
|
||||
* @param {goog.ui.ButtonRenderer=} opt_renderer Button renderer; defaults to
|
||||
* {@link goog.ui.ToolbarButtonRenderer} if unspecified.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM
|
||||
* creation; defaults to the current document if unspecified.
|
||||
* @return {!goog.ui.Button} A toolbar button.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.makeButton = function(id, tooltip, caption,
|
||||
opt_classNames, opt_renderer, opt_domHelper) {
|
||||
var button = new goog.ui.ToolbarButton(
|
||||
goog.ui.editor.ToolbarFactory.createContent_(caption, opt_classNames,
|
||||
opt_domHelper),
|
||||
opt_renderer,
|
||||
opt_domHelper);
|
||||
button.setId(id);
|
||||
button.setTooltip(tooltip);
|
||||
return button;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a toggle button with the given ID, tooltip, and caption. Applies
|
||||
* any custom CSS class names to the button's caption element. The button
|
||||
* returned has checkbox-like toggle semantics.
|
||||
* @param {string} id Button ID; must equal a {@link goog.editor.Command} for
|
||||
* built-in buttons, anything else for custom buttons.
|
||||
* @param {string} tooltip Tooltip to be shown on hover.
|
||||
* @param {goog.ui.ControlContent} caption Button caption.
|
||||
* @param {string=} opt_classNames CSS class name(s) to apply to the caption
|
||||
* element.
|
||||
* @param {goog.ui.ButtonRenderer=} opt_renderer Button renderer; defaults to
|
||||
* {@link goog.ui.ToolbarButtonRenderer} if unspecified.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM
|
||||
* creation; defaults to the current document if unspecified.
|
||||
* @return {!goog.ui.Button} A toggle button.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.makeToggleButton = function(id, tooltip, caption,
|
||||
opt_classNames, opt_renderer, opt_domHelper) {
|
||||
var button = goog.ui.editor.ToolbarFactory.makeButton(id, tooltip, caption,
|
||||
opt_classNames, opt_renderer, opt_domHelper);
|
||||
button.setSupportedState(goog.ui.Component.State.CHECKED, true);
|
||||
return button;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a menu button with the given ID, tooltip, and caption. Applies
|
||||
* any custom CSS class names to the button's caption element. The button
|
||||
* returned doesn't have an actual menu attached; use {@link
|
||||
* goog.ui.MenuButton#setMenu} to attach a {@link goog.ui.Menu} to the
|
||||
* button.
|
||||
* @param {string} id Button ID; must equal a {@link goog.editor.Command} for
|
||||
* built-in buttons, anything else for custom buttons.
|
||||
* @param {string} tooltip Tooltip to be shown on hover.
|
||||
* @param {goog.ui.ControlContent} caption Button caption.
|
||||
* @param {string=} opt_classNames CSS class name(s) to apply to the caption
|
||||
* element.
|
||||
* @param {goog.ui.ButtonRenderer=} opt_renderer Button renderer; defaults to
|
||||
* {@link goog.ui.ToolbarMenuButtonRenderer} if unspecified.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM
|
||||
* creation; defaults to the current document if unspecified.
|
||||
* @return {!goog.ui.MenuButton} A menu button.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.makeMenuButton = function(id, tooltip, caption,
|
||||
opt_classNames, opt_renderer, opt_domHelper) {
|
||||
var button = new goog.ui.ToolbarMenuButton(
|
||||
goog.ui.editor.ToolbarFactory.createContent_(caption, opt_classNames,
|
||||
opt_domHelper),
|
||||
null,
|
||||
opt_renderer,
|
||||
opt_domHelper);
|
||||
button.setId(id);
|
||||
button.setTooltip(tooltip);
|
||||
return button;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a select button with the given ID, tooltip, and caption. Applies
|
||||
* any custom CSS class names to the button's root element. The button
|
||||
* returned doesn't have an actual menu attached; use {@link
|
||||
* goog.ui.Select#setMenu} to attach a {@link goog.ui.Menu} containing
|
||||
* {@link goog.ui.Option}s to the select button.
|
||||
* @param {string} id Button ID; must equal a {@link goog.editor.Command} for
|
||||
* built-in buttons, anything else for custom buttons.
|
||||
* @param {string} tooltip Tooltip to be shown on hover.
|
||||
* @param {goog.ui.ControlContent} caption Button caption; used as the
|
||||
* default caption when nothing is selected.
|
||||
* @param {string=} opt_classNames CSS class name(s) to apply to the button's
|
||||
* root element.
|
||||
* @param {goog.ui.MenuButtonRenderer=} opt_renderer Button renderer;
|
||||
* defaults to {@link goog.ui.ToolbarMenuButtonRenderer} if unspecified.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM
|
||||
* creation; defaults to the current document if unspecified.
|
||||
* @return {!goog.ui.Select} A select button.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.makeSelectButton = function(id, tooltip, caption,
|
||||
opt_classNames, opt_renderer, opt_domHelper) {
|
||||
var button = new goog.ui.ToolbarSelect(null, null,
|
||||
opt_renderer,
|
||||
opt_domHelper);
|
||||
if (opt_classNames) {
|
||||
// Unlike the other button types, for goog.ui.Select buttons we apply the
|
||||
// extra class names to the root element, because for select buttons the
|
||||
// caption isn't stable (as it changes each time the selection changes).
|
||||
goog.array.forEach(opt_classNames.split(/\s+/), button.addClassName,
|
||||
button);
|
||||
}
|
||||
button.addClassName(goog.getCssName('goog-toolbar-select'));
|
||||
button.setDefaultCaption(caption);
|
||||
button.setId(id);
|
||||
button.setTooltip(tooltip);
|
||||
return button;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a color menu button with the given ID, tooltip, and caption.
|
||||
* Applies any custom CSS class names to the button's caption element. The
|
||||
* button is created with a default color menu containing standard color
|
||||
* palettes.
|
||||
* @param {string} id Button ID; must equal a {@link goog.editor.Command} for
|
||||
* built-in toolbar buttons, but can be anything else for custom buttons.
|
||||
* @param {string} tooltip Tooltip to be shown on hover.
|
||||
* @param {goog.ui.ControlContent} caption Button caption.
|
||||
* @param {string=} opt_classNames CSS class name(s) to apply to the caption
|
||||
* element.
|
||||
* @param {goog.ui.ColorMenuButtonRenderer=} opt_renderer Button renderer;
|
||||
* defaults to {@link goog.ui.ToolbarColorMenuButtonRenderer}
|
||||
* if unspecified.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM
|
||||
* creation; defaults to the current document if unspecified.
|
||||
* @return {!goog.ui.ColorMenuButton} A color menu button.
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.makeColorMenuButton = function(id, tooltip,
|
||||
caption, opt_classNames, opt_renderer, opt_domHelper) {
|
||||
var button = new goog.ui.ToolbarColorMenuButton(
|
||||
goog.ui.editor.ToolbarFactory.createContent_(caption, opt_classNames,
|
||||
opt_domHelper),
|
||||
null,
|
||||
opt_renderer,
|
||||
opt_domHelper);
|
||||
button.setId(id);
|
||||
button.setTooltip(tooltip);
|
||||
return button;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DIV that wraps a button caption, optionally applying CSS
|
||||
* class names to it. Used as a helper function in button factory methods.
|
||||
* @param {goog.ui.ControlContent} caption Button caption.
|
||||
* @param {string=} opt_classNames CSS class name(s) to apply to the DIV that
|
||||
* wraps the caption (if any).
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper DOM helper, used for DOM
|
||||
* creation; defaults to the current document if unspecified.
|
||||
* @return {!Element} DIV that wraps the caption.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.editor.ToolbarFactory.createContent_ = function(caption, opt_classNames,
|
||||
opt_domHelper) {
|
||||
// FF2 doesn't like empty DIVs, especially when rendered right-to-left.
|
||||
if ((!caption || caption == '') && goog.userAgent.GECKO &&
|
||||
!goog.userAgent.isVersionOrHigher('1.9a')) {
|
||||
caption = goog.string.Unicode.NBSP;
|
||||
}
|
||||
return (opt_domHelper || goog.dom.getDomHelper()).createDom(
|
||||
goog.dom.TagName.DIV,
|
||||
opt_classNames ? {'class' : opt_classNames} : null, caption);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
All Rights Reserved.
|
||||
|
||||
@author jparent@google.com (Julie Parent)
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.ui.editor.ToolbarFactory
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.ui.editor.ToolbarFactoryTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="myField">foo</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2010 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.
|
||||
|
||||
goog.provide('goog.ui.editor.ToolbarFactoryTest');
|
||||
goog.setTestOnly('goog.ui.editor.ToolbarFactoryTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.testing.ExpectedFailures');
|
||||
goog.require('goog.testing.editor.TestHelper');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.ui.editor.ToolbarFactory');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
var helper;
|
||||
var expectedFailures;
|
||||
|
||||
function setUpPage() {
|
||||
helper = new goog.testing.editor.TestHelper(goog.dom.getElement('myField'));
|
||||
expectedFailures = new goog.testing.ExpectedFailures();
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
helper.setUpEditableElement();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
helper.tearDownEditableElement();
|
||||
expectedFailures.handleTearDown();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Makes sure we have the correct conversion table in
|
||||
* goog.ui.editor.ToolbarFactory.LEGACY_SIZE_TO_PX_MAP_. Can only be tested in
|
||||
* a browser that takes legacy size values as input to execCommand but returns
|
||||
* pixel size values from queryCommandValue. That's OK because that's the only
|
||||
* situation where this conversion table's precision is critical. (When it's
|
||||
* used to size the labels of the font size menu options it's ok if it's a few
|
||||
* pixels off.)
|
||||
*/
|
||||
function testGetLegacySizeFromPx() {
|
||||
// We will be warned if other browsers start behaving like webkit pre-534.7.
|
||||
expectedFailures.expectFailureFor(
|
||||
!goog.userAgent.WEBKIT ||
|
||||
(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('534.7')));
|
||||
try {
|
||||
var fieldElem = goog.dom.getElement('myField');
|
||||
// Start from 1 because size 0 is bogus (becomes 16px, legacy size 3).
|
||||
for (var i = 1; i <
|
||||
goog.ui.editor.ToolbarFactory.LEGACY_SIZE_TO_PX_MAP_.length; i++) {
|
||||
helper.select(fieldElem, 0, fieldElem, 1);
|
||||
document.execCommand('fontSize', false, i);
|
||||
helper.select('foo', 1);
|
||||
var value = document.queryCommandValue('fontSize');
|
||||
assertEquals('Px size ' + value + ' should convert to legacy size ' + i,
|
||||
i, goog.ui.editor.ToolbarFactory.getLegacySizeFromPx(
|
||||
parseInt(value, 10)));
|
||||
}
|
||||
} catch (e) {
|
||||
expectedFailures.handleException(e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user