Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
302 views
in Technique[技术] by (71.8m points)

backbone.js - Underscore.js Template Issue - Cannot call method 'replace' of null

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});
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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/


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...