3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-26

Webブラウザ上で
ファイルツリーを表示させれるものがないかな。。。
と探すと下記のものがヒットしたので使ってみました。

主役

jstree
https://www.jstree.com/

やりたいこと

  • ファイルツリーを表示
    • ajax で情報を取得する

ということでやっていきましょう。

調査

今回は Perl の Mojolicious を使って
webアプリ(cgi)で作っていきます。

まず jstree に情報を流し込むときに
どのようなデータを渡せばいいかというのを調査します。

Home > JSON data
https://www.jstree.com/docs/json/

上記ページにいろいろ書いてありますね。
ファイルから読み込む方法 や データを渡す方法・・・

今回は、データを渡して処理してもらう方法 にて対応します。

Using JSON > Using the alternative JSON format

$('#using_json_2').jstree({ 'core' : {
    'data' : [
       { "id" : "ajson1", "parent" : "#", "text" : "Simple root node" },
       { "id" : "ajson2", "parent" : "#", "text" : "Root node 2" },
       { "id" : "ajson3", "parent" : "ajson2", "text" : "Child 1" },
       { "id" : "ajson4", "parent" : "ajson2", "text" : "Child 2" },
    ]
} });

親子構造を作る関係で
id にはユニークな名前を付けてあげる。(ファイル名ではない)

parent に親の id を指定してあげる。
根本となるところ(root)には # を指定する。

text はファイル名を指定する。

使い方は、Overview あたりに書いてありますね。
https://www.jstree.com/

  1. jstreeのテーマを入れる(head > link(css))
  2. ツリーを表示させるところを作る(body > div)
  3. jQueryを入れる(head > script)
  4. jstreeを入れる(head > script)
  5. インスタンスをつくる(script)

コーディング

データ構造はperl側で作ります。

windows環境で作成しています。

事前準備

  1. perlをインストール
  2. Mojolicious の準備

実装

まずは、Mojolicius でサーバーサイドの動きを定義していきます。

index.cgi
# !perl
use strict;
use warnings;
use utf8;
use Mojolicious::Lite;
use lib qw(lib);

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

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

app->start;

__DATA__

@@index.html.ep
% layout 'default';
ここにコンテンツをいれる。
<div id="tree"></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/jstree/default/style.css' %>
    <%= javascript '/js/jquery-3.5.1.min.js' %>
    <%= javascript '/js/jstree.min.js' %>
    <title>ページタイトル</title>
</head>
<body>
<%== content %>
</body>
</html>

使うファイルを準備していきます。

ファイル構造を読み取る先としてtestdataの中に適当なファイルをいれてます。

ファイル構造
│  index.cgi
│
├─public
│  ├─css
│  │  │
│  │  └─jstree
│  │      ├─default
│  │      │      32px.png
│  │      │      40px.png
│  │      │      style.css
│  │      │      style.min.css
│  │      │      throbber.gif
│  │      │
│  │      └─default-dark
│  │              32px.png
│  │              40px.png
│  │              style.css
│  │              style.min.css
│  │              throbber.gif
│  │
│  └─js
│          jquery-3.5.1.min.js
│          jstree.min.js
│
└─testdata
    │  a.txt
    │  b.txt
    │
    └─hoge
        └─  c.txt

上記のファイルが準備できたら、準備は完了。

morbo index.cgi

でサーバーを立ち上げて、動くか見てみましょう。

$ morbo index.cgi
Web application available at http://127.0.0.1:3000

ブラウザで http://127.0.0.1:3000 にアクセスするとページが表示されます。

perl側でファイルの構造を作る。
※ここは好きな方法で取得してください

testdata に入れたファイル構造を jstree の data に形に変えていきます。

まずは小さなスクリプトとして動作を試してみます。

