ko.utils(その他)その2

これはKnockoutJSアドベントカレンダー10日目の記事です。


KnockoutJS Advent Calendar 2014 - Qiita



今日もまた Knockout の便利機能が入った ko.utils について適当に紹介します。
まだまだあるよ!

今日紹介するのは以下のメソッド

  • ko.utils.domData.clear
  • ko.utils.domNodeDisposal.cleanNode / ko.utils.domNodeDisposal.removeNode
  • ko.utils.domNodeDisposal.addDisposeCallback
  • ko.utils.parseHtmlFragment
  • ko.utils.setHtml
  • ko.utils.compareArrays
  • ko.utils.setDomNodeChildrenFromArrayMapping


内部的に使われているのが殆どなので、実際に使う機会はほとんどないと思いますが気にしないでいきましょう。
※たぶん使うとしたら ko.utils.domNodeDisposal.addDisposeCallback ぐらい?



  • ko.utils.domData.clear

ko.utils.domData というオブジェクトが持っているメソッドです。
clearだけじゃなくて set/get/nextKey があります。

elementオブジェクトに対してkoで使う内部的なデータを保持しています。
ko.utils.domData.clear だけ独立して exportSymbol してるのはテストとかで内部的な値をクリアするときに使うからっぽいです。
内部的なやつなので殆ど使うことはないでしょう。

サンプル

<div id="sample"></div>
<script>
var output = null;
ko.utils.domData.set(document.getElementById("sample"), "foo", "bar");
output = ko.utils.domData.get(document.getElementById("sample"), "foo");
console.log(output);
ko.utils.domData.clear(document.getElementById("sample"));
output = ko.utils.domData.get(document.getElementById("sample"), "foo");
console.log(output);
</script>

結果

bar
undefined
  • ko.utils.domNodeDisposal.cleanNode / ko.utils.domNodeDisposal.removeNode

それぞれ ko.cleanNode と ko.removeNode です。

ko.cleanNodeは ko.applyBindings で ViewModelとDOM要素とバインディングしたデータをクリアできます。
例えば以下のような HTML を用意します。

<input id="sample1" type="text" data-bind="textInput: foo" />
<span id="sample2" data-bind="text: bar"></span>
<script>
function ViewModel(){
  var self = this;
  self.foo = ko.observable();
  self.bar = ko.pureComputed(function(){
    return self.foo();
  });
}
ko.applyBindings(new ViewModel());
</script>

ここで ID=sample1 の入力フィールドに値をいれるとID=sample2 の値も同時に変わります。
次に、(Chromeの場合)デベロッパーコンソールの Console を開いて以下を入力してみます。

ko.cleanNode(document.getElementById("sample2"));

するとどうでしょう。
さっきまで反応していた ID=sample2 が入力フィールドに何を入れても反応しなくなりました。

今度は Console に以下を入力してみましょう。

ko.removeNode(document.getElementById("sample1"));

今度は入力フィールドが削除されました。
Knockoutを使ってバインディングされた要素はこのように removeNode で削除してください。
これをしないで単純にDOM要素を削除してしまうとメモリリークの原因になるので要注意です。
※このメソッドは要素を削除する前に内部でko.cleanNodeを呼び出して要素のデータをクリアしています。



  • ko.utils.domNodeDisposal.addDisposeCallback

説明は公式ドキュメントにあります。
http://knockoutjs.com/documentation/custom-bindings-disposal.html

公式ドキュメントにもあるようにこのメソッドはカスタムバインディングハンドラでよく使用します。

pureComputedで使ったサンプルを使用してこれに独自のカスタムバインディングを作って使ってみましょう。

    <!--ko if: step() == 0-->
    <p>First name: <input data-bind="textInput: firstName" /></p>
    <!--/ko-->
    <!--ko if: step() == 1-->
    <p>Last name: <input data-bind="textInput: lastName, hoge" /></p>
    <!--/ko-->
    <!--ko if: step() == 2-->
    <div>Prefix: <select data-bind="value: prefix, options: ['Mr.', 'Ms.','Mrs.','Dr.']"></select></div>
    <h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
    <!--/ko-->
    <p><button type="button" data-bind="click: next">Next</button></p>
      ko.bindingHandlers.hoge = {
        init: function(element, valueAccessor) {
          alert("init");
          ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
            alert("dispose");
          });
        }
      };
      function AppData() {
        this.firstName = ko.observable('John');
        this.lastName = ko.observable('Burns');
        this.prefix = ko.observable('Dr.');
        this.fullName = ko.pureComputed(function () {
          var value = this.prefix() + " " + this.firstName() + " " + this.lastName();
          return value;
        }, this);
 
        this.step = ko.observable(0);
        this.next = function () {
          this.step(this.step() === 2 ? 0 : this.step()+1);
        };
      };
      ko.applyBindings(new AppData());

hoge というバインディングハンドラを作り2つめの画面のテキストフィールドに適用します。

サンプル
http://jsfiddle.net/tan_go238/86vxks2u/1/

