En las plantillas de Moustache, ¿hay una forma elegante de expresar una lista separada por comas sin la coma al final?

83

Estoy usando la biblioteca de plantillas Moustache e intento generar una lista separada por comas sin una coma al final, por ejemplo

rojo verde azul

Crear una lista con la coma final es sencillo, dada la estructura

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

y la plantilla

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

esto se resolverá

rojo verde azul,

Sin embargo, no veo una forma elegante de expresar el caso sin la coma al final. Siempre puedo generar la lista en código antes de pasarla a la plantilla, pero me preguntaba si la biblioteca ofrece un enfoque alternativo, como permitirle detectar si es el último elemento de una lista dentro de la plantilla.

John Kane
fuente
Sugiero construir la lista separada por comas en su código y pasarla al bigote como una sola cadena. La lógica más compleja que las opciones opcionales y las listas simples casi siempre es más legible en los lenguajes de programación clásicos.
yeoman
Los motores de plantilla más complejos que el bigote pueden hacer esto con bastante facilidad. Sin embargo, no es muy legible en ninguno de ellos, y con esto en mente, la decisión de hacer que el bigote sea tan simple como es fue bastante deliberada: D
yeoman

Respuestas:

43

Hrm, dudoso, la demostración de bigote prácticamente le muestra, con la firstpropiedad, que debe tener la lógica dentro de los datos JSON para averiguar cuándo poner la coma.

Entonces, sus datos se verían así:

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

y tu plantilla

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

Sé que no es elegante, pero como han mencionado otros, Moustache es muy liviano y no ofrece tales características.

