Giter VIP home page Giter VIP logo

handlebars.java's Introduction

Become a Patreon Maven Central javadoc

Handlebars.java

Logic-less and semantic Mustache templates with Java

Handlebars handlebars = new Handlebars();

Template template = handlebars.compileInline("Hello {{this}}!");

System.out.println(template.apply("Handlebars.java"));

Output:

Hello Handlebars.java!

Handlebars.java is a Java port of handlebars.

Handlebars provides the power necessary to let you build semantic templates effectively with no frustration.

Mustache templates are compatible with Handlebars, so you can take a Mustache template, import it into Handlebars, and start taking advantage of the extra Handlebars features.

Requirements

  • Handlebars 4.4+ requires Java 17 or higher.
  • Handlebars 4.3+ requires Java 8 or higher (NOT MAINTAINED).

Getting Started

In general, the syntax of Handlebars templates is a superset of Mustache templates. For basic syntax, check out the Mustache manpage.

The Handlebars.java blog is a good place for getting started too. Javadoc is available at javadoc.io.

Maven

Stable version: Maven Central

  <dependency>
    <groupId>com.github.jknack</groupId>
    <artifactId>handlebars</artifactId>
    <version>${handlebars-version}</version>
  </dependency>

Loading templates

Templates are loaded using the TemplateLoader class. Handlebars.java provides three implementations of a TemplateLoader:

  • ClassPathTemplateLoader (default)
  • FileTemplateLoader
  • SpringTemplateLoader (see the handlebars-springmvc module)

This example loads mytemplate.hbs from the root of the classpath:

mytemplate.hbs:

Hello {{this}}!
var handlebars = new Handlebars();

var template = handlebars.compile("mytemplate");

System.out.println(template.apply("Handlebars.java"));

Output:

Hello Handlebars.java!

You can specify a different TemplateLoader by:

TemplateLoader loader = ...;
Handlebars handlebars = new Handlebars(loader);

Templates prefix and suffix

A TemplateLoader provides two important properties:

  • prefix: useful for setting a default prefix where templates are stored.
  • suffix: useful for setting a default suffix or file extension for your templates. Default is: .hbs

Example:

TemplateLoader loader = new ClassPathTemplateLoader();
loader.setPrefix("/templates");
loader.setSuffix(".html");
Handlebars handlebars = new Handlebars(loader);

Template template = handlebars.compile("mytemplate");

System.out.println(template.apply("Handlebars.java"));

Handlebars.java will resolve mytemplate to /templates/mytemplate.html and load it.

The Handlebars.java Server

The handlebars.java server is small application where you can write Mustache/Handlebars template and merge them with data.

It is a useful tool for Web Designers.

Download from Maven Central:

  1. Go here
  2. Under the Download section click on jar

Maven:

<dependency>
  <groupId>com.github.jknack</groupId>
  <artifactId>handlebars-proto</artifactId>
  <version>${current-version}</version>
</dependency>

Usage: java -jar handlebars-proto-${current-version}.jar -dir myTemplates

Example:

myTemplates/home.hbs

