使用Express MongoDB開發一個完整MVC專案

germo發表於2021-09-09

先決條件

這個教程需要使用以下技術:

以及良好的網路條件,基礎的HTML,CSS和Javascript技能是完成教程的必備的。

c9使用者可以看這裡

如果你是使用,例如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會給出一個特定的連結格式如下:替代。另外c9使用8080埠代替3000埠。

安裝Node.js以及NPM

提醒:Node會隨著NPM一起安裝。

MAC OSX&Windows

進入。下載相應的檔案按照指導完成安裝。

Linux

選項1-透過PPA安裝

sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install node.js

選項2-透過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( ... )
是Express的一個方法,可以接受來自客戶端(browser)的要求和響應,使用res.send可以傳送訊息給browser。這是在Express中常見的引數。

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檔案。

檔案內容如下:

html>
    
        First Node App
    
    
        

Hello, world!

    

這是一個極其簡單的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檔案,讓其包含更多的內容以及互動性。這是相關的程式碼:

html>
   
      Clementine.js - The elegant and lightweight full-stack boilerplate.
   

   

    
         [站外圖片上傳中……(6)]         
         

Clementine.js

      
      
         

You have clicked the button  times.

         
         
                                   
      
   

在HTML中,我們新增了一些內容,你可以根據你的喜好執行新增。如果你想要新增Clementine logo,可以從新增。把檔案放在`/public/img資料夾中。

HTML中包含兩個div元素。上面的div中是一幅圖片和一段話,下面的div是包含一段話和兩個按鈕,一個是新增次數的按鈕,另外一個是重設的按鈕。我們的計劃就是將中的元素更新為按鈕點按的次數。

現在確認所有的內容都能執行良好。測試之後發現如下的介面:

圖片描述

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}}的意思是使用了方法。$inc方法找出要調整的資料clicks,使用提供的數字來使原來的數字+1。所以每一次使用addClick函式,這個數字的內容就會增加1。

之後我們使用了回撥函式如果出現問題的時候丟擲錯誤。沒有錯誤的時候就會以JSON格式顯示結果。

最後,我們在resetClicks方法中使用方法。之後的兩個引數,{}是返回所有內容,{'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 = 'api/clicks';
})();

新建控制器函式

首先當頁面載入的時候,我們要接受API返回的資料庫內容s。元素就是資料庫內容要顯示的位置。要做到這些,我們要先新建一個函式來檢視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 = '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 = '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函式。

下一步,我們寫一個小的函式,來更新元素。

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 = '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 = '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 = '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 = 'api/clicks';

   ...
   ...

   addButton.addEventListener( ... );

   deleteButton.addEventListener('click', function () {

      ajaxRequest('DELETE', apiUrl, function () {
         ajaxRequest('GET', apiUrl, updateClickCount);
      });

   }, false);
})();

最後,我們新增事件到reset按鈕。這和CLICK ME事件類似。

最後,我們需要在我們的HTML中填入這個控制器。

index.html:

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:

    Clementine.js - The elegant and lightweight full-stack boilerplate.

        

第一個是引用了Google字型,這不是必要的,但是我真的喜歡Roboto字型,第二個是和css檔案聯絡。

現在我們啟動我們的程式,就是如下的效果:

圖片描述

下一步

恭喜你完成了教程內容,如果你是初學者,建議你開始真正嘗試做一些東西,例如登入程式,留言程式。這樣會讓你理解更深,並能強制你學習到更多的東西。



作者:lvsjack1
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1868/viewspace-2802673/,如需轉載,請註明出處,否則將追究法律責任。

相關文章