tpl$基本
tpl$解説
el$
list$
curryQ
tpl$(テンプレ,データオブジェクト):テンプレートエンジン
  • テンプレート関数に関数実行機能を追加
  • 予約語:fun = 関数用, self = 自身(wrapで使う時)<%=raw self%>
  • または、:$node().wrap(テンプレデータ,テンプレ); ::ターゲットで共通の変数を使う場合
<%= … %>: テキストデータの出力
<%=raw …%>: エスケープなしで記述 -> htmlタグを使う場合
データは複数利用可能
テンプレート関数のテスト単体

let $body3 = '変数に入れたテキストデータ'
let $body4 = '<p>変数に入れたhtmlタグ</p>'

let $tpl3 = '<div id="<%=id%>" class="<%=clss%>"><%=body%><%=body2%><%=raw body2%><%=body3%><%=body4%><%=raw body4%></div>';
let $tpldata = {id:'hogereta',clss:'p-2 blue text-white',body:'=を付けてテキスト出力',body2:'<p>htmlタグ付きのデータ</p>',body3:$body3,body4:$body4};
<% … %>: normal script part ー> javascriptコードを記述したい場合

<% if (isFoo) { %>   // javascriptのコードを記述
  <%= foobar %>
<% } else { %>  // javascriptのコードを記述
  <%= foobaz %>
<% } %>  // javascriptのコードを記述
<%=raw html %>

window.onload = function () {
  var html = template('tmpl1', {

    isFoo : true,
    foobar : 'foobar!!',
    foobaz : 'foobaz!!',
    html : '<marquee>Helloooo</marquee>'
  });
console.log(html);
};



Nodeは指定できるー>できない!
node指定できる?!
テンプレート関数のテスト単体
JavaScript Micro-Templating::jQueryのJohn Resig

とにかく軽い:jQueryのジョン・グレグ氏作

解説:Micro-Templatingをコードリーディング

重要:TODO)セキュリティについての配慮:javascriptセキュリティ

Micro-Templatingのアップデート: Rick Stahl's blog

// Simple JavaScript Templating
// John Resig - https://johnresig.com/ - MIT Licensed
(function(){
  var cache = {};

  this.tmpl = function tmpl(str, data){
    var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
        "with(obj){p.push('" +
        str
          .replace(/[\r\t\n]/g, " ")
          .split("<%").join("\t")
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
          .replace(/\t=(.*?)%>/g, "',$1,'")
          .split("\t").join("');")
          .split("%>").join("p.push('")
          .split("\r").join("\\'")
      + "');}return p.join('');");
    return data ? fn( data ) : fn;
  };
})();
micro-template.js

Micro-Templatingをカスタムして利用

lodashのテンプレートエンジンと同じ変数記法が使える

記法の変更が簡単! -> laraabelのBladeテンプレートと統一するかを検討中

raw html機能が付いている

<- include, wrapは、外して使用