use strict;
use warnings;
use utf8;
use File::Find::Rule;
use Digest::SHA qw/sha256_hex/;
use Encode;
use Path::Tiny;
use JSON::PP;

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 = substr sha256_hex($filepath), 0, 10;
    if ($dir eq "" or $dir eq ".") {
        push @data, {id => $id, parent => "#", text => $filename, icon => "jstree-file"};
    } else {
        push @data, {id => $id, parent => $dirs->{$dir}, text => $filename, icon => "jstree-file"};
    }
}
my $json = JSON::PP->new->pretty->canonical;
print $json->encode({data => \@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} = substr sha256_hex(encode("cp932", $dir)), 0, 10;

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

    if (not exists $dirs->{$parent}) {
        &add_dir_hash($dirs, $parent, $data);
    }
    push @$data, {id => $dirs->{$dir}, parent => $dirs->{$parent}, text => path($dir)->basename};
}
出力結果
{
   "data" : [
      {
         "id" : "810ff2fb24",
         "parent" : "#",
         "text" : "testdata"
      },
      {
         "icon" : "jstree-file",
         "id" : "ba5f89d91f",
         "parent" : "810ff2fb24",
         "text" : "a.txt"
      },
      {
         "icon" : "jstree-file",
         "id" : "aa902bf366",
         "parent" : "810ff2fb24",
         "text" : "b.txt"
      },
      {
         "id" : "fa7f80ff87",
         "parent" : "810ff2fb24",
         "text" : "hoge"
      },
      {
         "icon" : "jstree-file",
         "id" : "e2df894515",
         "parent" : "fa7f80ff87",
         "text" : "c.txt"
      }
   ]
}

ファイルとなる部分に"icon" : "jstree-file"を入れています。
ディレクトリパスをハッシュに置き換えてユニークなidを作って
結び付けているという方法を取ってますが、この辺は好きな方法で。。。

ファイル構造が作れたので、あとはブラウザで受け取る側を作ります。

ページ送る時にデータ埋め込んでも送り返す方が簡単のですが、
あとでファイルの構造を書き換えたりしたいので、ajaxを作って非同期で取得します。

public/js/read_tree.js という自分が書いたものを入れていきます。

public/js/read_tree.js
$(function () {
    $('#tree').jstree();

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

function redraw_tree(hash) {
    $.ajax({
        type: "get",
        url: "/get_files_data",
        dataType: "json",
        success: function (data) {
            // 通信成功時
            $('#tree').jstree(true).settings.core.data = data;
            $('#tree').jstree(true).refresh();
        },
        error: function () {
            // 通信失敗時
            alert("Server Error. Please try again later.");
        },
        complete: function () {
            // 通信終了時の処理
        }
    });
}

index.cgi/js/read_tree.js を追加しておきます。

index.cgi
__DATA__

@@index.html.ep
% layout 'default';
ここにコンテンツをいれる。
<div id="tree"></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/jstree/default/style.css' %>
    <%= javascript '/js/jquery-3.5.1.min.js' %>
    <%= javascript '/js/jstree.min.js' %>
    <%= javascript '/js/read_tree.js' %>
    <title>ページタイトル</title>
</head>
<body>
<%== content %>
</body>
</html>

index.cgi/get_files_data と先ほどの処理を追加します。

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

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 = substr sha256_hex($filepath), 0, 10;
        if ($dir eq "" or $dir eq ".") {
            push @data, {id => $id, parent => "#", text => $filename, icon => "jstree-file"};
        } else {
            push @data, {id => $id, parent => $dirs->{$dir}, text => $filename, icon => "jstree-file"};
        }
    }
    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} = substr sha256_hex(encode("cp932", $dir)), 0, 10;

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

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

いい感じに表示できました。(ファイルツリーを展開した後)

tree.png

上級者向け

詳細な説明を割愛しています。

チェックボックスを付ける

public/js/read_tree.js
    $('#tree').jstree({
        "plugins" : [
            "checkbox",
        ]
    });

plugin 指定するだけで完了。

tree2.png

全チェック・解除

ボタン作ってイベントに処理をつける。

public/js/read_tree.js
    $('#select_all_btn').click(function () {
        $("#tree").jstree(true).check_all();
        $("#tree").jstree(true).refresh();
    });

    $('#unselect_all_btn').click(function () {
        $("#tree").jstree(true).uncheck_all();
        $("#tree").jstree(true).refresh();
    });

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

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

public/js/read_tree.js
    var selectedElms = $('#tree').jstree("get_selected", true);
    var files = [];
    $.each(selectedElms, function(index, element){
        var path = [];
        if ($("#" + element.id).hasClass("jstree-leaf") == false) {
            // directory path
            return true;
        }

        path.push(element.text);
        $.each(element.parents, function (i, e) {
            if (e != "#") {
                path.push($("#" + e + "_anchor").text());
            }
        });
        files.push(path.reverse().join("/"));
    });

まとめ

簡単にきれいなファイルツリーが表示できました。

チェックボックスを付けて
選択された情報が取れるといろいろ幅が広がりそうですね。

またチャレンジしてみてくださいね。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?