最初の画面からNextボタンを押して、2つめの画面が呼び出されたときに hogeバインディングハンドラが初期化されます。
そのページからNextボタンを押して次のページに移動しようとしたときにバインディングハンドラが破棄されます。
上記のサンプルでは、初期化、破棄のそれぞれタイミングでアラートダイアログが表示されます。

  • ko.utils.parseHtmlFragment

文字列のHTMLコードからDOM要素を作成して返します。jQueryが使える場合は jQuery.parseHTMLを呼び出します。
knockoutは"jQueryが使えれば jQuery を使う、でなければ自前のやつを使う" みたいな実装のところがちらほらあるので、
KnockoutJSを使うときは jQuery も一緒に使うとより安心して使えると思います。


  • ko.utils.setHtml

ko.utils.setHtml(node, html)

第二引数で渡したHTMLコードをDOM要素にして第一引数に渡したノードにセットします。
jQueryがある場合は $.html(htmlString) を使用します。
ない場合は ko.utils.parseHtmlFragment を使いDOM要素を作成してappendChildしていきます。


  • ko.utils.compareArrays

配列同士を比較します。
結果は Objectの配列になります。
Objectのプロパティには status と value があり、valueは実際の値、statusは3つの状態("added", "retained", "deleted")のいずれかになります。

var foo = ["aaa", "bbb", "ccc"];
var bar = ["bbb", "ddd", "eee"];
var differences = ko.utils.compareArrays(foo, bar);
var addedValues = [];
var retainedValues = [];
var deletedValues = [];
ko.utils.arrayForEach(differences, function (difference) {
    if (difference.status === "added") {
        addedValues.push(difference.value);
    } else if (difference.status === "retained") {
        retainedValues.push(difference.value);
    } else if (difference.status === "deleted") {
        deletedValues.push(difference.value);
    }
});
console.log(addedValues);
console.log(retainedValues);
console.log(deletedValues);

結果

 ["ddd", "eee"]
 ["bbb"]
 ["aaa", "ccc"]
  • ko.utils.setDomNodeChildrenFromArrayMapping

option バインディングや foreach バインディングに使用しています。
子要素が追加されたり削除されたりしたときにコールバックを呼んだり削除時にko.removeNodeを呼んだりと色々なことをしてくれます。



3回に分けてざっと ko.utils のメソッドやオブジェクトを紹介してきましたが、
まだあると思うので探してみるといいかと思います。
3.1までのバージョンなら以下に一覧があります。
※一部ko内部でしか使えないものもあり


KnockoutJS 3.1.0 utils (ko.utils) signatures


明日は @sukobuto さんの「KO + TypeScript で大規模 SPA 開発」です!
よろしくお願いします!

ko.utils(その他)

これはKnockoutJSアドベントカレンダー8日目の記事です。


KnockoutJS Advent Calendar 2014 - Qiita



今日は Knockout の便利機能が入った ko.utils について適当に紹介します。

前回は配列だったのでそれ以外を紹介します。

ソースコードはここ。

https://github.com/knockout/knockout/blob/master/src/utils.js


紹介するのは以下のメソッドです。

  • ko.utils.extend
  • ko.utils.fieldsIncludedWithJsonPost
  • ko.utils.getFormFields
  • ko.utils.peekObservable
  • ko.utils.postJson
  • ko.utils.postJson
  • ko.utils.parseJson
  • ko.utils.stringifyJson
  • ko.utils.range
  • ko.utils.registerEventHandler
  • ko.utils.triggerEvent
  • ko.utils.unwrapObservable
  • ko.utils.objectForEach
  • ko.utils.toggleDomNodeCssClass
  • ko.utils.addOrRemoveItem


それでは順番にいきましょう。

  • ko.utils.extend

Objectを拡張します。

サンプル

var target = {a:1, b:2}
var option = {d:3, e:4, f:5, a:6}
var result = ko.utils.extend(target, option);
console.log(result);

結果

Object {a: 1, b: 2, d: 3, e: 4, f: 5}
  • ko.utils.fieldsIncludedWithJsonPost

これはメソッドではなく配列です。あとで出てくる ko.utils.postJson のデータを送信するときに自動的に含めるフィールド名が入ってます。

fieldsIncludedWithJsonPost: ['authenticity_token', /^__RequestVerificationToken(_.*)?$/],

軽くググった感じだとRailsCSRF対策で自動的に埋め込まれるトークン用、もう一つは ASP.NETでリクエストの検証トークン用っぽいです。

  • ko.utils.getFormFields

フォームから指定した文字列もしくは正規表現に一致するフィールドを配列で返します。

HTML

<form id="form1" action="/" method="post">
  <input type="text" name="f1" value="" />
  <input type="checkbox" name="f2" value="" />
