Vue 人気ありますね。あまり真剣に触ったことがなく ToDo サンプルとかだとどうも分かった気になれない。ということで JointJS 使って CodePen で書いたトイ・ダイアグラムエディタを題材に構造を検討してみます。
JointJS は Backbone ベースなので Vue にうまく混ぜられるのかなあと思いましたが、あっさり表示できました。data に graph オブジェクトを保持して mounted で joint.dia.Graph オブジェクトを設定すれば、methods 内のメソッドから graph オブジェクト経由でダイアグラムにノードを追加することができるようになりました。
JointDia.vue 抜粋
<template> <div> <div ref="myholder"></div> <input type="text" placeholder="new node name" v-model="nodeName" /> <input type="button" v-on:click="addNode" value="add node" /> </div> </template> <script> export default { mounted() { this.graph = new joint.dia.Graph let paper = new joint.dia.Paper({ el: this.$refs.myholder, model: this.graph, } }, data() { return { graph: {} } }, methods : { addNode() { const rect = new joint.shapes.standard.Rectangle() rect.addTo(this.graph) } } </script>
一通り動きましたが、ダイアグラム部分とノード登録の Form 部分が分かれていないので両者のデータ、メソッドが混然一体となっています。Vue らしく別コンポーネントにしたいところです。
Form 部分を InputForm.Vue に切り出しました。追加するノード名を nodeName というデータで保持し、ボタンのクリック時 $emit
で親コンポーネント (App.vue) に通知するようにしました。
<template> <div> <div> <input type="text" placeholder="new node name" v-model.trim="nodeName" /> <input type="button" v-on:click="add" value="add node" /> </div> </div> </template> <script> export default { name: 'InputForm', data() { return { nodeName: '', } }, methods: { add() { this.$emit('addNode', this.nodeName) this.nodeName = '' } } } </script>
App.vue では InputForm と JointDia をコンポーネントとして利用しています。InputForm の v-on
属性で InputForm で emit したイベントハンドラーの関数 addNode を登録しています JointDia の v-bind:
属性で JointDia の nodeName プロパティと App.vue の data である nodeName をバインドしています。addNode 関数では、nodeName 受け取ったデータで書き換えます。これにより、JointDia コンポーネントの nodeName プロパティが書き換わります。
<template> <div id="app"> <InputForm v-on:addNode="addNode" /> <JointDia v-bind:nodeName="nodeName" /> </div> </template> <script> import JointDia from '@/components/JointDia' import InputForm from '@/components/InputForm' export default { name: 'App', components: { JointDia, InputForm }, data() { return { nodeName: '' } }, methods: { addNode(name) { this.nodeName = name } } } </script>
JointDia.vue の抜粋。プロパティ nodeName の変化を watch し変化時に Graph にノードを追加しています。
<template> <div> <div ref="myholder"></div> </div> </template> <script> import joint from 'jointjs' export default { name: 'JointDia', mounted(){ }, data() { }, props: { nodeName: String, }, watch: { nodeName: { handler (newVal, oldVal) { this.addNode(newVal) } } }, methods: { addNode(name) { } } } </script>
コンポーネントを分割すると、データ伝搬のコードが増えますが、コンポーネント内部は自身の責務に集中できてシンプルになる感じです。Vue らしいもっとすっきりした書き方があるかもしれませんが、ひとまずコードは CodeSandbox に置きました。
新規追加のノード名だけではなくダイアグラム内のノードのコレクションも管理する場合、JointJS のデータ構造とマッピングするためのコードを追加していく必要があります。