Previously node_click_handle dispatched node_selected(false) for old node before dispatching node_selected(true) for new node, causing apps to briefly show old content. Now only visually deselects old node, dispatches only for new selection.
573 lines
14 KiB
JavaScript
573 lines
14 KiB
JavaScript
var bricks = window.bricks || {};
|
|
bricks.TreeNode = class extends bricks.VBox {
|
|
constructor(tree, pnode, data){
|
|
var opts = {
|
|
width:'100%',
|
|
height:'auto',
|
|
}
|
|
super(opts);
|
|
this.tree = tree;
|
|
this.parent_node = pnode;
|
|
this.children_loaded = false;
|
|
this.user_data = data;
|
|
this.setup_icon_urls()
|
|
this.is_leaf_field = this.tree.is_leafField || 'is_leaf';
|
|
this.is_leaf = this.user_data[this.is_leaf_field];
|
|
this.params = bricks.extend({}, this.tree.params)
|
|
this.params = bricks.extend(this.params, {id:this.user_data[this.tree.opts.idField]});
|
|
if (this.tree.opts.typeField){
|
|
this.params.type = this.user_data[this.tree.opts.typeField];
|
|
}
|
|
var n = new bricks.HBox({
|
|
height:this.tree.row_height,
|
|
width:'100%'
|
|
})
|
|
n.dom_element.style.margin = bricks.app.charsize * 0.2;
|
|
this.add_widget(n);
|
|
n.bind('click', this.tree.node_click_handle.bind(this.tree, this));
|
|
this.node_widget = n;
|
|
schedule_once(this.create_node_content.bind(this,n), 0.01);
|
|
if (! this.user_data.is_leaf) {
|
|
this.container = new bricks.VBox({height:'auto'});
|
|
this.add_widget(this.container);
|
|
this.container.dom_element.style.marginLeft = bricks.app.charsize + 'px';
|
|
if (this.user_data.children){
|
|
this.tree.create_node_children(this, this.user_data.children);
|
|
}
|
|
this.container.hide();
|
|
}
|
|
}
|
|
getValue(){
|
|
var v = this.user_data;
|
|
if (this.container){
|
|
var children = [];
|
|
for (var i=0; i<this.container.children.length;i++){
|
|
var sv = this.container.children[i].getValue();
|
|
children.push(sv);
|
|
}
|
|
v.children = children;
|
|
}
|
|
return v;
|
|
}
|
|
get_id(){
|
|
return this.user_data[this.tree.idField];
|
|
}
|
|
selected(flg){
|
|
if (flg){
|
|
this.str_w.set_css('selected');
|
|
} else {
|
|
this.str_w.set_css('selected',true);
|
|
}
|
|
}
|
|
async toggleExpandCollapse(event){
|
|
if (event.params == 'open') {
|
|
await this.expand();
|
|
} else {
|
|
this.collapse()
|
|
}
|
|
}
|
|
|
|
async expand(){
|
|
this.node_state = 'open';
|
|
if (this.is_leaf){
|
|
return;
|
|
}
|
|
if (this.tree.opts.dataurl && !this.is_leaf && !this.children_loaded){
|
|
await this.tree.get_children_data(this)
|
|
this.children_loaded = true;
|
|
}
|
|
this.container.show();
|
|
}
|
|
collapse(){
|
|
this.node_state = 'close'
|
|
if (this.is_leaf){
|
|
return;
|
|
}
|
|
this.container.hide();
|
|
}
|
|
create_triple(){
|
|
return new bricks.StatedSvg({
|
|
state:'close',
|
|
states:[
|
|
{
|
|
state: 'open',
|
|
url: bricks_resource('imgs/node-expand.svg')
|
|
},
|
|
{
|
|
state: 'close',
|
|
url: bricks_resource('imgs/node-collapse.svg')
|
|
}
|
|
],
|
|
rate: 1,
|
|
});
|
|
}
|
|
change_node_type(){
|
|
if (this.is_leaf){
|
|
this.triple.set_state('close');
|
|
this.triple.bind('state_changed', this.toggleExpandCollapse.bind(this));
|
|
return;
|
|
this.triple.set_url(null);
|
|
this.triple.unbind('state_changed', this.toggleExpandCollapse.bind(this));
|
|
} else {
|
|
this.triple.set_state('close');
|
|
this.triple.bind('state_changed', this.toggleExpandCollapse.bind(this));
|
|
}
|
|
}
|
|
async create_node_content(widget){
|
|
var img_size = bricks.app.charsize;
|
|
this.triple = this.create_triple();
|
|
widget.add_widget(this.triple);
|
|
this.change_node_type();
|
|
if (this.tree.checkField){
|
|
var v = this.user_data[this.tree.checkField];
|
|
this.check_w = new bricks.UiCheck({name:'check', value:v});
|
|
widget.add_widget(this.check_w);
|
|
this.check_w.bind('changed', this.tree.node_checked.bind(this.tree, this))
|
|
}
|
|
var icon_url;
|
|
if ( isString(this.icons_urls)){
|
|
icon_url = this.icons_urls;
|
|
} else {
|
|
icon_url = this.icons_urls.leaf;
|
|
if (this.is_leaf) icon_url = this.icons_urls.leaf;
|
|
else if (this.node_state == 'expand') this.icon_url = this.icons_urls.open;
|
|
else this.icon_url = this.icons_urls.close;
|
|
}
|
|
console.log('icons=', this.icons_urls, 'icon_url=', icon_url, this.user_data);
|
|
var img = new bricks.Svg({
|
|
rate:1,
|
|
url:icon_url
|
|
});
|
|
widget.add_widget(img);
|
|
if (this.tree.node_view){
|
|
this.view_w = await bricks.widgetBuild(this.tree.node_view, widget, this.user_data);
|
|
if (this.view_w){
|
|
widget.add_widget(this.view_w);
|
|
}
|
|
} else {
|
|
var txt = this.user_data[this.tree.opts.textField];
|
|
this.str_w = new bricks.Text({text:txt});
|
|
widget.add_widget(this.str_w);
|
|
}
|
|
}
|
|
async update_content(){
|
|
if (this.tree.node_view){
|
|
this.node_widget.remove_Widget(this.view_w);
|
|
this.view_w = await bricks.widgetBuild(this.tree.node_view, widget, this.user_data);
|
|
if (this.view_w){
|
|
widget.add_widget(this.view_w);
|
|
}
|
|
}
|
|
this.str_w.set_text(this.user_data[this.tree.opts.textField]);
|
|
}
|
|
setup_icon_urls(){
|
|
var icons = null;
|
|
if (this.tree.opts.typeField){
|
|
var ntype = this.user_data[this.tree.opts.typeField];
|
|
if (this.tree.node_typeicons){
|
|
icons = this.tree.node_typeicons[ntype];
|
|
if (!icons){
|
|
var dt = this.tree.node_typeicons.default_type;
|
|
icons = this.tree.node_typeicons[dt];
|
|
}
|
|
}
|
|
console.log('ntype=', ntype, 'icons=', icons, this.user_data, this.tree.opts.typeField);
|
|
}
|
|
if (! icons){
|
|
icons = {
|
|
open: bricks_resource('imgs/open-folders.svg'),
|
|
close: bricks_resource('imgs/close-folder.svg'),
|
|
leaf: bricks_resource('imgs/close-folder.svg')
|
|
};
|
|
}
|
|
this.icons_urls = icons;
|
|
}
|
|
}
|
|
|
|
bricks.Tree = class extends bricks.VScrollPanel {
|
|
/*
|
|
{
|
|
row_height:
|
|
idField:
|
|
is_leafField:
|
|
textField:
|
|
type_icons:
|
|
typeField:
|
|
default_type:
|
|
data:
|
|
dataurl:
|
|
node_typeicons:{
|
|
nodetype:{
|
|
open:url,
|
|
close:url,
|
|
leaf:url
|
|
}
|
|
default_type:
|
|
},
|
|
admin:{
|
|
{
|
|
addurl:
|
|
deleteurl:
|
|
updateurl:
|
|
othertools:[
|
|
]
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
constructor(options){
|
|
super(options);
|
|
this.row_height = this.opts.row_height || '35px';
|
|
this.multitype_tree = this.opts.multitype_tree||false;
|
|
this.selected_node = null;
|
|
this.build_title();
|
|
this.build_description();
|
|
this.create_toolbar();
|
|
this.checked_data = [];
|
|
this.container = new bricks.VBox({
|
|
"width":"100%",
|
|
"height":"auto",
|
|
"overflow":"auto"
|
|
});
|
|
this.add_widget(this.container);
|
|
this.data_id = null;
|
|
if (this.opts.dataurl){
|
|
this.params = options.params || {};
|
|
schedule_once(this.get_children_data.bind(this, this), 0.1);
|
|
} else {
|
|
this.user_data = {
|
|
id:null,
|
|
children:this.opts.data
|
|
}
|
|
this.create_node_children(this, this.opts.data);
|
|
}
|
|
}
|
|
getValue(){
|
|
var v = this.user_data;
|
|
if (this.container){
|
|
var children = [];
|
|
for (var i=0; i<this.container.children.length;i++){
|
|
var sv = this.container.children[i].getValue();
|
|
children.push(sv);
|
|
}
|
|
v.children = children;
|
|
}
|
|
return v;
|
|
}
|
|
get_id(){
|
|
return this.user_data[this.idField];
|
|
}
|
|
create_toolbar(){
|
|
var toolbar = bricks.extend({}, this.toolbar);
|
|
var tools = [];
|
|
if (this.editable){
|
|
tools.push({icon:bricks_resource('imgs/add.svg'), name:'add'});
|
|
tools.push({icon:bricks_resource('imgs/edit.svg'), name:'update'});
|
|
tools.push({icon:bricks_resource('imgs/delete.svg'), name:'delete'});
|
|
}
|
|
if (toolbar.tools){
|
|
toolbar.tools.forEach(f => tools.push(f));
|
|
}
|
|
if (tools.length == 0){
|
|
return;
|
|
}
|
|
toolbar.tools = tools;
|
|
this.toolbar_w = new bricks.IconTextBar(toolbar);
|
|
this.add_widget(this.toolbar_w);
|
|
this.toolbar_w.bind('command', this.toolbar_command.bind(this))
|
|
}
|
|
toolbar_command(event){
|
|
var opts = event.params;
|
|
switch (opts.name){
|
|
case 'add':
|
|
this.add_new_node();
|
|
break;
|
|
case 'delete':
|
|
this.delete_node();
|
|
break;
|
|
case 'update':
|
|
this.update_node();
|
|
break;
|
|
default:
|
|
if (opts.selected_data && ! this.selected_node){
|
|
var w = new bricks.Error({title:'Error', message:'No selected node found'});
|
|
w.open();
|
|
return;
|
|
}
|
|
if (opts.checked_data && this.checked_data.length == 0){
|
|
var w = new bricks.Error({title:'Error', message:'No checked node found'});
|
|
w.open();
|
|
return;
|
|
}
|
|
var d = {};
|
|
if (opts.selected_data){
|
|
d = this.selected_node.user_data;
|
|
} else if (opts.checked_data){
|
|
d = this.checked_data;
|
|
} else {
|
|
if (this.selected_node){
|
|
d = this.selected_node.user_data;
|
|
} else if (this.checked_data.length>0) {
|
|
d = this.checked_data[0];
|
|
} else {
|
|
d = this.opts.params;
|
|
}
|
|
}
|
|
d.meta_data = {
|
|
referer: this.id,
|
|
title: opts.label,
|
|
icon: opts.icon
|
|
}
|
|
this.dispatch(opts.name, d);
|
|
break;
|
|
}
|
|
}
|
|
async add_new_node(){
|
|
var w = new bricks.ModalForm({
|
|
target:this,
|
|
"width":"80%",
|
|
"height":"80%",
|
|
title:'add new node',
|
|
fields:this.editable.fields
|
|
});
|
|
w.bind('submit', this.new_node_inputed.bind(this))
|
|
}
|
|
|
|
async new_node_inputed(event){
|
|
var d = event.params;
|
|
var node = this;
|
|
if (this.selected_node){
|
|
console.log('selected node exists')
|
|
node = this.selected_node;
|
|
if (d instanceof FormData){
|
|
d.append(this.parentField, node.get_id());
|
|
} else {
|
|
d[this.parentField] = node.get_id();
|
|
}
|
|
} else if (this.opts.params) {
|
|
if (d instanceof FormData){
|
|
d.append(this.parentField, this.opts.params.id);
|
|
} else {
|
|
d[this.parentField] = this.opts.params.id;
|
|
}
|
|
}
|
|
if (this.opts.newdata_params){
|
|
for (const [k, v] of Object.entries(this.opts.newdata_params)){
|
|
if (d instanceof FormData){
|
|
d.append(k, v);
|
|
} else {
|
|
d[k] = v;
|
|
}
|
|
}
|
|
}
|
|
if (this.editable.add_url){
|
|
var jc = new bricks.HttpJson()
|
|
var desc = await jc.post(this.editable.add_url, {params:d});
|
|
if (desc.widgettype == 'Message'){
|
|
var data = desc.options.user_data;
|
|
this.append_new_subnode(node, data);
|
|
node.is_leaf = false;
|
|
if (node != this) node.change_node_type();
|
|
}
|
|
var w = await bricks.widgetBuild(desc, this);
|
|
w.open();
|
|
} else {
|
|
d[this.idField] = bricks.uuid();
|
|
this.append_new_subnode(node, d);
|
|
}
|
|
}
|
|
async create_tree_nodes(node, records){
|
|
for (var i=0;i<records.length;i++){
|
|
this.build_subnode(node, records[i]);
|
|
}
|
|
}
|
|
append_new_subnode(node, data){
|
|
data.is_left = true;
|
|
if (!node.user_data){
|
|
node.user_data = {};
|
|
}
|
|
if (!node.user_data.children){
|
|
node.user_data.children = [];
|
|
}
|
|
node.is_leaf = false;
|
|
node.children_loaded = true;
|
|
node.user_data.children.push(data)
|
|
this.build_subnode(node, data);
|
|
}
|
|
|
|
build_subnode(node, data){
|
|
var n = new bricks.TreeNode(this, node, data);
|
|
node.container.add_widget(n);
|
|
}
|
|
|
|
async delete_node(){
|
|
if (! this.selected_node){
|
|
w = new bricks.Error({title:'Delete', message:'No selected node found'});
|
|
w.open();
|
|
return;
|
|
}
|
|
var w = new bricks.Conform({
|
|
cwidth:16,
|
|
cheight:9,
|
|
title:'Delete node',
|
|
message:'Please conform delete selected node'
|
|
});
|
|
w.bind('conformed', this.delete_node_conformed.bind(this))
|
|
}
|
|
async delete_node_conformed(event){
|
|
if (this.editable.delete_url){
|
|
var jc = new bricks.HttpJson()
|
|
var desc = await jc.post(this.editable.delete_url, {params:this.selected_node.user_data});
|
|
if (desc.widgettype == 'Message'){
|
|
var pnode = this.selected_node.parent_node;
|
|
console.log('parent node=', pnode);
|
|
if (pnode && pnode.container){
|
|
pnode.container.remove_widget(this.selected_node);
|
|
}
|
|
this.selected_node = null;
|
|
}
|
|
var w = await bricks.widgetBuild(desc, this);
|
|
w.open();
|
|
} else {
|
|
var pnode = this.selected_node.parent_node;
|
|
if (pnode && pnode.container){
|
|
pnode.container.remove_widget(this.selected_node);
|
|
}
|
|
this.selected_node = null;
|
|
}
|
|
}
|
|
delete_subnode(node, subnode){
|
|
var subid = subnode.user_data[this.idField];
|
|
var children = [];
|
|
for (var i=0;i< node.user_data.children.length;i++){
|
|
var c = node.user_data.children[i];
|
|
if (c[this.idField] != subid){
|
|
children.push(c);
|
|
}
|
|
}
|
|
node.user_data.children = children;
|
|
node.container.remove_widget(subnode);
|
|
}
|
|
|
|
async update_node(){
|
|
if (! this.selected_node){
|
|
w = new bricks.Error({title:'Update', message:'No selected node found'});
|
|
w.open();
|
|
return;
|
|
}
|
|
var fields = [];
|
|
for (var i=0;i<this.editable.fields.length;i++){
|
|
var f = bricks.extend({}, this.editable.fields[i]);
|
|
f.value = this.selected_node.user_data[f.name];
|
|
fields.push(f);
|
|
}
|
|
var w = new bricks.ModalForm({
|
|
target:this,
|
|
"width":"80%",
|
|
"height":"80%",
|
|
title:'add new node',
|
|
fields:fields
|
|
});
|
|
w.bind('submit', this.update_node_inputed.bind(this))
|
|
}
|
|
|
|
async update_node_inputed(event){
|
|
var d = event.params;
|
|
var node = this.selected_node;
|
|
if (d instanceof FormData){
|
|
d.append(this.idField, node.get_id());
|
|
} else {
|
|
d[this.idField] = node.get_id();
|
|
}
|
|
if(this.editable.update_url){
|
|
var jc = new bricks.HttpJson()
|
|
var desc = await jc.post(this.editable.update_url, {params:d});
|
|
if (desc.widgettype == 'Message'){
|
|
var o = formdata2object(d);
|
|
await this.update_node_data(node, o);
|
|
}
|
|
var w = await bricks.widgetBuild(desc, this);
|
|
w.open();
|
|
} else {
|
|
var o = formdata2object(d);
|
|
await this.update_node_data(node, o);
|
|
}
|
|
}
|
|
|
|
async update_node_data(node, data){
|
|
var data_keys = Object.keys(node.user_data);
|
|
Object.keys(data).forEach(k => {
|
|
if (data_keys.includes(k)){
|
|
console.log(node.user_data[k], ':', k, ':', data[k]);
|
|
node.user_data[k] = data[k];
|
|
}
|
|
});
|
|
await node.update_content();
|
|
}
|
|
|
|
async get_children_data(node){
|
|
var jc = new bricks.HttpJson();
|
|
var p = bricks.extend({}, this.params);
|
|
if (node != this){
|
|
p.id = node.user_data[this.idField];
|
|
}
|
|
console.log('params=', p);
|
|
var d = await jc.httpcall(this.opts.dataurl,{
|
|
method : this.opts.method || 'GET',
|
|
params : p
|
|
})
|
|
if (d.length == 0){
|
|
node.is_leaf = true;
|
|
} else {
|
|
this.user_data = {
|
|
children:d
|
|
}
|
|
this.create_tree_nodes(node, d);
|
|
}
|
|
}
|
|
create_node_children(node, data){
|
|
if(!data) return;
|
|
for (var i=0; i<data.length; i++){
|
|
var n = new bricks.TreeNode(this, node, data[i]);
|
|
node.container.add_widget(n);
|
|
}
|
|
}
|
|
node_click_handle(node, event){
|
|
if (this.selected_node && this.selected_node != node){
|
|
this.selected_node.selected(false);
|
|
}
|
|
if (this.selected_node == node && !this.opts.select_only){
|
|
this.selected_node = null;
|
|
} else {
|
|
this.selected_node = node;
|
|
this.node_selected(node, true);
|
|
}
|
|
}
|
|
node_selected(node, flag){
|
|
console.log('node_selected():node=', node, flag);
|
|
node.selected(flag);
|
|
var d = bricks.extend(node.user_data, {
|
|
selected:flag
|
|
});
|
|
|
|
this.dispatch('node_selected', d);
|
|
}
|
|
async node_checked(node, event){
|
|
var cb = event.target.bricks_widget;
|
|
var stat = cb.getValue().check;
|
|
if (stat){
|
|
this.checked_data.push(node.user_data);
|
|
} else {
|
|
this.checked_data = this.checked_data.filter((d , idnex) => d.id == node.user_data.id);
|
|
}
|
|
node.user_data[this.checkField] = stat;
|
|
if (stat){
|
|
console.log('value=', cb.getValue(), 'node=', node);
|
|
}
|
|
this.dispatch('check_changed', node.user_data);
|
|
}
|
|
}
|
|
|
|
bricks.Factory.register('Tree', bricks.Tree);
|
|
bricks.Factory.register('EditableTree', bricks.EditableTree);
|