デザインパターンかなと思うものに名前を考えるポエムです。
ちょっと前に見て、いいな、と思ったデザインが、Cheetah-GridのVue.js用のアダプターのAPIです。Cheetah-Gridを直接使うには、DOMエレメントを渡しつつnewします。これ自体は、jQueryとかReactとかもろもろのフレームワークに依存しない、どこでも使えるテーブルコンポーネント類とかでよく見るAPIです。テーブルを作るのに必要な情報はすべてJSONで渡します。
grid = new cheetahGrid.ListGrid({
// Parent element on which to place the grid
parentElement: document.querySelector('#sample2'),
// Header definition
header: [
{field: 'check', caption: '', width: 50, columnType: 'check', action: 'check'},
{field: 'personid', caption: 'ID', width: 100, columnType: 'center'},
{field: 'fname', caption: 'First Name', width: 200},
{field: 'lname', caption: 'Last Name', width: 200},
{field: 'email', caption: 'Email', width: 250},
],
// Array data to be displayed on the grid
records,
// Column fixed position
frozenColCount: 2,
});
Vue用のアダプターはこんな感じ。ここでは、<c-grid-input-column>
などのタグを使って定義します。
<template>
<c-grid
:data="records"
:frozen-col-count="1">
<c-grid-column-group caption="Name">
<c-grid-input-column field="fname">First Name</c-grid-input-column>
<c-grid-input-column field="lname">Last Name</c-grid-input-column>
</c-grid-column-group>
<c-grid-button-column caption="SHOW REC" @click="onClickRecord">
</c-grid-button-column>
</c-grid>
</template>
ソースコードを見てもらえばわかるのですが、内部では、display: none;というスタイルを定義して非表示にして、タグに設定された情報からheaderに渡すJSONを作って、上記のJS APIを内部で読んでいます。
Cheetah Grid以外にも、ag-Gridも同じように、マークアップでカラム作成というAPIを提供しています(JSONでも定義できる)。
実際には表示されないのだけど、仮想DOMの文脈の中で、メタなデータを作り出すための擬似的なタグということで、僕は擬似仮想DOMと心の中で呼んでいますが、これって何かすでに名前とかあったりするんですかね?感覚的には、実際のCPU命令にはならないけど、セグメント位置とかを指定するアセンブリ言語の擬似命令(pseudo instructions)です。
メリットとしては、カラムの表示順であったり、幅といった見た目の情報をプログラムからビュー側に移動できるため、責務の分担としてはわかりやすくなるという点はあるかと思います。まあ、幅は本来はCSSなのかもしれませんが、テーブル系のコンポーネントだとドラッグでカラム幅を変更ということが発生したりするので、CSS側には持っていきにくいので、HTMLのテンプレート側でもまぁいいのかなと。デメリットは、実装がやや煩雑な点ですね。
実装方法検討
というわけで、いろいろな仮想DOMフレームワークでの実装方法も検討してみました。
Angular
Angularで実現するには、親となる(ここではGridコンポーネント)で @ContentChildren(ColumnComponent) columns: QueryList<ColumnComponent>
という定義を使って、DIしてあげれば、そのタグの子供のタグのインスタンスの一覧が取得できるので、似たような感じで実現ができます。JSON情報を返すメソッドをカラム側に定義してあげればそれで一撃ですね。テンプレートの中で<ng-content></ng-content>
を書けば、子供のタグで非表示とかしなくても、子供のタグが表示されなくなります。
ただし、ContentChildren
はそのままでは実装クラスでしかフィルタリングできません。カラムのクラスが何種類かあって、共通のカラムクラスから継承とかしているのであれば、次のStack Overflowのエントリーのように、子供のカラムの実装クラス側で、providersで親の抽象カラムクラスとの関連を記述してあげる必要があります。
React
Reactはコンポーネントの props.children
で子供の一覧が取得できます。こちらもAngularと同じく、JSX内で{props.children}
と書かなければ、子供は表示されないので非表示に設定してあげる必要はないかと。
子供の情報を取得してくるのは、React.Children
のヘルパーメソッドを呼べば簡単ですね。forEach
でループで回しつつ、特定のカラム親クラスのインスタンスを見つけて、カラム実装クラスに定義したJSONを返すメソッドを呼ぶとかですかね。リスト処理的にやりたければ、filterはないので、toArrayで配列にしてfilter、mapですかね。そんなに難しくはないかと。
Mithril
Mithirlも、children
で子供一覧が取れて、view()
メソッドのレスポンスにこれを入れなければchildrenが表示されることはないです。
children
に含まれるVNodeの結果のオブジェクトの構造はドキュメント化されているためいくつかの属性へのアクセスは可能です。クラス形式のコンポーネントの場合、tag属性がクラス、state属性がインスタンスです。この場合は、stateが特定のカラム親クラスのインスタンスになっているかどうかを検査して、カラム実装クラスに定義したJSONを返すメソッドか何かを呼んであげれば良いかと思います。
まとめ
実際問題、この手の技法が役に立つ場面というのはあんまりなくて、基本的には、内部にビジュアルな構造を持つような大き目の部品です。それ以外には表コンポーネントとか、グラフとかですかね。ReactのRechartsとか実際、こんな感じの定義をしてます。
それ以外には、HTMLのエレメントとは違う毛色の違うやつらを扱うとかはありな気がします。Reactだと最後のレンダラー部分を置き換えて3Dとかできますが、そこまで大げさな実装はしたくないけど、ちょっとタグで定義したい、みたいな時には良いかな、と。