构建一个软件设计有两种方式:一种是让它简单到没有明显的缺陷,另一种是让它复杂到没有明显的缺陷。第一种方法要困难得多。——东尼·霍尔T3】
本章涵盖以下主题:
- JSON、AJAX 和 CORS 的定义
- 主要 jQuery 函数概述
- Twitter 引导脚手架
- 主要零部件较少
- OpenWeatherMap API 示例上 JSONP 调用的图示
- Parse.com 概述
- 关于如何使用 jQuery 和 Parse.com 构建留言板前端应用的说明
- 关于部署到 Windows Azure 和 Heroku 的分步说明
- 消息的更新和删除
本章是前端 web 开发的基本介绍。它涵盖了对 Twitter Bootstrap 等应用前端开发很重要的东西。这些令人惊叹的库让开发人员可以很快拥有一个漂亮的用户界面。
它涵盖了术语并解释了 JSON、AJAX 和 CORS。然后,我们探索天气应用的例子。
我们使用 Parse.com 作为我们的后端来简化事情,使开发更快,同时仍然保持现实。本章的基础是用 Parse.com 和 jQuery 构建的持久性留言板应用。
在此之前,让我们先澄清一些术语。它们非常重要,足以让我们停下来熟悉它们。如果这些对你来说很熟悉,你可能想跳过。
下面是来自 www 的 JavaScript 对象表示法(JSON)的定义。json。组织
JavaScript Object Notation(JSON)是一种轻量级的数据交换格式。对人类来说,读和写很容易。机器很容易解析生成。它基于 JavaScript 编程语言的子集,标准 ECMA-262 第三版-1999 年 12 月 ( www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf )。
JSON 是一种完全独立于语言的文本格式,但是它使用了 C 语言系列的程序员所熟悉的约定,包括 C、C++、C#、Java、JavaScript、Perl、Python 和许多其他语言。这些特性使 JSON 成为理想的数据交换语言。
JSON 已经成为在 web 和移动应用以及第三方服务的不同组件之间传输数据的标准。JSON 也广泛用于应用内部,作为配置、地区、翻译文件或任何其他数据的格式。
典型的 JSON 对象如下所示:
{
"a": "value of a",
"b": "value of b"
}
我们有一个带有键/值对的对象。键在冒号(:)的左边,值在冒号的右边。在计算机科学术语中,JSON 相当于哈希表、键列表或关联数组(取决于特定的语言)。JSON 和 JS object literal notation(原生 JS 对象)之间唯一的大区别是,前者更严格,要求键标识符和字符串值使用双引号(")。假设我们有一个字符串格式的有效 JSON 对象,这两种类型都可以用JSON.stringify()序列化为字符串表示,用JSON.parse()反序列化。
然而,对象的每个成员都可以是数组、原语或另一个对象;例如:
{
"posts": [{
"title": "Get your mind in shape!",
"votes": 9,
"comments": ["nice!", "good link"]
}, {
"title": "Yet another post",
"votes": 0,
"comments": []
}
],
"totalPost": 2,
"getData":``function ()T2】
return new Data().getDate();
}
}
在这个例子中,我们有一个带有posts属性的对象。属性的值是一个对象数组,每个对象都有title、votes和comments键。属性保存一个数字原语,而属性是一个字符串数组。我们也可以用函数作为值。在这种情况下,键被称为方法;也就是getData。
JSON 比 XML 或其他数据格式灵活和紧凑得多,如本文所述:JSON:XML 的无脂肪替代品 ( www.json.org/xml.html )。为了方便起见,MongoDB 使用了一种类似 JSON 的格式,称为二进制 JSON ( http:// bsonspec。org ) (BSON),稍后在 BSON 的第七章中进一步讨论。
在客户端(浏览器)使用异步 JavaScript 和 XML (AJAX)通过利用 JavaScript 语言中的XMLHttpRequest对象来发送和接收来自服务器的数据。尽管有这个名字,但并不要求使用 XML,通常使用 JSON。这就是为什么开发者几乎再也不说 AJAX 了。请记住,HTTP 请求可以同步发出,但是这样做并不是一个好的做法。同步请求最典型的例子是包含<script>标签。
出于安全原因,当客户端代码和服务器端代码位于不同的域时,XMLHTTPRequest 对象的初始实现不允许跨域调用。有一些方法可以解决这个问题。
其中之一就是使用 JSONP(http://en . Wikipedia . org/wiki/JSONP),带 padding/前缀的 JSON。这基本上是通过 DOM 生成的<script>标签进行的动态操作。脚本标签不属于相同的域限制。JSONP 请求在请求查询字符串中包含回调函数的名称。例如,jQuery.ajax()函数自动生成一个惟一的函数名,并将其附加到请求中(为了便于阅读,将一个字符串分成多行):
T2https://graph.facebook.com/search
?type=post
&limit=20
&q=Gatsby
&callback=jQuery16207184716751798987_1368412972614&_=1368412984735
第二种方法是使用跨源资源共享(CORS(http://www . w3 . org/TR/CORS)),这是一个更好的解决方案,但是它需要控制服务器端来修改响应头。我们在留言板示例应用的最终版本中使用这种技术。以下是 CORS 服务器响应标头的示例:
Access-Control-Allow-Origin: *
更多关于 CORS 的信息可以在资源中通过启用 CORS ( http://enable-cors.org/resources.html )和使用 CORS 通过 HTML5 岩石教程( http://www.html5rocks.com/en/tutorials/cors/ )获得。你可以在 test-cors.org 测试 CORS 的请求。
在培训期间,我们将使用 jQuery ( http://jquery.com/ )进行 DOM 操作、HTTP 请求和 JSONP 调用。jQuery 成为事实上的标准是因为它的$对象或函数,它提供了一种简单而有效的方法,通过 ID、类、标记名、属性值、结构或它们的任意组合来访问页面上的任何 HTML DOM 元素。语法非常类似于 CSS,我们用#表示 id,用.表示类选择。例如:
$(’#main’).hide()
$(’p.large’).attr(’style’,’color:red’)
$(’#main’).show().html(’<div>new div</div>’)
下面是最常用的 jQuery API 函数列表:
find() (http://api.jquery.com/find):根据提供的选择器字符串选择元素hide() (http://api.jquery.com/hide):隐藏可见的元素show() (http://api.jquery.com/show):显示一个隐藏的元素html() (http://api.jquery.com/html):获取或设置一个元素的内部 HTMLappend() (http://api.jquery.com/append)将一个元素注入到选中元素后的 DOM 中prepend() (http://api.jquery.com/prepend)将一个元素注入到选定元素之前的 DOM 中on() (http://api.jquery.com/on):将事件监听器附加到元素off() (http://api.jquery.com/off)从元素中分离事件监听器css() (http://api.jquery.com/css):获取或设置元素的样式属性值attr() (http://api.jquery.com/attr)获取或设置元素的任意属性val() (http://api.jquery.com/val):获取或设置元素的值属性text() (http://api.jquery.com/text):获取元素及其子元素的组合文本each() (http://api.jquery.com/each):迭代一组匹配的元素
大多数 jQuery 函数不仅作用于调用它们的单个元素,如果选择的结果有多个项目,还作用于一组匹配的元素。这是一个导致错误的常见陷阱,通常发生在 jQuery 选择器太宽的时候。
此外,jQuery 有许多可用的插件和库,它们提供了丰富的用户界面或其他功能。例如:
- jQuery UI (
http://jqueryui.com/ - jQuery Mobile (
http://jquerymobile.com/
引导您完成实施并演示项目的补充视频: http://bit.ly/1RKx9uY 。
Twitter Bootstrap(http://get Bootstrap。com )是 CSS/LESS 规则和 JavaScript 插件的集合,用于创建良好的用户界面和用户体验,而无需在圆角按钮、交叉兼容性、响应性等细节上花费大量时间。这个集合或框架非常适合你的想法的快速原型制作。然而,由于其可定制的能力,Twitter Bootstrap 也是严肃项目的良好基础。源代码是用 LESS(http://lessscss 写的。org ),但是普通的 CSS 也可以下载使用。
这里有一个简单的例子,使用 Twitter Bootstrap 搭建版本 4.0.0-alpha。项目的结构应该是这样的:
/01-bootstrap
-index.html
/css
-bootstrap.css
-bootstrap.min.css
... (other files if needed)
/js
-bootstrap.js
-bootstrap.min.js
-npm.js
首先让我们用适当的标签创建index.html文件:
<!DOCTYPE html>
<html``lang="en"T2】
<head>
</head>
<body>
</body>
</html>
包含 Twitter 引导库作为一个缩小的 CSS 文件:
<!DOCTYPE html>
<html``lang="en"T2】
<head>
<link
type="text/css"
rel="stylesheet"
href="css/bootstrap.min.css" />
</head>
<body>
</body>
</html>
应用具有container-fluid和row-fluid等级的脚手架:
<body >
<div``class="container-fluid"T2】
<div``class="row-fluid"T2】
</div>``<!--``row-fluidT3】
</div>``<!--``container-fluidT3】
</body>
Twitter Bootstrap 使用 12 列网格。单个小区的大小可以由类别spanN指定,例如span1、span2、span12。还有offsetN类,比如offset1,offset2,...offset12,向右移动单元格。完整的参考资料可在 http://twitter.github.com/bootstrap/scaffolding.html 获得。
我们将对主要内容块使用span12和hero-unit类:
<div``class="row-fluid"T2】
<div``class="span12"T2】
<div``id="content"T2】
<div``class="row-fluid"T2】
<div``class="span12"T2】
<div``class="hero-unit"T2】
<h1>
Welcome to Super
Simple Backbone
Starter Kit
</h1>
<p>
This is your home page.
To edit it just modify
the``<i>``index.html``</i>T4】
</p>
<p>
<a
class="btn btn-primary btn-large"
href="http://twitter.github.com/bootstrapT2】
target="_blank" >
Learn more
</a>
</p>
</div>``<!--``hero-unitT3】
</div>``<!--``span12T3】
</div>``<!--``row-fluidT3】
</div>``<!--``contentT3】
</div>``<!--``span12T3】
</div>``<!--``row-fluidT3】
这是来自 1-bootstrap 的index.html的完整源代码:
<!DOCTYPE html>
<html``lang="en"T2】
<head>
<link``type="text/css" rel="stylesheet" href="css/bootstrap.css"T2】
</head>
<body >
<div``class="container-fluid"T2】
<div``class="row-fluid"T2】
<div``class="span12"T2】
<div``id="content"T2】
<div``class="row-fluid"T2】
<div``class="span12"T2】
<div``class="hero-unit"T2】
<h1>``Welcome to Super Simple Backbone Starter KitT2】
<p>``This is your home page. To edit it just modify``<i>``index.html``</i>``file!T6】
<p><a``class="btn btn-primary btn-large" href="http://twitter.github.com/bootstrap" target="_blank"``>T5</a></p>
</div>``<!--``hero-unitT3】
</div>``<!--``span12T3】
</div>``<!--``row-fluidT3】
</div>``<!--``contentT3】
</div>``<!--``span12T3】
</div>``<!--``row-fluidT3】
</div>``<!--``container-fluidT3】
</body>
</html>
这个例子可以从 GitHub 公共库github.com/azat-co/fullstack-javascript 下载和拉取,在 01-bootstrap 文件夹 ( https://github.com/azat-co/fullstack-javascript/tree/master/01-bootstrap )下。如果你更喜欢看截屏,我在 YouTube 上录了一个( http://bit.ly/1RKx9uY )。
本视频和其他视频将带您完成书中概述的相同步骤。所以,如果你正在阅读这本书的印刷版本,不用担心。书中的信息足够了。
这里有一些其他有用的工具——CSS 框架和 CSS 预处理程序——值得一试:
- 指南针 : CSS 框架(
http://compass-style.org/) - 萨斯:CSS3 的扩展和模拟少(
http://sass-lang.com/) - 蓝图 : CSS 框架(
http://blueprintcss.org/) - 基础:响应式前端框架(
http://foundation.zurb.com/) - Bootswatch :定制 Twitter 引导主题集合(
http://bootswatch.com/) - WrapBootstrap :定制自举主题市场(
https://wrapbootstrap.com/)
要使用 Twitter Bootstrap 源文件,您需要使用 LESS 或 SASS (另一个类似 LESS 的 CSS 框架)。
LESS 是一种动态样式表语言。有时候,在这种情况下,少即是多,多即是少。浏览器无法解释更少的语法,因此必须以三种方式之一将更少的源代码编译成 CSS:
In the browser by the LESS JavaScript library On the server side by language or framework; for example, for Node.js there is the LESS module ( https://www.npmjs.com/package/less ) Locally on your machine by command line (installed with npm by running $ npm install -g less), WinLess ( http://winless.org/ ), LESS App ( http://incident57.com/codekit/index.html ), SimpLESS ( http://wearekiss.com/simpless ), or a similar app
浏览器选项适用于开发环境,但不太适合生产环境。
以下是一些在线编译工具:
- LESS2CSS (
http://less2css.org/):一个基于 Express.js 的基于浏览器的 LESS to CSS 转换器 - lessphp (
http://leafo.net/lessphp/):一个在线演示编译器 - 多普佛利 (
http://www.dopefly.com/LESS-Converter/less-converter.html):一种在线少转换器
LESS 具有变量、混合和操作符,使得开发人员可以更快地重用 CSS 规则。
变量减少了冗余,并允许开发人员通过将它们放在一个规范的地方来快速更改值,我们知道在设计(和样式)中我们经常需要非常频繁地更改值。
我们有时会用一些更少的代码,用符号@标记变量,比如在@color中:
@color: #4D926F;
#header {
color:``@colorT2】
}
h2 {
color:``@colorT2】
}
这段代码将被编译成 CSS 中的等效代码:
#header {
color:``#4D926FT2】
}
h2 {
color:``#4D926FT2】
}
好处是,只需要在一个地方更新颜色值,而在 CSS 中只需要两个地方。这是最好的抽象。
这是关于混音功能的。混合的语法与创建类选择器的语法相同。例如,这是一个.border混音:
.border {
border-top:``dotted 1px blackT2】
border-bottom:``solid 2px blackT2】
}
#menu a {
color:``#111T2】
.border;
}
.post a {
color:``redT2】
.border;
}
转换成这个 CSS,其中的.border被替换为实际的样式,而不是名称:
.border {
border-top:``dotted 1px blackT2】
border-bottom:``solid 2px blackT2】
}
#menu a {
color:``#111T2】
border-top:``dotted 1px blackT2】
border-bottom:``solid 2px blackT2】
}
.post a {
color:``redT2】
border-top:``dotted 1px blackT2】
border-bottom:``solid 2px blackT2】
}
更有用的是给混音传递一个参数。这使得开发人员能够创建更加通用的代码。例如,.rounded-corners是一个可以根据参数radius的值改变大小的混音:
.rounded-corners (@radius: 5px) {
border-radius:``@radiusT2】
-webkit-border-radius:``@radiusT2】
-moz-border-radius:``@radiusT2】
}
#header {
.rounded-corners;
}
#footer {
.rounded-corners(10px);
}
该代码将编译成 CSS 格式:
#header {
border-radius:``5pxT2】
-webkit-border-radius:``5pxT2】
-moz-border-radius:``5pxT2】
}
#footer {
border-radius:``10pxT2】
-webkit-border-radius:``10pxT2】
-moz-border-radius:``10pxT2】
}
无论您使用不带参数的 mix-in 还是带多个参数的 mix-in,它们在创建抽象和实现更好的代码重用方面都非常出色。
少支持运营。通过运算,我们可以对数字、颜色或变量执行数学函数。这对于大小、颜色和其他与数字相关的样式非常有用。
下面是一个在 LESS 中执行乘法和加法的运算符示例:
@the-border: 1px;
@base-color: #111;
@red: #842210;
#header {
color:``@base-color * 3T2】
border-left:``@the-borderT2】
border-right:``@the-border * 2T2】
}
#footer {
color:``@base-color + #003300T2】
border-color:``desaturate(@red, 10%)T2】
}
该代码在 CSS 中编译,编译器用变量和操作替换表达式的结果:
#header {
color:``#333333T2】
border-left:``1pxT2】
border-right:``2pxT2】
}
#footer {
color:``#114411T2】
border-color:``#7d2717T2】
}
如您所见,LESS 显著提高了普通 CSS 的可重用性。在大型项目中,这可以节省时间,因为您可以创建更少的模块,并在多个应用中重用它们。
其他重要的少特征 ( http://lesscss.org/#docs )包括如下:
- 模式匹配
- 嵌套规则
- 功能
- 名称空间
- 范围
- 评论
- 进口
引导您完成实施并演示项目的补充视频: http://bit.ly/1RKxyxA 。
这个例子纯粹是为了演示的目的。它不是后面章节中讨论的主要留言板应用的一部分。目标只是说明 jQuery、JSONP 和 REST API 技术的组合。
注意,这个例子使用了 OpenWeatherMap API 2.5。API 要求对 REST 调用进行身份验证(一个应用 ID)。您可以在 openweathermap.org/appid 获得所需的钥匙。API 文档可在 openweathermap.org/api 获得。
这个天气应用的想法是向您显示城市名称的输入字段以及公制和英制的按钮。一旦你输入城市名称并点击其中一个按钮,应用将从 OpenWeatherMap 获取天气预报。
在这个例子中,我们将使用 jQuery 的$.ajax()函数。它具有以下语法:
var request = $.ajax({
url: url,
dataType: ’jsonp’,
data: {q: cityName, appid: appId, units: units},
jsonpCallback: ’fetchData’,
type: ’GET’
}).fail(function(error){
console.error(error)
alert(’Error sending request’)
})
在刚刚显示的ajax()函数的代码片段中,我们使用了以下参数:
url是 API 的一个端点。dataType是我们期望从服务器得到的数据类型;例如,“json”、“xml”、“jsonp”(带前缀的 JSON—不支持 CORS 的服务器的格式)。data是要发送到服务器的数据。jsonpCallback是函数名,字符串格式,请求返回后调用;默认情况下,jQuery 会创建一个名称。type是请求的 HTTP 方法;比如“得”、“贴”。
还有一个链接的方法.fail,它有一个逻辑,当请求有错误(即失败)时做什么。
有关ajax()功能的更多参数和示例,请访问 api.jquery.com/jQuery.ajax 。
为了将我们的函数分配给用户触发的事件,我们需要使用 jQuery 库中的click函数。语法非常简单:
$(’#btn’).click(function() {
...
}
$(’#btn’)是一个 jQuery 对象,用btn的id指向 DOM 中的一个 HTML 元素。
为了确保我们想要访问和使用的所有元素都在 DOM 中,我们需要将所有 DOM 操作代码包含在以下 jQuery 函数中:
$(document).ready(function(){
...
}
这是动态生成的 HTML 元素的常见错误。它们在被创建并注入 DOM 之前是不可用的。
我们必须将按钮的事件处理程序放在$(document).ready()回调中。否则,代码可能会尝试将事件侦听器附加到不存在的 DOM 元素。$(document).ready()确保浏览器呈现所有的 DOM 元素。
$(document).ready(function(){
$(’.btn-metric’).click(function() {
prepareData(’metric’)
})
$(’.btn-imperial’).click(function() {
prepareData(’imperial’)
})
})
我们使用类而不是 ID,因为类更灵活(不能有多个同名的 ID)。下面是按钮的 HTML 代码:
<div``class="row"T2】
<div``class="span6 offset1"T2】
<input``type="button" class="btn-primary btn btn-metric" value="Get forecast in metric"T2】
<div``class="span6 offset1"T2】
<input``type="button" class="btn-danger btn btn-imperial" value="Get forecast in imperial"T2】
</div>
<div``class="span3"T2】
<p``id="info"T2】
</div>
</div>
ID 为info的最后一个容器是我们放置预测的地方。
这个想法很简单:我们有按钮和事件侦听器,一旦用户单击按钮,它们就会做一些事情。前面提到的按钮调用prepareData()方法。这是它的定义:
var openWeatherAppId = ’GET-YOUR-KEY-AT-OPENWEATHERMAP’,
openWeatherUrl = ’http://api.openweathermap.org/data/2.5/forecastT2】
var prepareData = function(units) {
var cityName = $(’#city-name’).val()
if (cityName && cityName != ’’){
cityName = cityName.trim()
getData(openWeatherUrl, cityName, openWeatherAppId, units)
}
else {
alert(’Please enter the city name’)
}
}
代码应该简单明了。我们从输入框中获取城市名称的值,检查它是否为空,并调用getDada(),这将向服务器发出 XHR 请求。您已经看到了一个$.ajax请求的例子。请注意,回调函数名为fetchData。这个函数将在浏览器从 OpenWeatherMap API 获得响应后被调用。不用说,我们必须按如下方式传递城市名称、应用 ID 和单位:
function getData (url, cityName, appId, units) {
var request = $.ajax({
url: url,
dataType: ’jsonp’,
data: {
q: cityName,
appid: appId,
units: units
},
jsonpCallback: ’fetchData’,
type: ’GET’
}).fail(function(error){
console.error(error)
alert(’Error sending request’)
})
}
JSONP 获取函数神奇地(多亏了 jQuery)通过注入脚本标记和将回调函数名附加到请求查询字符串来进行跨域调用。
此时,我们需要实现fetchData并用预测更新视图。console.log有助于查找响应的数据结构;也就是字段所在的位置。城市名称和国家将显示在预测上方,以确保找到的位置与我们在输入框中请求的位置相同。
function fetchData (forecast) {
console.log(forecast)
var html = ’’,
cityName = forecast.city.name,
country = forecast.city.country
现在,我们通过迭代预测并连接字符串来形成 HTML:
html += ’<h3> Weather Forecast for ’
+ cityName
+ ’, ’
+ country
+ ’</h3>’
forecast.list.forEach(function(forecastEntry, index, list){
html += ’<p>’
+ forecastEntry.dt_txt
+ ’: ’
+ forecastEntry.main.temp
+ ’</p>’
})
最后,我们为 ID 为log的 div 获取一个 jQuery 对象,并在 HTML 中注入城市名称和预测:
$(’#log’).html(html)
简单来说,有一个按钮元素触发了prepareData(),它调用了getData(),在它的回调中是fetchData()。如果您觉得这令人困惑,这里是index.html文件的完整代码:
<!DOCTYPE html>
<html``lang="en"T2】
<head>
<link``type="text/css" rel="stylesheet" href="css/bootstrap.css"T2】
<script``src="js/jquery.js" type="text/javascript"T2】
<meta``name="viewport" content="width=device-width, initial-scale=1.0"T2】
<script>
var openWeatherAppId = ’GET-YOUR-KEY-AT-OPENWEATHERMAP’,
openWeatherUrl = ’http://api.openweathermap.org/data/2.5/forecastT2】
var``prepareData =``functionT3】
var cityName = $(’#city-name’).val()
if (cityName && cityName != ’’){
cityName = cityName.trim()
getData(openWeatherUrl, cityName, openWeatherAppId, units)
}
else {
alert(’Please enter the city name’)
}
}
$(document).ready(function(){
$(’.btn-metric’).click(function() {
prepareData(’metric’)
})
$(’.btn-imperial’).click(function() {
prepareData(’imperial’)
})
})
function getData (url, cityName, appId, units) {
var request = $.ajax({
url: url,
dataType: ’jsonp’,
data: {
q: cityName,
appid: appId,
units: units
},
jsonpCallback: ’fetchData’,
type: ’GET’
}).fail(function(error){
console.error(error)
alert(’Error sending request’)
})
}
function fetchData (forecast) {
console.log(forecast)
var html = ’’,
cityName = forecast.city.name,
country = forecast.city.country
html += ’<h3> Weather Forecast for ’
+ cityName
+ ’, ’
+ country
+ ’</h3>’
forecast.list.forEach(function(forecastEntry, index, list){
html += ’<p>’
+ forecastEntry.dt_txt
+ ’: ’
+ forecastEntry.main.temp
+ ’</p>’
})
$(’#log’).html(html)
}
</script>
</head>
<body>
<div``classT2】
<div``classT2】
<div``classT2】
<h2>Weather App</h2>
<p>Enter city name to get the weather forecast</p>
</div>
<div class="span6 offset1"><input class="span4" type="text" placeholder="Enter the city name" id="city-name" value=""/>
</div>
</div>
<div``classT2】
<div``class``="span6 offset1"><input type="button"``classT4】
<div class="span6 offset1"><input type="button" class="btn-danger btn btn-imperial" value="Get forecast in imperial"/>
</div>
<div class="span3">
<p id="info"></p>
</div>
</div>
<div``classT2】
<div``classT2】
<div id="log">Nothing to show yet</div>
</div>
</div>
<div class="row">
<hr/>
<p>Azat Mardan (<a href="``http://twitter.com/azat_co">@azat_co</a>)</pT2】
</div>
</div>
</body>
</html>
试着启动它,看看它是否在本地 HTTP 服务器上工作(只需在浏览器中打开index.html)。如果没有 HTTP 服务器,它应该无法工作,因为它依赖于 JSONP 技术。你可以得到http-static或http-server命令行工具,如第 2 章所述。
源代码可以在 0 2-weather 文件夹和 GitHub ( https:// github)上找到。com/azat-co/full stack-JavaScript/tree/master/02-weather)。YouTube 上有一个截屏视频,它会带你完成实现并演示该应用。
此示例是用 OpenWeatherMap API v2.5 构建的,可能不适用于更高版本。此外,您还需要名为 app ID 的 API 键。您可以在 openweathermap.org/appid 获得所需的钥匙。如果你觉得必须要有一个工作实例,请将你的反馈提交到 GitHub 资源库,以获得本书的项目( https://github.com/azat-co/fullstack-javascript )。
jQuery 是一个很好的从 RESTful 服务器获取数据的库。有时我们不仅仅是从服务器上读取数据;我们也想写它。通过这种方式,信息得以保存,并可以在以后被访问。Parse.com 将允许你没有摩擦地保存你的数据。
引导您完成实施并演示项目的补充视频: http://bit.ly/1SU8imX 。
Parse.com(http://parse。com )是一个可以替代数据库和服务器的服务。它最初是作为支持移动应用开发的手段出现的。然而,有了 REST API 和 JavaScript SDK,Parse.com 可以在任何 web 和桌面应用中用于数据存储(以及更多),这使得它成为快速原型开发的理想选择。
去 Parse.com 注册一个免费账户。创建一个应用,复制应用 ID、REST API 键和 JavaScript 键。我们需要这些钥匙来打开我们在 Parse.com 的收藏。请注意数据浏览器选项卡,因为在那里您可以看到您的收藏和项目。
我们将创建一个简单的应用,使用 Parse.com JavaScript SDK 将值保存到集合中。我们的应用将由一个index.html文件和一个app.js文件组成。下面是我们项目文件夹的结构:
/03-parse-sdk
-index.html
-app.js
-jquery.js
/css
-boostrap. css
该示例位于 GitHub ( https:// github)上的 03-parse-sdk 文件夹中。com/azat-co/full stack-JavaScript/tree/master/03-parse-SDK),但是鼓励您从头开始键入自己的代码。首先,创建index.html文件:
<html``lang="en"T2】
<head>
从本地文件中包含缩小的 jQuery v2.1.4 库(可以下载并保存到文件夹中):
<script
type="text/javascript"
src=
"jquery.js" >
</script>
包括来自解析 CDN 位置的 Parse.com JavaScript SDK 库 v1.6.7:
<script
src="// www.parsecdn.com/js/parse-1.6.7.min.js "
</script>
包括我们的app.js文件和 Twitter Bootstrap v4.0.0-alpha:
<script``type="text/javascript" src="app.js"T2】
<link``type="text/css" rel="stylesheet" href="css/bootstrap.css"T2】
</head>
<body>
<!--``We’ll do something hereT2】
</body>
</html>
HTML 页面的<body>由<textarea>元素组成。我们将用它来输入 JSON:
<body>
<div``class="container-fluid"T2】
<div``class="row-fluid"T2】
<div``class="span12"T2】
<div``id="content"T2】
<div``class="row-fluid"T2】
<div``class="span12"T2】
<div``class="hero-unit"T2】
<h1>``Parse JavaScript SDK demoT2】
<textarea``cols="60" rows="7"``>T3】
"name": "John",
"text": "hi"
} </textarea>
<textarea>的缩进看起来不正常,因为这个元素保留了空白,当我们将该字符串处理成 JSON 时,我们不想要它。
在输入区域之后,有一个按钮将触发保存到 Parse.com:
<p><a``class="btn btn-primary btn-large btn-save"``>``Save objectT4】
<pre``class="log"T2】
Go to``<a``href="https://parse.com/apps/" target="_blank"``>``Parse.com``</a>``to check the data.
</div>``<!--``hero-unitT3】
</div>``<!--``span12T3】
</div>``<!--``row-fluidT3】
</div>``<!--``contentT3】
</div>``<!--``span12T3】
</div>``<!--``row-fluidT3】
</div>``<!--``container-fluidT3】
</body>
</html>
创建app.js文件并使用$(document).ready函数来确保 DOM 已准备好进行操作:
$(document).ready(function() {
将parseApplicationId和parseJavaScriptKey更改为 Parse.com 应用仪表板中的值(您需要注册该服务):
var parseApplicationId = ’GET-YOUR-KEYS-AT-PARSE.COM’
var parseJavaScriptKey = ’GET-YOUR-KEYS-AT-PARSE.COM’
因为我们已经包含了 Parse JavaScript SDK 库,所以我们现在可以访问全局对象Parse。我们用键初始化一个连接,并创建一个对Test集合的引用:
Parse.initialize(parseApplicationId, parseJavaScriptKey)
var Test = Parse.Object.extend(’Test’)
var test = new Test()
这个简单的代码将把一个带有键name和text的对象保存到 Parse.comTest集合中:
var Test = Parse.Object.extend(’Test’)
var test = new Test()
$(’.btn-save’).click(function(){
接下来的几条语句处理从<textarea>获取 JSON,并将其解析成普通的 JavaScript 对象。try/catch非常关键,因为 JSON 的结构非常严格。你不能有任何额外的符号。每次出现语法错误,都会破坏整个应用。因此,我们需要考虑错误的语法:
try {
var data = JSON.parse($(’textarea’).val())
} catch (e) {
alert(’Invalid JSON’)
}
if (!data) return false
方便的是,save()方法接受回调参数success和error,就像jQuery.ajax()函数一样。要得到确认,我们只需查看页面上的log容器(<pre class="log"></pre>):
success: function(object) {
console.log(’Parse.com object is saved: ’, object)
$(’.log’).html(JSON.stringify(object, null, 2))
// Alternatively you could use alert(’Parse.com object is saved’)
},
了解我们保存对象失败的原因很重要:
error: function(object) {
console.log(’Error! Parse.com object is not saved: ’, object)
}
})
})
})
这样你就不必点击 Github 链接(或从书中键入)来查找app.js文件的完整源代码,我在这里提供了它:
$(document).ready(function() {
var parseApplicationId = ’GET-YOUR-KEYS-AT-PARSE.COM’
var parseJavaScriptKey = ’GET-YOUR-KEYS-AT-PARSE.COM’
// Change parseApplicationId and parseJavaScriptKey to values from Parse.com application dashboard
Parse.initialize(parseApplicationId, parseJavaScriptKey)
var Test = Parse.Object.extend(’Test’)
var test = new Test()
$(’.btn-save’).click(function(){
try {
var data = JSON.parse($(’textarea’).val())
} catch (e) {
alert(’Invalid JSON’)
}
if (!data) return false
test.save(data, {
success: function(object) {
console.log(’Parse.com object is saved: ’, object)
$(’.log’).html(JSON.stringify(object, null, 2))
},
error: function(object) {
console.log(’Error! Parse.com object is not saved: ’, object)
}
})
})
})
对于这种方法,我们需要使用 Parse.com 仪表板中的 JavaScript SDK 键。对于 jQuery 示例,我们将使用来自同一 web 页面的 REST API 键。
要运行该应用,请在项目文件夹中启动本地 web 服务器,并在浏览器中导航到该地址(如http://localhost:8080)。如果你从 Parse.com 得到一个 401 未授权错误,那可能是因为你有错误的 API 密匙。
如果一切正常,您应该能够在 Parse.com 的数据浏览器中看到填充了值“John”和“hi”的Test。此外,您应该看到带有新创建的 ID 的正确消息。Parse.com 自动创建对象 id 和时间戳,这在我们的留言板应用中非常有用。
Parse.com 还为 Hello World 应用提供了详细的说明,这些说明可以在新项目( https://parse.com/apps/quickstart#js/blank )和现有项目( https://parse.com/apps/quickstart#js/existing )的快速入门指南部分中找到。
让我们继续讨论留言板应用。
引导您完成实施并演示项目的补充视频: http://bit.ly/1SU8pyS 。
留言板将由一个输入框、一个消息列表和一个发送按钮组成。我们需要显示现有消息的列表,并能够提交新消息。我们现在将使用 Parse.com 作为后端,稍后使用 MongoDB 切换到 Node.js。
你可以在 Parse.com 获得一个免费账户。JavaScript 指南在 https://parse.com/docs/js_guide 可用,JavaScript API 在 https://parse.com/docs/js/ 可用。
注册 Parse.com 后,转到仪表板并创建一个新的应用(如果您还没有这样做)。复制您新创建的应用的应用 ID 和 JavaScript 密钥以及 REST API 密钥。你以后会需要它们的。有几种方法可以使用 Parse.com:
- REST API:我们将在 jQuery 示例中使用这种方法。
- JavaScript SDK:我们刚刚在前面的测试示例中使用了这种方法,稍后我们将在 Backbone.js 示例中使用它。
REST API 是一种更通用的方法。Parse.com 提供了端点,我们可以用 jQuery 库中的$.ajax()方法请求这些端点。可用 URL 和方法的描述可以在 parse.com/docs/rest 找到。
完整的代码可以在04-board-parse-rest ( https:// github 中找到。com/azat-co/full stack-JavaScript/tree/master/04-board-parse-rest)文件夹,但我们鼓励您先尝试编写自己的应用。
我们将使用 Parse.com 的 REST API 和 jQuery。Parse.com 支持不同的源域 AJAX 调用,所以我们不需要 JSONP。
当您决定在不同的域上部署您的后端应用(它将充当 Parse.com 的替代品)时,您需要在前端使用 JSONP,或者在后端使用定制的 CORS 头文件。这一主题将在本书的后面介绍。
现在,应用的结构应该如下所示:
index.html
css/bootstrap.min.css
css/style.css
js/app.js
img/spinner.gif
让我们为留言板应用创建一个可视化表示。我们只想按时间顺序显示带有用户名的消息列表。因此,一个表格就足够了,我们可以动态地创建<tr>元素,并在获得新消息时不断地插入它们。
用以下内容创建一个简单的 HTML 文件index.html:
- 包含 JS 和 CSS 文件
- 具有 Twitter 引导的响应结构
- 信息表
- 新邮件的表单
让我们从head和依赖项开始。我们将包括 CDN jQuery、local app.js、local minified Twitter Bootstrap 和自定义样式表style.css:
<!DOCTYPE html>
<html``lang="en"T2】
<head>
<script``src="js/jquery.js" type="text/javascript" language="javascript"T2】
<script``src="js/app.js" type="text/javascript" language="javascript"T2】
<link``href="css/bootstrap.min.css" type="text/css" rel="stylesheet"T2】
<link``href="css/style.css" type="text/css" rel="stylesheet"T2】
<meta``name="viewport" content="width=device-width, initial-scale=1"T2】
</head>
body 元素将具有由类container-fluid和row-fluid定义的典型 Twitter Boostrap scaffolding 元素:
<body>
<div``class="container-fluid"T2】
<div``class="row-fluid"T2】
<h1>``Message Board with Parse REST APIT2】
消息表是空的,因为我们将从 JS 代码中以编程方式填充它:
<table``class="table table-bordered table-striped"T2】
<caption>``MessagesT2】
<thead>
<tr>
<th>
Username
</th>
<th>
Message
</th>
</tr>
</thead>
<tbody>
<tr>
<td``colspan="2"``><img``src="img/spinner.gif" width="20"T4】
</tr>
</tbody>
</table>
</div>
另一行是我们的新消息表单,其中的发送按钮使用了 Twitter 引导类btn和btn-primary:
<div``class="row-fluid"T2】
<form``id="new-user"T2】
<input type="text" name="username"
placeholder="Username" />
<input type="text" name="message"
placeholder="Message" />
<a``id="send" class="btn btn-primary"``>``SENDT4】
</form>
</div>
</div>
</body>
</html>
该表将包含我们的消息,该表单将为新消息提供输入。
现在我们要写三个主要函数:
getMessages(): The function to get the messages updateView(): The function to render the list of messages $(’#send’).click(...): The function that triggers sending a new message
为了简单起见,我们将所有的逻辑放在一个文件app.js中。当然,当你的项目越来越大时,根据功能来分离代码是一个好主意。
用您自己的值替换这些值,并注意使用 REST API 键(不是前面示例中的 JavaScript SDK 键):
var parseID=’YOUR_APP_ID’
var parseRestKey=’YOUR_REST_API_KEY’
先说document.ready。它将具有获取消息的逻辑,并定义发送按钮的点击事件:
$(document).ready(function(){
getMessages()
$(’#send’).click(function(){
让我们保存按钮对象:
var $sendButton = $(this)
我们应该显示一个微调图像(“正在加载...”)按钮上,因为请求可能需要一些时间,我们希望用户看到我们的应用正在工作,而不是无缘无故地冻结。
$sendButton.html(’<img src="img/spinner.gif" width="20"/>’)
var username = $(’input[name=username]’).val()
var message = $(’input[name=message]’).val()
当我们提交一个新消息(POST 请求)时,我们用jQuery.ajax函数进行 HTTP 调用。在 api.jquery.com/jQuery.ajax 可获得ajax功能的完整参数列表。最重要的是 URL、头和类型参数。
$.ajax({
url: ’https://api.parse.com/1/classes/MessageBoardT2】
headers: {
’X-Parse-Application-Id’: parseAppID,
’X-Parse-REST-API-Key’: parseRestKey
},
contentType: ’application/json’,
数据的类型是 JSON:
dataType: ’json’,
processData: false,
data: JSON.stringify({
’username’: username,
’message’: message
}),
type: ’POST’,
success: function() {
console.log(’sent’)
假设我们的 POST 请求 Parse 保存了新消息(success),我们现在希望获得包含我们的消息的更新的消息列表,并使用文本替换微调图像,就像有人单击按钮之前一样:
getMessages()
$sendButton.html(’SEND’)
},
error: function() {
console.log(’error’)
$sendButton.html(’SEND’)
}
})
总的来说,单击 Send 按钮将向 Parse.com REST API 发送一个 POST 请求,然后在成功响应时,获得调用getMessages()函数的消息。
从我们的远程 REST API 服务器获取消息的getMessages()方法也使用了jQuery.ajax函数。该 URL 包含收藏集的名称(MessageBoard)和一个查询字符串参数,该参数将限制设置为 1,000:
function getMessages() {
$.ajax({
url: ’https://api.parse.com/1/classes/MessageBoard?limit=1000T2】
我们需要在头中传递密钥:
headers: {
’X-Parse-Application-Id’: parseAppID,
’X-Parse-REST-API-Key’: parseRestKey
},
contentType: ’application/json’,
dataType: ’json’,
type: ’GET’,
如果请求成功完成(状态200/ok或类似),我们调用updateView函数:
success: function(data) {
console.log(’get’)
updateView(data)
},
error: function() {
console.log(’error’)
}
})
}
然后,在成功响应时,它将调用updateView()函数,该函数清除表tbody,并使用$.each jQuery 函数( api.jquery.com/jQuery.each )遍历响应的结果。
该函数呈现我们从服务器获得的消息列表:
function updateView(messages) {
我们使用 jQuery 选择器.table tbody创建一个引用该元素的对象。然后我们清理该元素的所有 innerHTML:
var table=$(’.table tbody’)
table.html(’’)
我们使用jQuery.each函数遍历每条消息:
$.each(messages.results, function (index, value) {
var trEl =
以下代码以编程方式创建 HTML 元素(以及这些元素的 jQuery 对象):
(’<tr><td>’
+ value.username
+ ’</td><td>’
+ value.message +
’</td></tr>’)
从某种意义上来说,trEl是一个字符串,包含留言板中每条消息或每一行的 HTML。下一行将表的tbody元素追加到我们的行中:
table.append(trEl)
})
console.log(messages)
}
下面是使用 jQuery 动态创建 HTML 元素(例如,div)的另一种方法:
$(’<div>’)
以下是完整的app.js供您参考:
var parseAppID=’your-parse-app-id’
var parseRestKey=’your-rest-api-key’
$(document).ready(function(){
getMessages()
$(’#send’).click(function(){
var $sendButton = $(this)
$sendButton.html(’<img src="img/spinner.gif" width="20"/>’)
var username = $(’input[name=username]’).val()
var message = $(’input[name=message]’).val()
$.ajax({
url: ’https://api.parse.com/1/classes/MessageBoardT2】
headers: {
’X-Parse-Application-Id’: parseAppID,
’X-Parse-REST-API-Key’: parseRestKey
},
contentType: ’application/json’,
dataType: ’json’,
processData: false,
data: JSON.stringify({
’username’: username,
’message’: message
}),
type: ’POST’,
success: function() {
console.log(’sent’)
getMessages()
$sendButton.html(’SEND’)
},
error: function() {
console.log(’error’)
$sendButton.html(’SEND’)
}
})
})
})
function getMessages() {
$.ajax({
url: ’https://api.parse.com/1/classes/MessageBoard?limit=1000T2】
headers: {
’X-Parse-Application-Id’: parseAppID,
’X-Parse-REST-API-Key’: parseRestKey
},
contentType: ’application/json’,
dataType: ’json’,
type: ’GET’,
success: function(data) {
console.log(’get’)
updateView(data)
},
error: function() {
console.log(’error’)
}
})
}
function updateView(messages) {
var table=$(’.table tbody’)
table.html(’’)
$.each(messages.results, function (index, value) {
var trEl=(’<tr><td>’
+ value.username
+ ’</td><td>’
+ value.message
+ ’</td></tr>’)
table.append(trEl)
})
console.log(messages)
}
尝试使用本地 HTTP 服务器运行代码。您应该看到消息(显然,第一次应该没有消息),并通过单击按钮能够发布新消息。
如果您需要做的只是在本地机器上开发应用,这没问题,但是将它部署到云上呢?要做到这一点,我们首先需要用 Git 应用版本控制。
向您介绍项目部署的补充视频(Git 和 Heroku 部分从 9 分 57 秒开始): http://bit.ly/1SU8K4I 。
要创建 GitHub 存储库,请进入 github.com ,登录并创建一个新的存储库。会有一个 SSH 地址;复制它。在您的终端窗口中,导航到您想要推送到 GitHub 的项目文件夹。
Create a local Git and .git folder in the root of the project folder: $ git init Add all of the files to the repository and start tracking them: $ git add . Make the first commit: $ git commit -am "initial commit" Add the GitHub remote destination: $ git remote add your-github-repo-ssh-url It might look something like this: $ git remote add origin git@github.com:azat-co/simple-message-board.git Now everything should be set to push your local Git repository to the remote destination on GitHub with the following command: $ git push origin master You should be able to see your files at github.com under your account and repository.
稍后,当您对文件进行更改时,没有必要重复所有这些步骤。只需执行:
$ git add .
$ git commit -am "some message"
$ git push origin master
如果没有要开始跟踪的新的未跟踪文件,请使用以下命令:
$ git commit -am "some message"
$ git push origin master
要包括单个文件的更改,请运行:
$ git commit filename -m "some message"
$ git push origin master
要从 Git 存储库中删除文件,请使用:
$ git rm filename
有关更多 Git 命令,请参见:
$ git --help
使用 Windows Azure 或 Heroku 部署应用就像将代码和文件推送到 GitHub 一样简单。最后三个步骤(4–6)将替换为不同的远程目标(URL)和不同的别名。
使用此过程,您应该能够使用 Git 部署到 Windows Azure。
Go to the Windows Azure Portal at https://windows.azure.com/ 1, log in with your Live ID and create a web site if you haven’t done so already. Enable Set Up Git Publishing by providing a user name and password (they should be different from your Live ID credentials). Copy your URL somewhere. Create a local Git repository in the project folder that you would like to publish or deploy: $ git init Add all of the files to the repository and start tracking them: $ git add . Make the first commit: $ git commit -am "initial commit" Add Windows Azure as a remote Git repository destination: $ git remote add azure your-url-for-remote-repository In my case, this command looked like this: $ git remote add > azure https://azatazure@azat.scm.azurewebsites.net/azat.git Push your local Git repository to the remote Windows Azure repository, which will deploy the files and application: $ git push azure master
与 GitHub 一样,当您稍后更新文件时,没有必要重复前面的几个步骤,因为我们已经应该在项目文件夹的根目录中有一个以.git文件夹形式的本地 Git 存储库。
向您介绍项目部署的补充视频(Git 和 Heroku 部分从 9 分 57 秒开始): http://bit.ly/1SU8K4I 。
唯一的主要区别是 Heroku 使用 Cedar Stack,它不支持静态项目,包括普通的 HTML 应用,如我们的 Parse.com 测试应用或 Parse.com 版本的留言板应用。我们可以使用一个“假”的 PHP 项目来克服这个限制。在项目文件夹中创建一个与index.html处于同一级别的文件index.php,您希望将该文件发布或部署到 Heroku,其内容如下:
<?php``echo file_get_contents(’index.html’);T2】
为了方便起见,index.php文件已经包含在04-board-parse-rest中。
有一种更简单的方法可以用 Cedar Stack 在 Heroku 上发布静态文件,这在 Heroku Cedar 上的 post Static 站点中有所描述。heroku-cedar 河畔 static-sites。html 。要让 Cedar Stack 处理静态文件,您只需在项目文件夹中键入并执行以下命令:
$``touchT2】
$``echo``’php_flag engine off’``>T4】
或者,您可以使用 Ruby Bamboo 堆栈。在这种情况下,我们需要以下结构:
-project folder
-config.ru
/public
-index.html
-/css
app.js
...
index.html中到 CSS 和其他资产的路径应该是相对的,也就是’css/style.css’。config.ru文件应该包含以下代码:
use Rack::Static,
:urls => ["/stylesheets", "/images"],
:root => "public"
run lambda { |env|
[
200,
{
’Content-Type’ => ’text/html’,
’Cache-Control’ => ’public, max-age=86400’
},
File.open(’public/index.html’, File::RDONLY)
]
}
更多详情可以参考 devcenter.heroku.com/articles/static-sites-on-heroku 。
一旦您有了 Cedar Stack 或 Bamboo 的所有支持文件,请遵循以下步骤:
Create a local Git repository and .git folder if you haven’t done so already: $ git init Add files: $ git add . Commit files and changes: $ git commit -m "my first commit" Create the Heroku Cedar Stack application and add the remote destination: $ heroku create If everything went well, it should tell you that the remote has been added and the app has been created, and give you the app name. To look up the remote type and execute (optional): $ git remote show Deploy the code to Heroku with: $ git push heroku master Terminal logs should tell you whether or not the deployment went smoothly. To open the app in your default browser, type: $ heroku open or just go to the URL of your app, something like http://yourappname-NNNN.herokuapp.com . To look at the Heroku logs for this app, type: $ heroku logs To update the app with the new code, repeat the following steps only: $ git add -A $ git commit -m "commit for deploy to heroku" $ git push -f heroku
每次使用命令$ heroku create创建一个新的 Heroku 应用时,您都会被分配一个新的应用 URL。
根据 REST API,对象的更新通过PUT方法执行,删除通过DELETE方法执行。只要我们提供一个想要对其执行操作的对象的 ID,这两个操作都可以用我们对GET和POST使用的同一个jQuery.ajax函数轻松执行。
这一章很难。希望您对 JSON、AJAX 和跨域调用有所了解。记住,当访问服务器时,你需要确保它们支持 CORS 或 JSONP。
我们已经介绍了一些最基本的特性,并使用 Parse 来持久化数据。我们还使用 Git 版本系统将我们的应用部署到云中。