来源:yoember.com
作者:Zoltan

声明本文的转载与翻译是经过作者认可的,再次感谢原作,如有侵权请给我留言,我会删除博文!! 希望本系列教程能帮助更多学习Ember.js的初学者。

接着前面三篇:

  1. 环境搭建以及使用Ember.js创建第一个静态页面
  2. 引入计算属性、action、动态内容
  3. 模型,保存数据到数据库

应用发布

发布方式一

发布的详细教程请看guide on firebase。执行如下命令发布项目。

1
2
3
4
npm install -g firebase-tools
ember build --prod
firebase login
firebase init

执行命令过程需要输入一个public的目录,输入dist后按enter。更新firebase.json的内容。

1
2
3
4
5
6
7
8
{
"firebase": "YOUR-APP-NAME",
"public": "dist",
"rewrites": [{
"source": "**",
"destination": "/index.html"
}]
}

遗憾的是在我电脑上一直提示没有firebase命令,即使我已经安装了这个插件也不行。

发布方式二

由于上述方式无法发布想到到firebase,所以使用最原始的发布方式,使用ember命令打包项目。然后自己把项目部署到服务器上。

  1. 打包项目
    打包项目使用命令ember build --prod,等到命令执行完毕后再项目的dist目录下的所有文件即使打包后的项目文件。
  2. 复制打包后的文件到服务器上
    得到打包后的文件后可以直接把这些文件复制到服务器上运行,比如复制到tomcat的webapps目录下。
  3. 运行项目
    复制到服务器之后启动服务器,直接访问:http://localhost:8080

增加删除、修改功能

修改项目的library列表页面,增加删除和修改功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!-- app/templates/libraries/index.hbs -->

## List

<div class="row">
{{#each model as |library|}}
<div class="col-md-4">
<div class="panel panel-default library-item">
<div class="panel-heading">

### {{library.name}}

</div>
<div class="panel-body">

Address: {{library.address}}

Phone: {{library.phone}}

</div>
<div class="panel-footer text-right">
{{#link-to 'libraries.edit' library.id class='btn btn-success btn-xs'}}Edit{{/link-to}}
<button class="btn btn-danger btn-xs" {{action 'deleteLibrary' library}}>Delete</button>
</div>
</div>
</div>
{{/each}}
</div>

相比原来的代码增加了一个连接和一个按钮,分别用于编辑和删除library信息。相对于需要增加一个路由libraries/edit和一个处理的动作{{action 'deleteLibrary'}}
如果此时运行http://localhost:4200/libraries会出现错误,因为还没定义路由libraries/editaction。别急,先一步步来,下面先增加一些css样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app/styles/app.scss
@import 'bootstrap';

body {
padding-top: 20px;
}

html {
overflow-y: scroll;
}

.library-item {
min-height: 150px;
}

创建路由libraries/edit和路由对应的模板

简单起见直接使用Ember CLI命令创建,就不手动创建了。执行命令:ember g route libraries/edit创建路由和路由对应的模板。
创建完成之后还需要手动修改app/router.js文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 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('contact');
});

this.route('libraries', function() {
this.route('new');
// :library_id是一个动态段,会根据实际的URL变化
this.route('edit', { path: '/:library_id/edit' });
});
});

export default Router;

注意this.route('edit', { path: '/:library_id/edit' });这行代码的设置。与普通的路由稍有不同这里增加了一个参数,并且参数内使用path设定路由渲染之后edit会被/:library_id/edit替换。
编译、渲染之后的URL格式为http://example.com/libraries/1234/edit其中:library_id这是一个动态段,这个URL例子中动态段library_id的值就是1234,并且可以在路由类中获取这个动态段的值。
更多有关动态段的介绍请看Ember.js 入门指南之十三{{link-to}} 助手或者Dynamic Segments

配置完路由之后修改路由libraries/edit.js的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// app/routes/libraries/edit.js
import Ember from 'ember';

export default Ember.Route.extend({

model(params) {
// 获取动态段library_id的值
return this.store.findRecord('library', params.library_id);
},

actions: {

saveLibrary(newLibrary) {
newLibrary.save().then(() => this.transitionTo('libraries'));
},

willTransition(transition) {

let model = this.controller.get('model');

if (model.get('hasDirtyAttributes')) {
let confirmation = confirm("Your changes haven't saved yet. Would you like to leave this form?");

if (confirmation) {
model.rollbackAttributes();
} else {
transition.abort();
}
}
}
}
});

代码this.store.findRecord('library', params.library_id);的意思是根据模型的id属性值查询某个记录,其中library_id就是动态段的值,这个值是Ember解析URL得到的。正如前面所说:http://example.com/libraries/1234/edit这个URL动态段的值就是1234
Ember会自动根据URL的格式解析得到。并且可以在路由类中获取。默认情况下动态段的值是数据的id值。代码中的另外两个方法saveLibrary()willTransition()在前一篇文章模型,保存数据到数据库已经介绍过,在此不再赘述。
方法willTransition()的作用就是:当用户修改了数据之后没有点击保存就离开页面时会提示用户是否确认不保存就离开页面!通过控制器中的属性hasDirtyAttributes判断页面的值是否发生了变化。方法rollbackAttributes()会重置model中的值。方法abourt()可以阻止路由的跳转,有关路由的跳转请看Ember.js 入门指南之二十四终止与重试路由跳转。从new.hbs复制代码到edit.hbs,然后在稍加修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- app/templates/libraries/edit.hbs -->

