LoginSignup
3
4

More than 5 years have passed since last update.

【knockout.js】チェックボックスをビットフィールドにバインド【bitField-binding】

Last updated at Posted at 2013-12-17

仕事にて、ビットフィールドをそのままチェックボックスにバインドしたい場面があったので作りました。
本家の checked バインディング を真似ています。

KnockoutJS bifField binding

やりたいこと

ビットフィールド(ビットマスクを使ってフラグを合成した数値) の各フラグの ON/OFF をチェックボックスに対応づける

 0  1  0  1 ←2進数) ビットフィールド (10進数→ 4)
[ ][+][ ][+] ←チェックボックス
 0  0  0  1 ビットマスク(flag1)
 0  0  1  0  ビットマスク(flag2)
 0  1  0  0  ビットマスク(flag3)
...

使い方

checked バインディング の基本的な使い方とほぼ同じです。

View
<input type="checkbox" data-bind="bitField: bitFieldValue, checkedFlag: flag1"/>
<input type="checkbox" data-bind="bitField: bitFieldValue, checkedFlag: flag2"/>
<input type="checkbox" data-bind="bitField: bitFieldValue, checkedFlag: flag3"/>
値: <span data-bind="text: bitFieldValue"> </span>
ViewModel
ko.applyBindings({
    bitFieldValue: ko.observable(0),
    flag1: 1 << 0,  // = 0b0001
    flag2: 1 << 1,  // = 0b0010
    flag3: 1 << 2   // = 0b0100
});

チェックボックス以外に one-way バインディングする方法

チェックボックス以外へバインドする場合、ViewModel → View への one-way バインディングのみ可能です。
特別な方法ではなく、ビット演算 property & flag をするだけです。

View
<!-- visible binding の例 -->
<span data-bind="visible: bitFieldValue() & flag1">
    flag1 が ON です
</span>

<!-- Twitter Bootstrap で checkbox の見た目をトグルボタンにする例 -->
<label class="btn" data-bind="css: { active: bitFieldValue() & flag1 }">
    <input type="checkbox" style="display: none;"
             data-bind="bitFIeld: bitFieldValue, checkedFlag: flag1"/>
    FLAG1
</label>

動作サンプル

KnockoutJS bitField binding : jsFiddle

Knockout ES5 プラグインを使ったバージョンはこちら
KnockoutJS bitField binding (ES5) : jsFiddle

曜日をビットフィールド化したものと、Linux のファイル権限をモデルにサンプルを作成しました。

カスタムバインディング

下記コードを ko.applyBindings 呼び出し前に挿入すれば使えます。
knockout.js 以外に依存しているライブラリはありません。

bitFIeld-binding
ko.bindingHandlers.bitField = {
    'after': ['value', 'attr'],
    'init': function (element, valueAccessor, allBindings) {
        var propWriters = allBindings()['_ko_property_writers'];

        function checkedFlag() {
            return allBindings['has']('checkedFlag')
                ? ko.unwrap(allBindings.get('checkedFlag'))
                : element.value;
        }

        function updateModel() {
            // This updates the model value from the view value
            // It runs in response to DOM events (click) and changes in checkedFlag.
            var isChecked = element.checked,
                elemValue = checkedFlag();

            // When we're first setting up this computed, don't change any model state.
            if (!shouldSet) {
                return;
            }

            var modelValue = valueAccessor();
            var unwrapped = ko.unwrap(modelValue);
            if (oldElemValue !== elemValue) {
                if (isChecked) {
                    if (oldElemValue !== undefined) {
                        unwrapped &= ~oldElemValue;
                    }
                    unwrapped |= elemValue;
                }
                oldElemValue = elemValue;
            } else {
                if (isChecked) {
                    unwrapped |= elemValue;
                } else {
                    unwrapped &= ~elemValue;
                }
            }
            if (ko.isObservable(modelValue)) {
                modelValue(unwrapped);
            } else if (propWriters['bitField']) {
                propWriters.bitField(unwrapped);
            } else {
                valueAccessor(unwrapped);
            }
        }

        function updateView() {
            // This updates the view value from the model value.
            // It runs in response to changes in the bound (checked) value.
            var modelValue = ko.unwrap(valueAccessor());

            element.checked = (modelValue & checkedFlag()) > 0;
        }

        if (element.type !== "checkbox") return;

        var oldElemValue = checkedFlag(),
            shouldSet = false;

        // Set up two computeds to update the binding:

        // The first responds to changes in the checkedFlag value and to element clicks
        ko.computed(updateModel, null, { disposeWhenNodeIsRemoved: element });
        ko.utils.registerEventHandler(element, "click", updateModel);

        // The second responds to changes in the model value (the one associated with the bitField binding)
        ko.computed(updateView, null, { disposeWhenNodeIdRemoved: element });

        shouldSet = true;
    }
};
ko.expressionRewriting._twoWayBindings['bitField'] = true;
3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4