Skip to content

Commit a0d39c5

Browse files
committed
AC-8379: Migrate from extjs to jstree widget layout functionality
1 parent 824661b commit a0d39c5

File tree

2 files changed

+134
-182
lines changed
  • app/code/Magento
    • Catalog/view/adminhtml/templates/catalog/category/widget
    • Widget/view/adminhtml/templates/instance/edit

2 files changed

+134
-182
lines changed

app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml

Lines changed: 133 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -10,204 +10,178 @@
1010
<?php $_divId = 'tree' . $block->getId() ?>
1111
<div id="<?= $block->escapeHtmlAttr($_divId) ?>" class="tree"></div>
1212
<?php
13-
$isUseMassaction = $block->getUseMassaction() ? 1 : 0;
13+
$isUseMassAction = $block->getUseMassaction() ? 1 : 0;
1414
$isAnchorOnly = $block->getIsAnchorOnly() ? 1 : 0;
1515
$intCategoryId = (int)$block->getCategoryId();
1616
$intRootId = (int) $block->getRoot()->getId();
1717
$scriptString = <<<script
1818
19-
require(['jquery', 'prototype', 'extjs/ext-tree-checkbox'], function(jQuery){
19+
require(['jquery', 'jquery/jstree/jquery.jstree'], function($) {
2020
21-
var tree{$block->escapeJs($block->getId())};
21+
let tree = $('#tree{$block->escapeJs($block->getId())}');
22+
let useMassAction = {$isUseMassAction};
23+
let isAnchorOnly = {$isAnchorOnly};
24+
let checkedNodes = [];
2225
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'];
26+
function addLastNodeProperty(nodes) {
27+
return nodes.map(node => {
28+
return node.children
29+
? { ...node, children: addLastNodeProperty(node.children) }
30+
: { ...node, lastNode: true };
31+
});
32+
}
3833
39-
if ((typeof parameters['root_visible']) != 'undefined') {
40-
this.rootVisible = parameters['root_visible']*1;
41-
}
34+
function actionBasedOnIsAnchorOnly() {
35+
tree.jstree().get_json('#', { flat: true }).each((node, value) => {
36+
const attrId = node.a_attr.id;
37+
const rootNode = tree.jstree().get_node("#");
38+
const rootId = rootNode.children[0];
4239
43-
var root = new Ext.tree.TreeNode(parameters);
40+
if (isAnchorOnly === 1 && node.id === rootId) {
41+
tree.jstree(true).disable_node(node);
42+
} else if (isAnchorOnly === 0 && node.id !== rootId) {
43+
tree.jstree(true).disable_node(node);
44+
}
45+
});
46+
}
4447
45-
this.nodeHash = {};
46-
this.setRootNode(root);
48+
function handleLoadedTree(e, data) {
49+
const container = $(e.target).closest('div.chooser_container');
50+
checkedNodes = container.find('input[type="text"].entities').val().split(',').map(item => item.trim());
4751
48-
if (firstLoad) {
52+
data.instance.get_json('#', { flat: true }).forEach(nodeId => {
53+
const node = data.instance.get_node(nodeId);
4954
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-
}
55+
if (checkedNodes.includes(node.id)) {
56+
tree.jstree(true).select_node(node.id);
57+
}
5758
58-
this.loader.buildCategoryTree(root, data);
59-
this.el.dom.innerHTML = '';
60-
// render the tree
61-
this.render();
59+
actionBasedOnIsAnchorOnly();
60+
});
6261
}
63-
});
64-
65-
jQuery(function()
66-
{
67-
68-
script;
69-
$scriptString .= 'var emptyNodeAdded = ' . ($block->getWithEmptyNode() ? 'false' : 'true') . ';' . PHP_EOL;
7062
71-
$scriptString .= <<<script
63+
function handleChange(e, data) {
64+
if (data.action === 'ready') {
65+
return;
66+
}
7267
73-
var categoryLoader = new Ext.tree.TreeLoader({
74-
dataUrl: '{$block->escapeJs($block->escapeUrl($block->getLoadTreeUrl()))}'
75-
});
68+
if (useMassAction) {
69+
const clickedNodeID = data.node.id;
70+
const currentCheckedNodes = data.instance.get_checked();
7671
77-
categoryLoader.processResponse = function (response, parent, callback) {
78-
var config = JSON.parse(response.responseText);
72+
if (data.action === 'select_node' && !checkedNodes.includes(clickedNodeID)) {
73+
checkedNodes = currentCheckedNodes;
74+
} else if (data.action === 'deselect_node') {
75+
checkedNodes = currentCheckedNodes.filter(nodeID => nodeID !== clickedNodeID);
76+
}
7977
80-
this.buildCategoryTree(parent, config);
78+
checkedNodes.sort((a, b) => a - b);
8179
82-
if (typeof callback == "function") {
83-
callback(this, parent);
80+
const container = $(e.target).closest('div.chooser_container');
81+
container.find('input[type="text"].entities').val(checkedNodes.join(', '));
82+
} else {
83+
node = data.node;
84+
node.attributes = node.original;
85+
const nodeClickListener = {$block->getNodeClickListener()};
86+
nodeClickListener(node);
8487
}
85-
};
88+
}
8689
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-
}
90+
function getCheckedNodeIds(tree, node) {
91+
if (node.children_d && node.children_d.length > 0) {
92+
const selectChildrenNodes = node.children_d.filter(item => checkedNodes.includes(item));
93+
94+
if (selectChildrenNodes.length > 0) {
95+
tree.jstree(true).select_node(selectChildrenNodes);
12496
}
12597
}
126-
};
98+
}
12799
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);
100+
function addLastNodeFlag(treeData) {
101+
if (treeData.children) {
102+
treeData.children.forEach(child => addLastNodeFlag(child));
137103
} else {
138-
node = new Ext.tree.TreeNode(config);
104+
treeData.lastNode = true;
139105
}
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;
106+
}
151107
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-
}
108+
function handleSuccessResponse(response, childNode, data) {
109+
if (response.length > 0) {
110+
response.forEach(newNode => {
111+
addLastNodeFlag(newNode);
112+
113+
// Create the new node and execute node callback
114+
data.instance.create_node(childNode, newNode, 'last', node => {
115+
if (useMassAction) {
116+
if (checkedNodes.includes(node.id)) {
117+
tree.jstree(true).select_node(node.id);
118+
}
119+
getCheckedNodeIds(tree, node);
120+
actionBasedOnIsAnchorOnly();
121+
}
122+
});
123+
});
158124
}
125+
}
159126
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;
127+
function handleOpenNode(e, data) {
128+
let parentNode = data.node;
129+
130+
if (parentNode.children.length > 0) {
131+
let childNode = data.instance.get_node(parentNode.children, false);
132+
133+
// Check if the child node has no children (is not yet loaded)
134+
if (childNode.children && childNode.children.length === 0
135+
&& childNode.original && !childNode.original.lastNode) {
136+
$.ajax({
137+
url: '{$block->escapeJs($block->escapeUrl($block->getLoadTreeUrl()))}',
138+
data: {
139+
id: childNode.original.id,
140+
store: childNode.original.store,
141+
form_key: FORM_KEY
142+
},
143+
dataType: 'json',
144+
success: function (response) {
145+
handleSuccessResponse(response, childNode, data);
146+
},
147+
error: function (jqXHR, status, error) {
148+
console.log(status + ': ' + error + 'Response text:' + jqXHR.responseText);
149+
}
150+
});
151+
}
168152
}
153+
}
169154
170-
return data;
155+
var jstreeConfig = {
156+
core: {
157+
data: addLastNodeProperty({$block->getTreeJson()}),
158+
check_callback: true
159+
},
160+
plugins: []
171161
};
172162
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())});
163+
if (useMassAction) {
164+
jstreeConfig.plugins.push('checkbox');
165+
jstreeConfig.checkbox = {
166+
three_state: false
167+
};
195168
}
196169
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-
};
170+
tree.jstree(jstreeConfig);
205171
206-
tree{$block->escapeJs($block->getId())}.loadTree({parameters:parameters, data:{$block->getTreeJson()}},true);
172+
if (useMassAction) {
173+
tree.on('loaded.jstree', (e, data) => handleLoadedTree(e, data));
174+
}
207175
176+
tree.on('changed.jstree', (e, data) => handleChange(e, data));
177+
tree.on('open_node.jstree', (e, data) => handleOpenNode(e, data));
208178
});
209179
210-
});
211180
script;
212181
?>
182+
<?= /* @noEscape */ $secureRenderer->renderStyleAsTag(
183+
'overflow-x: auto;',
184+
'#tree' . $block->escapeJs($block->getId())
185+
);
186+
?>
213187
<?= /* @noEscape */ $secureRenderer->renderTag('script', [], $scriptString, false); ?>

