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.


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.


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.


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.


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}}

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.


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.


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}}

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


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 }}
{{ / }}

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)



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}}

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);

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());

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())
            })

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}}

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", "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/

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.