JavaScript
knockoutjs

Custom Component Loaders で外部のテンプレートファイルを読み込んでみる

More than 3 years have passed since last update.

どうやら、KnockoutJS アドベントカレンダー21日目はポエムですよー。

今回のサンプルファイルはこちらの Github にあります。


はじめに


やること

Web Components の Custom Elements と HTML Imports と Templates みたいなやつを

今回は KnockoutJS(神) と JQuery で実現させようとがんばるポエムです。


なぜ KnockoutJS ?

Google Polymer の webcomponents.js (旧 platform.js) を使うと Web Components が使えるのですが、

IE の場合 10 以上、Android の場合 Chromium ベース相当のブラウザでしか動作しないからです。


おおまかな流れ



  • オブジェクトの loadTemplate 内で Ajax からテンプレートを読み込む

  • ko.components.loaders で上記のオブジェクトを登録

  • ko.components.register で loaders を呼び出す


KnockoutJS の Components を使います。

@tan_go238 さんの 「Components を使ってみる」読んでみると良いかと思われます。

それと今回の内容は公式ドキュメントを読めば全てわかります。

英語が読める人は以下のページをどそー。

Advanced: Custom component loaders

あ、ちなみに自分はおばかさんなので

公式ドキュメントをちゃんと理解していません。


やってみよう


普通のコンポーネント

デモ


index.html

<div id="header">

<button data-bind="click: addCard">追加</button>
<input type="text" value="たいとるですよー" id="cardTitle">
</div>

<ul data-bind="foreach: cards">
<card params="data: $data"></card>
</ul>

<script>

ko.components.register('card', {
viewModel: function(params) {
var self = this;
self.title = params.data.title;
self.date = params.data.date;
},
template:"<li><p data-bind='text: title' class='card-title'></p><p data-bind='text: date' class='card-date'></p></li>"
});

var cardData = [
{ title:"春はあけぼの。", date:"2015/01/01" },
{ title:"やうやうしろくなりゆく山ぎは、", date:"2015/02/01" },
{ title:"すこしあかりて、", date:"2015/03/01" },
{ title:"紫だちたる雲のほそくたなびきたる。", date:"2015/04/01" }
]

function callTime(){
var date = new Date()
var str = "時刻 ";
str += date.getHours() + ":";
str += date.getMinutes() + ":";
str += date.getSeconds();
return str;
}

function AppViewModel() {
var self = this;
self.cards = ko.observableArray(cardData);

self.addCard = function() {
cardTitle = document.getElementById("cardTitle").value;
self.cards.unshift({ title: cardTitle, date: callTime() });
};
}

ko.applyBindings(new AppViewModel());

</script>



Custom Component Loaders を使ってみる

デモ(見た目、振る舞いは変わりません)


index.htmlの抜粋


<script src="template-loader.js"></script>

<script>

ko.components.register('card', {
viewModel: function(params) {
var self = this;
self.title = params.data.title;
self.date = params.data.date;
},
template: {fromUrl:"card.html"}
});

/* 〜 中略 〜 */

</script>


ko.components.register の template が HTML タグではなく、

{fromUrl:"card.html"} というオブジェクトになっていると思います。

 

card.html の中身は

ko.components.register の template へ直書きしていたものが外部ファイルになっています。


card.html

<li>

<p data-bind='text: title' class='card-title'></p>
<p data-bind='text: date' class='card-date'></p>
</li>

 

そんでもって、

途中で読み込んでいる template-loader.js が外部 HTML 呼び込む機能を付加しています。


template-loader.js

var templateFromUrlLoader = {

loadTemplate: function(name, templateConfig, callback) {
if (templateConfig.fromUrl) {
var fullUrl = templateConfig.fromUrl + '?cacheAge=' + templateConfig.maxCacheAge;
$.get(fullUrl, function(markupString) {
ko.components.defaultLoader.loadTemplate(name, markupString, callback);
});
} else {
callback(null);
}
}
};

