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 で入力をロールパックしてみる」です!
よろしくお願いします!