Vue.js/ファイラーっぽいUIを作ってみる/130_さらにコンポーネント化

Vue.js/ファイラーっぽいUIを作ってみる/130_さらにコンポーネント化

rename 部分が明らかにコンポーネント化できそうなのでやってみる。

新しいコンポーネント ItemName を作る

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>
javascript/vuejs/create_filer_like_ui/130_component_more.txt · 最終更新: 2018-11-26 11:57 by ore