Vue.js/ファイラーっぽいUIを作ってみる/070_クリックでフォルダを開閉する

Vue.js/ファイラーっぽいUIを作ってみる/070_クリックでフォルダを開閉する

データを書き換えてその結果が反映されて開閉できたので、今度はクリックをしてそのタイミングで対象を開閉させたい。 まず click に反応して何かする部分を作ってみる

Vue オブジェクトのコンスストラクタに処理を書く部分があるのでそれを追加する。

var vm = new Vue({
    el: "#app",
    data: data,
    methods: {
        toggleOpenClose: function(){
            console.log("test test");
        }
    }
});

試しに呼び出してみる。log が出力されている。

vm.toggleOpenClose();

methods に書いた関数はその名前で Vue オブジェクトそのものの直下に登録されることがわかる。

そしてこのメソッドをクリック時に呼び出してみる。 フォルダの名前のところをクリックさせるということで h2 のタグのところに仕込む

<h2 v-on:click="toggleOpenClose">{{ item.name }}</h2>

このようになる。v-on:click の属性名に対して methods の名前を値に指定する。 テンプレートではこの v-on:イベント名 でイベントにフックすることができるようになる。

ブラウザで読み込んで名前をクリックすると log が出ることが確認できる。

ではこの関数に引数を渡してみる。 関数が引数をうけつけるように書き換えて、

var vm = new Vue({
    el: "#app",
    data: data,
    methods: {
        toggleOpenClose: function(_id){
            console.log("test test " + _id);
        }
    }
});

呼び出し元も変える。普通に渡せばいいだけ

<h2 v-on:click="toggleOpenClose(item.id)">{{ item.name }}</h2>

実際動作させて click するとちゃんと値が渡っていることがわかる。

次にこれを改造して開閉をトグルするためのメソッドを実装する。 HTML動作として考えるならば li に close を付けたり取ったりすると閉じたり開いたりするわけだ。 Vue.js 的に考えるとデータの isOpen を書き換えた結果が反映されて閉じたり開いたりするわけだ。

しかし isOpen はデータ上にたくさんありどの isOpen を書き換えるかということがポイントになる。 そこで id からデータを特定するメソッドを実装する。

データは際限なく入れ子になっている構造で、その全部から検索するとなるので再帰的な関数を作ることになる。

methods: {
    toggleOpenClose: function(_id){
        console.log("test test" + _id);
    },
    findById: function(_id, _item){
        if(_item == null){
            return this.findById(_id, this.$data);
        }
        if(_item.id === _id){
            return _item;
        }
        if(_item.type === 'file'){
           return null;
        }
        for(var i = 0; i < _item.items.length; i++){
            var tmp = this.findById(_id, _item.items[i]);
            if(tmp != null){
                return tmp;
            }
        }
        return null;
    } 
}

試しに動かしてみる

var item = vm.findById(10);
console.log(item.name); // => iii
item = vm.findById(100);
console.log(item.name);  // 存在しないデータなのでエラーが出る

確かに動いているっぽい

ではこれを先程の toggleOpenClose に組み合わせる

methods: {
    toggleOpenClose: function(_id){
        var item = this.findById(_id);
        if(item == null){
            return;
        }
        if(item.type !== 'folder'){
            return;
        }
        item.isOpen = !item.isOpen;
    },

実は各 item そのものを引数に渡してそれを直に加工するとデータには反映される、

このように書く

methods: {
    toggleOpenClose: function(_id){
        var item = this.findById(_id);
        if(item == null){
            return;
        }
        if(item.type !== 'folder'){
            return;
        }
        item.isOpen = !item.isOpen;
    },

データを特定しそのデータを操作している。 それだけなのだがそれと関連付けられている HTML も同時に変化するというのが Vue.js の仕組みである。

next: Vue.js/ファイラーっぽいUIを作ってみる/080_Storeを使う

まとめ

これまでのまとめ

<!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">
        <ul>
            <li v-for="(item, i) in items" v-bind:class="[item.type, {close:(!item.isOpen && item.type === 'folder')}]">
                <template v-if="item.type === 'folder'">
                    <!-- ■■■■■■■■ 今回はココ ■■■■■■■■ --> 
                    <h2 v-on:click="toggleOpenClose(item.id)">{{ item.name }}</h2>
                    <ul>
                        <li v-for="(item, j) in item.items" v-bind:class="[item.type, {close:(!item.isOpen && item.type === 'folder')}]">
                            <template v-if="item.type === 'folder'">
                                <!-- ■■■■■■■■ 今回はココ ■■■■■■■■ --> 
                                <h2 v-on:click="toggleOpenClose(item.id)">{{ item.name }}</h2>
                                <ul>
                                    <li v-for="(item, k) in item.items" v-bind:class="[item.type, {close:(!item.isOpen && item.type === 'folder')}]">
                                        <template v-if="item.type === 'folder'">
                                            <!-- ■■■■■■■■ 今回はココ ■■■■■■■■ --> 
                                            <h2 v-on:click="toggleOpenClose(item.id)">{{ item.name }}</h2>
                                            <ul>
                                                <li v-for="(item, l) in item.items">{{ item.name }}</li>
                                            </ul>                                            
                                        </template>
                                        <template v-else>{{ item.name }}</template>
                                    </li>
                                </ul>
                            </template>
                            <template v-else>{{ item.name }}</template>
                        </li>
                    </ul>
                </template>
                <template v-else>{{ item.name }}</template>
            </li>
        </ul>
    </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" }
            ]
        };
        var vm = new Vue({
            el: "#app",
            data: data,
            // ■■■■■■■■ 今回はココ ■■■■■■■■
            methods: {
                toggleOpenClose: function(_id){
                    var item = this.findById(_id);
                    if(item == null){
                        return;
                    }
                    if(item.type !== 'folder'){
                        return;
                    }
                    item.isOpen = !item.isOpen;
                },
                findById: function(_id, _item){
                    if(_item == null){
                        return this.findById(_id, this.$data);
                    }
                    if(_item.id === _id){
                        return _item;
                    }
                    if(_item.type === 'file'){
                       return null;
                    }
                    for(var i = 0; i < _item.items.length; i++){
                        var tmp = this.findById(_id, _item.items[i]);
                        if(tmp != null){
                            return tmp;
                        }
                    }
                    return null;
                } 
            }
        });
        
        // ■■■■■■■■ 今回はココ ■■■■■■■■
        vm.toggleOpenClose(12);
        var item = vm.findById(10);
        console.log(item.name);
        item = vm.findById(100);
        console.log(item.name);
    </script>
</body>
</html>
javascript/vuejs/create_filer_like_ui/070_open_close_by_click.txt · 最終更新: 2018-11-22 20:20 by ore