todo案例可以到这个地址下载 https://github.com/jashkenas/backbone
添加数据后
todo案例不涉及Router,仅有Model、Collection、View的应用,但这个案例对于这三个对象的使用真是已经到了炉火纯青的地步,我看了两天(囧),也只是看懂了表面一层,至于更深一层的思想就太模糊、若有若无了。还需努力啊!
todo案例的核心在todos.js文件里,我分析的也是todos.js。
首先是数据模型Model
1 var Todo = Backbone.Model.extend({ 2 defaults : function() { //看好多人都用function return这种方式,但不知道好处在哪 3 return { 4 title : "empty todo ...", 5 order : Todos.nextOrder(), //要有Todos这个全局变量才行 6 done : false 7 } 8 }, 9 //切换状态 10 toggle : function(){ 11 this.save({done : !this.get("done")}); //注意:我在这里犯过2次错误,把save写成了set,导致数据保存不上。 12 }, //更新数据要使用 save,不是其他。RESTful风格就是这样。 13 });
这个Model和java中的bean何其相似啊!属性有:title、order、done,方法有 toggle,这个方法也仅仅是更新其中的一个属性而已。
然后是集合Collection
1 var TodoList = Backbone.Collection.extend({ 2 //model 3 model : Todo, //如java中List集合中的泛型,表面这是一个Todo类型的集合(即集合存储Todo对象) 4 5 //本地存储,操作的数据都存储在localStorage里面,没有后端 6 localStorage : new Backbone.LocalStorage("todos-backbone"), 7 //The app uses a LocalStorage adapter to transparently save all of your todos within your browser, instead of sending them to a server 8 9 //获取状态为 done:true 的Todo对象数组 10 done : function(){ 11 return this.where({done : true}); 12 }, 13 //获取状态为 done:false 的Todo对象数组 14 remaining : function(){ 15 return this.where({done : false}); 16 }, 17 18 //生成下一个Todo对象的编号 19 nextOrder : function(){ 20 if(!this.length){ //this.length 获取集合中model的数量 21 return 1; 22 } 23 return this.last().get("order") + 1; 24 }, 25 26 comparator : "order" , //按照 order 进行排序 27 28 });
总体来说,这个TodoList集合用来存储Todo对象到localStorage里并为他们编号,同时获取不同状态下的Todo对象。
小结:Model 和 Collection 都是用来存储数据的,里面的操作也都是对数据的操作,不与页面逻辑混合。
接下来是TodoView
1 var TodoView = Backbone.View.extend({ 2 3 tagName : "li", 4 5 template : _.template($("#item-template").html()), //页面模板,_.template是underScore的函数 6 7 events : { //DOM事件 8 "click .toggle" : "toggleDone", 9 "dblclick .view" : "edit", 10 "click a.destroy" : "clear", 11 "keypress .edit" : "updateOnEnter", 12 "blur .edit" : "close", 13 }, 14 15 initialize : function(){ //构造函数 16 //监听Collection的变化并作出反应 17 this.listenTo(this.model, "change", this.render); //model属性改变时会触发该事件 18 this.listenTo(this.model, "destroy", this.remove); //销毁model时,触发该事件。this.remove()移除一个视图(即销毁model的同时移除view) 19 }, 20 21 //渲染 22 render : function(){ 23 // el是tagName 24 this.$el.html(this.template(this.model.toJSON())); //将model的属性作为参数传入模板生成html代码,同时填充到el元素里 25 26 this.$el.toggleClass("done", this.model.get("done"));//根据model的属性值来判断元素的类 27 28 this.input = this.$(".edit"); //注意这个写法 29 30 return this; //为了链式调用 31 }, 32 33 /*** DOM事件处理函数 ***/ 34 35 //切换状态 36 toggleDone : function(){ 37 this.model.toggle(); 38 }, 39 40 edit : function(){ 41 this.$el.addClass("editing"); 42 this.input.focus(); 43 }, 44 45 close : function(){ 46 var value = this.input.val(); 47 if(!value){ 48 this.clear(); 49 }else{ 50 this.model.save({title : value}); 51 this.$el.removeClass("editing"); 52 } 53 54 }, 55 56 updateOnEnter : function(){ 57 if(e.keyCode === 13){ 58 this.close(); 59 } 60 }, 61 62 clear : function(){ 63 this.model.destroy(); //销毁model,触发destroy事件 64 }, 65 66 });
每个View视图都有一个DOM元素,这个DOM元素就是el,这个view里所有关于页面的操作都必须在el下(注意,在view操作el的父元素或同级元素都将无效),如果没有指定el,那么el将从tagName、className、id创建,如果也没有,那么el就是一个空的div。
TodoView视图表示的就是ul中的一个li,如
写view的思路:
1、 el,这是一个view的根元素
2、template,这是view的模板,该模板将在render插入到el元素中
3、render,渲染页面,负责将model的数据显示在html模板中
4、DOM事件,想象该view哪些地方需要和用户进行交互,然后在events中绑定事件
5、完成DOM事件处理函数
6、观察哪些操作造成了哪些数据的变化,为这些数据绑定事件,当数据变化时更新数据并再次渲染页面。
比如上面的TodoView,el是li,模板渲染需要Todo数据,在这个li里,要有
- 单击checkbox,更新model数据
- 单击li后面的X号,删除model
- 双击li,变成编辑状态
- 回车或者焦点离开更新model数据
我自己的感觉,前5个步骤都比较简单,就是第6步用起来有些生涩。不过多几次就好了!
最后是AppView
1 var AppView = Backbone.View.extend({ 2 3 el : $("#todoapp"), 4 5 statsTemplate : _.template($("#stats-template").html()), 6 7 events : { 8 "keypress #new-todo" : "createOnEnter", 9 "click #clear-completed": "clearCompleted", 10 "click #toggle-all" : "toggleAllComplete", 11 }, 12 13 initialize : function(){ 14 this.input = this.$("#new-todo"); 15 this.allCheckbox = this.$("#toggle-all")[0]; 16 17 this.listenTo(Todos, "add", this.addOne); //当有Todo对象添加到Todos集合触发该事件(添加一个model就添加一个view) 18 // this.listenTo(Todos, "reset", this.addAll); // 19 this.listenTo(Todos, "all", this.render); //任何事件的发生都会调用该回调函数this.render 20 21 this.footer = this.$("footer"); 22 this.main = this.$("#main"); 23 24 Todos.fetch(); //拉取所有的Model对象并保存到Todos集合中,会触发Todos的add事件 25 }, 26 27 render : function(){ 28 var done = Todos.done().length; 29 var remaining = Todos.remaining().length; 30 31 if(Todos.length){ 32 this.main.show(); 33 this.footer.show(); 34 this.footer.html(this.statsTemplate({done : done, remaining : remaining})); 35 }else{ 36 this.main.hide(); 37 this.footer.hide(); 38 } 39 this.allCheckbox.checked = !remaining; 40 }, 41 42 addOne : function(todo){ 43 var view = new TodoView({model : todo}); 44 view.render(); 45 this.$("#todo-list").append(view.el); 46 }, 47 /** 48 addAll : function(){ 49 //backbone代理了Underscore的一些方法 50 Todos.each(this.addOne, this); 51 }, 52 **/ 53 createOnEnter : function(e){ 54 if(e.keyCode != 13){ 55 return; 56 } 57 if(!this.input.val()){ 58 return; 59 } 60 Todos.create({title : this.input.val()}); 61 this.input.val(""); 62 }, 63 64 clearCompleted : function(){ 65 //_.invoke(Todos.done(), "destroy"); //遍历Todos.done(),每个元素都 todo.destroy(); 66 67 _.each(Todos.done(),function(todo){ 68 todo.destroy(); 69 }); 70 return false; 71 }, 72 73 toggleAllComplete : function(){ 74 var done = this.allCheckbox.checked; 75 Todos.each(function(todo){ 76 todo.save({ 77 'done' : done, 78 }); 79 }); 80 }, 81 82 });
这个view表示整个页面,比较复杂(自己觉得)。
将它从上到下拆分开来,就变的简单了。
最上面是头,一个标题和一个输入框,输入框有一个DOM事件keypress。
1 <header> 2 <h1>Todos</h1> 3 <input id="new-todo" type="text" placeholder="What needs to be done?"> 4</header>
中间是身体,一个复选框一个label提示信息一个ul列表,复选框有一个DOM事件,而ul列表中li子项则是作为TodoView存在的。
1 <section id="main"> 2 <input id="toggle-all" type="checkbox"> 3 <label for="toggle-all">Mark all as complete</label> 4 <ul id="todo-list"> 5 </ul> 6 </section>
最下是脚,一个div显示信息一个超链接,超链接有一个DOM事件,清除完成的任务。
1 <footer> 2 <a id="clear-completed">Clear completed</a> 3 <div id="todo-count"></div> 4 </footer>
这样在去看AppView就简单多了。
关键还是内在的思想,可以偶还没看明白! >_<
---------------------------------
AppView的数据核心就是 TodoList,TodoList对象的变化要同步到AppView,所有要监听TodoList对象。