前言
阅读本文首先需要对JavaScript中的backbone框架、jQuery、html有所了解,否则阅读可能会很困难
本文主要解释一个用backbone框架开发的一个打卡器的源码(只解释js与html),内容节选自《基于MVC的JavaScript Web 富应用开发》一书的第十二章
源代码url:点击打开链接 应用位于assets/ch12/todos中
正文
html(网页结构)
首先看index.html内容
<script src="lib/json2.js"></script> <script src="lib/jquery.js"></script> <script src="lib/jquery.tmpl.js"></script> <script src="lib/underscore.js"></script> <script src="lib/backbone.js"></script> <script src="lib/backbone.localstorage.js"></script> <script src="todos.js"></script>
head中引入的js脚本 这不会细谈,但最好还是把这些script标签放在body的最后一行以优化性能
以下是body 内容:
<div id="todoapp"> <div class="title"> <h1>Todos</h1> </div> <div class="content"> <div id="create-todo"> <input id="new-todo" placeholder="What needs to be done?" type="text" /> </div> <div id="todos"> <ul id="todo-list"></ul> </div> <div id="todo-stats"></div> </div> </div>结合应用面板来看:
整个框架的div id为todoapp todoapp分为四部分组成:
1.标题(todos)
2.内容(下面的input)
3.要加入事件的ul(以上那个li就是ul的一部分)
4.完成的事件状态
ok以上是解析网页的结构组成下面开始进入重头戏js
todos.js(实现功能)
js分为具体的四部分(实际上是五部分):
1.一个model(todo)用以存放事件的值(content)以及完成状态(done)和一个用来放model的list(todolist)负责管理数据的状态这两个我们可以看成是一个部分我们这里把它叫做数据
2.一个管理单个model的view(todoview 用来实现单个li的删除、编辑、完成)
3.一个view(appview)负责显示界面并且给上面的两部分建立联系,还有一个功能是给html中的new-todo建立提交功能。
4.template 严格来说这不能算一个大部分但方便理解就把它放在这它的功能是为li提供一个结构模板
数据层(model):
window.Todo = Backbone.Model.extend({ defaults: { done: false }, toggle: function() { this.save({done: !this.get("done")}); } });
以上代码代表的是单个记录有两个属性一个是li的完成状态,还有里面放的事件内容(以上没有提及但下面代码中创建数据时都会给其加入content属性)还有个函数用来改变li的完成状态(这暂时用不到)。
存放todo的todolist:
window.TodoList = Backbone.Collection.extend({ model: Todo, // Save all of the todo items under the `"todos"` namespace. localStorage: new Store("todos"), // Filter down the list of all todo items that are finished. done: function() { return this.filter(function(todo){ return todo.get('done'); }); }, remaining: function() { return this.without.apply(this, this.done()); } });
可以把todolist看成是用来管理li的ul ,todo看成是li
model属性用来建立todolist与todo的联系
localstorage属性用来存放todo并保存
done函数通过gei筛选出表中已经完成了的事件
remaining函数的功能是与done相反筛选出还没有完成的任务
window.Todos = new TodoList;
建立一个todolist的实例
template:
<script type="text/template" id="item-template"> <div class="todo {{if done}}done{{/if}}"> <div class="display" title="Double click to edit..."> <input class="check" type="checkbox" {{if done}}checked="checked"{{/if}} /> <div class="todo-content">${content}</div> <span class="todo-destroy"></span> </div> <div class="edit"> <input class="todo-input" type="text" value="${content}" /> </div> </div> </script>
template主要是作为一个模板为某个特定的model创建一系列的dom元素 它主要是被todoview中的render函数所使用(如果看不懂请去查阅有关jquery的tmpl函数)具体样式:
注意:以上template中的class为edit的div并没有显示是因为在css中对其进行了hidden更改,要想显示需要双击todo-content
另一个模板:
<script type="text/template" id="stats-template"> {{if total}} <span class="todo-count"> <span class="number">${ remaining }</span> <span class="word">${ remaining == 1 ? 'item' : 'items' }</span> left. </span> {{/if}} {{if done}} <span class="todo-clear"> <a href="#"> Clear <span class="number-done">${ done }</span> completed <span class="word-done">${ done == 1 ? 'item' : 'items' }</span> </a> </span> {{/if}} </script>
以上模板就是针对todo-stats而动态变化的,具体功能为显示当前还有多少未完成的事件和清楚已经完成的事件
view(todoview):
window.TodoView = Backbone.View.extend({ //... is a list tag. tagName: "li", // Cache the template function for a single item. template: $("#item-template").template(), events: { "change .check" : "toggleDone", "dblclick .todo-content" : "edit", "click .todo-destroy" : "destroy", "keypress .todo-input" : "updateOnEnter", "blur .todo-input" : "close" }, initialize: function() { _.bindAll(this, 'render', 'close', 'remove', 'edit'); this.model.bind('change', this.render); this.model.bind('destroy', this.remove); }, render: function() { var element = jQuery.tmpl(this.template, this.model.toJSON()); $(this.el).html(element); this.input = this.$(".todo-input"); return this; }, toggleDone: function() { this.model.toggle(); }, // Switch this view into `"editing"` mode, displaying the input field. edit: function() { $(this.el).addClass("editing"); this.input.focus(); }, // Close the `"editing"` mode, saving changes to the todo. close: function(e) { this.model.save({content: this.input.val()}); $(this.el).removeClass("editing"); }, // If you hit `enter`, we're through editing the item. updateOnEnter: function(e) { if (e.keyCode == 13) e.target.blur(); }, remove: function() { $(this.el).remove(); }, destroy: function() { this.model.destroy(); } });
todoview是整个js中最核心的部分,实现了这个应用的大部分功能此视图绑定的是一个li元素
(1)events(绑定dom元素事件(以上绑定的dom元素都是template中的元素)):
1.为class为check的元素添加事件当此元素(来自与template中的元素)的状态发生变化调用函数toggledone(调用上文中暂时没有用到的model的toggle函数) 改变model的done状态
2.为template中class为todo-content添加回调函数当双击时触发,功能主要是切换界面到编辑状态(上文有提到)
events中的绑定事件这不再细说,过段时间有空再补充完整
(2)initialize(初始化)函数:
_.bindall()函数功能正确调整上下文避免发生歧义
this.model.bind('change', this.render);
为this.model绑定事件触发条件和回调函数都有没什么问题??
等等好像发现了什么这是在todolist里面啊哪有model??
这就是我上文中说过的appview的作用 在addone中它动态的为todo-view添加了一个model的属性
这放到下文再说我们着重来看一下render函数
render函数主要是用来创建一个新的继承模板的元素(或者更新)并放在li里面(如果不明白下文还会提及)
another view(appview):
window.AppView = Backbone.View.extend({ // Instead of generating a new element, bind to the existing skeleton of // the App already present in the HTML. el: $("#todoapp"), statsTemplate: $("#stats-template").template(), events: { "keypress #new-todo": "createOnEnter", "click .todo-clear a": "clearCompleted" }, // At initialization we bind to the relevant events on the `Todos` // collection, when items are added or changed. Kick things off by // loading any preexisting todos that might be saved in *localStorage*. initialize: function() { _.bindAll(this, 'addOne', 'addAll', 'render'); this.input = this.$("#new-todo"); Todos.bind('add', this.addOne); Todos.bind('refresh', this.addAll); Todos.bind('all', this.render); Todos.fetch(); }, // Re-rendering the App just means refreshing the statistics -- the rest // of the app doesn't change. render: function() { var done = Todos.done().length; var element = jQuery.tmpl(this.statsTemplate, { total: Todos.length, done: Todos.done().length, remaining: Todos.remaining().length }); this.$('#todo-stats').html(element); }, // Add a single todo item to the list by creating a view for it, and // appending its element to the `<ul>`. addOne: function(todo) { var view = new TodoView({model: todo}); this.$("#todo-list").append(view.render().el); }, // Add all items in the **Todos** collection at once. addAll: function() { Todos.each(this.addOne); }, // If you hit return in the main input field, create new **Todo** model createOnEnter: function(e) { if (e.keyCode != 13) return; var value = this.input.val(); if ( !value ) return; Todos.create({content: value});//原本没有content属性 加上后触发了change事件 this.input.val(''); }, clearCompleted: function() { _.each(Todos.done(), function(todo){ todo.destroy(); }); return false; } });
el为其与html中的最外层div元素绑定
statsTemplate属性取状态模板
events 为html中id为new-todo的元素绑定事件:
当里面的value发生变化时调用事件createonenter:
createOnEnter: function(e) { if (e.keyCode != 13) return; var value = this.input.val(); if ( !value ) return; Todos.create({content: value});//原本没有content属性 加上后触发了change事件 this.input.val(''); }
当按下回车后todos中添加一条记录 todos调用了create函数为上文中的model动态添加content属性(下文还会提到这个函数)
events中另一个绑定是为template的a元素添加点击事件
clearCompleted: function() { _.each(Todos.done(), function(todo){ todo.destroy(); }); return false; }
遍历所有的数据把状态已经完成的数据全部删掉
render函数功能与todo-view的render函数功能类似 套todo-staus模板
以上讲完的appview函数也好,属性也罢,都还没有实现上文中讲到的appview应该实现的功能真正实现功能的是initialize函数中的addone函数
addOne: function(todo) { var view = new TodoView({model: todo}); this.$("#todo-list").append(view.render().el); }
第一句实现功能:1.建立todo-view与model的联系动态给其传入属性解决上文中的疑惑
2.上文中提到过的函数:
createOnEnter: function(e) { if (e.keyCode != 13) return; var value = this.input.val(); if ( !value ) return; Todos.create({content: value});//原本没有content属性 加上后触发了change事件 this.input.val(''); }
todos.create({content:value})这一语句上文解释为:为todolist创建一个新的有content属性的model并插入
等等插入不就是add吗 没错上面的create实际上用到了add函数而appview中为todos的add绑定了addone函数所以当执行todos.create({content:value})时也执行了addone函数实现第二个功能:给new-todo添加提交功能
小结:以上就是本文的全部内容还有一些不是很难的函数没有解释,有时间会继续补充,初学基于mvc的框架backbone感觉很奇妙, model view control 各管各的使程序的可扩展性大大增加,由于六级的准备,前端的学习先告一段落,持续更新