Javascript でHTML::Template

Filed Under (Uncategorized) by on 23-09-2008

どうも、ひろきのだいちです。こんにちは。

昨今のWeb開発ではAjaxやDHTMLを用いた大規模なプロジェクトが増えてきているようです。
大掛かりにDOMを書き換える際にまいどまいど、文字列連結やらなにやらを繰り返していたら
デザイナー/コーダとの連携などがむずかしく製作者以外、何をしているんだかわかりにくいコードができあがってしまいます。

一方、サーバサイド言語では多くのテンプレートライブラリやフレームワークが多数存在していて
phpやerubyのように埋め込みで言語を記述することができたりPerlであればTemplateToolkitやHTML::Templateを用いることでviewを分離することができたりとなにかと便利です。

TTのjavascript実装はJemplateとしてすでに存在しているようなので
HTMLTemplateをjavascriptで実装してみました。

JAVASCRIPT:
  1. function composeHTML(data){
  2. if(data.check){
  3. return "こんにちは
  4. 世界"+data.exc;
  5. }else{
  6. return "さようなら
  7. 世界"+data.exc;
  8. }
  9. }
  10.  
  11. composeHTML({check:true,exc:'!'});

こんなふうに直に文字列を結合していくとめんどうですよね。
HTML.Template.jsを使うと

JAVASCRIPT:
  1. var tmpl=new HTML.Template([
  2. 'こんにちは
  3. 世界',
  4. 'こんにちは
  5. 世界'
  6. ].join(''));
  7.  
  8. tmpl.param({check:true,exc:'!'});
  9. tmpl.output();

こんなかんじでかけます。

ん?メリットが見えない?

これはちっちゃな例なのでそんなもんですが、
このテキストデータをajaxでとってきたり、サーバサイドから出力してもらうことで
templateを製作する人とスクリプトを書く人を分離できたり、
メンテするときにHTMLの文法しかわからないひとが見てもある程度チェックすることができます。

チーム内にperlのHTML::Templateの知的資産がたまっているとさらに便利。

如何にソースをおいておきます。
(使いたい場合はコメントくれるとうれしいw)

html_template_js

追記:なんかテストとおらないバージョンのやつをあげてしまったので↑のをもっていってください。

それじゃ!れっつえんじょいjavascriptライフ。

