Riot.js+jQuery UIでのtrelloライクなGUIの作成
こんにちは、半袖と長袖の切り替えタイミングが良くわからない新入社員の平田です。 今回はRiot.jsをメインに、ドラッグ&ドロップのある簡単なGUIの作成について書いてみます。
Riot.jsについて
最近乱立しているJSフレームワークの一種です。Riot.jsをどのように使うかということについては、Qiitaの記事ですが、 Riot.js ことはじめが参考になります。
また、Riot.jsの公式ホームページにも使い方の詳細が載っています。ただ、ちょっと難しいので実際に作る段になってから参考にしています。
他のJSフレームワークを使ったことがないのですが、個人的には学習コストが低く、ライブラリのサイズも小さいことが大きなメリットかなと思っています。あとは「パット見でなんとなく把握できる」ということも大きいですね。小規模なアプリケーションならわざわざCakePHPやRailsのプロジェクトを作らなくてもRiot.jsでかなり自由に開発ができます。
カスタムタグの作成
Riot.jsでは予め「カスタムタグ」という形でDOMを作っておいて、 それを読み込む場所にそのままカスタムタグを書く流れになっています。実際に自分で作ったタグをその場所に生成することをマウントと呼びます。
呼び出し元では以下のように記述します。divは説明するまでもなくあのdivですが、unit_programなんて見慣れないタグがありますね。もちろんHTML5にはありません。これは自分で別ファイルに記述し、定義したものです。
<div class="horizontal blue lighten-4"> <div class="table card_sortable"> <div class="article" each="{ items, i in g_items }"> <unit_program items = {items} lists_info={g_lists_info[i]} id={'unit_program'+(i+1)}></unit_program> </div> </div> </div>
each属性はお察しの通りRiot.jsの機能で、g_itemsの配列から各要素、インデックスを順番に抜き出す事ができます。(ここでは、items, iです。)その中では、謎のタグ<unit_program>の変な属性"items"に値を渡していますね。
定義文は以下のようになります。unit_programの定義時にitems属性や、lists_info属性をまるで関数の様に引数として渡すことで、opts.itemsなどでそれらを参照することが出来ます。これらはclassなどの予約語でない限り自分で自由に設定することが出来ます。
<unit_program> <div class="card margin-1 unit_program"> <div each={opts.items} class="ui-state-default card-panel" id={entry_id}> <div class="row"> <div class="col s3 strong"> 申込ID: </div> <div class="col s9"> {entry_id} </div> </div> </div> </div> </unit_program>
新たな文法に抵抗があるかもしれませんが、波括弧{}の中はjavascriptです。ですので、配列は[]で参照できますし、'unit_program'+(i+1)で計算・連結も当然出来ます。
表示内容を交換したい、削除したい、追加したい場合、jqueryでreplace()やhtml()なんて使う必要はありません。ただ、g_itemsの配列を操作し、マウントすることで簡単に表示内容が変更されます。
もちろんCSSフレームワークやjQueryとの共存も出来ます。
(※実際、上記例ではMaterializeを用いていますし、以下ではjQueryUIを用います)
※マウント時の注意
ハマってしまって気づくのに時間がかかったのですが、カスタムタグは一つのオブジェクトです。このオブジェクト内でthis.update()を実行すれば表示内容が変更されるのですが、ネイティブなjavascriptの関数内で書いてしまうと実行されません.
<unit_program> /* DOM定義 */ <script> ugoku(){ this.update(); //実行される } function ugokanai(){ this.update(); //実行されない } </script> </unit_program>
上の例だと、ugokuの呼び出し元がunit_programオブジェクトであるのに対し、ugokanaiの方はwindowオブジェクト(状況によって変化)です。したがって、下のように書いてあげれば解決します。
var _this = this; ugoku(){ this.update(); //実行される _this.update(); //実行される } function ugokanai(){ _this.update(); //実行される }
参照渡しでthisをコピーしているんですね。右辺のthisはunit_programタグのオブジェクトです。
カスタムタグはオブジェクトだ!ということは、更にそこでマウントした子カスタムタグはオブジェクトらしく親を継承できます。詳しくはRiot.jsの公式ホームページ-ガイドのミックスインを参考にしてください。
参考:
jQuery UI のsortableを使う
下記はsotable初期化の例です。start関数内でplaceholderに実際に掴んでいるアイテムの高さ(加えてマージン分)を設定しています。このお陰で縦の大枠のガタガタ感がなくなり、なんかいい感じになります。
さらに、横スクロールしている状態で掴んだ瞬間カードが横に飛ぶ現象が発生します。スクロールしていないときのwindowの左上からポジションを計算していることが原因ですが、refreshPositionsをして再計算することによって解決できます。
stop時のsync_card()でg_itemsにカードの表示順番などを記録しています。 これによって、リアルタイムにカードの情報を更新することが簡単になります。
また、2つ初期化があるのはカード自体の移動も含んでいるからです。
function init_sortable(){ $( ".sortable" ).sortable({ helper: "clone", opacity: 0.7, placeholder: 'placeholder', connectWith: ".sortable", items: "> .ui-state-default", dropOnEmpty: true, start: function(e, ui){ ui.placeholder.height(ui.item.height()+22); $(this).sortable("refreshPositions"); }, stop: function(e, ui){ sync_card(); } }); $( ".card_sortable" ).sortable({ helper: "clone", opacity: 0.7, placeholder: 'placeholder', //revert: 100, stop: function(e, ui){ sync_card(); } }); }
下記はcssの例です。overflow,table-layout,displayで横に並べることを許可してあげると一気にtrelloっぽくなります。
div.horizontal { width: 100%; height: 100%; overflow: auto; } .table { display: table; table-layout: fixed; width: 100%; } .article { width: 400px; display: table-cell; }
JSフレームワークは山ほどありますが、Riot.jsは自分の居場所を見つけて乱世の中、是非生き残っていってほしいですね。