简体   繁体   中英

Render Handlebars template with variable length dictionaries

I have not been using Handlebars and Javascript all that long but I have been able to cobble together a mostly working App. However, after trying to add some more data I have hit a wall.

I have a Handlebars template that renders multiple dictionaries from an array in order to display the results of multiple API calls. Based on another Stack Overflow article I read, I was able to write a helper function that broke the array into its component dictionaries and rendered them based on getting key:values from the dictionary.

This has worked well up until this point as the dictionaries that are the product of each API call have been the same length. I would like to add some data to them in the form of CC'd emails but not all objects will have the same amount of CC'd emails.

Is it possible to dynamically render a template with a different amount of key:values?

Here is where my array a built (this comes after an API call where I get other required values and initialize the final array. I also get the linkedTicketCcs array in the previous function and pass it in.):

        let requesterName = data['user']['name'];

        let requesterEmail = data['user']['email'];


        let dataDictionary = {'ticket_number': ticketId, 'name': requesterName, 'email': requesterEmail, 'subject': ticketSubject,
                                'status': ticketStatus, 'description': ticketDescription};

        for (let i = 0; i < linkedTicketCcs.length; i++) {
            dataDictionary[`linked_cc_${i}`] = linkedTicketCcs[i]
        }
        finalArray.push(dataDictionary);

This function runs in a for loop in another function so multiple dictionaries are built and then pushed to the final array. Each dictionary in the array now has the right number of CC'd emails.

This final array is then broken into its components using this helper function

Handlebars.registerHelper("each", function (options) {
    let ret = "";

    for (let i = 0; i < finalArray.length; i++) {
        ret = ret + options.fn(finalArray[i]);
    }
    return ret;
});

The data is then injected into the following template:

    {{#each}}
    <table>
        <tr>
            <td><strong>Ticket: </strong> <a href="#" data-toggle="tooltip" data-trigger="hover focus" data-placement="top" title="View ticket" onclick="openTicket('{{ticket_number}}')">{{ticket_number}}</a> <strong>Status: </strong>{{status}}</td>
        </tr>
        <tr>
            <td><strong>Subject: </strong>{{subject}}</td>
        </tr>
        <tr>
            <td><strong>Requester: </strong><a href="mailto:{{email}}" class="no-link email">{{email}}</a></td>
        </tr>
     </table>
    {{/each}}   

As a workaround I can render something like this to add the CC'd emails:

       {{#if linked_cc_0}}
        <tr>
            <td><strong>CC: </strong><a href="mailto:{{linked_cc_0}}" class="no-link email">{{linked_cc_0}}</a></td>
        </tr>
        {{/if}}
        {{#if linked_cc_1}}
        <tr>
            <td><strong>CC: </strong><a href="mailto:{{linked_cc_1}}" class="no-link email">{{linked_cc_1}}</a></td>
        </tr>
        {{/if}}
        {{#if linked_cc_2}}

Where each linked CC is rendered conditionally if the key:value exists in the data passed to the template. However, it seems like there should be a cleaner way to do this, perhaps using another helper function.

So what I need to be able to do is to render multiple template blocks coming from dictionaries in an array where the dictionaries will not always be the same length. I assume this can be done but I am not sure how.

Using the #each builtin , you should be able to get what you want:

{{#eachArray}}
<table>
    <tr>
        <td><strong>Ticket: </strong> 
            <a href="#" data-toggle="tooltip" 
                        data-trigger="hover focus" 
                        data-placement="top" title="View ticket" 
                        onclick="openTicket('{{ticket_number}}')">{{ticket_number}}
            </a> <strong>Status: </strong>{{status}}
        </td>
    </tr>
    <tr>
        <td><strong>Subject: </strong>{{subject}}</td>
    </tr>
    <tr>
        <td><strong>Requester: </strong><a href="mailto:{{email}}" class="no-link email">{{email}}</a></td>
    </tr>
    {{#each ccList}}
    <tr>
        <td><strong>CC: </strong><a href="mailto:{{this}}" class="no-link email">{{this}}</a></td>
    </tr>
    {{/each}}
 </table>
{{/eachArray}}   

Your problem is made difficult by the data structure you are choosing.

By making each CC email a unique key on the email object ( linked_cc_0 , linked_cc_1 , etc) you are forcing your template to have to know all of these keys in order to render the values. This is not an ideal structure.

Since your CC emails are an arbitrarily long list of strings, why not structure your data as such?

If each email object has, for example, a linked_ccs key with an array value containing 0 or many strings, it is easy to handle this in your template with a simple #each .

You would need only assign the linkedTicketCss to the dataDictionary object at the linked_css key, as in:

dataDictionary.linked_ccs = linkedTicketCcs;

Your template then becomes:

{{#each emails}}
  {{! existing email template here }}
  {{#each linked_ccs as |linked_cc|}}
    <tr>
      <td><strong>CC: </strong><a href="mailto:{{linked_cc}}" class="no-link email">{{linked_cc}}</a></td>
    </tr>
  {{/each}}
{{/each}

I have created a fiddle for your reference.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM