javascriptcanjscanjs-view

Canjs – When to use which mustache tag when iterating lists?


There seems to be a few different ways to do the same thing in Can.js, which is great! But some of the ways work a little differently than others, and can affect how the DOM is rendered and updated. I'd appreciate it if someone could clear up the nuance.

I think the scenario where this choice becomes interesting is when you want to have some default text or a placeholder for an empty list.

{{#if list}} and {{#if list.length}}

These are not the same. Both an empty array and a can.List will render for {{#if list}}.

fiddle

{{#each list}}

So using what we learned with #if...

{{#if list.length}}
   <ul>
      {{#each list}}
         <li>...<li>
      {{/each}}
   </ul>
{{else}}
   <i>The list is empty.</i>
{{/if}}

{{#list}}

I think this is intended to be the best of both worlds. It only occurred to me today that since this is a block helper, it supports the {{else}}.

{{#list}}
   rendered for each item in list
{{else}}
   rendered once if list is empty
{{/list}}

The thing is, this can't produce the html we did with #each.

So the implementation seems to be dependent on the markup. Fair enough.

Here's the rub.

Supposedly, #each and #list update the DOM differently. From the docs for #each...

If the value of the key is a can.List, the resulting HTML is updated when the list changes. When a change in the list happens, only the minimum amount of DOM element changes occur.

So add one item to the list, only that item is rendered, remove an item, only that element is removed. The behavior of #list is not documented, but I'm under the impression it may re-render the entire block.

Questions

Which is best? Besides being more terse, I'm not sure #list has any advantages, so why do the authors suggest that it is preferred?


Solution

  • Assuming list is a can.List instance:

    {{#if list}} will check for the truthy value of list. This is akin to checking for the truthy value of any JS object and will result to true, regardless of list contents or length.

    {{#if list.length}} will check for the truthy value of the length attribute. If you have an empty list, the length will be 0, so the #if will result to false and no contents will be rendered. If there is a length >= 1, this will result to true and the contents of the #if will be rendered.

    #each and #list both iterate through an instance of can.List, however we setup the bindings differently.

    #each will setup bindings on every individual item being iterated through, while #list will not. This makes a difference in two scenarios:

    1) You have a large list, with minimal updates planned after initial render. In this case, #list might be more advantageous as there will be a faster initial render. However, if any part of the list changes, the entire #list area will be re-processed.

    2) You have a large list, with many updates planned after initial render. A grid with many columns of editable fields, for instance. In this case, you many want to use #each, even though it will be slower on initial render(we're setting up more bindings), you'll have faster updates as there are now many sections.

    Quick note on your case of if list has contents ..., else empty list area:

    {{#each items}} Looping through list items! {{/each}}

    {{^if items.length}} Show if there are no items! {{/if}}

    The above should account for that scenario. If there's additional logic, I would consider writing a custom helper(depending on what the logic looks like).