</form>
var form = document.getElementById("form1");
var result1 = ko.utils.getFormFields(form, "f2");
console.log("result1 length: " + result1.length);
console.log("result1: " + result1[0].getAttribute("name"));
var result2 = ko.utils.getFormFields(form, /f[12]/);
console.log("result2 length: " + result2.length);
console.log("result2: " + result2[0].getAttribute("name"));
console.log("result2: " + result2[1].getAttribute("name"));

結果

result1 length: 1
result1: f2
result2 length: 2
result2: f2
result2: f1
  • ko.utils.peekObservable

ko.observableの peek() と同じです。

<div id="peekObservable">
<p>ko.utils.peekObservable</p>
<form id="form1" action="/" method="post">
  <input type="text" name="f1" value="" data-bind="value: input1"/>
  <input type="text" name="f2" value="" data-bind="value: input2"/>
  <p data-bind="text: result"></p>
  <input type="checkbox" name="f3" value="1" data-bind="checked: input3" id="chk"/><label for="chk">ここを押せば更新される</label>
</form>
<script>
function ViewModel() {
  var self = this;
  self.input1 = ko.observable();
  self.input2 = "Input 2";
  self.input3 = ko.observable(false);
  self.result = ko.pureComputed(function(){
    var i1 = ko.utils.peekObservable(self.input1); // self.input.peek() と同じ
    var i2 = ko.utils.peekObservable(self.input2); // observableじゃなくても使える(無視される)
    var checked = self.input3();
    return i1 + " - " + i2 + " " + checked;
  });
}
ko.applyBindings(new ViewModel(), document.getElementById('peekObservable'));
</script>
</div>
  • ko.utils.postJson

ko.utils.postJson(urlOrForm, data, options)

urlOrForm
 URLかフォーム要素をいれる
data
 送信するデータ。Modelでもいいけどprototypeで継承してきたプロパティも送信するってソースに書いてる
 送信する値は ko.utils.stringifyJson で JSON化される
options
 オブジェクトで指定する。配列でもいけるかもしれないけど試してない。
 指定できるオプションは次のとおり
  params
   送信するパラメータ。dataとほぼ一緒だけどこちらは JSON化しない
  includeFields
   送信する対象のフィールドを配列で指定する
   指定がない場合は自動的に以下のフィールドが指定される(CSRF対策のため)
    authenticity_token
    __RequestVerificationToken_ から始まるフィールド
  submitter
   これに内部で作成したフォームを引数として渡して実行する

<form id="sendForm" action="/">
<input type="text" name="includeField1" value="1" />
<input type="text" name="includeField2" value="2" />
<input type="text" name="excludeField1" value="3" />
<input type="text" name="excludeField2" value="4" />
<input type="text" name="otherField" value="5" />
</form>
<script>
var model = {a:{b:"bb",c:["cc1","cc2"]}, d:["ddd"]};
var mySubmitter = function(data){ console.log(data); };
ko.utils.postJson(document.getElementById('sendForm'), model, {params: {x:'xx', y:'yy'}, includeFields: [/includeField[0-9]/, 'otherField'], submitter: mySubmitter});
</script>

結果(内部で生成されてPOSTされるフォーム)

<form action="http://localhost:8080/" method="post" style="display: none;">
    <input type="hidden" name="a" value="{"b":"bb","c":["cc1","cc2"]}">
    <input type="hidden" name="d" value="["ddd"]">
    <input type="hidden" name="x" value="xx">
    <input type="hidden" name="y" value="yy">
    <input type="hidden" name="otherField" value="5">
    <input type="hidden" name="includeField1" value="1">
    <input type="hidden" name="includeField2" value="2">
</form>
  • ko.utils.parseJson

内部的に JSON.parse が呼ばれるのでほぼ同じものとして利用すればよい。
違いは引数をJSON.parseする前にトリムすることと、JSON.parseに対応していない古いブラウザにも対応していること
古いブラウザはあんまり安全でない(Fallback on less safe)と書いてある

  • ko.utils.stringifyJson

JSON.stringifyを内部で呼び出すのでほぼ同じものとして利用すればよい
違いは IE8より前だとエラーをメッセージ付きで投げることと、
JSON.stringifyに渡すJavaScriptオブジェクトを ko.utils.unwrapObservableしてから渡す

  • ko.utils.range

ko.utils.range(min, max)
最小(min)から最大(max)までの範囲の値を配列にして返します

var result = ko.utils.range(2, 5);
console.log(result);

結果

[2, 3, 4, 5]
  • ko.utils.registerEventHandler

要素にイベントハンドラを登録できる

<button id="btnClick">Click</button>
<script>
console.log("ko.utils.registerEventHandler");
ko.utils.registerEventHandler(document.getElementById('btnClick'), 'click', function(){
  alert('clicked!');
});
</script>
  • ko.utils.triggerEvent

イベントをディスパッチできる。

<button id="btnClick">Click</button>
<script>
console.log("ko.utils.registerEventHandler");
ko.utils.registerEventHandler(document.getElementById('btnClick'), 'click', function(){
  alert('clicked!');
});
ko.utils.triggerEvent(document.getElementById('btnClick'), 'click');
</script>
  • ko.utils.unwrapObservable

