Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions handlebars/src/main/java/com/github/jknack/handlebars/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ protected BlockParam(final Context parent, final Map<String, Object> hash) {
this.extendedContext.resolver = parent.resolver;
this.parent = parent;
this.data = parent.data;
this.internalData = parent.internalData;
this.resolver = parent.resolver;
}

Expand Down Expand Up @@ -152,6 +153,7 @@ protected PartialCtx(final Context parent, final Object model, final Map<String,
this.extendedContext.extendedContext = new Context(Collections.emptyMap());
this.parent = parent;
this.data = parent.data;
this.internalData = parent.internalData;
this.resolver = parent.resolver;
}

Expand Down Expand Up @@ -432,6 +434,11 @@ public Object eval(final ValueResolver resolver, final Context context, final Ob
*/
protected Map<String, Object> data;

/**
* Functions similarly to data, but not resolved during rendering.
*/
protected Map<String, Object> internalData;

/**
* Additional, data can be stored here.
*/
Expand Down Expand Up @@ -470,6 +477,7 @@ private static Context root(final Object model) {
root.data.put(INLINE_PARTIALS, partials);
root.data.put(INVOCATION_STACK, new LinkedList<TemplateSource>());
root.data.put("root", model);
root.internalData = new HashMap<>();
return root;
}

Expand Down Expand Up @@ -535,6 +543,41 @@ public Context data(final Map<String, ?> attributes) {
return this;
}

/**
* Read the attribute from the internal data storage.
*
* @param name The attribute's name.
* @param <T> Data type.
* @return The attribute value or null.
*/
@SuppressWarnings("unchecked")
public <T> T internalData(final String name) {
return (T) internalData.get(name);
}

/**
* Set an attribute in the internal data storage.
*
* @param name The attribute's name. Required.
* @param value The attribute's value. Required.
* @return This context.
*/
public Context internalData(final String name, final Object value) {
internalData.put(name, value);
return this;
}

/**
* Store the map in the internal data storage.
*
* @param attributes The attributes to add. Required.
* @return This context.
*/
public Context internalData(final Map<String, ?> attributes) {
internalData.putAll(attributes);
return this;
}

/**
* Resolved as '.' or 'this' inside templates.
*
Expand Down Expand Up @@ -692,6 +735,9 @@ public void destroy() {
if (data != null) {
data.clear();
}
if (internalData != null) {
internalData.clear();
}
}
if (extendedContext != null) {
extendedContext.destroy();
Expand Down Expand Up @@ -790,6 +836,7 @@ private Context newChild(final Object model) {
child.setResolver(this.resolver);
child.parent = this;
child.data = this.data;
child.internalData = this.internalData;
return child;
}

Expand All @@ -813,6 +860,7 @@ protected Context newChildContext(final Object model) {
public static Context copy(final Context context, final Object model) {
Context ctx = Context.newContext(model);
ctx.data = context.data;
ctx.internalData = context.internalData;
ctx.resolver = context.resolver;
return ctx;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.github.jknack.handlebars;

import com.github.jknack.handlebars.context.FieldValueResolver;
import com.github.jknack.handlebars.context.JavaBeanValueResolver;
import com.github.jknack.handlebars.context.MapValueResolver;
import com.github.jknack.handlebars.context.MethodValueResolver;
import java.io.IOException;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class InternalDataTest extends AbstractTest {

@Override
protected Handlebars newHandlebars() {
Handlebars handlebars = new Handlebars();
handlebars.registerHelper("printFooAndBar", (context, options) ->
String.format("%s %s", options.context.data("foo"), options.context.internalData("bar")));
return handlebars;
}

@Override
protected Object configureContext(final Object model) {
return Context.newBuilder(model)
.resolver(
MapValueResolver.INSTANCE,
JavaBeanValueResolver.INSTANCE,
FieldValueResolver.INSTANCE,
MethodValueResolver.INSTANCE)
.build()
.data("foo", "foo")
.internalData("bar", "bar");
}

@Test
public void dataAvailableForRendering() throws IOException {
shouldCompileTo("{{foo}}", "", "foo");
assertEquals("foo", ((Context)configureContext("")).get("foo"));
}

@Test
public void internalDataNotAvailableForRendering() throws IOException {
shouldCompileTo("{{bar}}", "", "");
shouldCompileTo("{{./bar}}", "", "");
shouldCompileTo("{{../bar}}", "", "");
shouldCompileTo("{{.././bar}}", "", "");
shouldCompileTo("{{this.bar}}", "", "");
shouldCompileTo("{{internalData}}", "", "");
shouldCompileTo("{{internalData.bar}}", "", "");
shouldCompileTo("{{this.internalData}}", "", "");
shouldCompileTo("{{this.internalData.bar}}", "", "");
assertNull(((Context)configureContext("")).get("bar"));
}

@Test
public void helperAbleToAccessInternalData() throws IOException {
shouldCompileTo("{{printFooAndBar}}", "", "foo bar");
}
}