0
votes

I'm currently attempting to get upto speed with backbone.js, i figured the best way to do this is to get stuck into the online tutorials and documentation. The online tutorials and sample applications are excellent but in order to build by knowledge i'm attempting to build a sample website CRUD application of my own. For the sample, basically what i'm attempting to do is to merge two current online examples/tutorials. In an attempt to gain a better understanding of working with multiple models, collections and views.

Unfortunately i have gotten stuck... I apologies for the long winded explanation but as a novice i'm attempting to explain the issue as best as possible...

I have based my website application sample on the following tutorial:

https://github.com/ccoenraets/backbone-cellar/tree/master/bootstrap

View online example:

http://coenraets.org/backbone-cellar/bootstrap/

I was able to follow this tutorial and have a working version of the site. Now i wish to extent the application to contain more pages which fit into the application (backbone.js) structure. If you view the tutorial you will notice there is an 'about' page which simply loads a static html template into the application. What i would like to do is add a new page which displays a contact manager. The contact manager is deprived from the following tutorial:

http://net.tutsplus.com/tutorials/javascript-ajax/build-a-contacts-manager-using-backbone-js-part-1/

Please note: at this point in time for simplicity i'm only utilising part 1 of the tutorial.

Ok now to explain where i'm having the issue... Firstly i will outline what i have done. On the application i have added a new link in the headerView called Directory. On the main.js page (example of origianl: https://github.com/ccoenraets/backbone-cellar/blob/master/bootstrap/js/main.js) i have added the code as follows:

var AppRouter = Backbone.Router.extend({

routes: {
    ""                  : "list",
    "wines/page/:page"  : "list",
    "wines/add"         : "addWine",
    "wines/:id"         : "wineDetails",
    "about"             : "about",
    "directory"         : "directory"
},

initialize: function () {
    this.headerView = new HeaderView();
    $('.header').html(this.headerView.el);
},

list: function(page) {
    var p = page ? parseInt(page, 10) : 1;
    var wineList = new WineCollection();
    wineList.fetch({success: function(){
        $("#content").html(new WineListView({model: wineList, page: p}).el);
    }});
    this.headerView.selectMenuItem('home-menu');
},

wineDetails: function (id) {
    var wine = new Wine({id: id});
    wine.fetch({success: function(){
        $("#content").html(new WineView({model: wine}).el);
    }});
    this.headerView.selectMenuItem();
},

addWine: function() {
    var wine = new Wine();
    $('#content').html(new WineView({model: wine}).el);
    this.headerView.selectMenuItem('add-menu');
},

about: function () {
    if (!this.aboutView) {
        this.aboutView = new AboutView();
    }
    $('#content').html(this.aboutView.el);
    this.headerView.selectMenuItem('about-menu');
},

directory: function () {
    if (!this.directoryView) {
        this.directorytView = new DirectoryView();
    }
    $('#content').html(this.directoryView.el);
    this.headerView.selectMenuItem('directory-menu');
}

});

utils.loadTemplate(['HeaderView', 'WineView', 'WineListItemView', 'AboutView', 'DirectoryView'], function() { app = new AppRouter(); Backbone.history.start(); });

Now for the Directory (Contacts Manger) page, for the sake of the explanation, i have left the model view and collection on the single .js file as per the tutorial - i would of course look to separate the file (into model and view) once i get it working. As per the tutorial the code for the contact manager (directory) is as follows:

//demo data
window.contacts = [
    { name: "Contact 1", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "family" },
    { name: "Contact 2", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "family" },
    { name: "Contact 3", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "friend" },
    { name: "Contact 4", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "colleague" },
    { name: "Contact 5", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "family" },
    { name: "Contact 6", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "colleague" },
    { name: "Contact 7", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "friend" },
    { name: "Contact 8", address: "1, a street, a town, a city, AB12 3CD", tel: "0123456789", email: "[email protected]", type: "family" }
];

//define product model
window.Contact = Backbone.Model.extend({
    defaults: {
        photo: "/img/placeholder.png"
    }
});