JAVASCRIPT:
  1. if(!Prototype)throw('HTML.Template require prototype.js');
  2. var HTML = HTML||{};
  3. HTML.Template = Class.create();
  4. HTML.Template.Version ='0.1';
  5. HTML.Template.CHUNK_REGEXP = new RegExp('<(\\/)?TMPL_(VAR|LOOP|IF|ELSE|ELSIF|UNLESS)(\\s(NAME)=?(\\w+)|\\s(EXPR)="([^"]+)")?>');
  6. HTML.Template.TRUE_FUNC = function() {return true;}
  7. HTML.Template.FALSE_FUNC   = function() {return false;}
  8. HTML.Template.GLOBAL_FUNC ={};
  9. HTML.Template.createElement = function(type, option) {
  10.   return new HTML.Template[type.toUpperCase() + 'Element'](option);
  11. };
  12. HTML.Template.registerFunction = function(name,func){
  13.   HTML.Template.GLOBAL_FUNC[name] = func;
  14. };
  15. HTML.Template.Element = Class.create();
  16. HTML.Template.Element.prototype = {
  17.   initialize: function(option) {
  18.     if (this.type == 'text') {
  19.       this.value = option;
  20.     } else {
  21.       $H(option).each(function(e) {
  22.         this[e[0]] = e[1];
  23.       }.bind(this));
  24.     }
  25.   },
  26.   isParent: Prototype.emptyFunction,
  27.   execute: Prototype.emptyFunction,
  28.   isClose: function() {
  29.     return this.closeTag;
  30.   },
  31.   appendChild: function(child) {
  32.     if (!this.children) this.children = [];
  33.     this.children.push(child);
  34.   },
  35.   inspect: function() {
  36.     return Object.toJSON(this);
  37.   },
  38.   toString: function() {
  39.     return '<' + ((this.closeTag) ? '/': '') + this.type + ((this.hasName) ? ' NAME=': '') + ((this.name) ? this.name: '') + '>';
  40.   },
  41.   getParam:function(param){
  42.     if(this.hasName){
  43.       return (param[this.name])?param[this.name]:'';
  44.     }
  45.     if(this.hasExpr){
  46.       with(HTML.Template.GLOBAL_FUNC){with(param){
  47.         var retValue;
  48.         try{
  49.           eval('retValue='+this.expr);
  50.         }catch(e){
  51.           console.log(e);
  52.         }
  53.         if(Object.isUndefined(retValue)){
  54.           return false;
  55.         }
  56.         return retValue;
  57.       }}
  58.     }
  59.   }
  60. };
  61. Object.extend(HTML.Template, {
  62.   ROOTElement: Class.create(HTML.Template.Element, {
  63.     type: 'root',
  64.     isParent: HTML.Template.TRUE_FUNC,
  65.     execute: function(param) {
  66.       return this.children.map(function(e) {
  67.         return e.execute(param)
  68.       }).join('');
  69.     }
  70.   }),
  71.   LOOPElement: Class.create(HTML.Template.Element, {
  72.     type: 'loop',
  73.     isParent: HTML.Template.TRUE_FUNC,
  74.     execute: function(param) {
  75.       var blank  = '';
  76.       var target = this.getParam(param);
  77.       if (Object.isArray(target)) {
  78.         return target.map(function(t) {
  79.           return this.children.map(function(e) {
  80.             return e.execute(t)
  81.           }).join(blank);
  82.         }.bind(this)).join(blank);
  83.       }
  84.       return blank;
  85.     }
  86.   }),
  87.   VARElement: Class.create(HTML.Template.Element, {
  88.     type: 'var',
  89.     isParent: HTML.Template.FALSE_FUNC,
  90.     execute: function(param) {
  91.       return this.getParam(param).toString();
  92.     }
  93.   }),
  94.   IFElement: Class.create(HTML.Template.Element, {
  95.     type: 'if',
  96.     isParent: HTML.Template.TRUE_FUNC,
  97.     getCondition:function(param){
  98.         return !!this.getParam(param);
  99.     },
  100.     execute: function(param) {
  101.       var retValue = "";
  102.       var ELSE = 'else';
  103.       var ELSIF = 'elsif'
  104.       var condition = this.getCondition(param);
  105.       var length = this.children.length;
  106.       for (var i = 0; i <length; i++) {
  107.         var child = this.children[i];
  108.         if (child.type == ELSIF) {
  109.           if (condition) {
  110.             break;
  111.           } else {
  112.             condition = child.getCondition(param);
  113.           }
  114.         } else if (child.type == ELSE) {
  115.           if (condition) {
  116.             break;
  117.           } else {
  118.             condition = true;
  119.           }
  120.         } else {
  121.           if (condition) {
  122.             retValue += child.execute(param);
  123.           } else {
  124.             continue;
  125.           }
  126.         }
  127.       }
  128.       return retValue;
  129.     }
  130.   }),
  131.   ELSEElement: Class.create(HTML.Template.Element, {
  132.     type: 'else',
  133.     isParent: HTML.Template.FALSE_FUNC
  134.   }),
  135.   ELSIFElement: Class.create(HTML.Template.Element, {
  136.     type: 'elsif',
  137.     isParent: HTML.Template.FALSE_FUNC,
  138.     getCondition:function(param){
  139.         return !!this.getParam(param);
  140.     }
  141.   }),
  142.   TEXTElement: Class.create(HTML.Template.Element, {
  143.     type: 'text',
  144.     closeTag: false,
  145.     isParent: HTML.Template.FALSE_FUNC,
  146.     execute: function() {
  147.       return this.value;
  148.     }
  149.   })
  150. });
  151. HTML.Template.UNLESSElement = Class.create(HTML.Template.IFElement,{
  152.     type:'unless',
  153.     getCondition:function(param){
  154.         return !this.getParam(param);
  155.     }
  156. });
  157. HTML.Template.prototype = {
  158.   initialize: function(option) {
  159.     if (! (option['type'] && option['source'])) {
  160.       throw ('option needs {type:~~,source:~~}');
  161.     }
  162.     if (option['type'] == 'text') {
  163.       this._source = option['source'];
  164.     }
  165.     this._param  = {};
  166.     this._chunks = [];
  167.     this.parse().compile();
  168.   },
  169.   registerFunction:function(name,func){
  170.     this._param[name]=func;
  171.   },
  172.   param: function(obj) {
  173.     if (Object.isArray(obj)) {
  174.       throw ('template.param not array');
  175.     }
  176.     for (var prop in obj) {
  177.       this._param[prop] = obj[prop];
  178.     }
  179.   },
  180.   parse: function() {
  181.     var source = this._source;
  182.     this.root = HTML.Template.createElement('root', {
  183.       closeTag: false
  184.     });
  185.     this._chunks.push(this.root);
  186.     while (source.length> 0) {
  187.       var results = source.match(HTML.Template.CHUNK_REGEXP);
  188.       if (!results) {
  189.         this._chunks.push(HTML.Template.createElement('text', source));
  190.         source = '';
  191.         break;
  192.       }
  193.       var index = 0;
  194.       if ((index = source.indexOf(results[0]))> 0) {
  195.         var text = source.slice(0, index);
  196.         this._chunks.push(HTML.Template.createElement('text', text));
  197.         source = source.slice(index);
  198.       };
  199.       this._chunks.push(HTML.Template.createElement(results[2], {
  200.         hasName: (results[4]) ? true: false,
  201.         name: results[5],
  202.         closeTag: (results[1]) ? true: false,
  203.         hasExpr: (results[6]) ? true: false,
  204.         expr :results[7]
  205.       }));
  206.       source = source.slice(results[0].length);
  207.     };
  208.     this._chunks.push(HTML.Template.createElement('root', {
  209.       closeTag: true
  210.     }));
  211.     return this;
  212.   },
  213.   compile: function() {
  214.     var context = [];
  215.     this._chunks.each(function(e) {
  216.       if (e.isParent()) {
  217.         if (e.isClose()) {
  218.           var parent = context.pop();
  219.           if (parent.type != e.type) {
  220.             throw ('invalid');
  221.           }
  222.         } else {
  223.           var parent = context.last();
  224.           if (parent) {
  225.             parent.appendChild(e);
  226.           }
  227.           context.push(e);
  228.         }
  229.       } else {
  230.         var parent = context.last();
  231.         parent.appendChild(e);
  232.       }
  233.     });
  234.   },
  235.   output: function() {
  236.     return this.root.execute(this._param);
  237.   }
  238. };

Comments:

Post a comment