id 未在 child.eval 处定义

id is not defined at child.eval

我正在编写我的第一个 Backbone 博客应用程序,但是当我尝试添加新的 post 时它会引发错误。

这是我的 app.js(所有 backbone 相关组件都在这个文件中):

_.templateSettings = {
    interpolate: /\{\{(.+?)\}\}/g
};

var Post = Backbone.Model.extend({});
var Posts = Backbone.Collection.extend({
    model : Post,
    url : "/posts"
});

var PostListView = Backbone.View.extend({
    tagName: "li",
    template: _.template("<a href='/posts/{{id}}'>{{title}}</a>"),
    events: {
        'click a': 'handleClick'
    },
    handleClick: function (e) {
        e.preventDefault();
        postRouter.navigate($(e.currentTarget).attr("href"),
            {trigger: true});
    },
    render: function () {
        this.el.innerHTML = this.template(this.model.toJSON());
        return this;
    }
});

var PostsListView = Backbone.View.extend({
    template: _.template("<h1>My Blog</h1><a href='/post/new' class='newPost'>New</a> <ul></ul>"),
    events: {
        'click .newPost': 'handleNewClick'
    },
    handleNewClick: function (e) {
        e.preventDefault();
        postRouter.navigate($(e.currentTarget).attr("href"),
            {trigger: true});
    },
    render: function () {
        this.el.innerHTML = this.template();
        var ul = this.$el.find("ul");
        this.collection.forEach(function (post) {
            ul.append(new PostListView({
                model: post
            }).render().el);
        });
        return this;
    }
});

var PostView = Backbone.View.extend({
    template: _.template($("#postView").html()),
    events: {
        'click a': 'handleClick'
    },
    render: function () {
        var model = this.model.toJSON();
        model.pubDate = new Date(Date.parse(model.pubDate)).toDateString();
        this.el.innerHTML = this.template(model);
        return this;
    },
    handleClick: function (e) {
        e.preventDefault();
        postRouter.navigate($(e.currentTarget).attr("href"),
            {trigger: true});
        return false;
    }
});

var PostFormView = Backbone.View.extend({
    tagName: 'form',
    template: _.template($("#postFormView").html()),
    initialize: function (options) {
        this.posts = options.posts;
    },
    events: {
        'click #submitPost': 'createPost',
        'click .back' : 'backButton'
    },
    render: function () {
        this.el.innerHTML = this.template();
        return this;
    },
    backButton: function (e) {
        e.preventDefault();
        postRouter.navigate($(e.currentTarget).attr("href"),
            {trigger: true});
        return false;
    },
    createPost: function (e) {
        e.preventDefault();
        var postAttrs = {
            content: $("#postText").val(),
            title: $("#postTitle").val(),
            pubDate: new Date(),
        };
        this.posts.create(postAttrs);
        postRouter.navigate("/", { trigger: true });
        return false;
    }
});

var PostRouter = Backbone.Router.extend({
    initialize: function (options) {
        this.posts = options.posts;
        this.main  = options.main;
    },
    routes: {
        '': 'index',
        'posts/:id': 'singlePost',
        'post/new': 'newPost'
    },
    index: function () {
        var pv = new PostsListView({ collection: this.posts });
        this.main.html(pv.render().el);
    },
    singlePost: function (id) {
        var post = this.posts.get(id);
        var pv = new PostView({ model: post });
        this.main.html(pv.render().el);
    },
    newPost: function () {
        var pfv = new PostFormView({ posts: this.posts });
        this.main.html(pfv.render().el);
    }
});

我的索引文件中也有一些视图模板:

<!DOCTYPE html>
<html>
<head>
    <title> Simple Blog </title>
</head>
<body>
<div id="main"></div>
<script src="/jquery.js"></script>
<script src="/underscore.js"></script>
<script src="/backbone.js"></script>
<script type="text/template" id="postFormView">
    <a href="/" class="back">All Posts</a><br />
    <input type="text" id="postTitle" placeholder="post title" />
    <br />
    <textarea id="postText"></textarea>
    <br />
    <button id="submitPost"> Post </button>
</script>
<script type="text/template" id="postView">
    <a href='/'>All Posts</a>
    <h1>{{title}}</h1>
    <p>{{pubDate}}</p>
    {{content}}
</script>
<script src="/app.js"></script>
<script>
    var postRouter = new PostRouter({
        posts: new Posts(<%- posts %>),
        main: $("#main")
    });
    Backbone.history.start({pushState: true});
</script>
</body>
</html>

查看 post 和主页工作正常,但是当我尝试创建一个新的 post 时,我从开发工具控制台收到此错误:

Uncaught ReferenceError: id is not defined
    at child.eval (eval at _.template (http://localhost:3000/underscore.js:1:1), <anonymous>:6:8)
    at child.template (http://localhost:3000/underscore.js:1214:21)
    at child.render (http://localhost:3000/app.js:27:34)
    at http://localhost:3000/app.js:48:16
    at Array.forEach (native)
    at Function._.each._.forEach (http://localhost:3000/underscore.js:79:11)
    at child.Collection.(anonymous function) [as forEach] (http://localhost:3000/backbone.js:956:24)
    at child.render (http://localhost:3000/app.js:45:25)
    at child.index (http://localhost:3000/app.js:118:27)
    at Object.callback (http://localhost:3000/backbone.js:1242:30)

服务器是一个简单的 nodejs 服务器,创建 post 的输出是这样的:

{"result":{"ok":1,"n":1},"ops":[{"content":"kljhlkjh","title":"jkhjklh","pubDate":"2016-10-29T10:21:47.793Z","id":12,"_id":"5814783b732bbe153461eca4"}],"insertedCount":1,"insertedId
s":["5814783b732bbe153461eca4"]}

哪里出错了?

首先,您需要找到导致错误的原因,以及错误来自何处。

错误来自 PostListViewrender 函数中的以下行:

this.el.innerHTML = this.template(this.model.toJSON());

并且是underscore的模板渲染抛出的错误。归结为:

template: _.template("<a href='/posts/{{id}}'>{{title}}</a>"),

看到{{id}}了吗?就是出错时没有定义的那个

{{id}} 未定义 是什么意思?

您正在传递 this.model.toJSON() 作为模板的数据。因此,这意味着 this.modelid 属性尚未定义

为什么我的模型 id 还没有定义?

因为通过collection's create function创建新模型是异步的

this.posts.create(postAttrs);
// the model hasn't received an id from the server yet.
postRouter.navigate("/", { trigger: true });

如何在集合的 create 调用后等待?

Backbone 为大多数(如果不是全部)异步函数提供 successerror 回调。

The options hash takes success and error callbacks which will both be passed (collection, response, options) as arguments.

因此,您可以将 createPost 函数更改为以下内容,添加一个 onPostCreated 回调。

createPost: function(e) {
    e.preventDefault();
    var postAttrs = {
        content: $("#postText").val(),
        title: $("#postTitle").val(),
        pubDate: new Date(),
    };
    this.posts.create(postAttrs, {
        context: this,
        success: this.onPostCreated
    });
    return false;
},
onPostCreated: function() {
    // you don't need to use the router, so your views are self-contained
    Backbone.history.navigate("/", { trigger: true });
},