Search code examples
jquery-templates

How do I prevent jQuery Template from digging too deeply on a string array in a recursively "reflection" template?


full jsFiddle Example

From what I found out on my last question about $.tmpl, passing an array of strings into a template containing an {{each}} will result in the template going over each string in the array like an array of characters because tmpl implicitly loops over arrays already. Unfortunately, that means, if I am already in a given template and I recursively call that template again on an array of strings (sub-objects work fine), I skip a level of recursion and try to template each string in the array instead of the templating the array itself.

If I am recursively templating an object by {{each}} (reflection, basically), how do I keep that implicit array loop from happening?

HTML

<div class="results"></div>
<script id="reflectTemplate" type="text/x-jquery-tmpl"> 
    <ul>
        {{each(i, prop) $data}}
        {{if $data.hasOwnProperty(i)}}
        <li>
            ${i}:
            {{if $item.shouldDigDeeper(prop)}}
            {{tmpl(prop, { shouldDigDeeper: $item.shouldDigDeeper, formatDisplay: $item.formatDisplay }) "#reflectTemplate"}}
            {{else}}
            ${$item.formatDisplay(prop)}
            {{/if}}
        </li>
        {{/if}}
        {{/each}}
    </ul>
</script>

JavaScript

var data = {
        test2: "abc",
        test3: [ "abc", "123", "def", "456" ]
    },
    templateFunctions = {
        shouldDigDeeper: function(itemToCheck) {
            return null !== itemToCheck && "object" === typeof(itemToCheck);
        },
        formatDisplay: function(propertyValue) {
            var result = propertyValue;    
            if (null === result) {
                result = "null";
            }
            else if ("string" === typeof (propertyValue)) {
                result = "\"" + result + "\"";
            }
            return result;
        }
    };

$("#reflectTemplate").tmpl(data, templateFunctions).appendTo($(".results"));

Actual Output

<ul>
    <li>test2: "abc" </li>
    <li>test3:
        <ul>
            <li>0: "a" </li>
            <li>1: "b" </li>
            <li>2: "c" </li>
        </ul>
        ...
        <ul>
            <li>0: "4" </li>
            <li>1: "5" </li>
            <li>2: "6" </li>
        </ul>
    </li>
</ul>

Desired Output

<ul>
    <li>test2: "abc" </li>
    <li>test3:
        <ul>
            <li>0: "abc" </li>
            ...
            <li>1: "456" </li>
        </ul>
    </li>
</ul>

Solution

  • In case @mblase75 is right (read: it isn't possible) and no one else comes up with anything else, here is a workaround I came up with.

    jsFiddle example

    Since tmpl pre-jumps a level deeper into arrays, I just came up with a system of templates that bounce off each other as an array comes up. One template handles only array items, while allowing nested objects/arrays within it (it seems). The duplication is a bit rough, but it appears to do the job for even crazy nested arrays.

    HTML

    <div class="results"></div>
    <script id="arrayDisplayTemplate" type="text/x-jquery-tmpl">
    <li>
        {{if null !== $data && "object" === typeof ($data)}}
            {{if $data instanceof Array}}
        [
        <ul>
            {{tmpl($data, { formatDisplay: $item.formatDisplay }) "#arrayItemTemplate"}}
        </ul>
        ]
            {{else}}
        {{tmpl($data, { formatDisplay: $item.formatDisplay }) "#reflectTemplate"}}
            {{/if}}
        {{else}}
        ${$item.formatDisplay($data)}
        {{/if}}
    </li>
    </script>
    <script id="reflectTemplate" type="text/x-jquery-tmpl"> 
    <ul>
        {{each(i, prop) $data}}
        {{if $data.hasOwnProperty(i)}}
        <li>
            ${i}:
            {{if null !== prop && "object" === typeof (prop)}}
                {{if prop instanceof Array}}
            [
            <ul>
                {{tmpl(prop, { formatDisplay: $item.formatDisplay }) "#arrayDisplayTemplate"}}
            </ul>
            ]
                {{else}}
            {{tmpl(prop, { formatDisplay: $item.formatDisplay }) "#reflectTemplate"}}
                {{/if}}
            {{else}}
            ${$item.formatDisplay(prop)}
            {{/if}}
        </li>
        {{/if}}
        {{/each}}
    </ul>
    </script>
    

    JavaScript

    var data = {
        test1: 123,
        test2: { w: [ "some", "string", "array" ], x: 1, y: 2, z: "abc" },
        test3: [ "abc", "123", "def", "456" ],
        test4: null
    },
        templateFunctions = {
            formatDisplay: function(propertyValue) {
                var propertyType = typeof (propertyValue),
                    result = propertyValue;
    
                if (null === result) {
                    result = "null";
                }
                else if ("string" === propertyType) {
                    result = "\"" + result + "\"";
                }
                return result;
            }
        };
    
    $("#reflectTemplate").tmpl(data, templateFunctions).appendTo($(".results"));