zTree でファイルツリーを表示する

下記の記事でjstreeでファイルツリーを表示させていたのですが、

jstree でファイルツリーを表示する
https://qiita.com/hiro_nico/items/5c6ab238ed71036a6f46

かなり大きいファイルツリーになると
ブラウザのメモリを食ってしまいサクッと表示されなかったので
別のものを探してみました。

そして見つけたのが、下記のもの。

zTree
http://www.treejs.cn/v3/

触ってみたところ jstree と同じ感じで使えました。

ただ zTree だと面倒なところは、
データの構造を id や pId (parent) が数字でないといけないところですかね・・・

jstreeな記事をベースにご紹介していきます。

## 入手する

まずは zTree をダウンロードする。

* https://github.com/zTree/zTree_v3
    * Code
        * Download ZIP

## 実装

ファイル構造が以下のようにファイルを入れる。

```text:ファイル構造
│  index.cgi
│
├─public
│  ├─css
│  │  └─ztree
│  │      │
│  │      └─zTreeStyle
│  │          │  zTreeStyle.css
│  │          │
│  │          └─img
│  │              │  line_conn.gif
│  │              │  loading.gif
│  │              │  zTreeStandard.gif
│  │              │  zTreeStandard.png
│  │              │
│  │              └─diy
│  │                      1_close.png
│  │                      1_open.png
│  │                      2.png
│  │                      3.png
│  │                      4.png
│  │                      5.png
│  │                      6.png
│  │                      7.png
│  │                      8.png
│  │                      9.png
│  │
│  └─js
│          jquery-3.5.1.min.js
│          jquery.ztree.all.min.js
│          read_tree.js
│
└─testdata
    │  a.txt
    │  b.txt
    │
    └─hoge
            c.txt
```

* jQuery
    * https://jquery.com/download/
        * jQuery
            * `Download the compressed, production jQuery *.*.*`

* public/js/read_tree.js
    * ページに埋め込むスクリプト

* testdata
    * ファイルツリー読み込み対象

### データ構造

先ほど言ったように jstree と異なるところとして,
`id` と `pId` が数字でないといけないのでその辺を変更してます。
iconの指定は、必要ないので除いています。

```javascript
[
    {"id":1,"name":"testdata","pId":0},
    {"id":2,"name":"a.txt","pId":1},
    {"id":3,"name":"b.txt","pId":1},
    {"id":4,"name":"hoge","pId":1},
    {"id":5,"name":"c.txt","pId":4}
]
```

### サーバー

サーバー側を作っていきます。

```perl:index.cgi
#!perl
use strict;
use warnings;
use utf8;
use Mojolicious::Lite;
use lib qw(lib);
use File::Find::Rule;
use Digest::SHA qw/sha256_hex/;
use Encode;
use Path::Tiny;

get '/' => sub {
    my $self = shift;
    $self->render;
} => 'index';


get '/get_files_data' => sub {
    my $self = shift;
    $self->render(json => get_files_data());
};

my $used_id = 1;
sub get_files_data {
    my @files = File::Find::Rule->file()->in("testdata");

    my $dirs = {};

    my @data = ();
    foreach my $file (map {path($_)} @files) {
        my $dir = $file->parent->stringify;
        if (not exists $dirs->{$dir}) {
            &add_dir_hash($dirs, $dir, \@data);
        }

        my $filepath = encode("cp932", $file->stringify);
        my $filename = $file->basename;
        my $id = $used_id;
        $used_id++;
        if ($dir eq "" or $dir eq ".") {
            push @data, {id => $id, pId => 0, name => $filename};
        } else {
            push @data, {id => $id, pId => $dirs->{$dir}, name => $filename};
        }
    }

    return \@data;
}

sub add_dir_hash {
    my $dirs = shift;
    my $dir = shift;
    my $data = shift;

    return if $dir eq "" or $dir eq ".";

    if (exists $dirs->{$dir}) {
        return;
    }
    $dirs->{$dir} = $used_id;
    $used_id++;

    my $parent = path($dir)->parent->stringify;
    if ($parent eq ".") {
        push @$data, {id => $dirs->{$dir}, pId => 0, name => $dir};
        return;
    }

    if (not exists $dirs->{$parent}) {
        &add_dir_hash($dirs, $parent, $data);
    }
    push @$data, {id => $dirs->{$dir}, pId => $dirs->{$parent}, name => path($dir)->basename};
}

push @{app->static->paths}, 'public';

app->start;

__DATA__

@@index.html.ep
% layout 'default';
ファイルツリー
<ul id="tree" class="ztree"></div>

@@ layouts/default.html.ep
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <%= stylesheet '/css/ztree/zTreeStyle/zTreeStyle.css' %>
    <%= javascript '/js/jquery-3.5.1.min.js' %>
    <%= javascript '/js/jquery.ztree.all.min.js' %>
    <%= javascript '/js/read_tree.js' %>
    <title>ページタイトル</title>
</head>
<body>
<%== content %>
</body>
</html>
```

ページテンプレート部 zTree 関連で使うところは、下記です。

```perl
    <%= stylesheet '/css/ztree/zTreeStyle/zTreeStyle.css' %>
    <%= javascript '/js/jquery.ztree.all.min.js' %>
```

あと埋め込む部分は、jstreeと比べて以下の変更になっています。

```html
<ul id="tree" class="ztree"></div>
```

div -> ul タグに変更。
class を設定。

```javascript:public/js/read_tree.js
var setting = {
    view: {
        selectedMulti: true, // 複数選択
        dblClickExpand: false // ダブルクリックで展開
    },
    check: {
        enable: true, // チェックボックス機能
    },
    data: {
        simpleData: {
            enable: true,  // Whether to adopt a simple data mode
        }
    },
    callback: {
        beforeClick: beforeClick // クリックした後に呼び出す関数
    }
};

function beforeClick(treeId, treeNode) {
    var zTree = $.fn.zTree.getZTreeObj("tree");
    zTree.checkNode(treeNode, !treeNode.checked, true, true); // ファイル名を押下したときに選択状態を変える
    return false;
}

$(function () {
    // ページ読み込み後に実行
    $(document).ready(function () {
        redraw_tree();
    });
});

function redraw_tree() {
    $.ajax({
        type: "get",
        url: "/get_files_data",
        dataType: "json",
        cache: false,
        success: function (data) {
            $.fn.zTree.init($("#tree"), setting, data);
        },
        error: function () {
            alert("Server Error. Please try again later.");
        },
        complete: function () {
            // 通信完了時
        }
    });
}

```

### 選択したファイルパスを取得する

ディレクトリパスは除いています。

```javascript:public/js/read_tree.js
function get_select_files() {
    var treeObj = $.fn.zTree.getZTreeObj("tree");
    var nodes = treeObj.getCheckedNodes(true);
    var files = [];
    $.each(nodes, function (index, node) {
        // 末尾のファイルパスのみ取得
        if (node.children == null) {
            var path = [];
            $.each(node.getPath(), function (i, n) {
                path.push(n.name);
            });
            files.push(path.join("/"));
        }
    });
    return files;
}
```

他にもやりたいことがあれば、API Documentを見るといろいろできそうです。
http://www.treejs.cn/v3/api.php

## まとめ

jstreeと比べて 軽めで使いやすい と思いました。

少ないファイルパスだとどちらも変わらない感じだと思いますが、
多くなるとその差がわかるかと思います。

でもユーザビリティはjstreeの方がよいかなぁ～。
ファイルパスを再読み込みしたときに選択状態を記憶してくれたり。。。

ぜひ両方使って試してみてくださいね～。