ko.unwrapはこれのショートカット。observableは値を取り出すときに unwrapしないと取り出せない。
とはいえ observable でないものを手動で unwrap する判断が間違え易いのでこのメソッドがあると思ってる。
例えば
self.a = ko.observable();
で宣言した変数の値を取り出すときは
self.a()
としてやらないといけないが、
self.b = 1;
で宣言した変数の値を取り出すときに
self.b();
とするとエラーになるため。

  • ko.utils.objectForEach

オブジェクトのプロパティをぐるぐるする

<script>
var obj = {a:1, b:2, c:3};
ko.utils.objectForEach(obj, function(item, index){
  console.log(index + ":" + item);
});
</script>

結果

1:a
2:b
3:c
  • ko.utils.toggleDomNodeCssClass

jQueryでいう removeClass や addClass ができる

jQuery

$(element).addClass(value);
$(element).removeClass(value)

KnockoutJS

ko.utils.toggleDomNodeCssClass(element, value, true);
ko.utils.toggleDomNodeCssClass(element, value, false);
<style>
.add {
  color: red;
}
.remove {
  text-decoration:line-through;
}
</style>
<span id="myText" class="remove">Hello</span>
<script>
ko.utils.toggleDomNodeCssClass(document.getElementById('myText'), 'add', true);
ko.utils.toggleDomNodeCssClass(document.getElementById('myText'), 'remove', false);
</script>
  • ko.utils.addOrRemoveItem

配列に特定の要素を追加もしくは削除する

ko.utils.toggleDomNodeCssClass の内部や
"checked" のバインディングハンドラでモデルが更新されたときにチェックボックスを更新するのに使われている

APIは以下のようになる
ko.utils.addOrRemoveItem(array, value, included)

サンプル

var arr = ["aaa","bbb"];
ko.utils.addOrRemoveItem(arr, "ccc", true);
console.log(arr);
ko.utils.addOrRemoveItem(arr, "bbb", false);
console.log(arr);

結果

["aaa", "bbb", "ccc"]
["aaa", "ccc"]


明日は @hkusu_ さんの 「ko.editables で入力をロールパックしてみる」です!
よろしくお願いします!

ko.utils(配列編)

これはKnockoutJSアドベントカレンダーの記事です。


KnockoutJS Advent Calendar 2014 - Qiita


今日は Knockout の便利機能が入った ko.utils について適当に紹介します。

今回は配列編。

紹介するのは以下のメソッドです。

  • ko.utils.arrayForEach
  • ko.utils.arrayFirst
  • ko.utils.arrayFilter
  • ko.utils.arrayGetDistinctValues
  • ko.utils.arrayMap
  • ko.utils.arrayPushAll
  • ko.utils.arrayRemoveItem

ちなみにソースコードはここです。

https://github.com/knockout/knockout/blob/master/src/utils.js


  • ko.utils.arrayForEach

配列をぐるぐるします。

サンプル

var years = [1950, 1960, 1970, 1980];
  ko.utils.arrayForEach(years, function(item, index){
    console.log(index + " : " + item);
});

結果

0 : 1950
1 : 1960
2 : 1970
3 : 1980
  • ko.utils.arrayFirst

配列の中で最初に条件に一致した値を返します。

サンプル

var strArray = ["a1", "b2", "c3", "d4"];
var result = ko.utils.arrayFirst(strArray, function(value){ return value.charAt(0) === "b"});
console.log(result);

結果

b2
  • ko.utils.arrayFilter

配列を指定した条件でフィルタした結果の配列を返します。

サンプル

var result = ko.utils.arrayFilter(["a", "b", "c", "d", "e", "f"], function (item, index) {
  return index % 2 == 0;
});
console.log(result);

結果

["a", "c", "e"]
  • ko.utils.arrayGetDistinctValues

配列の中の重複しない値を返します。

var result = ko.utils.arrayGetDistinctValues(["a", "b", "b", "c", "c"]);
console.log(result);

結果

["a", "b", "c"]
  • ko.utils.arrayIndexOf

最初に見つかった配列の値のインデックスを返します。
見つからなかった場合は -1 を返します。

var result1 = ko.utils.arrayIndexOf(["a", "b", "c", "d", "c"], "c");
console.log(result1);
var result2 = ko.utils.arrayIndexOf(["a", "b", "c"], "d");
console.log(result2);

結果

2
-1
  • ko.utils.arrayMap

配列の中の各要素に対して特定の処理を行った配列を返します。

var result = ko.utils.arrayMap([1, 2, 3, 4, 5], function(item, index){
  return item * 2;
});
console.log(result);

結果

[2, 4, 6, 8, 10]
  • ko.utils.arrayPushAll

配列に他の配列を追加します。

サンプル

var targetArray = [1,2,3];
var extraArray = ["a", "b", "c"];
ko.utils.arrayPushAll(targetArray, extraArray);
console.log(targetArray);

結果

