文章来源:模型高级特性,引入模型关联关系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
吧。您的确定对我来讲是最大的动力!!