文章來源:模型,保存數據到數據庫javascript
繼續爲讀者介紹如何使用Ember構建一個完整的、複雜的項目。html
在前面兩篇中實現瞭如何獲取界面輸入的郵箱值,可是並無真正保存到數據,僅僅只是獲取界面輸入的值並顯示出來。在本篇中將爲讀者演示如何保存數據到數據庫中。可是我並不會去建立一個數據庫,而是使用firebase,更多有關firebase的信息請自行查閱資料學習(若是訪問firebase官網很慢或者是沒法訪問那麼你須要fanqiang)!java
言歸正傳,回到Ember的模型介紹中來。簡單講Ember的模型其實就是一個與數據表對應的一個實體類,與Java
中的JavaBean
有點相似。
建立一個模型也很是簡單,能夠直接使用Ember CLI命令建立,下面的命令就是用於建立模型類,並在模型中增長一個string
類型的屬性email
。git
ember g model invitation email:string
命令執行完畢以後能夠在項目對應目錄下看到建立的文件app/models/invitaction.js
,文件內如以下:github
// app/models/invitation.js import DS from 'ember-data'; export default DS.Model.extend({ email: DS.attr('string') });
有了模型類以後修改控制器index.js
的代碼,加入模型,經過模型來保存數據對象。web
// app/controller/index.js import Ember from 'ember'; export default Ember.Controller.extend({ headerMessage: 'Coming Soon', responseMessage: '', // 設置默認值爲空字符串 emailAddress: '', // 設置默認值爲空字符串 // 使用正則表達式判斷郵箱格式,若是正確則返回true反之返回false isValid: Ember.computed.match('emailAddress', /^.+@.+\..+$/), // 把計算屬性isValid綁定到isDisabled上 isDisabled: Ember.computed.not('isValid'), //當`disabled=false`時按鈕可用,因此正好須要取反 actions: { saveInvitation: function() { const email = this.get('emailAddress'); // 建立一個模型對象 const newInvitaction = this.store.createRecord('invitation', { email: email }); newInvitaction.save(); //保存模型對象到store中 this.set('responseMessage', `Thank you! We've just saved your email address: ${this.get('emailAddress')}`); // 狀況輸入框內容 this.set('emailAddress', ''); } } });
等待項目從新啓動,在界面輸入正確的郵箱,點擊按鈕,能夠在瀏覽器控制檯看到以下錯誤信息:正則表達式
爲什麼會出現這些錯誤呢??其實很簡單,咱們並無對應的後臺服務區處理數據,目前僅僅是提交了數據而已,那麼怎麼處理呢?咱們引入一個很是好用的數據庫——firebase。firebase爲Ember提交了很是豐富的API,咱們不須要再本身寫後臺的處理程序,能夠直接調用firebase提供的API便可完成數據的CRUD操做。更多有關firebase的使用教程請看EmberFire Guide,在這個參考文檔上詳細介紹了firebase如何與Ember整合,Ember如何調用firebase提供的API。sql
下面簡單介紹如何把firebase整合到本項目中。數據庫
ember install emberfire
,會自動建立文件app/adapters/application.js
config/environment.js
,在文件中增長firebase的配置。
圖1ubuntu
config/environment.js
配置代碼以下:
var ENV = { modulePrefix: 'library-app', environment: environment, contentSecurityPolicy: { 'connect-src': "'self' https://auth.firebase.com wss://*.firebaseio.com" }, firebase: 'https://YOUR-FIREBASE-NAME.firebaseio.com/', //改爲本身在firebase上APP的地址 baseURL: '/', locationType: 'auto', EmberENV: { FEATURES: { // Here you can enable experimental features on an ember canary build // e.g. 'with-controller': true } }, APP: { // Here you can pass flags/options to your application instance // when it is created } }; // ……其餘代碼省略
注意上述代碼中的第5行,firebase
屬性的值是本身在firebase申請的APP的URL。必定記得要修改!!!
修改完成以後手動重啓項目,記得是手動關閉終端運行的項目(ctrl+c
關閉),而後再使用命令ember s
啓動項目。不然新安裝的emberfire
沒法起做用。
等待項目啓動完成,若是啓動過程當中沒有出現錯誤,說明emberfire
安裝成功!
而後激動的時刻到了,在首頁輸入正確的郵箱,點擊按鈕,能夠看到瀏覽器控制來不會報錯了!而且在firebase官網的APP中看到剛剛新增的郵箱!!
注意:點擊按鈕提交後可能看到界面沒有任何反應,先別急,因爲firebase是外國的東西,在天朝訪問都是比較慢,你懂的。提交後到響應回來可能比較慢。
從瀏覽器控制檯打印的日誌能夠看出向firebase發送請求,截圖以下:
而且在界面上提示了保存成功的信息!
最後在firebase官網上能夠查看到剛剛提交數據。
能夠感覺到firebase的強大了吧!咱們幾乎沒有作任何處理數據就直接保存到firebase了,而且會自動根據模型建立數據,不過須要注意的是咱們在模型定義中並不須要定義id
屬性,firebase會自動生成一個惟一的id
屬性值,截圖中的-KEr3XwUQjgLjb5yx0dp
就是id
屬性值。
到此,數據的保存工做完成了,藉助firebase大大簡化了本身須要處理的東西,不須要本身建立數據庫、數據表、以及保存數據sql等等!不知道你是否看明白了,若是有疑問請及時給我留言,我會盡力爲你解答!
promise(承諾)在JavaScript中是一個異步特性。這個特性還在完善之中,更多有關promise的介紹請看promises-book或者Mozilla MDN Promise。
在前面保存數據的代碼中save()
方法返回值就是一個promise
,咱們能夠根據save()
方法的返回值作不一樣的處理,好比保存失敗時候的處理。
saveInvitation: function() { const email = this.get('emailAddress'); // 建立一個模型對象 const newInvitaction = this.store.createRecord('invitation', { email: email }); //保存模型對象到store中 newInvitaction.save().then(function(msg) { console.log('保存成功。'); }, function(reason) { console.log('保存失敗!'); }); this.set('responseMessage', `Thank you! We've just saved your email address: ${this.get('emailAddress')}`); // 狀況輸入框內容 this.set('emailAddress', ''); }
若是你看過有關promise的介紹那麼理解上述代碼應該是很簡單的,在方法then()
中第一個函數(參數)會在save()
執行成功的時候執行,第二個函數(參數)會在save()
執行失敗的時候執行。明白這個以後咱們再修改控制器index.js
的代碼。咱們把提示信息放在save()
執行成功的時候執行方法中。
saveInvitation: function() { const email = this.get('emailAddress'); // 建立一個模型對象 const newInvitaction = this.store.createRecord('invitation', { email: email }); //保存模型對象到store中 newInvitaction.save().then(function(msg) { this.set('responseMessage', `Thank you! We've just saved your email address: ${this.get('emailAddress')}`); // 狀況輸入框內容 this.set('emailAddress', ''); }, function(reason) { this.set('responseMessage', `Saved: ${this.get('emailAddress')} failed!`); // 狀況輸入框內容 this.set('emailAddress', ''); }); }
等待項目自動重啓完成,在界面輸入正確郵箱,提交數據,此時並無出現任何反應,而且會在瀏覽器控制檯看到以下錯誤,
這又是什麼緣由呢?其實緣由很簡單,由於this
做用域問題,因爲是在then()
內部使用了this
致使此時的this
指向的並非控制器類了,只有在Ember的上下文中才能使用set()
方法!咱們用一個臨時變量解決這個問題,代碼修改成以下:
// app/controller/index.js import Ember from 'ember'; export default Ember.Controller.extend({ headerMessage: 'Coming Soon', responseMessage: '', // 設置默認值爲空字符串 emailAddress: '', // 設置默認值爲空字符串 // 使用正則表達式判斷郵箱格式,若是正確則返回true反之返回false isValid: Ember.computed.match('emailAddress', /^.+@.+\..+$/), // 把計算屬性isValid綁定到isDisabled上 isDisabled: Ember.computed.not('isValid'), //當`disabled=false`時按鈕可用,因此正好須要取反 actions: { saveInvitation: function() { const email = this.get('emailAddress'); // 建立一個模型對象 const newInvitaction = this.store.createRecord('invitation', { email: email }); var _this = this; //保存模型對象到store中 newInvitaction.save().then(function(msg) { _this.set('responseMessage', `Thank you! We've just saved your email address: ${_this.get('emailAddress')}`); // 狀況輸入框內容 _this.set('emailAddress', ''); }, function(reason) { _this.set('responseMessage', `Saved: ${_this.get('emailAddress')} failed!`); // 狀況輸入框內容 _this.set('emailAddress', ''); }); } } });
等待項目自動重啓完成,在頁面輸入正確的郵箱並提交,能夠看到此時效果與以前是同樣的,而後去firebase查看結果,也是能夠看到新增的數據。
雖然是用臨時變量方式能夠解決因爲this
做用域問題,可是還有更加優美的解決辦法,現在幾乎全部新版的瀏覽器引擎已經支持ES2015,可使用ES2015的=>
操做符解決this
做用域問題,請看下面的處理代碼:
saveInvitation: function() { const email = this.get('emailAddress'); // 建立一個模型對象 const newInvitaction = this.store.createRecord('invitation', { email: email }); //保存模型對象到store中 newInvitaction.save().then((response) => { console.log('response = ' + response); this.set('responseMessage', `Thank you! We've just saved your email address: ${response.get('id')}`); // 狀況輸入框內容 this.set('emailAddress', ''); }, (reason) => { this.set('responseMessage', `Saved: ${this.get('emailAddress')} failed!`); // 狀況輸入框內容 this.set('emailAddress', ''); }); }
使用ES2015的特性以後不只解決了this
做用域問題,並且連關鍵字function
都不須要了,使用=>
操做會自動把外層this
所指的對象傳遞到函數內部,而且修改了保存成功時的提示信息,使用${response.get('id')}
從firebase響應的數據中獲取到保存成功後返回的id
值,返回的response
就是一個模型invitation
的對象,可使用get()
方法獲取對象值。
再次測試,若是項目代碼沒有誤那麼你能夠獲得以下截圖的提示信息(id
值跟你的是不同的),
若是你對this
不是很懂,請看認真看下面文章的解釋:
前面已經介紹瞭如何整合firebase到項目中,而且已經成功保存增長的數據。能夠在firebase上看到全部數據,咱們建立一個後臺頁面去管理這些數據。
下面建立一個子路由和路由對應的模板頁面,仍然是使用Ember CLI命令建立,命令以下:
ember g route admin/invitaction
命令執行完畢後會獲得一個路由文件(app/routes/admin/invitaction.js
)和一個模板文件(app/templates/admin/invitaction.hbs
),命令會自動建立文件夾admin
,子路由和子模板會放在子子目錄下。
而後在首頁增長菜單連接,修改navbar.hbs
模板。
<!-- app/templates/navbar.hbs --> <nav class="navbar navbar-inverse"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#main-navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> {{#link-to 'index' class="navbar-brand"}}Library App{{/link-to}} </div> <div class="collapse navbar-collapse" id="main-navbar"> <ul class="nav navbar-nav"> {{#link-to 'index' tagName="li"}}<a href>Home</a>{{/link-to}} {{#link-to 'about' tagName="li"}}<a href>About</a>{{/link-to}} {{#link-to 'contact' tagName="li"}}<a href>Contact</a>{{/link-to}} </ul> <!-- 後臺管理頁面連接 --> <ul class="nav navbar-nav navbar-right"> <li class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin<span class="caret"></span></a> <ul class="dropdown-menu"> {{#link-to 'admin.invitation' tagName="li"}}<a href="">Invitations</a>{{/link-to}} </ul> </li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav>
代碼{{#link-to 'admin.invitation' tagName="li"}}<a href="">Invitations</a>{{/link-to}}
中admin.invitation
是一個嵌套路由或者說是子路由。更多有關路由嵌套問題請看Ember.js 入門指南之十三{{link-to}} 助手。
在模板中使用表格遍歷顯示全部的郵箱數據。修改模板invitaction.hbs
。
<!-- app/templates/admin/invitations.hbs --> <h1>Invitations</h1> <table class="table table-bordered table-striped"> <thead> <tr> <th>ID</th> <th>E-mail</th> </tr> </thead> <tbody> {{#each model as |invitation|}} <tr> <th>{{invitation.id}}</th> <td>{{invitation.email}}</td> </tr> {{/each}} </tbody> </table>
上述代碼中{{#each}}{{/each}}
是Ember提供的遍歷表達式,此表達式用於遍歷數組數據。本例子中用戶遍歷從路由的model
回調中返回的數據。更多有關此表達式的介紹請看Ember.js 入門指南之十handlebars遍歷標籤。
修改路由app/routes/admin/invitations.js
在model
回調中獲取服務器(firebase)上的數據。
// app/routes/admin/invitations.js import Ember from 'ember'; export default Ember.Route.extend({ model() { return this.store.findAll('invitation'); } });
等待項目重啓完成,能夠在項目首頁導航欄的右側看到能夠點擊下拉的Admin菜單項,點擊菜單看到子菜單項「Invitation」,點擊「Invitation」進入到http://localhost:4200/admin/invitation。
在界面上能夠看到以前新增的全部郵箱信息和firebase自動生成的ID
屬性值(因爲firebase是老外的東西獲取數據會比較慢,數據顯示天然也會比較慢,稍等一會就在界面上看到了!)。若是你項目代碼沒問題也能夠看到以下的截圖。
到這一步咱們已經能夠完整的從服務器獲取數據,並以列表形式顯示在界面上。本教程的目標是建立一個簡單的圖書管理系統,前面的文章已經完成了相似於用戶註冊的功能,接下來咱們建立一個library
模型,用於保存書庫信息。
一樣是使用Ember CLI命令建立模型。
ember g model library name:string address:string phone:string
上述命令建立了一個包含三個屬性的模型,這三個屬性都是string
類型的數據。建立完模型以後再繼續建立三個模板,分別用戶顯示library列表新建library數據。
ember g template libraries ember g template libraries/index ember g template libraries/new
模板建立完畢以後手動在router.js
中增長路由配置,此次咱們不採用Ember CLI命令建立了!!!
// app/router.js import Ember from 'ember'; import config from './config/environment'; var Router = Ember.Router.extend({ location: config.locationType }); Router.map(function() { this.route('about'); this.route('contact'); this.route('admin', function() { this.route('invitation'); }); this.route('libraries', function() { this.route('new'); }); }); export default Router;
再更新首頁模板navbar.hbs
,增長一個菜單項「libraries」,其餘代碼不變。
<ul class="nav navbar-nav"> {{#link-to 'index' tagName="li"}}<a href="">Home</a>{{/link-to}} {{#link-to 'libraries' tagName="li"}}<a href="">Libraries</a>{{/link-to}} {{#link-to 'about' tagName="li"}}<a href="">About</a>{{/link-to}} {{#link-to 'contact' tagName="li"}}<a href="">Contact</a>{{/link-to}} </ul>
修改模板libraries.hbs
,增長菜單連接。
<!-- app/templates/libraries.hbs --> <h1>Libraries</h1> <div class="well"> <ul class="nav nav-pills"> {{#link-to 'libraries.index' tagName="li"}}<a href="">List all</a>{{/link-to}} {{#link-to 'libraries.new' tagName="li"}}<a href="">Add new</a>{{/link-to}} </ul> </div> <!-- 子模板new.hbs、index.hbs會渲染到outlet上 --> {{outlet}}
等待項目重啓完成,進入http://localhost:4200/libraries。能夠看到以下圖的界面
此時點擊界面的上的「List all」和「Add new」除了看到URL變化以外還沒任何效果,由於咱們的子模板libraries/index.hbs
、libraries/new.hbs
尚未任何內容,下面在這兩個模板中增長一些代碼。
<!-- app/templates/libraries/index.hbs --> <h2>List</h2> {{#each model as |library|}} <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">{{library.name}}</h3> </div> <div class="panel-body"> <p>Address: {{library.address}}</p> <p>Phone: {{library.phone}}</p> </div> </div> {{/each}}
<!-- app/templates/libraries/new.hbs --> <h2>Add a new local Library</h2> <div class="form-horizontal"> <div class="form-group"> <label class="col-sm-2 control-label">Name</label> <div class="col-sm-10"> {{input type="text" value=model.name class="form-control" placeholder="The name of the Library"}} </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">Address</label> <div class="col-sm-10"> {{input type="text" value=model.address class="form-control" placeholder="The address of the Library"}} </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">Phone</label> <div class="col-sm-10"> {{input type="text" value=model.phone class="form-control" placeholder="The phone number of the Library"}} </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default" {{action 'saveLibrary' model}}>Add to library list</button> </div> </div> </div>
模板new.hbs
是一個表單,用於新增數據,經過點擊按鈕「Add to library list」提交表單數據,表單數據由路由libraries/new.js
中的saveLibrary
方法處理,此時此方法還沒定義,在接下來的代碼中會定義。
在{{action}}
表達式中傳遞了一個參數model
處處理的後臺,表單中的其餘屬性會以model
的屬性方式傳遞到後臺,之因此能夠這樣作是由於在模板對應的路由中返回了一個空的library
對象,在接下來的路由libraries/new.js
將看到。
等待項目重啓完,在點擊「List all」和「Add new」能夠看到這兩個子模板的內容渲染到父模板libraries.hbs
的{{outlet}}
上。不過因爲並無在路由中獲取模型library
的數據因此「List all」頁面尚未任何數據,「Add new」頁面是第一個新增數據吧表單。
下面在路由libraries/index.js
中獲取library的數據,並在model
回調中返回到模板中遍歷顯示。
使用Ember CLI命令建立路由,建立過程會詢問是否覆蓋已經存在的模板文件,輸入n
選擇否。
ember g route libraries/index ember g route libraries/new
路由建立完成以後分別在這兩個路由中增長獲取數據的代碼。
// app/routes/libraries/index.js import Ember from 'ember'; export default Ember.Route.extend({ model() { return this.store.findAll('library'); } });
// app/routes/libraries/new.js import Ember from 'ember'; export default Ember.Route.extend({ model() { return this.store.createRecord('library'); }, actions: { // 處理模板上輸入的數據 saveLibrary(newLibrary) { newLibrary.save().then(() => this.transitionTo('libraries')); }, willTransition() { // rollbackAttributes() removes the record from the store // if the model 'isNew' this.controller.get('model').rollbackAttributes(); } } });
在此路由的model
回調中咱們建立了一個空的library
對象,並返回到模板頁面。這也是爲何能在模板中傳遞參數model
的緣由。
代碼中方法willTransition()
是Ember提供的內置方法,此方法的做用是當用戶離開當前URL時會清空未保存到服務器的library
數據,若是不重置model
Ember會在路由切換的時候自動保存數據到服務器上。
保存數據的方法saveLibrary()
寫的比較簡潔,它的做用是:先調用save()
方法保存數據,若是保存成功在調用方法transitionTo()
跳轉到路由libraries
下(library首頁),有關=>
語法的介紹請看Mozill MDN Arrow_functions。
在上述代碼中屢次是用了this.controller
,可是在路由中並無這個屬性controller
並且也沒有控制器文件app/controllers/libraries/new.js
,運行項目並不會報錯!這是爲何呢?這是由於Ember會自動生成一個虛擬的控制器並在保存在內存中,根據Ember的命名規則會自動生成一個與路由同名的控制器,
爲了驗證這個說法,打開瀏覽器的控制檯,在打開標籤「Ember」而後點擊左側的「/# Routes」,找到路由libraries
這一塊,能夠看到以下截圖的信息。
能夠看到每一個路由都對應着一個同名的控制器。
等待項目重啓完畢,開始驗證代碼是否實現了所設想的要求。
首先在新增頁面輸入以下截圖信息,而後點擊按鈕保存數據。
稍等片刻,等待數據保存完畢,能夠看到界面順利跳轉到了http://localhost:4200/libraries下,以下圖所示,而且看到了剛剛新增的數據,爲了驗證數據是否真的保存到服務器中,咱們進入到firebase的APP中查看,能夠看到數據以及保存到裏library下。
library數據列表頁面截圖
firebase上保存的library數據截圖
此時,若是你註釋了方法willTransition()
結果會是怎麼樣的呢!!若是沒有這個方法去重置model
,當你每次在「Add new」頁面輸入輸入而且沒有點擊「Add to library list」保存數,而後切換到其餘路由下(好比點擊「List all」切換到路由libraries
下)會自動保存一條數據到服務器。
libraries
下,能夠看到在「Add new」頁面添加的數據,以下圖所示,可是若是你手動刷新頁面後能夠發現這條數據不見了,而且在firebase上也沒有這條數據,可見這條數據僅僅是保存到Ember的store中,並無真正保存到服務器上。這樣的體驗是很是糟糕的!!
其中,實現重置model
的方式還有另一種更加合適的方法,代碼以下:
willTransition() { // rollbackAttributes() removes the record from the store // if the model 'isNew' // this.controller.get('model').rollbackAttributes(); let model = this.controller.get('model'); if (model.get('isNew')) { model.destroyRecord(); } }
本篇的內容到此所有介紹完畢!本篇咱們實現了數據的保存、顯示,特別是library數據的保存。數據的保存、顯示都須要與模型關聯,模型在Ember是一個很是重要的內容!但願讀者好好掌握模型。
下面兩個做業完成其中之一便可。本篇選擇第一個,第二個請讀者自行完成!動手纔是真理。
email
和message
的模型contact
,參考代碼 app/routes/contact.js
返回一個空對象到模板上,參考代碼 contact.js
,保存數據到firebase,參考代碼 app/models/contact.js
中,參考代碼 contact.js
中保存數據的代碼移動到同名的路由中contact.js
做業演示結果
當輸入的郵箱格式正確,而且message長度大於6時按鈕「send」纔可用。
保存成功後狀況輸入框,而且顯示提示信息。當切換路由後再進入到http://localhost:4200/contact會清空保存成功的提示信息。
後臺頁面成功顯示了新增的數據,即便手動刷新頁面數據也不會丟失,可見數據已經保存到firebase,在此再也不貼firebase上的數據截圖了!
爲了照顧懶人我把完整的代碼放在GitHub上,若有須要請參考參考。博文通過屢次修改,博文上的代碼與github代碼可能有出入,不過影響不大!若是你以爲博文對你有點用,請在github項目上給我點個star
吧。您的確定對我來講是最大的動力!!