87

I am using the Mustache templating library and trying to generate a comma separated list without a trailing comma, e.g.

red, green, blue

Creating a list with the trailing comma is straightforward, given the structure

{
  "items": [
    {"name": "red"},
    {"name": "green"},
    {"name": "blue"}
  ]
}

and the template

{{#items}}{{name}}, {{/items}}

this will resolve to

red, green, blue,

However I cannot see an elegant way of expressing the case without the trailing comma. I can always generate the list in code before passing it into the template, but I was wondering whether the library offers an alternative approach such as allowing you to to detect whether it is the last item in a list within the template.

Cœur
  • 32,421
  • 21
  • 173
  • 232
John Kane
  • 3,211
  • 4
  • 21
  • 18
  • I suggest building the comma separated list in your code and pass it to moustache as a single string. More complex logic than optionals and simple lists is almost always more readable in classic programming languages. – yeoman Feb 10 '18 at 17:34
  • More complex template engines than moustache can do this quite easily. It's not very readable in any of them, though, and with this in mind, the decision to make moustache as simple as it is was quite deliberate :D – yeoman Feb 10 '18 at 17:36

19 Answers19

98

I think a better way is to change the model dynamically. For example, if you are using JavaScript:

model['items'][ model['items'].length - 1 ].last = true;

and in your template, use inverted section:

{{#items}}
    {{name}}{{^last}}, {{/last}}
{{/items}}

to render that comma.

Patrick McElhaney
  • 52,844
  • 37
  • 123
  • 157
Clyde
  • 1,392
  • 1
  • 9
  • 13
  • 2
    I love it. Each time I think Mustache has not enough feature, it's because I think the 'old' way where template does everything - well, I come from JSPs. – Nicolas Zozol Feb 06 '13 at 11:49
  • 1
    @NicolasZozol You do realize that moustache was created with exactly this kind of simplicity in mind? :D – yeoman Feb 10 '18 at 17:29
  • @NicolasZozol For really complicated cases, it's best to build strings directly in a programming language, and thus create a simple view model that the template language can handle. In this case, the comma separated list would be provided via code as one single string. – yeoman Feb 10 '18 at 17:31
  • Good solution, but needs a generalization as **reusable "helper function"**: see below, https://stackoverflow.com/a/67674855/287948 – Peter Krauss May 24 '21 at 15:33
44

Cheat and use CSS.

If your model is:

{
  "items": [
    {"name": "red"},
    {"name": "green"},
    {"name": "blue"}
  ]
}

then make your template

<div id="someContainer">
{{#items}}
    <span>{{name}}<span>
{{/items}}
</div>

and add a little bit of CSS

#someContainer span:not(:last-of-type)::after {
  content: ", "    
}

I'm guessing someone will say that this is a bad case of putting markup in the presentation but I don't think it is. Comma separating values is a presentation decision to make interpreting the underlying data easier. It's similar to alternating the font color on entries.

Richard Morgan
  • 7,439
  • 9
  • 46
  • 84
Jared
  • 1,372
  • 1
  • 11
  • 24
  • Should be noted this is compatible in IE9+ only, so if you need to support old browsers, you may have to use another approach – PoeHaH Nov 26 '14 at 09:37
  • 1
    This makes the assumption that mustache is being used for web-pages - it has plenty of uses outside of that too – tddmonkey Mar 16 '20 at 11:32
44

Hrm, doubtful, the mustache demo pretty much shows you, with the first property, that you have to have the logic inside the JSON data to figure out when to put the comma.

So your data would look something like:

{
  "items": [
    {"name": "red", "comma": true},
    {"name": "green", "comma": true},
    {"name": "blue"}
  ]
}

and your template

{{#items}}
    {{name}}{{#comma}},{{/comma}}
{{/items}}

I know it's not elegant, but as mentioned by others Mustache is very lightweight and does not provide such features.

Community
  • 1
  • 1
Luca Matteis
  • 28,287
  • 19
  • 109
  • 164
  • 25
    you can also format you data, so that "items": ["red","green","blue"] then you can just do a {{items}} this will output a comma separated list already :) – Anthony Chua Jul 21 '12 at 15:14
  • 11
    The comment should actually be the correct answer. Much cleaner. Its really bad form to modify your data source to serve the visual needs of the consumer – Slick86 May 14 '13 at 19:54
  • @AnthonyChua While elegant, this may (a) not be documented behavior and thus subject to future changes, although unlikely, and (b) this does not put spaces after the commas, so you get something like `first,second,third`. – caw Sep 15 '14 at 09:11
  • 10
    it is less work to only add one property to the last item `{"name": "blue", "last": 1}` and then use an inverted section `{{#items}} {{name}}{{^last}}, {{/last}} {{/items}}` – TmTron Oct 29 '14 at 18:07
  • This one helped me greatly with _elasticsearch_ search templates! – Jony Adamit May 18 '15 at 10:13
  • Commas are a function of presentation. Consider using CSS for this purpose: { "items": [ {"name": "red"}, {"name": "green"}, {"name": "blue"} ] } {{#items}}{{name}} {{/items}} – Aaron Cicali Dec 22 '15 at 01:57
  • @acicali Why do you assume the output is to be used for presentation? You aren't proposing a separation of concerns, you're offloading a portion of the needed behavior to a post-processor that's only available in a specific context. – GlenRSmith Jan 13 '16 at 15:01
  • 1
    @slick86 The comment above yours does result in a comma separated list, but not one in which each item is enclosed in double-quotes. – GlenRSmith Jan 13 '16 at 15:01
32

If you happen to be using jmustache, you can use the special -first or -last variables:

{{#items}}{{name}}{{^-last}}, {{/-last}}{{/items}}
dbort
  • 902
  • 7
  • 13
  • 4
    I realize that the OP was referring to the JavaScript Mustache library, but this may help other jmustache users (like me) who find this page. – dbort Nov 19 '15 at 02:44
  • Thanks a lot for the answer. I used this also for rendering a template with SpringBoot. No need to change the model. I was really looking for this feature. I also wonder if there are equality controls (e.g. `{{#something=TEXT}}`) – JeanValjean Mar 05 '16 at 16:40
8

I can't think of many situations where you'd want to list an unknown number of items outside of a <ul> or <ol>, but this is how you'd do it:

<p>
    Comma separated list, in sentence form;
    {{#each test}}{{#if @index}}, {{/if}}{{.}}{{/each}};
    sentence continued.
</p>

…will produce:

Command separated list, in sentence form; asdf1, asdf2, asdf3; sentence continued.

This is Handlebars, mind you. @index will work if test is an Array.

Steven Vachon
  • 3,168
  • 1
  • 21
  • 29
  • seems pretty damn elegant! assuming #if and @index are available in all or most implementations of mustache... Keep in mind, many of us mustache users are not generating HTML, even if that is the most common use-case. – Spike0xff Apr 08 '16 at 01:56
  • This is an awesome solution, works like a charm. Heads up for anyone coming across this answer, if you want to wrap the result in an HTML tag, do so around `{{.}}`. – NetOperator Wibby Apr 10 '17 at 21:48
  • This was the right pointer. It appears you can now also use [first] and [last] conditionals for an even better control. https://stackoverflow.com/questions/11479094/conditional-on-last-item-in-array-using-handlebars-js-template – Maksym Apr 20 '20 at 16:38
6

The question of whether Mustache offers an elegant way to do this has been answered, but it occurred to me that the most elegant way to do this may be to use CSS rather than changing the model.

Template:

<ul class="csl">{{#items}}<li>{{name}}</li>{{/items}}</ul>

CSS:

.csl li
{
    display: inline;
}
.csl li:before
{
    content: ", "
}
.csl li:first-child:before
{
    content: ""
}

This works in IE8+ and other modern browsers.

David Hammond
  • 3,191
  • 1
  • 22
  • 18
6

There is not a built-in way to do this in Mustache. You have to alter your model to support it.

One way to implement this in the template is to use the inverted selection hat {{^last}} {{/last}} tag. It will only omit text for the last item in the list.

{{#items}}
    {{name}}{{^last}}, {{/last}}
{{/items}}

Or you can add a delimiter string as ", " to the object, or ideally the base class if you're using a language that has inheritance, then set "delimiter" to an empty string " " for the last element like this:

{{#items}}
    {{name}}{{delimiter}}
{{/items}}
cosbor11
  • 9,704
  • 8
  • 43
  • 58
3

For JSON data I suggest:

Mustache.render(template, settings).replace(/,(?=\s*[}\]])/mig,'');

The regexp will remove any , left hanging after the last properties.

This will also remove , from string values contining ", }" or ", ]" so make sure you know what data will be put into your JSON

mathiasrw
  • 594
  • 3
  • 10
3

In case using Handlebars is an option, which extends the capabilities of Mustache, you could use a @data variable:

{{#if @last}}, {{/if}}

More info: http://handlebarsjs.com/reference.html#data

yeoman
  • 1,549
  • 11
  • 12
Roberto14
  • 663
  • 4
  • 19
2

As the question is:

is there an elegant way of expressing a comma separated list without the trailing comma?

Then changing the data - when being the last item is already implicit by it being the final item in the array - isn't elegant.

Any mustache templating language that has array indices can do this properly,. ie. without adding anything to the data. This includes handlebars, ractive.js, and other popular mustache implementations.

{{# names:index}}
    {{ . }}{{ #if index < names.length - 1 }}, {{ /if }}
{{ / }}
mikemaccana
  • 81,787
  • 73
  • 317
  • 396
1

Simplest way I found was to render list and then remove last char.

  1. Render mustache.
  2. Remove any white space before and after string.
  3. Then remove last character

    let renderedData = Mustache Render(dataToRender, data); renderedData=(renderedData.trim()).substring(0, renderedData.length-1)

1

In more complex scenarios, a view model is desirable for lots of reasons. It represents the model's data in a manner that is better suited for display or, in this case, template processing.

In case you are using a view model, you can easily represent lists in a way that facilitates your goal.

Model:

{
    name: "Richard",
    numbers: [1, 2, 3]
}

View Model:

{
    name: "Richard",
    numbers: [
        { first: true, last: false, value: 1 },
        { first: false, last: false, value: 2 },
        { first: false, last: true, value: 3 }
    ]
}

The second list represention is horrible to type but extremely straightforward to create from code. While mapping your model to the view model, just replace every list you need first and last for with this representation.

function annotatedList(values) {
    let result = []
    for (let index = 0; index < values.length; ++index) {
        result.push({
            first: index == 0,
            last: index == values.length - 1,
            value: values[index]
        })
    }
    return result
}

In case of unbounded lists, you can also only set first and omit last, as one of them is sufficient for avoiding the trailing comma.

Using first:

{{#numbers}}{{^first}}, {{/first}}{{value}}{{/numbers}}

Using last:

{{#numbers}}{{value}}{{^last}}, {{/last}}{{/numbers}}
yeoman
  • 1,549
  • 11
  • 12
1

A little late to the party but perhaps helpful for someone looking now: 5.1.1 can do this:

{{#allVars}}{{name}}{{^-last}}, {{/-last}}{{/allVars}}

Output:

var1, var2, var3

Zoltán
  • 1,302
  • 14
  • 22
0

Interesting. I know it's kind of lazy but I usually get around this by templating in the value assignment rather than trying to comma delimitate the values.

var global.items = {};
{{#items}}
    global.items.{{item_name}} = {{item_value}};
{{/items}}
Iwnnay
  • 1,477
  • 13
  • 16
0

I tend to think this is a task well suited to CSS (as answered by others). However, assuming you are attempting to do something like produce a CSV file, you would not have HTML and CSS available to you. Also, if you are considering modifying data to do this anyway, this may be a tidier way to do it:

var data = {
  "items": [
    {"name": "red"},
    {"name": "green"},
    {"name": "blue"}
  ]
};

// clone the original data. 
// Not strictly necessary, but sometimes its
// useful to preserve the original object
var model = JSON.parse(JSON.stringify(data));

// extract the values into an array and join 
// the array with commas as the delimiter
model.items = Object.values(model.items).join(',');

var html = Mustache.render("{{items}}", model);
Jefferey Cave
  • 2,054
  • 1
  • 22
  • 41
0

If you are using java, you can use the following :

https://github.com/spullara/mustache.java/blob/master/compiler/src/test/java/com/github/mustachejava/util/DecoratedCollectionTest.java

MustacheFactory mf = new DefaultMustacheFactory();
Mustache test = mf.compile(new StringReader("{{#test}}{{#first}}[{{/first}}{{^first}}, {{/first}}\"{{value}}\"{{#last}}]{{/last}}{{/test}}"), "test");
StringWriter sw = new StringWriter();
test.execute(sw, new Object() {
    Collection test = new DecoratedCollection(Arrays.asList("one", "two", "three"));
}).flush();
System.out.println(sw.toString());
王子1986
  • 2,009
  • 3
  • 23
  • 38
0

I know this is an old question, but I still wanted to add an answer that provides another approach.

Main answer

Mustache supports lambdas, (see documentation) so one can write it this way:

Template:

    {{#removeTrailingComma}}{{#items}}{{name}}, {{/items}}{{/removeTrailingComma}}

Hash:

    {
      "items": [
        {"name": "red"},
        {"name": "green"},
        {"name": "blue"}
      ]
      "removeTrailingComma": function() {
        return function(text, render) {
          var original = render(text);
          return original.substring(0, original.length - 2);
        }
      }
    }

Output:

red, green, blue

Comment

Personally, I like this approach over the others, since IMHO the model should only specify what is rendered and not how it is rendered. Technically, the lambda is part of the model, but the intent is much more clear.

I use this approach for writing my own OpenApi generators. There, Mustache is wrapped by Java, but the functionality is pretty much the same. This is what creating lambdas looks like for me: (in Kotlin)

    override fun addMustacheLambdas(): ImmutableMap.Builder<String, Mustache.Lambda> =
        super.addMustacheLambdas()
            .put("lowerCase", Mustache.Lambda { fragment, writer ->
                writer.write(fragment.execute().toLowerCase())
            })
            .put("removeLastComma", Mustache.Lambda { fragment, writer ->
                writer.write(fragment.execute().removeSuffix(","))
            })
            .put("printContext", Mustache.Lambda { fragment, writer ->
                val context = fragment.context()
                println(context) // very useful for debugging if you are not the author of the model
                writer.write(fragment.execute())
            })
0

I'm using custom functions for that, in my case when working with dynamic SQL queries.

    $(document).ready(function () {
    var output = $("#output");    
    var template = $("#test1").html();
    var idx = 0;
    var rows_count = 0;
    var data = {};
    
    data.columns = ["name", "lastname", "email"];
    data.rows  = [
      ["John", "Wick", "john.wick@hotmail.com"],
      ["Donald", "Duck", "donald.duck@ducks.com"],
      ["Anonymous", "Anonymous","jack.kowalski@gmail.com"]
    ];

    data.rows_lines = function() {
      let rows = this.rows;
      let rows_new = [];
      for (let i = 0; i < rows.length; i++) {
        let row = rows[i].map(function(v) {
            return `'${v}'`
        })
        rows_new.push([row.join(",")]);
      }
      rows_count = rows_new.length;
      return rows_new
    }

    data.last = function() {
        return idx++ === rows_count-1; // omit comma for last record
    }
    
    var html = Mustache.render(template, data);
    output.append(html);

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/4.0.1/mustache.min.js"></script>
<h2>Mustache example: Generate SQL query (last item support - omit comma for last insert)</h2>

<div id="output"></div>

<script type="text/html" id="test1">
    INSERT INTO Customers({{{columns}}})<br/>
    VALUES<br/>
      {{#rows_lines}}
         ({{{.}}}){{^last}},{{/last}}<br/>
      {{/rows_lines}}
</script>

https://jsfiddle.net/tmdoit/4p5duw70/8/

tmdoit
  • 39
  • 4
0

The @LostMekkaSoft solution make sense, but lambda is a problem for simple tasks (helper functions):

Perhaps a solution is a complex helper-function that "inject" this kind of standard lambdas in the input, by additional input-configuration... Another solution is to elect a set of "standard functions" and build it as a Standard Mustache Helper-functions library for each language.

Mustache need a standard helper-library

Suppose a library of ms_*() Mustache helper functions. Example of ms_items_setLast() definition. To implement, the most voted solution here (in 2021) is the @Clyde's solution; so, generalizing it for any language:

For Javascript:

function ms_items_setLast(x,label='') {
   if (label=='' || label === undefined)
     x[ x.length - 1 ].last = true
   else
     x[label][ x[label].length - 1 ].last = true
   return x
}
// model = ms_items_setLast(model,'items');

For Python:

def ms_items_setLast(x,label=''):
   if label=='':
      x[ len(x) - 1 ]['last'] = True
   else:
      x[label][ len(x[label]) - 1 ]['last'] = True

using it at terminal:

model = {
"items": [
    {"name": "red"},
    {"name": "green"},
    {"name": "blue"}
  ]
}
items= [
    {"name": "red"},
    {"name": "green"},
    {"name": "blue"}
]
ms_items_setLast(model,'items')
ms_items_setLast(items)
model
items

results in

>>> model
{'items': [{'name': 'red'}, {'name': 'green'}, {'name': 'blue', 'last': True}]}
>>> items
[{'name': 'red'}, {'name': 'green'}, {'name': 'blue', 'last': True}]
Peter Krauss
  • 11,340
  • 17
  • 129
  • 247