Table of Contents generated with DocToc
Ember使用Handlebars作为模板引擎,并使用{{}}
的方式来渲染变量。
除此以外,我们可以自己写helper方法,作为在模板内调用的调用的方法:
// app/helpers/sum.js
import Ember from 'ember';
export function sum(params) {
return params.reduce((a, b) => {
return a + b;
});
};
export default Ember.Helper.helper(sum);
usage:
<p>Total: {{sum 1 2 3}}</p>
get
方法可用于获取key对应的value:
{{get address city}}
<!-- 等价于 this.get('address.city') -->
concat
方法用于合并字符串
{{concat "item" 1}}
<!-- result is "item1" -->
{{get "foo" (concat "item" index)}}
<!-- 当index为1时,上式等价于 -->
<!-- this.get("foo.item1") -->
{{if isTrue 'true' 'false'}}
<!-- 当isTrue为true的时候返回'true',否则返回'false' -->
<div class="foo {{if isActive "active" "disabled"}}">
</div>
<div>
{{if isTrue (if isActive "isActive")}}
</div>
{{#if isTrue}}
<div>isTrue</div>
{{/if}}
{{#if isTrue}}
<div>isTrye</div>
{{else if isActive}}
<div>isActive</div>
{{else}}
<div>isDisabled</div>
{{/if}}
{{#unless isFalse}}
<div>isTrue</div>
{{/unless}}
假设route-handler返回了一个列表:
// app/routes/list.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return [
{name: 'ecmadao', age: 1},
{name: 'ws', age: 2}
]
}
});
<!-- app/template/list.hbs -->
<li>
{{#each model as |peopel index|}}
<li>
name: {{people.name}},
age: {{people.age}},
index: {{index}}
</li>
{{/each}}
</li>
{{#each}}
helper还有{{else}}
语句。当Array为空时会被触发:
{{#each model as |people|}}
Hello, {{people.name}}
{{else}}
Nobody here
{{/each}}
可以使用{{#each-in}}
来显示对象的键:
// app/components/store-categories.js
import Ember from 'ember';
export default Ember.Component.extend({
willRender() {
this.set('categories', {
'Bourbons': ['Bulleit', 'Four Roses', 'Woodford Reserve'],
'Ryes': ['WhistlePig', 'High West']
});
}
});
<!-- /app/templates/components/store-categories.hbs
-->
<ul>
{{#each-in categories as |category products|}}
<li>{{category}}
<ol>
{{#each products as |product|}}
<li>{{product}}</li>
{{/each}}
</ol>
</li>
{{/each-in}}
</ul>
output:
<ul>
<li>Bourbons
<ol>
<li>Bulleit</li>
<li>Four Roses</li>
<li>Woodford Reserve</li>
</ol>
</li>
<li>Ryes
<ol>
<li>WhistlePig</li>
<li>High West</li>
</ol>
</li>
</ul>
但是{{#each-in}}
并不会监听到数据的变化。也就是说在上面的categories
里增加数据的时候,模板不会自动的重新渲染更新:
// app/components/store-categories.js
import Ember from 'ember';
export default Ember.Component.extend({
willRender() {
this.set('categories', {
'Bourbons': ['Bulleit', 'Four Roses', 'Woodford Reserve'],
'Ryes': ['WhistlePig', 'High West']
});
},
actions: {
addCategory(category) {
// This won't work!
let categories = this.get('categories');
categories[category] = [];
}
}
});
为了能够让数据监听正常工作,需要手动在数据更改之后调用rerender()
方法:
// /app/components/store-categories.js
import Ember from 'ember';
export default Ember.Component.extend({
willRender() {
this.set('categories', {
'Bourbons': ['Bulleit', 'Four Roses', 'Woodford Reserve'],
'Ryes': ['WhistlePig', 'High West']
});
},
actions: {
addCategory(category) {
let categories = this.get('categories');
categories[category] = [];
// A manual re-render causes the DOM to be updated
this.rerender();
}
}
});
获取的key的顺序和Object中key的顺序一致。
{{#each-in}}
也支持{{else}}
来应对空列表的状态:
{{#each-in people as |name person|}}
Hello, {{name}}! You are {{person.age}} years old.
{{else}}
Sorry, nobody is here.
{{/each-in}}
<div id="logo">
<img src={{logoUrl}} alt="Logo">
</div>
<input type="checkbox" disabled={{isAdministrator}}>
但是默认情况下Ember不会绑定形如data-xxx
的属性:
{{#link-to "photos" data-toggle="dropdown"}}Photos{{/link-to}}
{{input type="text" data-toggle="tooltip" data-placement="bottom" title="Name"}}
只能渲染为:
<a id="ember239" class="ember-view" href="#/photos">Photos</a>
<input id="ember257" class="ember-view ember-text-field" type="text"
title="Name">
为了能够绑定data-xxx
属性,需要手动在view文件中设置:
$ ember generate view binding-element-attributes
// app/view/binding-element-attributes.js
import Ember from 'ember';
export default Ember.View.extend({
});
Ember.TextField.reopen({
attributeBindings: ['data-toggle', 'data-placement']
});
Ember.LinkComponent.reopen({
attributeBindings: ['data-toggle']
});
{{link-to}}
helper方法
看下例子感受感受学习一下:
// app/router.js
Router.map(function() {
this.route('photos', function(){
this.route('edit', { path: '/:photo_id' });
});
});
<!-- app/templates/photos.hbs -->
<ul>
{{#each photos as |photo|}}
<li>{{#link-to "photos.edit" photo}}{{photo.title}}{{/link-to}}</li>
{{/each}}
</ul>
<!-- output -->
<ul>
<li><a href="/photos/1">Happy Kittens</a></li>
<li><a href="/photos/2">Puppy Running</a></li>
<li><a href="/photos/3">Mountain Landscape</a></li>
</ul>
并且,当当前的URL和某一个link匹配时,会自动给<a>
标签加上active
方法:
<!-- 当前在/photos/2页面 -->
<ul>
<li><a href="/photos/1">Happy Kittens</a></li>
<li><a href="/photos/2" class="active">Puppy Running</a></li>
<li><a href="/photos/3">Mountain Landscape</a></li>
</ul>
{{#link-to "posts" (query-params direction="asc")}}Sort{{/link-to}}
{{#link-to "posts" (query-params direction=otherDirection)}}Sort{{/link-to}}
{{link-to}}
的默认行为会给浏览器的历史新增一个路由入口。也就是说,当用户点击link的时候,浏览器的历史里会增加他刚刚点击的这个链接。而通过设置replace=true
,则用户点击链接的时候,该链接会替代浏览器当前所处的历史记录,而不是新增进去。
<!-- /index.html -->
{{#link-to "/about" replace=true}}about{{/link-to}}
假设在index页面,浏览器的历史记录是:
[..., A.html, index.html]
若点击正常的about,则历史记录变成:
[..., A.html, index.html, about.html]
但设置了replace=true
,则在点击之后历史记录为:
[..., A.html, about.html]
当你给任意HTML DOM元素添加{{action}}
helper的时候,都会触发通过action绑定在DOM上的事件,而那个事件是由template对应的component或者controller所提供:
<!-- app/templates/components/single-post.hbs -->
<h3><button {{action "toggleBody"}}>{{title}}</button></h3>
{{#if isShowingBody}}
<p>{{{body}}}</p>
{{/if}}
// app/components/single-post.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
toggleBody() {
this.toggleProperty('isShowingBody');
}
}
});
如果action需要接收参数,则调用方法为{{action actionName params}}
:
<p><button {{action "select" post}}>✓</button> {{post.title}}</p>
// app/components/single-post.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
select(post) {
console.log(post.get('title'));
}
}
});
在默认情况下,点击事件会触发action。但可以通过设置on="XXX"
来指定事件的触发:
<!-- 只有mouse up的时候才会触发事件 -->
<p>
<button {{action "select" post on="mouseUp"}}>✓</button>
{{post.title}}
</p>
可以通过allowedKey
来指定触发事件啊按键:
<!-- 只有alt才能触发 -->
<button {{action "anActionName" allowedKeys="alt"}}>
click me
</button>
在默认情况下。action会阻止浏览器的默认行为,即起到e.preventDafault
的作用。如果不想阻止默认行为,则要手动添加preventDefault=false
:
<a href="newPage.htm" {{action "logClick" preventDefault=false}}>Go</a>
即:
- 当没有
preventDefault=false
的时候,点击链接会触发action事件,而不会导航到新的链接 - 当存在
preventDefault=false
的时候,点击链接则正常导航到新链接
<label>What's your favorite band?</label>
<input type="text" value={{favoriteBand}} onblur={{action "bandDidChange" value="target.value"}} />
actions: {
bandDidChange(newValue) {
console.log(newValue);
}
}
{{input}}
和{{textarea}}
两个方法是Ember提供的便捷helper。{{input}}
封装了Ember.TextField
和Ember.Checkbox
,而{{textarea}}
方法则是封装了Ember.TextArea
关于他们的具体使用可戳标题链接或者这里
{{log XXXX}}
效果相当于console.log()
{{debugger}}
通过建立自己的helper,我们可以在template里便捷的引用并获得想要的结果。
helper 接受Array作为参数
例如,创建一个格式化金额显示的helper
$ ember g helper format-currency
// app/helpers/format-currency.js
import Ember from 'ember';
export function formatCurrency([value, ...rest]) {
let dollars = Math.floor(value / 100);
let cents = value % 100;
let sign = '$';
if (cents.toString().length === 1) { cents = '0' + cents; }
return `${sign}${dollars}.${cents}`;
}
export default Ember.Helper.helper(formatCurrency);
usage:
<span>total cost: {{format-currency 250}}</span>
<!-- output -->
<span>total cost: $2.50</span>
component的名称要求多个单词组成,单词中间带有-
,而helper则没有命名限制,一个或多个单词都可以。但多个单词间要以-
链接
在template中,你可以给helper传递一个或多个参数。因此helper方法本身接受一个Array作为参数:
{{my-helper "hello" "world"}}
// app/helpers/my-helper.js
import Ember from 'ember';
export default Ember.Helper.helper(function(params) {
let [arg1, arg2] = params;
console.log(arg1); // => "hello"
console.log(arg2); // => "world"
});
helper可以指定参数的名称,因此在调用的时候可以打乱参数顺序:
命名参数全部合在一起作为一个Object传给helper方法
// app/helpers/format-currency.js
import Ember from 'ember';
export default Ember.Helper.helper(function([value, ...rest], namedArgs) {
let dollars = Math.floor(value / 100);
let cents = value % 100;
let sign = namedArgs.sign === undefined ? '$' : namedArgs.signp;
if (cents.toString().length === 1) { cents = '0' + cents; }
return `${sign}${dollars}.${cents}`;
});
{{format-currency 350 sign="¥"}}
<!-- output -->
¥3.5
注意,命名参数在helper内,是作为一个整体的Object:
{{my-helper option1="hello" option2="world" option3="goodbye cruel world"}}
// app/helpers/my-helper.js
import Ember from 'ember';
export default Ember.Helper.helper(function(params, namedArgs) {
console.log(namedArgs.option1); // => "hello"
console.log(namedArgs.option2); // => "world"
console.log(namedArgs.option3); // => "goodbye cruel world"
});
或者你也可以这样:
// app/helpers/my-helper.j
import Ember from 'ember';
export default Ember.Helper.helper(function(params, { option1, option2, option3 }) {
console.log(option1); // => "hello"
console.log(option2); // => "world"
console.log(option3); // => "goodbye cruel world"
});
来创建一个生成<b>
标签的helper:
// app/helpers/make-bold.js
import Ember from 'ember';
export default Ember.Helper.helper(function([param, ...rest]) {
return `<b>${param}</b>`;
});
然后调用它:
{{make-bold "Hello World"}}
结果
<b>Hello World</b>
这是因为Ember禁止直接从方法里返回并插入一段HTML代码,以防止跨域的脚本攻击。想要成功的插入HTML,需要使用Ember的htmlSafe
方法,将其转化为安全的形式:
// app/helpers/make-bold.js
import Ember from 'ember';
export default Ember.Helper.helper(function([param, ...rest]) {
return Ember.String.htmlSafe(`<b>${param}</b>`);
});
但需要重视的是,即便我们是通过htmlSafe
返回了SafeString
,也有可能受到XSS攻击。比如,一段聊天室的代码是这样的:
Welcome back! {{make-bold model.firstName}} has joined the channel.
此时,一个恶意的用户或许可以把他的firstName
写成是一段以<script>
包含的代码,被我们的方法调用之后,就相当于往DOM里插入了一段JS。所以说,不建议通过这种形式在HTML里面插入DOM,如果想做,可以通过component来完成。
但如果你真的真的非常想还是使用这样的方式插入DOM的话,则要确保自己已经将那些不可信的内容过滤过:
// app/helpers/make-bold.js
import Ember from 'ember';
export default Ember.Helper.helper(function([param, ...rest]) {
let value = Ember.Handlebars.Utils.escapeExpression(param);
return Ember.String.htmlSafe(`<b>${value}</b>`);
});
现在在把一段被<script>
标签包裹的代码代入进去,会发现<script>
被过滤掉了:
Welcome back! <b><script
type="javascript">alert('pwned!');</script></b> has joined the channel.