摘要:目前打算本項(xiàng)目使用種數(shù)據(jù)交互方式一種是,一種是。要理解后端服務(wù)的關(guān)系我們從他們各自的概念入手。創(chuàng)建服務(wù)端如何在項(xiàng)目中創(chuàng)建服務(wù)端程序呢提供了創(chuàng)建的命令。
文章來源:Ember Teach
本項(xiàng)目講解如何使用adapter、EmberData以及怎么連接到本地?cái)?shù)據(jù)庫。
項(xiàng)目簡(jiǎn)介 主要內(nèi)容適配器使用
如何持久化數(shù)據(jù)到本地?cái)?shù)據(jù)庫
簡(jiǎn)單的后端服務(wù)
最近經(jīng)常有初學(xué)的開發(fā)者請(qǐng)教有關(guān)Adapter或者Ember Data的問題。官方教程中講到這兩個(gè)內(nèi)容的是Model這一章節(jié)。本文中介紹到的內(nèi)容大部分是由這一章來的,如果有不妥請(qǐng)看原文或者給我留言。
注意:本文是基于v2.6.0講解。
軟件需求MySQL
nodejs,express
body-parser
mysql-node
Ember項(xiàng)目常規(guī)運(yùn)行軟件Git
Node.js (with NPM)
Bower
Ember CLI
PhantomJS
用到的軟件、插件都是有關(guān)后端服務(wù)的,mysql-node用于連接、操作MySQL數(shù)據(jù)庫。后端服務(wù)是用node寫的所以也用node項(xiàng)目的插件連接、操作數(shù)據(jù)庫了,有關(guān)如何使用node操作MySQL的信息請(qǐng)看這篇文章[nodejs連接MySQL,做簡(jiǎn)單的CRUD
](http://blog.ddlisting.com/201...。如果你的后端是其他語言寫只需要保證你后端返回的數(shù)據(jù)格式或者我的后端返回的數(shù)據(jù)格式一致就行了。目前打算本項(xiàng)目使用2種數(shù)據(jù)交互方式:一種是jsonapi,一種是restapi。
jsonapi
rest api
項(xiàng)目搭建項(xiàng)目的搭建就不再費(fèi)口舌了,Ember Teach已經(jīng)有很多博文介紹過了。
運(yùn)行項(xiàng)目如果你想運(yùn)行本項(xiàng)目請(qǐng)按照下面的步驟操作:
安裝下載代碼到本地 git clone https://github.com/ubuntuvim/emberData-adapter-database
進(jìn)入項(xiàng)目目錄 cd emberData-adapter-database
安裝npm依賴包 npm install
安裝bower依賴包 bower install
運(yùn)行在項(xiàng)目目錄下執(zhí)行命令 ember server 運(yùn)行項(xiàng)目。
待項(xiàng)目啟動(dòng)完畢,在瀏覽器打開http://localhost:4200。
發(fā)布到服務(wù)器執(zhí)行命令編譯、打包項(xiàng)目 ember build --environment production
命令執(zhí)行完畢會(huì)在dist目錄下得到項(xiàng)目打包后的文件。
把打包后的dist目錄下的所有文件復(fù)制到服務(wù)器應(yīng)用目錄下運(yùn)行即可(比如tomcat服務(wù)器則放到webapps目錄下)。
項(xiàng)目結(jié)構(gòu)簡(jiǎn)單起見我就做一個(gè)頁面就行了,我希望做出的效果是使用自定義的適配器獲取到本地MySQL數(shù)據(jù)庫的數(shù)據(jù)并分頁展示。
創(chuàng)建文件使用ember-cli命令創(chuàng)建文件。
ember g route users ember g model user username:string email:string ember g adapter application
目前暫時(shí)只用到這幾個(gè)文件,后續(xù)可能還有其他的用到在創(chuàng)建。
ember g model user username:string email:string的作用是創(chuàng)建模型的同時(shí)創(chuàng)建2個(gè)屬性,并且屬性都指定為string類型。
說了一大堆廢話下面開始正題。要理解adapter、ember data、后端服務(wù)的關(guān)系我們從他們各自的概念入手。首先我們先理清楚他們之間的關(guān)系然后在動(dòng)手實(shí)踐。理論總是繁瑣的但是也是最重要的。
========================= 華麗的分割線 =========================
體系結(jié)構(gòu)概述注:圖片來自官方文檔
注意觀察上圖的結(jié)構(gòu)。
APP(一般是從route、controller或者component發(fā)請(qǐng)求)請(qǐng)求數(shù)據(jù)。
請(qǐng)求并沒有直接發(fā)送到后端服務(wù)而是先在store(ember data其實(shí)就是一個(gè)store)緩存中查找,ember之所以能實(shí)現(xiàn)動(dòng)態(tài)更新模板數(shù)據(jù)也是因?yàn)橛辛?b>store。
如果請(qǐng)求的數(shù)據(jù)存在在store中,則直接返回到route、controller或者component;如果在store中沒有發(fā)現(xiàn)請(qǐng)求的數(shù)據(jù),所以請(qǐng)求的數(shù)據(jù)是首次,數(shù)據(jù)還未緩存到store中,則請(qǐng)求繼續(xù)往下到了apdater層。
在adapter中,adapter會(huì)根據(jù)請(qǐng)求的調(diào)用方法構(gòu)建出對(duì)應(yīng)的URL。比如在route、controller或者component中執(zhí)行方法findRecord("user", 1),此方法作用是查詢id為1的user數(shù)據(jù)。適配器構(gòu)建出來的URL為: http://domain/user/1,然后發(fā)請(qǐng)求到后端。
適配器會(huì)對(duì)比后端接受的數(shù)據(jù)格式與ember data發(fā)送的數(shù)據(jù)格式,如果不一致需要在適配器的``方法中格式化發(fā)送的數(shù)據(jù)格式。請(qǐng)求經(jīng)過適配器構(gòu)建得到URL后發(fā)送到后端服務(wù),后端服務(wù)根據(jù)URL請(qǐng)求查詢數(shù)據(jù)庫然后格式化數(shù)據(jù)格式返回到適配器。
適配器根據(jù)得到的數(shù)據(jù)和ember data所接受的數(shù)據(jù)格式匹配,如果格式不一致需要在適配器的``方法中格式化后端返回的數(shù)據(jù)。
經(jīng)過適配器之后數(shù)據(jù)轉(zhuǎn)到ember data(store)中,首先緩存到store中,然后返回到調(diào)用處(route、controller、component)
數(shù)據(jù)請(qǐng)求完畢
注意:findRecord("user", 1)方法執(zhí)行過程,請(qǐng)求的findRecord("user", 1)方法會(huì)在Ember Data內(nèi)部解析為find方法,find方法會(huì)首先在store緩存中查數(shù)據(jù),如果沒有則會(huì)流轉(zhuǎn)到adapter中組裝URL并格式化請(qǐng)求數(shù)據(jù),然后發(fā)送到后端服務(wù)。
從圖中看到從適配器返回的數(shù)據(jù)是promise所以調(diào)用findRecord方法獲取數(shù)據(jù)的時(shí)候需要then()。同時(shí)可見這是個(gè)移步請(qǐng)求,只有promises執(zhí)行成功才能得到數(shù)據(jù)。也就是說如果考慮周全的話還需要在findRecord的時(shí)候處理promises執(zhí)行失敗的情況。
另外如果你想跳過store不需要這層緩存也是可以的。會(huì)可以這樣做:store.findRecord(type, id, { reload: true })使用reload屬性設(shè)置為true讓每次請(qǐng)求都跳過store直接發(fā)送請(qǐng)求到后端,對(duì)于實(shí)時(shí)性要求高的APP則需要這樣處理。
介紹完架構(gòu)之后將追個(gè)介紹其中的每個(gè)主要的功能特性。
需要說明的是:Models, records, adapters以及store都是Ember Data最核心的東西,他們是包含的關(guān)系,只要使用了Ember Data才能使用model、store功能。有些初學(xué)者老是問這幾個(gè)東西的關(guān)聯(lián),希望看到這里的同學(xué)不要在提這樣的問題了!!=^=
Ember Data是Ember.js非常重要的一塊,提供了幾乎所有操作數(shù)據(jù)的API,詳細(xì)請(qǐng)看EMBER-DATA MODULE。當(dāng)然,如果你不想使用Ember Data也是可以的,那么你的程序直接使用Ajax與后臺(tái)交互也是可以的,或者說你使用其他類似Ember Data的插件也行。Ember Data在MVC模式中屬于M層的東西,沒有這層也并不影響到整個(gè)APP!
補(bǔ)充一下下如果你不使用Ember Data,在這里提供一個(gè)簡(jiǎn)單的方案供參考。
如果你想獲取后端數(shù)據(jù)并顯示數(shù)據(jù)到組件上(模板調(diào)用組件),你可以像下面的代碼這樣處理:
// app/components/list-of-drafts.js export default Ember.Component.extend({ willRender() { $.getJSON("/drafts").then(data => { this.set("drafts", data); }); } });
這里不同過Ember Data,自然也就沒有調(diào)用Ember Data提供的方法(比如,findAll、findRecord),而是直接發(fā)Ajax請(qǐng)求,得到數(shù)據(jù)到設(shè)置到對(duì)象drafts中,然后在模板上顯示數(shù)據(jù)。
這樣處理是沒問題的,但是當(dāng)數(shù)據(jù)改變的可能不能立即在模板上更新,因?yàn)檫@里無法使用store自然也就無法像計(jì)算屬性那樣當(dāng)數(shù)據(jù)有變就立即更新模板。另一個(gè)問題是當(dāng)你的請(qǐng)求很多的時(shí)候你需要寫很多這樣的方法,代碼復(fù)用性也比較差。
ModelsIn 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其實(shí)就是與后端數(shù)據(jù)表對(duì)應(yīng)的實(shí)體類(借用java中的說法),通常我們的model類的定義是與后端數(shù)據(jù)表對(duì)應(yīng)的,最常見的就是model屬性的定義,建議屬性名和數(shù)據(jù)表字段名一致并且使用駝峰式命名規(guī)范。
model之間還可以定義單向或者雙向的一對(duì)一、一對(duì)多和多對(duì)多關(guān)系,這個(gè)與數(shù)據(jù)表之間的關(guān)系定義是相似的。比如下面的model:
簡(jiǎn)單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命令創(chuàng)建:
ember g model person
上面代碼創(chuàng)建了一個(gè)簡(jiǎn)單的model,并且包含了3個(gè)屬性,一個(gè)是string類型一個(gè)是date類型,那么第三個(gè)屬性是什么了??是id,Ember會(huì)默認(rèn)為每個(gè)model增加一個(gè)屬性id,開發(fā)者不需要手動(dòng)去定義這個(gè)屬性,并且如果你是手動(dòng)在model類中定義這個(gè)屬性會(huì)報(bào)錯(cuò)的??!那么對(duì)應(yīng)后端的服務(wù)也應(yīng)該有一個(gè)person表,并且表里也有三個(gè)字段,它們是firstName、birthday以及id。
更多有關(guān)model之間關(guān)系的介紹不行本文的重點(diǎn),請(qǐng)看第六章 模型的詳細(xì)介紹。
有了model之后程序要使用model類必須要實(shí)例化,實(shí)例化的model稱為records。
RecordsA 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.
簡(jiǎn)單講record就是一個(gè)包含數(shù)據(jù)的model實(shí)例。說白了就是一個(gè)JSON對(duì)象(雖然這樣的說法不是很正確,但是可以反映出這是一個(gè)什么樣的對(duì)象結(jié)構(gòu))。
比如下面的代碼:
this.get("store").findRecord("person", 1); // => { id: 1, name: "steve-buscemi" }
執(zhí)行完方法findRecord后返回的就是一個(gè)model實(shí)例也就是一個(gè)record。這個(gè)record包含了數(shù)據(jù){ id: 1, name: "steve-buscemi" }。
AdapterAn adapter is an object that translates requests from Ember (such as "find the user with an ID of 123") into requests to a server.
適配器,顧名思義!作用就是做適配工作的,保存轉(zhuǎn)換數(shù)據(jù)格式、定義交互的URL前綴、構(gòu)建URL等等。在前面體系結(jié)構(gòu)已經(jīng)詳細(xì)介紹過,不在贅述。
Caching緩存在Ember中是非常重要的,但是有一點(diǎn)需要注意的是不要把太多數(shù)據(jù)緩存到store中,數(shù)據(jù)量太大瀏覽器受不了!緩存的作用是非常明顯的,前面也介紹了他的作用,特別是在請(qǐng)求數(shù)據(jù)的時(shí)候,如果能在緩存中獲取的則立即返回到調(diào)用處,只有在緩存中查不到的數(shù)據(jù)才發(fā)請(qǐng)求到服務(wù)端,通常是第一次獲取的數(shù)據(jù)的時(shí)候緩存沒有則需要發(fā)請(qǐng)求到服務(wù)端。也正是有了緩存Ember才能快速把數(shù)據(jù)的變化響應(yīng)到模板上。
到此主要核心的概念介紹完畢了,不算多,但是認(rèn)真看下來還是很有益的!!
下面接著是如何實(shí)踐了……
創(chuàng)建數(shù)據(jù)庫本例子使用的是MySQL數(shù)據(jù)庫,有關(guān)數(shù)據(jù)庫的安裝以及使用不在本文講解范圍,請(qǐng)自行學(xué)習(xí)!
建表怎么建表我也不說了,下面直接貼建表的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;
創(chuàng)建一個(gè)名為user的數(shù)據(jù)表。
創(chuàng)建服務(wù)端如何在ember項(xiàng)目中創(chuàng)建服務(wù)端程序呢?ember提供了創(chuàng)建的命令。
ember g server
創(chuàng)建完畢之后再按照開始介紹的依賴插件。
npm install mysql-node npm install body-parser npm install supervisor
創(chuàng)建的是一個(gè)node服務(wù)端程序,運(yùn)行的端口也是4200,不需要另外手動(dòng)去啟動(dòng)node服務(wù),只要ember項(xiàng)目運(yùn)行了會(huì)自動(dòng)運(yùn)行起來的。
到此所有的原料都準(zhǔn)備好了,下面驗(yàn)證一下項(xiàng)目是否還能正常運(yùn)行。啟動(dòng)項(xiàng)目,然后在瀏覽器打開http://localhost:4200。還能看到Welcome to Ember說明是成功的!
有了原料開始做菜吧?。?!
編寫user模塊 更改URL方式為了不使服務(wù)端和Ember請(qǐng)求URL沖突修改了URL的默認(rèn)方式,修改config/environment.js的第8行代碼為如下:
locationType: "hash",
auto改為hash。訪問Ember項(xiàng)目的URL則需要注意:http://localhost:4200/users改為http://localhost:4200/#/users。增加一個(gè)#號(hào)。
獲取數(shù)據(jù)、顯示數(shù)據(jù)首先簡(jiǎn)單列出數(shù)據(jù)庫數(shù)據(jù)。
用戶列表
# | 用戶名 | 郵箱 |
---|---|---|
{{user.id}} | {{user.username}} | {{user.email}} |
// app/routes/users.js import Ember from "ember"; export default Ember.Route.extend({ model() { return this.store.findAll("user"); } });
目前項(xiàng)目還沒連接到任何數(shù)據(jù)庫,也沒有使用自定義的適配器,如果直接執(zhí)行http://localhost:4200/#/users可以在控制臺(tái)看到是會(huì)報(bào)錯(cuò)的。那么下一步該如何處理呢??
加入適配器 使用RESTAdapter先從適配器下手!在前面已經(jīng)創(chuàng)建好了適配器,如果是2.0之后的項(xiàng)目默認(rèn)會(huì)創(chuàng)建JSONAPIAdapter這個(gè)適配器所接收、發(fā)送的數(shù)據(jù)格式都必須符合jsonapi規(guī)范,否則會(huì)報(bào)錯(cuò),無法正常完成數(shù)據(jù)的交互。不過為了簡(jiǎn)便我們先不使用這個(gè)適配器,改用另一個(gè)簡(jiǎn)單的適配器RESTAdapter,這個(gè)適配器不是需要遵循jsonapi規(guī)范,只要自己約定好前后端的數(shù)據(jù)格式即可。
// app/adapters/application.js // import JSONAPIAdapter from "ember-data/adapters/json-api"; import DS from "ember-data"; export default DS.RESTAdapter.extend({ });
手動(dòng)修改好之后的適配器還不能起作用,這個(gè)適配器并沒有連接到任何的后端服務(wù),如果你想連接到你的服務(wù)上需要使用屬性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" });
等待項(xiàng)目重啟完畢,仍然是訪問http://localhost:4200/#/user,在控制臺(tái)仍然看到前面的錯(cuò)誤,截圖如下:
為何還是錯(cuò)誤呢?如果能看到錯(cuò)誤說明你的程序是正確,到目前為止還沒提供任何的后端服務(wù),雖然前面使用ember g server創(chuàng)建了node后端服務(wù),但是并沒有針對(duì)每個(gè)請(qǐng)求做處理。當(dāng)你訪問路由user在進(jìn)入回到model時(shí)候會(huì)發(fā)送請(qǐng)求獲取所有模型user數(shù)據(jù),請(qǐng)求首選轉(zhuǎn)到Ember Data(store),但是在store中并沒有,然后請(qǐng)求繼續(xù)轉(zhuǎn)到適配器RESTAdapter,適配器會(huì)構(gòu)建URL得到GET請(qǐng)求http://localhost:4200/users,至于是如何構(gòu)建URL的請(qǐng)看build url method。這個(gè)請(qǐng)求可以在報(bào)錯(cuò)的信息中看到。但是為何會(huì)報(bào)錯(cuò)呢?很正常,因?yàn)槲业暮蠖朔?wù)并沒響應(yīng)這個(gè)請(qǐng)求。下面針對(duì)這個(gè)請(qǐng)求做處理。
修改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")); // 對(duì)象轉(zhuǎn)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 })); // 處理請(qǐng)求 http://localhost:4200/user app.get("/users", function(req, res) { // 返回三個(gè)對(duì)象 res.status(200).send({ users: [ { id: 1, username: "ubuntuvim", email: "[email protected]" }, { id: 2, username: "ddlisting.com", email: "[email protected]" }, { id: 3, username: "www.ddlising.com", email: "[email protected]" } ] }); }); };
在服務(wù)端增加了一個(gè)node請(qǐng)求處理,攔截/users這個(gè)請(qǐng)求。對(duì)于express不是本文的重點(diǎn),請(qǐng)自行學(xué)習(xí),網(wǎng)址expressjs.com。如果你使用的是其他語言的服務(wù)端程序,那么你只需要返回的json格式為:{"modelName":[{"id":1,"屬性名":"屬性值","屬性名2":"屬性值2"},{"id":2,"屬性名3":"屬性值3","屬性名4":"屬性4"}]},只需要格式正確適配器就能正確解析返回的數(shù)據(jù)。
另外再多介紹一個(gè)屬性namespace,這個(gè)屬性是用于定義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也需要做相應(yīng)的處理,需要在攔截的請(qǐng)求上加前綴,比如下面的代碼。
// 處理請(qǐng)求 http://localhost:4200/api/v1/user app.get("/api/v1/users", function(req, res) { // 返回三個(gè)對(duì)象 res.status(200).send({ users: [ { id: 1, username: "ubuntuvim", email: "[email protected]" }, { id: 2, username: "ddlisting.com", email: "[email protected]" }, { id: 3, username: "www.ddlising.com", email: "[email protected]" } ] }); });
之前面唯一不同的就是請(qǐng)求的URL不一樣了,原來是http://localhost:4200/users改為http://localhost:4200/api/v1/users。那么這樣做的好處是什么呢?當(dāng)你的后端的API更新的時(shí)候這個(gè)設(shè)置是非常有用的,只需要設(shè)置命名前綴就能適應(yīng)不用版本的API。
項(xiàng)目重啟之后,再次進(jìn)入到路由users可以看到返回的3條數(shù)據(jù)。如下截圖:
到此,我想你應(yīng)該知道個(gè)大概了吧??!更多有關(guān)適配器的介紹請(qǐng)看下面的2篇博文:
adapter與serializer使用示例一
adapter與serializer使用示例二
使用JSONAPIAdapter使用JSONAPIAdapter適配器和使用RESTAdapter適配器有何不同呢?我覺得最重要的一點(diǎn)是:數(shù)據(jù)規(guī)范。JSONAPIAdapter適配器要求交互的數(shù)據(jù)格式必須遵循jsonapi規(guī)范,否則是不能完成數(shù)據(jù)交互的。要求高了相應(yīng)的你的處理代碼也相應(yīng)的要復(fù)雜。下面我們改用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。如果你不修改后端的服務(wù)那么控制臺(tái)可以看到報(bào)錯(cuò)信息。
從截圖當(dāng)中可以清楚地看到報(bào)錯(cuò)出來的錯(cuò)誤,must return a valid JSON API document必須是一個(gè)有效jsonapi文檔。要修復(fù)好這個(gè)錯(cuò)誤也很簡(jiǎn)單,只需要滾吧后端服務(wù)返回的數(shù)據(jù)格式改成jsonapi的就行了。請(qǐng)看下面的代碼:
// 處理請(qǐng)求 http://localhost:4200/user app.get("/api/v1/users", function(req, res) { // 返回三個(gè)對(duì)象 // res.status(200).send({ // users: [ // { // id: 1, // username: "ubuntuvim", // email: "[email protected]" // }, // { // id: 2, // username: "ddlisting.com", // email: "[email protected]" // }, // { // id: 3, // username: "www.ddlising.com", // email: "[email protected]" // } // ] // }); // 構(gòu)建jsonapi對(duì)象 var input = { data: [ { id: "1", type: "user", //對(duì)應(yīng)前端程序中模型的名字 attributes: { // 模型中的屬性鍵值對(duì) username: "ubuntuvim", property: true, email: "[email protected]", property: true } }, { id: "2", type: "user", //對(duì)應(yīng)前端程序中模型的名字 attributes: { // 模型中的屬性鍵值對(duì) username: "ddlisting.com", property: true, email: "[email protected]", property: true } }, { id: "3", type: "user", //對(duì)應(yīng)前端程序中模型的名字 attributes: { // 模型中的屬性鍵值對(duì) username: "www.ddlising.com", property: true, email: "[email protected]", property: true } } ] }; res.status(200).send(JSON.stringify(input)); });
注:為了構(gòu)建jsonapi對(duì)象更加簡(jiǎn)便另外在安裝一個(gè)插件: npm install jsonapi-parse。安裝完畢后手動(dòng)關(guān)閉再重啟項(xiàng)目。然后再次進(jìn)入路由users可以看到與前面的結(jié)果一樣,正確了顯示后端返回的數(shù)據(jù)。
到此,我相信讀者應(yīng)該能明白這兩個(gè)適配器之間的差別了!需要注意的是Ember.js2.0版本之后JSONAPIAdapter作為默認(rèn)的適配器,也就是說平常如果你沒有自定義任何適配器那么Ember Data會(huì)默認(rèn)使用的是JSONAPIAdapter適配器。所以如果你沒有使用其他的適配器那么你的后端返回的數(shù)據(jù)格式必須是遵循jsonapi規(guī)范的。另外在路由users.js中使用到Ember Data提供的方法findAll("modelName"),我想從中你也應(yīng)該明白了Ember Data是何等重要了吧
看到這里不知道讀者是否已經(jīng)明白適配器和后端服務(wù)的關(guān)聯(lián)關(guān)系?如果有疑問請(qǐng)給我留言。
文中所說的后端就是我的node程序(放在server目錄下),前端就是我的Ember.js項(xiàng)目。
下面就是再結(jié)合數(shù)據(jù)庫。
加入數(shù)據(jù)庫其實(shí)到這步加不加數(shù)據(jù)庫已經(jīng)不那么重要了!重要把服務(wù)端返回的數(shù)據(jù)改成從數(shù)據(jù)庫讀取就完了。我就簡(jiǎn)單講解了。
連接MySQL連接MySQL的工作交給前面已經(jīng)安裝好的node-mysql,如果還沒安裝請(qǐng)執(zhí)行命令npm install mysqljs/mysql進(jìn)行安裝。繼續(xù)修改后端服務(wù)代碼server/index.js。
module.exports = function(app) { // 與之前的內(nèi)容不變 // // 引入MySQL模塊 var mysql = require("mysql"); // 獲取連接對(duì)象 var conn = mysql.createConnection({ host: "localhost", user: "root", password: "", // 開啟debug,可以在啟動(dòng)ember項(xiàng)目的終端看到更多詳細(xì)的信息 database: "test" }); // 處理請(qǐng)求 http://localhost:4200/user app.get("/api/v1/users", function(req, res) { var jsonArr = new Array(); // 打開數(shù)據(jù)庫連接 conn.connect(); //查詢數(shù)據(jù) conn.query("select * from user", function(err, rows, fields) { if (err) throw err; //遍歷返回的數(shù)據(jù)并設(shè)置到返回的json對(duì)象中 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 }); }); // 關(guān)閉數(shù)據(jù)庫連接 conn.end(); }); };
相比之前的代碼只是引入了mysql,增加連接對(duì)象聲明,然后在請(qǐng)求處理方法里查詢數(shù)據(jù),默認(rèn)在數(shù)據(jù)庫初始化了3條數(shù)據(jù),如下截圖,另外 為了簡(jiǎn)單起見我仍然使用的是RESTAdapter適配器,這樣處理也相對(duì)簡(jiǎn)單。 獲取連接對(duì)象的代碼應(yīng)該不用過多解釋了,就是填寫你本地連接數(shù)據(jù)庫的對(duì)應(yīng)配置信息就行了。
記得修改適配器為RESTAdapter。
重啟項(xiàng)目。進(jìn)入路由users可以看到數(shù)據(jù)庫的數(shù)據(jù)正確顯示出來了。
CRUD操作對(duì)于CRUD操作都舉一個(gè)例子,由于前面已經(jīng)介紹過findAll查詢就不在此介紹CRUD中的R了。下面就對(duì)另外三個(gè)做一個(gè)例子:
更多有關(guān)數(shù)據(jù)的操作請(qǐng)看Ember.js 入門指南——新建、更新、刪除記錄。
為了方便演示再增加幾個(gè)路由和模板。
ember g template users/index ember g route users/new ember g route users/edit
上述3個(gè)命令創(chuàng)建了三個(gè)users的子路由和子模板。
新增、更新由于項(xiàng)目使用的是Ember Data,增加數(shù)據(jù)也是很簡(jiǎn)單的,直接調(diào)用createRecord()創(chuàng)建一個(gè)record之后再調(diào)用save()方法保存到服務(wù)器。
另外新增和更新的處理方式相似,就直接寫在一個(gè)方法內(nèi)。
// 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", "請(qǐng)輸入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", "請(qǐng)輸入username和email!"); } } } } });
新增和修改處理是相似的,根據(jù)id是否為空判斷是否是新增還是更新。
hbs:user-form.hbs{{! 新增、修改都用到的表單,提出為公共部分}}{{title}}
{{#if tipInfo}}{{#link-to "users" class="btn btn-primary"}}返回{{/link-to}}
{{tipInfo}}{{/if}}
// app/routes/users/edit.js import Ember from "ember"; export default Ember.Route.extend({ // 根據(jù)id獲取某個(gè)記錄 model(params) { return this.store.findRecord("user", params.user_id); } });
點(diǎn)擊“編輯”的時(shí)候需要根據(jù)被點(diǎn)擊記錄的id查詢數(shù)據(jù)詳情,并返回到編輯頁面。
{{! 增加數(shù)據(jù)的表單}} {{user-form title="新增user" store=store model=model}}
{{! 修改數(shù)據(jù)的表單}} {{user-form title="修改user" store=store model=model}}
提取新增和修改這兩個(gè)模板的相同代碼為一個(gè)組件,兩個(gè)模板都調(diào)用組件。
后端處理代碼與前端對(duì)應(yīng)的要有相應(yīng)的后端處理服務(wù),增加2個(gè)路由監(jiān)聽,一個(gè)是監(jiān)聽post提交(新增),一個(gè)是put提交(更新)。
// 處理請(qǐng)求 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); // 打開數(shù)據(jù)庫連接 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 { //沒有數(shù)據(jù)返回一個(gè)空的 // 返回前端 res.status(200).send({ users: { id: "", username: "", email: "" } }); } }); console.log("sql: " + query.sql); conn.release(); //釋放連接,放回到連接池 }); }); // 處理請(qǐng)求 POST http://localhost:4200/users/id 根據(jù)id更新某個(gè)數(shù)據(jù) 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(); // 打開數(shù)據(jù)庫連接 pool.getConnection(function(err, conn) { // 參數(shù)的次序要與SQL語句的參數(shù)次序一致 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("更新出錯(cuò):"+err); throw err; } //遍歷返回的數(shù)據(jù)并設(shè)置到返回的json對(duì)象中,通常情況下只有一個(gè)數(shù)據(jù),直接取第一個(gè)數(shù)據(jù)返回 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 { //沒有數(shù)據(jù)返回一個(gè)空的 // // 返回前端 // res.status(200).send({ // users: { // id: "", // username: "", // email: "" // } // }); // } console.log("SQL: " + query.sql); }); conn.release(); //釋放連接,放回到連接池 }); });
為何新增對(duì)應(yīng)的是post方法,更新對(duì)應(yīng)的是put方法,請(qǐng)看the rest adapter的詳細(xì)介紹(主要是第一個(gè)表格的內(nèi)容)。
簡(jiǎn)單測(cè)試點(diǎn)擊右上角的新增按鈕進(jìn)入新增界面。
進(jìn)入新增界面后輸入相應(yīng)信息(我就不做數(shù)據(jù)的格式校驗(yàn)了,有需要自己校驗(yàn)數(shù)據(jù)格式)。然后點(diǎn)擊“保存”,保存成功會(huì)有提示信息。
點(diǎn)擊右上角的“返回”回到主列表頁面,查看新增的數(shù)據(jù)是否保存成功。
可以看到剛剛新增的數(shù)據(jù)已經(jīng)顯示在列表上,為了進(jìn)一步驗(yàn)證數(shù)據(jù)已經(jīng)保存成功,直接查看數(shù)據(jù)庫。
可以看到數(shù)據(jù)庫也已經(jīng)成功保存了剛剛新增的數(shù)據(jù)。
修改的測(cè)試方式我就不啰嗦了,點(diǎn)擊列表上的修改按鈕進(jìn)入修改頁面,修改后保存既可以,請(qǐng)自行測(cè)試。
刪除刪除處理相比新增更加簡(jiǎn)單,直接發(fā)送一個(gè)delete請(qǐng)求即可。
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 }); } } });
用戶列表
{{#link-to "users.new" class="btn btn-primary"}}新增{{/link-to}}
# | 用戶名 | 郵箱 | 操作 |
---|---|---|---|
{{user.id}} | {{user.username}} | {{user.email}} | {{#link-to "users.edit" user.id}}修改{{/link-to}} | 刪除 |
這段代碼的與前面的代碼基本一致,就是增加了刪除。
后端處理在后端增加一個(gè)監(jiān)聽刪除的路由。
// 處理請(qǐng)求 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); // 打開數(shù)據(jù)庫連接 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(); //釋放連接,放回到連接池 }); });測(cè)試刪除
測(cè)試刪除很簡(jiǎn)單,直接在列表上點(diǎn)擊“刪除”按鈕即可刪除一條記錄。界面和數(shù)據(jù)庫的截圖我就不貼出來了,自己動(dòng)手測(cè)試就知道了!!
數(shù)據(jù)可以正確刪除,但是,刪除之后控制臺(tái)會(huì)報(bào)如下錯(cuò)誤:
找了官網(wǎng)文檔the rest adapter delete record按照官網(wǎng)的文檔處理仍然報(bào)錯(cuò)!目前還沒找到好的處理方法,不知道是哪里出了問題,如果讀者知道請(qǐng)告訴我,謝謝。
到此CRUD操作也完成了,不足的就是在處理刪除的時(shí)候還是有點(diǎn)問題,目前還沒找到覺得辦法!但是總的來說對(duì)于CRUD的操作都是這么處理的,調(diào)用的方法也都是上述代碼所使用的方法。
未完待續(xù)……還差分頁沒完成。
總結(jié)文章寫到這里已經(jīng)把我所想的內(nèi)容介紹完畢了,不知道讀者是否看明白了。其中主要理解的知識(shí)點(diǎn)是:
Ember Data和adapter、record、model的關(guān)系
如何自定義適配器
如何根據(jù)Ember前端請(qǐng)求編寫后端處理
CRUD操作
分頁處理(目前還沒整合進(jìn)來)
明白了上述幾點(diǎn),本文的目的也達(dá)到了!如何有疑問歡迎給我留言,也期待著讀者能給我解答刪除報(bào)錯(cuò)的問題!
文章源碼如果有需要?dú)g迎star或者fork學(xué)習(xí)。下面是源碼地址:
https://github.com/ubuntuvim/emberData-adapter-database,歡迎follow我,一起學(xué)習(xí)交流!我在全球最大的同性交友網(wǎng)站等你哦!!
參考網(wǎng)址ember.js
ember-cli
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/91636.html
摘要:由于能力有限本示例不會(huì)完全自定義適配器和序列化器,示例仍然是使用官方推薦方式,重寫或者擴(kuò)展以實(shí)現(xiàn)自定適配器和序列化器。在序列化器中調(diào)用響應(yīng)請(qǐng)求的方法格式化返回的數(shù)據(jù)。上述就是的一個(gè)簡(jiǎn)單實(shí)用示例。 文章來源:http://xcoding.tech/tags/Emberjs歡迎訪問源網(wǎng)站Ember Teach,Ember Teach致力于為您提供最權(quán)威、最前沿的Ember技術(shù)教程。。 ad...
摘要:就沒必要?jiǎng)优5?,?chuàng)建一個(gè)數(shù)據(jù)庫了執(zhí)行完后,在目錄下創(chuàng)建一個(gè)程序,自動(dòng)植入到當(dāng)前項(xiàng)目中,訪問的和與訪問域名端口一致。就沒必要?jiǎng)优5叮瑒?chuàng)建一個(gè)數(shù)據(jù)庫了本篇博文將為你介紹如何使用實(shí)現(xiàn)權(quán)限控制,我會(huì)創(chuàng)建一個(gè)簡(jiǎn)單的登錄示例加以說明。 文章來源:http://blog.ddlisting.com 官網(wǎng)對(duì)于登錄、用戶權(quán)限的介紹只有一段簡(jiǎn)單的說明,并沒有詳細(xì)說明如何使用service實(shí)現(xiàn)權(quán)限控制。下面...
摘要:在文件夾內(nèi)創(chuàng)建,內(nèi)容如下創(chuàng)建,內(nèi)容如下使用安裝依賴在的頭部加入調(diào)用命令,同時(shí)在你的默認(rèn)瀏覽器中打開。最后,我們更新下,給每個(gè)報(bào)道添加鏈接修改完畢地后,可以在瀏覽器中直接看到結(jié)果。 編者注:我們發(fā)現(xiàn)了有趣的系列文章《30天學(xué)習(xí)30種新技術(shù)》,正在翻譯,一天一篇更新,年終禮包。下面是第19天的內(nèi)容。 到目前為止,我們這一系列文章涉及了Bower、AngularJS、GruntJS、P...
摘要:創(chuàng)建模型并設(shè)置關(guān)聯(lián)關(guān)聯(lián)關(guān)系設(shè)置模型關(guān)系一個(gè)對(duì)應(yīng)多個(gè),一個(gè)對(duì)應(yīng)多個(gè)。手動(dòng)在中增加關(guān)聯(lián)關(guān)系。并且是實(shí)現(xiàn)了數(shù)據(jù)表之間的關(guān)聯(lián)關(guān)系,比如一個(gè)對(duì)應(yīng)多個(gè),如下圖。 文章來源:模型高級(jí)特性,引入模型關(guān)聯(lián)關(guān)系 接著前面五篇: 環(huán)境搭建以及使用Ember.js創(chuàng)建第一個(gè)靜態(tài)頁面 引入計(jì)算屬性、action、動(dòng)態(tài)內(nèi)容 模型,保存數(shù)據(jù)到數(shù)據(jù)庫 發(fā)布項(xiàng)目,加入CRUD功能 從服務(wù)器獲取數(shù)據(jù),引入組件 前言 ...
摘要:文章來源模型,保存數(shù)據(jù)到數(shù)據(jù)庫環(huán)境搭建以及使用創(chuàng)建第一個(gè)靜態(tài)頁面引入計(jì)算屬性動(dòng)態(tài)內(nèi)容繼續(xù)為讀者介紹如何使用構(gòu)建一個(gè)完整的復(fù)雜的項(xiàng)目。 文章來源:模型,保存數(shù)據(jù)到數(shù)據(jù)庫 環(huán)境搭建以及使用Ember.js創(chuàng)建第一個(gè)靜態(tài)頁面 引入計(jì)算屬性、action、動(dòng)態(tài)內(nèi)容 繼續(xù)為讀者介紹如何使用Ember構(gòu)建一個(gè)完整的、復(fù)雜的項(xiàng)目。 第一個(gè)Ember.js模型 在前面兩篇中實(shí)現(xiàn)了如何獲取界面輸入的...
閱讀 2239·2021-11-22 13:52
閱讀 3876·2021-11-10 11:36
閱讀 1418·2021-09-24 09:47
閱讀 1096·2019-08-29 13:54
閱讀 3371·2019-08-29 13:46
閱讀 1952·2019-08-29 12:16
閱讀 2119·2019-08-26 13:26
閱讀 3477·2019-08-23 17:10