3

I have been looking over and found alot of answers but none seem to work.

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>
           Shopping Cart
        </title>
        <link rel="stylesheet" href="lib/style.css" type="text/css">
    </head>
    <body>
<script id="rtemp" type="text/x-underscore-template"">
            <span><%= title %></span>
    </script>
        <script src="lib/jquery.js" type="text/javascript"></script>
        <script src="lib/underscore.js" type="text/javascript"></script>
        <script src="lib/backbone.js" type="text/javascript"></script>
        <script src="lib/script.js" type="text/javascript"></script>
    </body>
    <script>
var Photo = Backbone.Model.extend({


    initialize: function(){
        console.log('this model has been initialized');

        this.bind("change:title", function(){
            var title = this.get("title");
            console.log("My title has been changed to.. " + title);
            var pv = new PhotoView();
            pv.render();
        });

    },

    setTitle: function(newTitle){
        this.set({ title: newTitle });
    },

    setLocation: function(newLoc)
    {
        this.set({location:newLoc});
    }
});

var PhotoView = Backbone.View.extend
({
    el: $('body'),

    render: function(event)
    {
        var name = myPhoto.get('title');
        console.info(name);
        var template = _.template($('#rtemp').html(), {title:name});
        console.info(this.model);
        $(this.el).html(template);
        return this;
    }

});



    </script>
</html>

First;

Create a new instance of the method

 var newPhoto = new Photo();
 newPhoto.setTitle('Fishing');

This work fine, it will load into the body via the template. However if i then do it again,

newPhoto.setTitle('Sailing');

I get the error - "Cannot call method 'replace' of null"

No line error but I believe it is at

var template = _.template($('#rtemp').html(), {title:name});
0

1 Answer 1

24

You have a few things wrong here.

  1. Your template has a double " in the type attribute, it should be:

    <script id="rtemp" type="text/x-underscore-template">
    
  2. Your view's render is referencing myPhoto when it should be this.model:

    var name = this.model.get('title');
    
  3. And your main problem is that your view uses <body> as its this.el and your template is inside <body>.

You completely replace the content of <body> when you render your view:

$(this.el).html(template);

so after the first render call, there is no more #rtemp. Then, on the next render call, you try this:

var template = _.template($('#rtemp').html(), ...);

but since #rtemp isn't in the DOM anymore, everything falls apart.

If you grab the template immediately:

var PhotoView = Backbone.View.extend({
    el: $('body'),
    template: _.template($('#rtemp').html()),
    //...
});

and then use this.template() in render:

render: function(event) {
    //...
    var template = this.template({
        title: name
    });
    //...
}

you'll have better luck. You will, of course, need to make sure that you define your view inside a document-ready handler with this approach or #rtemp might not be available when you define your view.

Demo: http://jsfiddle.net/ambiguous/dwGsM/


That said, the structure of your application is rather bizarre. You have a model which listens to itself and then the model creates and renders a view when something changes. A model listening to itself is fine in itself but usually you have views listening to models and the view would re-render itself (or just parts of itself) as the model changes.

Your model should look more like this:

var Photo = Backbone.Model.extend({
    setTitle: function(newTitle) {
        this.set({ title: newTitle });
    },
    setLocation: function(newLoc) {
        this.set({ location: newLoc });
    }
});

And then your view like this:

var PhotoView = Backbone.View.extend({
    el: $('body'),
    template: _.template($('#rtemp').html()),
    initialize: function() {
        _.bindAll(this, 'render');
        this.model.on('change:title', this.render);
    },
    render: function(event) {
        this.$el.html(
            this.template(this.model.toJSON())
        );
        return this;
    }
});

The _.bindAll in initialize ensures that this.render will be called with the right this; then initialize binds to the event so that it can respond to changes in the model. The view knows what it cares about so it is responsible for dealing with changes in the model. And in the render, you usually just call toJSON to get the data for the template. Also, newer versions of Backbone include a cached version of $(this.el) in the view as this.$el so you don't have to $(this.el) yourself anymore.

Then you'd crank things up like this:

var newPhoto = new Photo();
var viewPhoto = new PhotoView({ model: newPhoto });
newPhoto.setTitle('Fishing');
newPhoto.setTitle('Sailing');

You tell the view what model to use by specifying the model option when creating the view.

You might also want to move the template <script> out of <body>.

New and Improved Demo: http://jsfiddle.net/ambiguous/Kytw7/

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you mu is to short, that has helped a lot. I have been bashing my head with this backbone stuff, but your explanation has cleared alot of issues :D
Excellent explanations! 1. I was wondering where do you call render() if after var viewPhoto = new PhotoView({ model: newPhoto }); you don't have any modifications to the model. 2. How do you reduce the number of 'rendering' times?
@serbanghita: Hard to say without more specifics but you'd probably just call it manually.
"You completely replace the content of <body> when you render your view." Thank You!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.