|
10 | 10 | <?php $_divId = 'tree' . $block->getId() ?> |
11 | 11 | <div id="<?= $block->escapeHtmlAttr($_divId) ?>" class="tree"></div> |
12 | 12 | <?php |
13 | | -$isUseMassaction = $block->getUseMassaction() ? 1 : 0; |
| 13 | +$isUseMassAction = $block->getUseMassaction() ? 1 : 0; |
14 | 14 | $isAnchorOnly = $block->getIsAnchorOnly() ? 1 : 0; |
15 | | -$intCategoryId = (int)$block->getCategoryId(); |
16 | | -$intRootId = (int) $block->getRoot()->getId(); |
17 | 15 | $scriptString = <<<script |
18 | 16 |
|
19 | | -require(['jquery', 'prototype', 'extjs/ext-tree-checkbox'], function(jQuery){ |
| 17 | +require(['jquery', 'jquery/jstree/jquery.jstree'], function($) { |
20 | 18 |
|
21 | | -var tree{$block->escapeJs($block->getId())}; |
| 19 | + let tree = $('#tree{$block->escapeJs($block->getId())}'); |
| 20 | + let useMassAction = {$isUseMassAction}; |
| 21 | + let isAnchorOnly = {$isAnchorOnly}; |
| 22 | + let checkedNodes = []; |
22 | 23 |
|
23 | | -var useMassaction = {$isUseMassaction}; |
24 | | -
|
25 | | -var isAnchorOnly = {$isAnchorOnly}; |
26 | | -
|
27 | | -Ext.tree.TreePanel.Enhanced = function(el, config) |
28 | | -{ |
29 | | - Ext.tree.TreePanel.Enhanced.superclass.constructor.call(this, el, config); |
30 | | -}; |
31 | | -
|
32 | | -Ext.extend(Ext.tree.TreePanel.Enhanced, Ext.tree.TreePanel, { |
33 | | -
|
34 | | - loadTree : function(config, firstLoad) |
35 | | - { |
36 | | - var parameters = config['parameters']; |
37 | | - var data = config['data']; |
| 24 | + function addLastNodeProperty(nodes) { |
| 25 | + return nodes.map(node => { |
| 26 | + return node.children |
| 27 | + ? { ...node, children: addLastNodeProperty(node.children) } |
| 28 | + : { ...node, lastNode: true }; |
| 29 | + }); |
| 30 | + } |
38 | 31 |
|
39 | | - if ((typeof parameters['root_visible']) != 'undefined') { |
40 | | - this.rootVisible = parameters['root_visible']*1; |
41 | | - } |
| 32 | + function actionBasedOnIsAnchorOnly() { |
| 33 | + tree.jstree().get_json('#', { flat: true }).each((node, value) => { |
| 34 | + const attrId = node.a_attr.id; |
| 35 | + const rootNode = tree.jstree().get_node("#"); |
| 36 | + const rootId = rootNode.children[0]; |
42 | 37 |
|
43 | | - var root = new Ext.tree.TreeNode(parameters); |
| 38 | + if (isAnchorOnly === 1 && node.id === rootId) { |
| 39 | + tree.jstree(true).disable_node(node); |
| 40 | + } else if (isAnchorOnly === 0 && node.id !== rootId) { |
| 41 | + tree.jstree(true).disable_node(node); |
| 42 | + } |
| 43 | + }); |
| 44 | + } |
44 | 45 |
|
45 | | - this.nodeHash = {}; |
46 | | - this.setRootNode(root); |
| 46 | + function handleLoadedTree(e, data) { |
| 47 | + const container = $(e.target).closest('div.chooser_container'); |
| 48 | + checkedNodes = container.find('input[type="text"].entities').val().split(',').map(item => item.trim()); |
47 | 49 |
|
48 | | - if (firstLoad) { |
| 50 | + data.instance.get_json('#', { flat: true }).forEach(nodeId => { |
| 51 | + const node = data.instance.get_node(nodeId); |
49 | 52 |
|
50 | | -script; |
51 | | -if ($block->getNodeClickListener()): |
52 | | - $scriptString .= 'this.addListener(\'click\', ' . /* @noEscape */ $block->getNodeClickListener() . |
53 | | - '.createDelegate(this));' . PHP_EOL; |
54 | | -endif; |
55 | | -$scriptString .= <<<script |
56 | | - } |
| 53 | + if (checkedNodes.includes(node.id)) { |
| 54 | + tree.jstree(true).select_node(node.id); |
| 55 | + } |
57 | 56 |
|
58 | | - this.loader.buildCategoryTree(root, data); |
59 | | - this.el.dom.innerHTML = ''; |
60 | | - // render the tree |
61 | | - this.render(); |
| 57 | + actionBasedOnIsAnchorOnly(); |
| 58 | + }); |
62 | 59 | } |
63 | | -}); |
64 | | -
|
65 | | -jQuery(function() |
66 | | -{ |
67 | | -
|
68 | | -script; |
69 | | - $scriptString .= 'var emptyNodeAdded = ' . ($block->getWithEmptyNode() ? 'false' : 'true') . ';' . PHP_EOL; |
70 | 60 |
|
71 | | -$scriptString .= <<<script |
| 61 | + function handleChange(e, data) { |
| 62 | + if (data.action === 'ready') { |
| 63 | + return; |
| 64 | + } |
72 | 65 |
|
73 | | - var categoryLoader = new Ext.tree.TreeLoader({ |
74 | | - dataUrl: '{$block->escapeJs($block->escapeUrl($block->getLoadTreeUrl()))}' |
75 | | - }); |
| 66 | + if (useMassAction) { |
| 67 | + const clickedNodeID = data.node.id; |
| 68 | + const currentCheckedNodes = data.instance.get_checked(); |
76 | 69 |
|
77 | | - categoryLoader.processResponse = function (response, parent, callback) { |
78 | | - var config = JSON.parse(response.responseText); |
| 70 | + if (data.action === 'select_node' && !checkedNodes.includes(clickedNodeID)) { |
| 71 | + checkedNodes = currentCheckedNodes; |
| 72 | + } else if (data.action === 'deselect_node') { |
| 73 | + checkedNodes = currentCheckedNodes.filter(nodeID => nodeID !== clickedNodeID); |
| 74 | + } |
79 | 75 |
|
80 | | - this.buildCategoryTree(parent, config); |
| 76 | + checkedNodes.sort((a, b) => a - b); |
81 | 77 |
|
82 | | - if (typeof callback == "function") { |
83 | | - callback(this, parent); |
| 78 | + const container = $(e.target).closest('div.chooser_container'); |
| 79 | + container.find('input[type="text"].entities').val(checkedNodes.join(', ')); |
| 80 | + } else { |
| 81 | + node = data.node; |
| 82 | + node.attributes = node.original; |
| 83 | + const nodeClickListener = {$block->getNodeClickListener()}; |
| 84 | + nodeClickListener(node); |
84 | 85 | } |
85 | | - }; |
| 86 | + } |
86 | 87 |
|
87 | | - categoryLoader.buildCategoryTree = function(parent, config) |
88 | | - { |
89 | | - if (!config) return null; |
90 | | -
|
91 | | -
|
92 | | - if (parent && config && config.length){ |
93 | | - for (var i = 0; i < config.length; i++) { |
94 | | - var node; |
95 | | - if (useMassaction && config[i].is_anchor == isAnchorOnly) { |
96 | | - config[i].uiProvider = Ext.tree.CheckboxNodeUI; |
97 | | - } |
98 | | - var _node = Object.clone(config[i]); |
99 | | -
|
100 | | - // Add empty node to reset category filter |
101 | | - if(!emptyNodeAdded) { |
102 | | - var empty = Object.clone(_node); |
103 | | - empty.text = '{$block->escapeJs(__('None'))}'; |
104 | | - empty.children = []; |
105 | | - empty.id = 'none'; |
106 | | - empty.path = '1/none'; |
107 | | - empty.cls = 'leaf'; |
108 | | - parent.appendChild(new Ext.tree.TreeNode(empty)); |
109 | | - emptyNodeAdded = true; |
110 | | - } |
111 | | -
|
112 | | - if (_node.children && !_node.children.length) { |
113 | | - delete(_node.children); |
114 | | - node = new Ext.tree.AsyncTreeNode(_node); |
115 | | - } else { |
116 | | - node = new Ext.tree.TreeNode(config[i]); |
117 | | - } |
118 | | - parent.appendChild(node); |
119 | | - node.loader = node.getOwnerTree().loader; |
120 | | - node.loader = node.getOwnerTree().loader; |
121 | | - if (_node.children) { |
122 | | - this.buildCategoryTree(node, _node.children); |
123 | | - } |
| 88 | + function getCheckedNodeIds(tree, node) { |
| 89 | + if (node.children_d && node.children_d.length > 0) { |
| 90 | + const selectChildrenNodes = node.children_d.filter(item => checkedNodes.includes(item)); |
| 91 | +
|
| 92 | + if (selectChildrenNodes.length > 0) { |
| 93 | + tree.jstree(true).select_node(selectChildrenNodes); |
124 | 94 | } |
125 | 95 | } |
126 | | - }; |
| 96 | + } |
127 | 97 |
|
128 | | - categoryLoader.createNode = function(config) { |
129 | | - var node; |
130 | | - if (useMassaction && config.is_anchor == isAnchorOnly) { |
131 | | - config.uiProvider = Ext.tree.CheckboxNodeUI; |
132 | | - } |
133 | | - var _node = Object.clone(config); |
134 | | - if (config.children && !config.children.length) { |
135 | | - delete(config.children); |
136 | | - node = new Ext.tree.AsyncTreeNode(config); |
| 98 | + function addLastNodeFlag(treeData) { |
| 99 | + if (treeData.children) { |
| 100 | + treeData.children.forEach(child => addLastNodeFlag(child)); |
137 | 101 | } else { |
138 | | - node = new Ext.tree.TreeNode(config); |
| 102 | + treeData.lastNode = true; |
139 | 103 | } |
140 | | - return node; |
141 | | - }; |
142 | | -
|
143 | | - categoryLoader.buildHash = function(node) |
144 | | - { |
145 | | - var hash = {}; |
146 | | -
|
147 | | - hash = this.toArray(node.attributes); |
148 | | -
|
149 | | - if (node.childNodes.length>0 || (node.loaded==false && node.loading==false)) { |
150 | | - hash['children'] = new Array; |
| 104 | + } |
151 | 105 |
|
152 | | - for (var i = 0, len = node.childNodes.length; i < len; i++) { |
153 | | - if (!hash['children']) { |
154 | | - hash['children'] = new Array; |
155 | | - } |
156 | | - hash['children'].push(this.buildHash(node.childNodes[i])); |
157 | | - } |
| 106 | + function handleSuccessResponse(response, childNode, data) { |
| 107 | + if (response.length > 0) { |
| 108 | + response.forEach(newNode => { |
| 109 | + addLastNodeFlag(newNode); |
| 110 | +
|
| 111 | + // Create the new node and execute node callback |
| 112 | + data.instance.create_node(childNode, newNode, 'last', node => { |
| 113 | + if (useMassAction) { |
| 114 | + if (checkedNodes.includes(node.id)) { |
| 115 | + tree.jstree(true).select_node(node.id); |
| 116 | + } |
| 117 | + getCheckedNodeIds(tree, node); |
| 118 | + actionBasedOnIsAnchorOnly(); |
| 119 | + } |
| 120 | + }); |
| 121 | + }); |
158 | 122 | } |
| 123 | + } |
159 | 124 |
|
160 | | - return hash; |
161 | | - }; |
162 | | -
|
163 | | - categoryLoader.toArray = function(attributes) { |
164 | | - var data = {}; |
165 | | - for (var key in attributes) { |
166 | | - var value = attributes[key]; |
167 | | - data[key] = value; |
| 125 | + function handleOpenNode(e, data) { |
| 126 | + let parentNode = data.node; |
| 127 | +
|
| 128 | + if (parentNode.children.length > 0) { |
| 129 | + let childNode = data.instance.get_node(parentNode.children, false); |
| 130 | +
|
| 131 | + // Check if the child node has no children (is not yet loaded) |
| 132 | + if (childNode.children && childNode.children.length === 0 |
| 133 | + && childNode.original && !childNode.original.lastNode) { |
| 134 | + $.ajax({ |
| 135 | + url: '{$block->escapeJs($block->escapeUrl($block->getLoadTreeUrl()))}', |
| 136 | + data: { |
| 137 | + id: childNode.original.id, |
| 138 | + store: childNode.original.store, |
| 139 | + form_key: FORM_KEY |
| 140 | + }, |
| 141 | + dataType: 'json', |
| 142 | + success: function (response) { |
| 143 | + handleSuccessResponse(response, childNode, data); |
| 144 | + }, |
| 145 | + error: function (jqXHR, status, error) { |
| 146 | + console.log(status + ': ' + error + 'Response text:' + jqXHR.responseText); |
| 147 | + } |
| 148 | + }); |
| 149 | + } |
168 | 150 | } |
| 151 | + } |
169 | 152 |
|
170 | | - return data; |
| 153 | + var jstreeConfig = { |
| 154 | + core: { |
| 155 | + data: addLastNodeProperty({$block->getTreeJson()}), |
| 156 | + check_callback: true |
| 157 | + }, |
| 158 | + plugins: [] |
171 | 159 | }; |
172 | 160 |
|
173 | | - categoryLoader.on("beforeload", function(treeLoader, node) { |
174 | | - treeLoader.baseParams.id = node.attributes.id; |
175 | | - treeLoader.baseParams.store = node.attributes.store; |
176 | | - treeLoader.baseParams.form_key = FORM_KEY; |
177 | | - $('{$block->escapeJs($_divId)}').fire('category:beforeLoad', {treeLoader:treeLoader}); |
178 | | - }); |
179 | | -
|
180 | | - tree{$block->escapeJs($block->getId())} = new Ext.tree.TreePanel.Enhanced('{$block->escapeJs($_divId)}', { |
181 | | - animate: false, |
182 | | - loader: categoryLoader, |
183 | | - enableDD: false, |
184 | | - containerScroll: true, |
185 | | - rootVisible: false, |
186 | | - useAjax: true, |
187 | | - currentNodeId: {$intCategoryId}, |
188 | | - addNodeTo: false |
189 | | - }); |
190 | | -
|
191 | | - if (useMassaction) { |
192 | | - tree{$block->escapeJs($block->getId())}.on('check', function(node) { |
193 | | - $('{$block->escapeJs($_divId)}').fire('node:changed', {node:node}); |
194 | | - }, tree{$block->escapeJs($block->getId())}); |
| 161 | + if (useMassAction) { |
| 162 | + jstreeConfig.plugins.push('checkbox'); |
| 163 | + jstreeConfig.checkbox = { |
| 164 | + three_state: false |
| 165 | + }; |
195 | 166 | } |
196 | 167 |
|
197 | | - // set the root node |
198 | | - var parameters = { |
199 | | - text: 'Psw', |
200 | | - draggable: false, |
201 | | - id: {$intRootId}, |
202 | | - expanded: true, |
203 | | - category_id: {$intCategoryId} |
204 | | - }; |
| 168 | + tree.jstree(jstreeConfig); |
205 | 169 |
|
206 | | - tree{$block->escapeJs($block->getId())}.loadTree({parameters:parameters, data:{$block->getTreeJson()}},true); |
| 170 | + if (useMassAction) { |
| 171 | + tree.on('loaded.jstree', (e, data) => handleLoadedTree(e, data)); |
| 172 | + } |
207 | 173 |
|
| 174 | + tree.on('changed.jstree', (e, data) => handleChange(e, data)); |
| 175 | + tree.on('open_node.jstree', (e, data) => handleOpenNode(e, data)); |
208 | 176 | }); |
209 | 177 |
|
210 | | -}); |
211 | 178 | script; |
212 | 179 | ?> |
| 180 | +<?= /* @noEscape */ $secureRenderer->renderStyleAsTag( |
| 181 | + 'overflow-x: auto;', |
| 182 | + '#tree' . $block->escapeJs($block->getId()) |
| 183 | +); |
| 184 | +?> |
213 | 185 | <?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false); ?> |
0 commit comments