文章來源:模型高級特性,引入模型關聯關係css
接着前面五篇:html
本篇主要是介紹模型直接的關聯關係,好比:一對1、一對多關係。會建立兩個模型author
和book
,設置它們的關係,並增長測試數據。git
關聯關係設置API:github
模型關係:一個library
對應多個book
,一個author
對應多個book
。關係圖以下:數據庫
使用Ember CLI命令建立模型。bootstrap
ember g model book title:string releaseYear:date library:belongsTo author:belongsTo ember g model author name:string books:hasMany
手動在library
中增長hasMany
關聯關係。ubuntu
import Model from 'ember-data/model'; import attr from 'ember-data/attr'; import { hasMany } from 'ember-data/relationships'; import Ember from 'ember'; export default Model.extend({ name: attr('string'), address: attr('string'), phone: attr('string'), books: hasMany('books'), isValid: Ember.computed.notEmpty('name'), });
ember g route admin/seeder
檢查router.js
看看路由是否成功建立。相關代碼以下:vim
// 其餘代碼不變,省略 this.route('admin', function() { this.route('invitations'); this.route('contacts'); this.route('seeder'); }); // 其餘代碼不變,省略
修改導航模板navbar.hbs
增長新建路由的入口連接。api
<ul class="dropdown-menu"> {{#nav-link-to 'admin.invitations'}}Invitations{{/nav-link-to}} {{#nav-link-to 'admin.contacts'}}Contacts{{/nav-link-to}} {{#nav-link-to 'admin.seeder'}}Seeder{{/nav-link-to}} </ul>
Ember.RSVP.hash()
在一個路由中返回多個模型的數據Ember支持在一個路由的model
回調中返回多個模型的數據。有關方法發API請看Ember.RSVP.hash()。瀏覽器
// app/routes/admin/seeder.js import Ember from 'ember'; export default Ember.Route.extend({ model() { return Ember.RSVP.hash({ libraries: this.store.findAll('library'), books: this.store.findAll('book'), authors: this.store.findAll('author') }) }, setupController(controller, model) { controller.set('libraries', model.libraries); controller.set('books', model.books); controller.set('authors', model.authors); } });
上述model()
回調中返回了三個模型的數據:library
、book
和author
。須要注意的是:上述代碼中方法Ember.RSVP.hash()
會發送3個請求,而且只有三個請求都成功纔會執行成功。
在setupController()
回調中,把三個模型分別設置到controller
中。
每一個路由內都內置了不少方法,好比前面介紹的model
、setupController
、renderTemplate
,這些都是內置在路由類中的方法,那麼這些方法調用次序又是如何的呢?請看下面的代碼:
// app/routes/test.js import Ember from 'ember'; export default Ember.Route.extend({ init() { debugger; }, beforeModel(transition) { debugger; }, model(params, transition) { debugger; }, afterModel(model, transition) { debugger; }, activate() { debugger; }, setupController(controller, model) { debugger; }, renderTemplate(controller, model) { debugger; } });
打開瀏覽器的debug模式並在執行到這個路由中http://localhost:4200/test。能夠看到方法的執行次序與上述代碼方法的次序是一致的。有關API請看下面網址的介紹:
建立一個組件用於顯示各個模型數據的總數。
ember g component number-box
組件建立完畢以後在組件類中增長css類,使用屬性classNames
設置。
// app/components/number-box.js import Ember from 'ember'; export default Ember.Component.extend({ classNames: ['panel', 'panel-warning'] });
而後在組件模板中增長代碼:
<!-- app/templates/components/number-box.hbs --> <div class="panel-heading"> <h3 class="text-center">{{title}}</h3> <h1 class="text-center">{{if number number '...'}}</h1> </div>
在修改app/templates/admin/seeder.hbs
<!-- app/templates/admin/seeder.hbs --> <h1>Seeder, our Data Center</h1> <div class="row"> <div class="col-md-4">{{number-box title="Libraries" number=libraries.length}}</div> <div class="col-md-4">{{number-box title="Authors" number=authors.length}}</div> <div class="col-md-4">{{number-box title="Books" number=books.length}}</div> </div>
等待項目重啓完成,進入到後臺的seeder下能夠看到三個小圓點,請記得,必定要在setupController
中設置數據,model
回調會自動從服務器獲取數據,obj.length
意思是調用length()
方法獲取數據長度,而後直接顯示到模板上,效果以下截圖,因爲後面兩個模型尚未數據因此顯示省略號。
前面已經介紹過屬性的傳遞,下面的代碼將爲讀者介紹一些更加高級的東西!!一大波代碼即未來臨!!!
ember g component seeder-block ember g component fader-label
// app/components/seeder-block.js import Ember from 'ember'; export default Ember.Component.extend({ actions: { generateAction() { this.sendAction('generateAction'); }, deleteAction() { this.sendAction('deleteAction'); } } });
<!-- app/templates/components/seeder-block.hbs --> <div class="row"> <div class="col-md-12"> <h3>{{sectionTitle}}</h3> <div class="row"> <div class="form-horizontal"> <label class="col-sm-2 control-label">Number of new records:</label> <div class="col-sm-2"> {{input value=counter class='form-control'}} </div> <div class="col-sm-4"> <button class="btn btn-primary" {{action 'generateAction'}}>Generate {{sectionTitle}}</button> {{#fader-label isShowing=createReady}}Created!{{/fader-label}} </div> <div class="col-sm-4"> <button class="btn btn-danger" {{action 'deleteAction'}}>Delete All {{sectionTitle}}</button> {{#fader-label isShowing=deleteReady}}Deleted!{{/fader-label}} </div> </div> </div> </div> </div>
// app/components/fader-label.js import Ember from 'ember'; export default Ember.Component.extend({ tagName: 'span', classNames: ['label label-success label-fade'], classNameBindings: ['isShowing:label-show'], isShowing: false, isShowingChanged: Ember.observer('isShowing', function() { Ember.run.later(() => { this.set('isShowing', false); }, 3000); }) });
代碼 classNames: ['label label-success label-fade']
的做用是綁定三個CSS類到標籤span
上,獲得html如<span class="label label-success label-fade">xxx</span>
。
代碼classNameBindings: ['isShowing:label-show']
的做用是根據屬性isShowing
的值判斷是否添加CSS類label-show
到標籤span
上。更多有關信息請看Ember.js 入門指南之十二handlebars屬性綁定
<!-- app/templates/components/fader-label.hbs --> {{yield}}
// app/styles/app.scss @import 'bootstrap'; body { padding-top: 20px; } html { overflow-y: scroll; } .library-item { min-height: 150px; } .label-fade { opacity: 0; @include transition(all 0.5s); &.label-show { opacity: 1; } }
最主要、最關鍵的部分來了。
<!-- app/templates/admin/seeder.hbs --> <h1>Seeder, our Data Center</h1> <div class="row"> <div class="col-md-4">{{number-box title="Libraries" number=libraries.length}}</div> <div class="col-md-4">{{number-box title="Authors" number=authors.length}}</div> <div class="col-md-4">{{number-box title="Books" number=books.length}}</div> </div> {{seeder-block sectionTitle='Libraries' counter=librariesCounter generateAction='generateLibraries' deleteAction='deleteLibraries' createReady=libDone deleteReady=libDelDone }} {{seeder-block sectionTitle='Authors with Books' counter=authorCounter generateAction='generateBooksAndAuthors' deleteAction='deleteBooksAndAuthors' createReady=authDone deleteReady=authDelDone }}
屬性generateAction
和deleteAction
用於關聯控制器中的action
方法,屬性createReady
和deleteReady
是標記屬性。
等待項目重啓完畢,頁面結果以下:
底部的兩個輸入框用於獲取生成的數據條數。
faker.js
構建測試數據使用faker.js構建測試數據。
ember install ember-faker
安裝完畢以後擴展各個模型,並在模型中調用randomize()
方法產生數據。下面是各個模型的代碼。
// app/models/library.js import Model from 'ember-data/model'; import attr from 'ember-data/attr'; import { hasMany } from 'ember-data/relationships'; import Ember from 'ember'; import Faker from 'faker'; export default Model.extend({ name: attr('string'), address: attr('string'), phone: attr('string'), books: hasMany('book', {inverse: 'library', async: true}), isValid: Ember.computed.notEmpty('name'), randomize() { this.set('name', Faker.company.companyName() + ' Library'); this.set('address', this._fullAddress()); this.set('phone', Faker.phone.phoneNumber()); // If you would like to use in chain. return this; }, _fullAddress() { return `${Faker.address.streetAddress()}, ${Faker.address.city()}`; } });
// app/models/book.js import Model from 'ember-data/model'; import attr from 'ember-data/attr'; import { belongsTo } from 'ember-data/relationships'; import Faker from 'faker'; export default Model.extend({ title: attr('string'), releaseYear: attr('date'), author: belongsTo('author', {inverse: 'books', async: true}), library: belongsTo('library', {inverse: 'books', async: true}), randomize(author, library) { this.set('title', this._bookTitle()); this.set('author', author); this.set('releaseYear', this._randomYear()); this.set('library', library); return this; }, _bookTitle() { return `${Faker.commerce.productName()} Cookbook`; }, _randomYear() { return new Date(this._getRandomArbitrary(1900, 2015)); }, _getRandomArbitrary(min, max) { return Math.random() * (max - min) + min; } });
// app/models/author.js import Model from 'ember-data/model'; import attr from 'ember-data/attr'; import { hasMany } from 'ember-data/relationships'; import Faker from 'faker'; export default Model.extend({ name: attr('string'), books: hasMany('book', {inverse: 'author', async: true}), randomize() { this.set('name', Faker.name.findName()); return this; } });
上述代碼中。 async
設置爲true
的做用是:在獲取book的同時會把關聯的author
也加載出來,默認是不加載(延遲加載)。
// app/controllers/admin/seeder.js import Ember from 'ember'; import Faker from 'faker'; export default Ember.Controller.extend({ libraries: [], books: [], authors: [], actions: { generateLibraries() { const counter = parseInt(this.get('librariesCounter')); for (let i = 0; i < counter; i++) { this.store.createRecord('library').randomize().save().then(() => { if (i === counter-1) { this.set('librariesCounter', 0); this.set('libDone', true); } }); } }, deleteLibraries() { this._destroyAll(this.get('libraries')); this.set('libDelDone', true); }, generateBooksAndAuthors() { const counter = parseInt(this.get('authorCounter')); for (let i = 0; i < counter; i++) { let newAuthor = this.store.createRecord('author'); newAuthor.randomize() .save().then(() => { if (i === counter-1) { this.set('authorCounter', 0); this.set('authDone', true); } } ); this._generateSomeBooks(newAuthor); } }, deleteBooksAndAuthors() { this._destroyAll(this.get('books')); this._destroyAll(this.get('authors')); this.set('authDelDone', true); } }, // Private methods _generateSomeBooks(author) { const bookCounter = Faker.random.number(10); for (let j = 0; j < bookCounter; j++) { const library = this._selectRandomLibrary(); this.store.createRecord('book') .randomize(author, library) .save(); author.save(); library.save(); } }, _selectRandomLibrary() { const libraries = this.get('libraries'); const librariesCounter = libraries.get('length'); // Create a new array from IDs const libraryIds = libraries.map((lib) => {return lib.get('id');}); const randomNumber = Faker.random.number(librariesCounter-1); const randomLibrary = libraries.findBy('id', libraryIds[randomNumber]); return randomLibrary; }, _destroyAll(records) { records.forEach((item) => { item.destroyRecord(); }); } });
重啓項目,進入到http://localhost:4200/admin/seeder。在輸入框內輸入要生成的測試數據條數,而後點擊右邊的藍色按鈕,若是生成成功能夠在按鈕右邊看到綠色的「created」提示文字。以下圖:
而後到firebase上查看。能夠看到數據已經存在了,而且是隨機的數據。
而且是實現了數據表之間的關聯關係,好比一個author
對應多個book
,以下圖。
或者是直接在http://localhost:4200/libraries下查看。
在接下來的一篇文章中將介紹如何遍歷關聯關係中的對象,使用起來也是很是簡單的,直接使用面向對象的方式遍歷便可。
本篇的家庭做業仍然是好好理解組件!參考下面的文章認真學習、理解組件。
爲了照顧懶人我把完整的代碼放在GitHub上,若有須要請參考參考。博文通過屢次修改,博文上的代碼與github代碼可能有出入,不過影響不大!若是你以爲博文對你有點用,請在github項目上給我點個star
吧。您的確定對我來講是最大的動力!!