More Related Content Similar to 【前端Mvc】之豆瓣说实践 (20) More from taobao.com (20) 【前端Mvc】之豆瓣说实践28. Downloads & Dependencies
Development Version (0.5.1) 41kb, Full Source with Comments
Production Version (0.5.1) 4.6kb, Packed and Gzipped
Backbone's only hard dependency is Underscore.js.
For RESTful persistence, history support via Backbone.ControllerRouter and
DOM manipulation with Backbone.View, include json2.js, and either jQuery
( > 1.4.2) or Zepto.
33. var Comment = Backbone.Model.extend();
var comment = new Comment({
id: 83562107
text: " “ ” "
created_at: "2011-07-03 09:04:34"
user: {
uid: "keso"
id: "1000185"
screen_name: "keso"
}
});
35. comment.get('text'); // “ ”
comment.set(
{'text':'<script>alert('xss')</script>'},
{silent:true}
);
comment.escape('text');
– extend
//
- constructor / initialize
<script>alert('xss')</script>
– get
;
– set
– escape
comment.has('city'); // false
– has
– unset
– clear comment.unset('text'); // trigger 'change' event
– id
– cid
– attributes var Comment = new Backbone.Model.extend({
– defaults // hash or function()
- toJSON defaults: {
'source': 'douban.com'
},
initialize: function() { ... }
});
var comment = new Comment();
comment.get('source'); // 'douban.com'
36. var Comment = new Backbone.Model.extend({
urlRoot: '/comment'
initialize: function(attrs) { ... }
});
var comment = new Comment({id:123456});
– fetch comment.url(); // '/comment/123456'
– save
– destroy
– validate
– url
– urlRoot var Comment = new Backbone.Model.extend({
– parse urlRoot: '/comment'
– clone initialize: function(attrs) { ... },
– isNew validate: function(attrs) {
– change if ( attrs.text.length < 3 ) {
– hasChanged return ' 3 '
– changedAttributes }
– previous }
– previousAttributes });
var comment = new Comment();
comment.set({text:'ok'},{
error: function(model,error) {
alert(error);
}
});
40. var Comments = new Backbone.Collection.extend({
model: Comment,
initialize: function(models,options) {
}
});
41. [
{
text: " “ ” "
created_at: "2011-07-03 09:04:34"
source: "douban.com"
user: {
city: " "
statuses_count: 172
uid: "keso"
following_count: 1688
created_at: "2005-04-07 18:01:26"
followers_count: 6178
small_avatar: "http://img3.douban.com/icon/u1000185-2.jpg"
id: "1000185"
screen_name: "keso"
...
}
id: 83562107
},
{...}
{...}
]
43. App.Collections.Comments = Backbone.Collection.extend({
– model
– constructor / initialize model: Comment,
– models
initialize: function(models,options) {
– toJSON
– Underscore Methods (25) this.url = '/api/statuses/' + options.id + '/comments'
– get + (options.count ? '?count=' + options.count : '')
– getByCid + (options.page ? '&page=' + options.page : '');
this.page = 1;
– at
– length },
– comparator
comparator: function(model) {
– sort
return -model.get('id');
– pluck }
– url
– parse
});
45. User = new Backbone.Model.extend({
initialize: function(attrs) {
this.url = '/users/' + attrs.id;
}
});
var user = new User({id: 2770683});
– add user.fetch({
– remove success:function(model,resp) {
– fetch document.title = model.get('screen_name') + '/ ';
– reset App.Pages.Profile = new App.Views.Profile({
model: model
– create });
oBody.append(App.Pages.Profile.render().el);
},
error: function(model,resp) {
switch (resp.status) {
case 404:
App.Pages.Error = new App.Views.Error({
msg:' '
});
oBody.append(App.Pages.Error.render().el);
break;
}
}
});
48. Backbone.View
• A logical UI component, not just the
template
• They are more like Rail’s Controller
• Responsible for instantiating
Collections and binding events that
update the UI
50. App.Views.Comment = Backbone.View.extend({
className: 'comment-item',
template: $('#comment-item-template').html(),
events: {
"mouseover" : "showActions",
"mouseout" : "hideActions"
},
initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
},
render: function() {
$(this.el).html(Mustache.to_html(this.template, this.model.toJSON()));
$(this.el).attr({
'data-item-id': this.model.id,
'data-comment-id': this.model.id
});
return this;
},
showActions: function(e) {
this.$('.icon-delete').show();
},
var view = new App.Views.Comment({
hideActions: function(e) { model: model
this.$('.icon-delete').hide();
});
}
}); $('body').append(view.render().el);
52. App.Router.Shuo = Backbone.Router.extend({
routes: {
"" : "home",
":uid" : "profile",
":uid/following" : "following",
":uid/status/:id" : "permalink",
"search/:query" : "search"
},
initialize: function() {
Backbone.history.start({pushState: true, root:'/'});
},
home: function() {
App.Pages.Home = new App.Views.Home();
oBody.append(App.Pages.Home.render().el);
this.navigate('');
},
profile: function(uid) {
App.Pages.Profile[uid] = new App.Views.Profile({uid: uid});
oBody.append(App.Pages.Profile[uid].render().el);
this.navigate(uid,true);
},
following: function(uid) { ...
permalink: function(uid,id) { ...
search: function(query) { ...
});
53. router.route(route, name, callback)
initialize: function(options) {
// Matches #page/10, passing "10"
this.route("page/:number", "page", function(number){ ... });
// Matches /117-a/b/c/open, passing "117-a/b/c"
this.route(/^(.*?)/open$/, "open", function(id){ ... });
}
54. Backbone.History
• A global router (per frame) to handle
hashchange events or pushState
• Match the appropriate route, and trigger
callbacks
55. Backbone.history.start([options])
App.Router.Shuo = Backbone.Router.extend({
routes: {
"" : "home",
":uid" : "profile",
":uid/following" : "following",
":uid/status/:id" : "permalink",
"search/:query" : "search"
},
initialize: function() {
Backbone.history.start({pushState: true, root:'/'});
},
...
56. Backbone.Events
◦ "add" (model, collection) — when a model is added to a collection.
◦ "remove" (model, collection) — when a model is removed from a collection.
◦ "reset" (collection) — when the collection's entire contents have been replaced.
◦ "change" (model, collection) — when a model's attributes have changed.
◦ "change:[attribute]" (model, collection) — when a specific attribute has been updated.
◦ "destroy" (model, collection) — when a model is destroyed.
◦ "error" (model, collection) — when a model's validation fails, or a save call fails on the server.
◦ "route:[name]" (router) — when one of a router's routes has matched.
◦ "all" — this special event fires for any triggered event, passing the event name as the first
argument.
57. object.bind(event, callback)
App.Views.Comment = Backbone.View.extend({
className: 'comment-item',
template: $('#comment-item-template').html(),
initialize: function() {
_.bindAll(this, 'render');
this.model.bind('change', this.render);
},
render: function() {
$(this.el).html(Mustache.to_html(this.template, this.model.toJSON()));
$(this.el).attr({
'data-item-id': this.model.id,
'data-comment-id': this.model.id
});
return this;
}
});
58. Backbone.Sync
Backbone.sync is the function that Backbone calls every time it
attempts to read or save a model to the server. By default, it uses
(jQuery/Zepto).ajax to make a RESTful JSON request. You can
override it in order to use a different persistence strategy, such as
WebSockets, XML transport, or Local Storage.
61. Models Collections
Interactive Data Domain-
Ordered Sets of Models
specific methods
62. Models Collections
Interactive Data Domain-
Ordered Sets of Models
specific methods
Views
Render HTML/CSS With
Javascript templating
63. Models Collections
Interactive Data Domain-
Ordered Sets of Models
specific methods
Views Router
Render HTML/CSS With Methods For Routing URL
Javascript templating Fragments
64. Models Collections
Interactive Data Domain-
Ordered Sets of Models
specific methods
Views Router
Render HTML/CSS With Methods For Routing URL
Javascript templating Fragments
65. Models Collections
Interactive Data Domain-
Ordered Sets of Models
specific methods
Views Router
Render HTML/CSS With Methods For Routing URL
Javascript templating Fragments
67. ../libs/
jquery.js
backbone.js
underscore.js
json2.js
mustache.js
cacheprovider.js
../applications.js
../models/
../collections/
../views/
../controllers/
69. var App = {
Views: {},
Router: {},
Collections: {},
Cache: new CacheProvider(),
initialize: function(){
new App.Controllers.Shuo();
Backbone.history.start({pushState: true});
}
};
public/js/application.js
70. App.Routers.Shuo = Backbone.Router.extend({
routes: {
"" : "home",
"comments" : "comments",
"mentions" : "mentions",
":uid" : "profile",
":uid/following" : "following",
":uid/followers" : "followers",
":uid/status/:id" : "permalink",
"search/users/:query" : "user_search",
"search/:query" : "search"
},
initialize: function() { ... },
home: function() { ... },
comments: function() { ... },
mentions: function() { ... },
profile: function(uid) { ... },
following: function(uid) { ... },
followers: function(uid) { ... },
permalink: function(uid,id) { ... },
user_search: function(query) { ... },
search: function(query) { ... }
});
public/js/controllers/shuo.js
85. HomeHeader
Navigation
Following
Followers
Suggestion
Stream
ADs
Footer
91. new App.Views.StreamItem();
model: Status
Status model.fetch()
this.render()
Resharers new App.Views.Resharers()...
new App.Views.Comments()
this.collection = new Comments({
id: status.id
Comments });
this.collection.fetch()
this.collection.add()
this.collection.remove()
92. ...
<div id="page-outer">
</div>
...
<script id="status-item-template" type="text/template">...</script>
<script id="comment-item-template" type="text/template">...</script>
...
template/app.html
93. App.Views.Permalink = Backbone.View.extend({
el: oDoc,
item_template: $('#permalink-item-template').html(),
initialize: function(options) {
_.bindAll(this, 'render', 'loadStatus', 'loadResharers', 'loadLikers', 'loadComments');
var self = this;
$.ajax('/api/statuses/' + options.id + '?pack=1&comment_count=50&reshare_count=14&like_count=14')
.success(function(resp) {
self.status = new Status(resp.status, {id: options.id});
self.comments = new App.Collections.Comments(resp.comments, {id: options.id});
self.resharers = new App.Collections.Resharers(resp.reshare_users, {id: options.id});
self.loadStatus();
})
.error(function(resp){
// 404
}
});
},
render: function() {
return this;
},
loadStatus: function() {
var view = new App.Views.StreamItem({
model: this.status
});
this.loadComments();
this.loadResharers();
oPageContainer.prepend(view.render().el);
},
loadResharers: function() { ... },
loadComments: function() { ... }
});
public/js/views/permalink.js
98. {
entities: {
user_mentions: [],
urls: []
}
text: " “ ” "
created_at: "2011-07-03 09:04:34"
source: "douban.com"
user: {
city: " "
icon_avatar: "http://img3.douban.com/icon/ui1000185-2.jpg"
statuses_count: 172
uid: "keso"
following_count: 1688
url: ""
created_at: "2005-04-07 18:01:26"
description: "keso http://
blog.donews.com/keso"
followers_count: 6178
location: " "
small_avatar: "http://img3.douban.com/icon/u1000185-2.jpg"
following: false
verified: false
large_avatar: "http://img3.douban.com/icon/ul1000185-2.jpg"
id: "1000185"
screen_name: "keso"
}
id: 83562107
}
99. <script id="comment-item-template" type="text/template">
<div class="comment-item-content" data-user-id="{{#user}}{{uid}}{{/user}}" data-item-id="{{id}}" data-
comment-id="{{id}}">
{{#user}}
<div class="icon">
<a href="/#{{uid}}"><img src="{{small_avatar}}" alt="{{screen_name}}"/></a>
</div>
{{/user}}
<div class="content">
<p>
{{#user}}
<a href="/#!/{{uid}}" class="to" title="{{uid}}">{{screen_name}}</a>
{{/user}}
<span class="comment-time">{{timestamp}}</span>
</p>
<p>
<span class="comment-text">{{{text}}}</span>
<a href="" class="icon-comment" title=" "><img src="http://img3.douban.com/anduin/pics/
blank.gif"/></a>
</p>
</div>
</div>
</script>