这是菜鸟与老油条,屌丝与高富帅的战争 —— 译者:茶几 @chajn
原作者:脱袜子·戴蕾丝 原文:http://backbonetutorials.com/
1. 为啥你需要Backbone.js?
我们知道,仅仅使用jQuery或MooTools啥的来构建web应用或复杂的用户界面是极其困难的。
主要是因为这些JS库都把心思花费在它们擅长做的事情上了,而并没有意识到的是,即便没有任何页面结构,也是可以构建完整的应用的。
用Backbone就可以很容易的把你的应用部署到一个通过jQuery回调的嵌套堆中,然后在页面生成DOM元素实体。
我不需要解释为什么没有结构的页面构建是一个坏主意。
你当然可以每次构造应用的时候都发明一种实现方式,但这只是瞬间的美丽,你错过的是后世的精彩!
那你将永远只能是个屌ser。Balabala……
2. 啥是model?
网络上对MVC的定义是比较尿的,你甚至不知道怎样去理解和怎样去做。
但backbone.js的作者对backbone.js的model表现的定义就很明确。
Models是任何JS应用的核心部分,包括数据交互和众多相关逻辑:格式转换,校验,属性计算,访问控制等。
好的,让我们先创建一个model:
1
2
3
4
5
6
7
|
Person = Backbone.Model.extend({
initialize:
function
(){
alert(
"Welcome to this world"
);
}
});
var
person =
new
Person;
|
-
SO我们看到new一个model的实例后就会触发initialize()函数(models,collections 和 views 的工作机制都是一样滴)。虽然可以不声明initialize,但我们可能会经常用到。
2.1 设置属性
现在我们想设置一些属性,有两种方式,可以在创建model实例时进行传参,也可以在实例生成后通过model.set(obj)来进行设置。
01
02
03
04
05
06
07
08
09
10
11
|
Person = Backbone.Model.extend({
initialize:
function
(){
alert(
"Welcome to this world"
);
}
});
var
person =
new
Person({ name:
"Thomas"
, age: 67});
delete
person;
//或者用set,操作等价
var
person =
new
Person();
person.set({ name:
"Thomas"
, age: 67});
|
-
两种方式在功能上是一样的。有设置属性,就有读取属性,下一步我们来获取它们。
2.2 获取属性
很简单,用 model.get(name)方法就可以读取属性值了。
01
02
03
04
05
06
07
08
09
10
11
|
Person = Backbone.Model.extend({
initialize:
function
(){
alert(
"欢迎来到这个二蛋的世界"
);
}
});
var
person =
new
Person({ name:
"Thomas"
, age: 67, children: [
'Ryan'
]});
var
age = person.get(
"age"
);
// 67
var
name = person.get(
"name"
);
// "Thomas"
var
children = person.get(
"children"
);
// ['Ryan']
|
-
2.3 设置model默认属性
有的时候你可能会想让model有默认属性值。
没的问题,只要在进行model声明的时候设置个'defaults'就行了。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
Person = Backbone.Model.extend({
defaults: {
name:
'Fetus'
,
age: 0,
children: []
},
initialize:
function
(){
alert(
"欢迎来到这个三蛋的世界"
);
}
});
var
person =
new
Person({ name:
"Thomas"
, age: 67, children: [
'Ryan'
]});
var
age = person.get(
"age"
);
// 67
var
name = person.get(
"name"
);
// "Thomas"
var
children = person.get(
"children"
);
// ['Ryan']
|
-
2.4 操纵model的属性
Models可以添加自定义方法来进行自身的属性修改,默认这些方法都是公开的。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
Person = Backbone.Model.extend({
defaults: {
name:
'Fetus'
,
age: 0,
children: []
},
initialize:
function
(){
alert(
"欢迎来到这个四蛋的世界"
);
},
adopt:
function
( newChildsName ){
var
children_array =
this
.get(
"children"
);
children_array.push( newChildsName );
this
.set({ children: children_array });
}
});
var
person =
new
Person({ name:
"Thomas"
, age: 67, children: [
'Ryan'
]});
person.adopt(
'John Resig'
);
var
children = person.get(
"children"
);
// ['Ryan', 'John Resig']
|
-
添加了自定义方法之后,我们就可以更好的控制实例的自身属性了。
2.5 监听model的属性改变
现在说点有用的,我们可以通过model.bind(event,callback)方法来绑定change事件来监听属性改变。
下面的这个例子就是在initialize方法中绑定了一个name属性改变的事件监听。
如果person的name属性改变了,就会弹出个对话框显示新值。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
Person = Backbone.Model.extend({
defaults: {
name:
'Fetus'
,
age: 0,
children: []
},
initialize:
function
(){
alert(
"欢迎来到这个无蛋的世界"
);
this
.bind(
"change:name"
,
function
(){
var
name =
this
.get(
"name"
);
// 'Stewie Griffin'
alert(
"我的名字变为"
+ name );
});
},
replaceNameAttr:
function
( name ){
this
.set({ name: name });
}
});
var
person =
new
Person({ name:
"Thomas"
, age: 67, children: [
'Ryan'
]});
person.replaceNameAttr(
'Stewie Griffin'
);
// 改变后触手会alert()
|
-
可以单个属性监听,也可以直接监听所有的属性,如:'this.bind("change", function(){});'
2.6 提取,存储和销毁
Models实际上是collection的一部分,用于向服务器请求啥的。
本部分的教程关注的是个体models,咳咳,更多的功能请查看collection部分。
2.7 提示和技巧
2.7.1 获取当前所有的属性
1
2
3
4
5
6
|
var
person =
new
Person({ name:
"Thomas"
, age: 67, children: [
'Ryan'
]});
var
attributes = person.toJSON();
// { name: "Thomas", age: 67, children: ['Ryan']}
/* 返回对当前属性的copy */
delete
attributes;
var
attributes = person.attributes;
/* 上行返回属性的直接引用,对其的任何改变就等于实例属性本身的改变,所以还是建议你使用.set()来进行属性的设置,因为还有backbone的监听呀 */
|
-
2.7.2 在设置或存储属性的时候进行数据校验
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
Person = Backbone.Model.extend({
// 如果从validate中返回字符串了, Backbone就会抛个实例异常
validate:
function
( attributes ){
if
( attributes.age < 0 && attributes.name !=
"Dr Manhatten"
){
return
"你的存在是个错误"
;
}
},
initialize:
function
(){
alert(
"欢迎来到这个报错的世界"
);
this
.bind(
"error"
,
function
(model, error){
// 收到个错误,记录,警告,然后忘记它。╮(‵▽′)╭
alert( error );
});
}
});
var
person =
new
Person;
person.set({ name:
"Mary Poppins"
, age: -1 });
// 会触发error,输出警告
delete
person;
var
person =
new
Person;
person.set({ name:
"Dr Manhatten"
, age: -1 });
// 上帝为何怜悯我们的灵魂,只因为打开的方式不对
|
-
3. 啥是view?
Backbone的views是用来体现应用的数据模型的。他们还可以用于监听事件。
本部分教程不会说关于views绑定models和collections的事情,重点说一下视图能力和如何使用views与JS模板库,特别是Underscore.js的 _.template。
我们使用jQuery 1.5操作DOM,当然也可以使用其他的库比如MooTools或Sizzle,但Backbone.js的官方文档说支持使用jQuery。
咳咳,也就是说Backbone.View事件在jQuery以外的库中可能无法使用。
好我们开始,这回我们要实现一个搜索框。
1
2
3
4
5
6
7
8
9
|
SearchView = Backbone.View.extend({
initialize:
function
(){
alert(
"Alerts suck."
);
}
});
// The initialize function is always called when instantiating a Backbone View.
// Consider it the constructor of the class.
var
search_view =
new
SearchView;
|
-
3.1 “el”属性
“el”属性引用DOM对象。每个Backbone.js的view都会有个“el”属性。
如果没定义的话它会默认创建一个空的div元素。
来让我们设置个div#search_container到el属性上去,以保证这个Backbone.View是有效的。
01
02
03
04
05
06
07
08
09
10
11
|
<
div
id
=
"search_container"
>
div
>
<
script
type
=
"text/javascript"
>
SearchView = Backbone.View.extend({
initialize: function(){
alert("Alerts suck.");
}
});
var search_view = new SearchView({ el: $("#search_container") });
script
>
|
-
注意:既然是绑定在了这个容器元素了,那就代表着所有的事件都是以此DOM元素为基础而触发的噢。
3.2 模板加载
Backbone.js是依赖于Underscore.js的,因为它包括micro-templating方案。
更多信息请参考Underscore.js'的官方文档去。
让我们实现一个“render()”方法进行视图初始化,“render()”方法会用jQuery加载我们的模板到“el”属性的容器中。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<
div
id
=
"search_container"
>
div
>
<
script
type
=
"text/template"
id
=
"search_template"
>
<
label
>Search
label
>
<
input
type
=
"text"
id
=
"search_input"
/>
<
input
type
=
"button"
id
=
"search_button"
value
=
"Search"
/>
script
>
<
script
type
=
"text/javascript"
>
SearchView = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
// Compile the template using underscore
var template = _.template( $("#search_template").html(), {} );
// Load the compiled HTML into the Backbone "el"
this.el.html( template );
}
});
var search_view = new SearchView({ el: $("#search_container") });
script
>
|
-
提示:可以将你的模板放到CDN中的单独文件里,这样用户使用就不用每次都下载了。
3.3 事件监听
在view加一个监听,可以用Backbone.View的“events”属性。
要记得,事件监听只能绑定到“el”属性的子元素中。
让我们绑定个“click”事件到按钮上去。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<
div
id
=
"search_container"
>
div
>
<
script
type
=
"text/template"
id
=
"search_template"
>
<
label
>Search
label
>
<
input
type
=
"text"
id
=
"search_input"
/>
<
input
type
=
"button"
id
=
"search_button"
value
=
"Search"
/>
script
>
<
script
type
=
"text/javascript"
>
SearchView = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
var template = _.template( $("#search_template").html(), {} );
this.el.html( template );
},
events: {
"click input[type=button]": "doSearch"
},
doSearch: function( event ){
// Button clicked, you can access the element that was clicked with event.currentTarget
alert( "Search for " + $("#search_input").val() );
}
});
var search_view = new SearchView({ el: $("#search_container") });
script
>
|
-
3.4 提示和技巧
3.4.1 使用模板变量
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<
div
id
=
"search_container"
>
div
>
<
script
type
=
"text/template"
id
=
"search_template"
>
<
label
><%= search_label %>
label
>
<
input
type
=
"text"
id
=
"search_input"
/>
<
input
type
=
"button"
id
=
"search_button"
value
=
"Search"
/>
script
>
<
script
type
=
"text/javascript"
>
SearchView = Backbone.View.extend({
initialize: function(){
this.render();
},
render: function(){
//Pass variables in using Underscore.js Template
var variables = { search_label: "My Search" };
// Compile the template using underscore
var template = _.template( $("#search_template").html(), variables );
// Load the compiled HTML into the Backbone "el"
this.el.html( template );
},
events: {
"click input[type=button]": "doSearch"
},
doSearch: function( event ){
// Button clicked, you can access the element that was clicked with event.currentTarget
alert( "Search for " + $("#search_input").val() );
}
});
var search_view = new SearchView({ el: $("#search_container") });
script
>
|
-
4. 啥是collection?
Backbone的collections其实就是个有序的models集合。适用于以下情况;
-
Model: Student, Collection: ClassStudents
-
Model: Todo Item, Collection: Todo List
-
Model: Animals, Collection: Zoo
通常你的collection可能只用一个类型的model,但是models并不局限于某一类型的collection;
-
Model: Student, Collection: Gym Class
-
Model: Student, Collection: Art Class
-
Model: Student, Collection: English Class
关于Model/Collection,这里有一个通用的例子。
1
2
3
4
5
6
7
8
9
|
var
Song = Backbone.Model.extend({
initialize:
function
(){
console.log(
"Music is the answer"
);
}
});
var
Album = Backbone.Collection.extend({
model: Song
});
|
-
4.1 构建 collection
现在我们来点实在的,下面的例子演示了collection的使用方法。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
var
Song = Backbone.Model.extend({
defaults: {
name:
"Not specified"
,
artist:
"Not specified"
},
initialize:
function
(){
console.log(
"Music is the answer"
);
}
});
var
Album = Backbone.Collection.extend({
model: Song
});
var
song1 =
new
Song({ name:
"How Bizarre"
, artist:
"OMC"
});
var
song2 =
new
Song({ name:
"Sexual Healing"
, artist:
"Marvin Gaye"
});
var
song3 =
new
Song({ name:
"Talk It Over In Bed"
, artist:
"OMC"
});
var
myAlbum =
new
Album([ song1, song2, song3]);
console.log( myAlbum.models );
// [song1, song2, song3]
|
-
5. 啥是router?
1
2
3
4
5
|
//@注1:本章的所有实例都是在http://example.com顶级域名下实现的。
//如果是http://example.com/tset/test.html的形式,则路径标记#前不能加/。
//如:http://example.com/#/user/helphttp://example.com/test/test.html#/user/help
//@注2:Backbone.js 0.9.2 的取路径后的值是posts/12,匹配^/posts/([^/]+)$不能(/posts/:id转换后的正则表达式)。所以原参数要改为posts/:id,测试才能通过。
//以下有错误的地方标红不改,以此类推。
|
-
Backbone的routers其实就相当于你的应用URL的hash(#)。
如果你读过“啥是view?”,那你应该知道,他们并不符合传统的MVC语义,本章将会详细解释这一点。
尽管Backbone的“router”对任何程序和功能都很有用,但它需要URL的routing/history功能。
Routers的定义包括路径和映射函数,下面的例子就让我们定义一个路径。
还请注意,routes只解析url的“#”之后的标记。
你的应用的所有链接都应该是像“#/action”或“#action”这样。
(附加个斜杠看起来会更好一些,比如:http://example.com/#/user/help)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
var
AppRouter = Backbone.Router.extend({
routes: {
"*actions"
:
"defaultRoute"
// matcheshttp://example.com/#anything-here
},
defaultRoute:
function
( actions ){
// The variable passed in matches the variable in the route definition "actions"
alert( actions );
}
});
// Initiate the router
var
app_router =
new
AppRouter;
// Start Backbone history a neccesary step for bookmarkable URL's
Backbone.history.start();
_URL进行变化_
[Activate route](
#action)
[Activate another route](
#/route/action)
|
-
请注意:Backbone 0.5 (released 1. July 2011)之前的版本,Router被称作Controller。为了避免字义混淆,Backbone的开发人员把名字改成Router了。
因此,如果你发现自己使用了Backbone的一个旧版本,那你应该写Backbone.Controller.extend({ ** });
5.1 动态Routing
原始框架允许你的routes定义包含动静混合态路径参数。
例如,你可能想进行检索一个post的id,你的URL看起来可能会是“http://example.com/#/posts/12”。
一旦这个路径被激活,你就可以取到id的值并做相应的处理了。下面是这个例子的实现。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var
AppRouter = Backbone.Router.extend({
routes: {
"/posts/:id"
:
"getPost"
,
//@注2
"*actions"
:
"defaultRoute"
// Backbone will try match the route above first
},
getPost:
function
( id ) {
// Note the variable in the route definition being passed in here
alert(
"Get post number "
+ id );
},
defaultRoute:
function
( actions ){
alert( actions );
}
});
// Instantiate the router
var
app_router =
new
AppRouter;
// Start Backbone history a neccesary step for bookmarkable URL's
Backbone.history.start();
_URL进行改变_
[Post 120](
#/posts/120)
[Post 130](
#/posts/130)
|
-
5.2 动态路径对比——":params"和"*splats"
Backbone使用两种参数模式实现路径,第一种是":params"匹配URL路径内任何斜杠之间的字符串,而还有一种是"splats"匹配任意数量的URL路径。
注意"splat"的本质意思,他总是最后一个变量,因为它匹配的是URL路径之后的全部内容。
在route中定义的任何"*splats"或":params"都会以参数的形式穿入关联函数中。
比如一个route定义了"/:route/:action",那就会传入两个参数(“route”和“action”) 到回调函数中。
下面的示例使用":params"和"*splats"。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
routes: {
"/posts/:id"
:
"getPost"
,
//@注2,下同
// Example
"/download/*path"
:
"downloadFile"
,
// Download
"/:route/:action"
:
"loadView"
,
},
getPost:
function
( id ){
alert(id);
// 121
},
downloadFile:
function
( path ){
alert(path);
// user/images/hey.gif
},
loadView:
function
( route, action ){
alert(route +
"_"
+ action);
// dashboard_graph
}
|
-