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 [email protected] (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 [email protected] (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 [email protected] (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 [email protected] (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);
