Handlebars.java

Logic-less and semantic templates with Java


by Edgar Espina Theme by mattgraham

The {{ Mustache }} Context Stack in Handlebars.java

In this section you will learn how to interact with the Context Stack in Handlebars.java.

The Context Stack

Mustache is a contextual and stack based template engine. The context stack is where the your data live.

The Context class represent the context stack in Handlebars.java. You rarely need to instantiate aContextobject, bc Handlebars.java does it for you every time you call:

  String output = template.apply(model);
  // or
  template.apply(model, writer);

Theapplymethod accepts aContext or anObject. Theapply(Object)function, automatically convert the object into aContext.

Resolving values

The resolution algorithm looks something like:
  while (context != null && context.get("value") == null) {
    context = context.parent;
  }

A value will be resolved in the current context.

If the value isn't here, the parent context need to resolved the value.

If the value isn't in the whole stack, the value is resolved to null and nothing will be rendered.

Extending the context stack

A common use case in web applications is to access to the logged user from a web page.

A first try might looks like:
  User user = ...;
  Object model = ...;
  Map modelWithUser = new HashMap();
  modelWithUser.put("user", user);
  modelWithUser.put("model", model);

  template.apply(modelWithUser);

This isn't so bad but there are at least two drawbacks:

  1. You will need to write or add this snippet of code in every single page that want access to the current logged user.
  2. You are force to access to your data using the {{#model}}...{{/model}} expression.
A better approach is to use thecombinemethod:
  User user = ...;
  Object model = ...;
  Context context = Context
    .newBuilder(model)
    .combine("user", user)
    .build();

  template.apply(context);

We definitely fix the second drawback described before.

The first one depends on your application architecture. For example, if you have ahookmethod where you can globally merge the model with a view (and of course you can access to the current logged user).

Value resolvers

AValueResolveris responsible of extracting a single value from a source object. By default, only mapsandJavaBeanobjects are supported.

The JavaBeanValueResolver

Let you access to JavaBean properties. It is registered by default.

  Context context = Context
    .newBuilder(model)
    .resolver(JavaBeanValueResolver.INSTANCE)
    .build();

  template.apply(context);

The MapValueResolver

Let you access tomapvalues. It is registered by default.

  Context context = Context
    .newBuilder(model)
    .resolver(MapValueResolver.INSTANCE)
    .build();

  template.apply(context);

The FieldValueResolver

Let you access to instance fields (private or public).

  Context context = Context
    .newBuilder(model)
    .resolver(FieldValueResolver.INSTANCE)
    .build();

  template.apply(context);

The MethodValueResolver

Let you access to public methods.

  Context context = Context
    .newBuilder(model)
    .resolver(MethodValueResolver.INSTANCE)
    .build();

  template.apply(context);

Using multiples value resolvers

You can combine any number of value resolvers according to your application data.

Access to JavaBean and Map properties:
  Context context = Context
    .newBuilder(model)
    .resolver(
        MapValueResolver.INSTANCE,
        JavaBeanValueResolver.INSTANCE
    )
    .build();

  template.apply(context);
Access to JavaBean, Map properties and public methods:
  Context context = Context
    .newBuilder(model)
    .resolver(
        MapValueResolver.INSTANCE,
        JavaBeanValueResolver.INSTANCE,
        MethodValueResolver.INSTANCE
    )
    .build();

  template.apply(context);
Access to JavaBean, Map properties and instance fields:
  Context context = Context
    .newBuilder(model)
    .resolver(
        MapValueResolver.INSTANCE,
        JavaBeanValueResolver.INSTANCE,
        FieldValueResolver.INSTANCE
    )
    .build();

  template.apply(context);

Conclusion

TheContextclass does all the hard work: walk through the stack, resolve expressions, etc. This component isn't puggable and can't be set.

Where aValueResolverextracts a single value from a source. You can add, remove or even create your own value resolver. It plays an important role if your application deal with a custom data source.

For example, if your service layer returns the widely usedJsonNodefrom Jackson a custom ValueResolver can access to that object and extract values from it.

Want to contribute?

Thank you, for reading the Handlebars.java blog.