Czy w szablonach Mustache istnieje elegancki sposób wyrażania listy oddzielonej przecinkami bez końcowego przecinka?

84

Używam biblioteki szablonów Mustache i próbuję wygenerować listę oddzieloną przecinkami bez końcowego przecinka, np

czerwony, zielony, niebieski

Tworzenie listy z końcowym przecinkiem jest proste, biorąc pod uwagę strukturę

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

i szablon

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

to rozwiąże

czerwony, zielony, niebieski,

Jednak nie widzę eleganckiego sposobu wyrażenia przypadku bez końcowego przecinka. Zawsze mogę wygenerować listę w kodzie przed przekazaniem jej do szablonu, ale zastanawiałem się, czy biblioteka oferuje alternatywne podejście, takie jak umożliwienie wykrycia, czy jest to ostatnia pozycja na liście w szablonie.

John Kane
źródło
Proponuję zbudować listę oddzieloną przecinkami w swoim kodzie i przekazać ją do wąsów jako pojedynczy ciąg. Bardziej złożona logika niż opcje i proste listy jest prawie zawsze bardziej czytelna w klasycznych językach programowania.
yeoman,
Bardziej złożone silniki szablonów niż wąsy mogą to zrobić dość łatwo. Nie jest to jednak zbyt czytelne w żadnym z nich i mając to na uwadze, decyzja, aby wąsy były tak proste, jak są, była dość celowa: D
yeoman,

Odpowiedzi:

43

Hrm, wątpliwe, demo wąsów prawie pokazuje ci, z tą firstwłaściwością, że musisz mieć logikę wewnątrz danych JSON, aby dowiedzieć się, kiedy wstawić przecinek.

Twoje dane wyglądałyby więc mniej więcej tak:

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

i twój szablon

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

Wiem, że nie jest elegancki, ale jak wspominali inni Mustache jest bardzo lekki i nie ma takich funkcji.

