Search code examples
javascripthtml-tablemustache

Using mustache.js to render array data into a table rows; stuck at implementing row editing


I am creating an app for tracking expenses.

Here is a codepen with all of the source code.

Expense Tracking App Screenshot


I have a global object called object that I render out to a Mustache template:

var object = {
    info : [
        {date: "14, July", day: "Saturday", item: "Lunch", price: 40}
        {date: "15, July", day: "Sunday", item: "Airport", price: 80}
        {date: "14, July", day: "Saturday", item: "Snacks", price: 25}
    ],
    withdrew: 0
};
{{#info}}
<tr>
    <td>{{date}}</td>
    <td>{{day}}</td>
    <td>{{item}}</td>
    <td>{{price}} /-</td>
    <td><a href="#" id="editInfo">Edit</a> | <a href="#" id="deleteInfo">Delete</a></td>
</tr>
{{/info}}
Mustache.render(template, object);

As the user enters details in the Add Item div (to the right of the screenshot), the info array in object gets filled (it currently has 3 entries).


The part I'm stuck on is editing a row when the user clicks either the "Edit" or "Delete" button in the "MODIFY" table column. I have the following click listener bound to the table:

var modifyBtn = document.querySelector('table');
modifyBtn.addEventListener('click', function(e){
    console.log(e.target);
});

With this, I am able to get both of the button nodes correctly with console.log(), but how do I make them point uniquely to the original array elements that each row is generated from?

Any help is highly appreciated.


Solution

  • There are a couple of ways to do this, but the way that immediately stands out to me is the following:

    1. Add the indexes to the array elements for the Mustache render function
    2. Render out the indexes as data- attributes in the HTML
    3. Retrieve this data in the click listener
    4. Modify the original info array using this data

    Let's run through this.


    Step 1: Add the indexes to the array elements for the Mustache render function

    First things first, we want to retrieve the array indexes in the HTML. This data isn't available by default when rendering with Mustache, so we need to add it in ourselves. This can't be a permanent addition to the array objects. The actual indexes can change at any time (say, if an element is removed). Instead, we only want to render out the indexes for the HTML and nowhere else.

    Create the following addIndexes() function:

    function addIndexes(object) {
        object = JSON.parse(JSON.stringify(object));
        var info = object.info;
    
        for (var i = 0; i < info.length; i++) {
            info[i].index = i;
        }
    
        return object;
    }
    
    • The first line clones the object. Remember, we don't want to modify the original object or array. This allows us to work on a copy that doesn't affect the original.
    • We run through the array and add the index of that array to a property called index.

    var object = {
        info : [
            {date: "14, July", day: "Saturday", item: "Lunch", price: 40},
            {date: "15, July", day: "Sunday", item: "Airport", price: 80},
            {date: "14, July", day: "Saturday", item: "Snacks", price: 25}
        ],
        withdrew: 0
    };
    
    function addIndexes(object) {
        object = JSON.parse(JSON.stringify(object));
        var info = object.info;
    
        for (var i = 0; i < info.length; i++) {
            info[i].index = i;
        }
    
        return object;
    }
    
    console.log("--- The object AFTER addIndexes() ---");
    console.log(addIndexes(object));
    
    console.log("--- The object BEFORE addIndexes() ---");
    console.log(object);

    Now, simply modify the Mustache.render() method to use our new addIndexes() function:

    function refreshDOM() {
        var outputBody = Mustache.render(template, addIndexes(object));
        ...
    }
    

    Step 2: Render out the indexes as data- attributes in the HTML

    Now we need to attach those indexes in our HTML template. In your main index.html, go to the render function and change the button code to read this:

    <td><a href="#" id="editInfo" data-index="{{index}}">Edit</a> | <a href="#" id="deleteInfo" data-index="{{index}}">Delete</a></td>
    

    Now we are outputting the index to the data-index attribute, which we can retrieve later. This renders out each button element to look something like this:

    <td><a href="#" id="editInfo" data-index="0">Edit</a> | <a href="#" id="deleteInfo" data-index="0">Delete</a></td>
    <td><a href="#" id="editInfo" data-index="1">Edit</a> | <a href="#" id="deleteInfo" data-index="1">Delete</a></td>
    

    And so on, for each row.

    Step 3: Retrieve this data in the click listener

    We can now actually handle this data. Change your modifyBtn click event listener to be the following:

    var modifyBtn = document.querySelector('table');
    modifyBtn.addEventListener('click', function(e){
        var el = e.target;
        var action = el.id;
        var index = parseInt(el.dataset.index);
    
        switch (action) {
            case "editInfo": editInfo(index); break;
            case "deleteInfo": deleteInfo(index); break;
        }
    });
    
    • We're now getting the el (which is just the element) from event.target.
    • We can get a string with the action (like "editInfo") from your el.id.
    • The index value is data-index which can be retrieved with el.dataset.index. Note that this gets placed in HTML as a string (which we can't use), so we have to run parseInt() on it to get an integer from the string.
    • With a switch statement, we can now perform an action depending on the ID.

    Neither of these handler functions (editInfo() nor deleteInfo()) are currently declared, so on to the final step!

    Step 4: Modify the original info array using this data

    I don't know what you want to do with the editInfo ID, so I'll leave that one up to you. I've created a simple deleteInfo function directly above the click listener:

    function deleteInfo(index) {
        object.info.splice(index, 1);
        refreshDOM();
    }
    

    This will remove the specified index from the global object.info array and then call refreshDOM() to update.


    We're now complete! Sorry this was such a long-winded answer, I wanted to break down each step as we went along. Let me know if this all made sense and if you have any questions!