<!DOCTYPE LNLY>

白ウサギを追え

Treee の進捗「ショートカットキー操作」編

Treee の開発をが少し進んだので、進捗報告です。

今日までの進捗

ノードの基本的な操作をショートカットキーで行えるようになりました。

f:id:lnly:20190220230301g:plain
ショートカットキーによる子孫ノードや兄弟ノードの追加

f:id:lnly:20190220230341g:plain
操作対象の選択と編集モードの切り替え操作

主な開発内容としては、

  • 操作対象ノードの選択状態管理
  • ショートカットキー支援ライブラリの選択
  • ショートカットキーの検討と実装

です。

操作対象ノードの選択状態管理

前回の進捗

編集状態かどうかの state を vuex にまで取り回すのが冗長に感じたので、コンポーネント内に data を持たせています。

としていた部分について、キーボード操作を検討するにあたって再設計しました。

  • 現在操作している対象のノードを特定する識別子
  • 操作状態「選択しているのみ / ラベル編集中 / 説明文編集中」の三値

を vuex の state に追加して、アプリケーションレベルで状態を把握できるようにしました。 結果的に、コンポーネント内に data として保持していた状態はなくなり、すべての状態を vuex から props 経由で受け渡すことにしました。

ショートカットキー支援ライブラリの選択

ショートカットキーの実装は Vue.js が標準で提供する機能で概ね実現可能ですが、利用件数が多かったライブラリ試したく、以下を組み合わせることにしました。

www.npmjs.com

特定のフォーム要素にフォーカスがしているときはショートカットキーの検出を無視する、
などといったことが、ディレクティブの指定のみでできるなどは便利でした。

Vue.js 標準機能でショートカットに関連が深いのは

jp.vuejs.org

この辺りです。
今日時点ではライブラリ有りでコミットしていますが、
結果的にはライブラリを導入しなくても、現状やりたいことはできるかもしれない、という感想でした。

ショートカットキーの検討と実装

実装したショートカット は、

  • 矢印キーの「上」「下」「左」「右」による選択ノードの変更
  • 選択状態から、ラベル編集状態へ移行
  • 選択状態から、説明文編集状態へ移行
  • いずれかの編集状態から、選択状態へ移行
  • 兄弟ノードの追加
  • 子孫ノードの追加
  • ノードの削除

ですが、

  • 矢印キーで枝を跨ぐ移動ができるべきか否か?
  • ノードの追加時に、ラベル編集状態へ移行するか否か?
  • ノードを削除した後、次に選択状態にするのはどのノードか?

など、実装よりも、どうするのが最もストレスなくエディットできるか?という仕様の検討が最も苦労し、
結果的に動くようにはなったもののまだ詰めきれていないという状態です。

日頃使っているエディタやアプリのショートカットがいかに練り込まれているか痛感します。 今後開発を進めながら、触り心地は磨いていきたいと思いました。

Mac の delete キーに関する余談

Mac を使っていると普段それほど意識しない(?)と思うのですが、
delete キーの入力を Javascript から検出しようとした時、 delete キー単体では「backspace」として入力され、
delete キーと Fn キーの複合キーで「delete」 になる、というのが今回得たちょっとした知見でした。

作業の進め方について

進捗とは直接関係ないですが、今回の作業は

frontend-temple.connpass.com

オンラインもくもく会に参加しながら行いました。 進捗を見せて反応がもらえて嬉しいし、時間を決めてガッと作れるのでまた参加したいと思いました。

今日の進捗は以上です。

Treee の進捗「ラベル・説明テキストの編集UI」編

Tree 2 再発明 改め「Treee」の開発進捗です。

今日までの進捗

ラベル説明テキストの編集UIを実装しました。

f:id:lnly:20190216020141g:plain
ラベルと説明テキストの編集

  • 表示状態と編集状態のボタンおよびインプット要素の実装
  • 自動的に高さを調整する textarea 要素のラッパーライブラリ導入
  • 子孫コンポーネントと vuex の分離

表示状態と編集状態の DOM 要素

f:id:lnly:20190216022235p:plain
編集中と通常時の DOM 要素切り替え
ラベル説明テキストは、表示状態は button 要素、編集状態は input / textarea 要素を切り替えるように実装しました。
編集状態かどうかの state を vuex にまで取り回すのが冗長に感じたので、コンポーネント内に data を持たせています。
これは、

  • コンポーネント内と vuex のそれぞれで「状態」を保持している
  • vuex に保持される「状態」が最終的なデータソースの形と一致している

と一長一短で判断に迷うところで、

  • 編集状態も vuex に state として保持する
  • button 要素との切り替えをやめて常時 input / textarea 要素にする

のどちらかに変更するかもしれません。CSS のスタイリングの自由度次第かなと思います。

textarea 要素のラッパーライブラリ導入

標準のtextarea 要素は、CSS で高さを変更可能ですが、中の要素に追従して大きさが変わったりはしません。
説明テキストを入力中に長文となった場合自動的にサイズを変更できるよう以下のライブラリを導入しました。

www.npmjs.com

子孫コンポーネントと vuex の分離

Treee のコンポーネントと状態管理の設計

Treee は最上位のコンポーネントだけが vuex と接続されていて、子孫コンポーネントへは props でデータを提供し、祖先コンポーネントへは this.$emit() でイベントや入力データを送る設計になっています。
これにより、子孫コンポーネントに下れば下るほど、そのコンポーネントが知りうる知識を限定することができ、結果的にコードをシンプルに保つことができると考えているからです。
どのコンポーネントが action を発行しているかなどを探索する必要もなくなり、コーディング時も楽できます。

反面、子孫・祖先へのバケツリレーがキツくなるといったデメリットもありますが、
これには Atomic Design に着想を得た Organisms > Molecules > Atoms までにコンポーネント粒度に制約を設けて、バケツリレーする階層に上限を作ることでその手間を一定までに抑止しています。

f:id:lnly:20190216015911p:plain
Vuex と コンポーネント群のデータフロー

木構造内の特定ノードのデータを更新する工夫

Treee は任意の階層の木構造を構築できるアプリです。 Vue.js 単体の場合は v-model により特定ノードとコンポーネントを紐づけることが容易にできました。

しかし、状態管理に vuex を使う場合 v-model の使用ではなく mutation によるデータ更新が推奨されていることと、前述したコンポーネントデータフローの兼ね合いから、以下の関数を mutation 内に設けて、特定ノードをシンプルに更新できるようにしました。

function findTargetNodeByNodeIndex (state: ITreeeState, nodeIndex: string): Node {
  return nodeIndex.split('.').reduce((accumulator: Node, currentValue: string) => {
    return accumulator.children.items[parseInt(currentValue, 10) - 1]
  }, state.treee)
}

第二引数の nodeIndex はノードの枝番を '.(ドット)' 繋ぎで表現した '1.1' や '1.1.2.1' などの文字列を取ります。
これを、

  [types.UPDATE_LABEL] (state, payload) {
    const targetNode = findTargetNodeByNodeIndex(state, payload.nodeIndex)
    targetNode.label = payload.value
  }

mutation ではこのように使ってデータを更新します。
先の関数 findTargetNodeByNodeIndex は Getter として一度実装しましたが、 Mutations から Getter を呼ぶことができなかったため、 mutations.ts 内に移しました。

今日はここまでです。