Luca Matteis
fuente
24
también puede formatear sus datos, de modo que "elementos": ["rojo", "verde", "azul"], entonces puede hacer un {{elementos}}, esto ya generará una lista separada por comas :)
Anthony Chua
10
El comentario debería ser la respuesta correcta. Mucho más limpio. Es muy mala forma de modificar su fuente de datos para satisfacer las necesidades visuales del consumidor
Slick86
@AnthonyChua Si bien es elegante, esto puede (a) no ser un comportamiento documentado y, por lo tanto, sujeto a cambios futuros, aunque es poco probable, y (b) esto no pone espacios después de las comas, por lo que obtiene algo como first,second,third.
caw
8
es menos trabajo agregar solo una propiedad al último elemento {"name": "blue", "last": 1}y luego usar una sección invertida{{#items}} {{name}}{{^last}}, {{/last}} {{/items}}
TmTron
1
@ slick86 El comentario sobre el suyo da como resultado una lista separada por comas, pero no una en la que cada elemento esté entre comillas dobles.
GlenRSmith
92

Creo que una mejor forma es cambiar el modelo de forma dinámica. Por ejemplo, si está utilizando JavaScript:

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

y en su plantilla, use la sección invertida:

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

para representar esa coma.

Clyde
fuente
2
Me encanta. Cada vez que creo que Moustache no tiene suficientes funciones, es porque creo que la forma 'antigua' en la que la plantilla hace todo, bueno, vengo de JSP.
Nicolas Zozol
1
@NicolasZozol ¿Te das cuenta de que el bigote se creó exactamente con este tipo de simplicidad en mente? : D
yeoman
@NicolasZozol Para casos realmente complicados, es mejor construir cadenas directamente en un lenguaje de programación y así crear un modelo de vista simple que el lenguaje de plantilla pueda manejar. En este caso, la lista separada por comas se proporcionaría mediante código como una sola cadena.
yeoman
41

Haz trampa y usa CSS.

Si tu modelo es:

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

luego haz tu plantilla

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

y agregue un poco de CSS

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

Supongo que alguien dirá que este es un mal caso de poner marcado en la presentación, pero no creo que lo sea. La separación de valores por comas es una decisión de presentación para facilitar la interpretación de los datos subyacentes. Es similar a alternar el color de la fuente en las entradas.

Jared
fuente
Debe tenerse en cuenta que esto es compatible solo en IE9 +, por lo que si necesita admitir navegadores antiguos, es posible que deba usar otro enfoque
PoeHaH
1
Esto supone la suposición de que el bigote se está utilizando para páginas web; también tiene muchos usos fuera de eso
tddmonkey
30

Si está usando jmustache , puede usar las variables especiales -firsto -last:

{{#items}}{{name}}{{^-last}}, {{/-last}}{{/items}}
dbort
fuente
4
Me doy cuenta de que el OP se refería a la biblioteca de JavaScript Moustache, pero esto puede ayudar a otros usuarios de jmustache (como yo) que encuentran esta página.
dbort
Muchas gracias por la respuesta. Usé esto también para renderizar una plantilla con SpringBoot. No es necesario cambiar el modelo. Realmente estaba buscando esta característica. También me pregunto si hay controles de igualdad (por ejemplo {{#something=TEXT}})
JeanValjean
8

No puedo pensar en muchas situaciones en las que quieras enumerar un número desconocido de elementos fuera de <ul>o <ol>, pero así es como lo harías:

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

…Producirá:

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

Esto es Handlebars, eso sí. @indexfuncionará si testes una matriz.

Steven Vachon
fuente
parece bastante elegante! asumiendo que #if y @index están disponibles en todas o la mayoría de las implementaciones de bigote ... Tenga en cuenta que muchos de nosotros los usuarios de bigote no estamos generando HTML, incluso si ese es el caso de uso más común.
Spike0xff
Esta es una solución increíble, funciona como un encanto. Atención a cualquiera que se encuentre con esta respuesta, si desea envolver el resultado en una etiqueta HTML, hágalo {{.}}.
NetOperator Wibby
Este era el puntero correcto. Parece que ahora también puede usar [primero] y [último] condicionales para un control aún mejor. stackoverflow.com/questions/11479094/…
Maksym
6

La pregunta de si Moustache ofrece una forma elegante de hacer esto ha sido respondida, pero se me ocurrió que la forma más elegante de hacerlo podría ser usar CSS en lugar de cambiar el modelo.

Modelo:

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

CSS:

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

Esto funciona en IE8 + y otros navegadores modernos.

David Hammond
fuente
5

No hay una forma incorporada de hacer esto en Moustache. Tienes que modificar tu modelo para admitirlo.

Una forma de implementar esto en la plantilla es usar la {{^last}} {{/last}}etiqueta de sombrero de selección invertida . Solo omitirá el texto del último elemento de la lista.

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

O puede agregar una cadena delimitadora ", "al objeto, o idealmente la clase base si está usando un lenguaje que tiene herencia, luego establezca "delimitador" en una cadena vacía " "para el último elemento como este:

{{#items}}
    {{name}}{{delimiter}}
{{/items}}
cosbor11
fuente
1
Para que quede claro, algo en los datos de entrada debería ser nombrado "último" para que esto funcione. ¿Correcto?
FrustratedWithFormsDesigner
1
Correcto, tendrías que modificar tu modelo agregando una propiedad booleana llamada last. Luego, establezca el último elemento de la colección en last=true.
cosbor11
1
El "modelo" en este caso es en realidad un archivo de configuración que los usuarios pueden editar ... No creo que quiera confiar en ellos para colocar correctamente el "último" en el elemento correcto de una lista.
FrustratedWithFormsDesigner
¿Qué idioma está utilizando para invocar el motor de plantillas?
cosbor11
2
En ese caso, durante el tiempo de ejecución, convierta la configrepresentación del archivo en un objeto de Python. Supongo que la configuración está en jsono xml, ¿verdad? Luego, antes de pasarlo al motor de plantillas, obtenga el último elemento de la colección y aplique la lastpropiedad.
cosbor11
3

Para datos JSON sugiero:

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

La expresión regular eliminará todo lo que ,quede pendiente después de las últimas propiedades.

Esto también eliminará ,de los valores de cadena que continúan ",}" o ",]", así que asegúrese de saber qué datos se colocarán en su JSON

Mathiasrw
fuente
Esto definitivamente funcionó para mí ya que tengo un esquema JSON con plantilla. ¡Gracias!
yahyazini
2

Como la pregunta es:

¿Existe una forma elegante de expresar una lista separada por comas sin la coma final?

Luego, cambiar los datos, cuando ser el último elemento ya está implícito al ser el elemento final de la matriz, no es elegante.

Cualquier lenguaje de plantillas de bigote que tenga índices de matriz puede hacer esto correctamente. es decir. sin agregar nada a los datos . Esto incluye manubrios, ractive.js y otras implementaciones populares de bigote.

{{# names:index}}
    {{ . }}{{ #if index < names.length - 1 }}, {{ /if }}
{{ / }}
mikemaccana
fuente
1
Okay. Pero el bigote no tieneif
mauron85
@ mauron85 De hecho. Yo (y muchos otros) utilizo bigote como plural para varios lenguajes de plantillas inspirados en el bigote original.
mikemaccana
1

La forma más sencilla que encontré fue renderizar la lista y luego eliminar el último carácter.

  1. Render bigote.
  2. Elimina cualquier espacio en blanco antes y después de la cuerda.
  3. Luego elimine el último carácter

    let renderingData = Moustache Render (dataToRender, datos); renderingData = (renderingData.trim ()). substring (0, renderingData.length-1)

Thabelo Phusu Mmbengeni
fuente
1

En caso de que usar Handlebars sea una opción, que amplía las capacidades de Moustache, puede usar una variable @data:

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

Más información: http://handlebarsjs.com/reference.html#data

Roberto14
fuente
3
El manillar se construye sobre Moustache, no al revés
Andy Jones
0

Interesante. Sé que es un poco perezoso, pero por lo general lo soluciono creando plantillas en la asignación de valor en lugar de tratar de delimitar los valores por comas.

var global.items = {};
{{#items}}
    global.items.{{item_name}} = {{item_value}};
{{/items}}
Iwnnay
fuente
0

Tiendo a pensar que esta es una tarea muy adecuada para CSS (como respondieron otros). Sin embargo, asumiendo que está intentando hacer algo como producir un archivo CSV, no tendría HTML y CSS disponibles. Además, si está considerando modificar los datos para hacer esto de todos modos, esta puede ser una forma más ordenada de hacerlo:

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);
Cueva Jefferey
fuente
0

Si está usando java, puede usar lo siguiente:

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
fuente
0

Sé que esta es una pregunta antigua, pero aún quería agregar una respuesta que proporcione otro enfoque.

Respuesta principal

Moustache admite lambdas, ( ver documentación ) por lo que se puede escribir de esta manera:

Modelo:

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

Picadillo:

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

Salida:

rojo verde azul

Comentario

Personalmente, me gusta este enfoque sobre los demás, ya que en mi humilde opinión, el modelo solo debe especificar qué se representa y no cómo se representa. Técnicamente, la lambda es parte del modelo, pero la intención es mucho más clara.

Utilizo este enfoque para escribir mis propios generadores OpenApi. Allí, Moustache está envuelto por Java, pero la funcionalidad es prácticamente la misma. Así es como se ve la creación de lambdas para mí: (en 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())
            })
LostMekkaSoft
fuente
0

En escenarios más complejos, un modelo de vista es deseable por muchas razones. Representa los datos del modelo de una manera más adecuada para la visualización o, en este caso, el procesamiento de plantillas.

En caso de que esté utilizando un modelo de vista, puede representar listas fácilmente de una manera que facilite su objetivo.

Modelo:

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

Ver modelo:

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

La segunda representación de la lista es horrible de escribir, pero extremadamente sencilla de crear a partir del código. Mientras mapea su modelo al modelo de vista, simplemente reemplace todas las listas que necesite firsty lastpara con esta representación.

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
}

En el caso de listas ilimitadas, también puede establecer firsty omitir last, ya que una de ellas es suficiente para evitar la coma al final.

Utilizando first:

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

Usando last:

{{#numbers}}{{value}}{{^last}}, {{/last}}{{/numbers}}
hacendado
fuente
0

Estoy usando funciones personalizadas para eso, en mi caso cuando trabajo con consultas SQL dinámicas.

    $(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", "[email protected]"],
      ["Donald", "Duck", "[email protected]"],
      ["Anonymous", "Anonymous","[email protected]"]
    ];

    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
fuente