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>