Files
2025-07-18 16:20:14 +07:00

338 lines
16 KiB
PHP

<?php
/* -------- Category Tree ------------------- */
global $lang, $baseurl, $css_reload_key;
global $category_tree_open, $is_search, $category_tree_add_parents,
$category_tree_remove_children, $k;
$is_search = (isset($is_search) ? $is_search : false);
$forsearchbar = (isset($forsearchbar) ? $forsearchbar : false);
$edit_autosave = (isset($edit_autosave) ? $edit_autosave : false);
$hidden_input_elements = '';
$hidden_input_elements_id_prefix = ($is_search ? 'nodes_searched_' : 'nodes_');
$status_box_id = ($is_search ? "nodes_searched_{$field['ref']}_statusbox" : "nodes_{$field['ref']}_statusbox");
$status_box_elements = '';
$update_result_count_function_call = 'UpdateResultCount();';
$tree_id = ($is_search ? "search_tree_{$field['ref']}" : "tree_{$field['ref']}");
$tree_container_styling = ($category_tree_open ? 'display: block;' : 'display: none;');
$current_val_fieldname = "field_{$field['ref']}_currentval";
if (isset($user_set_values[$field['ref']]) && is_array($user_set_values[$field['ref']]) && !empty($user_set_values[$field['ref']])) {
// User set values are options selected by user - used to render what users selected before submitting the form and
// receiving an error (e.g required field missing
$selected_nodes = $user_set_values[$field['ref']];
} elseif (isset($field_nodes)) {
$selected_nodes = $field_nodes;
} elseif (isset($searched_nodes) && is_array($searched_nodes)) {
$selected_nodes = $searched_nodes;
} elseif (!isset($selected_nodes)) {
$selected_nodes = [];
}
// When editing multiple resources, we don't want to preselect any options; the user must make the necessary selection
if ($multiple && $copyfrom == '') {
$selected_nodes = [];
}
// Strip out invalid nodes
$valid_nodes = [];
foreach ($selected_nodes as $node) {
$node_data = array();
if (get_node($node, $node_data) && $node_data["resource_type_field"] != $field["ref"]) {
continue;
}
if (get_node($node, $node_data) === false) {
continue;
}
$valid_nodes [] = $node_data;
}
$valid_nodes = array_filter($valid_nodes, 'node_is_active');
foreach ($valid_nodes as $node) {
$hidden_input_elements .= "<input id=\"{$hidden_input_elements_id_prefix}{$node["ref"]}\" class =\"{$tree_id}_nodes\" type=\"hidden\" name=\"" . escape($name) . "\" value=\"{$node["ref"]}\">";
// Show previously selected options on the status box
if (!(isset($treeonly) && $treeonly)) {
$status_box_elements .= "<div id=\"" . $tree_id . "_selected_" . $node['ref'] . "\" class=\"" . $tree_id . "_option_status\" ><span id=\"{$status_box_id}_option_{$node['ref']}\">" . escape(i18n_get_translated($node['name'])) . "</span><br /></div>";
}
}
if ($forsearchbar) {
$update_result_count_function_call = '';
}
if (!$is_search) {
$update_result_count_function_call = '';
}
?>
<div class="Fixed">
<?php if (!(isset($treeonly) && $treeonly)) { ?>
<div id="<?php echo $status_box_id; ?>" class="CategoryBox">
<div id="<?php echo $tree_id; ?>_statusbox_begin" style="display:none;"></div>
<div id="<?php echo $tree_id; ?>_statusbox_platform" style="display:none;"></div>
<?php echo $status_box_elements; ?>
</div>
<div>
<a
href="#"
onclick="
if (document.getElementById('<?php echo $tree_id; ?>').style.display!='block') {
document.getElementById('<?php echo $tree_id; ?>').style.display='block';
} else {
document.getElementById('<?php echo $tree_id; ?>').style.display='none';
}
return false;"
><?php echo LINK_CARET . escape($lang['showhidetree']); ?></a>
&nbsp;
<a href="#" onclick="clearCategoryTree_<?php echo $tree_id; ?>(); return false;">
<?php echo LINK_CARET . escape($lang['clearall']); ?>
</a>
</div>
<script>
// The nodes in the statusbox list have been rendered in ascending node sequence.
// The list must be reordered to reflect the preorder traversal sequence of the jstree to make it more readable.
function reorder_selected_statusbox_<?php echo $tree_id; ?>() {
var thisJstree = jQuery('#<?php echo $tree_id; ?>');
var treefieldid = jQuery(thisJstree)[0].id;
// The preorder sequence will only contain the loaded tree nodes (ie. not necessarily the whole tree).
var preorderlist = jQuery(thisJstree).find("li");
// The statusbox platform is the element before which we need to insert selected divs in preorder sequence
// This method should give us the order we need for improved readability (instead of by node ref)
let statusboxbegin = jQuery("#"+treefieldid+"_statusbox_begin");
let statusboxplatform = jQuery("#"+treefieldid+"_statusbox_platform");
// Re-position the platform
// Any selected nodes not yet loaded in the tree will remain below the platform and so are effectively "moved" to the end of the list
statusboxplatform.insertAfter(statusboxbegin);
// Place each node (in preorder sequence) onto the platform
for (var i = 0;i<preorderlist.length;i++) {
// var pentry = jQuery("#tree_"+treefieldid+"_selected_"+preorderlist[i].id);
var pentry = jQuery("#"+treefieldid+"_selected_"+preorderlist[i].id);
if (pentry) {
pentry.insertBefore(statusboxplatform);
}
}
}
function clearCategoryTree_<?php echo $tree_id; ?>() {
if (!confirm('<?php echo escape($lang["clearcategoriesareyousure"]);?>')) {
return false;
}
var thisJstree = jQuery('#<?php echo $tree_id; ?>');
// Ensure that the child deselection occurs irrespective of the setting of category_tree_remove_children
category_tree_clear = true;
// Establish all root nodes of this tree, open them and deselect them
var rootNodesJstree = thisJstree.jstree(true).get_node("#").children;
rootNodesJstree.forEach(function(rootNode) {
thisJstree.jstree('open_node', rootNode, function(e, data) {
if (thisJstree.jstree('is_selected', e.id)) {
thisJstree.jstree('deselect_node', e.id);
} else {
deselect_children_of_jstree_node(thisJstree, e.id);
}
});
});
category_tree_clear = false;
// Remove the hidden inputs
var elements = document.getElementsByName('<?php echo $name; ?>');
while (elements[0]) {
elements[0].parentNode.removeChild(elements[0]);
}
// Clear contents of status box
var node_statusbox = document.getElementById('<?php echo $status_box_id; ?>');
while (node_statusbox.lastChild) {
node_statusbox.removeChild(node_statusbox.lastChild);
}
<?php
if (!$is_search && $edit_autosave) {
echo "AutoSave('{$field['ref']}');";
}
echo $update_result_count_function_call;
?>
}
</script>
<?php
}
echo $hidden_input_elements;
?>
<div id="<?php echo $tree_id; ?>" style="<?php echo $tree_container_styling; ?>"></div>
<script>
jQuery('#<?php echo $tree_id; ?>').jstree({
'core' : {
'data' : {
url : '<?php echo $baseurl; ?>/pages/ajax/category_tree_lazy_load.php',
data : function(node) {
return {
ajax : true,
node_ref : node.id,
field : <?php echo $field['ref']; ?>,
selected_nodes : <?php echo json_encode(array_column($valid_nodes, "ref")); ?>,
k : '<?php echo escape((string)$k); ?>'
};
}
},
'themes' : {
'name' : 'default-dark',
'icons': false
},
'multiple' : true,
},
'plugins' : [
'wholerow',
'checkbox',
],
'checkbox' : {
// jsTree Documentation: three_state is a boolean indicating if checkboxes should cascade down and have an
// undetermined state. Defaults to true
'three_state': false,
// jsTree Documentation: This setting controls how cascading and undetermined nodes are applied.
// If 'up' is in the string - cascading up is enabled, if 'down' is in the string - cascading down is enabled,
// if 'undetermined' is in the string - undetermined nodes will be used.
// If three_state is set to true this setting is automatically set to 'up+down+undetermined'. Defaults to ''.
// IMPORTANT: we set it to default so we can create our intended behaviour
'cascade': ''
}
});
/*
Intended behaviour (jstree does certain things by default that we don't want)
----------------------------------------------
1. Selecting a sub (child) node will automatically select all parent nodes up to and including the root level, unless
the option $category_tree_add_parents is set to false
2. Deselecting a parent node will automatically deselect all child nodes, unless the option $category_tree_remove_children
is set to false
3. Deselecting a sub node will not affect any parent nodes
4. Selection of a parent node will have no effect on any sub nodes
*/
var jquery_tree_by_id = jQuery('#<?php echo $tree_id; ?>');
var category_tree_add_parents = <?php echo $category_tree_add_parents ? 'true' : 'false'; ?>;
var category_tree_remove_children = <?php echo $category_tree_remove_children ? 'true' : 'false'; ?>;
var category_tree_clear = false;
// When a node is selected, and add parents is enabled, then automatically select all ancestors
jquery_tree_by_id.on('select_node.jstree', function (event, data) {
var thisJstree = jQuery(this);
if (category_tree_add_parents) {
// Establish the parent of the selected node
var parent_node = thisJstree.jstree('get_parent', data.node.id);
if (parent_node) {
// Trigger selection of the parent node
thisJstree.jstree('select_node', parent_node);
}
}
});
<?php if (!$is_search) { ?>
// When a node is deselected and remove children is enabled, then automatically deselect the children if present
jquery_tree_by_id.on('deselect_node.jstree', function (event, data) {
var thisJstree = jQuery(this);
if (thisJstree.jstree('is_leaf', data.node.id)) {
// console.log("NODE "+data.node.id+" DESELECTED LEAF - NO CHILDREN - NO FURTHER ACTION");
return;
}
if (category_tree_remove_children || category_tree_clear) {
if (thisJstree.jstree('is_parent', data.node.id)) {
// console.log("PARENT NODE "+data.node.id+" DESELECTED - PROCESS ITS CHILDREN");
if (thisJstree.jstree('is_closed', data.node.id)) {
// console.log("-- PARENT NODE "+data.node.id+" IS CLOSED - OPEN IT");
thisJstree.jstree('open_node', data.node.id, function(e, data) {
// console.log("-- -- NODE "+e.id+" OPENED CALLBACK ");
deselect_children_of_jstree_node(thisJstree, e.id);
});
} else {
// Parent is already open
// console.log("-- PARENT NODE "+data.node.id+" ALREADY OPEN - DESELECT ITS CHILDREN");
deselect_children_of_jstree_node(thisJstree, data.node.id);
}
}
}
});
<?php } ?>
// Reflect node selections onto the status box and hidden inputs
jquery_tree_by_id.on('select_node.jstree', function (event, data) {
// Add hidden input which is used to post to the edit or search
document.getElementById('<?php echo $tree_id; ?>').insertAdjacentHTML(
'beforeBegin',
'<input id="<?php echo $hidden_input_elements_id_prefix; ?>' + data.node.id +
'" type="hidden" name="<?php echo $name; ?>" class ="<?php echo $tree_id; ?>_nodes" value="' + data.node.id +
'">');
document.getElementById('<?php echo $status_box_id; ?>').insertAdjacentHTML(
'beforeEnd',
'<div id="<?php echo $tree_id . "_selected_";?>' + data.node.id + '" class="<?php echo $tree_id; ?>_option_status"><span id="<?php echo $status_box_id;?>_option_'
+ data.node.id + '">'
+ data.node.text
+ '</span><br /></div>');
});
// Reflect node deselections onto the status box and hidden inputs
jquery_tree_by_id.on('deselect_node.jstree', function (event, data) {
// Remove hidden input so that it is no longer posted to the edit or search
jQuery('#<?php echo $hidden_input_elements_id_prefix; ?>'+data.node.id+'.<?php echo "{$tree_id}_nodes"?>').remove();
// Remove entry from statusbox
jQuery('#<?php echo $status_box_id;?>_option_'+data.node.id).parent().remove();
});
// Reflect aggregated changes - trigger centralspace events; trigger autosave
jquery_tree_by_id.on('changed.jstree', function (event, data) {
if (!(data.action == 'select_node' || data.action == 'deselect_node')) {
return;
}
<?php if (!(isset($treeonly) && $treeonly)) { ?>
reorder_selected_statusbox_<?php echo $tree_id; ?>();
<?php } ?>
var selected_rs_node_ids = data.selected;
for (var i = 0; i < selected_rs_node_ids.length; i++) {
// Trigger an event so we can chain actions once we've changed a category tree option
jQuery('#<?php echo $forsearchbar ? "SearchBox" : "CentralSpace" ?>').trigger('categoryTreeChanged', [{node: selected_rs_node_ids[i]}]);
}
if (selected_rs_node_ids.length == 0) {
// Category tree cleared
jQuery('#<?php echo $forsearchbar ? "SearchBox" : "CentralSpace" ?>').trigger('categoryTreeChanged', 0);
}
<?php if (!$is_search && $edit_autosave) { ?>
// Give time for all actions e.g. check parents to complete before autosaving
if (typeof(edit_tree_block_autosave) == 'undefined' || !edit_tree_block_autosave) {
edit_tree_block_autosave = true;
loadingtimers.push(window.setTimeout("AutoSave('<?php echo escape($field['ref']); ?>');edit_tree_block_autosave = false;",500));
}
<?php }
echo $update_result_count_function_call;
?>
});
// Reorder associated statusbox list if present
jquery_tree_by_id.on('after_open.jstree', function (event, data) {
<?php if (!(isset($treeonly) && $treeonly)) { ?>
reorder_selected_statusbox_<?php echo $tree_id; ?>();
<?php } ?>
});
</script>
</div>