Skip to content

Latest commit

 

History

History
1011 lines (745 loc) · 18.8 KB

TEMPLATE.md

File metadata and controls

1011 lines (745 loc) · 18.8 KB

模板系统

概述

模板系统主要用来分离视图与数据,用以生成特定格式的文档,可以提升开发效率,良好的设计也可以使得代码重用变得更加容易,主要特点包括:

  • 分离代码(业务逻辑代码与视图代码)
  • 数据分离(动态数据与静态数据)
  • 代码单元共享(代码共享)

本文主要介绍NEJ框架的模板系统及其使用,模板系统提供两大类模板的支持:基本类型和资源类型,模版系统的实现模块包括util/template/tpl和util/template/jst

模板结构

NEJ模板系统可以直接用字符串作为模板,也可以使用TEXTAREA标签封装模板,使用TEXTAREA标签封装模版的形式如下

模板类型

基本类型

结构范例

<textarea name="txt" id="txt-template-1">
  <div>
    <p>aaaaaaaaaaaaaa</p>
    <!-- content here -->
  </div>
</textarea>

name

标识模板类型,主要包括txt/jst/ntp三种,每种类型的详细说明见模板类型章节

id

模板标识,后续可以用模板接口输入该ID取得模板的内容

资源类型

结构范例

<textarea name="css" data-src="./a.css" data-version="20140901">
  .a{color:#000;}
  .b{color:#ddd;}
</textarea>

name

标识模板类型,主要包括css/js/html/res四种类型

data-src

标识模板资源地址,多个地址用“,”分隔,以“./”,“../”开始的相对路径相对于当前文件所在的目录

data-version

资源版本信息

模板类型

模板系统提供两类模板,根据实际需求,每类模板又做了细化分类

基本类型

基本类型模板主要包括 txt/jst/ntp 三类模板

txt

txt模板提供了基本的文本缓存功能, 模板标示符为txt,开发人员可以使用这个模板来缓存html结构

如果脚本里已经有了模版内容则可以通过_$addTextTemplate接口添加到缓存中

代码举例

NEJ.define([
    'util/template/tpl'
],function(_t){
    // 模版添加到缓存池中
    _t._$addTextTemplate('txt-template-1','\
        <div>\
          <p>aaaaaaaaaaaaaa</p>\
          <!-- content here -->\
        </div>\
    ');

    // TODO
});

如果模版使用textarea标签封装的话采用以下形式

代码举例

<textarea name="txt" id="txt-template-1">
  <div>
    <p>aaaaaaaaaaaaaa</p>
    <!-- content here -->
  </div>
</textarea>

此类模板后续可以使用_$getTextTemplate接口获取

NEJ.define([
    'util/template/tpl'
],function(_t){
    // 解析模版,支持textarea的批量解析
    _t._$parseTemplate('txt-template-1');

    // 根据模板ID取模板内容
    // 返回字符串类型的模板内容
    var _text = _t._$getTextTemplate('txt-template-1');

    // TODO
});

ntp

节点模版,主要用于UI控件中对复杂结构的缓存和重用,见ITEM控件

如果是字符串的模版,则可以通过_$addNodeTemplate接口添加至缓存

代码举例

NEJ.define([
    'util/template/tpl'
],function(_t){
    // 模版添加到缓存池中
    _t._$addNodeTemplate('ntp-template-1','\
        <div>\
          <p>aaaaaaaaaaaaaa</p>\
          <!-- content here -->\
        </div>\
    ');

    // TODO
});

如果模版使用textarea标签封装的话采用以下形式

代码举例

<textarea name="ntp" id="ntp-template-1">
  <div>
    <p>aaaaaaaaaaaaaa</p>
    <!-- content here -->
  </div>
</textarea>

此类模板后续可以使用_$getNodeTemplate接口获取

NEJ.define([
    'util/template/tpl'
],function(_t){
    // 解析模版,支持textarea的批量解析
    _t._$parseTemplate('ntp-template-1');

    // 根据模板ID取模板内容
    // 返回模板对应的DOM树结构
    var _node = _t._$getNodeTemplate('ntp-template-1');

    // TODO
});

jst

符合JST语法规则的模版,类似服务器端模版如freemarker、verlocity等

如果模版是字符串,则可以通过_$add接口添加到缓存

代码举例

NEJ.define([
    'util/template/jst'
],function(_t){
    // 添加JST模板缓存
    _t._$add('\
        <table class="w-table">\
          <thead>\
            <tr><th>序号</th><th>姓名</th><th>性别</th></tr>\
          </thead>\
          {if !defined("workers")}\
          <tr><td colspan="3">数据加载失败,请稍后再试!</td></tr>\
          {elseif workers&&workers.length}\
            {list workers as x}\
              <tr{if x_index==x_length-1} class="last"{/if}>\
                <td>${x_index+1}</td>\
                <td>${x.name}</td>\
                <td>{if x.gender==1}男{else}女{/if}</td>\
              </tr>\
            {/list}\
          {else}\
          <tr><td colspan="3">没有工人!</td></tr>\
          {/if}\
        </table>\
    ');
})

如果模版使用textarea标签封装的话采用以下形式

代码举例

<textarea name="jst" id="jst-template-1">
    <table class="w-table">
      <thead>
        <tr><th>序号</th><th>姓名</th><th>性别</th></tr>
      </thead>\
      {if !defined("workers")}
      <tr><td colspan="3">数据加载失败,请稍后再试!</td></tr>
      {elseif workers&&workers.length}
        {list workers as x}
          <tr{if x_index==x_length-1} class="last"{/if}>
            <td>${x_index+1}</td>
            <td>${x.name}</td>
            <td>{if x.gender==1}男{else}女{/if}</td>
          </tr>
        {/list}
      {else}
      <tr><td colspan="3">没有工人!</td></tr>
      {/if}
    </table>
</textarea>

后续可以使用_$get接口获取整合数据的结果

代码举例

NEJ.define([
    'util/template/jst'
],function(_t){
    // 添加模版缓存
    // 也可以用_$parseTemplate接口批量添加
    _t._$add('jst-template-1');

    // 根据模板ID取模板内容
    // 返回整合数据后的html代码
    var _html = _t._$get('jst-template-1',{
        workers:[
            {name:'abc',gender:1},
            {name:'def',gender:1},
            {name:'ghi'}
        ]
    });

    // TODO
});

资源类型

资源类型模板又分为 css/js/html/res 四类,资源类模板只能用TEXTAREA标签封装

css

样式资源模版,可以采用内联也可以采用外联方式载入

代码举例

<div id="template-box">
    <!-- 纯内联样式 -->
    <textarea name="css">
      .a{color:#aaa;}
      .b{color:#bbb;}
    </textarea>

    <!-- 纯外联 -->
    <textarea name="css" data-src="./a.css,./b.css" data-version="v1"></textarea>

    <!-- 复合内联外联 -->
    <textarea name="css" data-src="./a.css,./b.css" data-version="v1">
      .a{color:#aaa;}
      .b{color:#bbb;}
    </textarea>
</div>

对于内外联复合的样式模板,解析成样式时,内联的样式在外联的样式的后面

系统初始化时使用_$parseTemplate接口解析模版

代码举例

NEJ.define([
    'util/template/tpl'
],function(_t){
    // 激活样式模版
    _t._$parseTemplate('template-box');

    // TODO
});

js

脚本资源模版,可以采用内联也可以采用外联方式载入

代码举例

<div id="template-box">
    <!-- 纯内联样式 -->
    <textarea name="js">
      var a = 'aaaa';
      var b = 'bbbb';
    </textarea>

    <!-- 纯外联 -->
    <textarea name="js" data-src="./a.js,./b.js" data-version="v1"></textarea>

    <!-- 复合内联外联 -->
    <textarea name="js" data-src="./a.css,./b.css" data-version="v1">
      var a = 'aaaa';
      var b = 'bbbb';
    </textarea>
</div>

对于内外联复合的脚本模板激活时,内联的脚本在外联的脚本载入完成后执行

系统初始化时使用_$parseTemplate接口解析模版

代码举例

NEJ.define([
    'util/template/tpl'
],function(_t){
    // 激活脚本
    _t._$parseTemplate('template-box');

    // TODO
});

html

外联模版集合,载入的结构会递归进行调用_$parseTemplate接口解析模版,项目中一般用于载入通用控件结构,html模版的data-src不支持多个模版用“,”分隔的形式

代码举例

<div id="template-box">
    <textarea name="html" data-src="./a.html" data-version="v1"></textarea>
    <textarea name="html" data-src="./b.html" data-version="v1"></textarea>
</div>

系统初始化时使用_$parseTemplate接口解析模版,如果要确保代码中能够使用外联的模版,则需要在document的ontemplateready事件中处理

代码举例

NEJ.define([
    'base/event',
    'util/template/tpl'
],function(_v,_t){
    // 载入外联模版集合
    _t._$parseTemplate('template-box');

    _v._$addEvent(
        document,'templateready',function(){
            // 这里可以保证外联的模板可用

            // TODO
        }
    );

    // TODO
});

res

外联文本资源,载入后作为txt类型的模版使用,需要指定id

代码举例

<div id="template-box">
    <textarea name="txt" id="txt-0" data-src="./a.html" data-version="v1"></textarea>
    <textarea name="txt" id="txt-1" data-src="./b.html" data-version="v1"></textarea>
</div>

后续通过_$getTextTemplate接口使用,如果要确保代码中能够使用外联的模版,则需要在document的ontemplateready事件中处理

代码举例

NEJ.define([
    'util/template/tpl'
],function(_t){
    // 解析模版,支持textarea的批量解析
    _t._$parseTemplate('template-box');

    _v._$addEvent(
        document,'templateready',function(){
            // 这里可以保证外联的模板可用

            // 根据模板ID取模板内容
            // 返回字符串类型的模板内容
            var _text0 = _t._$getTextTemplate('txt-0');
            var _text1 = _t._$getTextTemplate('txt-1');

            // TODO
        }
    );

    // TODO
});

JST语法

表达式

${}

描述:求值表达式,表达式中不可以包含 “{”或者“}”

语法:

${expr}

${expr|modifier}

${expr|modifier1|modifier2|...|modifierN}

${expr|modifier1:argExpr1_1}

${expr|modifier1:argExpr1_1,argExpr1_2,...,argExpr1_N}

${expr|modifier1:argExpr1_1|...|modifierN:argExprN_1,argExprN_2,...,argExprN_M}

范例:

${customer.firstName}

${customer.firstName|capitalize}

${customer.firstName|default:"no name"|capitalize}

${article.getCreationDate|default:new Date()|toCalendarControl:"YYYY.MM.DD",true,"creation Date"}

${(lastQuarter.calcRevenue() - fixedCosts) / 10000}

${% %}

描述:求值表达式,表达式中可以包含 “{”或者“}”

语法:

${% expr %}

范例:

${% emitLink("Solution and Products", {color: "red", blink: false}) %}

语句

list break

描述:遍历数组

语法1:

{list seq as varName}
    ...
{break}
    ...
{/list}

范例1:

{list ["aaa", "bbbb", "ccccc"] as x}
  ${x_index}/${x_length}:${x}<br/>
{/list}

备注:

  • x_index为内置变量,值为循环的索引值。
  • x_length为内置变量,值为列表长度, 上例中值为3。

语法2:

{list from..to as varName}
    ...
{/list}

备注:循环时包含from和to值

范例2:

{list 2..10 as x}
    ${x_index}/${x_length}:${x}<br/>
{/list}

备注:

  • x_index为内置变量,值为循环的索引值。
  • x_length为内置变量,值为列表长度, 上例中值为9。

for forelse

描述:遍历HASH表

语法:

{for varName in hash}
    ...
{forelse}
    ...
{/for}

注:forelse 子语句为可选

范例:

{for x in {a:"aaa", b:"bbbb", c:"ccccc"}}
    ${x_key} - ${x}<br/>
{forelse}
    no pro
{/for}

注:x_key为内置变量,值为当前项的键值。

if elseif else

描述:条件控制语句

语法:

{if expr}
    ...
{elseif expr}
    ...
{else}
    ...
{/if}

注:elseif、else 子语句为可选

范例:

{if gender == 1}
    男
{elseif gender == 0}
    女
{else}
    春哥
{/if}

var

描述:变量定义

语法:

{var varName}

{var varName = expr}

范例:

{var test = "sssssss"}

macro

描述:宏定义

语法:

{macro macroName(arg1, arg2, ... argN)}
    ... body of the macro ...
{/macro}

范例:

{macro htmlList(dataList, optionalListType)}
    {var listType = optionalListType != null ? optionalListType : "ul"}
    <${listType}>
        {for item in dataList}
            <li>${item}</li>
        {/for}
    </${listType}>
{/macro}

调用宏:

${htmlList(["首页", "日志","相册", "关于我"])}

输出:

<ul>
    <li>首页</li>
    <li>日志</li>
    <li>相册</li>
    <li>关于我</li>
</ul>

cdata

描述:文本块,内容不做语法解析

语法:

{cdata}
    ...no parsed text ...
{/cdata}

{cdata EOF}
    ...no parsed text ...
EOF

范例:

{cdata}
    ${customer.firstName}${customer.lastName}
{/cdata}

{cdata END_OF_CDATA_SECTION}
    ${customer.firstName}${customer.lastName}
END_OF_CDATA_SECTION

输出:${customer.firstName}${customer.lastName}

minify

描述:压缩文本内容,内容不做语法解析

语法:

{minify}
    ...multi-line text which will be stripped of line-breaks...
{/minify}

{minify EOF}
    ...multi-line text which will be stripped of line-breaks...
EOF

范例:

{minify}
    no parsed
    text
    and
    merge
    one
    line
{/minify}

{minify EOF}
    no parsed
    text
    and
    merge
    one
    line
EOF

输出:no parsed text and merge one line

eval

描述:执行javascript语句,不做语法解析

语法:

{eval}
    ...javascript statement...
{/eval}

{eval EOF}
    ...javascript statement...
EOF

范例:

{eval}
    var a = "aaaa";
    alert(a);
    function b(arg){
        alert(arg);
    }
{/eval}

{eval EOF}
    var a = "aaaa";
    alert(a);
    function b(arg){
        alert(arg);
    }
EOF

扩展

rand

描述:随机一个指定长度的纯数字的串

语法:

${number_expr|rand}

范例:

${10|rand}

输出:3456785438

escape

描述:编码字符串

语法:

${expr|escape}

范例:

${"<div>1234<a href="#">163</a></div>"|escape}

输出:&lt;div&gt;1234&lt;a href="#"&gt;163&lt;/a&gt;&lt;/div&gt;

format

描述:格式化日期

语法:

${data_expr|format:format_expr}

范例:

${new Date()|format:"yyyy-MM-dd HH:mm:ss"}

输出:2012-06-13 16:30:55

default

描述:指定默认值

语法:

${expr|default:default_expr}

范例:

${null|default:"default value"}

输出:default value

注:当expr为undefiend,null,false,0或者空字符串时取默认值

ITEM控件

ITEM控件提供了结构+逻辑的缓存功能,适合于列表项带复杂逻辑的模版,一般使用ntp模版来封装结构,列表类的ITEM基类抽象在ui/item/list模块中实现,因为ITEM控件也是UI控件,所以遵循UI控件的规则

如下图所示的评论列表中,每一项评论又具有回复列表、回复和删除功能,我们可以将这种结构做成ITEM模版

评论列表

ITEM控件的目录结构

  comment
     | - comment.css    评论项样式
     | - comment.html   评论项结构
     | - comment.js     评论项逻辑

comment.html文件使用ntp类型的模版做结构

<div class="m-cmt">
  <div class="fce"><img class="j-flag"/></div>
  <div class="box">
    <div class="ttl j-flag"><!-- 用户占位 --></div>
    <div class="cnt j-flag"><!-- 内容占位 --></div>
    <div class="act j-flag">
      <a href="#" data-action="reply">回复</a>
      <a href="#" data-action="delete">删除</a>
    </div>
  </div>
</div>

comment.js文件继承ui/item/list模块的_$$ListItem类进行扩展

NEJ.define([
    'base/klass',
    'base/element',
    'base/event',
    'ui/item/list',
    'util/template/tpl',
    'text!./comment.css',
    'text!./comment.html'
],function(_k,_e,_v,_i,_t,_css,_html,_p,_o,_f,_r){
    var _pro;

    // 列表项构造
    _p._$$CommentItem = _k._$klass();
    _pro = _p._$$CommentItem._$extend(_i._$$ListItem);

    // 外观
    _pro.__initXGui = (function(){
        var _seed_css = _e._$pushCSSText(_css),
            _seed_html = _e._$addNodeTemplate(_html);
        return function(){
            this.__seed_css = _seed_css;
            this.__seed_html = _seed_html;
        };
    })();

    // 结构
    _pro.__initNode = function(){
        this.__super();
        // 0 - 头像图片节点
        // 1 - 用户名节点
        // 2 - 内容节点
        // 3 - 操作行为节点
        var _list = _e._$getByClassName(
            this.__body,'j-flag'
        );
        this.__nface = _list[0];
        this.__nuser = _list[1];
        this.__ncont = _list[2];
        // 事件
        _v._$addEvent(
            _list[3],'click',
            this.__onAction._$bind(this)
        );
    };

    // 刷新
    _pro.__doRefresh = function(_data){
        this.__nface.src = _data.face;
        this.__nuser.innerHTML = _data.username;
        this.__ncont.innerHTML = _data.content;
        // 子评论列表
        if (!!_data.replies){
            // 子评论构造同当前评论项
            this.__items = _t._$getItemTemplate(
                _data.replies,this.constructor,{
                    parent:this.__body,
                    onreply:this.__onReply._$bind(this),
                    ondelete:this.__onDelete._$bind(this)
                }
            );
        }
    };

    // 操作
    _pro.__onAction = function(_event){
        var _node = _v._$getElement(_event,'d:action');
        if (!_node) return;
        // 操作
        switch(_e._$dataset(_node,'action')){
            case 'reply':
                // 分配回复编辑器控件
                // 触发onreply事件
                // TODO
            break;
            case 'delete':
                // 删除确认
                // 触发ondelete事件
                // TODO
            break;
        }
    };

    // TODO

    return _p;
});

在上层应用中使用_$getItemTemplate来分配ITEM控件列表

NEJ.define([
    'util/template/tpl',
    '/path/to/comment.js'
],function(_t,_i){

    // TODO

    var _list = _t._$getItemTemplate(
        _data.replies,this.constructor,{
            parent:'list-box',
            onreply:function(_data){
                // TODO
            },
            ondelete:function(_data){
                // TODO
            }
        }
    );

});