https://svgjs.dev/docs/3.0/events/ を読んで。
文書を読むと次のように読める。null = {null}。
signature Element { sort self operation click: (self, ClickHandler) → self operation click: (self, null) → self }
同じような方式で、dblclick, mousedown, mouseup, mouseover, mouseout, mousemove, touchstart, touchmove, touchleave, touchend, touchcancel がサポートされている。
イベント名そのものがリスナー登録/リスナー削除関数の名前になっている。
- addEventListener("foo", handler) ≡ foo(handler)
- removeEventListener("foo") ≡ foo(null)
イベント名間違いは関数名間違いになるのでエラー検出が容易。登録・削除に2つの名前を必要としない。同じ方式でプロパティsetter/getterを単一名にすることが出来る。
- setFoo(val) ≡ foo(val)
- getFoo() ≡ foo()
TypeScript定義は:
class Element extends Dom implements Sugar { constructor(node?: SVGElement); constructor(attr: object); // ... click(cb: Function | null): this; dblclick(cb: Function | null): this; // ... }
Sugerインターフェイスを実装しているのはElementクラスだけ。Sugerは描画APIプロバイダのインターフェイス。Dom API に追加分が Suger に入っているようだ。差分が明確になるので、extends Base implement Addition はいい書き方だ。
interface Sugar { fill(): any fill(fill: FillData): this; fill(color: string): this; fill(pattern: Element): this; fill(image: Image): this; stroke(): any; stroke(stroke: StrokeData): this; stroke(color: string): this; //... }
イベント登録・削除方式
上記の element.click(handler) , element.click(null) 方式以外に element.on(名前, ハンドラー), element.off(名前) も使える。Eventobject の定義は循環的だが? Event, EventListener型はDOMのEvent, EventListenerをそのままらしい。
class Eventobject { [key: string]: Eventobject; } class EventTarget { events: Eventobject // 内部状態 // ... on(events: string | Event[], cb: EventListener, binbind?: any, options?: AddEventListenerOptions): this; off(events?: string | Event[], cb?: EventListener | number, options?: AddEventListenerOptions): this; // .. }
addEventLitener, removeEventListenerも名前だけあるが、使ってない。すべてのイベントリスナー(ほぼイベントハンドラー)を消すには element.off() とする。引数パターン(アリティとアリティごとの引数型リスト)ごとに意味が変わる、あの悪しき習慣。名前節約にはなる。
APIの雰囲気〈フレーバー〉の話
jQueryは名前短縮/名前節約により「憶えることが少ない」印象を持たせウケたのかも知れない。名前短縮/名前節約とその逆の比較検討は重要だな。SVG.js は jQuery的発想で設計実装されているから、検討素材には適切だ。
ハンドラー関数の操作で言えば、名前を区別する派だと:
- addFooHandler(cb : FooHandler) : Result
- getFooHandlesr() : Array
- removeFooHandler(id : HandlerID) : Result
メソッドチェーンを使いたいなら:
- setFooHandler(cb : FooHandler) : Self
- getFooHandles() : FooHandler? これはチェーンにしない。
- removeFooHandler() : Self
複数ハンドラーは使わない(単一上書きストア)かスタックに保存する。名前を減らすには:
- fooHandler(cb : FooHandler) : Self
- getFooHandles() : FooHandler? これはチェーンにしない。
- fooHandler(null) : Self
イベントのファイア
ファイア〈発火〉とは、ハンドラー関数がイベントデータを引数にもらって実行されること。
- element.fire(event) イベントデータを発火させる。
- element.fire(event, data) 付加的データを伴ってイベントデータを発火させる。
- element.dispatch(event) 発火した後のイベントを返す。
class Dom extends EventTarget { fire(event: Event | string, data?: object): this dispatch(event: Event | string, data?: object): Event // ... }
Domクラスに addEventLitener, removeEventListener, dispathcEvent があるが使う気はないらしい。Domクラスは名前短縮/名前節約されている。Dom に対する JDom(Java)のように、JSDom指向なのだろう。JSDomの典型実装がjQuery。
class EventTarget, class Dom, class Element, class Container, class Svg, interface Sugar あたりを https://m-hiyama-second.hatenablog.com/entry/2022/07/16/192103 で見ると、JSDom的発想が分かる。