r9669 - in helma-ng/trunk: apps/demo/skins modules/helma src/org/helma/template

[email protected] Thu, 23 Apr 2009 21:54:26 +0200 (CEST)
Newsgroups gmane.comp.java.helma.cvs
Message-ID <20090423195426.919393D0D6@mia>
Author: hannes
Date: 2009-04-23 21:54:26 +0200 (Thu, 23 Apr 2009)
New Revision: 9669

Modified:
   helma-ng/trunk/apps/demo/skins/skins.html
   helma-ng/trunk/modules/helma/skin.js
   helma-ng/trunk/src/org/helma/template/MacroTag.java
Log:
Add new concept of submacros and use it to implement a handful of essential macros: for-in, if, set.

Details at http://dev.helma.org/trac/helma/changeset/9669

Modified: helma-ng/trunk/apps/demo/skins/skins.html
===================================================================
--- helma-ng/trunk/apps/demo/skins/skins.html	2009-04-23 19:54:23 UTC (rev 9668)
+++ helma-ng/trunk/apps/demo/skins/skins.html	2009-04-23 19:54:26 UTC (rev 9669)
@@ -15,55 +15,55 @@
 
 <p>And the following subskin in skins.html:</p>
 
-<pre class="code">&lt;% subskin 'hello' %&gt;
+<pre class="code">&lt;% subskin hello %&gt;
 Hello &lt;% name %&gt;!</pre>
 
 
 <p>Your basic skin rendering macro then looks like this:</p>
 
-<pre class="code">&lt;% render 'hello' %&gt;</pre>
+<pre class="code">&lt;% render hello %&gt;</pre>
 
 <p>And this is the output this produces:</p>
 
 <p style="background-color: pink;">
-    <% render 'hello' %>
+    <% render hello %>
 </p>
 
 <p>The render macro also lets you loop through arrays and other iterables:</p>
 
-<pre class="code">&lt;% render 'hello' on=&lt;% names %&gt; as='name' %&gt;</pre>
+<pre class="code">&lt;% for name in &lt;% names %&gt; render hello %&gt;</pre>
 
 <p>This is what you should get:</p>
 
 <p style="background-color: pink;">
-    <% render 'hello' on=<%names%> as='name' %>
+    <% for name in <% names %> render hello %>
 </p>
 
 
 You can also bind values to the macro context in the tag:
 
-<pre class="code">&lt;% render 'hello' bind={ name: "Julia" } %&gt;</pre>
+<pre class="code">&lt;% set {name: Julia} render hello %&gt;</pre>
 
 <p>This gets you</p>
 
 <p style="background-color: pink;">
-    <% render 'hello' bind={ name: 'Julia' } %>
+    <% set {name: Julia } render hello %>
 </p>
 
 <p>Or you can define the objects to loop through using object and array literals in the macro tag:</p>
 
-<pre class="code">&lt;% render 'hello' on=['Gerlinde', 'Thomas'] as='name' %&gt;</pre>
+<pre class="code">&lt;% for name in [Gerlinde, Ralph, Thomas] render hello %&gt;</pre>
 
 <p>Produces:</p>
         
 <p style="background-color: pink;">
-<% render 'hello' on=['Gerlinde', 'Thomas'] as='name'  %>
+<% for name in [Gerlinde, Ralph, Thomas] render hello %>
 </p>
         
 <p>Finally, <a href="http://dev.helma.org/ng/helma.filters/">filters</a> let you do all kinds of post-processing
 on the output of a macro.</p>
 
-<pre class="code">&lt;% render 'hello' | uppercase %&gt;</pre>
+<pre class="code">&lt;% render hello | uppercase %&gt;</pre>
 
 <p>Renders:</p>
 

Modified: helma-ng/trunk/modules/helma/skin.js
===================================================================
--- helma-ng/trunk/modules/helma/skin.js	2009-04-23 19:54:23 UTC (rev 9668)
+++ helma-ng/trunk/modules/helma/skin.js	2009-04-23 19:54:26 UTC (rev 9669)
@@ -6,11 +6,11 @@
 import('helma/logging', 'logging');
 import('helma/system', 'system');
 
-export('render',
-       'createSkin',
-       'Skin');
+export('render', 'createSkin', 'Skin');
 
+var __shared__ = true;
 var log = logging.getLogger(__name__);
+var skincache = false; // {}
 
 system.addHostObject(org.helma.template.MacroTag);
 
