You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flex.apache.org by ft...@apache.org on 2015/09/17 17:28:35 UTC
[20/51] [abbrv] [partial] git commit: [flex-falcon]
[refs/heads/JsToAs] - Added GCL extern.
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/range.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/range.js b/externs/GCL/externs/goog/editor/range.js
new file mode 100644
index 0000000..ec1a6a7
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/range.js
@@ -0,0 +1,632 @@
+// 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 Utilties for working with ranges.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+
+goog.provide('goog.editor.range');
+goog.provide('goog.editor.range.Point');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.Range');
+goog.require('goog.dom.RangeEndpoint');
+goog.require('goog.dom.SavedCaretRange');
+goog.require('goog.editor.node');
+goog.require('goog.editor.style');
+goog.require('goog.iter');
+goog.require('goog.userAgent');
+
+
+/**
+ * Given a range and an element, create a narrower range that is limited to the
+ * boundaries of the element. If the range starts (or ends) outside the
+ * element, the narrowed range's start point (or end point) will be the
+ * leftmost (or rightmost) leaf of the element.
+ * @param {goog.dom.AbstractRange} range The range.
+ * @param {Element} el The element to limit the range to.
+ * @return {goog.dom.AbstractRange} A new narrowed range, or null if the
+ * element does not contain any part of the given range.
+ */
+goog.editor.range.narrow = function(range, el) {
+ var startContainer = range.getStartNode();
+ var endContainer = range.getEndNode();
+
+ if (startContainer && endContainer) {
+ var isElement = function(node) {
+ return node == el;
+ };
+ var hasStart = goog.dom.getAncestor(startContainer, isElement, true);
+ var hasEnd = goog.dom.getAncestor(endContainer, isElement, true);
+
+ if (hasStart && hasEnd) {
+ // The range is contained entirely within this element.
+ return range.clone();
+ } else if (hasStart) {
+ // The range starts inside the element, but ends outside it.
+ var leaf = goog.editor.node.getRightMostLeaf(el);
+ return goog.dom.Range.createFromNodes(
+ range.getStartNode(), range.getStartOffset(),
+ leaf, goog.editor.node.getLength(leaf));
+ } else if (hasEnd) {
+ // The range starts outside the element, but ends inside it.
+ return goog.dom.Range.createFromNodes(
+ goog.editor.node.getLeftMostLeaf(el), 0,
+ range.getEndNode(), range.getEndOffset());
+ }
+ }
+
+ // The selection starts and ends outside the element.
+ return null;
+};
+
+
+/**
+ * Given a range, expand the range to include outer tags if the full contents of
+ * those tags are entirely selected. This essentially changes the dom position,
+ * but not the visible position of the range.
+ * Ex. <li>foo</li> if "foo" is selected, instead of returning start and end
+ * nodes as the foo text node, return the li.
+ * @param {goog.dom.AbstractRange} range The range.
+ * @param {Node=} opt_stopNode Optional node to stop expanding past.
+ * @return {!goog.dom.AbstractRange} The expanded range.
+ */
+goog.editor.range.expand = function(range, opt_stopNode) {
+ // Expand the start out to the common container.
+ var expandedRange = goog.editor.range.expandEndPointToContainer_(
+ range, goog.dom.RangeEndpoint.START, opt_stopNode);
+ // Expand the end out to the common container.
+ expandedRange = goog.editor.range.expandEndPointToContainer_(
+ expandedRange, goog.dom.RangeEndpoint.END, opt_stopNode);
+
+ var startNode = expandedRange.getStartNode();
+ var endNode = expandedRange.getEndNode();
+ var startOffset = expandedRange.getStartOffset();
+ var endOffset = expandedRange.getEndOffset();
+
+ // If we have reached a common container, now expand out.
+ if (startNode == endNode) {
+ while (endNode != opt_stopNode &&
+ startOffset == 0 &&
+ endOffset == goog.editor.node.getLength(endNode)) {
+ // Select the parent instead.
+ var parentNode = endNode.parentNode;
+ startOffset = goog.array.indexOf(parentNode.childNodes, endNode);
+ endOffset = startOffset + 1;
+ endNode = parentNode;
+ }
+ startNode = endNode;
+ }
+
+ return goog.dom.Range.createFromNodes(startNode, startOffset,
+ endNode, endOffset);
+};
+
+
+/**
+ * Given a range, expands the start or end points as far out towards the
+ * range's common container (or stopNode, if provided) as possible, while
+ * perserving the same visible position.
+ *
+ * @param {goog.dom.AbstractRange} range The range to expand.
+ * @param {goog.dom.RangeEndpoint} endpoint The endpoint to expand.
+ * @param {Node=} opt_stopNode Optional node to stop expanding past.
+ * @return {!goog.dom.AbstractRange} The expanded range.
+ * @private
+ */
+goog.editor.range.expandEndPointToContainer_ = function(range, endpoint,
+ opt_stopNode) {
+ var expandStart = endpoint == goog.dom.RangeEndpoint.START;
+ var node = expandStart ? range.getStartNode() : range.getEndNode();
+ var offset = expandStart ? range.getStartOffset() : range.getEndOffset();
+ var container = range.getContainerElement();
+
+ // Expand the node out until we reach the container or the stop node.
+ while (node != container && node != opt_stopNode) {
+ // It is only valid to expand the start if we are at the start of a node
+ // (offset 0) or expand the end if we are at the end of a node
+ // (offset length).
+ if (expandStart && offset != 0 ||
+ !expandStart && offset != goog.editor.node.getLength(node)) {
+ break;
+ }
+
+ var parentNode = node.parentNode;
+ var index = goog.array.indexOf(parentNode.childNodes, node);
+ offset = expandStart ? index : index + 1;
+ node = parentNode;
+ }
+
+ return goog.dom.Range.createFromNodes(
+ expandStart ? node : range.getStartNode(),
+ expandStart ? offset : range.getStartOffset(),
+ expandStart ? range.getEndNode() : node,
+ expandStart ? range.getEndOffset() : offset);
+};
+
+
+/**
+ * Cause the window's selection to be the start of this node.
+ * @param {Node} node The node to select the start of.
+ */
+goog.editor.range.selectNodeStart = function(node) {
+ goog.dom.Range.createCaret(goog.editor.node.getLeftMostLeaf(node), 0).
+ select();
+};
+
+
+/**
+ * Position the cursor immediately to the left or right of "node".
+ * In Firefox, the selection parent is outside of "node", so the cursor can
+ * effectively be moved to the end of a link node, without being considered
+ * inside of it.
+ * Note: This does not always work in WebKit. In particular, if you try to
+ * place a cursor to the right of a link, typing still puts you in the link.
+ * Bug: http://bugs.webkit.org/show_bug.cgi?id=17697
+ * @param {Node} node The node to position the cursor relative to.
+ * @param {boolean} toLeft True to place it to the left, false to the right.
+ * @return {!goog.dom.AbstractRange} The newly selected range.
+ */
+goog.editor.range.placeCursorNextTo = function(node, toLeft) {
+ var parent = node.parentNode;
+ var offset = goog.array.indexOf(parent.childNodes, node) +
+ (toLeft ? 0 : 1);
+ var point = goog.editor.range.Point.createDeepestPoint(
+ parent, offset, toLeft, true);
+ var range = goog.dom.Range.createCaret(point.node, point.offset);
+ range.select();
+ return range;
+};
+
+
+/**
+ * Normalizes the node, preserving the selection of the document.
+ *
+ * May also normalize things outside the node, if it is more efficient to do so.
+ *
+ * @param {Node} node The node to normalize.
+ */
+goog.editor.range.selectionPreservingNormalize = function(node) {
+ var doc = goog.dom.getOwnerDocument(node);
+ var selection = goog.dom.Range.createFromWindow(goog.dom.getWindow(doc));
+ var normalizedRange =
+ goog.editor.range.rangePreservingNormalize(node, selection);
+ if (normalizedRange) {
+ normalizedRange.select();
+ }
+};
+
+
+/**
+ * Manually normalizes the node in IE, since native normalize in IE causes
+ * transient problems.
+ * @param {Node} node The node to normalize.
+ * @private
+ */
+goog.editor.range.normalizeNodeIe_ = function(node) {
+ var lastText = null;
+ var child = node.firstChild;
+ while (child) {
+ var next = child.nextSibling;
+ if (child.nodeType == goog.dom.NodeType.TEXT) {
+ if (child.nodeValue == '') {
+ node.removeChild(child);
+ } else if (lastText) {
+ lastText.nodeValue += child.nodeValue;
+ node.removeChild(child);
+ } else {
+ lastText = child;
+ }
+ } else {
+ goog.editor.range.normalizeNodeIe_(child);
+ lastText = null;
+ }
+ child = next;
+ }
+};
+
+
+/**
+ * Normalizes the given node.
+ * @param {Node} node The node to normalize.
+ */
+goog.editor.range.normalizeNode = function(node) {
+ if (goog.userAgent.IE) {
+ goog.editor.range.normalizeNodeIe_(node);
+ } else {
+ node.normalize();
+ }
+};
+
+
+/**
+ * Normalizes the node, preserving a range of the document.
+ *
+ * May also normalize things outside the node, if it is more efficient to do so.
+ *
+ * @param {Node} node The node to normalize.
+ * @param {goog.dom.AbstractRange?} range The range to normalize.
+ * @return {goog.dom.AbstractRange?} The range, adjusted for normalization.
+ */
+goog.editor.range.rangePreservingNormalize = function(node, range) {
+ if (range) {
+ var rangeFactory = goog.editor.range.normalize(range);
+ // WebKit has broken selection affinity, so carets tend to jump out of the
+ // beginning of inline elements. This means that if we're doing the
+ // normalize as the result of a range that will later become the selection,
+ // we might not normalize something in the range after it is read back from
+ // the selection. We can't just normalize the parentNode here because WebKit
+ // can move the selection range out of multiple inline parents.
+ var container = goog.editor.style.getContainer(range.getContainerElement());
+ }
+
+ if (container) {
+ goog.editor.range.normalizeNode(
+ goog.dom.findCommonAncestor(container, node));
+ } else if (node) {
+ goog.editor.range.normalizeNode(node);
+ }
+
+ if (rangeFactory) {
+ return rangeFactory();
+ } else {
+ return null;
+ }
+};
+
+
+/**
+ * Get the deepest point in the DOM that's equivalent to the endpoint of the
+ * given range.
+ *
+ * @param {goog.dom.AbstractRange} range A range.
+ * @param {boolean} atStart True for the start point, false for the end point.
+ * @return {!goog.editor.range.Point} The end point, expressed as a node
+ * and an offset.
+ */
+goog.editor.range.getDeepEndPoint = function(range, atStart) {
+ return atStart ?
+ goog.editor.range.Point.createDeepestPoint(
+ range.getStartNode(), range.getStartOffset()) :
+ goog.editor.range.Point.createDeepestPoint(
+ range.getEndNode(), range.getEndOffset());
+};
+
+
+/**
+ * Given a range in the current DOM, create a factory for a range that
+ * represents the same selection in a normalized DOM. The factory function
+ * should be invoked after the DOM is normalized.
+ *
+ * All browsers do a bad job preserving ranges across DOM normalization.
+ * The issue is best described in this 5-year-old bug report:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=191864
+ * For most applications, this isn't a problem. The browsers do a good job
+ * handling un-normalized text, so there's usually no reason to normalize.
+ *
+ * The exception to this rule is the rich text editing commands
+ * execCommand and queryCommandValue, which will fail often if there are
+ * un-normalized text nodes.
+ *
+ * The factory function creates new ranges so that we can normalize the DOM
+ * without problems. It must be created before any normalization happens,
+ * and invoked after normalization happens.
+ *
+ * @param {goog.dom.AbstractRange} range The range to normalize. It may
+ * become invalid after body.normalize() is called.
+ * @return {function(): goog.dom.AbstractRange} A factory for a normalized
+ * range. Should be called after body.normalize() is called.
+ */
+goog.editor.range.normalize = function(range) {
+ var isReversed = range.isReversed();
+ var anchorPoint = goog.editor.range.normalizePoint_(
+ goog.editor.range.getDeepEndPoint(range, !isReversed));
+ var anchorParent = anchorPoint.getParentPoint();
+ var anchorPreviousSibling = anchorPoint.node.previousSibling;
+ if (anchorPoint.node.nodeType == goog.dom.NodeType.TEXT) {
+ anchorPoint.node = null;
+ }
+
+ var focusPoint = goog.editor.range.normalizePoint_(
+ goog.editor.range.getDeepEndPoint(range, isReversed));
+ var focusParent = focusPoint.getParentPoint();
+ var focusPreviousSibling = focusPoint.node.previousSibling;
+ if (focusPoint.node.nodeType == goog.dom.NodeType.TEXT) {
+ focusPoint.node = null;
+ }
+
+ return function() {
+ if (!anchorPoint.node && anchorPreviousSibling) {
+ // If anchorPoint.node was previously an empty text node with no siblings,
+ // anchorPreviousSibling may not have a nextSibling since that node will
+ // no longer exist. Do our best and point to the end of the previous
+ // element.
+ anchorPoint.node = anchorPreviousSibling.nextSibling;
+ if (!anchorPoint.node) {
+ anchorPoint = goog.editor.range.Point.getPointAtEndOfNode(
+ anchorPreviousSibling);
+ }
+ }
+
+ if (!focusPoint.node && focusPreviousSibling) {
+ // If focusPoint.node was previously an empty text node with no siblings,
+ // focusPreviousSibling may not have a nextSibling since that node will no
+ // longer exist. Do our best and point to the end of the previous
+ // element.
+ focusPoint.node = focusPreviousSibling.nextSibling;
+ if (!focusPoint.node) {
+ focusPoint = goog.editor.range.Point.getPointAtEndOfNode(
+ focusPreviousSibling);
+ }
+ }
+
+ return goog.dom.Range.createFromNodes(
+ anchorPoint.node || anchorParent.node.firstChild || anchorParent.node,
+ anchorPoint.offset,
+ focusPoint.node || focusParent.node.firstChild || focusParent.node,
+ focusPoint.offset);
+ };
+};
+
+
+/**
+ * Given a point in the current DOM, adjust it to represent the same point in
+ * a normalized DOM.
+ *
+ * See the comments on goog.editor.range.normalize for more context.
+ *
+ * @param {goog.editor.range.Point} point A point in the document.
+ * @return {!goog.editor.range.Point} The same point, for easy chaining.
+ * @private
+ */
+goog.editor.range.normalizePoint_ = function(point) {
+ var previous;
+ if (point.node.nodeType == goog.dom.NodeType.TEXT) {
+ // If the cursor position is in a text node,
+ // look at all the previous text siblings of the text node,
+ // and set the offset relative to the earliest text sibling.
+ for (var current = point.node.previousSibling;
+ current && current.nodeType == goog.dom.NodeType.TEXT;
+ current = current.previousSibling) {
+ point.offset += goog.editor.node.getLength(current);
+ }
+
+ previous = current;
+ } else {
+ previous = point.node.previousSibling;
+ }
+
+ var parent = point.node.parentNode;
+ point.node = previous ? previous.nextSibling : parent.firstChild;
+ return point;
+};
+
+
+/**
+ * Checks if a range is completely inside an editable region.
+ * @param {goog.dom.AbstractRange} range The range to test.
+ * @return {boolean} Whether the range is completely inside an editable region.
+ */
+goog.editor.range.isEditable = function(range) {
+ var rangeContainer = range.getContainerElement();
+
+ // Closure's implementation of getContainerElement() is a little too
+ // smart in IE when exactly one element is contained in the range.
+ // It assumes that there's a user whose intent was actually to select
+ // all that element's children, so it returns the element itself as its
+ // own containing element.
+ // This little sanity check detects this condition so we can account for it.
+ var rangeContainerIsOutsideRange =
+ range.getStartNode() != rangeContainer.parentElement;
+
+ return (rangeContainerIsOutsideRange &&
+ goog.editor.node.isEditableContainer(rangeContainer)) ||
+ goog.editor.node.isEditable(rangeContainer);
+};
+
+
+/**
+ * Returns whether the given range intersects with any instance of the given
+ * tag.
+ * @param {goog.dom.AbstractRange} range The range to check.
+ * @param {goog.dom.TagName} tagName The name of the tag.
+ * @return {boolean} Whether the given range intersects with any instance of
+ * the given tag.
+ */
+goog.editor.range.intersectsTag = function(range, tagName) {
+ if (goog.dom.getAncestorByTagNameAndClass(range.getContainerElement(),
+ tagName)) {
+ return true;
+ }
+
+ return goog.iter.some(range, function(node) {
+ return node.tagName == tagName;
+ });
+};
+
+
+
+/**
+ * One endpoint of a range, represented as a Node and and offset.
+ * @param {Node} node The node containing the point.
+ * @param {number} offset The offset of the point into the node.
+ * @constructor
+ * @final
+ */
+goog.editor.range.Point = function(node, offset) {
+ /**
+ * The node containing the point.
+ * @type {Node}
+ */
+ this.node = node;
+
+ /**
+ * The offset of the point into the node.
+ * @type {number}
+ */
+ this.offset = offset;
+};
+
+
+/**
+ * Gets the point of this point's node in the DOM.
+ * @return {!goog.editor.range.Point} The node's point.
+ */
+goog.editor.range.Point.prototype.getParentPoint = function() {
+ var parent = this.node.parentNode;
+ return new goog.editor.range.Point(
+ parent, goog.array.indexOf(parent.childNodes, this.node));
+};
+
+
+/**
+ * Construct the deepest possible point in the DOM that's equivalent
+ * to the given point, expressed as a node and an offset.
+ * @param {Node} node The node containing the point.
+ * @param {number} offset The offset of the point from the node.
+ * @param {boolean=} opt_trendLeft Notice that a (node, offset) pair may be
+ * equivalent to more than one descendent (node, offset) pair in the DOM.
+ * By default, we trend rightward. If this parameter is true, then we
+ * trend leftward. The tendency to fall rightward by default is for
+ * consistency with other range APIs (like placeCursorNextTo).
+ * @param {boolean=} opt_stopOnChildlessElement If true, and we encounter
+ * a Node which is an Element that cannot have children, we return a Point
+ * based on its parent rather than that Node itself.
+ * @return {!goog.editor.range.Point} A new point.
+ */
+goog.editor.range.Point.createDeepestPoint =
+ function(node, offset, opt_trendLeft, opt_stopOnChildlessElement) {
+ while (node.nodeType == goog.dom.NodeType.ELEMENT) {
+ var child = node.childNodes[offset];
+ if (!child && !node.lastChild) {
+ break;
+ } else if (child) {
+ var prevSibling = child.previousSibling;
+ if (opt_trendLeft && prevSibling) {
+ if (opt_stopOnChildlessElement &&
+ goog.editor.range.Point.isTerminalElement_(prevSibling)) {
+ break;
+ }
+ node = prevSibling;
+ offset = goog.editor.node.getLength(node);
+ } else {
+ if (opt_stopOnChildlessElement &&
+ goog.editor.range.Point.isTerminalElement_(child)) {
+ break;
+ }
+ node = child;
+ offset = 0;
+ }
+ } else {
+ if (opt_stopOnChildlessElement &&
+ goog.editor.range.Point.isTerminalElement_(node.lastChild)) {
+ break;
+ }
+ node = node.lastChild;
+ offset = goog.editor.node.getLength(node);
+ }
+ }
+
+ return new goog.editor.range.Point(node, offset);
+};
+
+
+/**
+ * Return true if the specified node is an Element that is not expected to have
+ * children. The createDeepestPoint() method should not traverse into
+ * such elements.
+ * @param {Node} node .
+ * @return {boolean} True if the node is an Element that does not contain
+ * child nodes (e.g. BR, IMG).
+ * @private
+ */
+goog.editor.range.Point.isTerminalElement_ = function(node) {
+ return (node.nodeType == goog.dom.NodeType.ELEMENT &&
+ !goog.dom.canHaveChildren(node));
+};
+
+
+/**
+ * Construct a point at the very end of the given node.
+ * @param {Node} node The node to create a point for.
+ * @return {!goog.editor.range.Point} A new point.
+ */
+goog.editor.range.Point.getPointAtEndOfNode = function(node) {
+ return new goog.editor.range.Point(node, goog.editor.node.getLength(node));
+};
+
+
+/**
+ * Saves the range by inserting carets into the HTML.
+ *
+ * Unlike the regular saveUsingCarets, this SavedRange normalizes text nodes.
+ * Browsers have other bugs where they don't handle split text nodes in
+ * contentEditable regions right.
+ *
+ * @param {goog.dom.AbstractRange} range The abstract range object.
+ * @return {!goog.dom.SavedCaretRange} A saved caret range that normalizes
+ * text nodes.
+ */
+goog.editor.range.saveUsingNormalizedCarets = function(range) {
+ return new goog.editor.range.NormalizedCaretRange_(range);
+};
+
+
+
+/**
+ * Saves the range using carets, but normalizes text nodes when carets
+ * are removed.
+ * @see goog.editor.range.saveUsingNormalizedCarets
+ * @param {goog.dom.AbstractRange} range The range being saved.
+ * @constructor
+ * @extends {goog.dom.SavedCaretRange}
+ * @private
+ */
+goog.editor.range.NormalizedCaretRange_ = function(range) {
+ goog.dom.SavedCaretRange.call(this, range);
+};
+goog.inherits(goog.editor.range.NormalizedCaretRange_,
+ goog.dom.SavedCaretRange);
+
+
+/**
+ * Normalizes text nodes whenever carets are removed from the document.
+ * @param {goog.dom.AbstractRange=} opt_range A range whose offsets have already
+ * been adjusted for caret removal; it will be adjusted and returned if it
+ * is also affected by post-removal operations, such as text node
+ * normalization.
+ * @return {goog.dom.AbstractRange|undefined} The adjusted range, if opt_range
+ * was provided.
+ * @override
+ */
+goog.editor.range.NormalizedCaretRange_.prototype.removeCarets =
+ function(opt_range) {
+ var startCaret = this.getCaret(true);
+ var endCaret = this.getCaret(false);
+ var node = startCaret && endCaret ?
+ goog.dom.findCommonAncestor(startCaret, endCaret) :
+ startCaret || endCaret;
+
+ goog.editor.range.NormalizedCaretRange_.superClass_.removeCarets.call(this);
+
+ if (opt_range) {
+ return goog.editor.range.rangePreservingNormalize(node, opt_range);
+ } else if (node) {
+ goog.editor.range.selectionPreservingNormalize(node);
+ }
+};
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/seamlessfield.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/seamlessfield.js b/externs/GCL/externs/goog/editor/seamlessfield.js
new file mode 100644
index 0000000..7d84533
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/seamlessfield.js
@@ -0,0 +1,746 @@
+// Copyright 2006 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 Class to encapsulate an editable field that blends in with
+ * the style of the page. The field can be fixed height, grow with its
+ * contents, or have a min height after which it grows to its contents.
+ * This is a goog.editor.Field, but with blending and sizing capabilities,
+ * and avoids using an iframe whenever possible.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ * @see ../demos/editor/seamlessfield.html
+ */
+
+
+goog.provide('goog.editor.SeamlessField');
+
+goog.require('goog.cssom.iframe.style');
+goog.require('goog.dom');
+goog.require('goog.dom.Range');
+goog.require('goog.dom.TagName');
+goog.require('goog.dom.safe');
+goog.require('goog.editor.BrowserFeature');
+goog.require('goog.editor.Field');
+goog.require('goog.editor.icontent');
+goog.require('goog.editor.icontent.FieldFormatInfo');
+goog.require('goog.editor.icontent.FieldStyleInfo');
+goog.require('goog.editor.node');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.html.uncheckedconversions');
+goog.require('goog.log');
+goog.require('goog.string.Const');
+goog.require('goog.style');
+
+
+
+/**
+ * This class encapsulates an editable field that blends in with the
+ * surrounding page.
+ * To see events fired by this object, please see the base class.
+ *
+ * @param {string} id An identifer for the field. This is used to find the
+ * field and the element associated with this field.
+ * @param {Document=} opt_doc The document that the element with the given
+ * id can be found it.
+ * @constructor
+ * @extends {goog.editor.Field}
+ */
+goog.editor.SeamlessField = function(id, opt_doc) {
+ goog.editor.Field.call(this, id, opt_doc);
+};
+goog.inherits(goog.editor.SeamlessField, goog.editor.Field);
+
+
+/**
+ * @override
+ */
+goog.editor.SeamlessField.prototype.logger =
+ goog.log.getLogger('goog.editor.SeamlessField');
+
+// Functions dealing with field sizing.
+
+
+/**
+ * The key used for listening for the "dragover" event.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.listenForDragOverEventKey_;
+
+
+/**
+ * The key used for listening for the iframe "load" event.
+ * @type {goog.events.Key}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.listenForIframeLoadEventKey_;
+
+
+/**
+ * Sets the min height of this editable field's iframe. Only used in growing
+ * mode when an iframe is used. This will cause an immediate field sizing to
+ * update the field if necessary based on the new min height.
+ * @param {number} height The min height specified as a number of pixels,
+ * e.g., 75.
+ */
+goog.editor.SeamlessField.prototype.setMinHeight = function(height) {
+ if (height == this.minHeight_) {
+ // Do nothing if the min height isn't changing.
+ return;
+ }
+ this.minHeight_ = height;
+ if (this.usesIframe()) {
+ this.doFieldSizingGecko();
+ }
+};
+
+
+/**
+ * Whether the field should be rendered with a fixed height, or should expand
+ * to fit its contents.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.isFixedHeight_ = false;
+
+
+/**
+ * Whether the fixed-height handling has been overridden manually.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.isFixedHeightOverridden_ = false;
+
+
+/**
+ * @return {boolean} Whether the field should be rendered with a fixed
+ * height, or should expand to fit its contents.
+ * @override
+ */
+goog.editor.SeamlessField.prototype.isFixedHeight = function() {
+ return this.isFixedHeight_;
+};
+
+
+/**
+ * @param {boolean} newVal Explicitly set whether the field should be
+ * of a fixed-height. This overrides auto-detection.
+ */
+goog.editor.SeamlessField.prototype.overrideFixedHeight = function(newVal) {
+ this.isFixedHeight_ = newVal;
+ this.isFixedHeightOverridden_ = true;
+};
+
+
+/**
+ * Auto-detect whether the current field should have a fixed height.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.autoDetectFixedHeight_ = function() {
+ if (!this.isFixedHeightOverridden_) {
+ var originalElement = this.getOriginalElement();
+ if (originalElement) {
+ this.isFixedHeight_ =
+ goog.style.getComputedOverflowY(originalElement) == 'auto';
+ }
+ }
+};
+
+
+/**
+ * Resize the iframe in response to the wrapper div changing size.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.handleOuterDocChange_ = function() {
+ if (this.isEventStopped(goog.editor.Field.EventType.CHANGE)) {
+ return;
+ }
+ this.sizeIframeToWrapperGecko_();
+};
+
+
+/**
+ * Sizes the iframe to its body's height.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.sizeIframeToBodyHeightGecko_ = function() {
+ if (this.acquireSizeIframeLockGecko_()) {
+ var resized = false;
+ var ifr = this.getEditableIframe();
+ if (ifr) {
+ var fieldHeight = this.getIframeBodyHeightGecko_();
+
+ if (this.minHeight_) {
+ fieldHeight = Math.max(fieldHeight, this.minHeight_);
+ }
+ if (parseInt(goog.style.getStyle(ifr, 'height'), 10) != fieldHeight) {
+ ifr.style.height = fieldHeight + 'px';
+ resized = true;
+ }
+ }
+ this.releaseSizeIframeLockGecko_();
+ if (resized) {
+ this.dispatchEvent(goog.editor.Field.EventType.IFRAME_RESIZED);
+ }
+ }
+};
+
+
+/**
+ * @return {number} The height of the editable iframe's body.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.getIframeBodyHeightGecko_ = function() {
+ var ifr = this.getEditableIframe();
+ var body = ifr.contentDocument.body;
+ var htmlElement = body.parentNode;
+
+
+ // If the iframe's height is 0, then the offsetHeight/scrollHeight of the
+ // HTML element in the iframe can be totally wack (i.e. too large
+ // by 50-500px). Also, in standard's mode the clientHeight is 0.
+ if (parseInt(goog.style.getStyle(ifr, 'height'), 10) === 0) {
+ goog.style.setStyle(ifr, 'height', 1 + 'px');
+ }
+
+ var fieldHeight;
+ if (goog.editor.node.isStandardsMode(body)) {
+
+ // If in standards-mode,
+ // grab the HTML element as it will contain all the field's
+ // contents. The body's height, for example, will not include that of
+ // floated images at the bottom in standards mode.
+ // Note that this value include all scrollbars *except* for scrollbars
+ // on the HTML element itself.
+ fieldHeight = htmlElement.offsetHeight;
+ } else {
+ // In quirks-mode, the body-element always seems
+ // to size to the containing window. The html-element however,
+ // sizes to the content, and can thus end up with a value smaller
+ // than its child body-element if the content is shrinking.
+ // We want to make the iframe shrink too when the content shrinks,
+ // so rather than size the iframe to the body-element, size it to
+ // the html-element.
+ fieldHeight = htmlElement.scrollHeight;
+
+ // If there is a horizontal scroll, add in the thickness of the
+ // scrollbar.
+ if (htmlElement.clientHeight != htmlElement.offsetHeight) {
+ fieldHeight += goog.editor.SeamlessField.getScrollbarWidth_();
+ }
+ }
+
+ return fieldHeight;
+};
+
+
+/**
+ * Grabs the width of a scrollbar from the browser and caches the result.
+ * @return {number} The scrollbar width in pixels.
+ * @private
+ */
+goog.editor.SeamlessField.getScrollbarWidth_ = function() {
+ return goog.editor.SeamlessField.scrollbarWidth_ ||
+ (goog.editor.SeamlessField.scrollbarWidth_ =
+ goog.style.getScrollbarWidth());
+};
+
+
+/**
+ * Sizes the iframe to its container div's width. The width of the div
+ * is controlled by its containing context, not by its contents.
+ * if it extends outside of it's contents, then it gets a horizontal scroll.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.sizeIframeToWrapperGecko_ = function() {
+ if (this.acquireSizeIframeLockGecko_()) {
+ var ifr = this.getEditableIframe();
+ var field = this.getElement();
+ var resized = false;
+ if (ifr && field) {
+ var fieldPaddingBox;
+ var widthDiv = ifr.parentNode;
+
+ var width = widthDiv.offsetWidth;
+ if (parseInt(goog.style.getStyle(ifr, 'width'), 10) != width) {
+ fieldPaddingBox = goog.style.getPaddingBox(field);
+ ifr.style.width = width + 'px';
+ field.style.width =
+ width - fieldPaddingBox.left - fieldPaddingBox.right + 'px';
+ resized = true;
+ }
+
+ var height = widthDiv.offsetHeight;
+ if (this.isFixedHeight() &&
+ parseInt(goog.style.getStyle(ifr, 'height'), 10) != height) {
+ if (!fieldPaddingBox) {
+ fieldPaddingBox = goog.style.getPaddingBox(field);
+ }
+ ifr.style.height = height + 'px';
+ field.style.height =
+ height - fieldPaddingBox.top - fieldPaddingBox.bottom + 'px';
+ resized = true;
+ }
+
+ }
+ this.releaseSizeIframeLockGecko_();
+ if (resized) {
+ this.dispatchEvent(goog.editor.Field.EventType.IFRAME_RESIZED);
+ }
+ }
+};
+
+
+/**
+ * Perform all the sizing immediately.
+ */
+goog.editor.SeamlessField.prototype.doFieldSizingGecko = function() {
+ // Because doFieldSizingGecko can be called after a setTimeout
+ // it is possible that the field has been destroyed before this call
+ // to do the sizing is executed. Check for field existence and do nothing
+ // if it has already been destroyed.
+ if (this.getElement()) {
+ // The order of operations is important here. Sizing the iframe to the
+ // wrapper could cause the width to change, which could change the line
+ // wrapping, which could change the body height. So we need to do that
+ // first, then size the iframe to fit the body height.
+ this.sizeIframeToWrapperGecko_();
+ if (!this.isFixedHeight()) {
+ this.sizeIframeToBodyHeightGecko_();
+ }
+ }
+};
+
+
+/**
+ * Acquires a lock on resizing the field iframe. This is used to ensure that
+ * modifications we make while in a mutation event handler don't cause
+ * infinite loops.
+ * @return {boolean} False if the lock is already acquired.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.acquireSizeIframeLockGecko_ = function() {
+ if (this.sizeIframeLock_) {
+ return false;
+ }
+ return this.sizeIframeLock_ = true;
+};
+
+
+/**
+ * Releases a lock on resizing the field iframe. This is used to ensure that
+ * modifications we make while in a mutation event handler don't cause
+ * infinite loops.
+ * @private
+ */
+goog.editor.SeamlessField.prototype.releaseSizeIframeLockGecko_ = function() {
+ this.sizeIframeLock_ = false;
+};
+
+
+// Functions dealing with blending in with the surrounding page.
+
+
+/**
+ * String containing the css rules that, if applied to a document's body,
+ * would style that body as if it were the original element we made editable.
+ * See goog.cssom.iframe.style.getElementContext for more details.
+ * @type {string}
+ * @private
+ */
+goog.editor.SeamlessField.prototype.iframeableCss_ = '';
+
+
+/**
+ * Gets the css rules that should be used to style an iframe's body as if it
+ * were the original element that we made editable.
+ * @param {boolean=} opt_forceRegeneration Set to true to not read the cached
+ * copy and instead completely regenerate the css rules.
+ * @return {string} The string containing the css rules to use.
+ */
+goog.editor.SeamlessField.prototype.getIframeableCss = function(
+ opt_forceRegeneration) {
+ if (!this.iframeableCss_ || opt_forceRegeneration) {
+ var originalElement = this.getOriginalElement();
+ if (originalElement) {
+ this.iframeableCss_ =
+ goog.cssom.iframe.style.getElementContext(originalElement,
+ opt_forceRegeneration);
+ }
+ }
+ return this.iframeableCss_;
+};
+
+
+/**
+ * Sets the css rules that should be used inside the editable iframe.
+ * Note: to clear the css cache between makeNotEditable/makeEditable,
+ * call this with "" as iframeableCss.
+ * TODO(user): Unify all these css setting methods + Nick's open
+ * CL. This is getting ridiculous.
+ * @param {string} iframeableCss String containing the css rules to use.
+ */
+goog.editor.SeamlessField.prototype.setIframeableCss = function(iframeableCss) {
+ this.iframeableCss_ = iframeableCss;
+};
+
+
+/**
+ * Used to ensure that CSS stylings are only installed once for none
+ * iframe seamless mode.
+ * TODO(user): Make it a formal part of the API that you can only
+ * set one set of styles globally.
+ * In seamless, non-iframe mode, all the stylings would go in the
+ * same document and conflict.
+ * @type {boolean}
+ * @private
+ */
+goog.editor.SeamlessField.haveInstalledCss_ = false;
+
+
+/**
+ * Applies CSS from the wrapper-div to the field iframe.
+ */
+goog.editor.SeamlessField.prototype.inheritBlendedCSS = function() {
+ // No-op if the field isn't using an iframe.
+ if (!this.usesIframe()) {
+ return;
+ }
+ var field = this.getElement();
+ var head = goog.dom.getDomHelper(field).getElementsByTagNameAndClass(
+ goog.dom.TagName.HEAD)[0];
+ if (head) {
+ // We created this <head>, and we know the only thing we put in there
+ // is a <style> block. So it's safe to blow away all the children
+ // as part of rewriting the styles.
+ goog.dom.removeChildren(head);
+ }
+
+ // Force a cache-clearing in CssUtil - this function was called because
+ // we're applying the 'blend' for the first time, or because we
+ // *need* to recompute the blend.
+ var newCSS = this.getIframeableCss(true);
+ goog.style.installStyles(newCSS, field);
+};
+
+
+// Overridden methods.
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.usesIframe = function() {
+ // TODO(user): Switch Firefox to using contentEditable
+ // rather than designMode iframe once contentEditable support
+ // is less buggy.
+ return !goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE;
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.setupMutationEventHandlersGecko =
+ function() {
+ goog.editor.SeamlessField.superClass_.setupMutationEventHandlersGecko.call(
+ this);
+
+ if (this.usesIframe()) {
+ var iframe = this.getEditableIframe();
+ var outerDoc = iframe.ownerDocument;
+ this.eventRegister.listen(outerDoc,
+ goog.editor.Field.MUTATION_EVENTS_GECKO,
+ this.handleOuterDocChange_, true);
+
+ // If the images load after we do the initial sizing, then this will
+ // force a field resize.
+ this.listenForIframeLoadEventKey_ = goog.events.listenOnce(
+ this.getEditableDomHelper().getWindow(),
+ goog.events.EventType.LOAD, this.sizeIframeToBodyHeightGecko_,
+ true, this);
+
+ this.eventRegister.listen(outerDoc,
+ 'DOMAttrModified',
+ goog.bind(this.handleDomAttrChange, this, this.handleOuterDocChange_),
+ true);
+ }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.handleChange = function() {
+ if (this.isEventStopped(goog.editor.Field.EventType.CHANGE)) {
+ return;
+ }
+
+ goog.editor.SeamlessField.superClass_.handleChange.call(this);
+
+ if (this.usesIframe()) {
+ this.sizeIframeToBodyHeightGecko_();
+ }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.dispatchBlur = function() {
+ if (this.isEventStopped(goog.editor.Field.EventType.BLUR)) {
+ return;
+ }
+
+ goog.editor.SeamlessField.superClass_.dispatchBlur.call(this);
+
+ // Clear the selection and restore the current range back after collapsing
+ // it. The ideal solution would have been to just leave the range intact; but
+ // when there are multiple fields present on the page, its important that
+ // the selection isn't retained when we switch between the fields. We also
+ // have to make sure that the cursor position is retained when we tab in and
+ // out of a field and our approach addresses both these issues.
+ // Another point to note is that we do it on a setTimeout to allow for
+ // DOM modifications on blur. Otherwise, something like setLoremIpsum will
+ // leave a blinking cursor in the field even though it's blurred.
+ if (!goog.editor.BrowserFeature.HAS_CONTENT_EDITABLE &&
+ !goog.editor.BrowserFeature.CLEARS_SELECTION_WHEN_FOCUS_LEAVES) {
+ var win = this.getEditableDomHelper().getWindow();
+ var dragging = false;
+ goog.events.unlistenByKey(this.listenForDragOverEventKey_);
+ this.listenForDragOverEventKey_ = goog.events.listenOnce(
+ win.document.body, 'dragover',
+ function() {
+ dragging = true;
+ });
+ goog.global.setTimeout(goog.bind(function() {
+ // Do not clear the selection if we're only dragging text.
+ // This addresses a bug on FF1.5/linux where dragging fires a blur,
+ // but clearing the selection confuses Firefox's drag-and-drop
+ // implementation. For more info, see http://b/1061064
+ if (!dragging) {
+ if (this.editableDomHelper) {
+ var rng = this.getRange();
+
+ // If there are multiple fields on a page, we need to make sure that
+ // the selection isn't retained when we switch between fields. We
+ // could have collapsed the range but there is a bug in GECKO where
+ // the selection stays highlighted even though its backing range is
+ // collapsed (http://b/1390115). To get around this, we clear the
+ // selection and restore the collapsed range back in. Restoring the
+ // range is important so that the cursor stays intact when we tab out
+ // and into a field (See http://b/1790301 for additional details on
+ // this).
+ var iframeWindow = this.editableDomHelper.getWindow();
+ goog.dom.Range.clearSelection(iframeWindow);
+
+ if (rng) {
+ rng.collapse(true);
+ rng.select();
+ }
+ }
+ }
+ }, this), 0);
+ }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.turnOnDesignModeGecko = function() {
+ goog.editor.SeamlessField.superClass_.turnOnDesignModeGecko.call(this);
+ var doc = this.getEditableDomHelper().getDocument();
+
+ doc.execCommand('enableInlineTableEditing', false, 'false');
+ doc.execCommand('enableObjectResizing', false, 'false');
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.installStyles = function() {
+ if (!this.usesIframe()) {
+ if (!goog.editor.SeamlessField.haveInstalledCss_) {
+ if (this.cssStyles) {
+ goog.style.installStyles(this.cssStyles, this.getElement());
+ }
+
+ // TODO(user): this should be reset to false when the editor is quit.
+ // In non-iframe mode, CSS styles should only be instaled once.
+ goog.editor.SeamlessField.haveInstalledCss_ = true;
+ }
+ }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.makeEditableInternal = function(
+ opt_iframeSrc) {
+ if (this.usesIframe()) {
+ goog.editor.SeamlessField.superClass_.makeEditableInternal.call(this,
+ opt_iframeSrc);
+ } else {
+ var field = this.getOriginalElement();
+ if (field) {
+ this.setupFieldObject(field);
+ field.contentEditable = true;
+
+ this.injectContents(field.innerHTML, field);
+
+ this.handleFieldLoad();
+ }
+ }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.handleFieldLoad = function() {
+ if (this.usesIframe()) {
+ // If the CSS inheriting code screws up (e.g. makes fonts too large) and
+ // the field is sized off in goog.editor.Field.makeIframeField, then we need
+ // to size it correctly, but it needs to be visible for the browser
+ // to have fully rendered it. We need to put this on a timeout to give
+ // the browser time to render.
+ var self = this;
+ goog.global.setTimeout(function() {
+ self.doFieldSizingGecko();
+ }, 0);
+ }
+ goog.editor.SeamlessField.superClass_.handleFieldLoad.call(this);
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.getIframeAttributes = function() {
+ return { 'frameBorder': 0, 'style': 'padding:0;' };
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.attachIframe = function(iframe) {
+ this.autoDetectFixedHeight_();
+ var field = this.getOriginalElement();
+ var dh = goog.dom.getDomHelper(field);
+
+ // Grab the width/height values of the field before modifying any CSS
+ // as some of the modifications affect its size (e.g. innerHTML='')
+ // Here, we set the size of the field to fixed so there's not too much
+ // jiggling when we set the innerHTML of the field.
+ var oldWidth = field.style.width;
+ var oldHeight = field.style.height;
+ goog.style.setStyle(field, 'visibility', 'hidden');
+
+ // If there is a floated element at the bottom of the field,
+ // then it needs a clearing div at the end to cause the clientHeight
+ // to contain the entire field.
+ // Also, with css re-writing, the margins of the first/last
+ // paragraph don't seem to get included in the clientHeight. Specifically,
+ // the extra divs below force the field's clientHeight to include the
+ // margins on the first and last elements contained within it.
+ var startDiv = dh.createDom(goog.dom.TagName.DIV,
+ {'style': 'height:0;clear:both', 'innerHTML': ' '});
+ var endDiv = startDiv.cloneNode(true);
+ field.insertBefore(startDiv, field.firstChild);
+ goog.dom.appendChild(field, endDiv);
+
+ var contentBox = goog.style.getContentBoxSize(field);
+ var width = contentBox.width;
+ var height = contentBox.height;
+
+ var html = '';
+ if (this.isFixedHeight()) {
+ html = ' ';
+
+ goog.style.setStyle(field, 'position', 'relative');
+ goog.style.setStyle(field, 'overflow', 'visible');
+
+ goog.style.setStyle(iframe, 'position', 'absolute');
+ goog.style.setStyle(iframe, 'top', '0');
+ goog.style.setStyle(iframe, 'left', '0');
+ }
+ goog.style.setSize(field, width, height);
+
+ // In strict mode, browsers put blank space at the bottom and right
+ // if a field when it has an iframe child, to fill up the remaining line
+ // height. So make the line height = 0.
+ if (goog.editor.node.isStandardsMode(field)) {
+ this.originalFieldLineHeight_ = field.style.lineHeight;
+ goog.style.setStyle(field, 'lineHeight', '0');
+ }
+
+ goog.editor.node.replaceInnerHtml(field, html);
+ // Set the initial size
+ goog.style.setSize(iframe, width, height);
+ goog.style.setSize(field, oldWidth, oldHeight);
+ goog.style.setStyle(field, 'visibility', '');
+ goog.dom.appendChild(field, iframe);
+
+ // Only write if its not IE HTTPS in which case we're waiting for load.
+ if (!this.shouldLoadAsynchronously()) {
+ var doc = iframe.contentWindow.document;
+ if (goog.editor.node.isStandardsMode(iframe.ownerDocument)) {
+ doc.open();
+ var emptyHtml = goog.html.uncheckedconversions
+ .safeHtmlFromStringKnownToSatisfyTypeContract(
+ goog.string.Const.from('HTML from constant string'),
+ '<!DOCTYPE HTML><html></html>');
+ goog.dom.safe.documentWrite(doc, emptyHtml);
+ doc.close();
+ }
+ }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.getFieldFormatInfo = function(
+ extraStyles) {
+ var originalElement = this.getOriginalElement();
+ if (originalElement) {
+ return new goog.editor.icontent.FieldFormatInfo(
+ this.id,
+ goog.editor.node.isStandardsMode(originalElement),
+ true,
+ this.isFixedHeight(),
+ extraStyles);
+ }
+ throw Error('no field');
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.writeIframeContent = function(
+ iframe, innerHtml, extraStyles) {
+ // For seamless iframes, hide the iframe while we're laying it out to
+ // prevent the flicker.
+ goog.style.setStyle(iframe, 'visibility', 'hidden');
+ var formatInfo = this.getFieldFormatInfo(extraStyles);
+ var styleInfo = new goog.editor.icontent.FieldStyleInfo(
+ this.getOriginalElement(),
+ this.cssStyles + this.getIframeableCss());
+ goog.editor.icontent.writeNormalInitialBlendedIframe(
+ formatInfo, innerHtml, styleInfo, iframe);
+ this.doFieldSizingGecko();
+ goog.style.setStyle(iframe, 'visibility', 'visible');
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.restoreDom = function() {
+ // TODO(user): Consider only removing the iframe if we are
+ // restoring the original node.
+ if (this.usesIframe()) {
+ goog.dom.removeNode(this.getEditableIframe());
+ }
+};
+
+
+/** @override */
+goog.editor.SeamlessField.prototype.clearListeners = function() {
+ goog.events.unlistenByKey(this.listenForDragOverEventKey_);
+ goog.events.unlistenByKey(this.listenForIframeLoadEventKey_);
+
+ goog.editor.SeamlessField.base(this, 'clearListeners');
+};
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/style.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/style.js b/externs/GCL/externs/goog/editor/style.js
new file mode 100644
index 0000000..55b703b
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/style.js
@@ -0,0 +1,225 @@
+// Copyright 2009 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 Utilties for working with the styles of DOM nodes, and
+ * related to rich text editing.
+ *
+ * Many of these are not general enough to go into goog.style, and use
+ * constructs (like "isContainer") that only really make sense inside
+ * of an HTML editor.
+ *
+ * The API has been optimized for iterating over large, irregular DOM
+ * structures (with lots of text nodes), and so the API tends to be a bit
+ * more permissive than the goog.style API should be. For example,
+ * goog.style.getComputedStyle will throw an exception if you give it a
+ * text node.
+ *
+ * @author nicksantos@google.com (Nick Santos)
+ */
+
+goog.provide('goog.editor.style');
+
+goog.require('goog.array');
+goog.require('goog.dom');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.TagName');
+goog.require('goog.editor.BrowserFeature');
+goog.require('goog.events.EventType');
+goog.require('goog.object');
+goog.require('goog.style');
+goog.require('goog.userAgent');
+
+
+/**
+ * Gets the computed or cascaded style.
+ *
+ * This is different than goog.style.getStyle_ because it returns null
+ * for text nodes (instead of throwing an exception), and never reads
+ * inline style. These two functions may need to be reconciled.
+ *
+ * @param {!Node} node Node to get style of.
+ * @param {string} stylePropertyName Property to get (must be camelCase,
+ * not css-style).
+ * @return {?string} Style value, or null if this is not an element node.
+ * @private
+ */
+goog.editor.style.getComputedOrCascadedStyle_ = function(
+ node, stylePropertyName) {
+ if (node.nodeType != goog.dom.NodeType.ELEMENT) {
+ // Only element nodes have style.
+ return null;
+ }
+ return goog.userAgent.IE ?
+ goog.style.getCascadedStyle(/** @type {!Element} */ (node),
+ stylePropertyName) :
+ goog.style.getComputedStyle(/** @type {!Element} */ (node),
+ stylePropertyName);
+};
+
+
+/**
+ * Checks whether the given element inherits display: block.
+ * @param {!Node} node The Node to check.
+ * @return {boolean} Whether the element inherits CSS display: block.
+ */
+goog.editor.style.isDisplayBlock = function(node) {
+ return goog.editor.style.getComputedOrCascadedStyle_(
+ node, 'display') == 'block';
+};
+
+
+/**
+ * Returns true if the element is a container of other non-inline HTML
+ * Note that span, strong and em tags, being inline can only contain
+ * other inline elements and are thus, not containers. Containers are elements
+ * that should not be broken up when wrapping selections with a node of an
+ * inline block styling.
+ * @param {Node} element The element to check.
+ * @return {boolean} Whether the element is a container.
+ */
+goog.editor.style.isContainer = function(element) {
+ var nodeName = element && element.nodeName;
+ return !!(element &&
+ (goog.editor.style.isDisplayBlock(element) ||
+ nodeName == goog.dom.TagName.TD ||
+ nodeName == goog.dom.TagName.TABLE ||
+ nodeName == goog.dom.TagName.LI));
+};
+
+
+/**
+ * Return the first ancestor of this node that is a container, inclusive.
+ * @see isContainer
+ * @param {Node} node Node to find the container of.
+ * @return {Element} The element which contains node.
+ */
+goog.editor.style.getContainer = function(node) {
+ // We assume that every node must have a container.
+ return /** @type {Element} */ (
+ goog.dom.getAncestor(node, goog.editor.style.isContainer, true));
+};
+
+
+/**
+ * Set of input types that should be kept selectable even when their ancestors
+ * are made unselectable.
+ * @type {Object}
+ * @private
+ */
+goog.editor.style.SELECTABLE_INPUT_TYPES_ = goog.object.createSet(
+ 'text', 'file', 'url');
+
+
+/**
+ * Prevent the default action on mousedown events.
+ * @param {goog.events.Event} e The mouse down event.
+ * @private
+ */
+goog.editor.style.cancelMouseDownHelper_ = function(e) {
+ var targetTagName = e.target.tagName;
+ if (targetTagName != goog.dom.TagName.TEXTAREA &&
+ targetTagName != goog.dom.TagName.INPUT) {
+ e.preventDefault();
+ }
+};
+
+
+/**
+ * Makes the given element unselectable, as well as all of its children, except
+ * for text areas, text, file and url inputs.
+ * @param {Element} element The element to make unselectable.
+ * @param {goog.events.EventHandler} eventHandler An EventHandler to register
+ * the event with. Assumes when the node is destroyed, the eventHandler's
+ * listeners are destroyed as well.
+ */
+goog.editor.style.makeUnselectable = function(element, eventHandler) {
+ if (goog.editor.BrowserFeature.HAS_UNSELECTABLE_STYLE) {
+ // The mousing down on a node should not blur the focused node.
+ // This is consistent with how IE works.
+ // TODO: Consider using just the mousedown handler and not the css property.
+ eventHandler.listen(element, goog.events.EventType.MOUSEDOWN,
+ goog.editor.style.cancelMouseDownHelper_, true);
+ }
+
+ goog.style.setUnselectable(element, true);
+
+ // Make inputs and text areas selectable.
+ var inputs = element.getElementsByTagName(goog.dom.TagName.INPUT);
+ for (var i = 0, len = inputs.length; i < len; i++) {
+ var input = inputs[i];
+ if (input.type in goog.editor.style.SELECTABLE_INPUT_TYPES_) {
+ goog.editor.style.makeSelectable(input);
+ }
+ }
+ goog.array.forEach(element.getElementsByTagName(goog.dom.TagName.TEXTAREA),
+ goog.editor.style.makeSelectable);
+};
+
+
+/**
+ * Make the given element selectable.
+ *
+ * For IE this simply turns off the "unselectable" property.
+ *
+ * Under FF no descendent of an unselectable node can be selectable:
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=203291
+ *
+ * So we make each ancestor of node selectable, while trying to preserve the
+ * unselectability of other nodes along that path
+ *
+ * This may cause certain text nodes which should be unselectable, to become
+ * selectable. For example:
+ *
+ * <div id=div1 style="-moz-user-select: none">
+ * Text1
+ * <span id=span1>Text2</span>
+ * </div>
+ *
+ * If we call makeSelectable on span1, then it will cause "Text1" to become
+ * selectable, since it had to make div1 selectable in order for span1 to be
+ * selectable.
+ *
+ * If "Text1" were enclosed within a <p> or <span>, then this problem would
+ * not arise. Text nodes do not have styles, so its style can't be set to
+ * unselectable.
+ *
+ * @param {Element} element The element to make selectable.
+ */
+goog.editor.style.makeSelectable = function(element) {
+ goog.style.setUnselectable(element, false);
+ if (goog.editor.BrowserFeature.HAS_UNSELECTABLE_STYLE) {
+ // Go up ancestor chain, searching for nodes that are unselectable.
+ // If such a node exists, mark it as selectable but mark its other children
+ // as unselectable so the minimum set of nodes is changed.
+ var child = element;
+ var current = /** @type {Element} */ (element.parentNode);
+ while (current && current.tagName != goog.dom.TagName.HTML) {
+ if (goog.style.isUnselectable(current)) {
+ goog.style.setUnselectable(current, false, true);
+
+ for (var i = 0, len = current.childNodes.length; i < len; i++) {
+ var node = current.childNodes[i];
+ if (node != child && node.nodeType == goog.dom.NodeType.ELEMENT) {
+ goog.style.setUnselectable(current.childNodes[i], true);
+ }
+ }
+ }
+
+ child = current;
+ current = /** @type {Element} */ (current.parentNode);
+ }
+ }
+};
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/editor/table.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/editor/table.js b/externs/GCL/externs/goog/editor/table.js
new file mode 100644
index 0000000..1bddc6d
--- /dev/null
+++ b/externs/GCL/externs/goog/editor/table.js
@@ -0,0 +1,570 @@
+// 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 Table editing support.
+ * This file provides the class goog.editor.Table and two
+ * supporting classes, goog.editor.TableRow and
+ * goog.editor.TableCell. Together these provide support for
+ * high level table modifications: Adding and deleting rows and columns,
+ * and merging and splitting cells.
+ *
+ * @supported IE6+, WebKit 525+, Firefox 2+.
+ */
+
+goog.provide('goog.editor.Table');
+goog.provide('goog.editor.TableCell');
+goog.provide('goog.editor.TableRow');
+
+goog.require('goog.dom');
+goog.require('goog.dom.DomHelper');
+goog.require('goog.dom.NodeType');
+goog.require('goog.dom.TagName');
+goog.require('goog.log');
+goog.require('goog.string.Unicode');
+goog.require('goog.style');
+
+
+
+/**
+ * Class providing high level table editing functions.
+ * @param {Element} node Element that is a table or descendant of a table.
+ * @constructor
+ * @final
+ */
+goog.editor.Table = function(node) {
+ this.element = goog.dom.getAncestorByTagNameAndClass(node,
+ goog.dom.TagName.TABLE);
+ if (!this.element) {
+ goog.log.error(this.logger_,
+ "Can't create Table based on a node " +
+ "that isn't a table, or descended from a table.");
+ }
+ this.dom_ = goog.dom.getDomHelper(this.element);
+ this.refresh();
+};
+
+
+/**
+ * Logger object for debugging and error messages.
+ * @type {goog.log.Logger}
+ * @private
+ */
+goog.editor.Table.prototype.logger_ =
+ goog.log.getLogger('goog.editor.Table');
+
+
+/**
+ * Walks the dom structure of this object's table element and populates
+ * this.rows with goog.editor.TableRow objects. This is done initially
+ * to populate the internal data structures, and also after each time the
+ * DOM structure is modified. Currently this means that the all existing
+ * information is discarded and re-read from the DOM.
+ */
+// TODO(user): support partial refresh to save cost of full update
+// every time there is a change to the DOM.
+goog.editor.Table.prototype.refresh = function() {
+ var rows = this.rows = [];
+ var tbody = this.element.getElementsByTagName(goog.dom.TagName.TBODY)[0];
+ if (!tbody) {
+ return;
+ }
+ var trs = [];
+ for (var child = tbody.firstChild; child; child = child.nextSibling) {
+ if (child.nodeName == goog.dom.TagName.TR) {
+ trs.push(child);
+ }
+ }
+
+ for (var rowNum = 0, tr; tr = trs[rowNum]; rowNum++) {
+ var existingRow = rows[rowNum];
+ var tds = goog.editor.Table.getChildCellElements(tr);
+ var columnNum = 0;
+ // A note on cellNum vs. columnNum: A cell is a td/th element. Cells may
+ // use colspan/rowspan to extend over multiple rows/columns. cellNum
+ // is the dom element number, columnNum is the logical column number.
+ for (var cellNum = 0, td; td = tds[cellNum]; cellNum++) {
+ // If there's already a cell extending into this column
+ // (due to that cell's colspan/rowspan), increment the column counter.
+ while (existingRow && existingRow.columns[columnNum]) {
+ columnNum++;
+ }
+ var cell = new goog.editor.TableCell(td, rowNum, columnNum);
+ // Place this cell in every row and column into which it extends.
+ for (var i = 0; i < cell.rowSpan; i++) {
+ var cellRowNum = rowNum + i;
+ // Create TableRow objects in this.rows as needed.
+ var cellRow = rows[cellRowNum];
+ if (!cellRow) {
+ // TODO(user): try to avoid second trs[] lookup.
+ rows.push(
+ cellRow = new goog.editor.TableRow(trs[cellRowNum], cellRowNum));
+ }
+ // Extend length of column array to make room for this cell.
+ var minimumColumnLength = columnNum + cell.colSpan;
+ if (cellRow.columns.length < minimumColumnLength) {
+ cellRow.columns.length = minimumColumnLength;
+ }
+ for (var j = 0; j < cell.colSpan; j++) {
+ var cellColumnNum = columnNum + j;
+ cellRow.columns[cellColumnNum] = cell;
+ }
+ }
+ columnNum += cell.colSpan;
+ }
+ }
+};
+
+
+/**
+ * Returns all child elements of a TR element that are of type TD or TH.
+ * @param {Element} tr TR element in which to find children.
+ * @return {!Array<Element>} array of child cell elements.
+ */
+goog.editor.Table.getChildCellElements = function(tr) {
+ var cells = [];
+ for (var i = 0, cell; cell = tr.childNodes[i]; i++) {
+ if (cell.nodeName == goog.dom.TagName.TD ||
+ cell.nodeName == goog.dom.TagName.TH) {
+ cells.push(cell);
+ }
+ }
+ return cells;
+};
+
+
+/**
+ * Inserts a new row in the table. The row will be populated with new
+ * cells, and existing rowspanned cells that overlap the new row will
+ * be extended.
+ * @param {number=} opt_rowIndex Index at which to insert the row. If
+ * this is omitted the row will be appended to the end of the table.
+ * @return {!Element} The new row.
+ */
+goog.editor.Table.prototype.insertRow = function(opt_rowIndex) {
+ var rowIndex = goog.isDefAndNotNull(opt_rowIndex) ?
+ opt_rowIndex : this.rows.length;
+ var refRow;
+ var insertAfter;
+ if (rowIndex == 0) {
+ refRow = this.rows[0];
+ insertAfter = false;
+ } else {
+ refRow = this.rows[rowIndex - 1];
+ insertAfter = true;
+ }
+ var newTr = this.dom_.createElement(goog.dom.TagName.TR);
+ for (var i = 0, cell; cell = refRow.columns[i]; i += 1) {
+ // Check whether the existing cell will span this new row.
+ // If so, instead of creating a new cell, extend
+ // the rowspan of the existing cell.
+ if ((insertAfter && cell.endRow > rowIndex) ||
+ (!insertAfter && cell.startRow < rowIndex)) {
+ cell.setRowSpan(cell.rowSpan + 1);
+ if (cell.colSpan > 1) {
+ i += cell.colSpan - 1;
+ }
+ } else {
+ newTr.appendChild(this.createEmptyTd());
+ }
+ if (insertAfter) {
+ goog.dom.insertSiblingAfter(newTr, refRow.element);
+ } else {
+ goog.dom.insertSiblingBefore(newTr, refRow.element);
+ }
+ }
+ this.refresh();
+ return newTr;
+};
+
+
+/**
+ * Inserts a new column in the table. The column will be created by
+ * inserting new TD elements in each row, or extending the colspan
+ * of existing TD elements.
+ * @param {number=} opt_colIndex Index at which to insert the column. If
+ * this is omitted the column will be appended to the right side of
+ * the table.
+ * @return {!Array<Element>} Array of new cell elements that were created
+ * to populate the new column.
+ */
+goog.editor.Table.prototype.insertColumn = function(opt_colIndex) {
+ // TODO(user): set column widths in a way that makes sense.
+ var colIndex = goog.isDefAndNotNull(opt_colIndex) ?
+ opt_colIndex :
+ (this.rows[0] && this.rows[0].columns.length) || 0;
+ var newTds = [];
+ for (var rowNum = 0, row; row = this.rows[rowNum]; rowNum++) {
+ var existingCell = row.columns[colIndex];
+ if (existingCell && existingCell.endCol >= colIndex &&
+ existingCell.startCol < colIndex) {
+ existingCell.setColSpan(existingCell.colSpan + 1);
+ rowNum += existingCell.rowSpan - 1;
+ } else {
+ var newTd = this.createEmptyTd();
+ // TODO(user): figure out a way to intelligently size new columns.
+ newTd.style.width = goog.editor.Table.OPTIMUM_EMPTY_CELL_WIDTH + 'px';
+ this.insertCellElement(newTd, rowNum, colIndex);
+ newTds.push(newTd);
+ }
+ }
+ this.refresh();
+ return newTds;
+};
+
+
+/**
+ * Removes a row from the table, removing the TR element and
+ * decrementing the rowspan of any cells in other rows that overlap the row.
+ * @param {number} rowIndex Index of the row to delete.
+ */
+goog.editor.Table.prototype.removeRow = function(rowIndex) {
+ var row = this.rows[rowIndex];
+ if (!row) {
+ goog.log.warning(this.logger_,
+ "Can't remove row at position " + rowIndex + ': no such row.');
+ }
+ for (var i = 0, cell; cell = row.columns[i]; i += cell.colSpan) {
+ if (cell.rowSpan > 1) {
+ cell.setRowSpan(cell.rowSpan - 1);
+ if (cell.startRow == rowIndex) {
+ // Rowspanned cell started in this row - move it down to the next row.
+ this.insertCellElement(cell.element, rowIndex + 1, cell.startCol);
+ }
+ }
+ }
+ row.element.parentNode.removeChild(row.element);
+ this.refresh();
+};
+
+
+/**
+ * Removes a column from the table. This is done by removing cell elements,
+ * or shrinking the colspan of elements that span multiple columns.
+ * @param {number} colIndex Index of the column to delete.
+ */
+goog.editor.Table.prototype.removeColumn = function(colIndex) {
+ for (var i = 0, row; row = this.rows[i]; i++) {
+ var cell = row.columns[colIndex];
+ if (!cell) {
+ goog.log.error(this.logger_,
+ "Can't remove cell at position " + i + ', ' + colIndex +
+ ': no such cell.');
+ }
+ if (cell.colSpan > 1) {
+ cell.setColSpan(cell.colSpan - 1);
+ } else {
+ cell.element.parentNode.removeChild(cell.element);
+ }
+ // Skip over following rows that contain this same cell.
+ i += cell.rowSpan - 1;
+ }
+ this.refresh();
+};
+
+
+/**
+ * Merges multiple cells into a single cell, and sets the rowSpan and colSpan
+ * attributes of the cell to take up the same space as the original cells.
+ * @param {number} startRowIndex Top coordinate of the cells to merge.
+ * @param {number} startColIndex Left coordinate of the cells to merge.
+ * @param {number} endRowIndex Bottom coordinate of the cells to merge.
+ * @param {number} endColIndex Right coordinate of the cells to merge.
+ * @return {boolean} Whether or not the merge was possible. If the cells
+ * in the supplied coordinates can't be merged this will return false.
+ */
+goog.editor.Table.prototype.mergeCells = function(
+ startRowIndex, startColIndex, endRowIndex, endColIndex) {
+ // TODO(user): take a single goog.math.Rect parameter instead?
+ var cells = [];
+ var cell;
+ if (startRowIndex == endRowIndex && startColIndex == endColIndex) {
+ goog.log.warning(this.logger_, "Can't merge single cell");
+ return false;
+ }
+ // Gather cells and do sanity check.
+ for (var i = startRowIndex; i <= endRowIndex; i++) {
+ for (var j = startColIndex; j <= endColIndex; j++) {
+ cell = this.rows[i].columns[j];
+ if (cell.startRow < startRowIndex ||
+ cell.endRow > endRowIndex ||
+ cell.startCol < startColIndex ||
+ cell.endCol > endColIndex) {
+ goog.log.warning(this.logger_,
+ "Can't merge cells: the cell in row " + i + ', column ' + j +
+ 'extends outside the supplied rectangle.');
+ return false;
+ }
+ // TODO(user): this is somewhat inefficient, as we will add
+ // a reference for a cell for each position, even if it's a single
+ // cell with row/colspan.
+ cells.push(cell);
+ }
+ }
+ var targetCell = cells[0];
+ var targetTd = targetCell.element;
+ var doc = this.dom_.getDocument();
+
+ // Merge cell contents and discard other cells.
+ for (var i = 1; cell = cells[i]; i++) {
+ var td = cell.element;
+ if (!td.parentNode || td == targetTd) {
+ // We've already handled this cell at one of its previous positions.
+ continue;
+ }
+ // Add a space if needed, to keep merged content from getting squished
+ // together.
+ if (targetTd.lastChild &&
+ targetTd.lastChild.nodeType == goog.dom.NodeType.TEXT) {
+ targetTd.appendChild(doc.createTextNode(' '));
+ }
+ var childNode;
+ while ((childNode = td.firstChild)) {
+ targetTd.appendChild(childNode);
+ }
+ td.parentNode.removeChild(td);
+ }
+ targetCell.setColSpan((endColIndex - startColIndex) + 1);
+ targetCell.setRowSpan((endRowIndex - startRowIndex) + 1);
+ if (endColIndex > startColIndex) {
+ // Clear width on target cell.
+ // TODO(user): instead of clearing width, calculate width
+ // based on width of input cells
+ targetTd.removeAttribute('width');
+ targetTd.style.width = null;
+ }
+ this.refresh();
+
+ return true;
+};
+
+
+/**
+ * Splits a cell with colspans or rowspans into multiple descrete cells.
+ * @param {number} rowIndex y coordinate of the cell to split.
+ * @param {number} colIndex x coordinate of the cell to split.
+ * @return {!Array<Element>} Array of new cell elements created by splitting
+ * the cell.
+ */
+// TODO(user): support splitting only horizontally or vertically,
+// support splitting cells that aren't already row/colspanned.
+goog.editor.Table.prototype.splitCell = function(rowIndex, colIndex) {
+ var row = this.rows[rowIndex];
+ var cell = row.columns[colIndex];
+ var newTds = [];
+ for (var i = 0; i < cell.rowSpan; i++) {
+ for (var j = 0; j < cell.colSpan; j++) {
+ if (i > 0 || j > 0) {
+ var newTd = this.createEmptyTd();
+ this.insertCellElement(newTd, rowIndex + i, colIndex + j);
+ newTds.push(newTd);
+ }
+ }
+ }
+ cell.setColSpan(1);
+ cell.setRowSpan(1);
+ this.refresh();
+ return newTds;
+};
+
+
+/**
+ * Inserts a cell element at the given position. The colIndex is the logical
+ * column index, not the position in the dom. This takes into consideration
+ * that cells in a given logical row may actually be children of a previous
+ * DOM row that have used rowSpan to extend into the row.
+ * @param {Element} td The new cell element to insert.
+ * @param {number} rowIndex Row in which to insert the element.
+ * @param {number} colIndex Column in which to insert the element.
+ */
+goog.editor.Table.prototype.insertCellElement = function(
+ td, rowIndex, colIndex) {
+ var row = this.rows[rowIndex];
+ var nextSiblingElement = null;
+ for (var i = colIndex, cell; cell = row.columns[i]; i += cell.colSpan) {
+ if (cell.startRow == rowIndex) {
+ nextSiblingElement = cell.element;
+ break;
+ }
+ }
+ row.element.insertBefore(td, nextSiblingElement);
+};
+
+
+/**
+ * Creates an empty TD element and fill it with some empty content so it will
+ * show up with borders even in IE pre-7 or if empty-cells is set to 'hide'
+ * @return {!Element} a new TD element.
+ */
+goog.editor.Table.prototype.createEmptyTd = function() {
+ // TODO(user): more cross-browser testing to determine best
+ // and least annoying filler content.
+ return this.dom_.createDom(goog.dom.TagName.TD, {}, goog.string.Unicode.NBSP);
+};
+
+
+
+/**
+ * Class representing a logical table row: a tr element and any cells
+ * that appear in that row.
+ * @param {Element} trElement This rows's underlying TR element.
+ * @param {number} rowIndex This row's index in its parent table.
+ * @constructor
+ * @final
+ */
+goog.editor.TableRow = function(trElement, rowIndex) {
+ this.index = rowIndex;
+ this.element = trElement;
+ this.columns = [];
+};
+
+
+
+/**
+ * Class representing a table cell, which may span across multiple
+ * rows and columns
+ * @param {Element} td This cell's underlying TD or TH element.
+ * @param {number} startRow Index of the row where this cell begins.
+ * @param {number} startCol Index of the column where this cell begins.
+ * @constructor
+ * @final
+ */
+goog.editor.TableCell = function(td, startRow, startCol) {
+ this.element = td;
+ this.colSpan = parseInt(td.colSpan, 10) || 1;
+ this.rowSpan = parseInt(td.rowSpan, 10) || 1;
+ this.startRow = startRow;
+ this.startCol = startCol;
+ this.updateCoordinates_();
+};
+
+
+/**
+ * Calculates this cell's endRow/endCol coordinates based on rowSpan/colSpan
+ * @private
+ */
+goog.editor.TableCell.prototype.updateCoordinates_ = function() {
+ this.endCol = this.startCol + this.colSpan - 1;
+ this.endRow = this.startRow + this.rowSpan - 1;
+};
+
+
+/**
+ * Set this cell's colSpan, updating both its colSpan property and the
+ * underlying element's colSpan attribute.
+ * @param {number} colSpan The new colSpan.
+ */
+goog.editor.TableCell.prototype.setColSpan = function(colSpan) {
+ if (colSpan != this.colSpan) {
+ if (colSpan > 1) {
+ this.element.colSpan = colSpan;
+ } else {
+ this.element.colSpan = 1,
+ this.element.removeAttribute('colSpan');
+ }
+ this.colSpan = colSpan;
+ this.updateCoordinates_();
+ }
+};
+
+
+/**
+ * Set this cell's rowSpan, updating both its rowSpan property and the
+ * underlying element's rowSpan attribute.
+ * @param {number} rowSpan The new rowSpan.
+ */
+goog.editor.TableCell.prototype.setRowSpan = function(rowSpan) {
+ if (rowSpan != this.rowSpan) {
+ if (rowSpan > 1) {
+ this.element.rowSpan = rowSpan.toString();
+ } else {
+ this.element.rowSpan = '1';
+ this.element.removeAttribute('rowSpan');
+ }
+ this.rowSpan = rowSpan;
+ this.updateCoordinates_();
+ }
+};
+
+
+/**
+ * Optimum size of empty cells (in pixels), if possible.
+ * @type {number}
+ */
+goog.editor.Table.OPTIMUM_EMPTY_CELL_WIDTH = 60;
+
+
+/**
+ * Maximum width for new tables.
+ * @type {number}
+ */
+goog.editor.Table.OPTIMUM_MAX_NEW_TABLE_WIDTH = 600;
+
+
+/**
+ * Default color for table borders.
+ * @type {string}
+ */
+goog.editor.Table.DEFAULT_BORDER_COLOR = '#888';
+
+
+/**
+ * Creates a new table element, populated with cells and formatted.
+ * @param {Document} doc Document in which to create the table element.
+ * @param {number} columns Number of columns in the table.
+ * @param {number} rows Number of rows in the table.
+ * @param {Object=} opt_tableStyle Object containing borderWidth and borderColor
+ * properties, used to set the inital style of the table.
+ * @return {!Element} a table element.
+ */
+goog.editor.Table.createDomTable = function(
+ doc, columns, rows, opt_tableStyle) {
+ // TODO(user): define formatting properties as constants,
+ // make separate formatTable() function
+ var style = {
+ borderWidth: '1',
+ borderColor: goog.editor.Table.DEFAULT_BORDER_COLOR
+ };
+ for (var prop in opt_tableStyle) {
+ style[prop] = opt_tableStyle[prop];
+ }
+ var dom = new goog.dom.DomHelper(doc);
+ var tableElement = dom.createTable(rows, columns, true);
+
+ var minimumCellWidth = 10;
+ // Calculate a good cell width.
+ var cellWidth = Math.max(
+ minimumCellWidth,
+ Math.min(goog.editor.Table.OPTIMUM_EMPTY_CELL_WIDTH,
+ goog.editor.Table.OPTIMUM_MAX_NEW_TABLE_WIDTH / columns));
+
+ var tds = tableElement.getElementsByTagName(goog.dom.TagName.TD);
+ for (var i = 0, td; td = tds[i]; i++) {
+ td.style.width = cellWidth + 'px';
+ }
+
+ // Set border somewhat redundantly to make sure they show
+ // up correctly in all browsers.
+ goog.style.setStyle(
+ tableElement, {
+ 'borderCollapse': 'collapse',
+ 'borderColor': style.borderColor,
+ 'borderWidth': style.borderWidth + 'px'
+ });
+ tableElement.border = style.borderWidth;
+ tableElement.setAttribute('bordercolor', style.borderColor);
+ tableElement.setAttribute('cellspacing', '0');
+
+ return tableElement;
+};
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/actioneventwrapper.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/events/actioneventwrapper.js b/externs/GCL/externs/goog/events/actioneventwrapper.js
new file mode 100644
index 0000000..779150f
--- /dev/null
+++ b/externs/GCL/externs/goog/events/actioneventwrapper.js
@@ -0,0 +1,151 @@
+// Copyright 2009 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 Action event wrapper implementation.
+ * @author eae@google.com (Emil A Eklund)
+ */
+
+goog.provide('goog.events.actionEventWrapper');
+
+goog.require('goog.a11y.aria');
+goog.require('goog.a11y.aria.Role');
+goog.require('goog.dom');
+goog.require('goog.events');
+/** @suppress {extraRequire} */
+goog.require('goog.events.EventHandler');
+goog.require('goog.events.EventType');
+goog.require('goog.events.EventWrapper');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * Event wrapper for action handling. Fires when an element is activated either
+ * by clicking it or by focusing it and pressing Enter.
+ *
+ * @constructor
+ * @implements {goog.events.EventWrapper}
+ * @private
+ */
+goog.events.ActionEventWrapper_ = function() {
+};
+
+
+/**
+ * Singleton instance of ActionEventWrapper_.
+ * @type {goog.events.ActionEventWrapper_}
+ */
+goog.events.actionEventWrapper = new goog.events.ActionEventWrapper_();
+
+
+/**
+ * Event types used by the wrapper.
+ *
+ * @type {Array<goog.events.EventType>}
+ * @private
+ */
+goog.events.ActionEventWrapper_.EVENT_TYPES_ = [
+ goog.events.EventType.CLICK,
+ goog.userAgent.GECKO ?
+ goog.events.EventType.KEYPRESS : goog.events.EventType.KEYDOWN,
+ goog.events.EventType.KEYUP
+];
+
+
+/**
+ * Adds an event listener using the wrapper on a DOM Node or an object that has
+ * implemented {@link goog.events.EventTarget}. A listener can only be added
+ * once to an object.
+ *
+ * @param {goog.events.ListenableType} target The target to listen to events on.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
+ * method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @param {goog.events.EventHandler=} opt_eventHandler Event handler to add
+ * listener to.
+ * @override
+ */
+goog.events.ActionEventWrapper_.prototype.listen = function(target, listener,
+ opt_capt, opt_scope, opt_eventHandler) {
+ var callback = function(e) {
+ var listenerFn = goog.events.wrapListener(listener);
+ var role = goog.dom.isElement(e.target) ?
+ goog.a11y.aria.getRole(/** @type {!Element} */ (e.target)) : null;
+ if (e.type == goog.events.EventType.CLICK && e.isMouseActionButton()) {
+ listenerFn.call(opt_scope, e);
+ } else if ((e.keyCode == goog.events.KeyCodes.ENTER ||
+ e.keyCode == goog.events.KeyCodes.MAC_ENTER) &&
+ e.type != goog.events.EventType.KEYUP) {
+ // convert keydown to keypress for backward compatibility.
+ e.type = goog.events.EventType.KEYPRESS;
+ listenerFn.call(opt_scope, e);
+ } else if (e.keyCode == goog.events.KeyCodes.SPACE &&
+ e.type == goog.events.EventType.KEYUP &&
+ (role == goog.a11y.aria.Role.BUTTON ||
+ role == goog.a11y.aria.Role.TAB)) {
+ listenerFn.call(opt_scope, e);
+ e.preventDefault();
+ }
+ };
+ callback.listener_ = listener;
+ callback.scope_ = opt_scope;
+
+ if (opt_eventHandler) {
+ opt_eventHandler.listen(target,
+ goog.events.ActionEventWrapper_.EVENT_TYPES_,
+ callback, opt_capt);
+ } else {
+ goog.events.listen(target,
+ goog.events.ActionEventWrapper_.EVENT_TYPES_,
+ callback, opt_capt);
+ }
+};
+
+
+/**
+ * Removes an event listener added using goog.events.EventWrapper.listen.
+ *
+ * @param {goog.events.ListenableType} target The node to remove listener from.
+ * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
+ * method, or an object with a handleEvent function.
+ * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
+ * false).
+ * @param {Object=} opt_scope Element in whose scope to call the listener.
+ * @param {goog.events.EventHandler=} opt_eventHandler Event handler to remove
+ * listener from.
+ * @override
+ */
+goog.events.ActionEventWrapper_.prototype.unlisten = function(target, listener,
+ opt_capt, opt_scope, opt_eventHandler) {
+ for (var type, j = 0; type = goog.events.ActionEventWrapper_.EVENT_TYPES_[j];
+ j++) {
+ var listeners = goog.events.getListeners(target, type, !!opt_capt);
+ for (var obj, i = 0; obj = listeners[i]; i++) {
+ if (obj.listener.listener_ == listener &&
+ obj.listener.scope_ == opt_scope) {
+ if (opt_eventHandler) {
+ opt_eventHandler.unlisten(target, type, obj.listener, opt_capt,
+ opt_scope);
+ } else {
+ goog.events.unlisten(target, type, obj.listener, opt_capt, opt_scope);
+ }
+ break;
+ }
+ }
+ }
+};
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/events/actionhandler.js
----------------------------------------------------------------------
diff --git a/externs/GCL/externs/goog/events/actionhandler.js b/externs/GCL/externs/goog/events/actionhandler.js
new file mode 100644
index 0000000..190cc26
--- /dev/null
+++ b/externs/GCL/externs/goog/events/actionhandler.js
@@ -0,0 +1,184 @@
+// Copyright 2007 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 This file contains a class to provide a unified mechanism for
+ * CLICK and enter KEYDOWN events. This provides better accessibility by
+ * providing the given functionality to a keyboard user which is otherwise
+ * would be available only via a mouse click.
+ *
+ * If there is an existing CLICK listener or planning to be added as below -
+ *
+ * <code>this.eventHandler_.listen(el, CLICK, this.onClick_);<code>
+ *
+ * it can be replaced with an ACTION listener as follows:
+ *
+ * <code>this.eventHandler_.listen(
+ * new goog.events.ActionHandler(el),
+ * ACTION,
+ * this.onAction_);<code>
+ *
+ */
+
+goog.provide('goog.events.ActionEvent');
+goog.provide('goog.events.ActionHandler');
+goog.provide('goog.events.ActionHandler.EventType');
+goog.provide('goog.events.BeforeActionEvent');
+
+goog.require('goog.events');
+goog.require('goog.events.BrowserEvent');
+goog.require('goog.events.EventTarget');
+goog.require('goog.events.EventType');
+goog.require('goog.events.KeyCodes');
+goog.require('goog.userAgent');
+
+
+
+/**
+ * A wrapper around an element that you want to listen to ACTION events on.
+ * @param {Element|Document} element The element or document to listen on.
+ * @constructor
+ * @extends {goog.events.EventTarget}
+ * @final
+ */
+goog.events.ActionHandler = function(element) {
+ goog.events.EventTarget.call(this);
+
+ /**
+ * This is the element that we will listen to events on.
+ * @type {Element|Document}
+ * @private
+ */
+ this.element_ = element;
+
+ goog.events.listen(element, goog.events.ActionHandler.KEY_EVENT_TYPE_,
+ this.handleKeyDown_, false, this);
+ goog.events.listen(element, goog.events.EventType.CLICK,
+ this.handleClick_, false, this);
+};
+goog.inherits(goog.events.ActionHandler, goog.events.EventTarget);
+
+
+/**
+ * Enum type for the events fired by the action handler
+ * @enum {string}
+ */
+goog.events.ActionHandler.EventType = {
+ ACTION: 'action',
+ BEFOREACTION: 'beforeaction'
+};
+
+
+/**
+ * Key event type to listen for.
+ * @type {string}
+ * @private
+ */
+goog.events.ActionHandler.KEY_EVENT_TYPE_ = goog.userAgent.GECKO ?
+ goog.events.EventType.KEYPRESS :
+ goog.events.EventType.KEYDOWN;
+
+
+/**
+ * Handles key press events.
+ * @param {!goog.events.BrowserEvent} e The key press event.
+ * @private
+ */
+goog.events.ActionHandler.prototype.handleKeyDown_ = function(e) {
+ if (e.keyCode == goog.events.KeyCodes.ENTER ||
+ goog.userAgent.WEBKIT && e.keyCode == goog.events.KeyCodes.MAC_ENTER) {
+ this.dispatchEvents_(e);
+ }
+};
+
+
+/**
+ * Handles mouse events.
+ * @param {!goog.events.BrowserEvent} e The click event.
+ * @private
+ */
+goog.events.ActionHandler.prototype.handleClick_ = function(e) {
+ this.dispatchEvents_(e);
+};
+
+
+/**
+ * Dispatches BeforeAction and Action events to the element
+ * @param {!goog.events.BrowserEvent} e The event causing dispatches.
+ * @private
+ */
+goog.events.ActionHandler.prototype.dispatchEvents_ = function(e) {
+ var beforeActionEvent = new goog.events.BeforeActionEvent(e);
+
+ // Allow application specific logic here before the ACTION event.
+ // For example, Gmail uses this event to restore keyboard focus
+ if (!this.dispatchEvent(beforeActionEvent)) {
+ // If the listener swallowed the BEFOREACTION event, don't dispatch the
+ // ACTION event.
+ return;
+ }
+
+
+ // Wrap up original event and send it off
+ var actionEvent = new goog.events.ActionEvent(e);
+ try {
+ this.dispatchEvent(actionEvent);
+ } finally {
+ // Stop propagating the event
+ e.stopPropagation();
+ }
+};
+
+
+/** @override */
+goog.events.ActionHandler.prototype.disposeInternal = function() {
+ goog.events.ActionHandler.superClass_.disposeInternal.call(this);
+ goog.events.unlisten(this.element_, goog.events.ActionHandler.KEY_EVENT_TYPE_,
+ this.handleKeyDown_, false, this);
+ goog.events.unlisten(this.element_, goog.events.EventType.CLICK,
+ this.handleClick_, false, this);
+ delete this.element_;
+};
+
+
+
+/**
+ * This class is used for the goog.events.ActionHandler.EventType.ACTION event.
+ * @param {!goog.events.BrowserEvent} browserEvent Browser event object.
+ * @constructor
+ * @extends {goog.events.BrowserEvent}
+ * @final
+ */
+goog.events.ActionEvent = function(browserEvent) {
+ goog.events.BrowserEvent.call(this, browserEvent.getBrowserEvent());
+ this.type = goog.events.ActionHandler.EventType.ACTION;
+};
+goog.inherits(goog.events.ActionEvent, goog.events.BrowserEvent);
+
+
+
+/**
+ * This class is used for the goog.events.ActionHandler.EventType.BEFOREACTION
+ * event. BEFOREACTION gives a chance to the application so the keyboard focus
+ * can be restored back, if required.
+ * @param {!goog.events.BrowserEvent} browserEvent Browser event object.
+ * @constructor
+ * @extends {goog.events.BrowserEvent}
+ * @final
+ */
+goog.events.BeforeActionEvent = function(browserEvent) {
+ goog.events.BrowserEvent.call(this, browserEvent.getBrowserEvent());
+ this.type = goog.events.ActionHandler.EventType.BEFOREACTION;
+};
+goog.inherits(goog.events.BeforeActionEvent, goog.events.BrowserEvent);