You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by aw...@apache.org on 2009/05/30 00:33:33 UTC
svn commit: r780131 - in /incubator/shindig/trunk/java:
gadgets/src/main/java/org/apache/shindig/gadgets/templates/
gadgets/src/test/java/org/apache/shindig/gadgets/templates/
server/src/test/java/org/apache/shindig/server/endtoend/
server/src/test/res...
Author: awiner
Date: Fri May 29 22:33:32 2009
New Revision: 780131
URL: http://svn.apache.org/viewvc?rev=780131&view=rev
Log:
Add support for @oncreate and @x-oncreate to server-side templates
- Didn't make the cut for the spec, but often quite useful
- Patch from Lev Epshteyn, with modifications:
- Don't support mixed-case variants (we don't support them for anything else)
- Fix attribute mutation during iteration
- Add end-to-end test verifying this actually works
Modified:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java
incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java?rev=780131&r1=780130&r2=780131&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessor.java Fri May 29 22:33:32 2009
@@ -81,6 +81,10 @@
private static final Set<String> HTML4_BOOLEAN_ATTRIBUTES =
ImmutableSet.of("checked", "compact", "declare", "defer", "disabled", "ismap",
"multiple", "nohref", "noresize", "noshade", "nowrap", "readonly", "selected");
+
+ private static final Set<String> ONCREATE_ATTRIBUTES =
+ ImmutableSet.of("oncreate", "x-oncreate");
+
private final Expressions expressions;
// Reused buffer for creating template output
private final StringBuilder outputBuffer;
@@ -89,6 +93,8 @@
private TemplateContext templateContext;
private ELContext elContext;
+ private int uniqueIdCounter = 0;
+
@Inject
public DefaultTemplateProcessor(Expressions expressions) {
this.expressions = expressions;
@@ -345,9 +351,14 @@
}
clearSpecialAttributes(resultNode);
- processAttributes(resultNode);
+ Node additionalNode = processAttributes(resultNode);
+
processChildNodes(resultNode, element);
- result.appendChild(resultNode);
+ result.appendChild(resultNode);
+
+ if (additionalNode != null) {
+ result.appendChild(additionalNode);
+ }
}
if (curAttribute != null) {
@@ -366,11 +377,19 @@
/**
* Process expressions on attributes.
* @param element The Element to process attributes on
+ * @return Node to attach after this Element, or null
*/
- private void processAttributes(Element element) {
+ private Node processAttributes(Element element) {
NamedNodeMap attributes = element.getAttributes();
+ Node additionalNode = null;
+
+ // Mutations to perform after iterating (if needed)
List<Attr> attrsToRemove = null;
+ String newId = null;
+
for (int i = 0; i < attributes.getLength(); i++) {
+ boolean removeThisAttribute = false;
+
Attr attribute = (Attr) attributes.item(i);
// Boolean attributes: evaluate as a boolean. If true, set the value to the
// name of the attribute, e.g. selected="selected". If false, remove the attribute
@@ -383,24 +402,75 @@
if (Boolean.TRUE.equals(evaluate(attribute.getValue(), Boolean.class, Boolean.FALSE))) {
attribute.setNodeValue(attribute.getName());
} else {
- // Because NamedNodeMaps are live, removing them interferes with iteration.
- // Remove the attributes in a later pass
- if (attrsToRemove == null) {
- attrsToRemove = Lists.newArrayListWithCapacity(attributes.getLength());
- }
-
- attrsToRemove.add(attribute);
+ removeThisAttribute = true;
}
- } else {
+ } else if (ONCREATE_ATTRIBUTES.contains(attribute.getName())) {
+ String id = element.getAttribute("id");
+ if (id.length() == 0) {
+ newId = id = getUniqueId();
+ }
+
+ additionalNode = buildOnCreateScript(
+ evaluate(attribute.getValue(), String.class, null), id, element.getOwnerDocument());
+ removeThisAttribute = true;
+ } else {
attribute.setNodeValue(evaluate(attribute.getValue(), String.class, null));
}
+
+ // Because NamedNodeMaps are live, removing them interferes with iteration.
+ // Remove the attributes in a later pass
+ if (removeThisAttribute) {
+ if (attrsToRemove == null) {
+ attrsToRemove = Lists.newArrayListWithCapacity(attributes.getLength());
+ }
+
+ attrsToRemove.add(attribute);
+ }
}
+ // Now that iteration is complete, perform mutations
if (attrsToRemove != null) {
for (Attr attr : attrsToRemove) {
element.removeAttributeNode(attr);
}
}
+
+ if (newId != null) {
+ element.setAttribute("id", newId);
+ }
+
+ return additionalNode;
+ }
+
+ /**
+ * Inserts an inline script element that executes a snippet of Javascript
+ * code after the element is emitted.
+ * <p>
+ * The approach used involves using Javascript to find the previous sibling
+ * node and apply the code to it - this avoids decorating nodes with IDs, an
+ * approach that could potentially clash with existing element IDs that could
+ * be non-unique.
+ * <p>
+ * The resulting script element is subject to sanitization.
+ * <p>
+ * @param code Javascript code to execute
+ * @param id Element ID which should be used
+ * @param document document for creating elements
+ *
+ * TODO: Move boilerplate code for finding the right node out to a function
+ * to reduce code size.
+ */
+ private Node buildOnCreateScript(String code, String id, Document document) {
+ Element script = document.createElement("script");
+ script.setAttribute("type", "text/javascript");
+ StringBuilder builder = new StringBuilder();
+ builder.append("(function(){");
+ builder.append(code);
+ builder.append("}).apply(document.getElementById('");
+ builder.append(id);
+ builder.append("'));");
+ script.setTextContent(builder.toString());
+ return script;
}
/**
@@ -428,7 +498,7 @@
return defaultValue;
}
}
-
+
/**
* Coerce objects to iterables. Iterables and JSONArrays have the obvious
* coercion. JSONObjects are coerced to single-element lists, unless
@@ -491,4 +561,8 @@
return ImmutableList.of(value);
}
+
+ private String getUniqueId() {
+ return "ostid" + (uniqueIdCounter++);
+ }
}
Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java?rev=780131&r1=780130&r2=780131&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java Fri May 29 22:33:32 2009
@@ -19,22 +19,20 @@
package org.apache.shindig.gadgets.templates;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import org.apache.shindig.common.xml.XmlUtil;
import org.apache.shindig.expressions.Expressions;
import org.apache.shindig.expressions.RootELResolver;
-import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.parse.DefaultHtmlSerializer;
import org.apache.shindig.gadgets.parse.ParseModule;
import org.apache.shindig.gadgets.parse.nekohtml.SocialMarkupHtmlParser;
import org.apache.shindig.gadgets.render.SanitizingGadgetRewriter;
-
-
-import org.json.JSONObject;
import org.json.JSONArray;
+import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Document;
@@ -212,6 +210,22 @@
assertEquals("<input class=\"false\" disabled=\"disabled\">", output);
}
+ @Test
+ public void testOnCreate() throws Exception {
+ String output = executeTemplate("<span oncreate=\"foo\"></span>");
+ assertEquals("<span id=\"ostid0\"></span><script type=\"text/javascript\">" +
+ "(function(){foo}).apply(document.getElementById('ostid0'));</script>", output);
+
+ output = executeTemplate("<span x-oncreate=\"foo\"></span>");
+ assertEquals("<span id=\"ostid1\"></span><script type=\"text/javascript\">" +
+ "(function(){foo}).apply(document.getElementById('ostid1'));</script>", output);
+
+ output = executeTemplate("<span id=\"bar\" oncreate=\"foo\"></span>");
+ assertEquals("<span id=\"bar\"></span><script type=\"text/javascript\">" +
+ "(function(){foo}).apply(document.getElementById('bar'));</script>", output);
+
+ }
+
/**
* Ensure that the element cloning handling of processChildren correctly
* copies and element to the target element, including making sure that
Modified: incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java?rev=780131&r1=780130&r2=780131&view=diff
==============================================================================
--- incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java (original)
+++ incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndTest.java Fri May 29 22:33:32 2009
@@ -245,7 +245,11 @@
Element textPipeline = page.getElementById("text");
assertEquals("{\"key\": \"value\"}", textPipeline.getTextContent().trim());
- }
+
+ // Test of oncreate
+ Element oncreateSpan = page.getElementById("mutate");
+ assertEquals("mutated", oncreateSpan.getTextContent().trim());
+}
@Test
public void testTemplateLibrary() throws Exception {
Modified: incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml?rev=780131&r1=780130&r2=780131&view=diff
==============================================================================
--- incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml (original)
+++ incubator/shindig/trunk/java/server/src/test/resources/endtoend/templateRewriter.xml Fri May 29 22:33:32 2009
@@ -53,6 +53,7 @@
<span id="json">${json.content.key}</span>
<span id="text">${text.content}</span>
+ <span id="mutate" oncreate="this.innerHTML='mutated'"></span>
</script>
]]>
</Content>