Revert "Re-working @UiField and Widget replacement to use DOM walking rather then IDs."
This reverts svn r7816, because its handling of tables isn't yet
robust enough. It will be back.
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7858 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt20_21userApi.conf b/tools/api-checker/config/gwt20_21userApi.conf
index 399a5fb..1959dcd 100644
--- a/tools/api-checker/config/gwt20_21userApi.conf
+++ b/tools/api-checker/config/gwt20_21userApi.conf
@@ -43,7 +43,6 @@
:com/google/gwt/rpc/client/impl/ClientWriterFactory.java\
:com/google/gwt/rpc/client/impl/EscapeUtil.java\
:com/google/gwt/rpc/linker/*.java\
-:com/google/gwt/uibinder/client/UiBinderUtil.java\
:com/google/gwt/uibinder/attributeparsers/*.java\
:com/google/gwt/uibinder/elementparsers/*.java\
:com/google/gwt/uibinder/testing/*.java\
diff --git a/user/src/com/google/gwt/dom/client/DOMImpl.java b/user/src/com/google/gwt/dom/client/DOMImpl.java
index da3117a..dc61ea2 100644
--- a/user/src/com/google/gwt/dom/client/DOMImpl.java
+++ b/user/src/com/google/gwt/dom/client/DOMImpl.java
@@ -240,23 +240,6 @@
return node.nodeType;
}-*/;
- /**
- * Get the child of a parent ignoring all text nodes that might be children of the parent.
- * Returns null if the index is out of bounds.
- */
- public Node getNonTextChild(Node parent, int index) {
- int i = 0;
- for (Node child = parent.getFirstChild(); child != null; child = child.getNextSibling()) {
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- if (i == index) {
- return child.cast();
- }
- i++;
- }
- }
- return null;
- }
-
public native Element getParentElement(Node node) /*-{
var parent = node.parentNode;
if (!parent || parent.nodeType != 1) {
@@ -378,6 +361,4 @@
public native String toString(Element elem) /*-{
return elem.outerHTML;
}-*/;
-
-
}
diff --git a/user/src/com/google/gwt/dom/client/DOMImplIE6.java b/user/src/com/google/gwt/dom/client/DOMImplIE6.java
index 2f75ce9..843f931 100644
--- a/user/src/com/google/gwt/dom/client/DOMImplIE6.java
+++ b/user/src/com/google/gwt/dom/client/DOMImplIE6.java
@@ -24,8 +24,6 @@
private static boolean isIE6;
private static boolean isIE6Detected;
-
-
/**
* Check if the browser is IE6 or IE7.
*
@@ -86,11 +84,6 @@
/ getZoomMultiple(doc) + doc.getScrollTop());
}
- @Override
- public native Node getNonTextChild(Node parent, int index) /*-{
- return parent.children[index];
- }-*/;
-
@Override
public int getScrollLeft(Element elem) {
if (isRTL(elem)) {
diff --git a/user/src/com/google/gwt/dom/client/Element.java b/user/src/com/google/gwt/dom/client/Element.java
index b157a94..d3b3692 100644
--- a/user/src/com/google/gwt/dom/client/Element.java
+++ b/user/src/com/google/gwt/dom/client/Element.java
@@ -719,6 +719,4 @@
// on some browsers.
this.title = title || '';
}-*/;
-
-
}
diff --git a/user/src/com/google/gwt/dom/client/Node.java b/user/src/com/google/gwt/dom/client/Node.java
index aea2ee1..44ff0c8 100644
--- a/user/src/com/google/gwt/dom/client/Node.java
+++ b/user/src/com/google/gwt/dom/client/Node.java
@@ -168,16 +168,6 @@
}-*/;
/**
- * Get the child by index ignoring all text node children.
- * Returns null if the index is out of bounds.
- *
- * @param index The child index ignoring text node children
- */
- public final Node getNonTextChild(int index) {
- return DOMImpl.impl.getNonTextChild(this, index);
- }
-
- /**
* The Document object associated with this node. This is also the
* {@link Document} object used to create new nodes.
*/
@@ -193,7 +183,7 @@
public final Element getParentElement() {
return DOMImpl.impl.getParentElement(this);
}
-
+
/**
* The parent of this node. All nodes except Document may have a parent.
* However, if a node has just been created and not yet added to the tree, or
diff --git a/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
index b6487f3..4b8c1ed 100644
--- a/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
+++ b/user/src/com/google/gwt/uibinder/client/UiBinderUtil.java
@@ -18,14 +18,67 @@
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.UIObject;
/**
* Static helper methods used by UiBinder. These methods are likely to move,
* so please don't use them for non-UiBinder code.
*/
public class UiBinderUtil {
+ /**
+ * Temporary attachment record that keeps track of where an element was
+ * before attachment. Use the detach method to put things back.
+ *
+ */
+ public static class TempAttachment {
+ private final Element element;
+ private final Element origParent;
+ private final Element origSibling;
+
+ private TempAttachment(Element origParent, Element origSibling,
+ Element element) {
+ this.origParent = origParent;
+ this.origSibling = origSibling;
+ this.element = element;
+ }
+
+ /**
+ * Restore to previous DOM state before attachment.
+ */
+ public void detach() {
+ // Put the panel's element back where it was.
+ if (origParent != null) {
+ origParent.insertBefore(element, origSibling);
+ } else {
+ orphan(element);
+ }
+ }
+ }
private static Element hiddenDiv;
+
+ /**
+ * Attaches the element to the dom temporarily. Keeps track of where it is
+ * attached so that things can be put back latter.
+ *
+ * @return attachment record which can be used for reverting back to previous
+ * DOM state
+ */
+ public static TempAttachment attachToDom(Element element) {
+ // TODO(rjrjr) This is copied from HTMLPanel. Reconcile
+ ensureHiddenDiv();
+
+ // Hang on to the panel's original parent and sibling elements so that it
+ // can be replaced.
+ Element origParent = element.getParentElement();
+ Element origSibling = element.getNextSiblingElement();
+
+ // Attach the panel's element to the hidden div.
+ hiddenDiv.appendChild(element);
+
+ return new TempAttachment(origParent, origSibling, element);
+ }
public static Element fromHtml(String html) {
ensureHiddenDiv();
@@ -34,57 +87,13 @@
orphan(newbie);
return newbie;
}
-
- public static Node getChild(Node node, int child) {
- return node.getChild(child);
- }
-
- public static Node getNonTextChild(Node node, int child) {
- return node.getNonTextChild(child);
- }
-
- public static Node getTableChild(Node node, int child) {
- // If the table, has a tbody inside...
- Element table = (Element)node;
- Element firstChild = table.getNonTextChild(0).cast();
- if ("tbody".equalsIgnoreCase(firstChild.getTagName())) {
- return firstChild.getNonTextChild(child);
- } else {
- return table.getNonTextChild(child);
- }
- }
-
- public static native Element lookupNodeByTreeIndicies(Element parent, String query,
- String xpath) /*-{
- if (parent.querySelector) {
- return parent.querySelector(query);
- } else {
- return parent.ownerDocument.evaluate(
- xpath, parent, null, XPathResult.ANY_TYPE, null).iterateNext();
- }
- }-*/;
-
- public static native Element lookupNodeByTreeIndiciesIE(Element parent, int[] indicies) /*-{
- var currentNode = parent;
- for(var i = 0; i < indicies.length; i = i + 1) {
- currentNode = currentNode.children[indicies[i]];
- }
- return currentNode;
-}-*/;
-
- public static native Element lookupNodeByTreeIndiciesUsingQuery(Element parent, String query) /*-{
- return parent.querySelector(query);
- }-*/;
-
- public static native Element lookupNodeByTreeIndiciesUsingXpath(Element parent, String xpath) /*-{
- return parent.ownerDocument.evaluate(
- xpath, parent, null, XPathResult.ANY_TYPE, null).iterateNext();
- }-*/;
-
+
private static void ensureHiddenDiv() {
// If the hidden DIV has not been created, create it.
if (hiddenDiv == null) {
- hiddenDiv = Document.get().createDivElement();
+ hiddenDiv = Document.get().createDivElement();
+ UIObject.setVisible(hiddenDiv, false);
+ RootPanel.getBodyElement().appendChild(hiddenDiv);
}
}
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/CustomButtonParser.java b/user/src/com/google/gwt/uibinder/elementparsers/CustomButtonParser.java
index 774e8c7..5fade12 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/CustomButtonParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/CustomButtonParser.java
@@ -17,7 +17,6 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
@@ -68,9 +67,7 @@
HtmlInterpreter interpreter = HtmlInterpreter.newInterpreterForUiObject(
writer, fieldName);
- DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
- String innerHtml = child.consumeInnerHtml(interpreter, cursor);
- writer.endDomSection();
+ String innerHtml = child.consumeInnerHtml(interpreter);
if (innerHtml.length() > 0) {
writer.addStatement("%s.%s().setHTML(\"%s\");", fieldName,
faceNameGetter(faceName), innerHtml);
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/DialogBoxParser.java b/user/src/com/google/gwt/uibinder/elementparsers/DialogBoxParser.java
index 36e2d54..00dfd26 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/DialogBoxParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/DialogBoxParser.java
@@ -17,7 +17,6 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
import com.google.gwt.user.client.ui.DialogBox;
@@ -41,9 +40,7 @@
HtmlInterpreter interpreter = HtmlInterpreter.newInterpreterForUiObject(
writer, fieldName);
- DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
- caption = child.consumeInnerHtml(interpreter, cursor);
- writer.endDomSection();
+ caption = child.consumeInnerHtml(interpreter);
} else {
if (body != null) {
writer.die("In %s, may have only one widget, but found %s and %s",
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/DomElementParser.java b/user/src/com/google/gwt/uibinder/elementparsers/DomElementParser.java
index 0bdc543..95d9745 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/DomElementParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/DomElementParser.java
@@ -17,7 +17,6 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
@@ -36,12 +35,10 @@
interpreter.interpretElement(elem);
- DomCursor cursor = writer.beginDomSection(fieldName);
-
- String html = elem.consumeOpeningTag() + elem.consumeInnerHtml(interpreter,
- cursor)
+ writer.beginAttachedSection(fieldName);
+ String html = elem.consumeOpeningTag() + elem.consumeInnerHtml(interpreter)
+ elem.getClosingTag();
- writer.endDomSection();
+ writer.endAttachedSection();
writer.setFieldInitializer(fieldName, String.format(
"(%1$s) UiBinderUtil.fromHtml(\"%2$s\")",
type.getQualifiedSourceName(), html));
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
index 71a6d18..8c2f3c1 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/FieldInterpreter.java
@@ -37,8 +37,17 @@
throws UnableToCompleteException {
String fieldName = writer.declareFieldIfNeeded(elem);
if (fieldName != null) {
- writer.declareDomField(fieldName, elem.getLocalName());
- }
+ String token = writer.declareDomField(fieldName, element);
+
+ if (elem.hasAttribute("id")) {
+ writer.die(String.format(
+ "Cannot declare id=\"%s\" and %s=\"%s\" on the same element",
+ elem.consumeRawAttribute("id"), writer.getUiFieldAttributeName(),
+ fieldName));
+ }
+
+ elem.setAttribute("id", token);
+ }
/*
* Return null because we don't want to replace the dom element with any
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java
index e8d0991..83d65f7 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HTMLPanelParser.java
@@ -18,7 +18,6 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.uibinder.elementparsers.HtmlMessageInterpreter.PlaceholderInterpreterProvider;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
import com.google.gwt.uibinder.rebind.messages.MessageWriter;
@@ -46,10 +45,10 @@
*/
HtmlInterpreter htmlInterpreter = makeHtmlInterpreter(fieldName, writer);
- DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
+ writer.beginAttachedSection(fieldName + ".getElement()");
String html = elem.consumeInnerHtml(InterpreterPipe.newPipe(
- widgetInterpreter, htmlInterpreter), cursor);
- writer.endDomSection();
+ widgetInterpreter, htmlInterpreter));
+ writer.endAttachedSection();
/*
* HTMLPanel has no no-arg ctor, so we have to generate our own, using the
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HasHTMLParser.java b/user/src/com/google/gwt/uibinder/elementparsers/HasHTMLParser.java
index 80cdda9..601c03d 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HasHTMLParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HasHTMLParser.java
@@ -17,7 +17,6 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
@@ -29,12 +28,11 @@
public void parse(XMLElement elem, String fieldName, JClassType type,
UiBinderWriter writer) throws UnableToCompleteException {
- writer.addInitComment("HasHtmlParser.parse");
HtmlInterpreter interpreter =
HtmlInterpreter.newInterpreterForUiObject(writer, fieldName);
- DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
- String html = elem.consumeInnerHtml(interpreter, cursor);
- writer.endDomSection();
+ writer.beginAttachedSection(fieldName + ".getElement()");
+ String html = elem.consumeInnerHtml(interpreter);
+ writer.endAttachedSection();
// TODO(jgw): throw an error if there's a conflicting 'html' attribute.
if (html.trim().length() > 0) {
writer.genStringPropertySet(fieldName, "HTML", html);
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HtmlMessageInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/HtmlMessageInterpreter.java
index 24555fb..f21ac6d 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HtmlMessageInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HtmlMessageInterpreter.java
@@ -73,8 +73,7 @@
}
MessageWriter message = messages.newMessage(elem);
- message.setDefaultMessage(elem.consumeInnerHtml(phiProvider.get(message),
- uiWriter.getCurrentDomCursor()));
+ message.setDefaultMessage(elem.consumeInnerHtml(phiProvider.get(message)));
return uiWriter.tokenForExpression(messages.declareMessage(message));
}
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/HtmlPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/HtmlPlaceholderInterpreter.java
index 3966680..0819505 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/HtmlPlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/HtmlPlaceholderInterpreter.java
@@ -16,7 +16,6 @@
package com.google.gwt.uibinder.elementparsers;
import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
import com.google.gwt.uibinder.rebind.messages.MessageWriter;
@@ -65,15 +64,12 @@
* This recursive innerHtml call has already been escaped. Hide it in a
* token to avoid double escaping
*/
- DomCursor currentDomCursor = uiWriter.getCurrentDomCursor();
- String body = tokenator.nextToken(elem.consumeInnerHtml(this,
- currentDomCursor));
-
+ String body = tokenator.nextToken(elem.consumeInnerHtml(this));
+
String closeTag = elem.getClosingTag();
String closePlaceholder =
nextPlaceholder(name + "End", closeTag, closeTag);
- currentDomCursor.advanceChild();
return openPlaceholder + body + closePlaceholder;
}
@@ -83,8 +79,7 @@
@Override
protected String consumePlaceholderInnards(XMLElement elem)
throws UnableToCompleteException {
- return elem.consumeInnerHtml(fieldAndComputed,
- uiWriter.getCurrentDomCursor());
+ return elem.consumeInnerHtml(fieldAndComputed);
}
/**
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/StackLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/StackLayoutPanelParser.java
index 43384ad..7feb8ad 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/StackLayoutPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/StackLayoutPanelParser.java
@@ -19,7 +19,6 @@
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JEnumType;
import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
import com.google.gwt.user.client.ui.StackLayoutPanel;
@@ -76,9 +75,7 @@
HtmlInterpreter htmlInt = HtmlInterpreter.newInterpreterForUiObject(
writer, fieldName);
String size = children.header.consumeRequiredDoubleAttribute("size");
- DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
- String html = children.header.consumeInnerHtml(htmlInt, cursor);
- writer.endDomSection();
+ String html = children.header.consumeInnerHtml(htmlInt);
writer.addStatement("%s.add(%s, \"%s\", true, %s);", fieldName,
childFieldName, html, size);
} else if (children.customHeader != null) {
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/TabLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/TabLayoutPanelParser.java
index cb3aceb..a6880a9 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/TabLayoutPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/TabLayoutPanelParser.java
@@ -19,7 +19,6 @@
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JEnumType;
import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
import com.google.gwt.user.client.ui.TabLayoutPanel;
@@ -81,9 +80,7 @@
if (children.header != null) {
HtmlInterpreter htmlInt = HtmlInterpreter.newInterpreterForUiObject(
writer, fieldName);
- DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
- String html = children.header.consumeInnerHtml(htmlInt, cursor);
- writer.endDomSection();
+ String html = children.header.consumeInnerHtml(htmlInt);
writer.addStatement("%s.add(%s, \"%s\", true);", fieldName,
childFieldName, html);
} else if (children.customHeader != null) {
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/TabPanelParser.java b/user/src/com/google/gwt/uibinder/elementparsers/TabPanelParser.java
index 45902b8..627512b 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/TabPanelParser.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/TabPanelParser.java
@@ -17,7 +17,6 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
@@ -60,9 +59,7 @@
if (tabChild.getLocalName().equals(TAG_TABHTML)) {
HtmlInterpreter interpreter = HtmlInterpreter.newInterpreterForUiObject(
writer, fieldName);
- DomCursor cursor = writer.beginDomSection(fieldName + ".getElement()");
- tabHTML = tabChild.consumeInnerHtml(interpreter, cursor);
- writer.endDomSection();
+ tabHTML = tabChild.consumeInnerHtml(interpreter);
} else {
if (childFieldName != null) {
writer.die("%s may only have a single child widget", child);
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
index 6edc8e4..686cb2a 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetInterpreter.java
@@ -68,26 +68,30 @@
public String interpretElement(XMLElement elem)
throws UnableToCompleteException {
- if (uiWriter.isWidgetElement(elem)) {
-
- String tag = getLegalPlaceholderTag(elem);
-
+ if (uiWriter.isWidgetElement(elem)) {
+ // Allocate a local variable to hold the dom id for this widget. Note
+ // that idHolder is a local variable reference, not a string id. We
+ // have to generate the ids at runtime, not compile time, or else
+ // we'll reuse ids for any template rendered more than once.
+ String idHolder = uiWriter.declareDomIdHolder();
String childField = uiWriter.parseElementToField(elem);
+ uiWriter.ensureFieldAttached(fieldName);
- String elementPointer = "element" + uiWriter.getUniqueId();
+ String elementPointer = idHolder + "Element";
uiWriter.addInitStatement(
- "com.google.gwt.user.client.Element %s = %s;",
- elementPointer, uiWriter.getDomAccessExpression(elementPointer, tag));
-
- uiWriter.addInitStatement(
+ "com.google.gwt.user.client.Element %s = " +
+ "com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
+ elementPointer, idHolder);
+ // Delay replacing the placeholders with the widgets until after
+ // detachment so as not to end up attaching the widget to the DOM
+ // unnecessarily
+ uiWriter.addDetachStatement(
"%1$s.addAndReplaceElement(%2$s, %3$s);",
fieldName, childField, elementPointer);
-
- // Increment DOM cursor based on the tag we are adding.
- uiWriter.getCurrentDomCursor().advanceChild();
// Create an element to hold the widget.
- return "<" + tag + "></" + tag + ">";
+ String tag = getLegalPlaceholderTag(elem);
+ return "<" + tag + " id='\" + " + idHolder + " + \"'></" + tag + ">";
}
return null;
}
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
index 23a65a1..4080258 100644
--- a/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
+++ b/user/src/com/google/gwt/uibinder/elementparsers/WidgetPlaceholderInterpreter.java
@@ -18,7 +18,6 @@
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.uibinder.rebind.DomCursor;
import com.google.gwt.uibinder.rebind.UiBinderWriter;
import com.google.gwt.uibinder.rebind.XMLElement;
import com.google.gwt.uibinder.rebind.messages.MessageWriter;
@@ -62,10 +61,10 @@
private int serial = 0;
private final String ancestorExpression;
private final String fieldName;
- private final Map<String, XMLElement> elementHolderToWidgetElement =
- new HashMap<String, XMLElement>();
- private final Set<String> elementIsHasHTML = new HashSet<String>();
- private final Set<String> elementIsHasText = new HashSet<String>();
+ private final Map<String, XMLElement> idToWidgetElement =
+ new HashMap<String, XMLElement>();
+ private final Set<String> idIsHasHTML = new HashSet<String>();
+ private final Set<String> idIsHasText = new HashSet<String>();
WidgetPlaceholderInterpreter(String fieldName, UiBinderWriter writer,
MessageWriter message, String ancestorExpression) {
@@ -82,7 +81,6 @@
return super.interpretElement(elem);
}
- uiWriter.addInitComment("WidgetPlaceholderInterpreter.interpretElement");
JClassType type = uiWriter.findFieldType(elem);
TypeOracle oracle = uiWriter.getOracle();
@@ -92,46 +90,39 @@
name = "widget" + (++serial);
}
- DomCursor cursor = uiWriter.getCurrentDomCursor();
- String elementHolder = cursor.getAccessExpression();
- // We are going to generate another element right here, so we advance the child pointer.
- cursor.advanceChild();
- elementHolderToWidgetElement.put(elementHolder, elem);
+ String idHolder = uiWriter.declareDomIdHolder();
+ idToWidgetElement.put(idHolder, elem);
if (oracle.findType(HasHTML.class.getName()).isAssignableFrom(type)) {
- return handleHasHTMLPlaceholder(elem, name, elementHolder);
+ return handleHasHTMLPlaceholder(elem, name, idHolder);
}
if (oracle.findType(HasText.class.getName()).isAssignableFrom(type)) {
- return handleHasTextPlaceholder(elem, name, elementHolder);
+ return handleHasTextPlaceholder(elem, name, idHolder);
}
- return handleOpaqueWidgetPlaceholder(name);
+ return handleOpaqueWidgetPlaceholder(name, idHolder);
}
-
/**
* Called by {@link XMLElement#consumeInnerHtml} after all elements
* have been handed to {@link #interpretElement}.
*/
@Override
public String postProcess(String consumed) throws UnableToCompleteException {
- for (Map.Entry<String, XMLElement> entry : elementHolderToWidgetElement.entrySet()) {
- String element = entry.getKey();
- XMLElement childElem = entry.getValue();
+ for (String idHolder : idToWidgetElement.keySet()) {
+ XMLElement childElem = idToWidgetElement.get(idHolder);
String childField = uiWriter.parseElementToField(childElem);
- genSetWidgetTextCall(element, childField);
-
- uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, " +
- "(com.google.gwt.user.client.Element)%3$s);",
- fieldName, childField, element);
+ genSetWidgetTextCall(idHolder, childField);
+ uiWriter.addInitStatement("%1$s.addAndReplaceElement(%2$s, %3$s);",
+ fieldName, childField, idHolder);
}
/*
* We get used recursively, so this will be called again. Empty the map
* or else we'll re-register things.
*/
- elementHolderToWidgetElement.clear();
+ idToWidgetElement.clear();
return super.postProcess(consumed);
}
@@ -141,36 +132,34 @@
return closePlaceholder;
}
- private String genOpenTag(String name) {
- String openTag = "<span>";
+ private String genOpenTag(String name, String idHolder) {
+ String openTag = String.format("<span id='\" + %s + \"'>", idHolder);
String openPlaceholder =
nextPlaceholder(name + "Begin", "<span>", openTag);
return openPlaceholder;
}
- private void genSetWidgetTextCall(String elementHolder, String childField) {
- if (elementIsHasText.contains(elementHolder)) {
+ private void genSetWidgetTextCall(String idHolder, String childField) {
+ if (idIsHasText.contains(idHolder)) {
uiWriter.addInitStatement(
- "%s.setText(((com.google.gwt.user.client.Element)%s).getInnerText());",
- childField, elementHolder);
+ "%s.setText(%s.getElementById(%s).getInnerText());", childField,
+ fieldName, idHolder);
}
- if (elementIsHasHTML.contains(elementHolder)) {
+ if (idIsHasHTML.contains(idHolder)) {
uiWriter.addInitStatement(
- "%s.setHTML(((com.google.gwt.user.client.Element)%s).getInnerHTML());",
- childField, elementHolder);
+ "%s.setHTML(%s.getElementById(%s).getInnerHTML());", childField,
+ fieldName, idHolder);
}
}
private String handleHasHTMLPlaceholder(XMLElement elem, String name,
- String elementHolder) throws UnableToCompleteException {
- elementIsHasHTML.add(elementHolder);
- String openPlaceholder = genOpenTag(name);
+ String idHolder) throws UnableToCompleteException {
+ idIsHasHTML.add(idHolder);
+ String openPlaceholder = genOpenTag(name, idHolder);
- DomCursor cursor = uiWriter.beginDomSection(elementHolder);
String body =
elem.consumeInnerHtml(new HtmlPlaceholderInterpreter(uiWriter,
- message, ancestorExpression), cursor);
- uiWriter.endDomSection();
+ message, ancestorExpression));
String bodyToken = tokenator.nextToken(body);
String closePlaceholder = genCloseTag(name);
@@ -178,9 +167,9 @@
}
private String handleHasTextPlaceholder(XMLElement elem, String name,
- String elementHolder) throws UnableToCompleteException {
- elementIsHasText.add(elementHolder);
- String openPlaceholder = genOpenTag(name);
+ String idHolder) throws UnableToCompleteException {
+ idIsHasText.add(idHolder);
+ String openPlaceholder = genOpenTag(name, idHolder);
String body =
elem.consumeInnerText(new TextPlaceholderInterpreter(uiWriter,
@@ -191,8 +180,8 @@
return openPlaceholder + bodyToken + closePlaceholder;
}
- private String handleOpaqueWidgetPlaceholder(String name) {
- String tag = "<span></span>";
+ private String handleOpaqueWidgetPlaceholder(String name, String idHolder) {
+ String tag = String.format("<span id='\" + %s + \"'></span>", idHolder);
String placeholder = nextPlaceholder(name, "<span></span>", tag);
return placeholder;
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/DomCursor.java b/user/src/com/google/gwt/uibinder/rebind/DomCursor.java
deleted file mode 100644
index 76e3f00..0000000
--- a/user/src/com/google/gwt/uibinder/rebind/DomCursor.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- *
- * 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.
- */
-package com.google.gwt.uibinder.rebind;
-
-import com.google.gwt.core.ext.UnableToCompleteException;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * DOM Cursor keeps track of a given path in the DOM. This is useful for
- * plucking out nodes from a DOM tree.
- */
-public class DomCursor {
-
- private static class PathComponent {
-
- private int childIndex;
- private boolean isTableWithoutTbody;
-
- public PathComponent(XMLElement element) {
- this.childIndex = 0;
- this.isTableWithoutTbody = "table".equalsIgnoreCase(element.getLocalName());
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (!(obj instanceof PathComponent)) return false;
- PathComponent other = (PathComponent) obj;
- if (childIndex != other.childIndex) return false;
- if (isTableWithoutTbody != other.isTableWithoutTbody) return false;
- return true;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + childIndex;
- result = prime * result + (isTableWithoutTbody ? 1231 : 1237);
- return result;
- }
-
- public void incrementIndex() {
- childIndex++;
- }
- }
-
- private class ParagraphTracking {
- private LinkedList<Boolean> paragraphUnSafeNodes = new LinkedList<Boolean>();
-
- public void beginUnsafeTag() {
- paragraphUnSafeNodes.addLast(true);
- }
-
- public void endUnsafeTag() {
- paragraphUnSafeNodes.removeLast();
- }
-
- public boolean isSafeForField() {
- return paragraphUnSafeNodes.isEmpty();
- }
- }
-
- // Tags that cause P tags to end weirdly violating XHTML
- // http://dev.w3.org/html5/spec/Overview.html#parsing-main-inbody
- private static final Set<String> PARAGRAPH_UNSAFE_NODES = new HashSet<String>(Arrays.asList(
- new String[] {"address", "article", "aside", "blockquote", "center", "details", "dir", "div",
- "dl", "fieldset", "figure", "footer", "header", "hgroup", "menu", "nav", "ol", "p", "section",
- "ul"}));
- private static final Set<String> TABLE_SECTIONS = new HashSet<String>(Arrays.asList(new String[] {
- "thead", "tbody", "tfoot"}));
- private static final String NON_TEXT_LOOKUP_METHOD = "UiBinderUtil.getNonTextChild";
- private static final String STANDARD_LOOKUP_METHOD = "UiBinderUtil.getChild";
- private static final String TABLE_NON_TEXT_LOOKUP_METHOD = "UiBinderUtil.getTableChild";
-
- // Cache of path to a local variable that contains that.
- private Map<LinkedList<PathComponent>, String> domPathCache =
- new HashMap<LinkedList<PathComponent>, String>();
- private LinkedList<ParagraphTracking> paragraphs = new LinkedList<ParagraphTracking>();
- private final String parent;
- private LinkedList<PathComponent> pathComponents = new LinkedList<PathComponent>();
- private boolean preserveWhitespaceNodes = true;
- private boolean walkingTextChildNodes = false;
- private final UiBinderWriter writer;
-
- public DomCursor(String parent, UiBinderWriter writer) {
- this.parent = parent;
- this.writer = writer;
- }
-
- public void advanceChild() {
- pathComponents.getLast().incrementIndex();
- writer.addInitComment("advance DomCursor %s", this.toString());
- }
-
- public void advanceChildForWhitespaceText() {
- if (preserveWhitespaceNodes) {
- advanceTextChild();
- }
- }
-
- public void advanceTextChild() {
- if (walkingTextChildNodes) {
- advanceChild();
- }
- }
-
- /**
- * Finish visiting this subtree of the DOM.
- */
- public void finishChild(XMLElement elem) {
- if (!writer.getMessages().isMessage(elem) &&
- !isPlaceholderElement(elem)) {
- pathComponents.removeLast();
- writer.addInitComment("finish DomCursor %s", this.toString());
- String tag = elem.getLocalName();
- if ("p".equalsIgnoreCase(tag)) {
- endParagraph();
- }
- if (isInsideParagraph() && isUnsafeParagraphTag(tag)) {
- paragraphs.getLast().endUnsafeTag();
- }
- }
- }
-
-
- public String getAccessExpression() throws UnableToCompleteException {
- return getAccessExpression(null, null);
- }
-
- /**
- * Returns a Java expression for referencing the given node.
- *
- * @param localVar Optional variable that will be used to cache the result of the expression.
- * If there is no applicable local variable, callers can pass null.
- *
- * @param tag Optional tag name of the tag that would be referenced. This can be null.
- * @return Java access expression.
- * @throws UnableToCompleteException
- */
- public String getAccessExpression(String localVar, String tag)
- throws UnableToCompleteException {
- // P tags cause all sorts of problems with hierarchy as the HTML spec has lots of weird
- // semantics for block elements inside of P tags.
- if (!safeForExpression()) {
- writer.die("UiBinder no longer allows certain addressable " +
- "elements inside of <p> tags because of browser " +
- "inconsistency, consider using DIV instead");
- }
-
- String result = getDomWalkAccessExpression(tag);
- if (localVar != null) {
- domPathCache.put(new LinkedList<PathComponent>(pathComponents), localVar);
- return result;
- }
-
- String varName = "intermediate" + writer.getUniqueId();
- writer.addInitStatement("com.google.gwt.dom.client.Node %s = %s;", varName, result);
- domPathCache.put(new LinkedList<PathComponent>(pathComponents), varName);
- return varName;
- }
-
- /**
- * Visit a child subtree of the DOM.
- * @throws UnableToCompleteException
- */
- public void visitChild(XMLElement elem) throws UnableToCompleteException {
- if (!writer.getMessages().isMessage(elem) &&
- !isPlaceholderElement(elem)) {
- // If we do see an actual tbody in the uibinder, we should stop trying to account for the
- // automatic one that the browser will insert.
- String tag = elem.getLocalName();
- if ("tbody".equalsIgnoreCase(tag)) {
- pathComponents.getLast().isTableWithoutTbody = false;
- }
- if ("td".equalsIgnoreCase(tag) && !"tr".equalsIgnoreCase(elem.getParent().getLocalName())) {
- writer.die("TD tags must be inside of a TR tag");
- }
- pathComponents.addLast(new PathComponent(elem));
- writer.addInitComment("visit DomCursor %s", this.toString());
-
- if (isInsideParagraph() && isUnsafeParagraphTag(tag)) {
- paragraphs.getLast().beginUnsafeTag();
- }
- if ("p".equalsIgnoreCase(tag)) {
- beginParagraph();
- }
- }
- }
-
- private void beginParagraph() {
- paragraphs.addLast(new ParagraphTracking());
- }
-
- private void endParagraph() {
- paragraphs.removeLast();
- }
-
- private String getDomWalkAccessExpression(String tag) {
- writer.addInitComment("DomWalkAccess %s", this.toString());
-
- // First look and see if we have any part of the path in our variable cache.
- int end = pathComponents.size();
- String var = null;
- for (; end > 0; --end) {
- var = domPathCache.get(pathComponents.subList(0, end));
- if (var != null) {
- break;
- }
- }
-
- // Next, do the remaining DOM walking
- StringBuilder builder = new StringBuilder();
- if (var != null) {
- builder.append(var);
- } else {
- builder.append(parent);
- }
- for (int i = end; i < pathComponents.size(); ++i) {
- PathComponent component = pathComponents.get(i);
- if (i < (pathComponents.size() - 1)) {
- // For partial paths, create an intermediate variable that can be reused
- // by other elements that need to walk.
- String varName = "intermediate" + writer.getUniqueId();
- writer.addInitStatement("com.google.gwt.dom.client.Node %s = %s(%s, %d);",
- varName, getLookupMethod(component, tag), builder.toString(),
- component.childIndex);
- domPathCache.put(new LinkedList<PathComponent>(pathComponents.subList(0, i + 1)), varName);
- builder = new StringBuilder(varName);
- } else {
- builder.insert(0, getLookupMethod(component, tag) + "(");
- builder.append(", ").append(component.childIndex).append(")");
- }
- }
- builder.append(".cast()");
- return builder.toString();
- }
-
- private String getLookupMethod(PathComponent component, String tag) {
- if (walkingTextChildNodes) {
- return STANDARD_LOOKUP_METHOD;
- }
- if (component.isTableWithoutTbody && !isValidDirectTableChild(tag)) {
- return TABLE_NON_TEXT_LOOKUP_METHOD;
- }
- return NON_TEXT_LOOKUP_METHOD;
- }
-
- private String getQuery() {
- StringBuilder query = new StringBuilder();
-
- for (PathComponent component : pathComponents) {
- if (query.length() > 0) {
- query.append(" > ");
- }
- query.append(":nth-child(").append(component.childIndex + 1).append(")");
- }
- return query.toString();
- }
-
- /**
- * Get an access expression using XPATH or CSS query. This is currently disabled as it isn't
- * as fast as walking by hand.
- * @return
- */
- private String getQueryAccessExpression() {
- StringBuilder builder = new StringBuilder("UiBinderUtil.lookupNodeByTreeIndicies(");
- builder.append(parent);
- builder.append(",\"");
- builder.append(getQuery());
- builder.append("\",\"");
- builder.append(getXpath());
- builder.append("\").cast()");
- return builder.toString();
- }
-
- private String getXpath() {
- StringBuilder xpath = new StringBuilder();
- for (PathComponent component : pathComponents) {
- xpath.append("/*[").append(component.childIndex + 1).append("]");
- }
- return xpath.toString();
- }
-
- private boolean isInsideParagraph() {
- return !paragraphs.isEmpty();
- }
-
- private boolean isPlaceholderElement(XMLElement elem) {
- return "ph".equalsIgnoreCase(elem.getLocalName());
- }
-
- private boolean isUnsafeParagraphTag(String tag) {
- return PARAGRAPH_UNSAFE_NODES.contains(tag.toLowerCase());
- }
-
- private boolean isValidDirectTableChild(String tag) {
- return tag != null && TABLE_SECTIONS.contains(tag.toLowerCase());
- }
-
- private boolean safeForExpression() {
- for (ParagraphTracking tracking : paragraphs) {
- if (!tracking.isSafeForField()) {
- return false;
- }
- }
- return true;
- }
-}
diff --git a/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
index 5fc6d17..4eae5dd 100644
--- a/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
+++ b/user/src/com/google/gwt/uibinder/rebind/GetInnerHtmlVisitor.java
@@ -1,12 +1,12 @@
/*
* Copyright 2008 Google Inc.
- *
+ *
* 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
@@ -19,62 +19,38 @@
import com.google.gwt.uibinder.rebind.XMLElement.Interpreter;
import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.Text;
class GetInnerHtmlVisitor extends GetEscapedInnerTextVisitor {
/**
* Recursively gathers an HTML representation of the children of the given
- * Element, and stuffs it into the given StringBuffer. Applies the interpreter to
+ * Elem, and stuffs it into the given StringBuffer. Applies the interpreter to
* each descendant, and uses the writer to report errors.
*/
public static void getEscapedInnerHtml(Element elem, StringBuffer buffer,
- Interpreter<String> interpreter, XMLElementProvider writer, DomCursor domCursor,
- MortalLogger logger)
- throws UnableToCompleteException {
- XMLElement xmlElement = writer.get(elem);
- domCursor.visitChild(xmlElement);
- new ChildWalker().accept(elem, new GetInnerHtmlVisitor(buffer, interpreter, writer, domCursor,
- logger));
- domCursor.finishChild(xmlElement);
+ Interpreter<String> interpreter, XMLElementProvider writer)
+ throws UnableToCompleteException {
+ new ChildWalker().accept(elem, new GetInnerHtmlVisitor(buffer, interpreter,
+ writer));
}
-
- private DomCursor domCursor;
- private final MortalLogger logger;
- private GetInnerHtmlVisitor(StringBuffer buffer, Interpreter<String> interpreter,
- XMLElementProvider writer, DomCursor domCursor, MortalLogger logger) {
+ private GetInnerHtmlVisitor(StringBuffer buffer,
+ Interpreter<String> interpreter, XMLElementProvider writer) {
super(buffer, interpreter, writer);
- this.domCursor = domCursor;
- this.logger = logger;
}
-
+
@Override
public void visitElement(Element elem) throws UnableToCompleteException {
XMLElement xmlElement = elementProvider.get(elem);
String replacement = interpreter.interpretElement(xmlElement);
-
if (replacement != null) {
buffer.append(replacement);
return;
}
- Node parent = elem.getParentNode();
- buffer.append(xmlElement.consumeOpeningTag());
- getEscapedInnerHtml(elem, buffer, interpreter, elementProvider, domCursor, logger);
- buffer.append(xmlElement.getClosingTag());
- domCursor.advanceChild();
- }
-
- @Override
- public void visitText(Text t) {
- int startLength = buffer.length();
- super.visitText(t);
- if (buffer.length() != startLength && !buffer.toString().matches("^\\s*$")) {
- if (buffer.toString().substring(startLength).matches("^\\s*$")) {
- domCursor.advanceTextChild();
- }
- }
+ // TODO(jgw): Ditch the closing tag when there are no children.
+ buffer.append(xmlElement.consumeOpeningTag());
+ getEscapedInnerHtml(elem, buffer, interpreter, elementProvider);
+ buffer.append(xmlElement.getClosingTag());
}
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index a7ba90a..a013d27 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -105,7 +105,7 @@
text = text.replaceAll(">", ">");
if (!preserveWhitespace) {
- text = text.replaceAll("\\s+", " ");
+ text = text.replaceAll("\\s+", " ");
}
return escapeTextForJavaStringLiteral(text);
@@ -201,13 +201,22 @@
private String gwtPrefix;
private String rendered;
-
+
/**
* Stack of element variable names that have been attached.
*/
- private final LinkedList<DomCursor> domSectionElements =
- new LinkedList<DomCursor>();
-
+ private final LinkedList<String> attachSectionElements = new LinkedList<String>();
+ /**
+ * Maps from field element name to the temporary attach record variable name.
+ */
+ private final Map<String, String> attachedVars = new HashMap<String, String>();
+ private int nextAttachVar = 0;
+
+ /**
+ * Stack of statements to be executed after we detach the current attach
+ * section.
+ */
+ private final LinkedList<List<String>> detachStatementsStack = new LinkedList<List<String>>();
private final AttributeParsers attributeParsers;
private final BundleAttributeParsers bundleParsers;
@@ -258,13 +267,18 @@
}
/**
- * Adds a comment in the current init stream. Useful for debugging generated code.
+ * Add a statement to be executed right after the current attached element is
+ * detached. This is useful for doing things that might be expensive while the
+ * element is attached to the DOM.
+ *
* @param format
- * @param params
+ * @param args
+ * @see #beginAttachedSection(String)
*/
- public void addInitComment(String format, Object... params) {
- addInitStatement("// " + format, params);
+ public void addDetachStatement(String format, Object... args) {
+ detachStatementsStack.getFirst().add(String.format(format, args));
}
+
/**
* Add a statement to be run after everything has been instantiated, in the
* style of {@link String#format}.
@@ -282,35 +296,50 @@
}
/**
- * Begin a section where a new DOM tree is being parsed--that is,
- * one that will be constructed as a big innerHTML string, and then walked to
- * allow fields accessing its to be filled (at the
+ * Begin a section where a new attachable element is being parsed--that is,
+ * one that will be constructed as a big innerHTML string, and then briefly
+ * attached to the dom to allow fields accessing its to be filled (at the
* moment, HasHTMLParser, HTMLPanelParser, and DomElementParser.).
* <p>
+ * Succeeding calls made to {@link #ensureAttached} and
+ * {@link #ensureFieldAttached} must refer to children of this element, until
+ * {@link #endAttachedSection} is called.
*
* @param element Java expression for the generated code that will return the
- * DOM element to be attached.
+ * dom element to be attached.
*/
- public DomCursor beginDomSection(String element) {
- DomCursor cursor = new DomCursor(element, this);
- domSectionElements.addFirst(cursor);
- addInitComment("writer.beginAttachedSection %s", cursor.toString());
- return cursor;
+ public void beginAttachedSection(String element) {
+ attachSectionElements.addFirst(element);
+ detachStatementsStack.addFirst(new ArrayList<String>());
}
/**
- * Declare a field that will hold an Element instance.
+ * Declare a field that will hold an Element instance. Returns a token that
+ * the caller must set as the id attribute of that element in whatever
+ * innerHTML expression will reproduce it at runtime.
+ * <P>
+ * In the generated code, this token will be replaced by an expression to
+ * generate a unique dom id at runtime. Further code will be generated to be
+ * run after widgets are instantiated, to use that dom id in a getElementById
+ * call and assign the Element instance to its field.
*
* @param fieldName The name of the field being declared
- * @param tag The tag name of the DOM element that is being referenced.
- * @throws UnableToCompleteException
+ * @param parentElementExpression an expression to be evaluated at runtime,
+ * which will return an Element that is an ancestor of this one
+ * (needed by the getElementById call mentioned above).
*/
- public void declareDomField(String fieldName, String tag) throws UnableToCompleteException {
+ public String declareDomField(String fieldName, String parentElementExpression)
+ throws UnableToCompleteException {
+ ensureAttached(parentElementExpression);
+ String name = declareDomIdHolder();
setFieldInitializer(fieldName, "null");
- addInitStatement("%s = %s;", fieldName,
- domSectionElements.getFirst().getAccessExpression(fieldName, tag));
+ addInitStatement(
+ "%s = com.google.gwt.dom.client.Document.get().getElementById(%s).cast();",
+ fieldName, name);
+ addInitStatement("%s.removeAttribute(\"id\");", fieldName);
+ return tokenForExpression(name);
}
-
+
/**
* Declare a variable that will be filled at runtime with a unique id, safe
* for use as a dom element's id attribute.
@@ -318,15 +347,13 @@
* @return that variable's name.
*/
public String declareDomIdHolder() throws UnableToCompleteException {
- String domHolderName = "domId" + getUniqueId();
+ String domHolderName = "domId" + domId++;
FieldWriter domField = fieldManager.registerField(
oracle.findType(String.class.getName()), domHolderName);
domField.setInitializer("com.google.gwt.dom.client.Document.get().createUniqueId()");
return domHolderName;
}
-
-
/**
* Declares a field of the given type name, returning the name of the declared
* field. If the element has a field or id attribute, use its value.
@@ -400,11 +427,48 @@
* End the current attachable section. This will detach the element if it was
* ever attached and execute any detach statements.
*
- * @see #beginDomSection(String)
+ * @see #beginAttachedSection(String)
*/
- public void endDomSection() {
- DomCursor cursor = domSectionElements.removeFirst();
- addInitComment("writer.endAttachedSection %s", cursor.toString());
+ public void endAttachedSection() {
+ String elementVar = attachSectionElements.removeFirst();
+ List<String> detachStatements = detachStatementsStack.removeFirst();
+ if (attachedVars.containsKey(elementVar)) {
+ String attachedVar = attachedVars.remove(elementVar);
+ addInitStatement("%s.detach();", attachedVar);
+ for (String statement : detachStatements) {
+ addInitStatement(statement);
+ }
+ }
+ }
+
+ /**
+ * Ensure that the specified element is attached to the DOM.
+ *
+ * @param element variable name of element to be attached
+ * @see #beginAttachedSection(String)
+ */
+ public void ensureAttached(String element) {
+ String attachSectionElement = attachSectionElements.getFirst();
+ if (!attachedVars.containsKey(attachSectionElement)) {
+ String attachedVar = "attachRecord" + nextAttachVar;
+ addInitStatement(
+ "UiBinderUtil.TempAttachment %s = UiBinderUtil.attachToDom(%s);",
+ attachedVar, attachSectionElement);
+ attachedVars.put(attachSectionElement, attachedVar);
+ nextAttachVar++;
+ }
+ }
+
+ /**
+ * Ensure that the specified field is attached to the DOM. The field must hold
+ * an object that responds to Element getElement(). Convenience wrapper for
+ * {@link ensureAttached}<code>(field + ".getElement()")</code>.
+ *
+ * @param field variable name of the field to be attached
+ * @see #beginAttachedSection(String)
+ */
+ public void ensureFieldAttached(String field) {
+ ensureAttached(field + ".getElement()");
}
/**
@@ -472,35 +536,14 @@
public ImplicitClientBundle getBundleClass() {
return bundleClass;
}
-
- /**
- * Get the current cursor for the DOM. This can be used for accessing expressions, or walking
- * subtrees.
- */
- public DomCursor getCurrentDomCursor() {
- return domSectionElements.getFirst();
- }
/**
- * Returns an expression for accessing the current position in the DOM.
- * @param varName optional variable where the result of this expression will be stored for
- * cacheing
- * @param tag optional name of the HTML tag we are using the expression for.
- * @throws UnableToCompleteException
- */
- public String getDomAccessExpression(String varName, String tag)
- throws UnableToCompleteException {
- addInitComment("getDomAccessExpression %s", domSectionElements.getFirst().toString());
- return domSectionElements.getFirst().getAccessExpression(varName, tag);
- }
-
- /**
* @return The logger, at least until we get get it handed off to parsers via
* constructor args.
*/
public MortalLogger getLogger() {
return logger;
- }
+ }
/**
* Get the {@link MessagesWriter} for this UI, generating it if necessary.
@@ -524,10 +567,6 @@
return gwtPrefix + ":field";
}
- public int getUniqueId() {
- return domId++;
- }
-
public boolean isBinderElement(XMLElement elem) {
String uri = elem.getNamespaceUri();
return uri != null && UiBinderGenerator.BINDER_URI.equals(uri);
@@ -684,11 +723,15 @@
*
* @throws UnableToCompleteException
*/
- private void ensureCursorCleanedUp() {
- if (!domSectionElements.isEmpty()) {
+ private void ensureAttachmentCleanedUp() throws UnableToCompleteException {
+ if (!attachSectionElements.isEmpty()) {
throw new IllegalStateException("Attachments not cleaned up: "
- + domSectionElements);
- }
+ + attachSectionElements);
+ }
+ if (!detachStatementsStack.isEmpty()) {
+ throw new IllegalStateException("Detach not cleaned up: "
+ + detachStatementsStack);
+ }
}
/**
@@ -854,7 +897,7 @@
new PrintWriter(stringWriter));
writeBinder(niceWriter, rootField);
- ensureCursorCleanedUp();
+ ensureAttachmentCleanedUp();
return stringWriter.toString();
}
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
index ae1c1db..b9b9377 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -379,15 +379,13 @@
* @param interpreter Called for each element, expected to return a string
* replacement for it, or null if it should be left as is
*/
- public String consumeInnerHtml(Interpreter<String> interpreter,
- DomCursor domCursor)
+ public String consumeInnerHtml(Interpreter<String> interpreter)
throws UnableToCompleteException {
if (interpreter == null) {
throw new NullPointerException("interpreter must not be null");
}
StringBuffer buf = new StringBuffer();
- GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, provider,
- domCursor, logger);
+ GetInnerHtmlVisitor.getEscapedInnerHtml(elem, buf, interpreter, provider);
clearChildren(elem);
return buf.toString().trim();
@@ -397,11 +395,9 @@
* Refines {@link #consumeInnerHtml(Interpreter)} to handle
* PostProcessingInterpreter.
*/
- public String consumeInnerHtml(PostProcessingInterpreter<String> interpreter,
- DomCursor domCursor)
+ public String consumeInnerHtml(PostProcessingInterpreter<String> interpreter)
throws UnableToCompleteException {
- String html = consumeInnerHtml((Interpreter<String>) interpreter,
- domCursor);
+ String html = consumeInnerHtml((Interpreter<String>) interpreter);
return interpreter.postProcess(html);
}
diff --git a/user/test/com/google/gwt/dom/client/ElementTest.java b/user/test/com/google/gwt/dom/client/ElementTest.java
index cb7dfc6..5806456 100644
--- a/user/test/com/google/gwt/dom/client/ElementTest.java
+++ b/user/test/com/google/gwt/dom/client/ElementTest.java
@@ -286,23 +286,6 @@
assertEquals("foo", nodes.getItem(0).getInnerText());
assertEquals("bar", nodes.getItem(1).getInnerText());
}
-
- public void testGetNonTextElement() {
- DivElement div = Document.get().createDivElement();
- Text text1 = Document.get().createTextNode("my text");
- DivElement innerDiv = Document.get().createDivElement();
- Text text2 = Document.get().createTextNode(" ");
- SpanElement span = Document.get().createSpanElement();
- Text text3 = Document.get().createTextNode("my text2");
- div.appendChild(text1);
- div.appendChild(innerDiv);
- div.appendChild(text2);
- div.appendChild(span);
- div.appendChild(text3);
-
- assertEquals(innerDiv, div.getNonTextChild(0));
- assertEquals(span, div.getNonTextChild(1));
- }
public void testHasAttribute() {
DivElement div = Document.get().createDivElement();
diff --git a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
index 58e834c..e828dba 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -31,7 +31,6 @@
import com.google.gwt.uibinder.elementparsers.StackLayoutPanelParserTest;
import com.google.gwt.uibinder.elementparsers.TabLayoutPanelParserTest;
import com.google.gwt.uibinder.elementparsers.UIObjectParserTest;
-import com.google.gwt.uibinder.rebind.DomCursorTest;
import com.google.gwt.uibinder.rebind.FieldWriterOfGeneratedCssResourceTest;
import com.google.gwt.uibinder.rebind.GwtResourceEntityResolverTest;
import com.google.gwt.uibinder.rebind.HandlerEvaluatorTest;
@@ -52,7 +51,6 @@
TestSuite suite = new TestSuite("UiBinder tests that require the JRE");
// rebind
- suite.addTestSuite(DomCursorTest.class);
suite.addTestSuite(FieldWriterOfGeneratedCssResourceTest.class);
suite.addTestSuite(GwtResourceEntityResolverTest.class);
suite.addTestSuite(HandlerEvaluatorTest.class);
diff --git a/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java b/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java
index 1fbfe60..4eded61 100644
--- a/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java
+++ b/user/test/com/google/gwt/uibinder/client/UiBinderUtilTest.java
@@ -97,10 +97,10 @@
}
private void findAndAssertTextBeforeFirstChild(Element div, String id,
- String firstText) {
- Document.get().getBody().appendChild(div);
+ String firstText) {
+ UiBinderUtil.TempAttachment t = UiBinderUtil.attachToDom(div);
Element child = Document.get().getElementById(id);
- Document.get().getBody().removeChild(div);
+ t.detach();
assertStartsWith(child.getInnerHTML(), firstText + "<");
}
@@ -146,9 +146,9 @@
findAndAssertTextBeforeFirstChild(div, ableId, ableText);
findAndAssertTextBeforeFirstChild(div, bakerId, bakerText);
findAndAssertTextBeforeFirstChild(div, charlieId, charlieText);
- Document.get().getBody().appendChild(div);
+ UiBinderUtil.TempAttachment t = UiBinderUtil.attachToDom(div);
Element e = Document.get().getElementById(deltaId);
- Document.get().getBody().removeChild(div);
+ t.detach();
assertEquals(deltaText, e.getInnerText());
} finally {
// tearDown isn't reliable enough, e.g. doesn't fire when exceptions
diff --git a/user/test/com/google/gwt/uibinder/rebind/DomCursorTest.java b/user/test/com/google/gwt/uibinder/rebind/DomCursorTest.java
deleted file mode 100644
index 4a620a1..0000000
--- a/user/test/com/google/gwt/uibinder/rebind/DomCursorTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2008 Google Inc.
- *
- * 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.
- */
-package com.google.gwt.uibinder.rebind;
-
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
-
-import junit.framework.TestCase;
-
-import org.easymock.EasyMock;
-import org.easymock.IAnswer;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-
-/**
- * Test for DomCuror.
- */
-public class DomCursorTest extends TestCase {
-
- private static final String PARENT = "parent";
-
- private UiBinderWriter writer;
- private DomCursor cursor;
-
- @Override
- public void setUp() {
- writer = org.easymock.classextension.EasyMock.createMock(
- UiBinderWriter.class);
- org.easymock.classextension.EasyMock.expect(writer.getUniqueId()).andStubAnswer(new IAnswer<Integer>() {
- private int nextId = 1;
- public Integer answer() {
- return nextId++;
- }
- });
- MessagesWriter message = new MessagesWriter("ui", null, "", "", "");
- org.easymock.classextension.EasyMock.expect(writer.getMessages()).andStubReturn(message);
- writer.addInitComment((String) EasyMock.notNull(), EasyMock.notNull());
- org.easymock.classextension.EasyMock.expectLastCall().asStub();
- cursor = new DomCursor(PARENT, writer);
- }
-
- public void testAccessExpressions() throws Exception {
- verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(parent, 0).cast()", 1);
- verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(parent, 1).cast()", 2);
- verifyInitAssignment(writer,
- "UiBinderUtil.getNonTextChild(intermediate2, 0).cast()", 3);
- verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(parent, 2).cast()", 4);
- org.easymock.classextension.EasyMock.replay(writer);
-
- // parent
- cursor.visitChild(makeElement("div"));
- // parent's first child
- assertEquals(intermediate(1), cursor.getAccessExpression());
-
- cursor.advanceChild();
- assertEquals(intermediate(2), cursor.getAccessExpression());
- // intermediate 2, parent's second child
- XMLElement span = makeElement("span");
- cursor.visitChild(span);
- // intermediate 3, intermediate 2's first child
- assertEquals(intermediate(3), cursor.getAccessExpression());
- cursor.finishChild(span);
-
- cursor.advanceChild();
- // intermediate 4, parent's third child
- assertEquals(intermediate(4), cursor.getAccessExpression());
-
- org.easymock.classextension.EasyMock.verify(writer);
- }
-
- public void testParagraphsWith() throws Exception {
- writer.die((String) EasyMock.anyObject());
- org.easymock.classextension.EasyMock.expectLastCall().andThrow(new UnableToCompleteException());
- org.easymock.classextension.EasyMock.replay(writer);
-
- cursor.visitChild(makeElement("p"));
- XMLElement span = makeElement("span");
- cursor.visitChild(span);
- cursor.finishChild(span);
- cursor.visitChild(makeElement("div"));
-
- try {
- cursor.getAccessExpression();
- fail("Expected exception about block elements inside paragraphs");
- } catch (Exception e) {
- // Expected
- }
- org.easymock.classextension.EasyMock.verify(writer);
- }
-
- public void testTables() throws UnableToCompleteException {
-
- verifyInitAssignment(writer, intermediate(1), "UiBinderUtil.getNonTextChild", "parent", 0);
- verifyInitAssignment(writer, "UiBinderUtil.getTableChild(intermediate1, 0).cast()", 2);
- verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(intermediate2, 0).cast()", 3);
- verifyInitAssignment(writer, intermediate(4), "UiBinderUtil.getNonTextChild", "parent", 1);
- verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(intermediate4, 0).cast()", 5);
- verifyInitAssignment(writer, "UiBinderUtil.getNonTextChild(intermediate5, 0).cast()", 6);
-
- org.easymock.classextension.EasyMock.replay(writer);
- XMLElement div = makeElement("div");
- cursor.visitChild(div);
-
- XMLElement table1 = makeElement("table");
- cursor.visitChild(table1);
- assertEquals(intermediate(2), cursor.getAccessExpression());
-
- XMLElement tr = makeElement("tr");
- cursor.visitChild(tr);
- assertEquals(intermediate(3), cursor.getAccessExpression());
-
- cursor.finishChild(tr);
- cursor.finishChild(table1);
- cursor.advanceChild();
- XMLElement table2 = makeElement("table");
- cursor.visitChild(table2);
-
- XMLElement tbody = makeElement("tbody");
- cursor.visitChild(tbody);
- cursor.finishChild(tbody);
- assertEquals(intermediate(5), cursor.getAccessExpression());
-
- XMLElement tr2 = makeElement("tr");
- cursor.visitChild(tr2);
- assertEquals(intermediate(6), cursor.getAccessExpression());
- cursor.finishChild(tr2);
-
- XMLElement td = makeElement("td");
- try {
- cursor.visitChild(td);
- fail("Expected exception about tds inside tables without trs");
- } catch (Exception e) {
- // expected
- }
- org.easymock.classextension.EasyMock.verify(writer);
- }
-
- private String intermediate(int count) {
- return "intermediate" + count;
- }
-
- private XMLElement makeElement(String tag) {
- NamedNodeMap attributes = EasyMock.createNiceMock(NamedNodeMap.class);
- Element element = EasyMock.createNiceMock(Element.class);
- EasyMock.expect(element.getLocalName()).andStubReturn(tag);
- EasyMock.expect(element.getTagName()).andStubReturn(tag);
- EasyMock.expect(element.getAttributes()).andStubReturn(attributes);
- EasyMock.replay(element, attributes);
- return new XMLElement(element, null, null, null, null, null);
- }
-
- private void verifyInitAssignment(UiBinderWriter writer, String expr, int intermediateCount) {
- writer.addInitStatement("com.google.gwt.dom.client.Node %s = %s;",
- "intermediate" + intermediateCount, expr);
- }
-
- private void verifyInitAssignment(UiBinderWriter writer, String var, String method, String parent,
- int index) {
- writer.addInitStatement("com.google.gwt.dom.client.Node %s = %s(%s, %d);", var, method, parent,
- index);
- }
-}