@@ -46,7 +46,11 @@
  * @param resource
  */
 function createSkin(resourceOrString, scope) {
-    log.debug("creating skin: " + resourceOrString);
+    if (this.skincache && resourceOrString in skincache) {
+        return skincache[resourceOrString];
+    }
+    if (log.isDebugEnabled())
+        log.debug("creating skin: " + resourceOrString);
     var mainSkin = [];
     var subSkins = {};
     var currentSkin = mainSkin;
@@ -76,7 +80,10 @@
     if (typeof(lastPart) === 'string' && lastPart.trim() === '') {
         mainSkin.pop();
     }
-    return new Skin(mainSkin, subSkins, parentSkin);
+    var skin = new Skin(mainSkin, subSkins, parentSkin);
+    if (skincache)
+        skincache[resourceOrString] = skin;
+    return skin;
 }
 
 /**
@@ -121,20 +128,19 @@
         return parts;
     };
 
-    var renderInternal = function renderInternal(parts, context) {
+    function renderInternal(parts, context) {
         // extend context by globally provided filters. user-provided filters
         // override globally defined ones
         context = Object.merge(context, filters);
         return [renderPart(part, context) for each (part in parts)].join('');
-    };
+    }
 
-    var renderPart = function renderPart(part, context) {
+    function renderPart(part, context) {
         return part instanceof MacroTag && part.name ?
-                evaluateMacro(part, context) :
-                part;
-    };
+                evaluateMacro(part, context) : part;
+    }
 
-    var evaluateMacro = function evaluateMacro(macro, context) {
+    function evaluateMacro(macro, context) {
         // evaluate the macro itself
         var value = evaluateExpression(macro, context, '_macro');
         if (value instanceof Array) {
@@ -150,12 +156,13 @@
             value = evaluateExpression(filter, context, '_filter', value);
         }
         return value
-    };
+    }
 
-    var evaluateExpression = function evaluateExpression(macro, context, suffix, value) {
-        log.debug('evaluating expression: ' + macro);
-        if (builtins[macro.name]) {
-            return builtins[macro.name](macro, context);
+    function evaluateExpression(macro, context, suffix, value) {
+        if (log.isDebugEnabled())
+            log.debug('evaluating expression: ' + macro);
+        if (builtin[macro.name]) {
+            return builtin[macro.name](macro, context);
         }
         var path = macro.name.split('.');
         var elem = context;
@@ -182,54 +189,89 @@
         }
         // TODO: if filter is not found just return value as is
         return value;
-    };
+    }
 
-    var isDefined = function isDefined(elem) {
+    function isDefined(elem) {
         return elem !== undefined && elem !== null;
-    };
+    }
 
-    var isVisible = function isVisible(elem) {
+    function isVisible(elem) {
         return elem !== undefined && elem !== null && elem !== '';
-    };
+    }
 
     // builtin macro handlers
-    var builtins = {
-        render: function builtinsRender(macro, context) {
+    var builtin = {
+        "render": function(macro, context) {
             var skin = getEvaluatedParameter(macro.getParameter(0), context, 'render:skin');
-            var bind = getEvaluatedParameter(macro.getParameter('bind'), context, 'render:bind');
-            var on = getEvaluatedParameter(macro.getParameter('on'), context, 'render:on');
-            var as = getEvaluatedParameter(macro.getParameter('as'), context, 'render:as');
+            return self.renderSubskin(skin, context);
+        },
+
+        "for": function(macro, context) {
+            if (macro.parameters.length < 4)
+                throw Error("not enough parameters in for-in macro");
+            if (macro.parameters[1] != "in")
+                throw Error("syntax error in for-in macro: expected in")
+            var name = getEvaluatedParameter(macro.parameters[0], context, 'for:name');
+            var list = getEvaluatedParameter(macro.parameters[2], context, 'for:list');
             var subContext = context.clone();
-            if (bind) {
-                for (var b in bind) {
-                    subContext[b] = getEvaluatedParameter(bind[b], context, 'render:bind:value');
-                }
+            var subMacro = macro.getSubMacro(3);
+            result = [];
+            for (var [index, value] in list) {
+                subContext['index'] = index
+                subContext[name] = getEvaluatedParameter(value, context, 'for:value');
+                result.push(evaluateMacro(subMacro, subContext));
             }
-            if (on) {
-                var result = [];
-                var subContext = context.clone();
-                for (var [key, value] in on) {
-                    log.debug("key: " + value);
-                    subContext[('key')] = key;
-                    subContext[(as || 'value')] = value;
-                    result.push(self.renderSubskin(skin, subContext));
-                }
-                return result.join('');
+            var separator = getEvaluatedParameter(macro.getParameter("separator"),
+                    context, 'for:separator');
+            if (separator != null) {
+                return result.join(separator);
+            }
+            return result;
+        },
+
+        "if": function(macro, context) {
+            if (macro.parameters.length < 2)
+                throw Error("not enough parameters in if macro");
+            var negated = (macro.parameters[0] == "not");
+            var condition;
+            if (negated) {
+                if (macro.parameters.length < 3)
+                    throw Error("not enough parameters in if macro");
+                condition = getEvaluatedParameter(macro.parameters[1], context, 'if:condition');
             } else {
-                return self.renderSubskin(skin, subContext);
+                condition = getEvaluatedParameter(macro.parameters[0], context, 'if:condition');
             }
+            if (negated ? !!condition : !condition) {
+                return "";
+            }
+            var subMacro = macro.getSubMacro(negated ? 2 : 1);
+            return evaluateMacro(subMacro, context);
+        },
+
+        "set": function(macro, context) {
+            if (macro.parameters.length < 2)
+                throw Error("not enough parameters in with macro");
+            var map = getEvaluatedParameter(macro.parameters[0], context, 'with:map');
+            var subContext = context.clone();
+            var subMacro = macro.getSubMacro(1);
+            for (var [key, value] in map) {
+                subContext[key] = getEvaluatedParameter(value, context, 'with:value');;
+            }
+            return evaluateMacro(subMacro, subContext);
         }
 
     };
 
-    var getEvaluatedParameter = function getEvaluatedParameter(value, context, logprefix) {
-        log.debug(logprefix + ': macro called with value: ' + value);
+    function getEvaluatedParameter(value, context, logprefix) {
+        if (log.isDebugEnabled())
+            log.debug(logprefix + ': macro called with value: ' + value);
         if (value instanceof MacroTag) {
             value = evaluateExpression(value, context, '_macro');
-            log.debug(logprefix + ': evaluated value macro, got ' + value);
+            if (log.isDebugEnabled())
+                log.debug(logprefix + ': evaluated value macro, got ' + value);
         }
         return value;
-    };
+    }
 
     this.toString = function toString() {
         return "[Skin Object]";

Modified: helma-ng/trunk/src/org/helma/template/MacroTag.java
===================================================================
--- helma-ng/trunk/src/org/helma/template/MacroTag.java	2009-04-23 19:54:23 UTC (rev 9668)
+++ helma-ng/trunk/src/org/helma/template/MacroTag.java	2009-04-23 19:54:26 UTC (rev 9669)
@@ -25,6 +25,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.LinkedList;
+import java.util.Iterator;
 
 /**
  * A macro tag. Basically a list of unnamed parameters
@@ -32,7 +33,7 @@
  */
 public class MacroTag extends ScriptableObject {
 
-    String name;
+    Object name;
     LinkedList<Object> args = new LinkedList<Object>();
     Map<String,Object> namedArgs = new CaseInsensitiveMap<String,Object>();
     MacroTag filter = null;
@@ -80,7 +81,7 @@
     /**
      * The name of the macro tag.
      */
-    public String jsGet_name() {
+    public Object jsGet_name() {
         return name;
     }
 
@@ -104,10 +105,7 @@
      */
     public Object jsGet_parameters() {
         if (jsParams == null) {
-            Context cx = Context.getCurrentContext();
-            int size = args.size();
-            Object[] values = args.toArray(new Object[size]);
-            jsParams = cx.newArray(getTopLevelScope(this), values);
+            jsParams = new ScriptableList(getTopLevelScope(this), args);
         }
         return jsParams;
     }
@@ -140,6 +138,22 @@
                 "parameterNames".equals(name) || super.has(name, start);
     }
 
+    public Object jsFunction_getSubMacro(int start) {
+        MacroTag submacro = new MacroTag(start);
+        submacro.setParentScope(getParentScope());
+        submacro.setPrototype(getPrototype());
+        submacro.filter = filter;
+        submacro.namedArgs = new CaseInsensitiveMap<String,Object>(namedArgs);
+        submacro.args = new LinkedList<Object>();
+        if (start + 1 < args.size()) {
+            for (Iterator i = args.listIterator(start + 1); i.hasNext(); ) {
+                submacro.args.add(i.next());
+            }
+        }
+        submacro.name = args.get(start);
+        return submacro;
+    }
+
     /**
      * Get a named or unnamed parameter from the macro tag. This method takes a variable
      * number of string or integer arguments. It evaluates the arguments as parameter
@@ -193,7 +207,7 @@
         name = str;
     }
 
-    public String getName() {
+    public Object getName() {
         return name;
     }