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のジョン・グレグ氏作
重要: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に記述する場合
/* 生成エレメントを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);