Luca Matteis
źródło
24
możesz również sformatować swoje dane, tak aby "items": ["red", "green", "blue"] wtedy możesz po prostu zrobić {{items}} to wyświetli już listę oddzieloną przecinkami :)
Anthony Chua
10
Komentarz powinien być właściwie poprawną odpowiedzią. Dużo czystsze. To naprawdę zła forma modyfikowania źródła danych w celu zaspokojenia wizualnych potrzeb konsumenta
Slick86
@AnthonyChua Chociaż eleganckie, to może (a) nie być udokumentowanym zachowaniem, a zatem może podlegać przyszłym zmianom, chociaż jest to mało prawdopodobne, oraz (b) nie umieszcza spacji po przecinkach, więc otrzymasz coś w rodzaju first,second,third.
krakać
8
jest mniej pracy, aby dodać tylko jedną właściwość do ostatniego elementu, {"name": "blue", "last": 1}a następnie użyć sekcji odwróconej{{#items}} {{name}}{{^last}}, {{/last}} {{/items}}
TmTron
1
@ slick86 Komentarz powyżej twojego daje w wyniku listę oddzieloną przecinkami, ale nie taką, na której każdy element jest ujęty w podwójne cudzysłowy.
GlenRSmith
93

Myślę, że lepszym sposobem jest dynamiczna zmiana modelu. Na przykład, jeśli używasz JavaScript:

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

aw szablonie użyj odwróconej sekcji:

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

aby wyrenderować ten przecinek.

Clyde
źródło
2
Kocham to. Za każdym razem, gdy myślę, że Mustache nie ma wystarczającej funkcji, to dlatego, że myślę, że „stary” sposób, w jaki szablon robi wszystko - cóż, pochodzę z JSP.
Nicolas Zozol,
1
@NicolasZozol Zdajesz sobie sprawę, że wąsy zostały stworzone z myślą o właśnie takiej prostocie? : D
yeoman,
@NicolasZozol W naprawdę skomplikowanych przypadkach najlepiej jest budować ciągi znaków bezpośrednio w języku programowania, tworząc w ten sposób prosty model widoku, który obsługuje język szablonów. W takim przypadku lista oddzielona przecinkami zostanie dostarczona za pośrednictwem kodu jako jeden ciąg.
yeoman,
41

Oszukuj i używaj CSS.

Jeśli Twój model to:

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

następnie utwórz swój szablon

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

i dodaj trochę CSS

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

Myślę, że ktoś powie, że to zły przypadek umieszczania znaczników w prezentacji, ale nie sądzę. Wartości oddzielające przecinki to decyzja dotycząca prezentacji, która ułatwia interpretację danych bazowych. Jest to podobne do zmiany koloru czcionki we wpisach.

Jared
źródło
Należy zauważyć, że jest to zgodne tylko z IE9 +, więc jeśli potrzebujesz obsługiwać stare przeglądarki, być może będziesz musiał użyć innego podejścia
PoeHaH
1
To sprawia, że ​​zakłada się, że wąsy są używane na stronach internetowych - ma też wiele zastosowań poza tym
tddmonkey
30

Jeśli używasz jmustache , możesz użyć specjalnych -firstlub -lastzmiennych:

{{#items}}{{name}}{{^-last}}, {{/-last}}{{/items}}
dbort
źródło
4
Zdaję sobie sprawę, że OP odnosił się do biblioteki JavaScript Mustache, ale może to pomóc innym użytkownikom jmustache (takim jak ja), którzy znajdą tę stronę.
dbort
Bardzo dziękuję za odpowiedź. Użyłem tego również do renderowania szablonu za pomocą SpringBoot. Nie ma potrzeby zmiany modelu. Naprawdę szukałem tej funkcji. Zastanawiam się też, czy są kontrole równości (np. {{#something=TEXT}})
JeanValjean
8

Nie przychodzi mi do głowy wiele sytuacji, w których chciałbyś wymienić nieznaną liczbę elementów poza <ul>lub <ol>, ale tak byś to zrobił:

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

…będzie produkować:

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

Pamiętaj, że to kierownica. @indexbędzie działać, jeśli testjest tablicą.

Steven Vachon
źródło
wydaje się cholernie elegancki! zakładając, że #if i @index są dostępne we wszystkich lub większości implementacji wąsów ... Należy pamiętać, że wielu z nas użytkowników wąsów nie generuje kodu HTML, nawet jeśli jest to najczęstszy przypadek użycia.
Spike0xff
To niesamowite rozwiązanie, działa jak urok. Uwaga dla każdego, kto natknie się na tę odpowiedź, jeśli chcesz opakować wynik w znacznik HTML, zrób to wokół {{.}}.
NetOperator Wibby
To był właściwy wskaźnik. Wygląda na to, że możesz teraz również używać [pierwszy] i [ostatni] warunek, aby jeszcze lepiej kontrolować. stackoverflow.com/questions/11479094/…
Maksym
6

Odpowiedziano na pytanie, czy Mustache oferuje elegancki sposób, aby to zrobić, ale przyszło mi do głowy, że najbardziej eleganckim sposobem na to może być użycie CSS, a nie zmiana modelu.

Szablon:

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

CSS:

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

Działa to w IE8 + i innych nowoczesnych przeglądarkach.

David Hammond
źródło
5

W Mustache nie ma na to wbudowanego sposobu. Musisz zmienić swój model, aby go wspierać.

Jednym ze sposobów zaimplementowania tego w szablonie jest użycie odwróconego {{^last}} {{/last}}tagu kapeluszowego wyboru . Pominie tekst tylko dla ostatniej pozycji na liście.

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

Lub możesz dodać ciąg separatora jako ", "do obiektu lub najlepiej klasę bazową, jeśli używasz języka, który ma dziedziczenie, a następnie ustawić „delimiter” na pusty ciąg " "dla ostatniego elementu w następujący sposób:

{{#items}}
    {{name}}{{delimiter}}
{{/items}}
cosbor11
źródło
1
Żeby było jasne, coś w danych wejściowych musiałoby faktycznie mieć nazwę „ostatnia”, aby to zadziałało. Poprawny?
FrustratedWithFormsDesigner
1
Prawidłowo, należałoby zmienić model, dodając właściwość logiczną o nazwie last. Następnie ustaw ostatnią pozycję w kolekcji na last=true.
cosbor11
1
„Model” w tym przypadku jest w rzeczywistości plikiem konfiguracyjnym, który użytkownicy mogą edytować ... Nie sądzę, żebym chciał im ufać, że poprawnie umieścą „ostatni” na właściwej pozycji na liście.
FrustratedWithFormsDesigner
jakiego języka używasz do wywoływania silnika szablonów?
cosbor11
2
W takim przypadku w czasie wykonywania przekonwertuj configreprezentację pliku na obiekt Pythona. Zgaduję config jest jsonlub xml, prawda? Następnie, zanim przekażesz go do silnika szablonów, pobierz ostatni element kolekcji i zastosuj lastwłaściwość.
cosbor11
3

W przypadku danych JSON sugeruję:

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

Wyrażenie regularne usunie ,zawieszenie po ostatnich właściwościach.

Spowoduje to również usunięcie ,z ciągów wartości zawierających „,}” lub „,]”, więc upewnij się, że wiesz, jakie dane zostaną umieszczone w Twoim JSON

mathiasrw
źródło
To zdecydowanie załatwiło sprawę, ponieważ mam szablonowy schemat JSON. Dzięki!
yahyazini
2

Ponieważ pytanie brzmi:

czy istnieje elegancki sposób wyrażenia listy oddzielonej przecinkami bez końcowego przecinka?

Zatem zmiana danych - gdy bycie ostatnim elementem jest już domniemane przez to, że jest ostatnim elementem tablicy - nie jest eleganckie.

Każdy język szablonów wąsów, który ma indeksy tablicowe, może to zrobić poprawnie. to znaczy. bez dodawania czegokolwiek do danych . Obejmuje to kierownice, ractive.js i inne popularne implementacje wąsów.

{{# names:index}}
    {{ . }}{{ #if index < names.length - 1 }}, {{ /if }}
{{ / }}
mikemaccana
źródło
1
Dobrze. Ale wąsów nie maif
mauron85
@ mauron85 Rzeczywiście. Ja (i wielu innych) używam wąsów jako liczby mnogiej dla różnych języków szablonów inspirowanych oryginalnymi wąsami.
mikemaccana
1

Najprostszym sposobem, jaki znalazłem, było wyrenderowanie listy, a następnie usunięcie ostatniego znaku.

  1. Renderuj wąsy.
  2. Usuń wszelkie spacje przed i po ciągu.
  3. Następnie usuń ostatni znak

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

Thabelo Phusu Mmbengeni
źródło
1

W bardziej złożonych scenariuszach model widoku jest pożądany z wielu powodów. Przedstawia dane modelu w sposób lepiej dostosowany do wyświetlania lub, w tym przypadku, przetwarzania szablonu.

Jeśli używasz modelu widoku, możesz łatwo przedstawić listy w sposób ułatwiający osiągnięcie celu.

Model:

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

Zobacz model:

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

Reprezentacja drugiej listy jest okropna do wpisania, ale niezwykle prosta do utworzenia z kodu. Podczas mapowania modelu do modelu widoku, po prostu zastąp każdą listę, której potrzebujesz firsti lastdla której, taką reprezentacją.

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
}

W przypadku list nieograniczonych można również tylko ustawiać firsti pomijać last, ponieważ jedna z nich wystarczy, aby uniknąć końcowego przecinka.

Używając first:

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

Używając last:

{{#numbers}}{{value}}{{^last}}, {{/last}}{{/numbers}}
rolnik
źródło
1

W przypadku, gdy użycie Handlebars jest opcją, która rozszerza możliwości Mustache, możesz użyć zmiennej @data:

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

Więcej informacji: http://handlebarsjs.com/reference.html#data

Roberto14
źródło
3
Kierownica opiera się na wąsach, a nie na odwrót
Andy Jones
0

Ciekawy. Wiem, że to trochę leniwe, ale zwykle omijam to, umieszczając szablony w przypisaniu wartości, zamiast próbować ograniczać wartości przecinkami.

var global.items = {};
{{#items}}
    global.items.{{item_name}} = {{item_value}};
{{/items}}
Iwnnay
źródło
0

Wydaje mi się, że jest to zadanie dobrze dopasowane do CSS (na co odpowiadali inni). Jednak zakładając, że próbujesz zrobić coś takiego, jak utworzenie pliku CSV, nie będziesz mieć dostępnego HTML i CSS. Ponadto, jeśli zastanawiasz się nad zmodyfikowaniem danych, aby to zrobić, może to być prostszy sposób:

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);
Jaskinia Jefferey
źródło
0

Jeśli korzystasz z Java, możesz użyć:

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
źródło
0

Wiem, że to stare pytanie, ale nadal chciałem dodać odpowiedź, która zawiera inne podejście.

Główna odpowiedź

Mustache obsługuje lambdy ( patrz dokumentacja ), więc można to zapisać w ten sposób:

Szablon:

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

Haszysz:

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

Wynik:

czerwony, zielony, niebieski

Komentarz

Osobiście podoba mi się to podejście nad innymi, ponieważ IMHO model powinien określać tylko, co jest renderowane, a nie jak jest renderowany. Technicznie lambda jest częścią modelu, ale cel jest znacznie bardziej jasny.

Używam tego podejścia do pisania własnych generatorów OpenApi. Tam Mustache jest opakowany przez Javę, ale funkcjonalność jest prawie taka sama. Tak wygląda u mnie tworzenie lambd: (w Kotlinie)

    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
źródło
0

Używam do tego funkcji niestandardowych, w moim przypadku podczas pracy z dynamicznymi zapytaniami SQL.

    $(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
źródło