前端模板引擎(jQuery模板)jsrender的基本使用

jsrender官网号称简单直观、强大轻快可扩展。压缩体积也只有8.9kb,可以单独在浏览器或node中使用,也可以配合jQuery使用。

jsrender使用 {{}} 来做分界,如果{{}}与你现有的模板引擎冲突你可以使用api来自定义,比如:

$.views.settings.delimiters("<%", "%>");

//原本
<p>{{:name}}</p>
//修改后
<p><%:name%></p>

一、基本语法
1. 基本变量标签 {{:name}}

基本变量需要使用冒号 ":" 作为标识,一般是简单对象的某个属性。比如传入一个简单对象data

//模板
<script type="text/x-jsrender" id="j-specCard">
   <table>
    <tr>
        <td>Name: {{:name}}</td>
        <td>Age: {{:age}}</td>
    </tr>
   </table>
</script>
//逻辑
(function(jq, g) {
//传入一个简单对象
    var data = {
            'name': 'alice',
            'age': 18
        },
        //获取模板
        jsRenderTpl = $.templates('#j-specCard'),
        //末班与数据结合
        finalTpl = jsRenderTpl(data);

    $('.box').html(finalTpl);
})(jQuery, window);

//传入一个多层级对象complexData

<body>
    <div class="box"></div>
    <script type="text/x-jsrender" id="j-specCard">
        <table>
            <tr>
                <td>Name: {{:personalInfo.name}}</td>
                <td>Age: {{:personalInfo.age}}</td>
            </tr>
        </table>
        <p>{{:top.middle1}}</p>
        <p>{{:top.middle.bottom}}</p>
    </script>
</body>
<script src="../lib/jquery-1.11.2.min.js"></script>
<script src="../lib/jsrender.js"></script>
<script>
(function(jq, g) {

    var complexData = {
            'personalInfo': {
                'name': 'alice',
                'age': 18
            },
            'top': {
                'middle': {
                    'bottom': 'this is bottom value'
                },
                'middle1': 'this is middle1 value'
            }
        },
        //获取模板
        jsRenderTpl = $.templates('#j-specCard'),
        //末班与数据结合
        finalTpl = jsRenderTpl(complexData);

    $('.box').html(finalTpl);
})(jQuery, window)
</script>

如上所见,不管传入的对象有多复杂,都能按照层级去到属性,只是把最外层的对象名省略掉了。

2. 对基本变量有转译功能标签{{>name}}

转译功能标签使用大于号 ">" 作为标识,可以让数据原样输出,可以防止注入攻击。比如

<body>
    <div class="box"></div>
    <script type="text/x-jsrender" id="j-specCard">
        <p>{{:name}}</p>
        <p>{{>name}}</p>
    </script>
</body>
<script src="../lib/jquery-1.11.2.min.js"></script>
<script src="../lib/jsrender.js"></script>
<script>
(function(jq, g) {

    var data = {
            'name': '<b style="font-size:24px;">i am alice</b><script>alert("我是注入脚本攻击")<\/script>'
        },
        //获取模板
        jsRenderTpl = $.templates('#j-specCard'),
        //末班与数据结合
        finalTpl = jsRenderTpl(data);

    $('.box').html(finalTpl);

})(jQuery, window)
</script>
Paste_Image.png

虽然{{>}}有转译功能,我们不一定必须用他,因为有些标签我们还是需要显示的,视具体情况而定

3. 启用与定义全局变量的标签{{*}}与 {{*:}},不支持代码语法,如果是代码,那么会咦字符串的形式输出

在jsrender语法中,不仅可以处理简单的对象,也支持表达式,比如

