文末附原文链接 -- 微信公众号首发
Wriring Helpers
Helper 可以为你的模板提供额外的功能。Helper最有用的功能就是帮你做数据的格式化或者是转化等等。
比如说,我们现在有一个 Invoice model,它包含了一个 totalDue 属性,该属性用来代表 invoice 的总金额。并且我们会以美分的形式来存储小数点后面的额金额。
然而,如果我们将 $1.00 显示成 100¢,那么客户可能会不认识这个数字的含义。在这种场景之下,我们就可以通过helper来做数据的格式化。
接下来,我们create一个名叫 format-currency 的helper,用来把美分转换成美元。
同样的,用 {{ }} 来调用它:
Your total is {{format-currency model.totalDue}} .
下面,我们来正式实现这个helper. Helper 是一个接收多个参数,返回一个结果的函数。
通过下面的CLI命令,helper将会被创建在应用的 helpers 目录下:
ember generate helper format-currency
该helper文件将会export一个被 Ember.Helper.helper( ) 封装的函数:
app/helpers/format-currency.js
import { helper } from '@ember/component/helper';
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 helper(formatCurrency);
在上面例子中, 函数的第一个参数会接收以美分表示的金额数。并且会把美分换算成美元,然后进行字符串拼接再返回到调用helper的地方。
无论你在模板中何时调用它,Ember都会把helper最终的返回结果插入到DOM中。
所以呢,如果你想显示花费总金额,那么你就把钱数传到helper里:
Your total is {{format-currency 250}} .
上面的模板最终的渲染结果是:
Your total is $2.50 .
同样,传入Helper中的变量如果值发生改变,Helper也会将最新的结果更新到页面上。
Helper Names
Helper命名可以只用一个单词,也可以用多个单词。每个单词之间用 " - " 连接即可。
Helper Arguments
Helper可以被传入多个参数:
{{my-helper "hello" "world"}}
当传入多个参数的时候,如果函数的实参只有一个,那么将会以数组的形式来接收多个参数:
app/helpers/my-helper.js
import { helper } from '@ember/component/helper';
export function myHelper(params){
let [arg1, arg2] = params;
console.log(arg1); // => "hello"
console.log(arg2); // => "world"}
export default helper(myHelper);
你也可以通过解构来获取多个参数:
app/helpers/my-helper.js
import { helper } from '@ember/component/helper';
export function myHelper([arg1, arg2]){
console.log(arg1); // => "hello"
console.log(arg2); // => "world"}
export default helper(myHelper);
Named Arguments
一般的,参数用来做数据传入是比较方便的,但是由于一次传入多个参数时,参数的在helper 函数中接收的顺序并不确定。所以我们需要一种方法来解决这个问题。
那就是说,作为开发者我们需要helper的行为可配置。我们重新审视一下之前创建的 format-currency helper,我们再次稍微改造一下这个helper,让它带有一个可配置的货币符号。
Helper会将命名参数作为一个JavaScript对象传递到函数里,该对象包含参数的名称以及它的值。 命名参数不受接收的参数位置的影响。
例子: 我们传一个名为 sign 的参数到 format-currency helper 中:
{{ format-currency 350 sign="£" }}
上面的助手将会输出英镑:£3.50
命名参数被传入函数的时候是作为第二个参数被接收的。下面的代码就是我们改造后的 helper:
app/helpers/format-currency.js
import { helper } from '@ember/component/helper';
export function formatCurrency([value, ...reset], namedArgs) {
let dollars = Math.floor(value / 100);
let cents = value % 100;
let sign = namedArgs.sign === undefined ? '$' : namedArgs.sign;
if (cents.toString( ).length === 1) {
cents = '0' + cents;
}
return `${sign}${dollarts}.${cents}`;
}
export default helper(formatCurrency);
你想传多少命名参数都可以,这些参数都在 namedArgs 里面:
{{my-helper option1="hello" option2="world" }}
app/helpers/my-helper.js
import { helper } from '@ember/component/helper';
export function myHelper (params, namedArgs) {
console.log(namedArgs.option1); // => "hello" console.log(namedArgs.option2); // => "world"}
export default helper(myHelper);
普通参数和命名参数可以同时传入helper。
Class-based Helper
默认的,helper是无状态的。他们接收多个输入,经过一系列操作返回一个输出。它没啥副作用并且它不会保存任何东西。
不过在某些场合,你可能需要helper支持一些交互行为。那么,这时候,你就可以创建基于类的helper了,它可以保存状态和访问service。
相对于之前创建helper的方式,要创建类基的helper,你需要继承 Ember.Helper . 并且必须要包含一个名为 compute 的方法(该方法相当于之前创建helper时定义的函数)。
作为练习,我们来重构一下之前创建的 format-currency helper使它成为一个类基的helper:
app/helpers/format-currency.js
import Helper from '@ember/component/helper';
export default Helper.extend({
compute([value, ...reset], hash) {
let dollars = Math.floor(value / 100);
let cents = value % 100;
let sign = hash.sign === undefined ? '$' : hash.sign;
if (cents.toString( ).length === 1) {
cents = '0' + cents;
}
return `${sign}${dollarts}.${cents}`;
}
});
改造完毕,上面的例子与改造之前的 function 版本目前是等价。
下面,我们将为这个helper注入service:
app/helpers/format-currency.js
import Helper from '@ember/component/helper';
import { inject as service } from '@ember/service';
export default Helper.extend({
authentication: service( ),
compute([value, ...reset], hash) {
let authentication = this.get('authentication');
if (authentication.get('isAuthenticated')) {
return 'Welcome back, ' + authentication.get('username');
} else {
return Not logged in';
}
}
});
Escaping HTML Content
为了抵御跨站脚本攻击(XSS),Ember会把从helper中返回的任何值都转换为安全的字符串。
例子, 现在有个 make-bold helper,它返回的值中带有HTML标签:
app/helpers/make-bold.js
import { helper } from '@ember/component/helper';
export function makeBold([param, ...rest]) {
return `${param}`;
}
export default helper(makeBold);
我们这么调用它:
{{make-bold "Hello world"}}
Ember的转化结果:
<b>Hello world </%gt;
结果就是,helper返回的内容被当作字符串原样输出了。不过我们也可以让Ember不转化返回内容:
app/helpers/make-bold.js
import { helper } from '@ember/component/helper';
import { htmlSafe } from '@ember/string';
export function makeBold([param, ...rest]) {
return htmlSafe(`${param}`);
}
export default helper(makeBold);
如果你返回的是一个 SafeString . 那么Ember就知道你已经确保返回值中没有恶意的代码。
但是请注意,在上面的代码中,我们可能会无意中在应用程序中引入了XSS漏洞! 通过盲目地将字符串标记为安全,恶意用户可以将自己的HTML导入我们的应用程序,从而允许他们执行访问敏感的客户数据等操作。
例如,假设我们有一个聊天应用程序,并使用我们的make-bold助手来欢迎新用户:
Welcome! {{make-bold model.firstName}} has joined the channel..
现在,恶意用户只需要将其firstName设置为包含恶意代码的HTML的字符串(例如,客户的隐私数据通过
为了应对这种行为,Ember提供了 excapeExpression 来防止恶意script注入。
app/helpers/make-bold.js
import Ember from 'ember';
import { helper } from '@ember/component/helper';
import { htmlSafe } from '@ember/string';
export function makeBold([param, ...rest]) {
let value = Ember.Handlerbars.Utils.excapeExpresstion(params);
return htmlSafe(`${param}`);
}
export default helper(makeBold);
现在,被注入的恶意脚本将不会起作用了。
Welcom back! <script type="javascript">alert('pwned!');</script; has joined the channel..Writing Helpers - Templates - Ember Guidesguides.emberjs.com