左侧 User 列表上方添加了一个 "Add" 按钮,点击之后右侧的页面会显示一个增加 User 的表单,表单提交之后弹出一个提示框,左侧列表也会更新。如下图所示:
4.html,新增了表单的模板和 "Add" 按钮:
1 <!-- 增加User的表单 --> 2 <script type="text/template" id="user-form-template"> 3 <h3>Add User</h3> 4 <ul id="user-form" class="editing"> 5 <li>Username:<input type="text" name="username" /></li> 6 <li>Password:<input type="password" name="password" /></li> 7 <li>Email:<input type="text" name="email" /></li> 8 <li>Phone:<input type="text" name="phone" /></li> 9 <button id="add-submit">Submit</button> 10 </ul> 11 </script> 12 ...... 13 <body> 14 <div id="main"> 15 <div id="left"> 16 <h3></h3> 17 <!-- 增加一个按钮,点击后显示增加User的表单 --> 18 <p><button id="add">Add</button></p> 19 <ul id="user-list"></ul> 20 </div> 21 <div id="right"></div> 22 </div> 23 </body>
mvc4.js
1 $(document).ready(function() { 2 3 var User = Backbone.Model.extend({ 4 }); 5 6 var UserList = Backbone.Collection.extend({ 7 ... //不变 8 }); 9 10 var UserItemView = Backbone.View.extend({ 11 ... //不变 12 }); 13 14 var UserInfoView = Backbone.View.extend({ 15 ... //不变 16 }); 17 18 var UserListView = Backbone.View.extend({ 19 el : $("#main"), 20 userFormTemplate : _.template($("#user-form-template").html()), //新增,绑定模板 21 events : { 22 "click #add" : "displayUserForm", //新增 23 "click #add-submit" : "submitUserForm", //新增 24 }, 25 initialize : function() { 26 this.userList = new UserList(); 27 this.userList.bind('add', this.addOne, this); //新增,每当userList中加一个User时,列表中会加一个条目 28 this.userList.bind('reset', this.addAll, this); 29 this.userList.bind('all', this.render, this); 30 this.userList.fetch({silent: true, success:function(collection, response){ 31 if(response != null){ 32 collection.reset(response.user); 33 }else{ 34 userListView.render(); 35 } 36 }}); 37 this.displayUserForm(); //新增,加载页面的时候默认显示增加User的表单 38 }, 39 render : function() { 40 this.$("#left h3").html("Total Number:"+this.userList.length); 41 }, 42 addOne : function(user) { 43 var view = new UserItemView({model : user}); 44 this.$("#user-list").append(view.render().el); 45 }, 46 addAll : function() { 47 this.userList.each(this.addOne); 48 }, 49 displayUserForm : function() { //新增,显示增加用户的表单 50 this.$("#right").html(this.userFormTemplate()); 51 }, 52 submitUserForm : function() { //新增,提交增加用户的表单 53 var user = new User({ 54 "username":$("input[name='username']").val(), 55 "password":$("input[name='password']").val(), 56 "email":$("input[name='email']").val(), 57 "phone":$("input[name='phone']").val(), 58 }); 59 60 /**** 注释一 报错:id is not defined ***** 61 this.userList.create(user); 62 $("input[name='username']").val(""), 63 $("input[name='password']").val(""), 64 $("input[name='email']").val(""), 65 $("input[name='phone']").val(""), 66 alert("Add a user!"); 67 *****************************************/ 68 69 /************** 注释二 正确 *************/ 70 this.userList.create(user,{wait:true}); 71 $("input[name='username']").val(""), 72 $("input[name='password']").val(""), 73 $("input[name='email']").val(""), 74 $("input[name='phone']").val(""), 75 alert("Add a user!"); 76 /****************************************/ 77 78 /**** 注释三 报错:A "url" property or function must be specified **** 79 user.save({success : function(){ 80 $("input[name='username']").val(""), 81 $("input[name='password']").val(""), 82 $("input[name='email']").val(""), 83 $("input[name='phone']").val(""), 84 this.userList.add(user); 85 alert("Add a user!"); 86 }}); 87 **********************************************************************/ 88 89 /************** 注释四 正确 ************* 90 var ul = this.userList; 91 user.urlRoot = "/backbone-sample/rest/user"; 92 user.save({},{success : function(){ 93 $("input[name='username']").val(""), 94 $("input[name='password']").val(""), 95 $("input[name='email']").val(""), 96 $("input[name='phone']").val(""), 97 ul.add(user); 98 alert("Add a user!"); 99 }}); 100 *****************************************/ 101 }, 102 }); 103 104 var userListView = new UserListView(); 105 var infoView; 106 });
注释一:
如果使用第一段代码,会报错 “id is not defined”,让我们来找一下原因。
官网上对于 create() 方法的解释如下:collection.create(attributes, [options])
在集合中创建一个模型。等价于用键值对象实例一个模型,然后将模型保存到服务器,保存成功后将模型增加到集合中。如果验证失败会阻止模型创建,返回 false,否则返回该模型。为了能正常运行,需要在集合中设置 model 属性。create 方法接收键值对象或者已经存在尚未保存的模型对象作为参数。
也就是先执行 user.save(),再执行 this.userList.add(user)。很显然 save() 会发送一个 AJAX 请求,因此如果它是个异步请求,在执行 add() 方法的时候很可能还未得到响应,而 add() 触发了 userList 的 add 事件,于是会执行与之绑定的 addOne() 方法,在显示页面元素的时候需要读取 id,因此会报“id is not defined”。
注释二:
为了解决注释一中的错误,我们来看一下如何将 create() 方法的 AJAX 请求变成一个同步请求。
以下是 create() 方法的源码:
1 // Create a new instance of a model in this collection. Add the model to the collection immediately, unless `wait: true` is passed, in which case we wait for the server to agree. 2 create: function(model, options) { 3 var coll = this; 4 options = options ? _.clone(options) : {}; 5 model = this._prepareModel(model, options); 6 if (!model) return false; 7 if (!options.wait) coll.add(model, options); 8 var success = options.success; 9 options.success = function(nextModel, resp, xhr) { 10 if (options.wait) coll.add(nextModel, options); 11 if (success) { 12 success(nextModel, resp); 13 } else { 14 nextModel.trigger('sync', model, resp, options); 15 } 16 }; 17 model.save(null, options); 18 return model; 19 },
很显然如果 options.wait 为 false 时会直接将 model 加到 collection 中(7行),而 options.wait 为 true 的时候会在回调函数 success 执行的时候才将 model 加到 collection 中(10行)。
因此解决方法很简单,就是在调用 create() 方法的时候加上参数 {wait:true}。
注释三:
注释一中已经分析过,执行 this.userList.create(user)
等价于先执行 user.save()
,再执行this.userList.add(user)
,因此我想到如果将 add() 方法的执行放到 save() 方法的 success 回调函数中,应该也能解决注释一中出现的错误。
但是注释三中的代码报出了 “A "url" property or function must be specified” 的错误,原因是 User 的 url 需要通过 UserList.url 或 User.urlRoot 来生成,而这里的 user 在 save() 之前并没有被加到 UserList 中去, User.urlRoot 也并没有被赋值,因此得不到 url,无法发出 POST 请求。
注释四:
经过注释三的分析,我们知道只要在 user.save() 之前对 user.urlRoot 赋值就可以了,当然也可以直接在定义 User 模型的时候给他赋值,效果一样。
注意这里先使用了一个临时变量 ul 来存放 this.userList,然后再 success 回调函数中使用的是 ul 而不是 this.userList。这样做并不是多此一举,因为在执行 success 回调函数的时候,this 所代表的对象已经不是 UserListView 了,而是 Window 对象。关于 JavaScript 里面的 this 貌似比较复杂,有兴趣的可以自己上网搜一下,我现在也只是一知半解。