Ember.js模板开发:Handlebars的强大应用
1. Handlebars基础
Handlebars是一种强大的动态模板创建语言,类似于PHP、JSP、ASP和ERB等服务器端模板语言。它包含HTML元素标签和分隔符来处理数据对象,分隔符为双花括号
{{}}
。在双花括号内,可以渲染数据字符串并使用辅助方法执行有限的逻辑,例如
{{outlet}}
和
{{#each}}
。
Ember对Handlebars的实现最近过渡到了名为“HTMLBars”的新机制,本章中的一些语言细节来自HTMLBars,但也适用于较旧版本的Ember(至少可以追溯到1.13.x版本)。
2. 模型(Models)
在Ember中,模板总是由模型支持。这意味着在将模板渲染为HTML字符串并附加到DOM时,会将一个对象(或对象数组)作为参数传递。模型对象可以具有字符串、数组或其他对象的属性,在编写Ember和Handlebars模板时,使用双花括号访问该对象。例如,要显示模型属性的值,可以使用
{{model.name}}
,其中
name
是模型对象上的属性。
3. 辅助方法(Helpers)
Handlebars模板是由JavaScript函数插值的字符串。当函数遇到双花括号时,它会尝试用对象属性解析分隔符的实例,或调用嵌套函数返回字符串,这些嵌套函数称为辅助方法,在应用程序中创建。Handlebars库内置了一些辅助方法,Ember也添加了一些自己的辅助方法。
辅助方法有两种形式:
-
内联辅助方法
:使用语法
{{[辅助方法名称] [参数]}}
,参数可以包括选项哈希。例如:
{{input type="text" value=firstName disabled=entryNotAllowed size="50"}}
- 块辅助方法 :使用块语法:
{{#[辅助方法名称] [参数]}}
[块内容]
{{/[辅助方法名称]}}
例如,只向未登录的用户显示登录链接:
{{#if notSignedIn}}
<a href="/">Sign In</a>
{{/if}}
4. 条件语句(Conditionals)
条件语句允许在Handlebars模板中引入基本的控制流,语法如下:
{{#if argument}}
[渲染块内容]
{{else}}
[渲染其他内容]
{{/if}}
或者:
{{#unless argument}}
[渲染块内容]
{{/unless}}
条件语句接受一个解析为真值或假值的参数。真值是在布尔上下文中计算为
true
的值,除了定义为假值的值(
false
、
0
、
""
、
null
、
undefined
和
NaN
)之外,所有值都是真值。
操作步骤:
1. 打开
app/templates/sightings/index.hbs
文件,添加条件语句,使目击记录显示位置信息,或者在没有位置数据时显示缺失数据的提示:
<div class="panel panel-default">
<ul class="list-group">
{{#each model as |sighting|}}
<li class="list-group-item">
{{sighting.location}} - {{sighting.sightedAt}}
</li>
{{/each}}
</ul>
</div>
<div class="row">
{{#each model as |sighting|}}
<div class="col-xs-12 col-sm-3 text-center">
<div class="media well">
<div class="caption">
{{#if sighting.location}}
<h3>{{sighting.location}} - {{sighting.sightedAt}}</h3>
{{else}}
<h3 class="text-danger">Bogus Sighting</h3>
{{/if}}
</div>
</div>
</div>
{{/each}}
</div>
-
更改
app/routes/sightings.js中路由发送到模板的数据,将一个目击记录的location属性设置为空字符串:
model(){
...
let record3 = this.store.createRecord('sighting', {
location: 'Asilomar',
sightedAt: new Date('2016-03-21')
});
return [record1, record2, record3];
}
-
启动服务器,在浏览器中访问
http://localhost:4200/sightings查看新的目击记录列表。
5. 使用
{{#each}}
进行循环
{{#each}}
块辅助方法用于渲染数组中的每个对象,作为块内容的实例。
{{#each}}
的参数是数组,块内使用
|实例|
表示当前对象。只有当传递给
{{#each}}
的参数是至少有一个元素的数组时,块才会渲染。
与
{{#if}}
块辅助方法类似,
{{#each}}
支持
{{else}}
块,当数组参数为空时渲染。
操作步骤:
1. 在
app/templates/cryptids.hbs
中使用
{{#each}} {{else}} {{/each}}
创建所有记录的神秘生物列表,或者在没有记录时显示“没有生物”:
{{outlet}}
<div class="row">
{{#each model as |cryptid|}}
<div class="col-xs-12 col-sm-3 text-center">
<div class="media well">
<div class="caption">
<h3>{{cryptid.name}}</h3>
</div>
</div>
</div>
{{else}}
<div class="jumbotron">
<h1>No Creatures</h1>
</div>
{{/each}}
</div>
-
导航到
http://localhost:4200/cryptids查看新的神秘生物列表。 -
在
app/routes/cryptids.js中注释掉返回语句,移除模型回调,以测试{{else}}块:
model(){
// return this.store.findAll('cryptid');
}
-
重新加载
http://localhost:4200/cryptids,此时神秘生物列表应为空白。
6. 绑定元素属性(Binding element attributes)
元素属性值可以像在DOM元素标签之间渲染元素内容一样,从控制器属性渲染。在早期版本的Ember中,有一个名为
{{bind-attr}}
的辅助方法用于绑定属性,现在由于HTMLBars,可以直接使用
{{}}
将属性绑定到属性。
属性绑定在
class
和
src
等元素属性中很常见。神秘生物的数据中有图像路径,可以将其
src
属性动态绑定到模型属性。
操作步骤:
1. 在
app/templates/cryptids.hbs
中为神秘生物列表添加图像:
<div class="row">
{{#each model as |cryptid|}}
<div class="col-xs-12 col-sm-3 text-center">
<div class="media well">
<img class="media-object thumbnail" src="{{cryptid.profileImg}}"
alt="{{cryptid.name}}" width="100%" height="100%">
<div class="caption">
<h3>{{cryptid.name}}</h3>
</div>
</div>
</div>
{{else}}
<div class="jumbotron">
<h1>No Creatures</h1>
</div>
{{/each}}
</div>
-
将神秘生物的图像从课程资源添加到
tracker/public/assets/image/cryptids目录。 -
在
app/templates/cryptids.hbs中使用内联{{if}}辅助方法处理缺失的图像:
<div class="row">
{{#each model as |cryptid|}}
<div class="col-xs-12 col-sm-3 text-center">
<div class="media well">
<img class="media-object thumbnail" src="{{cryptid.profileImg}}"
src="{{if cryptid.profileImg cryptid.profileImg
'assets/images/cryptids/blank_th.png'}}"
alt="{{cryptid.name}}" width="100%" height="100%">
<div class="caption">
...
-
在
app/routes/cryptids.js的beforeModel钩子中创建一个没有图像路径的神秘生物:
import Ember from 'ember';
export default Ember.Route.extend({
beforeModel(){
this.store.createRecord('cryptid', {
"name": "Charlie",
"cryptidType": "unicorn"
});
},
model(){
return this.store.findAll('cryptid');
}
});
-
重新加载
http://localhost:4200/cryptids查看新的图像列表。
7. 链接(Links)
在基于浏览器的应用程序中,路由是独特的,Ember监听一些事件钩子来管理应用程序中的路由。因此,应该使用
{{#link-to}}
块辅助方法创建链接,该辅助方法将路由(用字符串表示)作为第一个参数创建锚元素。
操作步骤:
1. 在
app/templates/application.hbs
中更新主导航,使用
{{#link-to}}
辅助方法替换导航栏的测试链接:
<div class="collapse navbar-collapse" id="top-navbar-collapse">
<ul class="nav navbar-nav">
<li>
<a href="#">Test Link</a>
</li>
<li>
<a href="#">Test Link</a>
</li>
<li>
{{#link-to 'sightings'}}Sightings{{/link-to}}
</li>
<li>
{{#link-to 'cryptids'}}Cryptids{{/link-to}}
</li>
<li>
{{#link-to 'witnesses'}}Witnesses{{/link-to}}
</li>
</ul>
</div>
- 重新加载应用程序并测试链接。
-
在
app/templates/cryptids.hbs中,使用{{#link-to}}辅助方法将神秘生物的图像链接到每个生物的单独页面:
<div class="media well">
{{#link-to 'cryptid' cryptid.id}}
<img class="media-object thumbnail"
src="{{if cryptid.profileImg cryptid.profileImg
'assets/images/cryptids/blank_th.png'}}"
alt="{{cryptid.name}}" width="100%" height="100%">
{{/link-to}}
<div class="caption">
<h3>{{cryptid.name}}</h3>
</div>
</div>
-
编辑
router.js,让CryptidRoute知道要期待动态值:
Router.map(function() {
this.route('sightings', function() {
this.route('new');
});
this.route('sighting', function() {
this.route('edit');
});
this.route('cryptids');
this.route('cryptid', {path: 'cryptids/:cryptid_id'});
this.route('witnesses');
this.route('witness');
});
-
点击神秘生物的图像,应该会看到一个空白页面,这表明应用程序正在路由到
CryptidRoute。 -
移除
app/routes/cryptids.js中的beforeModel钩子,因为创建操作应该保留在创建新神秘生物的页面。 -
在
app/routes/cryptid.js中添加请求神秘生物数据的代码:
import Ember from 'ember';
export default Ember.Route.extend({
model(params){
return this.store.findRecord('cryptid', params.cryptid_id);
}
});
-
编辑
app/templates/cryptid.hbs,显示神秘生物的图像和名称:
{{outlet}}
<div class="container text-center">
<img class="img-rounded" src="{{model.profileImg}}" alt="{{model.type}}">
<h3>{{model.name}}</h3>
</div>
- 在浏览器中使用导航栏导航到“Cryptids”,然后点击一个神秘生物的图像查看其详细页面。
8. 自定义辅助方法(Custom Helpers)
目击记录的
sightedAt
日期属性显示为丑陋的原始日期字符串,为了更好地格式化日期,使用
moment
库。
操作步骤:
1. 从终端添加
moment
库:
bower install moment --save
-
在
ember-cli-build.js中使用app.import将moment添加到供应商资产:
// Add assets to app with import
app.import(bootstrapPath + 'javascripts/bootstrap.js');
app.import('bower_components/moment/moment.js');
return app.toTree();
-
更改
ember-cli-build.js后,停止Ember服务器(Control+C)并重新启动(ember server)。 - 从终端生成辅助方法模块:
ember g helper moment-from
-
打开生成的文件
app/helpers/moment-from.js,创建新函数:
import Ember from 'ember';
export function momentFrom(params) {
var time = window.moment(...params);
var formatted = time.fromNow();
return new Ember.Handlebars.SafeString(
'<span class="text-primary">'
+ formatted + '</span>'
);
}
export default Ember.Helper.helper(momentFrom);
-
在
app/templates/sightings/index.hbs模板中使用自定义辅助方法:
{{#if sighting.location}}
<h3>{{sighting.location}} - {{sighting.sightedAt}}</h3>
<p>{{moment-from sighting.sightedAt}}</p>
{{else}}
...
9. 挑战任务
-
青铜挑战
:为
{{#link-to}}辅助方法添加title属性,使链接具有悬停内容,标题应为目击记录的神秘生物名称。 -
白银挑战
:审查
moment文档,更改{{moment-from}}辅助方法的输出,将日期格式化为“Sunday May 31, 2016”。 - 黄金挑战 :创建一个自定义辅助方法来显示神秘生物缩略图,通过将图像路径传递给辅助方法,在所有出现神秘生物图像的地方使用该辅助方法来清理代码。
通过以上步骤,你可以深入了解Ember.js中Handlebars模板的使用,包括模型、辅助方法、条件语句、循环、属性绑定、链接和自定义辅助方法等方面,为开发功能丰富的Web应用程序打下坚实的基础。
Ember.js模板开发:Handlebars的强大应用
10. 技术总结与流程图
在前面的内容中,我们详细介绍了Ember.js中Handlebars模板开发的多个方面,下面通过流程图和表格来总结关键步骤。
创建和使用自定义辅助方法流程
graph LR
A[添加moment库] --> B[在ember - cli - build.js中导入]
B --> C[重启服务器]
C --> D[生成辅助方法模块]
D --> E[编写辅助方法函数]
E --> F[在模板中使用辅助方法]
各项功能操作步骤总结表格
| 功能 | 操作步骤 |
| — | — |
| 条件语句使用 | 1. 打开
app/templates/sightings/index.hbs
添加条件语句;2. 更改
app/routes/sightings.js
中数据;3. 启动服务器查看结果 |
|
{{#each}}
循环使用 | 1. 在
app/templates/cryptids.hbs
中创建列表;2. 导航查看列表;3. 移除模型回调测试
{{else}}
块 |
| 绑定元素属性 | 1. 在
app/templates/cryptids.hbs
添加图像;2. 复制图像到指定目录;3. 使用内联
{{if}}
处理缺失图像;4. 创建无图像路径生物;5. 重新加载页面查看结果 |
| 链接创建 | 1. 在
app/templates/application.hbs
更新导航;2. 测试链接;3. 在
app/templates/cryptids.hbs
添加图像链接;4. 编辑
router.js
;5. 移除
beforeModel
钩子;6. 添加请求数据代码;7. 编辑模板显示详细信息 |
| 自定义辅助方法 | 1. 添加
moment
库;2. 在
ember - cli - build.js
导入;3. 重启服务器;4. 生成辅助方法模块;5. 编写函数;6. 在模板中使用 |
11. 代码优化与注意事项
在实际开发中,除了按照步骤实现功能,还需要注意代码的优化和一些细节问题。
代码优化
-
减少重复代码
:使用自定义辅助方法可以减少模板中的重复逻辑,提高代码的可维护性。例如,在处理日期格式化时,将逻辑封装在
moment - from
辅助方法中,避免在多个地方重复编写日期处理代码。
-
合理使用条件语句和循环
:在使用
{{#if}}
和
{{#each}}
时,确保逻辑清晰,避免嵌套过深导致代码难以理解。可以将复杂的逻辑拆分成多个辅助方法或组件来处理。
注意事项
-
路径问题
:在处理图像路径和文件导入时,要确保路径的正确性。特别是在部署应用程序时,需要注意实际文件的存储位置和路径配置。
-
数据加载
:在使用
model
钩子获取数据时,要考虑数据加载的性能和错误处理。例如,在
app/routes/cryptid.js
中,使用
findRecord
方法获取单个记录时,如果记录不存在或网络出现问题,需要进行相应的错误处理。
12. 实际应用案例分析
假设我们要开发一个完整的神秘生物目击记录应用程序,结合前面所学的知识,我们可以构建出一个功能丰富的界面。
-
首页
:使用
{{#link-to}}创建导航栏,链接到目击记录列表、神秘生物列表等页面。 -
目击记录列表页
:使用
{{#each}}循环显示所有目击记录,使用条件语句判断是否显示位置信息,使用自定义辅助方法格式化日期。 -
神秘生物列表页
:使用
{{#each}}显示神秘生物列表,绑定图像属性,处理缺失图像的情况,将图像链接到单个生物的详细页面。 - 单个生物详细页 :通过动态路由参数获取生物信息,显示生物的图像和名称。
以下是一个简化的应用结构示例:
<!-- app/templates/application.hbs -->
<div class="collapse navbar-collapse" id="top-navbar-collapse">
<ul class="nav navbar-nav">
<li>{{#link-to 'sightings'}}Sightings{{/link-to}}</li>
<li>{{#link-to 'cryptids'}}Cryptids{{/link-to}}</li>
</ul>
</div>
<!-- app/templates/sightings/index.hbs -->
<div class="panel panel-default">
<ul class="list-group">
{{#each model as |sighting|}}
<li class="list-group-item">
{{#if sighting.location}}
<h3>{{sighting.location}} - {{sighting.sightedAt}}</h3>
<p>{{moment-from sighting.sightedAt}}</p>
{{else}}
<h3 class="text-danger">Bogus Sighting</h3>
{{/if}}
</li>
{{/each}}
</ul>
</div>
<!-- app/templates/cryptids.hbs -->
<div class="row">
{{#each model as |cryptid|}}
<div class="col-xs-12 col-sm-3 text-center">
<div class="media well">
{{#link-to 'cryptid' cryptid.id}}
<img class="media-object thumbnail" src="{{if cryptid.profileImg cryptid.profileImg 'assets/images/cryptids/blank_th.png'}}" alt="{{cryptid.name}}" width="100%" height="100%">
{{/link-to}}
<div class="caption">
<h3>{{cryptid.name}}</h3>
</div>
</div>
</div>
{{else}}
<div class="jumbotron">
<h1>No Creatures</h1>
</div>
{{/each}}
</div>
<!-- app/templates/cryptid.hbs -->
{{outlet}}
<div class="container text-center">
<img class="img-rounded" src="{{model.profileImg}}" alt="{{model.type}}">
<h3>{{model.name}}</h3>
</div>
13. 挑战任务实现思路
-
青铜挑战
:为
{{#link-to}}辅助方法添加title属性,只需要在使用{{#link-to}}的地方添加title参数即可。例如:
{{#link-to 'sightings' title=sighting.cryptid.name}}Sightings{{/link-to}}
-
白银挑战
:要更改
{{moment - from}}辅助方法的输出格式,需要修改app/helpers/moment - from.js中的函数。根据moment文档,使用合适的格式化方法。例如:
import Ember from 'ember';
export function momentFrom(params) {
var time = window.moment(...params);
var formatted = time.format('dddd MMMM D, YYYY');
return new Ember.Handlebars.SafeString(
'<span class="text-primary">'
+ formatted + '</span>'
);
}
export default Ember.Helper.helper(momentFrom);
- 黄金挑战 :创建自定义辅助方法显示神秘生物缩略图,首先生成辅助方法模块,然后编写函数处理图像路径。例如:
ember g helper cryptid-thumbnail
import Ember from 'ember';
export function cryptidThumbnail(params) {
var imgPath = params[0]? params[0] : 'assets/images/cryptids/blank_th.png';
return new Ember.Handlebars.SafeString(
'<img class="media-object thumbnail" src="' + imgPath + '" alt="Cryptid" width="100%" height="100%">'
);
}
export default Ember.Helper.helper(cryptidThumbnail);
在模板中使用:
<div class="media well">
{{#link-to 'cryptid' cryptid.id}}
{{cryptid-thumbnail cryptid.profileImg}}
{{/link-to}}
<div class="caption">
<h3>{{cryptid.name}}</h3>
</div>
</div>
通过以上内容,我们全面掌握了Ember.js中Handlebars模板开发的核心技术,包括功能实现、代码优化、注意事项以及挑战任务的解决思路,能够开发出高质量的Web应用程序。
超级会员免费看
19

被折叠的 条评论
为什么被折叠?