[1, 2, 3, "a", "b", "c"]
  • ko.utils.arrayRemoveItem

最初に一致した値を配列から削除します。(一つだけ)

サンプル

var input = ["a", "b", "c", "b", "e"];
ko.utils.arrayRemoveItem(input, "b");
console.log(input);

結果

["a", "c", "b", "e"]


ここのブログと内容がかぶったけどCoffeeではなくJavaScriptで書いたのでご愛嬌。
http://weathercook.hatenadiary.jp/entry/2013/01/06/211720

ko.computedとko.pureComputed

これはKnockoutJSアドベントカレンダー5日目の記事です。


KnockoutJS Advent Calendar 2014 - Qiita


依存性の追跡(トラッキング)は ko.observable / ko.observableArray と ko.computed の組み合わせで行います。

例えば、生まれた年(西暦)から現在の年齢を算出するのをKnockoutを使って書くと次のようになります。

    <p>Enter your birth year: <input data-bind='value: birthYear' maxlength="4"/></p>
    <p>You're <strong data-bind='text: age'></strong> years old.</p>

    <script>
      function ViewModel() {
        var self = this;
        self.birthYear = ko.observable();
        self.age = ko.computed(function(){
          var now = new Date();
          var age = now.getFullYear() - parseInt(self.birthYear());
          if(isNaN(age)) return 0;
          return age;
        });
      }
      ko.applyBindings(new ViewModel());
    </script>
  • (値が変わらなくても)常に通知するようにしたり、一定時間経ってから通知したりする

上記のサンプルにあるように ko.computed の中で呼ばれる ko.observable / ko.observableArray の値が変わると ko.computed が呼ばれます。

逆にいうと値が変わらないと通知されないのですが、これを常に通知するように簡単に拡張できます。

myViewModel.fullName = ko.pureComputed(function() {
    return myViewModel.firstName() + " " + myViewModel.lastName();
}).extend({ notify: 'always' });


上記のように computed に .extend({ notify: 'always'}) をつけるだけです。

この書き方は extender と呼ばれ、 notify 以外にも rateLimit extender があります。

これは通知されるまでに設定された時間分待ってから通知します。

これは KnockoutJS 3.1.0 で追加されたのでそれ以前のバージョンでは throttle という名前でした(若干動きが違いますが・・・)

また、この extender は簡単に拡張できるのでドキュメントや実際のプロジェクトとか見てみると参考になるかと思います。

ドキュメント
Knockout : Using extenders to augment observables

実際のプロジェクト
knockout.persist/knockout.persist.js at master · spoike/knockout.persist · GitHub



もし computed 内で呼び出している observable A と observable B のうち、

observable A が変更したときだけ computed を実行して、observable B が変更されても computed が実行されない場合はどうしたらよいでしょうか。

これをするには computed 内の observable B に peek() を最後に追加するだけで実現できます。



  • ko.pureComputed とは何か

ko.pureComputed は Knockout 3.2.0 で追加されました。

公式サイトによるとこの機能を使うことにより以下の点で改善があるようです。

1. メモリーリークを防ぐ

2. 計算のオーバーヘッドを防ぐ

これはHTMLに現在バインディングされていない(アクティブでない) pureComputed は実行されないということです。

公式ドキュメントのサンプルがよくできているのでこれを使って説明します。

http://knockoutjs.com/documentation/computed-pure.html

<div class="log" data-bind="text: computedLog"></div>
<!--ko if: step() == 0-->
    <p>First name: <input data-bind="textInput: firstName" /></p>
<!--/ko-->
<!--ko if: step() == 1-->
    <p>Last name: <input data-bind="textInput: lastName" /></p>
<!--/ko-->
<!--ko if: step() == 2-->
    <div>Prefix: <select data-bind="value: prefix, options: ['Mr.', 'Ms.','Mrs.','Dr.']"></select></div>
    <h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
<!--/ko-->
<p><button type="button" data-bind="click: next">Next</button></p>
function AppData() {
    this.firstName = ko.observable('John');
    this.lastName = ko.observable('Burns');
    this.prefix = ko.observable('Dr.');
    this.computedLog = ko.observable('Log: ');
    this.fullName = ko.pureComputed(function () {
        var value = this.prefix() + " " + this.firstName() + " " + this.lastName();
        // Normally, you should avoid writing to observables within a pure computed
        // observable (avoiding side effects). But this example is meant to demonstrate
        // its internal workings, and writing a log is a good way to do so.
        this.computedLog(this.computedLog.peek() + value + '; ');
        return value;
    }, this);
 
    this.step = ko.observable(0);
    this.next = function () {
        this.step(this.step() === 2 ? 0 : this.step()+1);
    };
};
ko.applyBindings(new AppData());


このサンプルでは表示するHTMLをNextボタンを押す度に次のステップになり、ステップ0〜2 の 3回に分けて表示しています。

ステップ0では First name の入力、ステップ1では Last name の入力、ステップ2では Prefix の入力と fullName の表示を行っています。

