[TOC]
先决条件
这个教程需要使用以下技术:
以及良好的网络条件,基础的HTML,CSS和Javascript技能是完成教程的必备的。
c9用户可以看这里
如果你是使用IDE,例如c9,你就不需要安装Node,要安装MongoDB,在命令行输入$ sudo apt-get install mongodb-org
,安装完成后,你需要输入以下的命令来运行MongoDB:
$mongod --smallfiles
这让mongod
服务运行起来,--smallfiles
参数是MongoDB默认使用小文件模式,对于IDE有文件限制来说,这个命令就很重要。
docs_c9_clemjs_setup04
之后,点击图中的+
,打开新的命令行界面,确保MongoDB在后台运行,每次在重新打开c9的时候都要记得运行mongodb。
最后,大部分的教程使用1ocalhost:3000
打开,c9会给出一个特定的链接格式如下:https://projectname-username.c9.io/
替代http://localhost:3000/
。另外c9使用8080
端口代替3000
端口。
安装Node.js以及NPM
提醒:Node会随着NPM一起安装。
MAC OSX&Windows
进入Node.js install page。下载相应的文件按照指导完成安装。
Linux
选项1-通过PPA安装
sudo add-apt-repository ppa:chris-lea/node.js sudo apt-get update sudo apt-get install node.js
选项2-通过LinuxBrew
首先,确定LinuxBrew已经安装,之后,输入下面的命令:
$ brew install node
安装MongoDB
MongoDB的安装可以看这里。
NPM包安装
以上步骤完成后,下一步就是安装程序要使用的Node包文件。
第一步就是建立package.json
文件,这是一个存放所有和程序有关的信息文件的集合。
在命令行界面输入$ npm init
,经过一系列的问题之后,在项目根目录下就生成了package.json
文件。如果你不知道这些问题的答案,直接一路enter下去就行。下面是类似的文件:
{ "name": "beginner-app", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "MIT"}
你可以任意修改这个文件,最重要的事情就是将main
对应为server.js
。
现在我们初始化NPM之后,我们开始来安装NPM包。
在命令行界面输入:$npm install express mongodb --save
这个命令用来安装Express和MongoDB,之后你可能注意到出现一个新的文件夹叫node_modules
。这就是存放Node包的本地文件夹。
--save
是将包依赖内容添加到package.json
文件。如果你打开这个文件,将会是以下的内容:
{ "name": "beginner-app", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "MIT", "dependencies": { "express": "^4.12.4", "mongodb": "^2.0.33" } }
关于Express
Express是一个node框架,用来为node生成web程序提供更多的功能性。框架一般意味着使用另外的技术书写,提供额外的功能。本质上,Express就是为Node提供了一系列非常有用的功能。
如果不使用Express,开发者不得不在每开始一个新的项目而写重复的代码。另外,Express没有自以为是的部署程序的实施。如果有要举一个自以为是的框架名就是--Ember.js。
更多的信息,查看website and documentation。
关于MongoDB
MongoDB我们都知道是一个文档存放的数据库,每一条记录都存放在一个单独的“文档“中,这种数据库类型叫做NoSQL数据库,是没有适用SQL数据结构的数据库。
SQL数据库是数据库的老字号。最基本的例子例如MySQL以及PostgreSQL。NoSQL数据库是SQL数据库的替代。
MongoDB是MEAN组合中的一部分,可以方便的使用Javascript语句操作数据库。
更多的关于MongoDB的数据,可以看这里。如果你掌握了Node,我建议你学习这门课程。
MongoDB Node.js可以让我们使用Node操作MongoDB数据库。
.gitignore
我们经常会在项目的根目录中看到.gitignore
文件,这个文件是告诉git(版本控制软件)所要忽略的文件。如果你没有这个文件,可以新建一个,使用$ touch .gitignore
命令新建,touch
命令是用来更新或者调整文件,或者当文件不存在的时候也能用来新建一个新的文件。
很多时候,项目中包含了node_modules
文件夹。使用.gitignore
可以阻止向类似GitHub上传这个文件夹。在.gitignore
文件中添加:
node_modules/
建立文件结构
现在让我们花点时间看一下文件结构。
+-- Project Folder +-- app | \-- controllers | \-- routes | +-- public | \-- css | \-- img
项目文件或者根文件夹包含:
.gitignore文件
package.json文件
app文件夹包含:
controllers文件夹 —— 用来存放操作数据或者服务端的控制器。
routes文件夹 —— 这个文件包含了根据路径显示不同内容的路由文件。
public文件夹:
css —— 包含程序样式
img —— 包含照片文件
程序结构
在开始写程序之前,最好我们对程序结构有一定的认识,要知道代码之间都是怎么联系在一起的。我们现在使用的是MVC架构。
这是一种常见的网站程序架构。Model负责管理程序中的数据逻辑关系,View用来表现页面,Controller在页面和Model之间帮助管理数据,将最后的结果送给页面。
这是图例:
更多的关于MVC的内容可以看这里。
在这个教程中,我们准备建立一个具有以下功能的小型程序:
记录按钮被按下的次数
在数据库中储存
重新设置计数为0
在程序的内容中,我们要做到以下:
Node/Express网站服务用来响应HTTP要求,以及传文件给浏览器。
MongoDB数据库用来存储点击的次数。
服务端controller(控制器)可以添加,可以重设,可以接受来自数据库的记录
数据将会传给API
客户端控制器使用暴露的API能够根据用户的输入响应不同的内容。
客户端能都给用户显示界面
HTML页面要包含一个logo以及用户交互的按钮。
以上就是简单的网站内容。
简单的Node服务器
开始我们的程序,首先建立一个简单的Node服务器。这将是我们程序的基础,根据这个基础我们在上面加内容。
在项目根目录新建server.js
文件。在文件中输入:
'use strict';var express = require('express');var app = express(); app.get('/', function (req, res) { res.send('Hello world!'); }); app.listen(3000, function () { console.log('Listening on port 3000...'); });
我们现在来逐条分析代码:use strict
这个代码的意思就是打开Javascript的"strict mode"。
var express = require('express');
这是node语法,意思是引用依赖Express。我们将其保存成变量,以备之后的需要。
var app = express();
这段是将Express实例化,这样可以使用app
这个变量获得express的功能。Express有很多很有用的方法可以使网站程序更快实现。
app.get( ... )
App.get是Express的一个方法,可以接受来自客户端(browser)的要求和响应,使用res.send
可以发送消息给browser。这是在Express中常见的参数。
app.listen( ... )
app.listen是用来告诉Node要监听的端口。例子中,我们使用了一个回调函数,来告诉我们程序已经运行起来了。
现在我们开始测试程序。在命令行界面输入$ node server.js
。你就能看到以下内容:
$ node server.js Listening on port 3000...
很好!
现在,打开浏览器输入localhost:3000
。在浏览器中你应该能看到Hello, world!
消息。
现在我们需要在每次修改完Node服务器后重启,我们可以按在命令行界面按Ctrl+C
停止服务。输入$ node server
开始。
我们来更近一步,给浏览器发送一个HTML文件。在项目文件夹,新建一个index.html
文件。
文件内容如下:
<!DOCTYPE html><html> <head> <title>First Node App</title> </head> <body> <p>Hello, world!</p> </body></html>
这是一个极其简单的HTML文件,但重要的是我们在向浏览器发送文件,而不仅仅是消息。
我们来更新server.js
文件。
之前:
app.get('/', function (req, res) { res.send('Hello world!'); });
之后:
app.get('/', function (req, res) { res.sendFile(process.cwd() + '/index.html'); });
首先,我们告诉Exprss我们要发送一个文件给浏览器,之后我们告诉要发送文件的位置以及名字:res.sendFile(process.cwd() + '/index.html')
;Node的process.cwd()
方法是显示现在的工作路径,让我们和文件一起拼成字符串。这样让Node能都找到文件。
现在我们测试程序是否工作正常。在命令行界面$ node server.js
。
浏览器中打开localhost:3000
,你应该能看到“Hello, world!“
配置路由
下一步是配置路由,路由是Express或者Node中最普遍的模式。网站程序中包含了大量的路由信息(HTTP要求到server),一般要将归类他们保存在不同文件中。这就是我们教程中目标其中之一。
从新建在/app/routes
文件夹中index.js
开始,这个文件将存放我们的路由文件。
删除server.js
文件中的这部分内容:
app.get('/', function (req, res) { res.sendFile(process.cwd() + '/index.html'); });
下一步,添加新的依赖到server.js
。
server.js:
'use strict';var express = require('express'), routes = require('./app/routes/index.js');var app = express(); ... ...
我们将路由文件传递到routes
的函数对象。我们之后会暴露我们的路由,这个函数接受一个参数app
,这样可以在routes
函数中使用Express中的方法内容。
代码显示如下:
routes(app);
server.js
看起来如下:
'use strict';var express = require('express'), routes = require('./app/routes/index.js');var app = express(); routes(app); app.listen(3000, function () { console.log('Listening on port 3000...'); });
现在到了添加index.js
文件的时候了。我们使用module.exports
方法将这个函数扩展到其他的Node文件(例如server.js文件)。这个函数接受一个参数(app),也就是Expres app。
index.js:
'use strict';module.exports = function (app) { app.route('/') .get(function (req, res) { res.sendFile(process.cwd() + '/public/index.html'); }); };
一些内容看起来有点眼熟,但是我们要知道我们使用的是Express的路由方法app.route
。这个是app.get的替代,将所有类型的routes绑到一个页面中。剩下的代码和以前使用app.get
的代码一模一样。
下一步,将index.html文件放到/public
文件夹中。这将是这个文件的固定位置了。
之后我们测试一下效果,运行$ node server.js
。
打开浏览器中localhost:3000
,你能看到熟悉的`Hello, world!"。
下面我们添加点内容到HTML文件当中。
添加元素到index.html文件中
在这节中,我们要更新我们的HTML文件,让其包含更多的内容以及交互性。这是相关的代码:
<!DOCTYPE html><html> <head> <title>Clementine.js - The elegant and lightweight full-stack boilerplate.</title> </head> <body> <div class="container"> [站外图片上传中……(6)] <br /> <p class="clementine-text">Clementine.js</p> </div> <div class="container"> <p>You have clicked the button <span id="click-nbr"></span> times.</p> <br /> <div class="btn-container"> <button type="submit" class="btn btn-add">CLICK ME!</button> <button class="btn btn-delete">RESET</button> </div> </div> </body></html>
在HTML中,我们添加了一些内容,你可以根据你的喜好执行添加。如果你想要添加Clementine logo,可以从this GitHub page添加。把文件放在`/public/img文件夹中。
HTML中包含两个div
元素。上面的div中是一幅图片和一段话,下面的div是包含一段话和两个按钮,一个是添加次数的按钮,另外一个是重设的按钮。我们的计划就是将<span>中的元素更新为按钮点按的次数。
现在确认所有的内容都能运行良好。测试之后发现如下的界面:
clemjstut01
照片没有被载入,好吧,当Node想要进入/public/img/
文件夹,没有任何方式方法进入这个相对路径。
我们可以在server.js
文件中解决的方法:
'use strict'; ... app.use('/public', express.static(process.cwd() + '/public')); app.use('/controllers', express.static(process.cwd() + '/app/controllers')); routes(app); ...
这里使用了Express的app.use
和express.static
将/public
绑定到了/public
。现在当/public
被引用的时候,node就知道文件的地方了。
现在重启服务,打开localhost:3000
都显示出来了。
连接MongoDB
在客户端和数据库之间传递数据要使用API。API是前端和数据之间的连接方式。
首先,我们先设置MongoDB的数据库。在文件server.js
中,我们要做一些修改。
'use strict';var express = require('express'), routes = require('./app/routes/index.js'), mongo = require('mongodb').MongoClient;var app = express(); mongo.connect('mongodb://localhost:27017/clementinejs', function (err, db) { if (err) { throw new Error('Database failed to connect!'); } else { console.log('MongoDB successfully connected on port 27017.'); } app.use('/public', express.static(process.cwd() + '/public')); app.use('/controllers', express.static(process.cwd() + '/app/controllers')); routes(app, db); app.listen(3000, function () { console.log('Listening on port 3000...'); }); });
这都是一些小的修改,最明显的是,之前所有的代码都被mongo.connect
函数包围。以及更上面的引入MongoDBrequire('mongodb').MongoClient
。mongoClient()
是可以使用类似connect
方法的对象。
同样的,在使用数据库之前,将Express实例化也是很重要的。例子中,mongo.connect之前我们实例化了express。
接下来,我们使用connect
方法连接数据库和MongClient对象。其中,第一个参数是字符串的形式,表示数据库的地址,端口27017是MongoDB的默认使用端口,这个端口也能随意更改。clementinejs
是数据库的名称,如果数据库中不存在,MongoDB就会新建一个。
第二个参数是一个回调函数,这个函数有两个参数,一个是错误,另外一个是数据库对象。
之后是连接数据库的时候显示错误,可以使用throw new Error(...)
抛出错误。
如果没有错误,就会在命令行显示'MongoDB successfully connected on Port 27017'的内容。余下的内容和之前都一样。
routes(app, db);
和参数app
一样,我们要向路由传递我们的数据库对象。
现在可以测试程序是否运行正常了。一切正常的话,命令行就会显示一条连接成功的消息提示。
设置服务端控制器
就像客户端控制器可以将数据在客户端(浏览器)和API之间传递,我们同样需要服务器端的控制器传递数据。
这个控制器可以查询数据,可以更新结果,而客户端的控制器会直接在浏览器中更新API结果。
我们开始建造服务端的控制器。首先在/app/controllers
文件夹中新建clickHandler.server.js
文件。
clickHandler.server.js:
'use strict';function clickHandler (db) { var clicks = db.collection('clicks'); this.getClicks = function (req, res) { var clickProjection = { '_id': false }; clicks.findOne({}, clickProjection, function (err, result) { if (err) { throw err; } res.json(result); }); }; }module.exports = clickHandler;
再一次,我们看见了一些熟悉的代码。首先定义了一个MongoDB collection。这样就能在数据库中使用。Collection就像是SQL数据库中的表格,一个数据库中可以有很多的collection。如果数据库中没有相应的collection,MongoDB会新建一个collection。
就像程序中那样,我们的collection的名字就是clicks
,之后,我们要新建一个从数据库中取得现在点击次数的方法。这个方法命名为getClicks()
。
现在我们来看看getClicks
方法中的内容:
function(req, res) —— 这和前面讲的内容一样,接受命令和响应作为函数参数的一个函数。
clicks —— 这是数据库中collection的名称,是我们上面通过
var clicks = ...
获得的。var clickProjection ...
每一个mongodb数据库中的document都有一个独一无二的_id
,除非是自己指定,一般数据库会自动生成,这样我们可以通过id来获取数据,我们先不去设置id,这个例子中,得出的结果中我们不想包含id内容,所以我们将id设置为false。.findOne —— 这是MongoDB获取内容的方法。我们也能使用
find()
方法,鉴于我们的数据库中只有一个document,所以没有必要。{}
,这是findOne()
方法要搜索的内容。如果我们有很多不同内容的document,我们要在这里指定要过滤出来的内容。clickProjection
—— 上面已经定义过的,过滤结果的参数。function(err, result){...}
findOne方法的回调函数,用来处理错误和结果。res.json(reslut)
—— 用JSON形式将数据发送给浏览器。
这里可不少新的内容。最后,我们将函数暴露出去。
我们的新的服务端的控制器就已经能用了,但是这有一个问题,假如数据库中没有相应的doucument怎么办?没关系,MongoDB是很智能的,它会自动生成相应的数据库collections,但是document一定要特别指定。如果这是第一次使用数据库,那里面一定没有collection,我们要让这个控制器更加完善。
现在我们来更新getClick()
方法:
this.getClicks = function (req, res) { var clickProjection = { '_id': false }; clicks.findOne({}, clickProjection, function (err, result) { if (err) { throw err; } if (result) { res.json(result); } else { clicks.insert({ 'clicks': 0 }, function (err) { if (err) { throw err; } clicks.findOne({}, clickProjection, function (err, doc) { if (err) { throw err; } res.json(doc); }); }); } }); };
首先确认findOne()
能否有数据返回,如果有数据返回if(result){...}
就传递给浏览器。
如果没有数据返回,我们就要在数据库中插入数据。数据包含两个参数,{clicks: 0}
,以及一个回调函数。这个回调函数是用来处理结果。如果过程中有错误发生,就会抛出错误。如果完成数据插入,我们就会在数据库中查找最新的数据并在浏览器中用JSON格式显示出来。
测试之前,我们再来做点改变。
index.js:
'use strict';var ClickHandler = require(process.cwd() + '/app/controllers/clickHandler.server.js');module.exports = function (app, db) { var clickHandler = new ClickHandler(db); app.route('/') .get(function (req, res) { res.sendFile(process.cwd() + '/public/index.html'); }); app.route('/api/clicks') .get(clickHandler.getClicks); };
我们来深入看下:
var ClickHandler...
, 这里我们使用一个变量储存函数对象。var clickHandler = new ClickHandler(db)
,这里我们实例化了函数对象,并向其传递了MongoDB对象作为参数。这样我们就能使用文件clickHandler.server.js
中的内容以及数据库中的内容。app.route('/api/clicks')
——定义了一个新的路由.get(clickHandler.getclicks)
——当路径为api/clicks
的HTTP GET的时候,所要执行的getClicks
函数。
接下来,我们开始测试。如果你是跟着教程做的,那么现在的数据库中应该是空的。浏览器中输入localhost:3000/api/clicks
。载入完成后,你就能看见[{"clicks":0}]
的内容。这意味着,所有的设置应该是正确的。
通过MongoDB终端测试API
如果想要使用终端再测试一次,我们可以使用MongoDB终端来进行手动测试。保持Node运行中,打开新的终端,输入$ mongo
连接MongoDB。
如果连接成功,你就能看见:
Mongo Shell Version: 3.0.3connecting to: test>
之后输入use clementinejs
,命令行会提示使用clementinejs。之后输入db.clicks.find({})
。这个命令会找出所有clicks collection中的数据。你就能看到以下的结果:
{ "_id": ObjectId(randomNumber), "clicks": 0 }
现在,我们来删除document。在终端输入db.click.remove({})
。这样会删除所有在collection中的内容。如果回到浏览器中刷新页面,新的内容就会又加入到数据库中。
添加新的方法和路由
我们现在能查询并且将数据库内容返回。但是,我们需要提供路由和逻辑告诉程序当HTML按钮按下的时候要做哪些事情。也就是说我们要添加相应的功能来响应按钮被按下之后的事情。
现在来更新我们的控制器文件。
clickHandler.server.js:
this.addClick = function (req, res) { clicks .findAndModify( {}, { '_id': 1 }, { $inc: { 'clicks': 1 } }, function (err, result) { if (err) { throw err; } res.json(result); } ); };this.resetClicks = function (req, res) { clicks .update( {}, { 'clicks': 0 }, function (err, result) { if (err) { throw err; } res.json(result); } ); };
这两个方法,addClick
以及resetClicks
和getClicks
方法差不多。但是,其中每个方法都使用了不同的MongoDB方法。
addClick
使用了findAndModify
方法。前两个参数和findOne
中使用的方法一样。{}
是要返回所有的数据。_id:1
是使用排序,但在这个例子中,因为只有一种数据,所以排序的关系不是很大。
{$inc: {'click': 1}}
的意思是使用了Mongo的 $inc方法。$inc方法找出要调整的数据clicks
,使用提供的数字来使原来的数字+1。所以每一次使用addClick
函数,这个数字的内容就会增加1。
之后我们使用了回调函数如果出现问题的时候抛出错误。没有错误的时候就会以JSON格式显示结果。
最后,我们在resetClicks方法中使用Mongo的update方法。之后的两个参数,{}
是返回所有内容,{'click': 0}
是要更新的内容。整了理解就是resetClick方法更新了clicks的内容为0。最后将结果传回到浏览器。
以下是所有的代码内容:
clickHandler.server.js:
function clickHandler (db) { var clicks = db.collection('clicks'); this.getClicks = function (req, res) { var clickProjection = { '_id': false }; clicks.findOne({}, clickProjection, function (err, result) { if (err) { throw err; } if (result) { res.json(result); } else { clicks.insert({ 'clicks': 0 }, function (err) { if (err) { throw err; } clicks.findOne({}, clickProjection, function (err, doc) { if (err) { throw err; } res.json(doc); }); }); } }); }; this.addClick = function (req, res) { clicks.findAndModify({}, { '_id': 1 }, { $inc: { 'clicks': 1 }}, function (err, result) { if (err) { throw err; } res.json(result); }); }; this.resetClicks = function (req, res) { clicks.update({}, { 'clicks': 0 }, function (err, result) { if (err) { throw err; } res.json(result); }); }; }module.exports = clickHandler;
最后,将方法添加到路由件中:
index.js:
app.route('/api/clicks') .get(clickHandler.getClicks) .post(clickHandler.addClick) .delete(clickHandler.resetClicks);
当HTTP GET/api/clicks
服务器就会调用getClicks
方法。同样的POST和DELETE也会根据方法同样响应。
这些都是服务端的内容,现在我们看看客户端的控制器。
添加客户端控制器
客户端的控制器会根据API的数据来响应,同时显示在页面。就是显示当用户点击按钮的时候,页面给出的反馈。
我们从细节着手:
当页面载入的时候,同时载入'click'的次数
当‘click me’按钮按下的时候,发送一个请求给API,并更新数据。
当‘REST’按钮按下的时候,发送DELETE响应,并更新数据
还记得我们在服务端控制器中发送POST的时候更新'clicks',发送DELETE的时候,更新"clicks"为0。
新建控制器
在/app/controllers文件夹中新建一个
clickController.client.js`文件。文件中,我们用括号括起来函数表示使用IIFE(声明后立即执行)方法。
clickController.client.js:
(function(){ })();
IIFE将所有的变量绑定在函数内,这样就不会与程序中其他的变量有命名空间的冲突。
下一步,我们使用Javascript来获取HTML内容。这里我们使用document.querySelector(cssSelector)
方法。
clickController.client.js:
'use strict'; (function () { var addButton = document.querySelector('.btn-add'); var deleteButton = document.querySelector('.btn-delete'); var clickNbr = document.querySelector('#click-nbr'); var apiUrl = 'http://localhost:3000/api/clicks'; })();
新建控制器函数
首先当页面载入的时候,我们要接受API返回的数据库内容s。<apan>
元素就是数据库内容要显示的位置。要做到这些,我们要先新建一个函数来查看DOM是否已经载入,之后执行另外一个函数。
clickController.client.js:
'use strict'; (function () { var addButton = document.querySelector('.btn-add'); var deleteButton = document.querySelector('.btn-delete'); var clickNbr = document.querySelector('#click-nbr'); var apiUrl = 'http://localhost:3000/api/clicks'; function ready (fn) { if (typeof fn !== 'function') { return; } if (document.readyState === 'complete') { return fn(); } document.addEventListener('DOMContentLoaded', fn, false); } })();
现在我们拆开来看这段代码的意思。我们新建了一个名为ready
的函数,函数使用一个参数-fn
。之后使用typeof
验证fn
是否是一个函数,如果不是一个函数就返回,不做任何动作。
之后,如果文档的readyState
属性为complete
,我们就执行参数函数,并返回。
最后,如果文档没有载入,我们就添加一个时间监听器document.addEventListener(type, listener, useCapture)
。这个方法使用3个参数:
type
: 表示监听事件的名称,这里是DOMContentLoaded
事件.listener
:事件发生后执行的函数。userCapture
表示当发起捕获的时候,只要DOM树下发现了该事件类型,都会先派发到该注册监听器,然后再派发到Dom树中的注册监听器。如果没有指定,默认值为false。
接下来制作一个接受API数据的函数。我们要使用XMLHttpRequest
,这个对象可以让我们在不改变整个页面的情况下,改变内容。这又叫做AJAX,是一个非常方便的方法。
clickController.client.js:
'use strict'; (function () { var addButton = document.querySelector('.btn-add'); var deleteButton = document.querySelector('.btn-delete'); var clickNbr = document.querySelector('#click-nbr'); var apiUrl = 'http://localhost:3000/api/clicks'; function ready (fn) { ... } function ajaxRequest (method, url, callback) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { callback(xmlhttp.response); } }; xmlhttp.open(method, url, true); xmlhttp.send(); } })();
我们再一点点看里面的内容:
function ajaxRequest(method, url, callback){...}
:函数中有3个参数:
method
是http request。url
是HTTP request的地址callback
是当数据接收之后的回调函数
var xmlhttp = new XMLHttpRequest()
:XMLHTTPRequest的实例化。
xmlhttp.onreadystatechange = function(){...}
:这里我们给onreadystatechange
assigning一个回调函数。每当readyState
属性改变的时候,就会执行里面的函数。
这个函数会在每次readyState
改变的时候执行。有很多种的readyState
数值,这里我们时候用readyState === 4
,这意味着操作(例如数据接收完成)已经完成。同样,我们要确保status
(HTTP status code)的数值是200
,这意味着request状态正常。
如果这两个条件都满足,就执行其中的函数。同时将xmlhttp.response
属性作为参数传递给函数。这个response
是AjAX request中得出的数据。
现在,我们已经写好了函数。当函数第一次被执行的时候,我们要初始化request。xmlhttp.open(method, url, async)
就是做这件事情的。这个方法有3个参数:
method: HTTP方法,这里我们使用
ajaxRequet
函数的参数。url: HTTP方法所要的url。
async: request要同步执行,还是异步执行,这里我们使用
true
。
最后,xmlhttp.send()
方法执行了上面初始化的request。这样,你就写了一个AJAX函数。
下一步,我们写一个小的函数,来更新<apan>
元素。
clickController.client.js:
'use strict'; (function () { var addButton = document.querySelector('.btn-add'); var deleteButton = document.querySelector('.btn-delete'); var clickNbr = document.querySelector('#click-nbr'); var apiUrl = 'http://localhost:3000/api/clicks'; function ready (fn) { ... } function ajaxRequest (method, url, callback) { ... } function updateClickCount (data) { var clicksObject = JSON.parse(data); clickNbr.innerHTML = clicksObject.clicks; } })();
这个函数是非常重要的一个函数。看到函数中的参数使用了data
,这个data
是上面xmlhttp.response
参数。AJAX request发出HTTP requst,之后从API返回带有数值的字符串。
有一点不好的地方就是,我们希望返回的数据是对象而不是字符串,这样我们方便的使用数据,就像从API得出的数据{'click': 0}
。
我们使用JSON.parse()
方法将data
参数转化为JSON对象。我们使用clicksObject储存变量。
下一步,我们将clickNbr
(前面定义的)元素内容改变为上面对象中的数值。这里我们使用了.innerHTML
。
下一步,我们定义当页面载入的时候以及按钮按下的时候的事件。
监听事件
首先,在页面中载入的时候显示获得的数值。我们就要使用ready
函数。ready函数参数是另外一个函数。
clickController.client.js:
'use strict'; (function () { var addButton = document.querySelector('.btn-add'); var deleteButton = document.querySelector('.btn-delete'); var clickNbr = document.querySelector('#click-nbr'); var apiUrl = 'http://localhost:3000/api/clicks'; function ready (fn) { ... } function ajaxRequest (method, url, callback) { ... } function updateClickCount (data) { ... } ready(ajaxRequest('GET', apiUrl, updateClickCount)); })();
现在,我们执行ready
函数,参数为Ajax函数,这个函数的参数为apiUrl
,以及updateClickCount
。
同样的,我们用同样的方法为按钮添加方法。先从CLICK ME
按钮开始。
clickController.client.js:
'use strict'; (function () { var addButton = document.querySelector('.btn-add'); var deleteButton = document.querySelector('.btn-delete'); var clickNbr = document.querySelector('#click-nbr'); var apiUrl = 'http://localhost:3000/api/clicks'; ... ... addButton.addEventListener('click', function () { ajaxRequest('POST', apiUrl, function () { ajaxRequest('GET', apiUrl, updateClickCount) }); }, false); })();
上面的代码应该有点熟悉,我们绑定一个事件监听到addButton
元素,监听click
事件。当事件发生的时候,运行函数。这个函数将会POST一个AJAX request,也就是增加一次click的数字。整个过程一旦完成,GET request会更新页面的数据。
下一步,我们同样添加一个类似的事件监听到RESET
按钮。
clickController.client.js:
'use strict'; (function () { var addButton = document.querySelector('.btn-add'); var deleteButton = document.querySelector('.btn-delete'); var clickNbr = document.querySelector('#click-nbr'); var apiUrl = 'http://localhost:3000/api/clicks'; ... ... addButton.addEventListener( ... ); deleteButton.addEventListener('click', function () { ajaxRequest('DELETE', apiUrl, function () { ajaxRequest('GET', apiUrl, updateClickCount); }); }, false); })();
最后,我们添加事件到reset
按钮。这和CLICK ME
事件类似。
最后,我们需要在我们的HTML中填入这个控制器。
index.html:
<!DOCTYPE html><html> <head> ... </head> <body> <div class="container"> ... </div> <div class="container"> ... </div> <script type="text/javascript" src="/controllers/clickController.client.js"></script> </body></html>
现在可以测试程序了,输入node server
,打开localhost:3000
,看看是否能工作正常。
页面现在看起来不好看,我们可以添加一些好看的颜色!
添加CSS样式
教程就快进入尾声了。
再在/public/css
文件夹新建一个main.css
文件来修改样式。
mian.css:
/****** Main Styling ******/body { font-family: 'Roboto', sans-serif; font-size: 16px; }p { margin: 8px 0 0 0; }.container p { text-align: center; padding: 0; }/****** Logo Div Styling ******/img { margin: 20px auto 0 auto; display: block; }.clementine-text { /* Styling for the Clementine.js text */ padding: 0; margin: -25px 0 0 0; font-weight: 500; font-size: 60px; color: #FFA000; }/****** Click Styling ******/.btn-container { /* Styling for the div that contains the buttons */ margin: -10px auto 0 auto; text-align: center; }.btn { /* Styling for buttons */ margin: 0 8px; color: white; background-color: #00BCD4; display: inline-block; border: 0; font-size: 14px; border-radius: 3px; padding: 10px 5px; width: 100px; font-weight: 500; }.btn:focus { /* Remove outline when hovering over button */ outline: none; }.btn:active { /* Scale the button down by 10% when clicking on button */ transform: scale(0.9, 0.9); -webkit-transform: scale(0.9, 0.9); -moz-transform: scale(0.9, 0.9); }.btn-delete { /* Styling for delete button */ background-color: #ECEFF1; color: #212121; }
现在把它和HTML文件合为一体。
index.html:
<head> <title>Clementine.js - The elegant and lightweight full-stack boilerplate.</title> <link href="http://fonts.googleapis.com/css?family=Roboto:400,500" rel="stylesheet" type="text/css"> <link href="/public/css/main.css" rel="stylesheet" type="text/css"></head>
第一个<link>是引用了Google字体,这不是必要的,但是我真的喜欢Roboto字体,第二个<link>
是和css文件联系。
现在我们启动我们的程序,就是如下的效果:
下一步
恭喜你完成了教程内容,如果你是初学者,建议你开始真正尝试做一些东西,例如登入程序,留言程序。这样会让你理解更深,并能强制你学习到更多的东西。如果你喜欢这篇文章,请Twitter。
资源
MongoDB
Express
Node.js