## Edit Library

<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}}>Save changes</button>
</div>
</div>
</div>

等待项目重启完成,进入到修改界面,任意修改界面上的数据,不点击保存然后任意点击其他链接会弹出提示,询问你是否确认离开页面。操作步骤如下截图。

library主页

library修改页面

注意:看浏览器的URL。首页模板代码{{#link-to 'libraries.edit' library.id class='btn btn-success btn-xs'}}Edit{{/link-to}}中的路由libraries.edit渲染之后会得到形如libraries/xxx/edit的URL,其中xxx就是动态段的值。

修改name的值,然后点击菜单的Home

library未保存离开页面提示

弹出提示信息。如果点击取消会停留在当前页面,如果选中确定会跳转到首页(因为我点击的是菜单的Home)。

修改后点击保存

成功保存了修改的内容。到此实现了修改功能。

实现删除功能

删除功能比修改更加简单,直接在方法deleteLibrary里根据id属性值删除数据即可。id属性值已经在模板页面作为参数传递到方法中。直接获取即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// app/routes/libraries/index.js

import Ember from 'ember';

export default Ember.Route.extend({
model() {
return this.store.findAll('library');
},
actions: {
// 删除一个library记录
deleteLibrary(library) { //参数library已经在模板中传递过来
let confirmation = confirm('Are you sure?');

if (confirmation) {
library.destroyRecord();
}
}
}
});

模板中是这样调用删除方法的,看到参数library了吧,这个参数就是一个library模型对象。
可以直接调用方法destroyRecord()实现删除数据。

点击删除按钮

选中确定之后删除就会立刻删除,列表上的数据也会动态更新。

家庭作业

参照library的功能实现contact的删除与修改。

新建路由和模板

1
2
ember g route admin/contact/edit
ember g template admin/contact/index

修改router.js,增加配置

1
2
3
4
5
6
7
8
// app/router.js

this.route('admin', function() {
this.route('invitation');
this.route('contact', function() {
this.route('edit', { path: '/:contact_id/edit' });
});
});

省略其他内容,仅仅列出修改部分。

复制admin/contact.hbs的内容到admin/contact/index.hbs,然后空admin/contact.hbs再在文件内添加{{outlet}}

admin/contact.hbs

1
2
<!-- app/templates/admin/contact.hbs -->
{{outlet}}

admin/contact/index.hbs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{{! app/templates/admin/contact/index.hbs}}

# Contacts

<table class="table table-bordered table-striped">
<thead>
<tr>
<th>ID</th>
<th>E-mail</th>
<th>Message</th>
<th>Operation</th>
</tr>
</thead>
<tbody>
{{#each model as |contact|}}
<tr>
<th>{{contact.id}}</th>
<td>{{contact.email}}</td>
<td>{{contact.message}}</td>
<td>
{{#link-to 'admin.contact.edit' contact.id class='btn btn-success btn-xs'}}Edit{{/link-to}}
<button class="btn btn-danger btn-xs" {{action 'deleteContact' contact}}>Delete</button>
</td>
</tr>
{{/each}}

</tbody>
</table>

增加删除、修改按钮。

复制app/templates/contact.hbsadmin/contact/edit.hbs并做修改

admin/contact/edit.hbs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{{! app/templates/admin/contact/edit.hbs}}

<div class="col-md-6 col-xs-6">
<form>
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
{{input type="email" value=model.email class="form-control col-sm-6 col--6" placeholder="Please type your e-mail address." autofocus="autofocus"}}
</div>
<div class="form-group">
<label for="exampleInputPassword1">Your message</label>
{{textarea class="form-control" placeholder="Your message. (At least 5 characters.)" rows="7" value=model.message}}
</div>

<button class="btn btn-primary" disabled={{model.isDisabled}} {{action 'saveContact' model}}>Save</button>
{{#link-to 'admin.contact' class="btn btn-default"}}Return{{/link-to}}
</form>
</div>

修改routes/context.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// app/routes/contact.js

import Ember from 'ember';

export default Ember.Route.extend({
model: function() {
return this.store.findAll('contact');
},
actions: {
deleteContact: function(contact) {
let confirmation = confirm('Are you sure?');

if (confirmation) {
contact.destroyRecord();
}
}
}
});

修改app/routes/admin/contact/edit.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// app/routes/admin/contact/edit.js

import Ember from 'ember';

export default Ember.Route.extend({

model(params) {
// 获取动态段library_id的值
return this.store.findRecord('contact', params.contact_id);
},

actions: {

saveContact(newContact) {
newContact.save().then(() => this.transitionTo('admin.contact'));
},

willTransition(transition) {

let model = this.controller.get('model');

if (model.get('hasDirtyAttributes')) {
let confirmation = confirm("Your changes haven't saved yet. Would you like to leave this form?");

if (confirmation) {
model.rollbackAttributes();
} else {
transition.abort();
}
}
}
}
});

运行结果不再截图列出,请读者自行试验。

为了照顾懒人我把完整的代码放在GitHub上,可以拿来做参照。博文经过多次修改,博文上的代码与github代码可能有出入,不过影响不大!如果你觉得博文对你有点用,请在github项目上给我点个star吧。您的肯定对我来说是最大的动力!!