wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

{{Mustache}} Helper for Domino and XPages


Previously we had a look, how to use Mustache with the CKEditor to have an editing environment for templates. I glossed over the part where to store and how to use these templates. Let me continue there.
I'll store the template in Notes documents and use an application managed bean to transform a document (and later other things) using these templates.
I have 2 use cases in mind: one is to allow the configuration of HTML notification messages in applications and the other to configure the display of workflow details (more on Workflow in a later post). The approach has two parts:
  1. loading (or reloading) the existing templates
  2. using the templates
The first one should need to run only once in the application lifecycle. Since managed beans can't have constructors with parameters, I created a seperate load function, so inside the bean there is nothing that depends on the XPages runtime environment. You therefore can use that bean in an agent, application or a plug-in.
I also opted to load all templates into memory at once, but only compile them when actually needed. If you plan to have lots of them, you want to look for a different solution.
The class that does the work is the MustacheHelper.java. To make it easy to use, I wrap it into a managed bean and a small SSJS Helper function:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <managed-bean>
    <managed-bean-name>Mustache</managed-bean-name>
    <managed-bean-class>com.notessensei.mustache.MustacheHelper</managed-bean-class>
    <!--  In a production system that should be application -->
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
  <!--AUTOGEN-START-BUILDER: Automatically generated by IBM Domino Designer. Do not modify.-->
  <!--AUTOGEN-END-BUILDER: End of automatically generated section-->
</faces-config>


function applyTemplate(doc, templateName) {
 try {
  // Check for init
  if (!Mustache.isInitialized()) {
   var templateView:NotesView = database.getView("templates");
   Mustache.initFromView(session, templateView);
   templateView.recycle(); 
  }
  return Mustache.renderDocumentToString(templateName,doc);
 } catch (e) {
  return e.message;
 }
}

With this done transforming a document becomes a one liner in SSJS: return applyTemplate(document1.getDocument(),"Color");. To demo how it works, I created a sample database.In that database I use 2 computed fields to demo the transfomation of a document into plain text and HTML. You might ask: so what? Keep in mind, that this is only to demo the technique. Real use cases would be notification eMails or cnfigurable summaries from documents where you don't know the fields while you design the solution.
There are some interesting details: Mustache has the capability to use partials (think subforms) using the syntax {{> common/header}}. By default Mustache would try to read a file from a file system for that partial. Since we don't have the partials in the file system, we need to provide our own resolver to the Mustache factory:
factory = new DefaultMustacheFactory(new MustacheNotesResolver(templateStrings));
The templateStrings are a Map containing the uncompiled Mustache templates. The Resolver code is quite lean:

public class MustacheNotesResolver implements MustacheResolver {

    private final Map<String, String> templates;
    
    public MustacheNotesResolver(Map<String, String> templateCollection) {
        this.templates = templateCollection;
    }

    @Override
    public Reader getReader(String resourceName) {
        if (!this.templates.containsKey(resourceName)) {
            return null;
        }
        return new StringReader(this.templates.get(resourceName));
    }

}

You will notice that the first time the transformation runs, there's a delay before the page displays. That's the step when all templates are loaded and the managed bean gets initialized.
Also have a look at the DocumentResolver that turns a document into something that Mustache can process.

public class DefaultDocumentResolver implements DocumentResolver {
    @Override
    public Map<String, Object> resolve(Document doc) {
        Map<String, Object> result = new HashMap<String, Object>();
        try {
            for (Object o : doc.getItems()) {
                Item i = (Item) o;
                if (i.getValues().size() > 1) {
                    Collection<String> c = new Vector<String>();
                    for (Object val : i.getValues()) {
                        c.add(val.toString());
                    }
                    result.put(i.getName(), c);
                } else {
                    result.put(i.getName(), i.getText());
                }
                Utils.shred(i);
            }
        } catch (NotesException e) {
            e.printStackTrace();
        }
        return result;
    }
}

Some documents might need special attention, e.g. when groups of field (multi-value or sequential named) form a table of values. You can provide your own resolver for them.
For now the templates are managed in the Notes client interface. Adding a template edit UI is left to your pleasure.
You can generate anything that can be expressed as ASCII stream: Plain Text, HTML, XML, JSON, XSL:FO, ODF, DOCX (the later two need to be zipped before delivery), PostScript, SVG etc. Get the code on GitHub
As usual YMMV. Next stop: transforming other stuff like views and adding more examples.

Posted by on 16 November 2014 | Comments (0) | categories: XPages

Comments

  1. No comments yet, be the first to comment