「jsTree 描画→Treeの編集操作→ 編集結果を次回表示の為に保存」
(ここでいうTreeの編集操作は、ツリーアイテムを移動したり新規作成・削除・名称変更をツリー図上で実行することです)
通常は、ブラウザで表示した jsTree → JavaScript で JSON 変換、→ サーバへ送信 → サーバ側の処理で、
jsTree 用の JSON に変換して次回の表示のデータにする。
ということをするのが一般的だと思います。
しかし、この為にサーバ側で処理を書くのがめんどくさい時など、
手元の開発用に、jsTree 描画→Treeの編集操作→ 編集結果を次回表示の為に保存、ということをするために、
Python の助けを借ります。
Python eel で、chrome 起動 → jsTree の HTML を表示、→ ツリーの編集操作
→ JavaScript で、ツリー図データを JSON にして、Python に渡す、
→ Python でJSONを解析して、jsTree がツリー表示できる形式の JSONに変換する
→ Python でローカルPCに保存する。→ 次回起動再読み込む JSON として保存する
HTMLソース
JavaScript src="/eel.js" を指定するのを忘れないようにします。
↓ font-awesome は、右クリックコンテキストメニューのアイコンで利用します。ここでは CDNサイトを指定してます。
↓ BootStrap も CDNサイト利用です。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>develop.html</title> <link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.5.0/css/all.min.css" rel="stylesheet"> <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"/> <link href="jstree/themes/default/style.min.css" rel="stylesheet" type="text/css"/> <link href="css/develop.css" rel="stylesheet" type="text/css"/> <script src="js/jquery-3.3.1.min.js" type="text/javascript"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script> <script src="jstree/jstree-3.3.6.min.js" type="text/javascript"></script> <script src="js/develop-tree.js" type="text/javascript"></script> <script type="text/javascript" src="/eel.js"></script> <script src="js/develop.js" type="text/javascript"></script> </head> <body> <div class="container-fluid"> <div class="d-flex justify-content-center edit-action"> <button id="create" type="button" class="btn btn-outline-primary">SAVE</button> </div> <div> <ul class="edit-container"> <li class="edit-border"> <div class="tree-container"> <div id="tree" class="tree-div"></div> </div> </li> </ul> </div> </div> </body> </html>
CSS ソース
@charset "UTF-8"; /** * develop.css */ ul{ margin: 0; padding: 0; } li{ list-style-type: none; } ul.edit-container::after{ content: ""; display: block; clear: both; } li.edit-border{ padding: 2px 10px; float: left; } li.edit-border:nth-child(1){ width: 100%; height: 400px; overflow: auto; } li.edit-border:nth-child(2){ width: 60%; } .edit-action{ margin: 20px 0px; } /* ツリー図エリア背景色 */ .tree-div{ background-color: #ffffff; } .edit-border{ border: 1px solid #a0c0b0; } .tree-container{ max-height: 360px; overflow-y: auto; } /*------------- for jsTee ---------------*/ /* jsTreeアイコン画像無し */ .jstree-default-large .jstree-notIcon { display: none; } .contextmenu-icon{ color: #0085c9; }
jsTree を描画する JS ソース
$(function(){ $.jstree.defaults.core.themes.variant = "large"; $.jstree.defaults.core.themes.responsive = true; $('#tree').jstree({ 'plugins': [ 'contextmenu','dnd' ], 'core':{ 'data':{ "url":"./tree.json", "dataType":"json" }, "check_callback" : function(operation, node, node_parent, node_position, more){ if (operation=="move_node"){ if (node_parent.icon != "jstree-folder" && node_parent.id != "#") return false; } }, }, "contextmenu":{ "items":function($node){ return { "createFolder":{ "separator_before": false, "separator_after": false, "icon": "contextmenu-icon fa fa-folder-plus", "label": "新規フォルダ作成", "_disabled": function(data){ return $.jstree.reference(data.reference).get_node(data.reference).icon != "jstree-folder"; }, "action": function(data){ var inst = $.jstree.reference(data.reference), obj = inst.get_node(data.reference); inst.create_node(obj, { text:'New Folder', 'icon':'jstree-folder' }, "last", function(new_node){ try{ inst.edit(new_node); }catch(ex){ setTimeout(function(){ inst.edit(new_node); },0); } }); } }, "createFile":{ "separator_before": false, "separator_after": false, "icon": "contextmenu-icon far fa-file", "label": "新規ファイル作成", "_disabled": function(data){ return $.jstree.reference(data.reference).get_node(data.reference).icon != "jstree-folder"; }, "action": function(data){ var inst = $.jstree.reference(data.reference), obj = inst.get_node(data.reference); inst.create_node(obj, { text:'New File', 'icon':'jstree-file' }, "last", function(new_node){ try{ inst.edit(new_node); }catch(ex){ setTimeout(function(){ inst.edit(new_node); },0); } }); } }, "rename":{ "separator_before": true, "separator_after": false, "icon": "contextmenu-icon fa fa-edit", "label": "名称の変更", "_disabled": false, "action": function(data){ var inst = $.jstree.reference(data.reference), obj = inst.get_node(data.reference); inst.edit(obj); } }, "remove":{ "separator_before": false, "separator_after": false, "icon": "contextmenu-icon fa fa-trash-alt", "label": "削除", "_disabled": function(data){ return $.jstree.reference(data.reference).get_node(data.reference).parent == "#"; }, "action": function(data){ var inst = $.jstree.reference(data.reference), obj = inst.get_node(data.reference); if (inst.is_selected(obj)){ inst.delete_node(inst.get_selected()); }else{ inst.delete_node(obj); } } }, "cut":{ "separator_before": true, "separator_after": false, "icon": "contextmenu-icon fa fa-cut", "label": "切り取り", "_disabled": function(data){ return $.jstree.reference(data.reference).get_node(data.reference).parent == "#"; }, "action": function(data){ var inst = $.jstree.reference(data.reference), obj = inst.get_node(data.reference); if (inst.is_selected(obj)){ inst.cut(inst.get_top_selected()); }else{ inst.cut(obj); } } }, "copy":{ "separator_before": false, "separator_after": false, "icon": "contextmenu-icon fa fa-copy", "label": "コピー", "_disabled": function(data){ return $.jstree.reference(data.reference).get_node(data.reference).parent == "#"; }, "action": function(data){ var inst = $.jstree.reference(data.reference), obj = inst.get_node(data.reference); if (inst.is_selected(obj)){ inst.copy(inst.get_top_selected()); }else{ inst.copy(obj); } } }, "paste":{ "separator_before": false, "separator_after": false, "icon": "contextmenu-icon fa fa-paste", "label": "貼り付け", "_disabled": function(data){ return !$.jstree.reference(data.reference).can_paste(); }, "action": function(data){ var inst = $.jstree.reference(data.reference), obj = inst.get_node(data.reference); inst.paste(obj); } } }; } } }).on('loaded.jstree', function(){ $(this).jstree('open_all'); }); });
ツリー表示の JSON データです。 tree.json の内容
[ { "id": "1", "icon": "jstree-folder", "text": "ルート", "children": [ { "id": "11", "icon": "jstree-file", "text": "A", "children": [] } ] } ]
注目のPython コードです。
# -*- coding: utf-8 -*- import eel import json import codecs @eel.expose def writeFile(str): # JavaScript から受け取ったテキスト→JSON parse → jsTree用 JSON作成 trees = [] for node in json.loads(str): if node['parent'] == '#': trees.append(makeNode(node)) else: setLeaf(node, trees) print(json.dumps(trees, ensure_ascii=False, indent=2)) # ファイル保存 with codecs.open('WebContent/tree.json', 'w+', 'utf-8') as fp: json.dump(trees, fp, ensure_ascii=False, indent=2) def makeNode(node): return dict(id=node['id'], icon=node['icon'], text=node['text'], children=[]) def setLeaf(node, trees): for n in trees: if (n['id']==node['parent']): n['children'].append(makeNode(node)) break else: setLeaf(node, n['children']) if __name__ == '__main__': web_app_options = { 'mode': "chrome-app", 'port': 8000, 'chromeFlags': ["--browser-startup-dialog"] } eel.init("WebContent") eel.start("develop.html", size=(800, 600), options=web_app_options)
「SAVE」ボタンをクリックした時に Python eel でエクスポーズしたメソッドを呼び出す
JavaScript です。「eel. + (Python で @expose したメソッド名)」で記述します。
/** * develop.js */ $(function(){ $('#create').click(function(){ var v = $('#tree').jstree(true).get_json('#', {flat:true}); eel.writeFile(JSON.stringify(v)) }); });
これらのコードの配置は、WebContent フォルダの下に HTML と tree.json
そして、Javascript 、css ファイルを直下または相対で配置します。
Python コードの eel initメソッドが、"WebContent" としているので、Python コードは、
WebContent フォルダと同じ階層に置きます。