为了账号安全,请及时绑定邮箱和手机立即绑定

使用Express MongoDB开发一个完整MVC项目

标签:
MongoDB

原文

[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.useexpress.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').MongoClientmongoClient()是可以使用类似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以及resetClicksgetClicks方法差不多。但是,其中每个方法都使用了不同的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(){...}:这里我们给onreadystatechangeassigning一个回调函数。每当readyState属性改变的时候,就会执行里面的函数。

这个函数会在每次readyState改变的时候执行。有很多种的readyState数值,这里我们时候用readyState === 4,这意味着操作(例如数据接收完成)已经完成。同样,我们要确保statusHTTP 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" class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="/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



点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消