<ul>
 {{#items}}
 {{name}}
 {{/items}}
</ul>

myTemplates/home.json

{
  "items": [
    {
      "name": "Handlebars.java rocks!"
    }
  ]
}

or if you prefer YAML myTemplates/home.yml:

items:
  - name: Handlebars.java rocks!

Open a browser a type:

http://localhost:6780/home.hbs

enjoy it!

Additional options:

  • -dir: set the template directory
  • -prefix: set the template's prefix, default is /
  • -suffix: set the template's suffix, default is .hbs
  • -context: set the context's path, default is /
  • -port: set port number, default is 6780
  • -content-type: set the content-type header, default is text/html

Multiple data sources per template

Sometimes you need or want to test multiple datasets over a single template, you can do that by setting a data parameter in the request URI.

Example:

http://localhost:6780/home.hbs?data=mytestdata

Please note you don't have to specify the extension file.

Helpers

Built-in helpers:

  • with
  • each
  • if
  • unless
  • log
  • block
  • partial
  • precompile
  • embedded
  • i18n and i18nJs
  • string helpers
  • conditional helpers

with, each, if, unless:

See the built-in helper documentation.

block and partial

Block and partial helpers work together to provide you Template Inheritance.

Usage:

  {{#block "title"}}
    ...
  {{/block}}

context: A string literal which defines the region's name.

Usage:

  {{#partial "title"}}
    ...
  {{/partial}}

context: A string literal which defines the region's name.

precompile

Precompile a Handlebars.java template to JavaScript using handlebars.js

user.hbs

Hello {{this}}!

home.hbs

<script type="text/javascript">
{{precompile "user"}}
</script>

Output:

<script type="text/javascript">
(function() {
  var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['user'] = template(function (Handlebars,depth0,helpers,partials,data) {
  helpers = helpers || Handlebars.helpers;
  var buffer = "", functionType="function", escapeExpression=this.escapeExpression;


  buffer += "Hi ";
  depth0 = typeof depth0 === functionType ? depth0() : depth0;
  buffer += escapeExpression(depth0) + "!";
  return buffer;});
})();
</script>

You can access the precompiled template with:

var template = Handlebars.templates['user']

By default it uses: /handlebars-v1.3.0.js to compile the template. Since handlebars.java 2.x it is also possible to use handlebars.js 2.x

Handlebars handlebars = new Handlebars();
handlebars.handlebarsJsFile("/handlebars-v2.0.0.js");

For more information have a look at the Precompiling Templates documentation.

Usage:

{{precompile "template" [wrapper="anonymous, amd or none"]}}

context: A template name. Required.

wrapper: One of "anonymous", "amd" or "none". Default is: "anonymous"

There is a maven plugin available too.

embedded

The embedded helper allow you to "embedded" a handlebars template inside a <script> HTML tag:

user.hbs

<tr>
  <td>{{firstName}}</td>
  <td>{{lastName}}</td>
</tr>

home.hbs

<html>
...
{{embedded "user"}}
...
</html>

Output:

<html>
...
<script id="user-hbs" type="text/x-handlebars">
<tr>
  <td>{{firstName}}</td>
  <td>{{lastName}}</td>
</tr>
</script>
...
</html>

Usage:

{{embedded "template"}}

context: A template name. Required.

i18n

A helper built on top of a {@link ResourceBundle}. A {@link ResourceBundle} is the most well known mechanism for internationalization (i18n) in Java.

Usage:

{{i18n "hello"}}

This require a messages.properties in the root of classpath.

Using a locale:

{{i18n "hello" locale="es_AR"}}

This requires a messages_es_AR.properties in the root of classpath.

Using a different bundle:

{{i18n "hello" bundle="myMessages"}}

This requires a myMessages.properties in the root of classpath.

Using a message format:

{{i18n "hello" "Handlebars.java"}}

Where hello is Hola {0}!, results in Hola Handlebars.java!.

i18nJs

Translate a ResourceBundle into JavaScript code. The generated code assumes you have the I18n in your application.

Usage:

{{i18nJs [locale] [bundle=messages]}}

If the locale argument is present it will translate that locale to JavaScript. Otherwise, it will use the default locale.

The generated code looks like this:

<script type="text/javascript">
  I18n.defaultLocale = 'es_AR';
  I18n.locale = 'es_AR';
  I18n.translations = I18n.translations || {};
  // Spanish (Argentina)
  I18n.translations['es_AR'] = {
    "hello": "Hi {{arg0}}!"
  }
</script>

Finally, it converts message patterns like: Hi {0} into Hi {{arg0}}. This make possible for the I18n JS library to interpolate variables.

string helpers

Functions like abbreviate, capitalize, join, dateFormat, yesno, etc., are available from StringHelpers.

NOTE: You need to register string helpers (they are not added by default)

conditional helpers

Functions like eq, neq, lt, gt, and, or, not, etc., are available from ConditionalHelpers.

NOTE: You need to register conditional helpers (they are not added by default)

TypeSafe Templates

TypeSafe templates are created by extending the TypeSafeTemplate interface. For example:

// 1
public static interface UserTemplate extends TypeSafeTemplate<User> {

  // 2
  public UserTemplate setAge(int age);

  public UserTemplate setRole(String role);

}

// 3
UserTemplate userTmpl = handlebars.compileInline("{{name}} is {{age}} years old!")
  .as(UserTemplate.class);

userTmpl.setAge(32);

assertEquals("Edgar is 32 years old!", userTmpl.apply(new User("Edgar")));
  1. You extend the TypeSafeTemplate interface.
  2. You add all the set method you need. The set method can returns void or TypeSafeTemplate object.
  3. You create a new type safe template using the: as() method.

Registering Helpers

There are two ways of registering helpers.

Using the Helper interface

handlebars.registerHelper("blog", new Helper<Blog>() {
  public CharSequence apply(Blog blog, Options options) {
    return options.fn(blog);
  }
});
handlebars.registerHelper("blog-list", new Helper<List<Blog>>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String ret = "<ul>";
    for (Blog blog: list) {
      ret += "<li>" + options.fn(blog) + "</li>";
    }
    return new Handlebars.SafeString(ret + "</ul>");
  }
});

Using a HelperSource

A helper source is any class with public methods returning an instance of a CharSequence.

  public static? CharSequence methodName(context?, parameter*, options?) {
  }

Where:

  • A method can/can't be static
  • The method's name becomes the helper's name
  • Context, parameters and options are all optionals
  • If context and options are present they must be the first and last arguments of the method

All these are valid definitions of helper methods:

public class HelperSource {
  public String blog(Blog blog, Options options) {
    return options.fn(blog);
  }

  public static String now() {
    return new Date().toString();
  }

  public String render(Blog context, String param0, int param1, boolean param2, Options options) {
    return ...
  }
}

...

handlebars.registerHelpers(new HelperSource());

Or, if you prefer static methods only:

handlebars.registerHelpers(HelperSource.class);

With plain JavaScript

That's right since 1.1.0 you can write helpers in JavaScript:

helpers.js:

Handlebars.registerHelper('hello', function (context) {
 return 'Hello ' + context;
})
handlebars.registerHelpers(new File("helpers.js"));

Cool, isn't?

Helper Options

Parameters

handlebars.registerHelper("blog-list", new Helper<Blog>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String p0 = options.param(0);
    assertEquals("param0", p0);
    Integer p1 = options.param(1);
    assertEquals(123, p1);
    ...
  }
});

Bean bean = new Bean();
bean.setParam1(123);

Template template = handlebars.compileInline("{{#blog-list blogs \"param0\" param1}}{{/blog-list}}");
template.apply(bean);

Default parameters

handlebars.registerHelper("blog-list", new Helper<Blog>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String p0 = options.param(0, "param0");
    assertEquals("param0", p0);
    Integer p1 = options.param(1, 123);
    assertEquals(123, p1);
    ...
  }
});

