menu
書いてる野郎
orebike@gmail.com
rename 部分が明らかにコンポーネント化できそうなのでやってみる。
var ItemName = Vue.extend({ template: ` <div> <template v-if="isEdit"> <input type="text" v-bind:value="name" v-on:change="setNewName($event.target.value)"/> <button type="button" v-on:click="updateName">OK</button> <button type="button" v-on:click="finishEdit">Cancel</button> </template> <template v-else> <span>{{ name }}</span> <button type="button" v-on:click="startEdit">rename</button> </template> </div>`, store: store, props: ['id', 'name'], data: function(){ return { newName: "" }; }, computed: { isEdit: function(){ return this.$store.state.editId === this.id } }, methods: { startEdit: function(){ this.$store.commit('updateEditId', this.id) }, finishEdit: function(){ this.$store.commit('updateEditId', null); }, updateName: function(){ this.$store.dispatch('updateName', { id: this.id, name: this.newName }); this.finishEdit(); }, setNewName: function(v){ this.newName = v; } } }); Vue.component("item-name", ItemName);
このようにする。 こいつをそれぞれで使ってみる。
var File = Vue.extend({ template: ` <li class="file"> <item-name v-bind:id="item.id" v-bind:name="item.name" /> </li>`, // 中略 });
使う側から props へデータバインディングしてやる。
動作させると問題なく動いた。
もう一つあるのが、フォルダの名前をクリックすると展開するというやつで。これは名前の要素から親要素へイベントを送ってやる必要がある。親要素はイベントを受け取る必要がある。
親側でカスタムイベントを定義してそれに対して method をフックしておく。子のコンポーネントへ設定してやる。
<item-name v-bind:id="item.id" v-bind:name="item.name" v-on:name-click="toggleOpenClose" />
name-click
というイベント発生を開閉メソッドにくっつけておく。
この命名規則は常にケバブケース推奨のようだ
子コンポーネント内部で name-click イベントを発生させる
var ItemName = Vue.extend({ template: ` <div> <template v-if="isEdit"> <input type="text" v-bind:value="name" v-on:change="setNewName($event.target.value)"/> <button type="button" v-on:click="updateName">OK</button> <button type="button" v-on:click="finishEdit">Cancel</button> </template> <template v-else> <span v-on:click="nameClick">{{ name }}</span> <button type="button" v-on:click="startEdit">rename</button> </template> </div>`, // 中略 methods: { //中略 nameClick: function(){ this.$emit("name-click"); } } });
emit というメソッドにより内部からイベントを発火できる。
これで親子連携ができるようになった。 File 側は受け取り側を設定していないので、発火しても何もおきない。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Hello</title> <script src="https://unpkg.com/vue@latest"></script> <script src="https://unpkg.com/vuex@latest"></script> <style> #app li.folder.close ul{ display: none; } </style> </head> <body> <div id="app"> <root-folder v-bind:items="rootItems"></root-folder> </div> <script> var data = { id: 1, name: "", type: "folder", isOpen: true, items: [ { id: 2, name: "aaa", type: "folder", isOpen : false, items: [ { id: 3, name: "bbb", type: "file" }, { id: 4, name: "ccc", type: "file" }, { id: 5, name: "ddd", type: "file" } ] }, { id: 6, name: "eee", type: "file" }, { id: 7, name: "fff", type: "folder", isOpen: false, items: [ { id: 8, name: "ggg", type: "file" }, { id: 9, name: "hhh", type: "folder", isOpen: false, items: [ { id: 10, name: "iii", type: "file" }, { id: 11, name: "jjj", type: "file" } ] } ] }, { id: 12, name: "kkk", type: "file" } ], editId: 5 }; var store = new Vuex.Store({ state: data, mutations: { toggleOpenClose: function(state, {getters, id}){ var item = getters.findById(id); if(item == null){ return; } if(item.type !== 'folder'){ return; } item.isOpen = !item.isOpen; }, updateEditId: function(state, id){ state.editId = id; }, updateName: function(state, {getters, id, name}){ var item = getters.findById(id); if(item == null){ return; } item.name = name; } }, getters: { findById: (state, getters) => (_id, _item) => { if(_item == null){ return getters.findById(_id, state); } if(_item.id === _id){ return _item; } if(_item.type === 'file'){ return null; } for(var i = 0; i < _item.items.length; i++){ var tmp = getters.findById(_id, _item.items[i]); if(tmp != null){ return tmp; } } return null; } }, actions: { toggleOpenClose: function(context, id){ context.commit('toggleOpenClose', {getters: context.getters, id: id}); }, updateName: function(context, {id, name}){ context.commit('updateName', {getters: context.getters, id: id, name: name}); } } }); var File = Vue.extend({ template: ` <li class="file"> <item-name v-bind:id="item.id" v-bind:name="item.name" /> </li>`, store: store, props: ['item'], data: function(){ return { newName: "" }; }, computed: { isEdit: function(){ return this.$store.state.editId === this.item.id } }, methods: { startEdit: function(){ this.$store.commit('updateEditId', this.item.id) }, finishEdit: function(){ this.$store.commit('updateEditId', null); }, updateName: function(){ this.$store.dispatch('updateName', { id: this.item.id, name: this.newName }); this.finishEdit(); }, setNewName: function(v){ console.log(v); this.newName = v; } } }); var RootFolder = File.extend({ template: ` <ul> <li v-for="(aitem, i) in items"> <component v-bind:is="aitem.type" v-bind:item="aitem"></component> </li> </ul>`, props: ['items'], computed: { isEdit: function(){ return false; } } }); var Folder = File.extend({ template: ` <li v-bind:class="['folder', {close: !item.isOpen}]"> <item-name v-bind:id="item.id" v-bind:name="item.name" v-on:name-click="toggleOpenClose" /> <ul> <li v-for="(aitem, i) in item.items"> <component v-bind:is="aitem.type" v-bind:item="aitem"></component> </li> </ul> </li>`, methods: { toggleOpenClose: function(){ this.$store.dispatch("toggleOpenClose", this.item.id); } } }); var ItemName = Vue.extend({ template: ` <div> <template v-if="isEdit"> <input type="text" v-bind:value="name" v-on:change="setNewName($event.target.value)"/> <button type="button" v-on:click="updateName">OK</button> <button type="button" v-on:click="finishEdit">Cancel</button> </template> <template v-else> <span v-on:click="nameClick">{{ name }}</span> <button type="button" v-on:click="startEdit">rename</button> </template> </div>`, store: store, props: ['id', 'name'], data: function(){ return { newName: "" }; }, computed: { isEdit: function(){ return this.$store.state.editId === this.id } }, methods: { startEdit: function(){ this.$store.commit('updateEditId', this.id) }, finishEdit: function(){ this.$store.commit('updateEditId', null); }, updateName: function(){ this.$store.dispatch('updateName', { id: this.id, name: this.newName }); this.finishEdit(); }, setNewName: function(v){ this.newName = v; }, nameClick: function(){ this.$emit("name-click"); } } }); Vue.component("file", File); Vue.component("root-folder", RootFolder); Vue.component("folder", Folder); Vue.component("item-name", ItemName); var vm = new Vue({ el: "#app", store: store, computed: { rootItems: function(){ return this.$store.state.items; } } }); </script> </body> </html>