Handlebars.js: Suporte para mapas, conjuntos e iteráveis ​​personalizados em "cada" ajudante integrado?

Criado em 10 jan. 2018  ·  6Comentários  ·  Fonte: handlebars-lang/handlebars.js

Ao usar Handlebars em um ambiente ES6, a limitação do ajudante interno each de suportar apenas arrays e objetos genéricos se torna inconveniente. Para contornar isso, comecei a registrar minha própria versão do auxiliar each que suporta arrays, mapas, conjuntos, iteráveis ​​personalizados e objetos genéricos. Esse ajudante está abaixo.

Existe um plano ou vontade de introduzir suporte para esses tipos de listas no ajudante interno each ? Pergunto porque entendo que o Handlebars visa evitar polyfills e imagino que a única maneira de fazer o novo helper funcionar sem comprometer o suporte do navegador seria habilitar progressivamente o suporte para os diferentes tipos de listas dependentes do suporte nativo ou pré-polipreenchido do ambiente por Set , Map e Symbol .

Handlebars.registerHelper("each", function (contexts, options) {

    // Throw a runtime exception if options were not supplied.
    if (!options) {
        throw new Handlebars.Exception("Must pass iterator to #each");
    }

    // If the "list of contexts" is a function, execute it to get the actual list of contexts.
    if (typeof contexts === "function") {
        contexts = contexts.call(this);
    }

    // If data was supplied, frame it.
    const data = options.data ? Object.assign({}, options.data, { _parent: options.data }) : undefined;

    // Create the string into which the contexts will be handled and returned.
    let string = "";

    // Create a flag indicating whether or not string building has begun.
    let stringExtensionStarted = false;

    // Create a variable to hold the context to use during the next string extension. This is done to
    // allow iteration through the supplied list of contexts one step out of sync as they are looped
    // through later in this helper, ensuring a predictable sequence of value retrieval, string
    // extension, value retrieval, string extension...
    let nextContext;

    // Create a function responsible for expanding the string.
    const extendString = (final = false) => {

        // If other contexts have been encountered...
        if (nextContext) {

            // Expand the string using the block function.
            string += options.fn(nextContext.value, {
                data: data ? Object.assign(data, {
                    index: nextContext.index,
                    key: nextContext.key,
                    first: !stringExtensionStarted,
                    last: final
                }) : undefined,
                blockParams: [nextContext.key, nextContext.value]
            });

            // Note that string extension has begun.
            stringExtensionStarted = true;

        // If no contexts have been encountered and this is the final extension...
        } else if (final) {

            // Expand the string using the "else" block function.
            string += options.inverse(this);

        }

    };

    // If a list of contexts was supplied...
    if (contexts !== null && typeof contexts !== "undefined") {

        // Start a counter.
        let index = 0;

        // If an array list was supplied...
        if (Array.isArray(contexts)) {

            // For each of the possible indexes in the supplied array...
            for (const len = contexts.length; index < len; index++) {

                // If the index is in the supplied array...
                if (index in contexts) {

                    // Call the string extension function.
                    extendString();

                    // Define the context to use during the next string extension.
                    nextContext = {
                        index: index,
                        key: index,
                        value: contexts[index]
                    };

                }

            }

        // If a map list was supplied...
        } else if (contexts instanceof Map) {

            // For each entry in the supplied map...
            for (const [key, value] of contexts) {

                // Call the string extension function.
                extendString();

                // Define the context to use during the next string extension.
                nextContext = {
                    index: index,
                    key: key,
                    value: value
                };

                // Increment the counter.
                index++;

            }

        // If an iterable list was supplied (including set lists)...
        } else if (typeof contexts[Symbol.iterator] === "function") {

            // Get an iterator from the iterable.
            const iterator = contexts[Symbol.iterator]();

            // Create a variable to hold the iterator's next return.
            let next;

            // Do the following...
            do {

                // Iterate and update the variable.
                next = iterator.next();

                // If there is anything left to iterate...
                if (!next.done) {

                    // Call the string extension function.
                    extendString();

                    // Define the context to use during the next string extension.
                    nextContext = {
                        index: index,
                        key: index,
                        value: next.value
                    };

                    // Increment the counter.
                    index++;

                }

            // ... until there is nothing left to iterate.
            } while (!next.done);

        // If a list other than an array, map, or iterable was supplied...
        } else {

            // For each key in the supplied object...
            for (const key of Object.keys(contexts)) {

                // Call the string extension function.
                extendString();

                // Define the context to use during the next string extension.
                nextContext = {
                    index: index,
                    key: key,
                    value: contexts[key]
                };

                // Increment the counter.
                index++;

            }

        }

    }

    // Call the string extension a final time now that the last supplied context has been encountered.
    extendString(true);

    // Return the fully-extended string.
    return string;

});

Comentários muito úteis

@karlvr , você poderia iniciar um novo problema para o suporte ao mapa. Partes deste problema já estão resolvidas e eu gostaria de ter um começo limpo.

Todos 6 comentários

Deve ser possível agora, com #1557

@nknapp parece que a implementação em #1557 não suporta Map corretamente. Atualmente, produz um item iterado sendo o _entry_ no Map , que é uma tupla de [key, value] , enquanto o código de exemplo acima torna o item iterado value e define @key , que eu _acho_ é preferível. É preferível para mim!

Além disso, parece que as expressões atualmente não suportam Map , então você não pode dizer {{person.myMap.myMapKey}} . Estou me aprofundando mais nesse assunto agora.

Com um acréscimo em lookupProperty em runtime.js podemos pesquisar propriedades em Map s:

    lookupProperty: function(parent, propertyName) {
      if (parent instanceof Map) {
        return parent.get(propertyName)
      }

Existe algum apetite para adicionar suporte como este?

@karlvr Acho que vale a pena analisar sua proposta. Mas eu gostaria de discutir isso.

@karlvr , você poderia iniciar um novo problema para o suporte ao mapa. Partes deste problema já estão resolvidas e eu gostaria de ter um começo limpo.

@nknapp muito obrigado por sua rápida resposta; Acabei de fazer um PR com as alterações sugeridas. Podemos discutir lá? #1679

Esta página foi útil?
0 / 5 - 0 avaliações