Template template = handlebars.compileInline("{{#blog-list blogs}}{{/blog-list}}");

Hash

handlebars.registerHelper("blog-list", new Helper<Blog>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String class = options.hash("class");
    assertEquals("blog-css", class);
    ...
  }
});

handlebars.compileInline("{{#blog-list blogs class=\"blog-css\"}}{{/blog-list}}");

Default hash

handlebars.registerHelper("blog-list", new Helper<Blog>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String class = options.hash("class", "blog-css");
    assertEquals("blog-css", class);
    ...
  }
});

handlebars.compileInline("{{#blog-list blogs}}{{/blog-list}}");

Error reporting

Syntax errors

file:line:column: message
   evidence
   ^
[at file:line:column]

Examples:

template.hbs

{{value
/templates.hbs:1:8: found 'eof', expected: 'id', 'parameter', 'hash' or '}'
    {{value
           ^

If a partial isn't found or if it has errors, a call stack is added:

/deep1.hbs:1:5: The partial '/deep2.hbs' could not be found
    {{> deep2
        ^
at /deep1.hbs:1:10
at /deep.hbs:1:10

Helper/Runtime errors

Helper or runtime errors are similar to syntax errors, except for two things:

  1. The location of the problem may (or may not) be the correct one
  2. The stack-trace isn't available

Examples:

Block helper:

public CharSequence apply(final Object context, final Options options) throws IOException {
  if (context == null) {
    throw new IllegalArgumentException(
        "found 'null', expected 'string'");
  }
  if (!(context instanceof String)) {
    throw new IllegalArgumentException(
        "found '" + context + "', expected 'string'");
  }
  ...
}

base.hbs


{{#block}} {{/block}}

Handlebars.java reports:

/base.hbs:2:4: found 'null', expected 'string'
    {{#block}} ... {{/block}}

In short, from a helper you can throw an Exception and Handlebars.java will add the filename, line, column and the evidence.

Advanced Usage

Extending the context stack

Let's say you need to access to the current logged-in user in every single view/page. You can publish the current logged in user by hooking into the context-stack. See it in action:

 hookContextStack(Object model, Template template) {
   User user = ....;// Get the logged-in user from somewhere
   Map moreData = ...;
   Context context = Context
     .newBuilder(model)
       .combine("user", user)
       .combine(moreData)
       .build();
   template.apply(context);
   context.destroy();
 }

Where is the hookContextStack method? Well, it depends on your application architecture.

Using the ValueResolver

By default, Handlebars.java use the JavaBean methods (i.e. public getXxx and isXxx methods) and Map as value resolvers.

You can choose a different value resolver. This section describe how to do this.

The JavaBeanValueResolver

Resolves values from public methods prefixed with "get/is"

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

The FieldValueResolver

Resolves values from no-static fields.

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

The MapValueResolver

Resolves values from a java.util.Map objects.

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

The MethodValueResolver

Resolves values from public methods.

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

The JsonNodeValueResolver

Resolves values from JsonNode objects.

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

Available in Jackson 1.x and Jackson 2.x modules.

Using multiples value resolvers

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

The Cache System

The cache system is designed to provide scalability and flexibility. Here is a quick view of the TemplateCache system:

 public interface TemplateCache {

  /**
   * Remove all mappings from the cache.
   */
  void clear();

  /**
   * Evict the mapping for this source from this cache if it is present.
   *
   * @param source the source whose mapping is to be removed from the cache
   */
  void evict(TemplateSource source);

  /**
   * Return the value to which this cache maps the specified key.
   *
   * @param source source whose associated template is to be returned.
   * @param parser The Handlebars parser.
   * @return A template.
   * @throws IOException If input can't be parsed.
   */
  Template get(TemplateSource source, Parser parser) throws IOException;
}

As you can see, there isn't a put method. All the hard work is done in the get method, which is basically the core of the cache system.

By default, Handlebars.java uses a null cache implementation (a.k.a. no cache at all) which looks like:

Template get(TemplateSource source, Parser parser) throws IOException {
  return parser.parse(source);
}

In addition to the null cache, Handlebars.java provides three more implementations:

  1. ConcurrentMapTemplateCache: a template cache implementation built on top of a ConcurrentMap that detects changes in files automatically. This implementation works very well in general, but there is a small window where two or more threads can compile the same template. This isn't a huge problem with Handlebars.java because the compiler is very very fast. But if for some reason you don't want this, you can use the HighConcurrencyTemplateCache template cache.

  2. HighConcurrencyTemplateCache: a template cache implementation built on top of ConcurrentMap that detects changes in files automatically. This cache implementation eliminates the window created by ConcurrentMapTemplateCache to zero. It follows the patterns described in Java Concurrency in Practice and ensures that a template will be compiled just once regardless of the number of threads.

  3. GuavaTemplateCache: a template cache implementation built on top of Google Guava. Available in handlebars-guava-cache module

You can configure Handlebars.java to use a cache by:

Handlebars hbs = new Handlebars()
  .with(new MyCache());

Using a MissingValueResolver (@deprecated)

NOTE: MissingValueResolver is available in <= 1.3.0. For > 1.3.0 use Helper Missing.

A MissingValueResolver let you use default values for {{variable}} expressions resolved to null.

  MissingValueResolver missingValueResolver = new MissingValueResolver() {
    public String resolve(Object context, String name) {
      //return a default value or throw an exception
      ...;
    }
  };
  Handlebars handlebars = new Handlebars().with(missingValueResolver);

Helper Missing

By default, Handlebars.java throws an java.lang.IllegalArgumentException() if a helper cannot be resolved. You can override the default behaviour by providing a special helper: helperMissing. Example:

  handlebars.registerHelperMissing(new Helper<Object>() {
    @Override
    public CharSequence apply(final Object context, final Options options) throws IOException {
      return options.fn.text();
    }
  });

String form parameters

You can access a parameter name if you set the: stringParams: true. Example:

{{sayHi this edgar}}
  Handlebars handlebars = new Handlebars()
    .stringParams(true);
  
  handlebars.registerHelper("sayHi", new Helper<Object>() {
    public Object apply(Object context, Options options) {
      return "Hello " + options.param(0) + "!";
    }
  });

results in:

Hello edgar!

How does this work? stringParams: true instructs Handlebars.java to resolve a parameter to it's name if the value isn't present in the context stack.

Allow Infinite loops

By default, Handlebars.java doesn't allow a partial to call itself (directly or indirectly). You can change this by setting the: Handlebars.inifiteLoops(true), but watch out for a StackOverflowError.

Pretty Print

The Mustache Spec has some rules for removing spaces and new lines. This feature is disabled by default. You can turn this on by setting the: Handlebars.prettyPrint(true).

Modules

Jackson 1.x

Maven:

 <dependency>
   <groupId>com.github.jknack</groupId>
   <artifactId>handlebars-json</artifactId>
   <version>${handlebars-version}</version>
 </dependency>

Usage:

 handlebars.registerHelper("json", JacksonHelper.INSTANCE);
 {{json context [view="foo.MyFullyQualifiedClassName"] [escapeHTML=false] [pretty=false]}}

Alternative:

 handlebars.registerHelper("json", new JacksonHelper().viewAlias("myView",
   foo.MyFullyQualifiedClassName.class);
 {{json context [view="myView"] [escapeHTML=false] [pretty=false]}}

context: An object, may be null.

view: The name of the Jackson View. Optional.

escapeHTML: True, if the JSON content contains HTML chars and you need to escaped them. Default is: false.

pretty: True, if the JSON content must be formatted. Default is: false.

Jackson

Maven:

 <dependency>
   <groupId>com.github.jknack</groupId>
   <artifactId>handlebars-jackson</artifactId>
   <version>${handlebars-version}</version>
 </dependency>

SpringMVC

Maven:

 <dependency>
   <groupId>com.github.jknack</groupId>
   <artifactId>handlebars-springmvc</artifactId>
   <version>${handlebars-version}</version>
 </dependency>

Using value resolvers:

 HandlebarsViewResolver viewResolver = ...;

 viewResolver.setValueResolvers(...);

In addition, the HandlebarsViewResolver add a message helper that uses the Spring MessageSource class:

{{message "code" [arg]* [default="default message"]}}

where:

  • code: the message's code. Required.
  • arg: the message's argument. Optional.
  • default: the default's message. Optional.

Checkout the HandlebarsViewResolver.

Performance

Handlebars.java is a modern and full featured template engine, but also has a very good performance (Hbs):

Template Comparison

Benchmark source code is available at: https://github.com/mbosecke/template-benchmark

Architecture and API Design

  • Handlebars.java follows the JavaScript API with some minors exceptions due to the nature of the Java language.
  • The parser is built on top of [ANTLR v4] (http://www.antlr.org/).
  • Data is provided as primitive types (int, boolean, double, etc.), strings, maps, list or JavaBeans objects.
  • Helpers are type-safe.
  • Handlebars.java is thread-safe.

Differences between Handlebars.java and Handlebars.js

Handlebars.java scope resolution follows the Mustache Spec. For example:

Given:

{
  "value": "parent",
  "child": {
  }
}

and

Hello {{#child}}{{value}}{{/child}}

will be:

Hello parent

Now, the same model and template with Handlebars.js is:

Hello 

That is because Handlebars.js doesn't look in the context stack for missing attributes in the current scope (this is consistent with the Mustache Spec).

Hopefully, you can turn-off the context stack lookup in Handlebars.java by qualifying the attribute with this.:

Hello {{#child}}{{this.value}}{{/child}}

Differences between Handlebars.java and Mustache.js

  • Handlebars.java throws a java.io.FileNotFoundException if a partial cannot be loaded.

Status

Mustache 1.0 Compliant

Handlebars.js Compliant

Dependencies

+- org.slf4j:slf4j-api:jar:1.6.4

FAQ

Want to contribute?

  • Fork the project on Github.
  • Wondering what to work on? See task/bug list and pick up something you would like to work on.
  • Do you want to donate one or more helpers? See handlebars=helpers a repository for community's helpers.
  • Create an issue or fix one from issues list.
  • If you know the answer to a question posted to our mailing list - don't hesitate to write a reply.
  • Share your ideas or ask questions on mailing list - don't hesitate to write a reply - that helps us improve javadocs/FAQ.
  • If you miss a particular feature - browse or ask on the mailing list - don't hesitate to write a reply, show us some sample code and describe the problem.
  • Write a blog post about how you use or extend handlebars.java.
  • Please suggest changes to javadoc/exception messages when you find something unclear.
  • If you have problems with documentation, find it non intuitive or hard to follow - let us know about it, we'll try to make it better according to your suggestions. Any constructive critique is greatly appreciated. Don't forget that this is an open source project developed and documented in spare time.

Help and Support

Help and discussion

Bugs, Issues and Features

Related Projects

Author

Edgar Espina

License

Apache License 2

handlebars.java's People

Contributors

agentgt avatar anotherchrisberry avatar c089 avatar cnoguera avatar dependabot-preview[bot] avatar dependabot[bot] avatar dracoblue avatar earyans avatar edgarespinawt avatar erdi avatar finnbock avatar happylynx avatar jknack avatar kilink avatar kokovtsev avatar lewisdawson avatar mathijs81 avatar mcampo avatar mcdan avatar mrhanlon avatar msparer avatar murali54 avatar nanamikon avatar nyon avatar pvgoran avatar skrysmanski avatar soldierkam avatar tburch avatar trautonen avatar xuwei-k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

handlebars.java's Issues

Typo in package name

It should says com.github.edgarespina.handlebars and NOT com.github.edgarespina.handlerbars

pushing new context to stack

Thanks for the nice implementation.

Are there a way from within a helper to push some new values on the existing context stack and then apply an unrelated template with the whole stack?

As a silly example, here is an include helper where I would like to read another template and render it, but with some additional values in the context.

    handlebars.registerHelper("include", new Helper<String>() {
        public CharSequence apply(String path, Options options) throws IOException {
            Map<String, Object> ctxt = new HashMap<>();
            ctxt.put("var", "value");

            Template included = handlebars.compile(new URI(path));

            return options.apply(included, ctxt); // this method is private and cannot be used like this.
        }
    });

All the available methods to render a template appears to all create a new stack without access to existing values.

Add a date formatter

Usage:
{{dateFormat date ["format"]}}

format: is an optional parameter. If present it should be one of:

  • "full"
  • "long"
  • "medium"
  • "short"
  • "date pattern"

Support @JsonView

I think it might be interesting to support an additional template.apply method that allows you to select a JsonView

Sometimes you want to @JsonIgnore some properties conditionally and that is what @JSONVIEW is intended for. Particularly with large object graphs, this could be useful.

This can also be handled at the helper level, so perhaps it's overkill, but I was thinking that it might be nice to be able to select a global JsonView, e.g. LIST or DETAIL, in order to have a global more efficient selection of properties.

The logical place would seem to be as a property at the Template level.

test failure in DateFormatTest caused by non-EN locale.

Running DateFormatTest on a non-EN cause test failure in longFormat, mediumFormat, shortFormat and fullFormat because the expected result is written in english. On a DK machine I get results like "19. juni 2012" instead of "Tuesday, June 19, 2012"

NPE when building a child context with combined map

Using 0.2.1. snaphot, I get a NPE when I create child2 since the extendedContext of a child is Null.

    Context root = Context.newBuilder("root").build();
    System.out.println(root.get("this"));
    Context child1 = Context.newBuilder(root, "child1").build();
    System.out.println(child1.get("this"));
    Context child2 = Context.newBuilder(root, "child2").combine(new HashMap<String, Object>()).build();

The use case is a #each like iterator that also add odd/even flags to the namespace.

Add a embedded helper

Given:

home.hbs

<html>
 ...
 {{emdedded "user.hbs" ["id"]}}
</html>

where user.hbs is:

 <tr>
   <td>{{firstName}}</td>
   <td>{{lastName}}</td>
 </tr>

output is:

<script id="user-hbs" type="text/x-handlebars-template">
 <tr>
   <td>{{firstName}}</td>
   <td>{{lastName}}</td>
 </tr>
</script>

Optionally, a user can set the template's name:

 {{emdedded "user.hbs" "user-tmpl" }}

output is:

<script id="user-tmpl" type="text/x-handlebars-template">
 <tr>
   <td>{{firstName}}</td>
   <td>{{lastName}}</td>
 </tr>
</script>

Caching

Hi Edgar,

Saw the Handlebars project you are creating. Good to see this! There was a discussion in the YUI forum on this missing link!
I did a quick review of the code and I was wondering how you are dealing with caching compile templates. I couldn't find this (could be I just missed it) and it would make sense to cache a template after compilation (and do all this in a multi-threaded environment). It would then also make sense to be able to have a method to clear the cache, which is useful in developer and redeploy scenarios.

Great to see this! Let me know if I can help

Marc

Fail if a template isn't found

The Mustache Spec returns an empty string if a template isn't found, specially partials.

Beside the spec, I don't think this is healthy, from now on Handlebars.java will raise an exception if a template cannot be found.

Introduce a ValueResolver

A value resolver looks like:

public interface ValueResolver {
  Object resolve(Object context, String name);
}

It allows client to resolve a named value in the current context.

Some built-in value resolvers will be available in com.github.edgarespina.handlebars.context:

  • MapValueResolver: resolve a value from a java.util.Map
  • JavaBeanValueResolver: resolve a value from a public getXxx method
  • FieldValueResolver: resolve a value from a non-static field value

More helpful error reporting

Hi,

right now, I find the error reporting very unhelpful. What I think we should have at least is

  1. The relative name/location of the actual file that generate the error.
  2. The call hierarchy that that led to the error

E.g.
embedded:32:9: found 'EOI', expected '{', ' ', '\t', '\f', '\r', '\n', ANY, block, partial, setDelimiters, comment or variable

^ Error occurred in: inbox/messages (32:9) Called from: inbox/inbox (10:15) frame/default (2:30)

Make public the Context API

Advance usage of models required to hook into the context stack. The context stack should have a public API for advance usage ONLY.

Context and ContextFactory will be accessible after this patch.

Error line correcting is incorrect

When you get an error the error location is reported something like this:

com.github.edgarespina.handlebars.HandlebarsException: embedded:27:24: found '', expected ' ', '\t', '\f', '\r', '\n', comment, '=', hash, string, integer, bool or '}'
{{#tag message.from "user"}}
^

I'm noticing two things:

  1. The error line is incorrect. For me, in this case, it is line 28 and not 27
  2. The column count starts from the start of the tag in stead of the start of the line, making it harder to find the actual column

test failure in EmbeddedHelperTest on windows.

The 2 tests embedded() & embeddedWithId() fails on windows because the file user.hbs contains \r\n when it is downloaded by git. If I convert this file to unix lineending (and last line without newline), then the test pass.

Implement pseudo variables for array and list

@Index, @FIRST, @last pseudo var should be available for iterables (array and collections)

@Index: provide the index of the element
@FIRST: provide the label: "first" for first element. Otherwise an empty string
@last: provide the label: "last" for last element. Otherwise an empty string
@odd: provide the label: "odd" for an odd element. Otherwise an empty string
@even: provide the label: "even" for an odd element. Otherwise an empty string

Fix context lookup order

Actual:

A value is resolved in the following order:

  1. current
  2. parent
  3. extended

The correct order is:

  1. current
  2. extended
  3. parent

Bug in the Context API

Now, the context API is public, there are a couple of bug that need to be resolve:

  1. Template#apply or Options#apply need to deal with a Context provided by a client
  2. ValueResolvers need to be propagated to child context

Change groupId/package's name to com.github.jknack

I'm going to change my user's name at github. This will affect the groupId/package's name of handlebars. The change will be scheduled for the next release which will be: 0.4.0

New package's name is: com.github.jknack.handlebars

Add prefix and suffix attributes to TemplateLoader

  • prefix: The prefix that gets prepended to view names when building a URI.
  • suffix: The suffix that gets appended to view names when building a URI.

Example:
loader: ClassTemplateLoader; prefix = "/"; suffix = ".hbs":

handlebars.compile(URI.create("home"));

"home" will be resolved as "/home.hbs"

../myVar should reference the rootContext

The .combine(someContext) allows me access to some global scoped variables. However, in order to maintain compatibility with my Javascript version, I need to be able use Handlebars Paths to be able to reference the root scope.

The Handlebars documentation references Paths as a way to reference the root scope (it's not entirely clear if it should be parent or root scope, but root scope would make the most sense).

Right now using {{../myVar}} throws an error. It should either display the variable or nothing from within a partial.

Project structure

Convert the project to a maven multi-project. This will help the release process and code organization

ClassCastException if Context isn't a ContextImpl

Congrats on the 0.1.0 release!

I wanted to try the new Context interface for an "advanced" use of adding support for public fields and for public static fields.

When one of my helpers want to render some other template I get an exception

    java.lang.ClassCastException: handlebars.Templating$9 cannot be cast to com.github.edgarespina.handlebars.ContextFactory$ContextImpl
    at com.github.edgarespina.handlebars.ContextFactory$ContextImpl.<init>(ContextFactory.java:92)
    at com.github.edgarespina.handlebars.ContextFactory$ContextImpl.<init>(ContextFactory.java:25)
    at com.github.edgarespina.handlebars.ContextFactory.wrapInternal(ContextFactory.java:321)
    at com.github.edgarespina.handlebars.ContextFactory.wrap(ContextFactory.java:306)
    at com.github.edgarespina.handlebars.internal.DefaultOptions.apply(DefaultOptions.java:136)

because my implementation of Context can not be cast to a ContextImpl. Replacing the cast with

    this.storage = parent.storage();

works for me.

Access to values in context stack

I think a way to access the values in the context stack from within a helper will be a valuable addition.

In my usecase I have a map of i18n messages stored in the context with key "messageMap" and would like to create a helper that retrieve a message.

    {{msg 'pagetitle'}}

but the helper can not retrieve a value from the current context stack.

    handlebars.registerHelper("msg", new Helper<String>() {
        public CharSequence apply(String key, Options options) throws IOException {
            Map msg = (Map) ???  // How to get a value? options.getKey("messageMap")
            String val = msg.get(key);
            if (val == null) {
                val = "??" + key + "??";
            }
            return val;
        }
    }); 

as a workaround I use

    {{msg 'pagetitle' messageMap}}

but it is ugly and a bit wrong that a helper isn't powerfull enough to do it itself.

Allow " (qoute) in hash string param

handlebars.js allow for escaping a quote with a backslash: (from handlebars.l)

  <mu>'"'("\\"["]|[^"])*'"'        { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }

but if I try this template I get a compile error:

  {{#each a="1\"2"}}{{/each}}

registering partials

Handlebars.java very sensibly allows you to include partials based on a directory-file reference.
When I started using the same templates in my javascript front end, I was annoyed that I had to register them separately. But then I read the documentation on Handlebars.js and I saw that this was actually "expected". So, then it would also make sense to have the option to register a partial by name in Handlebars.java. Not so much to improve the developer experience, because it doesn't, but more to improve cross platform compatibility

Improve the Context API

Problem:

If a user want to extends the context-stack, it have to use the "wrap" method of ContextFactory

Root Context:

  Context context = ContextFactory.wrap(model);

Child Context:

  Context context = ContextFactory.wrap(model);
  Context child = ContextFactory.wrap(context, moreData);

This works but have two disadvantages:

  1. The semantic isn't enough "wrap" isn't a clear name
  2. The object attributes are bind to the "root", this is error-prone and the context will get lost.

Solution

The new API has more semantic and help to reduce errors

Root Context:

  Context root = Context.newContext(model);

Child Context:

  Context root = Context.newContext(model);
  Context child = Context.newContext(child, data);

Extended Context:

  Context root = Context.newContext(model)
     .combine("user", user)
     .combine(map)
     .build();

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.