Search code examples
jqueryjquery-pluginsjquery-templatesjsrender

How to render an array in JsRender without iteration?


I have an some dynamic data loaded in to an array named "tabNames" like this:

    tabNames.push({name: hit.category});

Then I need to list the "name" fields in the following html. I want to list first 7 "name" values in the array "tabNames" horizontally and then the others in to a drop down.

This is my html

        <div id="categories" class="food-category-tab">
            <script id="categoriesList" type="text/x-jsrender">
                 <ul id="myTab" class="nav nav-tabs">
                 {{if #index <=6}}
                <li class="active"><a href="#home" data-toggle="tab">{{>name}}  </a></li>
                {{/if}}
                {{if #index >6}}
                 <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>
                  <ul class="dropdown-menu">
                    <li><a href="#dropdown1" data-toggle="tab">{{>name}}</a></li>
                  </ul>
                </li>
                {{/if}}
              </ul>
                </script>
         </div>

And I call jsrender from the js file,where the function is written for loading the content into "tabNames" array, like below:

    $("#categories").html($("#categoriesList").render(tabNames));

Can anybody tell what is wrong with the code and how to fix this?.


Solution

  • You are passing an array to render, so the whole template is being rendered for each item, including the wrapping <ul>.

    Two ways to deal with that:

    1: Call render with a boolean flag: noIteration:

    $("#categoriesList").render(tabNames, true);
    

    then use a template that iterates over #data such as:

    <ul id="myTab" class="nav nav-tabs">
        {{for #data}}
            ...
        {{/for}}
        {{if #data.length>6}}
            ...
        {{/if}}
    </ul>
    

    Or else, 2: Pass in your array as a property:

    var data = {names: tabNames};
    $("#categoriesList").render(data);
    

    And use a similar template, but iterating over names, not #data:

    <ul id="myTab" class="nav nav-tabs">
        {{for names}}
            ...
        {{/for}}
        {{if names.length>6}}
            ...
        {{/if}}
    </ul>
    

    Either way, the template to show the two levels of list can be structured like this:

    <ul id="myTab" class="nav nav-tabs">
        {{for names.slice(0, 7)}}
            <li class="active"><a href="#home" data-toggle="tab">{{>name}}  </a></li>
        {{/for}}
        {{if names.length>6}}
            <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>
                <ul class="dropdown-menu">
                    {{for names.slice(7)}}
                        <li><a href="#dropdown1" data-toggle="tab">{{>name}}</a></li>
                    {{/for}}
                </ul>
            </li>
        {{/if}}
    </ul>
    

    It is also possible to use {{if}}...{{else}} etc. but it breaks the natural combined hierarchy of HTML and JsRender markup, and so is less elegant and less maintainable - like this:

    <ul id="myTab" class="nav nav-tabs">
        {{for names}}
            {{if #index<=6}}
                <li class="active"><a href="#home" data-toggle="tab">{{>name}}  </a></li>
            {{else #index===7}}
                <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>
                <ul class="dropdown-menu">
                <li><a href="#dropdown1" data-toggle="tab">{{>name}}</a></li>
            {{else}}
                <li><a href="#dropdown1" data-toggle="tab">{{>name}}</a></li>
            {{/if}}
        {{/for}}
        {{if names.length>6}}
            </ul>
            </li>
        {{/if}}
    </ul>
    

    Note about #data:

    Any expression with #data can be simplified, since #data is the default current data context. You can write

    <ul>
        {{for #data}}
            ...
        {{/for}}
        {{if #data.length>6}}
            ...
        {{/if}}
    </ul>
    

    as

    <ul>
        {{for}} {{!-- in this case #data is an array so this iterates over the array--}}
            ...
        {{/for}}
        {{if length>6}}
            ...
        {{/if}}
    </ul>
    

    The whole template can be written like this:

    <ul>
      {{for slice(0, 7)}}
        <li>{{>name}}</li>
      {{/for}}
      {{if length>6}}
        <li><b>More:</b>
          <ul>
            {{for slice(7)}}
              <li>{{>name}}</li>
            {{/for}}
          </ul>
        </li>
      {{/if}}
    </ul>
    

    There is a running sample here http://jsfiddle.net/BorisMoore/x0h9drr0/