Search code examples
javascriptspring-mvcbackbone.jsunderscore.js

rendering multiple templates


I'm learning Backbone and Underscore right now and I'm making a little mock email app.

I managed to pass a JSON collection of Email to my view and displayed it in a unordered list. What I want is to click on an item and have a separate template rendered next to my list displaying the content of the email.

Code snippet from my view:

the html templates:

<div>
    <h1>Backbone test data bind to view</h1>
    <hr/>
    <div class="page">
    </div>
    <div class="content">
    </div>
</div>

<script type="text/template" id="email-list-template-styled">       
<div class="tab">
    <ul>
        <@ _.each(emails, function(email){ @>
            <a class="tablinks" data-id="<@= email.get('id') @>" href="backbone" >
                <td> From: <@= email.get('sender') @> </td> </br>
                <td> To: <@= email.get('recipient') @> </td> </br>
                <td> Date: <@= email.get('dateSent') @> </td> </br>
            </a>
        <@ }) @>
    </ul>
</div>
</script>

<script type="text/template" id="email-content">
    <div id="emailContent" class="tabcontent">
        <h3>From: sender</h3>
        <h3>To: recipient</h3>
        <h3>Subject: subject</h3>
        <h3>Date: dateSent</h3>
        <p>message</p>
    </div>
</script> 

Java script:

<script type="text/javascript">
var EmailsList = Backbone.Collection.extend({
    url : 'backbone/getEmails.html'
});

var Email = Backbone.Model.extend({});

var EmailList = Backbone.View.extend({
    el: '.page',
    events: {
         'click' : 'showAlert'

        },
        showAlert: function(e){
            e.preventDefault();
            var that = this;
            var id = $(event.target).data('id');
            var Email = Backbone.Model.extend({
                url: 'backbone/getEmail/' + id
            });

            var email = new Email();
            email.fetch({
                success: function(email){
                    console.log(email.toJSON());
                    var template = _.template($('#email-content').html(), {email: email.models} );
                    that.$el.append(template);
                }
            })
        },

    render: function(){
        var that = this;
        var emails = new EmailsList();
        emails.fetch({
            success : function(emails) {
                console.log(emails.toJSON());
                var template = _.template($('#email-list-template-styled').html(), {emails: emails.models} );
                that.$el.html(template);
            }
        })
    }
});

var emailList = new EmailList();

var Router = Backbone.Router.extend({
    routes:{
        '': 'backbone'
    }
})

var router = new Router();
router.on('route:backbone', function(){
    emailList.render();
});

Backbone.history.start();
</script>

Single object data example:

dateSent:"2017-03-21 08:36"
id:3
message:"Message3"
recipient:"Recipient3"
sender:"Sender3"
subject:"Subject3"

So far I've managed to get the id of the Email I'm clicking to the click event. Than I tried to render a template passing the single email object (i get the correct email JSON), I'm appending the template so at the moment it creates multiple instances and i cant seem to get variables printed in the email-content template. I get an error: Uncaught TypeError: Cannot read property 'get' of undefined when i type in something like:

<h3>From: sender <@= email.get('sender') @></h3>

So the questions would be:

How can I re render the content template

How do I access the email variable in the content template?


Solution

  • Your code is using old underscore.js versions syntax, if you're using new version then that's an issue, use the new syntax:

    var template = _.template(templateString);
    template(data);
    

    and in the below line:

    var template = _.template($('#email-content').html(), {email: email.models} );
    

    email is a model, not a collection.

    So your code should be:

    var template = _.template($('#email-content').html(), {email: email} );
    

    Now you will be able to do <h3>From: sender <@= email.get('sender') @></h3>.

    I suggest doing:

    var template = _.template($('#email-content').html(), {email: email.toJSON()} );
    

    and rendering like <h3>From: sender <@= email.sender @></h3> just to make sure templating stuff doesn't affect original modal.


    Also, you shouldn't define modal constructor in an event handler

    var Email = Backbone.Model.extend({
       url: 'backbone/getEmail/' + id
    });
    

    Instead define your model like

    var Email = Backbone.Model.extend({
       urlRoot: 'backbone/getEmail/'
    });
    

    and set id on the model. Backbone will append id to url.

    Setting urlRoot is not necessary if you pass {model: Email} to the collection, and find the matching model from the collection using it's get() method than creating new model instance every time, which is the proper way to do it imo.

    Other suggestions:

    • Define a separate view for the detailed view of email
    • Avoid using el property if you're new to Backbone
    • Upgrade to new version of underscore.js and cache the template function in view's template property
    • Make the event listenr 'click' : 'showAlert' more specific like 'click .email' : 'showAlert'. Otherwise clicking anywhere in the view will trigger the handler. If user clicks on an element which is not an actual email with data id (like a dropdown or something) it'll cause bugs