Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

@rryu

[小ネタ] knockout.jsのattrバインディングでreadonly属性を設定する

HTMLのreadonly属性はちょっと変わった属性で、名前と値の組ではなくて属性名だけで指定します1。この属性をattrバインディングを使って設定しようとすると意外と難しかったりします。どう難しいか順を追ってみていきましょう。

まず、attrバインディングを使ってreadonly属性を変更する場合は、以下のように記述することになります。

<input type="text" data-bind="attr: { readonly: readonlyAttr }">

readonlyAttrは属性値を返すobservableなプロパティです。が、これはいったい何を返せば良いのでしょうか。値など要らないわけですが、何かを返さないとattrバインディングが満足してくれません。

ここでHTMLの仕様を記憶からひねり出します。実は属性名のみの指定はreadonly="readonly"の略記です。ということはつまり、readonlyAttrは以下のようにすればいいはずです。

self.readonlyAttr = ko.computed(function() {
    return self.isReadonly() ? 'readonly' : '';
});

が、うまくいきません。isReadonly()が偽の時もreadonlyが有効になってしまいます。なぜかというとreadonly=""readonly="readonly"は同じ意味に解釈されるからです2。これはつまりreadonlyを無効にするためには属性名ごと削除しないといけないということです。

なんだか急に難易度が上がりました。attrバインディングのパラメータごと切り替えるみたいなことが果たしてできるのか、あるいは独自のバインディングを作ったほうが色々早いのではないか……

ここで独自バインディング実装の参考という名目でknockout.jsのソースコード探索に逃げてみましょう。するとattrバインディングになにやら意味深なコメントがあるのが見つかります。

knockout/src/binding/defaultBindings/attr.js
            // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
            // when someProp is a "no value"-like value (strictly null, false, or undefined)
            // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
            var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
            if (toRemove)
                element.removeAttribute(attrName);

なんと、属性値としてnullfalseundefinedを指定すると属性を削除してくれるようです。まさにこんな時のための機能があるではありませんか3

ということで、このように指定すれば目指した挙動になりました。めでたしめでたし。

self.readonlyAttr = ko.computed(function() {
    return self.isReadonly() ? 'readonly' : null;
});

ちなみにisReadonlyをそのまま使っても動作するようですが、その場合はtrueの時にreadonly="true"になってしまいます。属性値がtrueの場合の挙動はHTMLの仕様からは見つけられませんでしたが、概ねreadonly属性が有効だと解釈されるようです。


  1. 同じような属性としてcheckeddisabledなどがありますが、これらには対応するバインディングが用意されているので困ることはありません。 

  2. HTML5: 2.4.2 Boolean attributes 

  3. この仕様はドキュメントに記載されていません。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
3
Help us understand the problem. What are the problem?