micro-template.js - オリジナルコード

  /**
   * https://github.com/cho45/micro-template.js
   * (c) cho45 http://cho45.github.com/mit-license
   */
  function template (id, data) {
  	var me = arguments.callee;
  	if (!me.cache[id]) me.cache[id] = (function () {
  		var name = id, string = /^[\w\-]+$/.test(id) ? me.get(id): (name = 'template(string)', id); // no warnings
  		var line = 1, body = (
  			"try { " +
  				(me.variable ?  "var " + me.variable + " = this.stash;" : "with (this.stash) { ") +
  					"this.ret += '"  +
  					string.
  						replace(/<%/g, '\x11').replace(/%>/g, '\x13'). // if you want other tag, just edit this line
  						replace(/'(?![^\x11\x13]+?\x13)/g, '\\x27').
  						replace(/^\s*|\s*$/g, '').
  						replace(/\n|\r\n/g, function () { return "';\nthis.line = " + (++line) + "; this.ret += '\\n" }).
  						replace(/\x11=raw(.+?)\x13/g, "' + ($1) + '").
  						replace(/\x11=(.+?)\x13/g, "' + this.escapeHTML($1) + '").
  						replace(/\x11(.+?)\x13/g, "'; $1; this.ret += '") +
  				"'; " + (me.variable ? "" : "}") + "return this.ret;" +
  			"} catch (e) { throw 'TemplateError: ' + e + ' (on " + name + "' + ' line ' + this.line + ')'; } " +
  			"//@ sourceURL=" + name + "\n" // source map
  		).replace(/this\.ret \+= '';/g, '');
  		var func = new Function(body);
  		var map  = { '&' : '&', '<' : '<', '>' : '>', '\x22' : '"', '\x27' : ''' };
  		var escapeHTML = function (string) { return (''+string).replace(/[&<>\'\"]/g, function (_) { return map[_] }) };
  		return function (stash) { return func.call(me.context = { escapeHTML: escapeHTML, line: 1, ret : '', stash: stash }) };
  	})();
  	return data ? me.cache[id](data) : me.cache[id];
  }
  template.cache = {};
  template.get = function (id) { return document.getElementById(id).innerHTML };

  /**
   * Extended template function:
   *   requires: basic template() function
   *   provides:
   *     include(id)
   *     wrapper(id, function () {})
   */
  function extended (id, data) {
  	var fun = function (data) {
  		data.include = function (name, args) {
  			var stash = {};
  			for (var key in template.context.stash) if (template.context.stash.hasOwnProperty(key)) {
  				stash[key] = template.context.stash[key];
  			}
  			if (args) for (var key in args) if (args.hasOwnProperty(key)) {
  				stash[key] = args[key];
  			}
  			var context = template.context;
  			context.ret += template(name, stash);
  			template.context = context;
  		};

  		data.wrapper = function (name, fun) {
  			var current = template.context.ret;
  			template.context.ret = '';
  			fun.apply(template.context);
  			var content = template.context.ret;
  			var orig_content = template.context.stash.content;
  			template.context.stash.content = content;
  			template.context.ret = current + template(name, template.context.stash);
  			template.context.stash.content = orig_content;
  		};

  		return template(id, data);
  	};

  	return data ? fun(data) : fun;
  }

  template.get = function (id) {
  	var fun = extended.get;
  	return fun ? fun(id) : document.getElementById(id).innerHTML;
  };
  this.template = template;
  this.extended = extended;
tpl$(テンプレ,データ):: micro-template.jsをカスタム

var me = arguments.callee; が非推奨!! -> なしバージョンにカスタム

挿入する変数にタグが含まれる場合は、記述を、<%=raw 変数名 %>にする。

wrapは、_$(hoge).wrap($tpl,$data)でwrapできるようにしている。

includeは、別の関数を作成しているが、将来カスタムして組み込む?(SEO対策やロードバーツのJavaScript実行は?)


  /*  テンプレートエンジン  data[fun]->OK */
  const tpl$ = function(tpl,data) {
  	const me = tpl$;
  	if (!me.cache[tpl]) me.cache[tpl] = (function () {
  		let name = tpl, string = /^[\w\-]+$/.test(tpl) ? me.get(tpl): (name = 'tpl$(string)', tpl); // no warnings
  		let line = 1, $body = (
  			"try { " +
  				(me.variable ?  "let " + me.variable + " = this.stash;" : "with (this.stash) { ") +
  					"this.ret += '"  +
  					string.
  						replace(/<%/g, '\x11').replace(/%>/g, '\x13'). // if you want other tag, just edit this line
  						replace(/'(?![^\x11\x13]+?\x13)/g, '\\x27').
  						replace(/^\s*|\s*$/g, '').
  						replace(/\n|\r\n/g, function () { return "';\nthis.line = " + (++line) + "; this.ret += '\\n" }).
  						replace(/\x11=raw(.+?)\x13/g, "' + ($1) + '").
  						replace(/\x11=(.+?)\x13/g, "' + this.escapeHTML($1) + '").
  						replace(/\x11(.+?)\x13/g, "'; $1; this.ret += '") +
  				"'; " + (me.variable ? "" : "}") + "return this.ret;" +
  			"} catch (e) { throw 'templateError: ' + e + ' (on " + name + "' + ' line ' + this.line + ')'; } " +
  			"//# sourceURL=" + name + "\n" // source map
  		).replace(/this\.ret \+= '';/g, '');
  		let func = new Function($body);
  		let map  = { '&' : '&', '<' : '<', '>' : '>', '\x22' : '"', '\x27' : ''' };
  		let escapeHTML = function (string) { return (''+string).replace(/[&<>\'\"]/g, function (_) { return map[_] }) };
  		return function (stash) { return func.call(me.context = { escapeHTML: escapeHTML, line: 1, ret : '', stash: stash }) };
  	})();//END:if!me
  	let $out = data ? me.cache[tpl](data) : me.cache[tpl];
  	let dataattr;
  	if(data) dataattr = Object.keys(data).filter(d => d.indexOf('data-') === 0);
    if(dataattr && dataattr.length > 0) {
  		$out = htmlToNode($out);
      for(let i= 0; i< dataattr.length; i++){
        $out.setAttribute(dataattr[i],data[dataattr[i]]);
  		}
  	};
    if(data && data.fun && typeof data.fun == 'function') {
     	$out = htmlToNode($out);
      data.fun($out);
  	}
  	return $out;
  }
  tpl$.cache = {};
  tpl$.get = function (id) { return document.getElementBytpl(id).innerHTML };
  window.tpl$ = tpl$;
micro-template.jsを読み解いてみる

//replaceでやっていること
//制御文字 (control character)を使っている 制御文字
//  \x11(制御文字DC1),  \x13(制御文字DC3), ->  \x27(シングルコーテーション?) に変換!

replace(/<%/g, '\x11').replace(/%>/g, '\x13'). // if you want other tag, just edit this line
replace(/'(?![^\x11\x13]+?\x13)/g, '\\x27').
el$(属性オブジェクト,...inner):エレメント生成関数
属性オブジェクト -> {tag:'input', class:'hoge futa', style:'', 'data-hoge':'hogehoge', name="fuga", }
//data属性は、-(ハイフン)が含まれるので 'data-hoge'のようにkeyを書く
  fn$.insert('append',el$({tag:'input',type:'txt',name:'hoge',value:'fuga','data-target':'#hogge'}))(document.getElementById('newnode1'));

el$(op,inner):
//予約後的なop
tag:'input'などhtmlタグ
fun:function(){}//’生成ノードに関数を持たせる’
inner:内部要素をopに記述する場合
  • エレメント生成からテンプレート利用機能を外した ->
  • エレメント生成の第一引数の配列にinnerを追加した -> el$({class:'hoge',inner:'テキスト'})

/*  生成エレメントをnodeに挿入  */
let $newel = el$({class:'p-2 m-3 d-block',inner:'生成エレメント単体'});
fn$.insert('append',$newel)(document.getElementById('newnode0'));
生成エレメントを、nodeToHtmlで変換して、innerHTMLで挿入した場合
funで指定したevent関数は無効になる

  let $newel2 = el$({class:'p-2 m-3 d-inline-block text-white green rounded',inner:'生成エレメント単体',fun:clicklog});
  document.getElementById('newnode4').innerHTML = nodeToHTML($newel2);
list$(リスト配列,共通配列):Aパターン:エレメントのノードリスト生成

//共通オプション(オブジェクト)
let $n3op = {tag:'span', class:"d-inline-block m-1 p-2 text-white rounded red"};
//個別オプション(配列 -> オブジェクト)
let $n3ary = [
  [{"data-val":'1'},'その1','その1ダッシュ'],
  [{"data-val":'1'},'その2'],
  [{"data-val":'1'},'その3']
];
node$('#newnodelist1').insert('append',list$($n3ary,$n3op));
list$(リスト配列,共通配列,テンプレート):Bパターン:テンプレートでノードリスト生成

let $rdtpl = '<label class="m-2 text-white rounded p-1 <%= value%>"><input type="radio" name="<%= name%>" value="<%= value%>"><%= text%></label>';

let $radio = [
 {value: 'red', text:'あか'},
 {value: 'blue', text:'あお'},
];
let $rdcmn = {name: 'karaa'};

node$('#newnodelist2').insert('append',list$($radio,$rdcmn,$rdtpl));
Cパターン:list$wrap(個別List,共通List)(挿入先);
リストを生成しながら個別にwrapするーテンプレート利用
-> ラジオボタンを生成しながら、labelテンプレを個別にwrapする
/*  listをwrapして回す  */
//個別リスト配列  = [ {el:エレメント, tpl:テンプレ, data:テンプレデータ }, ... ]
let $colorlist = [
  { el:{value:"red-b"},data:{color:"danger-color-dark"},},
  { el:{value:"blk-b"}, data:{color:"black"},},
  { el:{value:"blu-b"}, data:{color:"primary-color-dark"},},
  { el:{value:"blu-l"}, data:{color:"blue lighten-4"},},
  { el:{value:"red-l"}, data:{color:"pink lighten-4"},},
  { el:{value:"grn-b"}, data:{color:"bg-line"},},
  { el:{value:"grn-l"}, data:{color:"light-green accent-1"},},
  { el:{value:"yll-b"}, data:{color:"orange accent-4"},},
  { el:{value:"yll-l"}, data:{color:"yellow accent-2"},},
  { el:{value:"blw-b"}, data:{color:"brown darken-4"},},
  { el:{value:"blw-l"}, data:{color:"brown lighten-3"},},
  { el:{value:"blk-l"},data:{color:"grey lighten-4"},},
];

let $clrclass = $colorlist.map((obj) => obj.el.value);

//el用関数
let $fun = function(e){ e.addEventListener('click',function(){console.log('cliclci')},false) }

//共通オブジェクト = {el:エレメント共通, tpl:テンプレ共通, data:テンプレデータ共通 }
let $listcmn = {
  el:{tag:'input',type:'radio',class:'lg-input', name:'box-color',fun:$fun,},
  tpl:'<label class="radio-lg"><%=raw self%><span class="lg-design"></span><div class="w53 <%=color%> o-"></div></label>',
};

let $color = document.getElementById('wrapcolor');

fn$.list$wrap($colorlist,$listcmn)($color);