Ember.js如何與後端服務交互?adapter、store、ember data關係揭祕

文章來源:Ember Teachjavascript

本項目講解如何使用adapter、EmberData以及怎麼鏈接到本地數據庫。html

項目簡介

主要內容

  • 適配器使用前端

  • 如何持久化數據到本地數據庫java

  • 簡單的後端服務node

最近常常有初學的開發者請教有關Adapter或者Ember Data的問題。官方教程中講到這兩個內容的是Model這一章節。本文中介紹到的內容大部分是由這一章來的,若是有不妥請看原文或者給我留言。mysql

注意:本文是基於v2.6.0講解。git

軟件需求

  1. MySQLgithub

  2. nodejs,expressweb

  3. body-parsersql

  4. mysql-node

Ember項目常規運行軟件

用到的軟件、插件都是有關後端服務的,mysql-node用於鏈接、操做MySQL數據庫。後端服務是用node寫的因此也用node項目的插件鏈接、操做數據庫了,有關如何使用node操做MySQL的信息請看這篇文章[nodejs鏈接MySQL,作簡單的CRUD
](http://blog.ddlisting.com/201...。若是你的後端是其餘語言寫只須要保證你後端返回的數據格式或者個人後端返回的數據格式一致就好了。目前打算本項目使用2種數據交互方式:一種是jsonapi,一種是restapi。

項目搭建

項目的搭建就再也不費口舌了,Ember Teach已經有不少博文介紹過了。

運行項目

若是你想運行本項目請按照下面的步驟操做:

安裝

  • 下載代碼到本地 git clone https://github.com/ubuntuvim/emberData-adapter-database

  • 進入項目目錄 cd emberData-adapter-database

  • 安裝npm依賴包 npm install

  • 安裝bower依賴包 bower install

運行

  • 在項目目錄下執行命令 ember server 運行項目。

  • 待項目啓動完畢,在瀏覽器打開http://localhost:4200

發佈到服務器

  • 執行命令編譯、打包項目 ember build --environment production

  • 命令執行完畢會在dist目錄下獲得項目打包後的文件。

  • 把打包後的dist目錄下的全部文件複製到服務器應用目錄下運行便可(好比tomcat服務器則放到webapps目錄下)。

項目結構

簡單起見我就作一個頁面就好了,我但願作出的效果是使用自定義的適配器獲取到本地MySQL數據庫的數據並分頁展現。

建立文件

使用ember-cli命令建立文件。

ember g route users
ember g model user username:string email:string
ember g adapter application

目前暫時只用到這幾個文件,後續可能還有其餘的用到在建立。
ember g model user username:string email:string的做用是建立模型的同時建立2個屬性,而且屬性都指定爲string類型。

說了一大堆廢話下面開始正題。要理解adapterember data、後端服務的關係咱們從他們各自的概念入手。首先咱們先理清楚他們之間的關係而後在動手實踐。理論老是繁瑣的可是也是最重要的。

========================= 華麗的分割線 =========================

體系結構概述

體系結構圖

注:圖片來自官方文檔

注意觀察上圖的結構。

  1. APP(通常是從routecontroller或者component發請求)請求數據。

  2. 請求並無直接發送到後端服務而是先在store(ember data其實就是一個store)緩存中查找,ember之因此能實現動態更新模板數據也是由於有了store

  3. 若是請求的數據存在在store中,則直接返回到routecontroller或者component;若是在store中沒有發現請求的數據,因此請求的數據是首次,數據還未緩存到store中,則請求繼續往下到了apdater層。

  4. adapter中,adapter會根據請求的調用方法構建出對應的URL。好比在routecontroller或者component中執行方法findRecord('user', 1),此方法做用是查詢id爲1的user數據。適配器構建出來的URL爲: http://domain/user/1,而後發請求到後端。

  5. 適配器會對比後端接受的數據格式與ember data發送的數據格式,若是不一致須要在適配器的``方法中格式化發送的數據格式。請求通過適配器構建獲得URL後發送到後端服務,後端服務根據URL請求查詢數據庫而後格式化數據格式返回到適配器。

  6. 適配器根據獲得的數據和ember data所接受的數據格式匹配,若是格式不一致須要在適配器的``方法中格式化後端返回的數據。

  7. 通過適配器以後數據轉到ember data(store)中,首先緩存到store中,而後返回到調用處(routecontrollercomponent

  8. 數據請求完畢

注意:findRecord('user', 1)方法執行過程,請求的findRecord('user', 1)方法會在Ember Data內部解析爲find方法,find方法會首先在store緩存中查數據,若是沒有則會流轉到adapter中組裝URL並格式化請求數據,而後發送到後端服務。

從圖中看到從適配器返回的數據是promise因此調用findRecord方法獲取數據的時候須要then()。同時可見這是個移步請求,只有promises執行成功才能獲得數據。也就是說若是考慮周全的話還須要在findRecord的時候處理promises執行失敗的狀況。

另外若是你想跳過store不須要這層緩存也是能夠的。會能夠這樣作:store.findRecord(type, id, { reload: true })使用reload屬性設置爲true讓每次請求都跳過store直接發送請求到後端,對於實時性要求高的APP則須要這樣處理。

介紹完架構以後將追個介紹其中的每一個主要的功能特性。
須要說明的是:Models, records, adapters以及store都是Ember Data最核心的東西,他們是包含的關係,只要使用了Ember Data才能使用modelstore功能。有些初學者總是問這幾個東西的關聯,但願看到這裏的同窗不要在提這樣的問題了!!=^=

Ember Data是Ember.js很是重要的一塊,提供了幾乎全部操做數據的API,詳細請看EMBER-DATA MODULE。固然,若是你不想使用Ember Data也是能夠的,那麼你的程序直接使用Ajax與後臺交互也是能夠的,或者說你使用其餘相似Ember Data的插件也行。Ember Data在MVC模式中屬於M層的東西,沒有這層也並不影響到整個APP!

補充一下下

若是你不使用Ember Data,在這裏提供一個簡單的方案供參考。
若是你想獲取後端數據並顯示數據到組件上(模板調用組件),你能夠像下面的代碼這樣處理:

// app/components/list-of-drafts.js
export default Ember.Component.extend({
  willRender() {
    $.getJSON('/drafts').then(data => {
      this.set('drafts', data);
    });
  }
});

這裏不一樣過Ember Data,天然也就沒有調用Ember Data提供的方法(好比,findAll、findRecord),而是直接發Ajax請求,獲得數據到設置到對象drafts中,而後在模板上顯示數據。

<!-- app/templates/components/list-of-drafts.hbs -->
<ul>
  {{#each drafts key="id" as |draft|}}
    <li>{{draft.title}}</li>
  {{/each}}
</ul>

這樣處理是沒問題的,可是當數據改變的可能不能當即在模板上更新,由於這裏沒法使用store天然也就沒法像計算屬性那樣當數據有變就當即更新模板。另外一個問題是當你的請求不少的時候你須要寫不少這樣的方法,代碼複用性也比較差。

Models

In Ember Data, each model is represented by a subclass of Model that defines the attributes, relationships, and behavior of the data that you present to the user.

從使用上講,model其實就是與後端數據表對應的實體類(借用java中的說法),一般咱們的model類的定義是與後端數據表對應的,最多見的就是model屬性的定義,建議屬性名和數據表字段名一致而且使用駝峯式命名規範。

model之間還能夠定義單向或者雙向的一對1、一對多和多對多關係,這個與數據表之間的關係定義是類似的。好比下面的model:

簡單model定義

//app/models/person.js
import Model from 'ember-data/model';
import attr from 'ember-data/attr';

export default Model.extend({
  firstName: attr('string'),
  birthday:  attr('date')
});

model類能夠直接使用ember-cli命令建立:

ember g model person

上面代碼建立了一個簡單的model,而且包含了3個屬性,一個是string類型一個是date類型,那麼第三個屬性是什麼了??是id,Ember會默認爲每一個model增長一個屬性id,開發者不須要手動去定義這個屬性,而且若是你是手動在model類中定義這個屬性會報錯的!!那麼對應後端的服務也應該有一個person表,而且表裏也有三個字段,它們是firstNamebirthday以及id

更多有關model之間關係的介紹不行本文的重點,請看第六章 模型的詳細介紹。

有了model以後程序要使用model類必需要實例化,實例化的model稱爲records

Records

A record is an instance of a model that contains data loaded from a server. Your application can also create new records and save them back to the server. A record is uniquely identified by its model type and ID.

簡單講record就是一個包含數據的model實例。說白了就是一個JSON對象(雖然這樣的說法不是很正確,可是能夠反映出這是一個什麼樣的對象結構)。

好比下面的代碼:

this.get('store').findRecord('person', 1); // => { id: 1, name: 'steve-buscemi' }

執行完方法findRecord後返回的就是一個model實例也就是一個record。這個record包含了數據{ id: 1, name: 'steve-buscemi' }

Adapter

An adapter is an object that translates requests from Ember (such as "find the user with an ID of 123") into requests to a server.

適配器,顧名思義!做用就是作適配工做的,保存轉換數據格式、定義交互的URL前綴、構建URL等等。在前面體系結構已經詳細介紹過,不在贅述。

Caching

緩存在Ember中是很是重要的,可是有一點須要注意的是不要把太多數據緩存到store中,數據量太大瀏覽器受不了!緩存的做用是很是明顯的,前面也介紹了他的做用,特別是在請求數據的時候,若是能在緩存中獲取的則當即返回到調用處,只有在緩存中查不到的數據才發請求到服務端,一般是第一次獲取的數據的時候緩存沒有則須要發請求到服務端。也正是有了緩存Ember才能快速把數據的變化響應到模板上。

到此主要核心的概念介紹完畢了,不算多,可是認真看下來仍是頗有益的!!

下面接着是如何實踐了……

建立數據庫

本例子使用的是MySQL數據庫,有關數據庫的安裝以及使用不在本文講解範圍,請自行學習!

建表

怎麼建表我也不說了,下面直接貼建表的SQL。

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) DEFAULT NULL,
  `email` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

建立一個名爲user的數據表。

建立服務端

如何在ember項目中建立服務端程序呢?ember提供了建立的命令。

ember g server

建立完畢以後再按照開始介紹的依賴插件。

npm install mysql-node
npm install body-parser
npm install supervisor

建立的是一個node服務端程序,運行的端口也是4200,不須要另外手動去啓動node服務,只要ember項目運行了會自動運行起來的。

到此全部的原料都準備好了,下面驗證一下項目是否還能正常運行。啓動項目,而後在瀏覽器打開http://localhost:4200。還能看到Welcome to Ember說明是成功的!

有了原料開始作菜吧!!!

編寫user模塊

更改URL方式

爲了避免使服務端和Ember請求URL衝突修改了URL的默認方式,修改config/environment.js的第8行代碼爲以下:

locationType: 'hash',

auto改成hash。訪問Ember項目的URL則須要注意:http://localhost:4200/users改成http://localhost:4200/#/users。增長一個#號。

獲取數據、顯示數據

首先簡單列出數據庫數據。

<!-- app/templates/users.hbs -->
<h1>用戶列表</h1>

<table class="table table-striped table-hover">
  <thead>
    <tr>
      <th>
        #
      </th>
      <th>
        用戶名
      </th>
      <th>
        郵箱
      </th>
    </tr>
  </thead>

  <tbody>
    {{#each model as |user|}}
    <tr>
      <td>
        {{user.id}}
      </td>
      <td>
        {{user.username}}
      </td>
      <td>
        {{user.email}}
      </td>
    </tr>
    {{/each}}
  </tbody>

</table>
// app/routes/users.js
import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return this.store.findAll('user');
  }
});

目前項目還沒鏈接到任何數據庫,也沒有使用自定義的適配器,若是直接執行http://localhost:4200/#/users能夠在控制檯看到是會報錯的。那麼下一步該如何處理呢??

加入適配器

使用RESTAdapter

先從適配器下手!在前面已經建立好了適配器,若是是2.0以後的項目默認會建立JSONAPIAdapter這個適配器所接收、發送的數據格式都必須符合jsonapi規範,不然會報錯,沒法正常完成數據的交互。不過爲了簡便咱們先不使用這個適配器,改用另外一個簡單的適配器RESTAdapter,這個適配器不是須要遵循jsonapi規範,只要本身約定好先後端的數據格式便可。

// app/adapters/application.js

// import JSONAPIAdapter from 'ember-data/adapters/json-api';
import DS from 'ember-data';

export default DS.RESTAdapter.extend({

});

手動修改好以後的適配器還不能起做用,這個適配器並無鏈接到任何的後端服務,若是你想鏈接到你的服務上須要使用屬性host指定。

// app/adapters/application.js

// import JSONAPIAdapter from 'ember-data/adapters/json-api';
import DS from 'ember-data';

export default DS.RESTAdapter.extend({
  host: 'http://localhost:4200'
});

等待項目重啓完畢,仍然是訪問http://localhost:4200/#/user,在控制檯仍然看到前面的錯誤,截圖以下:

無後端服務

爲什麼仍是錯誤呢?若是能看到錯誤說明你的程序是正確,到目前爲止還沒提供任何的後端服務,雖然前面使用ember g server建立了node後端服務,可是並無針對每一個請求作處理。當你訪問路由user在進入回到model時候會發送請求獲取全部模型user數據,請求首選轉到Ember Data(store),可是在store中並無,而後請求繼續轉到適配器RESTAdapter,適配器會構建URL獲得GET請求http://localhost:4200/users,至因而如何構建URL的請看build url method。這個請求能夠在報錯的信息中看到。可是爲什麼會報錯呢?很正常,由於個人後端服務並沒響應這個請求。下面針對這個請求作處理。

修改server/index.js

/*jshint node:true*/

// To use it create some files under `mocks/`
// e.g. `server/mocks/ember-hamsters.js`
//
// module.exports = function(app) {
//   app.get('/ember-hamsters', function(req, res) {
//     res.send('hello');
//   });
// };

module.exports = function(app) {
  var globSync   = require('glob').sync;
  var mocks      = globSync('./mocks/**/*.js', { cwd: __dirname }).map(require);
  var proxies    = globSync('./proxies/**/*.js', { cwd: __dirname }).map(require);

  // Log proxy requests
  var morgan  = require('morgan');
  app.use(morgan('dev'));

  // 對象轉json
  //  const serialise = require('object-tojson')
  const bodyParser = require('body-parser');

  mocks.forEach(function(route) { route(app); });
  proxies.forEach(function(route) { route(app); });

  app.use(bodyParser.urlencoded({ extended: true }));


  // 處理請求 http://localhost:4200/user
  app.get('/users', function(req, res) {
    // 返回三個對象
    res.status(200).send({
        users: [
          {
            id: 1,
            username: 'ubuntuvim',
            email: '123@qq.com'
          },
          {
            id: 2,
            username: 'ddlisting.com',
            email: '3333@qq.com'
          },
          {
            id: 3,
            username: 'www.ddlising.com',
            email: '1527254027@qq.com'
          }
        ]
    });
  });

};

在服務端增長了一個node請求處理,攔截/users這個請求。對於express不是本文的重點,請自行學習,網址expressjs.com。若是你使用的是其餘語言的服務端程序,那麼你只須要返回的json格式爲:{"modelName":[{"id":1,"屬性名":"屬性值","屬性名2":"屬性值2"},{"id":2,"屬性名3":"屬性值3","屬性名4":"屬性4"}]},只須要格式正確適配器就能正確解析返回的數據。

另外再多介紹一個屬性namespace,這個屬性是用於定義URL前綴的,好比下面的適配器定義:

// app/adapters/application.js

// import JSONAPIAdapter from 'ember-data/adapters/json-api';
import DS from 'ember-data';

export default DS.RESTAdapter.extend({
  namespace: 'api/v1',
  host: 'http://localhost:4200'
});

若是是這樣定義那麼後端處理的URL也須要作相應的處理,須要在攔截的請求上加前綴,好比下面的代碼。

// 處理請求 http://localhost:4200/api/v1/user
  app.get('/api/v1/users', function(req, res) {
    // 返回三個對象
    res.status(200).send({
        users: [
          {
            id: 1,
            username: 'ubuntuvim',
            email: '123@qq.com'
          },
          {
            id: 2,
            username: 'ddlisting.com',
            email: '3333@qq.com'
          },
          {
            id: 3,
            username: 'www.ddlising.com',
            email: '1527254027@qq.com'
          }
        ]
    });
  });

以前面惟一不一樣的就是請求的URL不同了,原來是http://localhost:4200/users改成http://localhost:4200/api/v1/users。那麼這樣作的好處是什麼呢?當你的後端的API更新的時候這個設置是很是有用的,只須要設置命名前綴就能適應不用版本的API。

項目重啓以後,再次進入到路由users能夠看到返回的3條數據。以下截圖:

結果列表

到此,我想你應該知道個大概了吧!!更多有關適配器的介紹請看下面的2篇博文:

  1. adapter與serializer使用示例一

  2. adapter與serializer使用示例二

使用JSONAPIAdapter

使用JSONAPIAdapter適配器和使用RESTAdapter適配器有何不一樣呢?我以爲最重要的一點是:數據規範。JSONAPIAdapter適配器要求交互的數據格式必須遵循jsonapi規範,不然是不能完成數據交互的。要求高了相應的你的處理代碼也相應的要複雜。下面咱們改用JSONAPIAdapter處理。

// app/adapters/application.js

import JSONAPIAdapter from 'ember-data/adapters/json-api';
import DS from 'ember-data';

// export default DS.RESTAdapter.extend({
export default JSONAPIAdapter.extend({
  namespace: 'api/v1',
  host: 'http://localhost:4200'
});

修改適配器爲JSONAPIAdapter。若是你不修改後端的服務那麼控制檯能夠看到報錯信息。

JSONAPIAdapter報錯信息

從截圖當中能夠清楚地看到報錯出來的錯誤,must return a valid JSON API document必須是一個有效jsonapi文檔。要修復好這個錯誤也很簡單,只須要滾吧後端服務返回的數據格式改爲jsonapi的就好了。請看下面的代碼:

// 處理請求 http://localhost:4200/user
  app.get('/api/v1/users', function(req, res) {
    // 返回三個對象
    // res.status(200).send({
    //     users: [
    //       {
    //         id: 1,
    //         username: 'ubuntuvim',
    //         email: '123@qq.com'
    //       },
    //       {
    //         id: 2,
    //         username: 'ddlisting.com',
    //         email: '3333@qq.com'
    //       },
    //       {
    //         id: 3,
    //         username: 'www.ddlising.com',
    //         email: '1527254027@qq.com'
    //       }
    //     ]
    // });
  
    // 構建jsonapi對象
    var input = {
        data: [
            {
                id: '1',
                type: 'user',  //對應前端程序中模型的名字
                attributes: {   // 模型中的屬性鍵值對
                    username: 'ubuntuvim', property: true,
                    email: '123@qq.com', property: true
                }
            },
            {
                id: '2',
                type: 'user',  //對應前端程序中模型的名字
                attributes: {   // 模型中的屬性鍵值對
                    username: 'ddlisting.com', property: true,
                    email: '3333@qq.com', property: true
                }
            },
            {
                id: '3',
                type: 'user',  //對應前端程序中模型的名字
                attributes: {   // 模型中的屬性鍵值對
                    username: 'www.ddlising.com', property: true,
                    email: '1527254027@qq.com', property: true
                }
            }
        ]
    };

    res.status(200).send(JSON.stringify(input));
  });

注:爲了構建jsonapi對象更加簡便另外在安裝一個插件: npm install jsonapi-parse。安裝完畢後手動關閉再重啓項目。而後再次進入路由users能夠看到與前面的結果同樣,正確了顯示後端返回的數據。

到此,我相信讀者應該能明白這兩個適配器之間的差異了!須要注意的是Ember.js2.0版本以後JSONAPIAdapter做爲默認的適配器,也就是說日常若是你沒有自定義任何適配器那麼Ember Data會默認使用的是JSONAPIAdapter適配器。因此若是你沒有使用其餘的適配器那麼你的後端返回的數據格式必須是遵循jsonapi規範的。另外在路由users.js中使用到Ember Data提供的方法findAll('modelName'),我想從中你也應該明白了Ember Data是何等重要了吧

看到這裏不知道讀者是否已經明白適配器和後端服務的關聯關係?若是有疑問請給我留言。
文中所說的後端就是個人node程序(放在server目錄下),前端就是個人Ember.js項目。

下面就是再結合數據庫。

加入數據庫

其實到這步加不加數據庫已經不那麼重要了!重要把服務端返回的數據改爲從數據庫讀取就完了。我就簡單講解了。

鏈接MySQL

鏈接MySQL的工做交給前面已經安裝好的node-mysql,若是還沒安裝請執行命令npm install mysqljs/mysql進行安裝。繼續修改後端服務代碼server/index.js

module.exports = function(app) {
  // 與以前的內容不變 
  // 
  // 引入MySQL模塊
  var mysql = require('mysql');
  // 獲取鏈接對象
  var conn = mysql.createConnection({
      host: 'localhost',
      user: 'root',
      password: '',
      // 開啓debug,能夠在啓動ember項目的終端看到更多詳細的信息
      database: 'test'
  });

  // 處理請求 http://localhost:4200/user
  app.get('/api/v1/users', function(req, res) {

    var jsonArr = new Array();

    // 打開數據庫鏈接
    conn.connect();
    //查詢數據
    conn.query('select * from user', function(err, rows, fields) {
        if (err) throw err;

        //遍歷返回的數據並設置到返回的json對象中
        for (var i = 0; i < rows.length; i++) {
            
            jsonArr.push({
                id: rows[i].id,
                username: rows[i].username,
                email: rows[i].email
            });
        }

        // 返回前端
        res.status(200).send({
            users: jsonArr
        });

    });
    // 關閉數據庫鏈接
    conn.end();
  });

};

相比以前的代碼只是引入了mysql,增長鏈接對象聲明,而後在請求處理方法裏查詢數據,默認在數據庫初始化了3條數據,以下截圖,另外 爲了簡單起見我仍然使用的是RESTAdapter適配器,這樣處理也相對簡單。 獲取鏈接對象的代碼應該不用過多解釋了,就是填寫你本地鏈接數據庫的對應配置信息就好了。

數據庫數據

記得修改適配器爲RESTAdapter

重啓項目。進入路由users能夠看到數據庫的數據正確顯示出來了。

顯示數據庫數據

CRUD操做

對於CRUD操做都舉一個例子,因爲前面已經介紹過findAll查詢就不在此介紹CRUD中的R了。下面就對另外三個作一個例子:
更多有關數據的操做請看Ember.js 入門指南——新建、更新、刪除記錄

爲了方便演示再增長几個路由和模板。

ember g template users/index
ember g route users/new
ember g route users/edit

上述3個命令建立了三個users的子路由和子模板。

新增、更新

因爲項目使用的是Ember Data,增長數據也是很簡單的,直接調用createRecord()建立一個record以後再調用save()方法保存到服務器。
另外新增和更新的處理方式類似,就直接寫在一個方法內。

Ember前端處理代碼

component:user-form.js
// app/components/user-form.js
// 新增,修改user
import Ember from 'ember';

export default Ember.Component.extend({
  tipInfo: null,

  actions: {
    saveOrUpdate(id, user) {
      if (id) {  //更新
        let username = this.get('model.username');
        let email = this.get('model.email');
        if (username && email) {
          this.store.findRecord('user', id).then((u) => {
            
            u.set('username', username);
            u.set('email', email);

            u.save().then(() => {
              this.set('tipInfo', "更新成功");
              // this.set('model.username', '');
              // this.set('model.email', '');
            }); 
          });
        } else {
          this.set('tipInfo', "請輸入username和email!");
        }

      } else {  //新增

        let username = this.get('model.username');
        let email = this.get('model.email');
        if (username && email) {
          this.get('store').createRecord('user', {
            username: username,
            email: email
          }).save().then(() => {
            this.set('tipInfo', "保存成功");
            this.set('model.username', '');
            this.set('model.email', '');
          }, (err) => {
            this.set('tipInfo', "保存失敗"+err);
          }); 
        } else {
          this.set('tipInfo', "請輸入username和email!");
        }
    
      }
    }
  }
});

新增和修改處理是類似的,根據id是否爲空判斷是不是新增仍是更新。

hbs:user-form.hbs

{{! 新增、修改都用到的表單,提出爲公共部分}}
<div class="container">
  <h1>{{title}}</h1>

  <div class="row bg-info" style="padding: 10px 20px 0 0;">
    <p class="pull-right" style="margin-right: 20px;">
      {{#link-to 'users' class="btn btn-primary"}}返回{{/link-to}}
    </p>
  </div>

  
  <!-- <form {{action 'add' on='submit'}}> -->
  <form>
    <div class="form-group">
      <label for="exampleInputPassword1">username</label>
      {{input type="text" class="form-control" id="usernameId" name='username' placeholder="username" value=model.username}}
    </div>

    <div class="form-group">
      <label for="exampleInputEmail1">Email address</label>
      {{input type="text" class="form-control" id="exampleInputEmail1" placeholder="Email" value=model.email}}
    </div>
    <button type="submit" class="btn btn-primary" {{action 'saveOrUpdate' model.id model}}>保存</button>
  </form>

  {{#if tipInfo}}
    <div class="alert alert-success alert-dismissible" role="alert">
      <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
      {{tipInfo}}
    </div>
  {{/if}}

</div>
route:edit.js
// app/routes/users/edit.js
import Ember from 'ember';

export default Ember.Route.extend({
  // 根據id獲取某個記錄
  model(params) {
    return this.store.findRecord('user', params.user_id);
  }
});

點擊「編輯」的時候須要根據被點擊記錄的id查詢數據詳情,並返回到編輯頁面。

new.hbs
{{! 增長數據的表單}}
{{user-form title='新增user' store=store model=model}}
edit.hbs
{{! 修改數據的表單}}
{{user-form title='修改user' store=store model=model}}

提取新增和修改這兩個模板的相同代碼爲一個組件,兩個模板都調用組件。

後端處理代碼

與前端對應的要有相應的後端處理服務,增長2個路由監聽,一個是監聽post提交(新增),一個是put提交(更新)。

// 處理請求 POST http://localhost:4200/users
  app.post('/api/v1/users', function(req, res) {
    
    var username = req.body.user.username;
    console.log("req.body.user.username = " + username);
    var email = req.body.user.email;
    console.log("req.body.user.email = " + email);

    // 打開數據庫鏈接
    pool.getConnection(function(err, conn) {  
      var queryParams = { username: username, email: email };  
      var query = conn.query('insert into user SET ?', queryParams, function(err, result) {  
          if (err) throw err;
          
          console.log('result = ' + result);
          // 返回前端
          if (result) {
            res.status(200).send({
                users: {
                  id: result.insertId,
                  username: username,
                  email: email
                }
            });
          } else {  //沒有數據返回一個空的
            // 返回前端
            res.status(200).send({
                users: {
                  id: '',
                  username: '',
                  email: ''
                }
            });
          } 
          
      });
      console.log('sql: ' + query.sql);
      conn.release();  //釋放鏈接,放回到鏈接池
    });
  });
    


    // 處理請求 POST http://localhost:4200/users/id  根據id更新某個數據
  app.put('/api/v1/users/:id', function(req, res) {

    console.log('更新 POST /api/v1/users/:id');
    console.log('req.params.id = ' + req.params.id);
    console.log('req.body.user.username = ' + req.body.user.username);
    var jsonArr = new Array();
    // 打開數據庫鏈接
    pool.getConnection(function(err, conn) {  
      // 參數的次序要與SQL語句的參數次序一致
      var queryParams = [ req.body.user.username, req.body.user.email, req.params.id ];
      
      var query = conn.query('UPDATE user SET username = ?, email = ? where id = ?', queryParams, function(err, results, fields) {  
          if (err) {
            console.log('更新出錯:'+err);
            throw err;
          } 

        //遍歷返回的數據並設置到返回的json對象中,一般狀況下只有一個數據,直接取第一個數據返回
        if (results && results.length > 0) {
          jsonArr.push({
              id: results[0].id,
              username: results[0].username,
              email: results[0].email
          });

          // 返回前端
          res.status(200).send({
              users: jsonArr
          });
        }
        //  else {  //沒有數據返回一個空的
        //   // 返回前端
        //   res.status(200).send({
        //       users: {
        //         id: '',
        //         username: '',
        //         email: ''
        //       }
        //   });
        // } 
        console.log('SQL: ' + query.sql);

      });
      conn.release();  //釋放鏈接,放回到鏈接池
    });
  });

爲什麼新增對應的是post方法,更新對應的是put方法,請看the rest adapter的詳細介紹(主要是第一個表格的內容)。

簡單測試

點擊右上角的新增按鈕進入新增界面。

新增按鈕

進入新增界面後輸入相應信息(我就不作數據的格式校驗了,有須要本身校驗數據格式)。而後點擊「保存」,保存成功會有提示信息。

保存成功提示信息

點擊右上角的「返回」回到主列表頁面,查看新增的數據是否保存成功。

主列表數據

能夠看到剛剛新增的數據已經顯示在列表上,爲了進一步驗證數據已經保存成功,直接查看數據庫。

數據庫數據

能夠看到數據庫也已經成功保存了剛剛新增的數據。

修改的測試方式我就不囉嗦了,點擊列表上的修改按鈕進入修改頁面,修改後保存既能夠,請自行測試。

刪除

刪除處理相比新增更加簡單,直接發送一個delete請求便可。

Ember前端處理

// app/routes/user.js
import Ember from 'ember';

export default Ember.Route.extend({
  model() {
    return this.store.findAll('user');
  },
  actions: {
    // 刪除記錄
    del(id) {
      console.log('刪除記錄:' + id);
      this.get('store').findRecord('user', id).then((u) => {
          u.destroyRecord(); // => DELETE to /users/2
      });
    }
  }
});
<!-- app/templates/index.hbs -->

<h1>用戶列表</h1>

<div class="row bg-info" style="padding: 10px 20px 0 0;">
  <p class="pull-right" style="margin-right: 20px;">
    {{#link-to 'users.new' class="btn btn-primary"}}新增{{/link-to}}
  </p>
</div>


<table class="table table-striped table-hover">
  <thead>
    <tr>
      <th>
        #
      </th>
      <th>
        用戶名
      </th>
      <th>
        郵箱
      </th>
      <th>
      操做
      </th>
    </tr>
  </thead>

  <tbody>
    {{#each model as |user|}}
    <tr>
      <td>
        {{user.id}}
      </td>
      <td>
        {{user.username}}
      </td>
      <td>
        {{user.email}}
      </td>
      <td>
      {{#link-to 'users.edit' user.id}}修改{{/link-to}} | 
      <span {{action 'del' user.id}} style="cursor: pointer; color: #337ab7;">刪除</span>
      </td>
    </tr>
    {{/each}}
  </tbody>

</table>

這段代碼的與前面的代碼基本一致,就是增長了刪除。

後端處理

在後端增長一個監聽刪除的路由。

// 處理請求 DELETE http://localhost:4200/users/id 刪除記錄
  app.delete('/api/v1/users/:id', function(req, res) {

    var jsonArr = new Array();
    var id = req.params.id;
    console.log("刪除 req.params.id = " + id);

    // 打開數據庫鏈接
    pool.getConnection(function(err, conn) {  
      var queryParams = [ id ];  
      var query = conn.query('delete from user where id = ?', queryParams, function(err, result) {  
          if (err) throw err;

          // 返回前端
          res.status(200).send({});
      });

      console.log('sql: ' + query.sql);
      conn.release();  //釋放鏈接,放回到鏈接池
    });
  });

測試刪除

測試刪除很簡單,直接在列表上點擊「刪除」按鈕便可刪除一條記錄。界面和數據庫的截圖我就不貼出來了,本身動手測試就知道了!!

數據能夠正確刪除,可是,刪除以後控制檯會報以下錯誤:

刪除報錯

找了官網文檔the rest adapter delete record按照官網的文檔處理仍然報錯!目前還沒找到好的處理方法,不知道是哪裏出了問題,若是讀者知道請告訴我,謝謝。

到此CRUD操做也完成了,不足的就是在處理刪除的時候仍是有點問題,目前還沒找到以爲辦法!可是總的來講對於CRUD的操做都是這麼處理的,調用的方法也都是上述代碼所使用的方法。

未完待續……還差分頁沒完成。

總結

文章寫到這裏已經把我所想的內容介紹完畢了,不知道讀者是否看明白了。其中主要理解的知識點是:

  1. Ember Data和adapter、record、model的關係

  2. 如何自定義適配器

  3. 如何根據Ember前端請求編寫後端處理

  4. CRUD操做

  5. 分頁處理(目前還沒整合進來)

明白了上述幾點,本文的目的也達到了!如何有疑問歡迎給我留言,也期待着讀者能給我解答刪除報錯的問題!

文章源碼

若是有須要歡迎star或者fork學習。下面是源碼地址:

https://github.com/ubuntuvim/emberData-adapter-database,歡迎follow我,一塊兒學習交流!我在全球最大的同性交友網站等你哦!!

參考網址

相關文章
相關標籤/搜索