DEV Community

Sebastijan Kaplar
Sebastijan Kaplar

Posted on

Extending Mustache usage in Pharo Smalltalk

Mustache is a template system with wide variety of implementations. One of those is in Pharo Smalltalk, which will be focused on in this article. You can find out more about it here.

First thing you'll notice when you open mustache.github.io is Logic-less templates, and it should remain that way. However often you want to extend the usage of one such template system and adjust it to your own needs.

In this article we'll see how to adapt to certain requirements, how to generate new templates and use them to render html forms, especially when you can't affect the incoming data.

Let's say we have a data that looks like:

{
  "name": "Worker",
  "formProperties": [
    {
      "name": "firstName",
      "component": "TextFieldComponent",
      "label": "First Name"
    },
    {
      "name": "lastName",
      "component": "TextFieldComponent",
      "label": "Last Name"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Now if we want to render this as an html form, it already looks familiar. One entry in formProperties should correspond to one form row, we have a name, and a label but we are missing a type for our input. Obviously we should use component, but we should do something because there is no TextFieldComponent input type.

Idea is to create a template with one partial which will contain partials for each form property.

MiniExample >> render: json

templateString := String new writeStream.
templateString nextPutAll: self baseTemplate.
partial := String new writeStream.
parts := Dictionary new.
s := NeoJSONReader fromString: json.
    (s at: 'formProperties') do: [ :el | 
        | tmp |
        tmp := self preparePartialTemplate: el.
        parts at: (el at: 'name') put: tmp.
        partial nextPutAll: '{{> '.
        partial nextPutAll: (el at: 'name').
        partial nextPutAll: '}}'.
    ].

p := partial contents asMustacheTemplate value: '' asDictionary partials: parts.
^ templateString contents asMustacheTemplate value: s partials: {'partial' -> p } asDictionary.
Enter fullscreen mode Exit fullscreen mode

Here we can see the json argument, it represents the data we want to handle and I am using NeoJSON to read JSON and convert it to a Smalltalk object. templateString contains the base template for this example, it has a

  • simple form,
  • {{ name }} template which will directly replaced with name from json,
  • {{> partial }} which will be replaced with that many partials, how many entries there are in the formProperties, or two in this case.
MiniExample >> baseTemplate

^
'<form action="http://localhost:8081/addNewEntity" method="post">
    {{ name }}
    {{> partial }}
    <button type="submit" class="btn btn-primary">Submit</button>
</form>'

Enter fullscreen mode Exit fullscreen mode

Next we have partial which will contain generated partials and it will look like this {{> firstName}}{{> lastName}} at the end of iteration, and parts dictionary that will hold two templates for the two generated partials. So let's take a deeper look at what do: does.

:el is a dictionary, it contains a name, component and a label, while preparePartialTemplate: is a method that expects a dictionary and returns a template. When the preparePartialTemplate: finishes we store the result in the parts dictionary, and generate partial that will correspond to that template eg. {{> firstName}}.

Here you can organize the template however you want, for the sake of tutorial I'm keeping it simple and in the same place.

MiniExample >> preparePartialTemplate: dict

templateString := String new writeStream.
templateString nextPutAll:
'
<div class="form-group">
    <label for="{{ label }}">{{ label }}</label>
'.
templateString nextPutAll: (self determineComponent: (dict at: 'component')).
templateString nextPutAll: '</div>'.
^ templateString contents asMustacheTemplate value: { 
    'label' -> (dict at: 'label').
} asDictionary.

Enter fullscreen mode Exit fullscreen mode

Also, here when you have your component you can do whatever you want, I'm just keeping it short.

MiniExample >> determineComponent: aComponent

aComponent = 'TextFieldComponent' ifTrue: [ 
    ^'<input type="text" id="{{label}}" name="{{ label }}" placeholder="Enter {{label}}">']
Enter fullscreen mode Exit fullscreen mode

And finally, we have our partial:

 partial -> {{> firstName}}{{> lastName}} 
Enter fullscreen mode Exit fullscreen mode

and the parts dictionary:

firstName ->

<div class="form-group">
    <label for="First Name">First Name</label>
    <input type="text" class="form-control" id="First Name" name="First Name" placeholder="Enter First Name">
</div>

lastName ->

<div class="form-group">
    <label for="Last Name">Last Name</label>
    <input type="text" class="form-control" id="Last Name" name="Last Name" placeholder="Enter Last Name">
</div>

Enter fullscreen mode Exit fullscreen mode

Now to return to the first snippet

p := partial contents asMustacheTemplate value: '' asDictionary partials: parts.
^ templateString contents asMustacheTemplate value: s partials: {'partial' -> p } asDictionary.
Enter fullscreen mode Exit fullscreen mode

p will get the final form:


<div class="form-group">
    <label for="First Name">First Name</label>
    <input type="text" class="form-control" id="First Name" name="First Name" placeholder="Enter First Name">
</div>
<div class="form-group">
    <label for="Last Name">Last Name</label>
    <input type="text" class="form-control" id="Last Name" name="Last Name" placeholder="Enter Last Name">
</div>
Enter fullscreen mode Exit fullscreen mode

And p will replace the {{> partial }} template in the original baseTemplate, and finally get to the

<form action="http://localhost:8081/addNewEntity" method="post">
    {{ name }}
    <div class="form-group">
        <label for="First Name">First Name</label>
        <input type="text" class="form-control" id="First Name" name="First Name" placeholder="Enter First Name">
    </div>
    <div class="form-group">
        <label for="Last Name">Last Name</label>
        <input type="text" class="form-control" id="Last Name" name="Last Name" placeholder="Enter Last Name">
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

Enter fullscreen mode Exit fullscreen mode

And after evaluating, with a little bit of css tweaking, we get something like this

Alt Text

Now, it is possible to extend your template and add support for other field types, components, requirements, etc.

Top comments (1)

Collapse
 
astares profile image
Astares

Nice!