ここで注目すべきところは、fullName が ko.pureComputed で定義されていることです。

fullNameの中では First name, Last name, Prefix を呼び出しているにも関わらず、fullName が実行されるのは

fullNameが実際にバインドされる「ステップ2」の段階になったときです。

今まで使っていた ko.computed だとどのステップにも関わらず実行されていたものを、ちゃんと「今つかっているか」を判断し、計算してくれます。

ko.pureComputed はComponentsを使った大規模なSPAでは、特に必要になってくるため地味な機能に見えて大変重要な機能ではないのかと思っています。

実際にサンプルも今まで ko.computed で書いてあったところを ko.pureComputed で書き直していたりしているので、

これからは ko.computed よりも ko.pureComputed を使った方が良い気がします。


逆に ko.computed を使う場面としては公式ドキュメントには以下のように書いてあります。

Knockout : Pure computed observables


1. 複数の observable に影響があるコールバックを ko.computed で使う場合

ko.computed(function () {
    var cleanData = ko.toJS(this);
    myDataClient.update(cleanData);
}, this);

2. バインディングの init メソッドで要素を更新するために ko.computed を使う場合

ko.computed({
    read: function () {
        element.title = ko.unwrap(valueAccessor());
    },
    disposeWhenNodeIsRemoved: element
});


用途として基本的には pureComputedを使い、初期値やサーバサイドで値が変わったら常に実行したい場合は ko.computed を使う感じなのかなと思います。



明日は @yusuke_nozoe さんです!よろしくお願いします!

Components を使ってみる

これはKnockoutJSアドベントカレンダー3日目の記事です。


KnockoutJS Advent Calendar 2014 - Qiita


KnockoutJS 3.2では新しく Components という機能が追加されました。

作者のブログにもあるようにこの機能は web components に強く影響された機能で、コンポーネント単位で非同期にページをレンダリングします。

Componentsを使うとどのように書くことができるのか見てみましょう。
まず普通のKnockoutで書いたサンプルです。

<html lang="ja">
<html>
  <head>
    <title> Component Examples</title>
    <script src="knockout-3.2.0.js"></script>
  </head>
  <body>
    
    <p>Enter your name: <input data-bind='value: name'/></p>
    <p>You entered <strong data-bind='text: name().toUpperCase()'></strong></p>
    
    <script>
      var viewModel = {
        name: ko.observable('something')
      };
      ko.applyBindings(viewModel);
    </script>
  </body>
</html>

デモはこちら

このデモを全く同じ動作のまま Components を使って書くと以下のようになります。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title> Component Example </title>
    <script src="js/knockout-3.2.0.js"></script>
  </head>
  <body>
  	<name-editor></name-editor>
    <script>
    	ko.components.register('name-editor', {
    		template: "<p>Enter your name: <input data-bind='value: name'/></p><p>You entered <strong data-bind='text: name().toUpperCase()'></strong></p>",
    		viewModel: function() {
    			this.name = ko.observable('something');
    		}
    	});
    	ko.applyBindings();
    </script>
  </body>
</html>

このデモはこちら

まったく同じ動作ですが、ko.components.register を使い "name-editor" という名前のComponentを登録しています。
これによりコンポーネントの再利用することが可能になります。


またコンポーネントは作成するときに以下のようにパラメータを渡してやることができます。
KnockoutJSのComponentsでは "component"バインディングも用意されており、他のバインディングと同じように data-bind でコンポーネントを指定することもできます。

デモはこちら

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title> Component Example </title>
    <script src="js/knockout-3.2.0.js"></script>
  </head>
  <body>
    <name-editor params="value: 'hello world'"></name-editor>
    <div data-bind="component: { name: 'name-editor', params: { value: 'hoge bar' } }"></div>

    <script>
    	ko.components.register('name-editor', {
    		template: "<p>Enter your name: <input data-bind='value: name'/></p><p>You entered <strong data-bind='text: name().toUpperCase()'></strong></p>",
    		viewModel: function(params) {
    			this.name = ko.observable(params.value);
    		}
    	});
    	ko.applyBindings();
    </script>
  </body>
</html>

いかがでしたでしょうか。
ざっと軽く見ただけでも便利なのが分かってもらえるかと思います。

さらに、KnockoutJSはこの機能もIE6のような古いブラウザーでも動くように実装しているらしいですが未検証です。

明日は @sukobuto さんの 「THETA BINDING」 です!よろしくお願いします!

JVM仕様にあるクラスファイルのフォーマットについて

これはJVMアドベントカレンダー1日目の記事です。


JVM Advent Calendar 2014 - Qiita


JVM仕様にあるクラスファイルのフォーマットについて書きます。

JavaのクラスファイルのフォーマットはJVM仕様の4章で定義されています。

Chapter 4. The class File Format

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}


全く分からない状態からこれらのフォーマットについてひとつづつ読んでいくのは辛いと思うのでざっくり説明します。

クラスファイルが必要なので適当なJavaソースコードを作ってコンパイルします。

