Handlebars.js: ¿Soporte para mapas, conjuntos e iterables personalizados en el ayudante "cada" incorporado?

Creado en 10 ene. 2018  ·  6Comentarios  ·  Fuente: handlebars-lang/handlebars.js

Al usar Handlebars en un entorno ES6, la limitación del ayudante each integrado de admitir solo matrices y objetos genéricos se vuelve inconveniente. Para solucionar esto, comencé a registrar mi propia versión del ayudante each que admite matrices, mapas, conjuntos, iterables personalizados y objetos genéricos. Ese ayudante está abajo.

¿Existe un plan o voluntad de introducir soporte para este tipo de listas en el asistente each incorporado? Pregunto porque entiendo que Handlebars tiene como objetivo evitar los polirrellenos e imagino que la única forma de hacer que el nuevo ayudante funcione sin comprometer la compatibilidad con el navegador sería habilitar progresivamente la compatibilidad con los diferentes tipos de listas que dependen de la compatibilidad nativa o preconfigurada del entorno. por Set , Map y 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;

});

Comentario más útil

@karlvr , ¿podría iniciar un nuevo problema para el soporte de mapas? Partes de este problema ya están resueltas y me gustaría tener un comienzo limpio.

Todos 6 comentarios

Debería ser posible ahora, con #1557

@nknapp parece que la implementación en #1557 no admite Map correctamente. Actualmente produce un elemento iterado que es la _entrada_ en Map , que es una tupla de [key, value] , mientras que el código de ejemplo anterior convierte el elemento iterado en value y establece @key , que _creo_ que es preferible. ¡Es preferible para mí!

Además, parece que las expresiones actualmente no son compatibles Map , por lo que no puede decir {{person.myMap.myMapKey}} . Estoy profundizando más en este tema ahora.

Con una adición en lookupProperty en runtime.js podemos buscar propiedades en Map s:

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

¿Hay algún deseo de agregar soporte como este?

@karlvr Creo que vale la pena analizar su propuesta. Pero me gustaría discutirlo.

@karlvr , ¿podría iniciar un nuevo problema para el soporte de mapas? Partes de este problema ya están resueltas y me gustaría tener un comienzo limpio.

@nknapp muchas gracias por su rápida respuesta; Acabo de hacer un PR con los cambios sugeridos. ¿Podemos discutir allí? #1679

¿Fue útil esta página
0 / 5 - 0 calificaciones