二ヶ月目の話に入っていきます。JavaScriptの勉強を開始して丸一ヶ月でアウトライナーの自作に挑戦しました。
NTA-DIY:1ヶ月目⑩~初めてのノートテイキングアプリDIY~で書きましたが、一ヶ月が経つまでに多少独自性のあるメモアプリ的なものを自作するところまでたどり着きました。
発想を具現化できる取っ掛かりをちょっと掴んだことで、色々なアイデアが思い浮かぶようになりました。少し前まで「JavaScriptって何が嬉しいの?」と思っていたわけですが、「超嬉しいじゃん!」という心境です。
そんな中でふと、アイコンをクリックするとul要素が display:none; になるようにすればアウトラインの形は作れるな、と思い至ったことで、試しに作ってみようと考えました。最初から既存のアウトライナーを代替するものを作ろうと意気込んでいたとかではなく、思いついたから試さずにいられないという気持ちで取り組んでみたわけです。
とはいえ、ただアウトライナー機能を再現しただけでは面白くないので、既存のアウトライナーに対して抱えているもやもやを解決するアイデアを実践できたら尚良いと考えました。こうして軽快な足取りで無謀な挑戦に踏み出してしまいました。
私が普段アウトライナーを使う中でうまくいっていなかったことのひとつが、「引用をメモする」ということでした。例えば、太宰治が『正義と微笑』の中で書いた「真にカルチベートされた人間になれ」という一文をアウトライナーに書いておきたいとします。これが『正義と微笑』の読書メモの中なら話は難しくありません。「太宰治著『正義と微笑』」というような項目の下にペーストすればそれで終わりです。引用だとわかるように括弧で囲むか「> 」などを頭に入れるかすれば混乱は生じないでしょう。
しかし引用は必ずしも読書メモのひとつとして書くわけではありません。WebページやTwitterなんかから取り出すこともあります。そうなると、然るべき親項目というのが無いパターンが発生します。形式的に「引用」とかいう項目の下位に「Twitter」などと作ってその下に貼り付けるというようなこともあり得なくはないですが、無意味に階層が深くなりますし、デイリーの項目を使っているならその中に書きたいこともあります。他のトピックの中に存在していてほしいこともあります。なんにせよ引用部分を親項目に囚われずに自由に移動させられたらその方が良いです。
では、「真にカルチベートされた人間になれ」という項目の下に「太宰治」「『正義と微笑』」といった情報を入れてしまえばよいでしょうか。それはひとつの解決策で、それでいいと思えばそれでいいだろうと思います。ただ個人的には、それらの情報は「下位項目」なのかと考えた時に、ちょっと違和感を覚えてしまうところがあります。それらの情報が、他の「下位項目らしい下位項目」とフラットに並んでしまうことへの違和感です。これはごく個人的な感覚の問題に過ぎないことなので、その構造はおかしいとか言いたいわけではありません。単に私個人の納得いかなさを解消するために、私は自分で工夫しなければならないということです。
もうひとつの解決策として、ノート機能を使って書くということがあります。これはとても現実的な方法です。それでも良いには良いのですが、しかし個人的にはすっかり納得できるわけでもありません。ノートを表示していると情報量が無闇に増えて煩わしく、畳むと今度はノート部分にどういう種類の情報を入れたのかがわからなくなるという問題があるのです。
いっそのこと、全部項目に突っ込んで「「真にカルチベートされた人間になれ」(太宰治『正義と微笑』)」などとする手もあります。出典部分が短ければそれほど違和感はありません。ただ、どこかのWebページの一節みたいなことになると同梱はちょっと苦しい感じがします。普通にあり得るやり方ですが、ベストではないように感じてしまいます。
要するに、ノードにメタデータを自由に設定できて、且つ、それらのメタデータの存在がひと目でわかるようにしたかったのです。
それができるアウトライナーはひとつもないのでしょうか。いえ、少し前にメタデータの設定が可能なアウトライナーが誕生していました。Dave Winer氏のDrummerです(Drummer)。おそらくですが、メタデータを利用したスクリプトを自分で書けばメタデータの内容をアウトライン上に可視化することも可能だろうと思います。多分スクリプトによって可能になることは無数にあり、Drummerには果てしのない可能性を感じます。
よって、自分の要求を満たすにあたり、Drummerをバリバリ使い倒すという選択肢もありえます。しかし当時はごく簡単なコードしか書けませんでしたし、Drummer用のメソッドの仕様を見ても何がなんだかわからなくて何もできませんでした。Drummerのデザインや操作感の癖に馴染むのが容易でないということもあり、とりあえずDrummerそのものを自在に活用するという道は諦めました。
じゃあ、自分で作るしかないよね、ということになるわけです。
ついでに、一般的なアウトライナーへの不満として「1カラムしかない」ということがあったので、その解決も試みます。画面の高さより離れた位置にあるノードを参照するにはいちいちスクロールしなければならない煩わしさをどうにかしたかったのです。なお、今なら例えばRemNoteは複数ファイルの同時編集が可能です。
当時作ったもののスクリーンショットを貼っておきます。
実際にアウトライナーもどきを作ってみると、まあ一筋縄ではいきませんでした。
アウトライナーっぽい動き(開閉やインデント)の再現だけなら、クリックイベントとキーボードイベントさえわかれば可能なので、それほど大変ではありません。「おお、『っぽい』ぞ」という感動は割と早く得ることができました。
しかし、「編集したデータの管理」と「ドラッグアンドドロップ処理」を実装するのにはかなり頭を悩ませました。アウトライン操作というのは実装すると色々なイベントを実行することになるわけですが、どのタイミングでどうやってデータを更新するのかをきちんと考えなくてはなりません。
おそらく最もシンプルなのは、DOMツリー自体をデータと見なして、それを丸ごとデータとして保存することです。アウトライン部分が入ったul要素のinnerHTMLをlocalStorageに突っ込むということです。確か最初はそうしていました。リロード時にはそのままul要素のinnerHTMLに代入し、中身のli要素などに各種イベントリスナーを設定する処理をしました。必ずしも処理が単純になるわけではありませんが、データの形式を考えずに済むという利点はあります。階層構造をどう管理したらいいのか当時はよくわかっていなかったのです。
データの管理を複雑にしたのは、「メタデータがある」ということと「2カラムである」ということです。データの形式としてHTMLを使うとなると、メタデータの管理のためにはHTMLのカスタム属性を駆使する必要があります。そして2カラムあると片方のカラムの更新をもう一方に反映させなければなりません。一言で言うととにかく無謀だったのですが、しかしそれがモチベーションになっているわけなので、なんとか頑張って実現するしかありません。
そこに更にドラッグアンドドロップが加わります。ドラッグアンドドロップ処理というのはなんとも複雑です。ドロップ時の処理が判別できるようにドラッグ中にスタイルを変更するようにしたのですが、そうなると「dragstart」「dragover」「dragleave」「drop」の四つのイベントをセットすることになりました。
そして階層構造になっているもののドラッグアンドドロップなので、親要素を子要素の中にドロップするということを禁じる必要もあります。あっちもこっちも初めて知った概念の連続で、数限りないエラーと格闘する羽目になってしまいました。
このブログは体験談として書いているので具体的なコードの説明はここではしませんが(別の機会にする可能性はあります)、とにかく沢山の試行をして無数のエラーを出しました。どれだけ失敗してもリスクはゼロなので気楽なものです。コードは最終的に1100行くらいになりました。
一応形にはなりましたが、とても「出来の良いツール」とは言えず、処理の信用ならなさが自分でわかっているのでしっかり実用したわけでもありません。しかし「こういうものが作りたい」と思いつきさえすればある程度は脳内を再現できるという手応えを感じました。当時このツールを作ったことについて「気分としてはメタルキングを倒した感じ」と表現しましたが、今振り返ってもそうだなと思います。
当時作ったものを全面リファクタリングしたサンプルがこちらです(例によってPC用です)。なお上記のスクリーンショットは当時のもので、今回のサンプルとはあちこち違っています。
以下は「2ヶ月目」の話ではない余談です。
コードの説明はしないと先述しましたが、サンプルはなるべくまともなコードに改めた上で公開すると決めているので、今回も頑張って書き直しました。
これがおそろしく大変でした。「エラーの恐怖がつきまとうが一応動く」という状態から「エラーの不安なく動き、メンテナンスとカスタマイズが比較的容易である」という状態に自分なりに書き換えたわけですが、データの管理やDOM生成はどうするのが良いのか一から考え直すことになり、設計するのに想像以上に時間がかかってしまいました。
結局、サンプル版では当初搭載した機能の半分くらいしか実装していません。いずれもこうすれば実装できるという見通しはついていますが、このツールを今後自分が使っていく予定は今のところないので、費やすに値する時間と労力の兼ね合いから再現は途中までにしました。当初作ろうとしたものは、内部処理をまともな形にするとなるとかなりの手間を生じるほど複雑だったということです。
しかし、これをリファクタリングしようとしたことで、それまで知らなかったメソッドや思いつかなかったアイデアを様々獲得しました。この連載を書くことで読み手に何かプラスがあるかわかりませんが、私自身の得るものが非常に大きいので、この先も勝手に頑張っていこうと思います。(1ヶ月目を終えてからかなり間が空きましたが、単純にコーディングに苦労していただけでモチベーションの低下があったわけではないのでした)