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

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

このような UI のツリー構造は開閉できることが多いので、今回これを開閉できるようにしてみる。

動きとして考えるなら開閉するとは、フォルダ無いのファイルやフォルダが見えなくなるということである。 そして Vue.js 的に考えるなら、閉じている開いているというデータに対する HTML への反映となる。

ということで開閉というデータを作ってやる。 まずは根本以外は全部閉じているという設定にしてみる。

こうなる

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" }
    ]
};

この設定をテンプレートにも反映できるようにする。 今回はこれにクラスを設置する。元々 li の要素に file か folder のクラスをつけていたので、 それに追加する

このように書く。

<li v-for="(item, i) in items" v-bind:class="[item.type, {close: !item.isOpen}]">

複数のクラスを設定したい場合配列のように [hoge, piyo] に囲む。

このように記述した場合、isOpen ではない、すなわち close ということで、isOpen が false の場合のみ close が付与されることになる

{close: !item.isOpen}

しかしこのように記述した場合 file にも close が付与されてしまうので、このようにする

<li v-for="(item, i) in items" v-bind:class="[item.type, {close:(!item.isOpen && item.type === 'folder')}]">

条件を複数まとめてやった。スマートな書き方ではないが、こういうことも出来るぞということで。

これを踏まえて全部書くとこうなる。

<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>{{ 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>{{ 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>{{ 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>

これで isOpen false で folder である li要素に close というクラスが付与されるようになった。

このクラスに合わせてスタイルを定義してやる

<style>
#app li.folder.close > ul{
    display: none;
}
</style>

名前は消したくない。消したいのは一覧なので直下の ul を指定する

これで close 指定したフォルダが閉じた。 data を書き換えてリロードしてみると。任意のフォルダが開閉できることが確認できるだろう。

まとめ

こうなる

<!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>{{ 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>{{ 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>{{ 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
        });
    </script>
</body>
</html>
javascript/vuejs/create_filer_like_ui/060_open_close.txt · 最終更新: 2018-11-22 18:46 by ore