ko.components.loaders.unshift(templateFromUrlLoader);



template-loader.js の内容

ドキュメントから丸コピーです。


内容


  • オブジェクトの loadTemplate をつくる。

  • templateConfig.fromUrl を調べて、なければスルー。

  • fullUrl にパスと付随情報を入れる。

  • $.get() をつかって fullUrl の URL からテンプレートの HTML を読み込む

  • 読み込み完了したら ko.components.defaultLoader.loadTemplate にテンプレートを登録。

  • ko.components.loaders.unshift で上記のオブジェクトを登録

loadTemplate: の引数


  • name はタグの名前(ここでは card)

  • templateConfig は template の中身(ここでは {fromUrl:"card.html"})

  • callback はコールバック(説明が雑)

ko.components.defaultLoader.loadTemplate の引数


  • name はタグの名前(ここでは card)

  • markupString は HTML の文字列

  • callback はコールバック(説明が雑)

(maxCacheAge が存在していますが今回は使っていません)


更に JavaScript のファイルを分けてみる

デモ(見た目、振る舞いは変わりません)

内容はほぼ変わらないのでソースは割愛。

register と ViewModel が別ファイルになりました。

register、ViewModel の JavaScript ファイルと

テンプレートの HTML ファイルを使いまわして楽しい Component ライフのはじまりです。


補足

params に $data で値を渡していますが、今回の場合は直接モデルの値を渡した方が動作速くなります。

また、見やすくするため li タグ以下をコンポーネントにしていますが、 一つ上の ul タグからした方が使い回しが楽になるかもしれません。


まとめ

KnockoutJS のドキュメントでは RequireJS を使い Custom Component Loaders を動かしている箇所が多いです。

ですが、小規模の場合、JQuery で手軽に作成してもよいのではないかと思いました。

 

 

 


おまけ


古い Internet Explorer でも Component を動くようにしてみる

デモ

そのままですと自作したタグを調べることができないので、

自作したタグより前に以下の JavaScript を書いてください。

<script>

document.createElement("タグの名前");
</script>

あと、JQuery を使っている場合はバージョンを 1.x 系に変更してください。

古い Internet Explorer で使えない JavaScript とかもはまりがちです。

Date.now() とか。


以前つくったタグに違うタグのコンポーネントを適応してみる

デモ

test というタグに test2 のコンポーネントを適応しています。


index.html

<test params="message:'test タグに test2 のコンポーネントを適応しますよー'"></test>

<script>

ko.components.getComponentNameForNode = function(node) {
var tagNameLower = node.tagName && node.tagName.toLowerCase();

if (ko.components.isRegistered(tagNameLower)) {
return tagNameLower;
} else if (tagNameLower === "test") {
return "test2";
} else {
return null;
}
}

ko.components.register('test2', {
viewModel: function(params){
var self = this;
self.message = params.message;
},
template: "<p data-bind='text:message' style='color:red'></p>"
});

ko.applyBindings();
</script>


test の ko.components.register がなく、

test2 のコンポーネントが動いているのがわかると思います。


内容

ko.components.getComponentNameForNode が処理をしています。

こやつの役目のひとつは自作タグを含めたタグの名前の列挙です。

関数の中身に console.log(node.tagName) を入れると列挙されるかと思われます。

その中で if 文からタグを調べてマッチしたら return で新しいタグの名前を返す。

この作業で既存のものに違うコンポーネントを適応することができます。

あ、tagName は大文字で返されるので toLowerCase() が必要ですぞー。


Knockoutjs.com のロゴのフォントって何?

Harlow Solid SB Regular

追記:どうやら ITC の方の Harlow Solid を使っている可能性が高い模様。

フリーのやつもあるみたいなのでググってみるとよいかもです。

なんとなくロゴの SVG ファイルをつくってみました。

SVGファイル