Handlebars.js: 支持内置的“每个”助手中的地图、集合和自定义迭代?

创建于 2018-01-10  ·  6评论  ·  资料来源: handlebars-lang/handlebars.js

在 ES6 环境中使用 Handlebars 时,内置的each helper 仅支持数组和泛型对象的限制变得不方便。 为了解决这个问题,我开始注册我自己的each帮助器版本,它支持数组、映射、集合、自定义迭代和通用对象。 那个助手在下面。

是否有计划或愿意在内置的each帮助程序中引入对这些类型的列表的支持? 我问是因为我知道 Handlebars 旨在避免 polyfills,我想在不影响浏览器支持的情况下使新助手工作的唯一方法是逐步启用对不同列表类型的支持,具体取决于环境的本机或预填充支持对于SetMapSymbol

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;

});

最有用的评论

@karlvr你能开始一个新的地图支持问题吗? 这个问题的一部分已经解决了,我想有一个干净的开始。

所有6条评论

现在应该可以了,#1557

@nknapp似乎#1557 中的实现不支持Map正确。 它当前生成一个迭代项,即Map中的 _entry_ ,它是[key, value]的元组,而上面的示例代码使迭代项成为value并设置@key ,我_认为_更可取。 这对我来说更可取!

此外,似乎表达式目前不支持Map ,所以你不能说{{person.myMap.myMapKey}} 。 我现在正在深入研究这个问题。

通过在runtime.js中添加lookupProperty ,我们可以在Map中查找属性:

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

有没有兴趣添加这样的支持?

@karlvr我认为您的建议值得研究。 但我想讨论一下。

@karlvr你能开始一个新的地图支持问题吗? 这个问题的一部分已经解决了,我想有一个干净的开始。

@nknapp非常感谢您的快速回复; 我刚刚对建议的更改进行了 PR。 我们可以在那里讨论吗? #1679

此页面是否有帮助?
0 / 5 - 0 等级