class AdventCalendar2014 {
    public static void main(String[] args) {
        System.out.println("Hello JVM Advent Calendar 2014!");
    }
}
% javac AdventCalendar2014.java
% ls
AdventCalendar2014.class AdventCalendar2014.java


クラスファイルが出来たので今度はバイナリを覗いてみましょう。
以前そういうやつ書いたことがあるのでこれを使うことにします。


ClassReader

これをそのままコピってきてコンパイルしてさっきのクラスファイルを引数に指定すると、
2進数と16進数でクラスファイルの中身を出力します。

% java ClassReader /path/to/AdventCalendar2014.class
Binary  |Hex
------------
11001010 CA
11111110 FE
10111010 BA
10111110 BE
00000000 0
00000000 0
00000000 0
00110100 34
00000000 0
00011101 1D
00001010 A
00000000 0
00000110 6
00000000 0
00001111 F
00001001 9
00000000 0
00010000 10
00000000 0
00010001 11
00001000 8
00000000 0
00010010 12
00001010 A
00000000 0
00010011 13
00000000 0
00010100 14
00000111 7
00000000 0
00010101 15
00000111 7
00000000 0
00010110 16
00000001 1
00000000 0
00000110 6
00111100 3C
01101001 69
01101110 6E
01101001 69
01110100 74
00111110 3E
・・・

2進数は読みにくいので16進数だけにしたら以下のような感じになりました。

CAFEBABE00000034001D0A0006000F09001000110800120A001300140700150700160100063C696E
69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100046D6169
6E010016285B4C6A6176612F6C616E672F537472696E673B295601000A536F7572636546696C6501
0017416476656E7443616C656E646172323031342E6A6176610C000700080700170C001800190100
1F48656C6C6F204A564D20416476656E742043616C656E64617220323031342107001A0C001B001C
010012416476656E7443616C656E646172323031340100106A6176612F6C616E672F4F626A656374
0100106A6176612F6C616E672F53797374656D0100036F75740100154C6A6176612F696F2F507269
6E7453747265616D3B0100136A6176612F696F2F5072696E7453747265616D0100077072696E746C
6E010015284C6A6176612F6C616E672F537472696E673B2956002000050006000000000002000000
070008000100090000001D00010001000000052AB70001B100000001000A00000006000100000002
0009000B000C00010009000000250002000100000009B200021203B60004B100000001000A000000
0A000200000005000800060001000D00000002000E


これを最初のJVM仕様のクラスフォーマットと照らし合わせていきましょう。
magic っていうのはマジックナンバーJavaクラスの先頭はこの値が入ります。
これは Cafe(C++, A Front End)というC++コンパイラのプロダクトマネージャの Kim Polese さんの愛称(babe)からきているというのがまことしやかに噂として流れています。

CA FE BA BE // u4 magic;
00 00       // u2 minor_version;
00 34       // u2 major_version;
00 1D       // u2 constant_pool_count;

ってみていくと cp_info っていうよく分からないのが登場しました。
これもJVM仕様読んでいくと 4.4 で以下のように定義されています。

cp_info {
    u1 tag;
    u1 info[];
}

一番最初の tag でデータの種類を定義していて、その値によって次の info[] の読み方が変わります。
例えば tag = 7 の場合は CONSTANT_Class なので、その場合は CONSTANT_Class_info を読みます。
というような感じで進めていくと以下のようになります。