app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ require([
2727
'mage/template',
2828
'Magento_Ui/js/modal/alert',
2929
"prototype",
30-
"extjs/ext-tree-checkbox"
30+
"jquery/jstree/jquery.jstree"
3131
], function (jQuery, mageTemplate, alert) {
3232
3333
//<![CDATA[
@@ -486,25 +486,6 @@ var WidgetInstance = {
486486
entitiesElm.value = this.selectedItems[selectionId].keys().join(',');
487487
}
488488
},
489-
checkCategory : function(event) {
490-
node = event.memo.node;
491-
container = event.target.up('div.chooser_container');
492-
value = container.down('input[type="text"].entities').value.strip();
493-
if (node.attributes.checked) {
494-
if (value) ids = value.split(',');
495-
else ids = [];
496-
if (-1 == ids.indexOf(node.id)) {
497-
ids.push(node.id);
498-
container.down('input[type="text"].entities').value = ids.join(',');
499-
}
500-
} else {
501-
ids = value.split(',');
502-
while (-1 != ids.indexOf(node.id)) {
503-
ids.splice(ids.indexOf(node.id), 1);
504-
container.down('input[type="text"].entities').value = ids.join(',');
505-
}
506-
}
507-
},
508489
togglePageGroupChooser : function(element) {
509490
element = $(element);
510491
if (element && (chooser = element.up('div.group_container').down('div.chooser_container'))) {
@@ -584,9 +565,6 @@ $scriptString .= <<<script
584565
Event.observe(document, 'product:changed', function(event){
585566
WidgetInstance.checkProduct(event);
586567
});
587-
Event.observe(document, 'node:changed', function(event){
588-
WidgetInstance.checkCategory(event);
589-
});
590568
Event.observe(document, 'category:beforeLoad', function(event) {
591569
container = event.target.up('div.chooser_container');
592570
value = container.down('input[type="text"].entities').value.strip();

0 commit comments

Comments
 (0)