Skip to content

Commit

Permalink
Add new concept of submacros and use it to implement a handful of ess…
Browse files Browse the repository at this point in the history
…ential macros: for-in, if, set.

git-svn-id: https://dev.helma.org/svn/helma-ng/trunk@9669 688a9155-6ab5-4160-a077-9df41f55a9e9
  • Loading branch information
Hannes Wallnoefer authored and Jan-Felix Wittmann committed Apr 25, 2009
1 parent 9a7deef commit cf32e4c
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 63 deletions.
20 changes: 10 additions & 10 deletions apps/demo/skins/skins.html
Original file line number Diff line number Diff line change
Expand Up @@ -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:https://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>

Expand Down
134 changes: 88 additions & 46 deletions modules/helma/skin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import('helma/filters', 'filters');
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);

Expand Down Expand Up @@ -46,7 +46,11 @@ function render(skinOrPath, context, scope) {
* @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;
Expand Down Expand Up @@ -76,7 +80,10 @@ function createSkin(resourceOrString, scope) {
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;
}

/**
Expand Down Expand Up @@ -121,20 +128,19 @@ function Skin(mainSkin, subSkins, parentSkin) {
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) {
Expand All @@ -150,12 +156,13 @@ function Skin(mainSkin, subSkins, parentSkin) {
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;
Expand All @@ -182,54 +189,89 @@ function Skin(mainSkin, subSkins, parentSkin) {
}
// 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]";
Expand Down
28 changes: 21 additions & 7 deletions src/org/helma/template/MacroTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
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
* and a map of named parameters.
*/
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;
Expand Down Expand Up @@ -80,7 +81,7 @@ public int jsGet_startLine() {
/**
* The name of the macro tag.
*/
public String jsGet_name() {
public Object jsGet_name() {
return name;
}

Expand All @@ -104,10 +105,7 @@ public Object jsGet_parameterNames() {
*/
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;
}
Expand Down Expand Up @@ -140,6 +138,22 @@ public boolean has(String name, Scriptable start) {
"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
Expand Down Expand Up @@ -193,7 +207,7 @@ protected void setName(String str) {
name = str;
}

public String getName() {
public Object getName() {
return name;
}

Expand Down

0 comments on commit cf32e4c

Please sign in to comment.