Adding float-no-zero branch hosted build

This commit is contained in:
ahocevar
2014-03-07 10:55:12 +01:00
parent 84cad42f6d
commit bd9092199b
1664 changed files with 731463 additions and 0 deletions
@@ -0,0 +1,440 @@
// 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.
*
*/
goog.provide('goog.ui.editor.AbstractDialog');
goog.provide('goog.ui.editor.AbstractDialog.Builder');
goog.provide('goog.ui.editor.AbstractDialog.EventType');
goog.require('goog.dom');
goog.require('goog.dom.classes');
goog.require('goog.events.EventTarget');
goog.require('goog.string');
goog.require('goog.ui.Dialog');
// *** 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_.addEventListener(goog.ui.Dialog.EventType.AFTER_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.classes.add(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_.addEventListener(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,525 @@
// 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)
* @author tildahl@google.com (Michael Tildahl)
*/
goog.provide('goog.ui.editor.Bubble');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.ViewportSizeMonitor');
goog.require('goog.dom.classes');
goog.require('goog.editor.style');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
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.base(this);
/**
* 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}
* @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': '&nbsp;'
});
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.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} The event handler.
* @protected
*/
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();
}
};
/**
* 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.classes.add(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.classes.remove(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.classes.remove(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);
/**
* 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, goog.ui.editor.Bubble.MARGIN_BOX_, overflow);
};
/**
* 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 + ':'), // TODO(robbyw): Does this work properly 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);
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,123 @@
// Copyright 2011 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.EquationEditorDialog');
goog.require('goog.editor.Command');
goog.require('goog.ui.Dialog');
goog.require('goog.ui.editor.AbstractDialog');
goog.require('goog.ui.editor.EquationEditorOkEvent');
goog.require('goog.ui.equation.TexEditor');
/**
* Equation editor dialog (based on goog.ui.editor.AbstractDialog).
* @param {Object} context The context that this dialog runs in.
* @param {goog.dom.DomHelper} domHelper DomHelper to be used to create the
* dialog's dom structure.
* @param {string} equation Initial equation.
* @param {string} helpUrl URL pointing to help documentation.
* @constructor
* @extends {goog.ui.editor.AbstractDialog}
*/
goog.ui.editor.EquationEditorDialog = function(context, domHelper,
equation, helpUrl) {
goog.ui.editor.AbstractDialog.call(this, domHelper);
this.equationEditor_ =
new goog.ui.equation.TexEditor(context, helpUrl, domHelper);
this.equationEditor_.render();
this.equationEditor_.setEquation(equation);
this.equationEditor_.addEventListener(goog.editor.Command.EQUATION,
this.onChange_, false, this);
};
goog.inherits(goog.ui.editor.EquationEditorDialog,
goog.ui.editor.AbstractDialog);
/**
* The equation editor actual UI.
* @type {goog.ui.equation.TexEditor}
* @private
*/
goog.ui.editor.EquationEditorDialog.prototype.equationEditor_;
/**
* The dialog's OK button element.
* @type {Element?}
* @private
*/
goog.ui.editor.EquationEditorDialog.prototype.okButton_;
/** @override */
goog.ui.editor.EquationEditorDialog.prototype.createDialogControl =
function() {
var builder = new goog.ui.editor.AbstractDialog.Builder(this);
/**
* @desc The title of the equation editor dialog.
*/
var MSG_EE_DIALOG_TITLE = goog.getMsg('Equation Editor');
/**
* @desc Button label for the equation editor dialog for adding
* a new equation.
*/
var MSG_EE_BUTTON_SAVE_NEW = goog.getMsg('Insert equation');
/**
* @desc Button label for the equation editor dialog for saving
* a modified equation.
*/
var MSG_EE_BUTTON_SAVE_MODIFY = goog.getMsg('Save changes');
var okButtonText = this.equationEditor_.getEquation() ?
MSG_EE_BUTTON_SAVE_MODIFY : MSG_EE_BUTTON_SAVE_NEW;
builder.setTitle(MSG_EE_DIALOG_TITLE)
.setContent(this.equationEditor_.getElement())
.addOkButton(okButtonText)
.addCancelButton();
return builder.build();
};
/**
* @override
*/
goog.ui.editor.EquationEditorDialog.prototype.createOkEvent = function(e) {
if (this.equationEditor_.isValid()) {
// Equation is not valid, don't close the dialog.
return null;
}
var equationHtml = this.equationEditor_.getHtml();
return new goog.ui.editor.EquationEditorOkEvent(equationHtml);
};
/**
* Handles CHANGE event fired when user changes equation.
* @param {goog.ui.equation.ChangeEvent} e The event object.
* @private
*/
goog.ui.editor.EquationEditorDialog.prototype.onChange_ = function(e) {
if (!this.okButton_) {
this.okButton_ = this.getButtonElement(
goog.ui.Dialog.DefaultButtonKeys.OK);
}
this.okButton_.disabled = !e.isValid;
};
@@ -0,0 +1,49 @@
// Copyright 2011 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.EquationEditorOkEvent');
goog.require('goog.events.Event');
goog.require('goog.ui.editor.AbstractDialog');
/**
* OK event object for the equation editor dialog.
* @param {string} equationHtml html containing the equation to put in the
* editable field.
* @constructor
* @extends {goog.events.Event}
*/
goog.ui.editor.EquationEditorOkEvent = function(equationHtml) {
this.equationHtml = equationHtml;
};
goog.inherits(goog.ui.editor.EquationEditorOkEvent,
goog.events.Event);
/**
* Event type.
* @type {goog.ui.editor.AbstractDialog.EventType}
* @override
*/
goog.ui.editor.EquationEditorOkEvent.prototype.type =
goog.ui.editor.AbstractDialog.EventType.OK;
/**
* HTML containing the equation to put in the editable field.
* @type {string}
*/
goog.ui.editor.EquationEditorOkEvent.prototype.equationHtml;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,123 @@
// 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');
/** @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>'});
/** @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>'});
/**
* @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,198 @@
// 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.dom.TagName');
goog.require('goog.dom.classes');
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}
*/
goog.ui.editor.TabPane = function(dom, opt_caption) {
goog.base(this, dom);
/**
* The event handler used to register events.
* @type {goog.events.EventHandler}
* @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.base(this, 'enterDocument');
// Get the root element and add a class name to it.
var root = this.getElement();
goog.dom.classes.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,295 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A class for managing the editor toolbar.
*
* @author attila@google.com (Attila Bodis)
* @author jparent@google.com (Julie Parent)
* @see ../../demos/editor/editor.html
*/
goog.provide('goog.ui.editor.ToolbarController');
goog.require('goog.editor.Field');
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}
* @private
*/
this.handler_ = new goog.events.EventHandler(this);
/**
* The field instance controlled by the toolbar.
* @type {!goog.editor.Field}
* @private
*/
this.field_ = field;
/**
* The toolbar that controls the field.
* @type {!goog.ui.Toolbar}
* @private
*/
this.toolbar_ = toolbar;
/**
* Editing commands whose state is to be queried when updating the toolbar.
* @type {!Array.<string>}
* @private
*/
this.queryCommands_ = [];
// Iterate over all buttons, and find those which correspond to
// queryable commands. Add them to the list of commands to query on
// each COMMAND_VALUE_CHANGE event.
this.toolbar_.forEachChild(function(button) {
if (button.queryable) {
this.queryCommands_.push(this.getComponentId(button.getId()));
}
}, this);
// Make sure the toolbar doesn't steal keyboard focus.
this.toolbar_.setFocusable(false);
// Hook up handlers that update the toolbar in response to field events,
// and to execute editor commands in response to toolbar events.
this.handler_.
listen(this.field_, goog.editor.Field.EventType.COMMAND_VALUE_CHANGE,
this.updateToolbar).
listen(this.toolbar_, goog.ui.Component.EventType.ACTION,
this.handleAction);
};
goog.inherits(goog.ui.editor.ToolbarController, goog.events.EventTarget);
/**
* Returns the Closure component ID of the control that corresponds to the
* given {@link goog.editor.Command} constant.
* Subclasses may override this method if they want to use a custom mapping
* scheme from commands to controls.
* @param {string} command Editor command.
* @return {string} Closure component ID of the corresponding toolbar
* control, if any.
* @protected
*/
goog.ui.editor.ToolbarController.prototype.getComponentId = function(command) {
// The default implementation assumes that the component ID is the same as
// the command constant.
return command;
};
/**
* Returns the {@link goog.editor.Command} constant
* that corresponds to the given Closure component ID. Subclasses may override
* this method if they want to use a custom mapping scheme from controls to
* commands.
* @param {string} id Closure component ID of a toolbar control.
* @return {string} Editor command or dialog constant corresponding to the
* toolbar control, if any.
* @protected
*/
goog.ui.editor.ToolbarController.prototype.getCommand = function(id) {
// The default implementation assumes that the component ID is the same as
// the command constant.
return id;
};
/**
* Returns the event handler object for the editor toolbar. Useful for classes
* that extend {@code goog.ui.editor.ToolbarController}.
* @return {!goog.events.EventHandler} The event handler object.
* @protected
*/
goog.ui.editor.ToolbarController.prototype.getHandler = function() {
return this.handler_;
};
/**
* Returns the field instance managed by the toolbar. Useful for
* classes that extend {@code goog.ui.editor.ToolbarController}.
* @return {!goog.editor.Field} The field managed by the toolbar.
* @protected
*/
goog.ui.editor.ToolbarController.prototype.getField = function() {
return this.field_;
};
/**
* Returns the toolbar UI component that manages the editor. Useful for
* classes that extend {@code goog.ui.editor.ToolbarController}.
* @return {!goog.ui.Toolbar} The toolbar UI component.
*/
goog.ui.editor.ToolbarController.prototype.getToolbar = function() {
return this.toolbar_;
};
/**
* @return {boolean} Whether the toolbar is visible.
*/
goog.ui.editor.ToolbarController.prototype.isVisible = function() {
return this.toolbar_.isVisible();
};
/**
* Shows or hides the toolbar.
* @param {boolean} visible Whether to show or hide the toolbar.
*/
goog.ui.editor.ToolbarController.prototype.setVisible = function(visible) {
this.toolbar_.setVisible(visible);
};
/**
* @return {boolean} Whether the toolbar is enabled.
*/
goog.ui.editor.ToolbarController.prototype.isEnabled = function() {
return this.toolbar_.isEnabled();
};
/**
* Enables or disables the toolbar.
* @param {boolean} enabled Whether to enable or disable the toolbar.
*/
goog.ui.editor.ToolbarController.prototype.setEnabled = function(enabled) {
this.toolbar_.setEnabled(enabled);
};
/**
* Programmatically blurs the editor toolbar, un-highlighting the currently
* highlighted item, and closing the currently open menu (if any).
*/
goog.ui.editor.ToolbarController.prototype.blur = function() {
// We can't just call this.toolbar_.getElement().blur(), because the toolbar
// element itself isn't focusable, so goog.ui.Container#handleBlur isn't
// registered to handle blur events.
this.toolbar_.handleBlur(null);
};
/** @override */
goog.ui.editor.ToolbarController.prototype.disposeInternal = function() {
goog.ui.editor.ToolbarController.superClass_.disposeInternal.call(this);
if (this.handler_) {
this.handler_.dispose();
delete this.handler_;
}
if (this.toolbar_) {
this.toolbar_.dispose();
delete this.toolbar_;
}
delete this.field_;
delete this.queryCommands_;
};
/**
* Updates the toolbar in response to editor events. Specifically, updates
* button states based on {@code COMMAND_VALUE_CHANGE} events, reflecting the
* effective formatting of the selection.
* @param {goog.events.Event} e Editor event to handle.
* @protected
*/
goog.ui.editor.ToolbarController.prototype.updateToolbar = function(e) {
if (!this.toolbar_.isEnabled() ||
!this.dispatchEvent(goog.ui.Component.EventType.CHANGE)) {
return;
}
var state;
/** @preserveTry */
try {
/** @type {Array.<string>} */
e.commands; // Added by dispatchEvent.
// If the COMMAND_VALUE_CHANGE event specifies which commands changed
// state, then we only need to update those ones, otherwise update all
// commands.
state = /** @type {Object} */ (
this.field_.queryCommandValue(e.commands || this.queryCommands_));
} catch (ex) {
// TODO(attila): Find out when/why this happens.
state = {};
}
this.updateToolbarFromState(state);
};
/**
* Updates the toolbar to reflect a given state.
* @param {Object} state Object mapping editor commands to values.
*/
goog.ui.editor.ToolbarController.prototype.updateToolbarFromState =
function(state) {
for (var command in state) {
var button = this.toolbar_.getChild(this.getComponentId(command));
if (button) {
var value = state[command];
if (button.updateFromValue) {
button.updateFromValue(value);
} else {
button.setChecked(!!value);
}
}
}
};
/**
* Handles {@code ACTION} events dispatched by toolbar buttons in response to
* user actions by executing the corresponding field command.
* @param {goog.events.Event} e Action event to handle.
* @protected
*/
goog.ui.editor.ToolbarController.prototype.handleAction = function(e) {
var command = this.getCommand(e.target.getId());
this.field_.execCommand(command, e.target.getValue());
};
@@ -0,0 +1,440 @@
// 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)
* @author jparent@google.com (Julie Parent)
*/
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 &lt;font size="6"&gt;} 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 &lt;font size="6"&gt;}, 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);
};