文章泉源:模子高等特征,引入模子关联关联
接着前面五篇:
媒介
本篇主假如引见模子直接的关联关联,比方:一对一、一对多关联。会建立两个模子author
和book
,设置它们的关联,并增添测试数据。
建立模子并设置关联
关联关联设置API:
模子关联:一个library
对应多个book
,一个author
对应多个book
。关联图以下:
运用Ember CLI敕令建立模子。
ember g model book title:string releaseYear:date library:belongsTo author:belongsTo
ember g model author name:string books:hasMany
手动在library
中增添hasMany
关联关联。
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'),
});
建立一个背景治理页面“Seeder”
ember g route admin/seeder
搜检router.js
看看路由是不是胜利建立。相干代码以下:
// 其他代码稳定,省略
this.route('admin', function() {
this.route('invitations');
this.route('contacts');
this.route('seeder');
});
// 其他代码稳定,省略
修正导航模板navbar.hbs
增添新建路由的进口链接。
<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请看下面网址的引见:
- init()
- beforeModel(transition)
- model(params, transition)
- activate()
- setupController(controller, model)
- renderTemplate(controller, model)
数目显现功用
建立一个组件用于显现各个模子数据的总数。
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下检察。
在接下来的一篇文章中将引见怎样遍历关联关联中的对象,运用起来也是异常简朴的,直接运用面向对象的体式格局遍历即可。
家庭作业
本篇的家庭作业仍然是好好明白组件!参考下面的文章认真学习、明白组件。
- Ember.js 入门指南之二十八组件定义
- Ember.js 入门指南之二十九属性通报
- Ember.js 入门指南之三十包裹内容
- Ember.js 入门指南之三十一自定义包裹组件的HTML标签
- Ember.js 入门指南之三十二处置惩罚事宜
- Ember.js 入门指南之三十三action触发变化
为了照应懒人我把完全的代码放在GitHub上,若有须要请参考参考。博文经由屡次修正,博文上的代码与github代码可能有相差,不过影响不大!假如你以为博文对你有点用,请在github项目上给我点个star
吧。您的一定对我来说是最大的动力!!