Java 对于 JavaScript 就像汽车对于地毯一样。— 克里斯·海尔曼T3】
在这一章中,我们将探索为一个 Backbone.js 应用利用 Parse.com 的实际方面。这一章将说明 Backbone.js 在修改后的留言板应用上与 Parse.com 及其 JavaScript SDK 一起使用。
如果您已经编写了一些复杂的客户端应用,您可能会发现维护 JavaScript 回调和 UI 事件的复杂代码很有挑战性。js 提供了一种轻量级但功能强大的方法来将您的逻辑组织成模型-视图-控制器(MVC)类型的结构。它还有一些不错的特性,比如 URL 路由、REST API 支持、事件监听器和触发器。有关从头构建 Backbone.js 应用的更多信息和分步示例,请参考“Backbone.js 简介”一章
引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqsQC 。
很容易看出,如果我们不断添加越来越多的按钮,如“删除”、“更新”和其他功能,我们的异步回调系统将变得更加复杂。我们必须知道何时根据数据是否有变化来更新视图(即消息列表)。js 模型-视图-控制器(MVC)框架可用于使复杂的应用更易于管理和维护。
如果您对前面的例子感到满意,让我们使用 Backbone.js 框架来构建它。在这里,我们将一步一步地使用 Backbone.js 和 Parse.com JavaScript SDK 创建一个留言板应用。如果你对它足够熟悉,你可以在 github.com/azat-co/super-simple-backbone-starter-kit 下载这个超级简单的主干初学者工具包。与 Backbone.js 的集成将允许通过将用户动作绑定到集合的异步更新来直接实现用户动作。
该应用可从 https://github.com/azat-co/fullstack-javascript/tree/master/06-board-backbone-parse-sdk 获得,但同样鼓励您从头开始,并尝试使用示例编写自己的代码,仅供参考。
下图显示了 Parse.com、JavaScript SDK 和 Backbone.js 版本的留言板结构:
/06-board-backbone-parse-sdk
-index.html
-home.html
-footer.html
-header.html
-app.js
/css
-bootstrap.css
-bootstrap.min.css
/js
-backbone.js
-jquery.js
-underscore.js
/libs
-require.min.js
-text.js
创建一个文件夹;在文件夹中创建一个具有以下内容框架的index.html文件:
<!DOCTYPE html>
<html``lang="en"T2】
<head>
...
</head>
<body>
...
</body>
</html>
从 Google API 下载必要的库或热链接它们。现在,在 head 元素中加入 JavaScript 库和 Twitter 引导样式表,以及其他重要但不是必需的 meta 元素。
<head>
<meta``charset="utf-8"T2】
<title>``Message BoardT2】
<meta``name="author" content="Azat Mardan"T2】
我们需要这种响应行为:
<meta name="viewport"
content="width=device-width, initial-scale=1.0" />
从本地文件链接 jQuery v2.1.4:
<script``src="js/jquery.js"T2】
对下划线版本 1.8.3 和主干版本 1.2.3 进行同样的操作:
<script``src="js/underscore.js"T2】
<script``src="js/backbone.js"T2】
解析 JavaScript SDK v1.5.0 热链接自 Parse.com CDN。请注意版本号,因为旧版本可能无法在此示例中正常工作:
<script``src="//www.parsecdn.com/js/parse-1.5.0.min.js"``></scriptT4】
Twitter 引导 CSS 包含:
<link``type="text/css" rel="stylesheet" href="css/bootstrap.css"T2】
我们需要 RequireJS v2.1.22 来加载依赖项:
<script``type="text/javascript" src="libs/require.js"T2】
这里是我们的 JS 应用包含:
<script``type="text/javascript" src="app.js"T2】
</head>
用 Twitter Bootstrap 脚手架填充<body>元素(在“基础知识”一章中有更多相关信息):
<body>
<div``class="container-fluid"T2】
<div``class="row-fluid"T2】
<div``class="span12"T2】
<div``id="header"T2】
</div>
</div>
</div>
<div``class="row-fluid"T2】
<div``class="span12"T2】
<div``id="content"T2】
</div>
</div>
</div>
<div``class="row-fluid"T2】
<div``class="span12"T2】
<div``id="footer"T2】
</div>
</div>
</div>
</div>
</body>
创建一个app.js文件并将 Backbone.js 视图放入其中:
headerView:菜单和 app-常用信息footerView:版权和联系链接homeView:首页内容
我们对 HTML 模板使用 Require.js 语法和 shim 插件:
require([
’libs/text!header.html’,
’libs/text!home.html’,
’libs/text!footer.html’], function (
headerTpl,
homeTpl,
footerTpl) {
具有单一索引路由的应用路由:
var ApplicationRouter = Backbone.Router.extend({
routes: {
"": "home",
"*actions": "home"
},
在我们做任何事情之前,我们可以初始化将在整个应用中使用的视图:
initialize: function() {
this.headerView = new HeaderView()
this.headerView.render()
this.footerView = new FooterView()
this.footerView.render()
},
这个代码负责回家的路线:
home: function() {
this.homeView = new HomeView()
this.homeView.render()
}
})
header Backbone 视图附加到#header元素,并使用headerTpl模板:
HeaderView = Backbone.View.extend({
el: ’#header’,
templateFileName: ’header.html’,
template: headerTpl,
initialize: function() {
},
render: function() {
console.log(this.template)
$(this.el).html(_.template(this.template))
}
})
为了呈现 HTML,我们使用了jQuery.html()函数:
FooterView = Backbone.View.extend({
el: ’#footer’,
template: footerTpl,
render: function() {
this.$el.html(_.template(this.template))
}
})
家庭主干视图定义使用了#content DOM 元素:
HomeView = Backbone.View.extend({
el: ’#content’,
template: homeTpl,
initialize: function() {
},
render: function() {
$(this.el).html(_.template(this.template))
}
})
要启动一个应用,我们创建一个新实例并调用Backbone.history.start():
app = new ApplicationRouter()
Backbone.history.start()
})
app.js文件的完整代码:
require([
’libs/text!header.html’,
// Example of a shim plugin use
’libs/text!home.html’,
’libs/text!footer.html’],
function (
headerTpl,
homeTpl,
footerTpl) {
var ApplicationRouter = Backbone.Router.extend({
routes: {
’’: ’home’,
’*actions’: ’home’
},
initialize: function() {
this.headerView = new HeaderView()
this.headerView.render()
this.footerView = new FooterView()
this.footerView.render()
},
home: function() {
this.homeView = new HomeView()
this.homeView.render()
}
})
HeaderView = Backbone.View.extend({
el: ’#header’,
templateFileName: ’header.html’,
template: headerTpl,
initialize: function() {
},
render: function() {
console.log(this.template)
$(this.el).html(_.template(this.template))
}
})
FooterView = Backbone.View.extend({
el: ’#footer’,
template: footerTpl,
render: function() {
this.$el.html(_.template(this.template))
}
})
HomeView = Backbone.View.extend({
el: ’#content’,
template: homeTpl,
initialize: function() {
},
render: function() {
$(this.el).html(_.template(this.template))
}
})
app = new ApplicationRouter()
Backbone.history.start()
})
上面的代码显示了模板。所有的视图和路由都在里面,这要求模块在我们开始处理模板之前确保它们已经被加载。
下面是home.html的样子:
- 信息表
- 下划线. js 逻辑输出表中的行
- 一种新的消息形式
让我们通过分配row-fluid和span12类来使用 Twitter 引导库结构(及其响应组件):
<div``class="row-fluid" id="message-board"T2】
<div``class="span12"T2】
<table``class="table table-bordered table-striped"T2】
<caption>``Message BoardT2】
<thead>
<tr>
<th``class="span2"``>``UsernameT4】
<th>``MessageT2】
</tr>
</thead>
<tbody>
这部分有 Underscore.js 模板指令,只是一些 js 代码包裹在<%和%>标记中。我们马上检查 models 变量是否已定义并且不为空:
< %
if (typeof models != ’undefined’ && models.length > 0) {
_.each()是 UnderscoreJS 库( underscorejs.org/#each )中的一个迭代函数,它确实像它听起来那样——遍历对象/数组的元素:
_.each(models, function (value, key, list) { %>
<tr>
在迭代器函数中,我们有一个模型值。我们可以用model.attributes.attributeName访问主干模型的属性。为了用下划线输出变量,我们用<%= NAME %>代替<% CODE %>:
<td><``%= value.attributes.username %>T2】
<td><``%= value.attributes.message %>T2】
</tr>
< % })
}
但是如果models未定义或者为空呢?在这种情况下,我们打印一条消息,说明还没有消息。它进入else块。我们使用colspan=2将两个单元格合并成一个:
else { %>
<tr>
<td``colspan="2"``>``No messages yetT4】
</tr>
我们关闭表格和其他 HTML 标签:
< %}%>
</tbody>
</table>
</div>
</div>
对于新的消息表单,我们也使用row-fluid类,然后添加<input>元素:
<div``class="row-fluid" id="new-message"T2】
<div``class="span12"T2】
<form``class="well form-inline"T2】
input 元素必须有名称username,因为这是我们在 JavaScript 代码中找到该元素并获得用户名值的方式:
<input type="text"
name="username"
class="input-small"
placeholder="Username" />
类似于用户名<input>标签,消息文本标签需要有名称。在这种情况下,它是message:
<input type="text" name="message"
class="input-small"
placeholder="Message Text" />
最后,发送按钮必须有 IDsend。这是我们在HomeView类的主干事件属性中使用的:
<a``id="send" class="btn btn-primary"``>``SENDT4】
</form>
</div>
</div>
为了方便起见,下面是home.html模板文件的完整代码:
<div``class="row-fluid" id="message-board"T2】
<div``class="span12"T2】
<table``class="table table-bordered table-striped"T2】
<caption>``Message BoardT2】
<thead>
<tr>
<th``class="span2"``>``UsernameT4】
<th>``MessageT2】
</tr>
</thead>
<tbody>
< % if (typeof models != ’undefined’ && models.length>0) {
_.each(models, function (value, key, list) { %>
<tr>
<td><``%= value.attributes.username %>T2】
<td><``%= value.attributes.message %>T2】
</tr>
< % })
}
else { %>
<tr>
<td``colspan="2"``>``No messages yetT4】
</tr>
< %}%>
</tbody>
</table>
</div>
</div>
<div``class="row-fluid" id="new-message"T2】
<div``class="span12"T2】
<form``class="well form-inline"T2】
<input type="text"
name="username"
class="input-small"
placeholder="Username" />
<input type="text" name="message"
class="input-small"
placeholder="Message Text" />
<a``id="send" class="btn btn-primary"``>``SENDT4】
</form>
</div>
</div>
现在,我们可以将以下组件添加到:
- Parse.com 收藏
- Parse.com 模型
- 发送/添加消息事件
- 获取/显示消息功能
来自 Parse.com JS SDK 的主干兼容模型对象/类,具有强制的className属性(这是将出现在 Parse.com 网络界面的数据浏览器中的集合的名称):
Message = Parse.Object.extend({
className: ’MessageBoard’
})
指向模型的 Parse.com JavaScript SDK 的主干兼容集合对象:
MessageBoard = Parse.Collection.extend ({
model: Message
})
主视图需要在“发送”按钮上有一个点击事件监听器:
HomeView = Backbone.View.extend({
el: ’#content’,
template: homeTpl,
events: {
’click #send’: ’saveMessage’
},
当我们创建 homeView 时,我们还要创建一个集合并将事件侦听器附加到它:
initialize: function() {
this.collection = new MessageBoard()
this.collection.bind(’all’, this.render, this)
this.collection.fetch()
this.collection.on(’add’, function(message) {
message.save(null, {
success: function(message) {
console.log(’saved ’ + message)
},
error: function(message) {
console.log(’error’)
}
})
console.log(’saved’ + message)
})
},
saveMessage()的定义调用了“发送”按钮的点击事件:
saveMessage: function(){
首先,我们通过 ID ( #new-message)获取表单对象,因为使用存储的对象比每次都使用 jQuery 选择器更有效,可读性更好。
var newMessageForm = $(’#new-message’)
接下来的两行将获得名为username和message的输入字段的值:
var username = newMessageForm.find(’[name="username"]’).val()
var message = newMessageForm.find(’[name="message"]’).val()
一旦我们有了新消息的值(文本和作者),我们就可以调用this.collection.add:
this.collection.add({
’username’: username,
’message’: message
})
},
最后,我们通过对模板使用_.template输出集合,然后用数据this.collection调用它:
render: function() {
$(this.el).html(_.template(this.template)(this.collection))
}
我们在app. js中操作的最终结果可能是这样的:
require([
’libs/text!header.html’,
’libs/text!home.html’,
’libs/text!footer.html’], function (
headerTpl,
homeTpl,
footerTpl) {
Parse.initialize(’your-parse-app-id’, ’your-parse-js-sdk-key’)
var ApplicationRouter = Backbone.Router.extend({
routes: {
’’: ’home’,
’*actions’: ’home’
},
initialize: function() {
this.headerView = new HeaderView()
this.headerView.render()
this.footerView = new FooterView()
this.footerView.render()
},
home: function() {
this.homeView = new HomeView()
this.homeView.render()
}
})
HeaderView = Backbone.View.``extendT2】
el: ’#header’,
templateFileName: ’header.html’,
template: headerTpl,
initialize: function() {
},
render: function() {
$(this.el).html(_.template(this.template))
}
})
FooterView = Backbone.View.extend({
el: ’#footer’,
template: footerTpl,
render: function() {
this.$el.html(_.template(this.template))
}
})
Message = Parse.Object.extend({
className: ’MessageBoard’
})
MessageBoard = Parse.Collection.extend ({
model: Message
})
HomeView = Backbone.View.extend({
el: ’#content’,
template: homeTpl,
events: {
’click #send’: ’saveMessage’
},
initialize: function() {
this.collection = new MessageBoard()
this.collection.bind(’all’, this.render, this)
this.collection.fetch()
this.collection.on(’add’, function(message) {
message.save(null, {
success: function(message) {
console.log(’saved ’ + message)
},
error: function(message) {
console.log(’error’)
}
})
console.log(’saved’ + message)
})
},
saveMessage: function(){
var newMessageForm = $(’#new-message’)
var username = newMessageForm.find(’[name="username"]’).val()
var message = newMessageForm.find(’[name="message"]’).val()
this.collection.add({
’username’: username,
’message’: message
})
},
render: function() {
$(this.el).html(_.template(this.template)(this.collection))
}
})
app = new``ApplicationRouterT2】
Backbone.history.start()
})
Backbone.js 和 Parse.com 留言板应用的完整源代码可在 https://github.com/azat-co/fullstack-javascript/tree/master/06-board-backbone-parse-sdk 获得。
一旦你对你的前端应用在本地运行良好感到满意,不管有没有像 MAMP 或 XAMPP 这样的本地 HTTP 服务器,把它部署到 Windows Azure 或 Heroku。“jQuery 和 Parse.com”一章中描述了深入的部署说明。
在最后两个例子中,留言板有非常基本的功能。您可以通过添加更多功能来增强应用。
面向intermediate级开发人员的附加功能:
- 在显示消息列表之前,通过 updateAt 属性对其进行排序。
- 添加一个“刷新”按钮来更新消息列表。
- 在运行时内存或会话中保存第一个消息条目后的用户名。
- 在每封邮件旁边添加一个向上投票按钮,并存储投票。
- 在每封邮件旁边添加一个向下投票按钮,并存储投票。
面向高级开发人员的附加功能:
- 添加用户集合。
- 防止同一用户多次投票。
- 使用 Parse.com 函数添加用户注册和登录操作。
- 在用户创建的每封邮件旁边添加一个“删除邮件”按钮。
- 在用户创建的每条消息旁边添加“编辑消息”按钮。
这短短的一章给了你另一种只用 JavaScript(当然还有 HTML 和 CSS)构建应用的方法。使用 Parse.com 或类似的后端即服务(BaaS)解决方案,无需编写自己的后端代码就可以直接保存数据。BaaS solutions event 通过允许访问级控制、身份验证、服务器端逻辑和第三方集成而更进一步。
除了 Parse.com,在这一章中我们看到了 Backbone 是如何灵活的,你可以重载它的类来构建你自己的定制类。这是一种使用 Backbone 构建自己的框架的方法。这就是我们在 DocuSign 所做的,我们有基本的主干模型,并为定制用例扩展了它们。我们甚至在服务器和浏览器之间共享主干模型,允许更快的数据加载。说到服务器 JavaScript,下一章我们将探索如何用 Node.js 在服务器上编写 JavaScript。