<p>{{*:name.firstName + '  ' + name.lastName </p>
 var data = {
        'name': {
            'firstName': 'cury',
            'lastName': 'steven'
        }
    }

但是为了保证封装性,并不是任何表达式都可以处理的,比如全局变量window就不行。比如

<p>{{:name.firstName + '  ' + name.lastName + '  ' + window.describe}}</p>
window.describe = " he is a basketball player";

这样的代码会报错的 Uncaught TypeError: Cannot read property 'describe' of undefined。即使这样,jsrender给我提供了手动开启的方式,只需要调用api,并且使用{{*:}}标签就可以使用了。比如

<body>
    <div class="box"></div>
    <script type="text/x-jsrender" id="j-specCard">
    <!--除了可以在js中定义全局变量,在模板中也是可以的-->
        {{* window.count = 1}}
        <p>{{*:count + 2}}</p>
        <p>{{:name.firstName + '  ' + name.lastName + '  ' + window.describe}}</p>
        <!-- <p>{{*:describe + '  ' + name + count}}</p> -->
    </script>
</body>
<script src="../lib/jquery-1.11.2.min.js"></script>
<script src="../lib/jsrender.js"></script>
<script>
(function(jq, g) {

    window.describe = " he is a basketball player";

    $.views.settings.allowCode(true);
    var data = {
        'name': {
            'firstName': 'cury',
            'lastName': 'steven'
        }
    },
        //获取模板
        jsRenderTpl = $.templates('#j-specCard'),
        //末班与数据结合
        finalTpl = jsRenderTpl(data);

    $('.box').html(finalTpl);

})(jQuery, window)
</script>

首先需要执行$.views.settings.allowCode(true);,把设置开启,然后使用{{* window.count = 1}}定义一个变量,然后再用{{*:count}}引用变量。当然,jsrender也提供了只有某一个模板支持全局变量的设置方式

var tmpl = $.templates({
    markup: "#myTemplate",
    allowCode: true
  });
4. 注释的标签 {{!-- --}}

在jsrender模板中,html的注释是不起作用的,jsrender会原样的输出到dom节点中,而dom中是认识这个注释的,所以并不会显示。但是呢,如果注释中有未定义的变量,那么jsrender就会报错,比如

 <script type="text/x-jsrender" id="j-specCard">
        <p>这是jsrender模板</p>
        <!-- <p>{{*:describe + '  ' + name + count}}</p> -->
    </script>

如果我没有定义window.describe 跟window.count变量,那么这段代码就会报错,所以我们在jsrender模板中要使用jsrender的注释{{!-- --}},而且jsrender在渲染的时候是不会把注释代码返回的。

5. 条件判断语句 {{if}} {{else}}

jsrender 的if else 语法跟正常的代码逻辑还是有点区别的,当只有两种情况的时候,是没有区别的,就是if else

<script type="text/x-jsrender" id="j-specCard">
    {{if name == 'alice'}}
    <p>Hello Alice</p>
    {{else}}
    <p>Hello tourisor</p>
    {{/if}}
</script>

但是当有多种情况的时候,也就是if elseif elseif else的时候,可是jsrender并没有elseif这样的写法,它会根据情况来判断,如果是多重情况,它会自动把else 当做elseif来使用

<script type="text/x-jsrender" id="j-specCard">
    {{if count == 1}}
        <p>welcome first!</p>
    {{else count == 2}}
        <p>welcome again</p>
        <p>这里的else会被当做elseif count==2 使用</p>
    {{else}}
        <p>welcom back!</p>
    {{/if}}
</script>

if else 除了以{{if}}....{{/if}}block闭合的形式使用,也可以使用{{if xxx=true ... /}}自闭合的形式,而且还可以引入模板

<script type="text/x-jsrender" id="j-specCard">
    {{if count == 1 tmpl="#j-firstTpl" /}}
</script>
<script type="text/x-jsrender" id="j-firstTpl">
    <h3>this is first template</h3>
</script>

当然如果你的情况比较复杂,那么你可以混着用

<script type="text/x-jsrender" id="j-specCard">
    {{if count == 1 tmpl="#j-firstTpl" }}
    {{else count == 2 tmpl="#j-secondTpl"}}
    {{else}}
    <p>no template</p>
    {{/if}}
</script>
<script type="text/x-jsrender" id="j-firstTpl">
    <h3>this is first template</h3>
</script>
<script type="text/x-jsrender" id="j-secondTpl">
    <h3>this is second template</h3>
</script>

有了引用模板的功能,我们就完全可以把比较通用的代码提取出来写一个模板,减少代码的冗余。

6. 使用{{for}}循环对数据进行循环或对对象进行遍历

jsrender的for循环会默认把数组或对象的第一层循环遍历掉,我们只要管里面的数据就行了,而且使用了循环之后的模板也可以单独写成一个模板,在for循环中引用,循环数组的时候可以使用{{:#index}}来获取当前数组的下标,并且index是从0开始。
传入模板的数据结构

<script>
(function(jq, g) {

    var animal = {
        'kind': 4,
        'price':{
            'cow': 19999,
            'pig': 1888
        },
        'list': [
            {
                'name': 'cow',
                'count': 4,
                'foot': 4
            },
            {
                'name': 'chicken',
                'count': 5,
                'foot': 2
            }
        ]
    },
        //获取模板
        jsRenderTpl = $.templates('#j-specCard'),
        //末班与数据结合
        finalTpl = jsRenderTpl(animal);

    $('.box').html(finalTpl);

})(jQuery, window)
</script>
<script type="text/x-jsrender" id="j-specCard">
    {{!-- animal 对象已经被默认遍历,所以属性前不用加animal.就可访问到 --}}
    <h3>there have {{:kind}} kinds animal</h3>
    <p>and the cow price is {{:price.cow}}</p>
    <p>and the cow price is {{:price.pig}}</p>

    {{!--
    也可以这样对对象进行for循环
    {{for price}}
    <p>and the cow price is {{:cow}}</p>
    <p>and the cow price is {{:pig}}</p>
    {{/for}}
    --}}
    <ul>
        {{!-- 对对象数组进行循环 --}}

        {{for list}}
        <li>{{:#index + 1}}. this animal call {{:name}}, and has {{:count}}, it has {{:foot}} foots</li>
        {{/for}}

        {{!--
            也可以使用模板引入作为循环的模板
            {{for list tmpl="#j-listTpl" /}}
         --}}

    </ul>

</script>
<script type="text/x-jsrender" id="j-listTpl">
    <li>this animal call {{:name}}, and has {{:count}}, it has {{:foot}} foots</li>
</script>

当循环数组或者遍历对象的时候,如果在{{for}}{{/for}}中间加上{{else}},还可以对遍历的对象进行判断,如果该对象或者属性不存在,那么就显示其他的内容。

{{!-- 遍历的时候顺便判断merbers是否存在 --}}
{{for members}}
    <div>{{:name}}</div>
{{else}} 
    <div>No members!</div>
{{/for}}
7. 使用{{props}}遍历对象并且获取到对象的key/value

当我们遍历对象需要使用到对象的key值时,使用props可以获取到key/value值,而且也可以在for循环中进行对象的遍历,在数据循环中获取可以使用#data获取到当前的对象,当然也可以使用引入外部模板来当做循环模板。
传入模板的数据结构

<script>
(function(jq, g) {

    var animal = {
        'kind': 4,
        'price':{
            'cow': 19999,
            'pig': 1888
        },
        'list': [
            {
                'name': 'cow',
                'count': 4,
                'foot': 4
            },
            {
                'name': 'chicken',
                'count': 5,
                'foot': 2
            }
        ]
    },
        //获取模板
        jsRenderTpl = $.templates('#j-specCard'),
        //末班与数据结合
        finalTpl = jsRenderTpl(animal);

    $('.box').html(finalTpl);

})(jQuery, window)
</script>

模板

<script type="text/x-jsrender" id="j-specCard">
    {{!-- 简单的对象遍历 --}}
    {{props price}}
    <p>and the {{:key}} price is {{:prop}}</p>
    {{/props}}

    <ul>
        {{!-- 在数据循环中再进行对象的遍历,病获取到key/prop --}}

        {{for list}}
        <li>{{:#index + 1}}. 
            {{props #data}}
                <b>{{:key}}</b> is {{>prop}}  
            {{/props}}
        </li>
        {{/for}}

        {{!--
            也可以使用模板引入作为循环的模板
            {{for list tmpl="#j-listTpl" /}}
         --}}

    </ul>

</script>
<script type="text/x-jsrender" id="j-listTpl">
    <li>{{:#index + 1}}. 
        {{props #data}}
            <b>{{:key}}</b> is {{>prop}}  
        {{/props}}
    </li>
</script>

当然,在prop也可以在遍历对象的时候对对象进行判断,只要在prop变迁中加入{{else}},如果对象为undefined或对象为空,那么就执行else逻辑。

{{props address}}
    <b>{{>key}}:</b>{{>prop}} <br />
{{else}} 
    The address is blank (no properties)!
{{/props}}
8. 使用{{include}}引入外部模板或者改变模板的上下文

虽然我们可以在{{for}}循环中或者{{if}}标签中直接引入模板,但是{{include}}引入模板才是符合我们的认知,应该什么标签该干什么事来的。

<script type="text/x-jsrender" id="j-specCard">
    {{if case == 1}}
        {{include tmpl="#j-case1Tpl" /}}
    {{else case == 2}}
        {{include tmpl="#j-case2Tpl" /}}
    {{else}}
        <p>no data</p>
    {{/if}}

</script>
<script type="text/x-jsrender" id="j-case1Tpl">
    <h3>{{:data.title}}</h3>
    <p>{{:data.text}}</p>
</script>
<script type="text/x-jsrender" id="j-case2Tpl">
    <h3>{{:data.title}}</h3>
    <p>{{:data.text}}</p>
</script>

模板数据

    var condition = {
        'case': 1,
        'data': {
            'title': 'this is first case',
            'text': 'case one text'
        }
    },
        //获取模板
        jsRenderTpl = $.templates('#j-specCard'),
        //末班与数据结合
        finalTpl = jsRenderTpl(condition);

    $('.box').html(finalTpl);

使用{{include}}标签引入模板显得比较语义化,虽然并没有什么差别。除了引入模板,{{include}}还可以在引入模板的同时引入对象或者数组,来改变所引入模板的上下文

<script type="text/x-jsrender" id="j-specCard">
    {{if case == 1}}
        {{include tmpl="#j-case1Tpl" /}}
    {{else case == 2}}
        {{include data tmpl="#j-case1Tpl" /}}
    {{else}}
    {{else case == 3}}
        {{include data1 tmpl="#j-case2Tpl" /}}
    {{else}}
        <p>no data</p>
    {{/if}}

</script>
<script type="text/x-jsrender" id="j-case1Tpl">
    {{!-- for循环会默认取到传进来的对象 使用data.title是访问不到的 --}}
    {{!-- 传进来的对象必须手动循环 --}}
    {{for}}
        <h3>{{:title}}</h3>
        <p>{{:text}}</p>
    {{/for}}
    {{!--
        或者这样使用
        <h3>{{:#data.title}}</h3>
        <p>{{:#data.text}}</p>
    --}}
</script>
<script type="text/x-jsrender" id="j-case2Tpl">
    {{!-- :length 可以获取当前数组的长度 --}}
    {{:length}}
    {{!-- 传进来的数组必须手动循环 --}}
    {{for #data}}
        <h3>{{:title}}</h3>
        <p>{{:text}}</p>
    {{/for}}
    {{!-- 
        <h3>{{*:extraData.title}}</h3>
        <p>{{*:extraData.text}}</p>
    --}}
</script>

传入的模板数据

    var condition = {
        'case': 2,
        'data': {
            'title': 'this is first case',
            'text': 'case one text'
        },
        'data1': [
            {
                'title': 'i am outer fisrt title',
                'text': 'it is me,diffrent light'
            },
            {
                'title': 'i am outer second title',
                'text': 'it is me,diffrent light'
            }
        ]
    };

而引入外部全局对象或者数组,发现循环 /遍历不了,过后再研究

9、使用{{converters:value}}把value转换成所需要的格式

当后端给我们返回的数据格式跟页面所以展示的格式不一样的时候,我们就需要对数据进行转换。比如说后端返回时间的毫秒数,可是页面却要显示 年-月-日的格式或者是后端返回小写的字符,页面却要显示成大写的字符,这个时候转换器就派上用场了。
jsrender提供了api $.views.converters()来注册转换方法。

    <script type="text/x-jsrender" id="j-myPersonalInfoTpl">
        <div>
            <h3>{{upper:name}}</h3>
            <p>{{:age}}</p>
        </div>
    </script>

    $.views.converters({
        upper: function(val){
            return val.toUpperCase();
        }
    })
    
    //使用jQuery选择器获取script标签声明的jsrender模板并传入数据跟一些方法渲染模板
    var myPersonalTpl = $("#j-myPersonalInfoTpl").render(info);
10、使用{{:~helper(value)}}对传入的参数value做处理

当我们拿到的数据不符合展示的需求是,我们需要对数据进行处理,那么我们可以使用辅助函数,把原始当参数传入,返回我们需要的数据。
jsrender提供了$.views.helper()方法来注册辅助函数。并使用~当前缀来调用辅助函数。

<script type="text/x-jsrender" id="j-myPersonalInfoTpl">
        <div>
            <h3>{{:~hello(firstName, lastName)}}</h3>
            <p>{{:age}}</p>
        </div>
</script>

    var info = {
        firstName: 'alice',
        lastName: 'Jogh',
        age: 18
    };

    $.views.helpers({
        hello: function(fisrtName, lastName){
            return 'Hello ' + fisrtName + ' ' + lastName;
        }
    })
    
    //使用jQuery选择器获取script标签声明的jsrender模板并传入数据渲染模板
    var myPersonalTpl = $("#j-myPersonalInfoTpl").render(info);

二、常用API
1. $.templates()

$.templates()方法是用来注册或者编译模板的,使用的情况有以下几种。

  • 把html字符串编译编译成模板。
  • 获取使用script标签声明的模板,并返回一个模板对象
  • 把html字符串或者在script标签中声明的模板注册成命名模板
  • 获取之前就存在的命名模板
  • 在nodejs中,根据文件路径获取一个模板对象

我们正常使用的方式就是使用$.templates()方法把html字符串编译成模板,返回一个模板对象,然后调用该对象的render方法并传入数据,就可以得到一份完整的html字符串代码。比如:

var info = {
        name: 'alice',
        age: 18
    };
   
    //获取模板
    var jsRenderTpl = $.templates('<div><h3>{{:name}}</h3><p>{{:age}}</p></div');
    //模板与数据结合
    var finalTpl = jsRenderTpl.render(info);
    //也可以这样用 jsRenderTpl(info);

或者我们也可以给模板定义一个名称

    //定义一个命名模板
    $.templates('myPersonalInfoTpl', '<div><h3>{{:name}}</h3><p>{{:age}}</p></div');
    //模板与数据结合
    var finalTpl = $.render.myPersonalInfoTpl(info);

当然,我们也可以把html字符串单独写在script标签中,然后根据id来获取

<script type="text/x-jsrender" id="j-myPersonalInfoTpl">
    <div>
        <h3>{{:name}}</h3>
        <p>{{:age}}</p>
    </div>
</script>
</body>
<script src="../lib/jquery-1.11.2.min.js"></script>
<script src="../lib/jsrender.js"></script>
<script>
(function(jq, g) {
    //定义一个命名模板
    $.templates('myPersonalInfoTpl', '#j-myPersonalInfoTpl');
    //模板与数据结合
    var finalTpl = $.render.myPersonalInfoTpl(info);
    $('.box').html(finalTpl);

})(jQuery, window)
</script>

更想当然,还可以在一个templates()方法里面注册多个命名模板

//定义一个命名模板
    $.templates({
        'myPersonalInfoTpl': '#j-myPersonalInfoTpl',
        'externalTpl': '<p>this is externalTpl</p>'
    });
    //模板与数据结合
    var finalTpl = $.render.myPersonalInfoTpl(info);
    var externalTpl = $.render.externalTpl();

    $('.box').html(finalTpl).append(externalTpl);

还可以指定一些只供这个模板使用的一些方法

    <script type="text/x-jsrender" id="j-myPersonalInfoTpl">
        <div>
            <h3>{{upper:~append(name, ' stev')}}</h3>
            <p>{{:age}}</p>
        </div>
    </script>

    //定义一个命名模板,并指定只供这个模板使用的转换方法与辅助方法
    $.templates("myPersonalInfoTpl", {
        markup: "#j-myPersonalInfoTpl",
        converters: {
            upper: function(val) {
                return val.toUpperCase();
            }
        },
        helpers: {
            append: function(a, b) {
                return a + b;
            }
        }
    });
    //模板与数据结合
    var finalTpl = $.render.myPersonalInfoTpl(info);
2、渲染模板的render()方法

当我们使用$.templates()方法注册一个模板对象时,最后还是需要把模板对象跟数据结合得到最终的html字符串的,render()的使用方式有以下三种

  1. 当模板对象myPersonalTpl是使用$.templates()注册的模板时,只能使用myPersonalTpl.render(data)的方式来渲染模板
    //定义一个匿名模板
    var myPersonalTpl = $.templates("#j-myPersonalInfoTpl");
    //模板与数据结合
    var finalTpl = myPersonalTpl(info);
  1. 当模板对象myPersonalTpl是以命名模板的方式注册时,需要使用$.render.myPersonalTpl(data)或者$.render['myPersonalTpl'](data)的方式来渲染模板
//定义一个命名模板
    $.templates("myPersonalInfoTpl","#j-myPersonalInfoTpl");
    //模板与数据结合
    var finalTpl = $.render.myPersonalInfoTpl(info);
  1. 当使用jQuery、并且模板是在script标签中声明时,还可以直接使用$("#personTemplate").render(data),并不需要调用$.templates()方法来注册模板。
var myHelpers = {
        upper: function(val){
            return val.toUpperCase();
        }
    }
    //使用jQuery选择器获取script标签声明的jsrender模板并传入数据跟一些方法渲染模板
    var myPersonalTpl = $("#j-myPersonalInfoTpl").render(info, myHelpers);

    $('.box').html(finalTpl);

我们还可以在渲染模板的同时,传入一些函数供模板使用

$.render.myTmpl(data, helpersOrContext)
3、 使用$.views.helpers()方法注册辅助函数

当我们拿到的数据跟页面显示的数据有差别、或者是我们需要把原数据转化成其他格式的数据时,我们使用使用一些辅助函数来实现,jsrender提供了两种方式,一种是helper函数,一种是converters转换器。我们先讲辅助函数helper。helper方法是以 "~" 为前缀作为方法的标示,在加上方法名,然后把值当参数传进去

//example
{{:~myHelperValue}}
{{:~myHelperFunction(name, title)}}
{{for ~myHelperObject.mySortFunction(people, "increasing")}} ... {{/for}}

辅助函数helper注册方式有以下三种:

  • 使用$.views.helpers()注册全局的helper方法。
    当我们需要一些比较通用的方法时,可以提取出来写到公用的js文件中去,以后就不用重新写一遍了。
    <div id="box"></div>
    <script id="myPersonInfo" type="text/x-jsrender">
        <h3>my name is {{:~concat(firstName, lastName)}}</h3>
        <p>{{:~util.addPrefix(age)}}</p>
    </script>
    <script>
        var info = {
            firstName: 'bolin',
            lastName: 'liao',
            age: 18
        };

        // 定义全局的helper方法
        $.views.helpers({
            concat: function(first , last){
                return first + '  ' + last;
            },
            util: {
                prefix: 'age is  ',
                addPrefix: function(age){
                    return this.prefix + age;
                }
            }
        });

        var html = $('#myPersonInfo').render(info);
        $('#box').html(html);
    </script>
  • 写局部的辅助方法
    当我们的某一个页面有多处使用一个辅助方法,但是又不够通用,不必写到common文件去时,我们可以写只供这个页面使用的辅助方法。
        // 定义局部的helper方法
        var myHelper = {
            concat: function(first , last){
                return first + '  ' + last;
            },
            util: {
                prefix: 'age is  ',
                addPrefix: function(age){
                    return this.prefix + age;
                }
            }
        }

        //并在渲染模板的时候把myHelper当做参数传进去
        var html = $('#myPersonInfo').render(info, myHelper);
  • 只给特定的模板写辅助函数,其实也就是在定义模板的时候把helper传进去
        //注册一个命名模板,并指定helper方法
        $.templates({
            myPersonInfo: {
                markup: '#myPersonInfo',
                helpers: {
                    concat: function(first , last){
                        return first + '  ' + last;
                    },
                    util: {
                        prefix: 'age is  ',
                        addPrefix: function(age){
                            return this.prefix + age;
                        }
                    }
                }
            }
        });

        //渲染模板,命名模板只能使用$.render调用
        var html = $.render.myPersonInfo(info);
        $('#box').html(html);
4、使用$.views.converters()注册转换器

在jsrender中,转换器主要是方便对数据或表达式的的结果进行处理或者格式化,jsrender本身自带了三个转换器,比如:

{{html:'<b>bolin</b>'}} //- 对html标签进行编码,原样输出 :<b>bolin</b>
{{>'<b>bolin</b>'}} //同上,html的别名
{{url:"<_>_\"_'"}} // 对特殊字符进行编码 基本是 <,>,",'...    :%3C_%3E_%22_'
{{attr:value}} //对标签的属性值进行编码,也是字符的转换
& → &< → <> → >\x00 → �' → '" → "` → `= → =

当然,仅仅是这三个转换时不够用的,jsrender提供了自定义转换器的方法。$.views.converters()。比如我想要定义一个时间格式化的转换器跟大小写转换的转换器。

    <script id="myPersonInfo" type="text/x-jsrender">
        <h3>{{upper: 'my name is ' + name}}</h3>
        <p>now is {{dateFormat: time}}</p>
    </script>
    <script>
        var info = {
            name: 'bolin liao',
            time: new Date()
        };

        //注册全局转化器
        $.views.converters({
            dateFormat: function(val){

                var time = new Date();

                time.setTime(val || 0); //设置需要格式化的时间
                return (time.getMonth() + 1) + '月' + time.getDate() + '日';
            },
            upper: function(val){
                console.log(val);
                return val.toUpperCase();
            }
        });

        //渲染模板,命名模板只能使用$.render调用
        var html = $('#myPersonInfo').render(info);
        $('#box').html(html);
    </script>

你会发现,其实转换器跟辅助函数差不多嘛,只是使用的方法不一样而已。虽然实现都差不多,但还是有点区别的,转换器也分全局定义跟局部定义,局部定义的转换只要把模板当做参数传进去就好了,所定义的转换器只能在此模板中生效。

    <script id="myPersonInfo" type="text/x-jsrender">
        <h3>{{upper: 'my name is ' + name}}</h3>
        <p>now is {{dateFormat: time}}</p>
    </script>
     //myText中的转换器不起作用,并报错
    <script id="myText" type="text/x-jsrender">
        <h3>{{upper: title}}</h3>
        <p>now is {{dateFormat: time}}</p>
    </script>

    //注册局部转换器,指定myPersonInfo模板可用
    $.views.converters({
        dateFormat: function(val){

            var time = new Date();

            time.setTime(val || 0); //设置需要格式化的时间
            return (time.getMonth() + 1) + '月' + time.getDate() + '日';
        },
        upper: function(val){
            console.log(val);
            return val.toUpperCase();
        }
    }, $.templates('#myPersonInfo'));

    //渲染模板,命名模板只能使用$.render调用
    var html = $('#myPersonInfo').render(info);
    $('#box').html(html);

    //不可用
    var html = $('#myText').render(text);
    $('#box').append(html);

除了使用在{{convert:}}标签中之外,还可以在{{if}},{{for}}标签中使用,可以说是在任何标签中使用,语法如下:

{{someTag ... convert=...}}
//if语句
{{if convert='inList' item itemList}}...{{/if}}
//for语句
{{for people convert='even'}}

在其他标签中使用时,只是需要把转化器赋值给convert,当然也可以把辅助方法赋值给convert,比如

{{:name convert=~hlp.bold}}

但是呢,如果你使用{{>name convert=~hlp.bold}}的话,是会报错的,还是老老实实使用辅助方法的形式,除了以上的使用方式之外,convert还提供了一些比较好的功能,比如使用this.tagCxt.props可以获取到标签中定义的属性,使用this.tagCxt.view.data可以获取到标签中所有的变量。

    <script id="myPersonInfo" type="text/x-jsrender">
        <h3>{{concat: first last age desc="123" myAttr="test"}}</h3>
    </script>

    //注册局部转换器,指定myPersonInfo模板可用
    $.views.converters({
        concat: function(){

            console.log(this.tagCtx.props);//Object {desc: "123", myAttr: "test"}

            console.log(this.tagCtx.view.data);//Object {first: "bolin", last: " liao", age: 18}
            
        }
    });
5、使用$.views.tags()注册自定义标签

jsrender 提供了一些内置的标签,但往往是不够用的,所以提供了$.views.tags()方法来定义一些比较灵活的标签。自定义标签比较灵活,能控制、访问的元素也比较多,比如写在该标签里面的args、props、甚至整个view model对象里面的全部数据。使用$.views.tags注册自定义标签的语法有以下四种

  1. $.views.tags('myTag', tagOptions); 当自定义的标签需要要模板与方法时,一般会使用这种方式来注册自定义标签,我们可以在render方法里面处理参数或者属性,然后渲染模板,this.tagCxt对象下,有当前view model的数据供访问
Paste_Image.png
    <script id="myPersonInfo" type="text/x-jsrender">
        {{myPersonInfo name age addPrefix=false /}}
    </script>

    var info = {
        name: 'bolin',
        age: 20
    };
    
    $.views.tags({
        'myPersonInfo': {
            render: function(){
                //这里可以获取到自定义标签里面的属性或者参数
                //可以使用 this.tagCtx.args, this.tagCtx.props, this.tagCtx.view.data 访问 view model里面的任何数据
                
                console.log(this.tagCtx);
                if (this.tagCtx.props.addPrefix) {

                    return '<h3>Hello, ' + this.tagCtx.args[0] + '</h3><p>' + this.tagCtx.args[1] + '</p>';
                }else{

                    //使用this.tagCtx.render({name:'lbl'})来渲染定义的模板,并把模板需要的数据传进去
                    return this.tagCtx.render({name:'lbl'}) + '<h3>' + this.tagCtx.args[0]  +'</h3><p>' + this.tagCtx.args[1] + '</p>';
                }
            },
            template: '<h2>{{:name}}</h2>'
        }
    });

当然,如果我们不需要模板,那么就只定义一个方法就好了。

    $.views.tags('myPersonInfo', function(){
                
        if (this.tagCtx.props.addPrefix) {

            return '<h3>Hello, ' + this.tagCtx.args[0] + '</h3><p>' + this.tagCtx.args[1] + '</p>';
        }
    });

当然,也可能你的自定义标签只需要模板,并不需要方法来处理,那么也是没问题的,当然模板里面也是可以访问各种参数、属性的,只是需要使用~tag.tagCtx对象访问。

    <script id="myPersonInfo" type="text/x-jsrender">
        {{myPersonInfo name age=age /}}
    </script>

    $.views.tags('myPersonInfo', {
        template: '<h3>{{:~tag.tagCtx.args[0]}}</h3><p>{{:~tag.tagCtx.props.age}}</p>'
    });

    //也可以这样
    <script id="myPersonInfo" type="text/x-jsrender">
        {{myPersonInfo name age addPrefix=false}}
            <h3>{{:~tag.tagCtx.args[0]}}</h3>
            <p>no info</p>
        {{/myPersonInfo}}
    </script>

    //也可以使用tag.tagCtx.content获取到自定义标签中的内容
    $.views.tags('myPersonInfo', {
        template: '{{if ~tag.tagCtx.props.addPrefix == true}}\
                    <h3>Hello, {{:~tag.tagCtx.args[0]}}</h3>\
                    <p>{{:~tag.tagCtx.props.age}}</p>\
                    {{else tmpl=~tag.tagCtx.content}}\
                    {{:~tag.tagCtx.content}}\
                    {{/if}}'
    });

  //或者这样
    <script id="teamTemplate" type="text/x-jsrender">
      <p><b>{{:title}}</b></p>
      <ul>
        {{range members start=1 end=2}} 
          <li>
            {{:name}}
          </li>
        {{/range}}
      </ul> 
    </script>

    $.views.tags("range", {
        template: 
            "{{for ~tag.tagCtx.args[0]}}" +
            "{{if #index >= ~tag.tagCtx.props.start && #index <= ~tag.tagCtx.props.end}}" +
            "{{include tmpl=~tag.tagCtx.content/}}" +
            "{{/if}}" +
            "{{/for}}"
    });

当然也可以为某个模板注册私有的自定义标签

    $.views.tags({
      myTag1: ...,
      myTag2: ...
    }, parentTemplate);
6、使用$.views.settings.debugMode()开启调试模式

当我们使用jsrender写代码时,难免会报一些错,然后直接在控制台抛出错误异常。但是我们想对错误信息做一些处理,比如直接把异常输出到页面,或者自定义错误信息为字符窜,或者抛出错误的时候,先调用函数处理再抛出错误。jsrender提供了$.views.settings.debugMode()传入不同的参数来改变全局抛出异常的情况。

  1. false 内容不会被渲染并且在控制台抛出异常 (默认)
  2. true 抛出的异常会渲染在页面中,在控制台中没有异常抛出
  3. "string" 字符串会替代错误信息渲染在页面中,在控制台没有异常抛出
  4. function 在异常抛出之前,会先经过传入的方法处理,如果此方法没有return,那么就会把错误信息渲染到页面中,如果有return,那么页面中就会渲染return 的信息
    $.views.settings.debugMode(function(err){

        var errMsg = '';

        if(err){

            errMsg = 'here has error the err is ' + err;
        }
        return errMsg;
    });

当然,除了为全局处理错误信息之外,也可以使用onError属性为某一个标签定义错误信息,如果你已经在全局设置了处理错误信息的方法,并且传入的参数有返回值(上面提到的3跟4),onError属性是不会起作用的

    {{:address.street onError="Address unavailable"}}
    {{for phones() onError="<em>No phones</em>"}}
    {{:address.street onError=name + " has no address"}}
    {{:address.street onError=~errorMessages(1, name, 'address')}}
    {{myCustomTag ... onError=""}}

    $.views.settings.debugMode("this is global err"); //会覆盖上面的onError属性
    or
    $.views.settings.debugMode(function(err){

        var errMsg = '';

        if(err){

            errMsg = 'here has error the err is ' + err;
        }
        return errMsg; //如果return 就会渲染这个,如果不return 就会渲染onError属性的值
    });

当我们使用render()方法渲染模板的时候,我们想查看某个对象或者某个属性的值,但是并不能在模板中打断点,jsrender提供以下方式在渲染模板的过程中输出对象或者属性的值

{{dbg expression/}}  tag
{{dbg: expression}}   convert
{{:~dbg(expression)}}  helper

以上的三种方式都会把值渲染在页面,并在控制台中输出值,但是呢,如果值是一个对象,那么就会输出字符串,比如

JsRender dbg breakpoint: [object Object]

我们需要看对象里面有什么属性,但是却给我们输出的是字符串,显然不方便调试,所以我们可以重写这个调试标签,让输入值原样输出

    //重写dbg调试模式
    $.views.helpers({
        dbg: function(val){

            try {
                console.log(val);
                return val;
            }catch (e) {
                console.log(e);
            }
        }
    });

    $.views.converters({
        dbg: function(val){

            try {
                console.log(val);
                return val;
            }catch (e) {
                console.log(e);
            }
        }
    });

    $.views.tags('dbg', function(val){

        try {
            console.log(val);
            return val;
        }catch (e) {
            console.log(e);
        }
    });

这样重写之后,控制台输出变成这样,就比较方便调试了

Object {name: "bolin", age: 20}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容