JavaScriptでDjangoのテンプレート風なもの

JavaScriptで要素を動的に付け加えるときに,Djangoのテンプレートみたいに動いてくれるものを作ってみたら便利でした.

Before

今までは,appendとかを沢山つかって要素を生成してた.jQueryのtext関数によるescapeも重要だったし.でもそうすると完成したhtmlの要素の構造がよくわからないのでいちいちコメントでこのようにメモを書いていた.

function addComment(container, comment) {
    /*
        container : jQuery object 
        comment : { id,
                    owner : { id: ..., dname: (decoded) },
                    post_id : ...,
                    text : (decoded)... }
      <div class="comment_box">
        <div class="icon">
          <img src="..." />
        </a>
        <div class="comment_text">Hello, World</div>
        <span class="comment_owner">すずき</span>
        <span class="comment_id">12345</span>
        <span class="comment_delete_button">del</div>
      </div>
      */
    var c = $('<div class="comment_box"></div>');
    var icon_elem = $('<div class="icon"><img width="32" src="' + comment.owner.img + '" /></div>');
    var comment_text_elem = $('<div class="comment_text"></div>');
    var comment_id_elem = $('<span class="comment_id">' + comment.id + '</span>');
    var comment_owner_elem = $('<span class="comment_owner"><a href="' + comment.owner.link + '"></a></span>');
    comment_text_elem.text(comment.text);
   /* このへんを呼んでも最終的な構造がどのようなものか分かりにくいので上のようにコメントで構造をメモしていた */
    comment_owner_elem.text(comment.owner.dname);
    c.append(icon_elem);
    c.append(comment_text_elem);
    c.append(comment_owner_elem);
    c.append(comment_id_elem);
    container.append(c);
}

After

でも今はこんな感じ.JavaScriptはヒアドキュメントがないので複数行の文字列を用意する際には末尾にバックスラッシュが必要.

function addComment(container, comment) {
    /*
        container : jQuery object 
        comment : { id,
                    owner : { id: ..., dname: (decoded) },
                    post_id : ...,
                    text : (decoded)... }
    */
    var format = '\
<div class="comment_box">\
  <div class="icon">\
    <img src="{{owner.img}}" />\
  </div>\
  <div class="comment_text">{{text|escape}}</div>\
  <span class="comment_owner">\
    <a href="{{owner.link}}">{{owner.dname|escape}}</a>\
  </span>\
  <span class="comment_id">{{id}}</span>\
</div>'
    var c = $($S.sub(format, comment));
    container.append(c);
}

ミソである$Sはこんなかんじ.

function substitute2(format, obj) {
    /*
      {{key|option}}    <= target
      
      key may have dots; e.g. 'owner.dname'
      if the object is { owner : { dname : 'suzuki<' } },
      then {{owner.dname|escape}} becomes 'suzuki&lt;'
      */
    var pattern = new RegExp('{{([^\\|{|}]+)(\\|(\\w+))?}}', 'm')
    ret = format
    while ((m = ret.match(pattern)) != null) {
        var target = m[0];
        var keys = m[1].split('.');
        var option = m[3];
        var kobj = obj;
        for (var i=0; i<keys.length; ++i) {
            kobj = kobj[keys[i]];
        }
        var replacement = kobj;
        if (typeof replacement == 'undefined') {
            log('No such attribute for the object');
        }
        if (typeof option != 'undefined') {
            switch(option) {
            case "escape":
                replacement = $('<div/>').text(replacement).html()
                break;
            default:
                console.log("no such option");
            }
        }
        ret = ret.replace(target, replacement);
    }
    return ret;
}


$S = {
    "sub" : substitute2
}

escapeオプションにより<や>のような文字もエスケープされます.シンプルだけれどもかなり便利になりました.