WebDesign Dackel

オブジェクト内の文字列をテンプレート化するtemplate-obj.jsを作ってみた

オブジェクト内の文字列をテンプレート化するtemplate-obj.jsを作ってみた

Hatena0
Google+0
Pocket0
Feedly0

何を作ったの?

JavaScriptのオブジェクトをちょこっと使いやすくするtemplate-obj.jsというライブラリを作りました。

tsuyoshiwada/template-obj

どんなものか、なんだかタイトルだけだと分かりづらいので早速例を挙げてみます。


あるクラス名を元にしたクラス名を量産したい場合、人それぞれ書き方がありますがこんな感じで書けると思います。

var base = "module";
var classes = {
  base: base,
  list: base + "__list",
  listItem: base + "__list__item"
};

これでも問題ないのですが、"module"baseという変数に入れている箇所と、listItemの宣言部分が長ったらしい気がします。
それをtemplate-obj.jsを使うと次の様に書くことができます。

var classes = templateObj({
  base: "module",
  list: "${base}__list",
  listItem: "${list}__item"
});

少しだけですが簡潔に書くことができました!

使い方

インストール

npmBowerからのインストールに対応していますので、お好きな方からインストールできます。もちろんリポジトリから直接ファイルを持ってきてscriptタグで読み込んで使うこともできます。

npm

$ npm install template-obj

実際に使う際はrequireして読み込みます。

var templateObj = require("template-obj");

Bower

$ bower install template-obj

scriptタグ

template-obj.min.jsをダウンロードします。

ダウンロードしたら、いつも通りscriptタグで読み込んであげればOKです。

<script src="./template-obj.min.js"></script>

シンプルな例

引数に指定したオブジェクトの、ルートからの階層を${}で括ることで値にアクセスすることができます。

var result = templateObj({
  key1: "value1",
  key2: "key1 = ${key1}"
});

console.log(result);
/*
{
  key1: "value1",
  key2: "key1 = value1"
}
*/

ネストした値の場合

ネストした値へは、通常のオブジェクトを辿っていくように、ドット表記法・ブラケット表記法を使ってアクセスします。

var result = templateObj({
  level1: {
    level2: {
      level3: ["value!!"]
    }
  },
  value: "level1.level2.level3[0] = ${level1.level2.level3[0]}"
});

console.log(result);
/*
{
  level1: {
    level2: {
      level3: ["value!!"]
    }
  },
  value: "level1.level2.level3[0] = value!!"
}
*/

値が存在しない場合は何もしない

もし値が存在しない場合は、特になんの操作もしないようになっています。
下記の例では、${hoge.fuga}に該当する値が存在しないためテンプレートが展開されません。

var result = templateObj({
  range: {
    min: 10,
    max: 90
  },
  message: "${range.min} ~ ${range.max} is ${hoge.fuga}"
});

console.log(result);
/*
{
  range: {
    min: 10,
    max: 90
  },
  message: "10 ~ 90 is ${hoge.fuga}"
}
*/

template-obj.jsの中身

npmGitHubから持ってくるのでも良いのですが、それほど内容が多いわけでも無いのでコピペで使えるように中身を載せておきます。

(function(root){
  "use strict";

  var OBJECT = "object", STRING = "string", ARRAY = "array", FUNCTION = "function";
  var objectPrototype = Object.prototype;


  function hasProp(obj, key){
    return objectPrototype.hasOwnProperty.call(obj, key);
  }


  function is(type, obj){
    var clas = objectPrototype.toString.call(obj);

    if( type === ARRAY ){
      return clas === "[object Array]";

    }else if( type === OBJECT ){
      clas = typeof obj;
      return clas === FUNCTION || clas === OBJECT && !!obj && !is(ARRAY, obj);

    }else{
      clas = clas.slice(8, -1).toLowerCase();
      return obj !== undefined && obj != null && clas === type;
    }
  }


  function clone(obj){
    var _isArray = is(ARRAY, obj),
        _isObject = is(OBJECT, obj);
    if( !_isArray && !_isObject ) return undefined;
    var result = _isArray ? [] : {}, key, val;
    for( key in obj ){
      if( !hasProp(obj, key) ) continue;
      val = obj[key];
      if( is(ARRAY, val) || is(OBJECT, val) ) val = clone(val);
      result[key] = val;
    }
    return result;
  }


  function each(obj, iterate, context){
    if( obj === null ) return obj;
    context = context || obj;
    if( is(OBJECT, obj) ){
      for( var key in obj ){
        if( !hasProp(obj, key) ) continue;
        if( iterate.call(context, obj[key], key) === false ) break;
      }
    }else if( is(ARRAY, obj) ){
      var i, length = obj.length;
      for( i = 0; i < length; i++ ){
        if( iterate.call(context, obj[i], i) === false ) break;
      }
    }
    return obj;
  }


  function getValue(obj, key){
    if( hasProp(obj, key) ) return obj[key];

    // key[index] => key.index
    key = key.split("[").join(".").split("]").join("");

    var keys = key.split("."),
        results = clone(obj);

    each(keys, function(val){
      if( hasProp(results, val) ){
        results = results[val];
      }else{
        results = null;
        return false;
      }
    });

    return results;
  }


  function template(str, values){
    if( !is(STRING, str) ) return str;
    return str.replace(/\$\{(.*?)\}/g, function(all, key){
      var val = getValue(values, key);
      return val != null ? val : all;
    });
  }


  function templateObj(obj, rootObj){
    var results = clone(obj);

    rootObj = is(OBJECT, rootObj) ? rootObj : results;

    each(results, function(val, key){
      if( is(OBJECT, val) ){
        results[key] = templateObj(val, rootObj);
      }else{
        results[key] = template(val, rootObj);
      }
    });

    return results;
  }


  // export modules
  if( typeof module === OBJECT && typeof module.exports === OBJECT ){
    module.exports = templateObj;

  /*global define */
  }else if( typeof define === FUNCTION && define.amd ){
    define("template-obj", templateObj);

  }else{
    root.templateObj = templateObj;
  }

}(this));

eachcloneなどのヘルパーを除けば、

  • getValue
  • template
  • templateObj

の3つが主要な処理内容です。

まとめ

やや限定的な用途ではありますが、地味に便利かなぁ〜と思いますので、もし使ってみて改善点などありましたらフィードバック頂けたらとってもうれしいです!

tsuyoshiwada/template-obj