CA FE BA BE     // u4 magic
00 00           // u2 minor_version = 0
00 34           // u2 major_version = 57
00 1D           // u2 constant_pool_count = 29
// cp_info #1
0A              // tag = 10 (CONSTANT_Methodref)
00 06           // u2 class_index = 6
00 0F           // u2 name_and_type_index = 15
// cp_info #2
09              // tag = 9 (CONSTANT_Fieldref)
00 10           // u2 class_index = #16
00 11           // u2 name_and_type_index = #17
// cp_info #3
08              // tag = 8 (CONSTANT_String)
00 12           // u2 string_index = #18
// cp_info #4
0A              // tag = 10 (CONSTANT_Methodref)
00 13           // u2 class_index = #19
00 14           // u2 name_and_type_index = #20
// cp_info #5
07              // tag = 7 (CONSTANT_Class)
00 15           // u2 name_index = #21
// cp_info #6
07              // tag = 7 (CONSTANT_Class)
00 16           // u2 name_index = #18
// cp_info #7
01              // u1 tag = 1 (CONSTANT_Utf8)
00 06           // u2 length = 6
3C 69 6E 69 74  // u1 bytes[length] = <init>
3E
// cp_info #8
01              // u1 tag = 1 (CONSTANT_Utf8)
00 03           // u2 length = 3
28 29 56        // u1 bytes[length] = ()V
// cp_info #9
01              // u1 tag = 1 (CONSTANT_Utf8)
00 04           // u2 length = 4
43 6F 64 65     // u1 bytes[length] = Code
// cp_info #10
01              // u1 tag = 1 (CONSTANT_Utf8)
00 0F           // u2 length = 15
4C 69 6E 65 4E  // u1 bytes[length] = LineNumberTable
75 6D 62 65 72 
54 61 62 6C 65 
// cp_info #11
01              // u1 tag = 1 (CONSTANT_Utf8)
00 04           // u2 length = 4
6D 61 69 6E     // u1 bytes[length] = main
// cp_info #12
01              // u1 tag = 1 (CONSTANT_Utf8)
00 16           // u2 length = 22
28 5B 4C 6A 61  // u1 bytes[length] = ([Ljava/lang/String;)V
76 61 2F 6C 61
6E 67 2F 53 74
72 69 6E 67 3B
29 56 
// cp_info #13
01              // u1 tag = 1 (CONSTANT_Utf8)
00 0A           // u2 length = 10
53 6F 75 72 63  // u1 bytes[length] = SourceFile
65 46 69 6C 65 
// cp_info #14
01              // u1 tag = 1 (CONSTANT_Utf8)
00 17           // u2 length = 23
41 64 76 65 6E  // u1 bytes[length] = AdventCalendar2014.java
74 43 61 6C 65 
6E 64 61 72 32 
30 31 34 2E 6A 
61 76 61 
// cp_info #15
0C              // u1 tag = 12 (CONSTANT_NameAndType)
00 07           // u2 name_index = #7
00 08           // u2 descriptor_index = #8
// cp_info #16
07              // tag = 7 (CONSTANT_Class)
00 17           // u2 name_index = #23
0C              // u1 tag = 12 (CONSTANT_NameAndType)
00 18           // u2 name_index = #24
00 19           // u2 descriptor_index = #25
・・・


まぁあとは同じです。。
なんと驚くべきことに、ここまですべて手作業で解析してきましたが、さすがにもうしんどいです。

Javaチョットデキル人はご存知の人も多いですが、この解析は javap すれば簡単に出力してくれます。

% javap -c -verbose AdventCalendar2014
Classfile /path/to/AdventCalendar2014.class
  Last modified 2014/11/30; size 461 bytes
  MD5 checksum 94ccbcbc2ad1fdedf9c05cf088ca89f5
  Compiled from "AdventCalendar2014.java"
class AdventCalendar2014
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // Hello JVM Advent Calendar 2014!
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // AdventCalendar2014
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               AdventCalendar2014.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               Hello JVM Advent Calendar 2014!
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               AdventCalendar2014
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  AdventCalendar2014();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello JVM Advent Calendar 2014!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
}
SourceFile: "AdventCalendar2014.java"


こんな感じです。

明日は @sugarlife さんの「OpenJDKのソースコード配置(1-2サブディレクトリ程度まで)」です!
よろしくお願いします!

KnockoutJSの紹介

これはKnockoutJSアドベントカレンダー1日目の記事です。


KnockoutJS Advent Calendar 2014 - Qiita


一発目なので最初は KnockoutJS とはどんなものなのかについて書きたいと思います。

KnockoutJSとはMVVMパターンJavaScriptで作る動的なUIをシンプルに構築するライブラリです。
※個人的にはフレームワークではなくライブラリだと思っている


公式サイトには以下のような標語で書かれています。

Simplify dynamic JavaScript UIs with the Model-View-View Model (MVVM)

Knockout : Home


KnokoutJSを一言でいえば、「バインディングフレームワーク」の一種です。

以下の特徴があります。

・他のライブラリ(jQueryなど)に依存しない
IE 6+, Firefox 3.5+, Chrome, Opera, Safari など比較的古いブラウザに対応している


個人的にKnockoutJSが好きな箇所としては主に以下の2点です。

(1) 余計な機能があまりない(学習コストが高くない)
(2)(前述の特徴にもあるように)古いブラウザに対応している


まず、(1)ですが、KnockoutJSでは「data-bind=""」って空でタイプすることができれば7割ぐらいKnockoutをマスターしたものだと思ってもらっても過言ではないです。
3.1までは・・・。

それほどまでに余分な機能は削ぎ落されていてはいるものの、拡張性も高く様々なプラグインも用意されています。


Plugins · knockout/knockout Wiki · GitHub


プラグインは上記ページだけではなく、他にも


SteveSanderson/knockout.mapping · GitHub



SteveSanderson/knockout-projections · GitHub


SteveSanderson/knockout-es5 · GitHub

などといったKnockoutJS作者本人が作ったプラグインも数多くあるので見てみるのもいいと思います。
knockout-mappingについてはまた後日説明しようと思います。

(2) については KnockoutJSはVue.jsとは違い、レガシー・ブラウザもサポートしています。
その代わりVue.jsほどスタイリッシュではないし色々内部で泥臭いこともやっているのですが、現実解として古いブラウザーもサポートしないといけない場面は実際の仕事ではよくあることなのではないでしょうか?


というわけで明日は @hakurai さんです!たぶん Haxe について書いてくれると思います!