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>