//define directory collection
window.Directory = Backbone.Collection.extend({
    model: Contact
});

//define individual contact view
window.ContactView = Backbone.View.extend({
    tagName: "article",
    className: "contact-container",
    template: $("#contactTemplate").html(),

    render: function () {
        var tmpl = _.template(this.template);

        $(this.el).html(tmpl(this.model.toJSON()));
        //alert('this model: ' + this.model.toJSON().name);

        return this;
    }
});

//define master view
window.DirectoryView = Backbone.View.extend({
    el: $("#contacts"),

    initialize: function () {
        this.collection = new Directory(contacts);
        this.render();
    },

    render: function () {
        var that = this;
        _.each(this.collection.models, function (item) {
            that.renderContact(item);
        }, this);
    },

    renderContact: function (item) {
        var contactView = new ContactView({
            model: item
        });         

        this.$el.append(contactView.render().el);
    }
});

The changes i have made is simply remove the 'var' and replace with 'window.' to fit the existing stucture of the app. For example:

var DirectoryView = Backbone.View.extend({

becomes:

window.DirectoryView = Backbone.View.extend({

Now to the issue i'm having. I'm able to get the code to output (render) the html code to display the template.

I believe the issue is with the

//define individual contact view
window.ContactView = Backbone.View.extend({
    tagName: "article",
    className: "contact-container",
    template: $("#contactTemplate").html(),

    render: function () {
        var tmpl = _.template(this.template);

    $(this.el).html(tmpl(this.model.toJSON()));
    alert('this model: ' + this.model.toJSON().name);

    return this;
    }
});

Now i know that the data is being parsed correctly as the 'alert' is outputting the names correctly. The problem i'm having is the following line of code:

var tmpl = _.template(this.template);

is throwing the following error: "Uncaught TypeError: Cannot call method 'replace' of null".

I'm clueless on how to fix the issue :(

The DirectoryView.html template code is:

<div class="row">
<div class="span12">
    <div id="contact"></div>
    <script id="contactTemplate" type="text/template">
        <img src="<%= photo %>" alt="<%= name %>" />
        <h1><%= name %><span><%= type %></span></h1>
        <div><%= address %></div>
        <dl>
            <dt>Tel:</dt><dd><%= tel %></dd>
            <dt>Email:</dt><dd><a href="mailto:<%= email %>"><%= email %></a></dd>
        </dl>
    </script>
</div>

I hope i have supplied enough information. Please let me know if there is any more info required.

Thanks for having a look :)

Jake

1
As far as I can tell, it works jsfiddle.net/nikoshr/tPBVD Maybe a model not fully initialized, try to fill Contact.defaults with all the variables used in your template.nikoshr

1 Answers

0
votes

Cannot call method 'replace' of null

This means that inside the _.template method you are trying to call replace for something that is null, presumably a String. The undescore method looks like this (from annotated source)

_.template = function(text, data, settings) {
  settings = _.defaults({}, settings, _.templateSettings);

  var matcher = new RegExp([
    (settings.escape || noMatch).source,
    (settings.interpolate || noMatch).source,
    (settings.evaluate || noMatch).source
  ].join('|') + '|$', 'g');

  // This is the only place where replace is used
  var index = 0;
  var source = "__p+='";
  // Replace used on variable text
  text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
    // replace used on source that can't be null
    source += text.slice(index, offset)
      .replace(escaper, function(match) { return '\\' + escapes[match]; });
    source +=
      escape ? "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'" :
      interpolate ? "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'" :
      evaluate ? "';\n" + evaluate + "\n__p+='" : '';
    index = offset + match.length;
  });
  source += "';\n";

So the variable text has to be null. In your code text is this.template, so it must be null at the time it is initialized.

Are you sure that when you are extending View to create ContactView, that the #contactTemplate element is loaded in the DOM? The problem must be there. Try console logging this.template to see if it is really null. If you want to make sure the DOM is loaded before running any javascript, wrap them in a jQuery ready function.