mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Load tree parent nodes if non-existant
This edge case can occur when a large tree is cached in HTML already, without any nodes expanded via ajax. If a new node is added with a parent that's not existant, it was simply placed on the root node. This is a display bug, a full CMS refresh fixes it. Fixes a related bug where tree causes (view) duplicates, where the same node is rendered twice. This was due to the whole subtree being refreshed (including the new node) through jstree's built-in "open"/"select" events, while at the same time creating a new node through updateNodesFromServer() callbacks. Also added a global tree loading indication to make it clear that the tree is still processing. See https://github.com/silverstripe/silverstripe-cms/issues/872
This commit is contained in:
parent
829b45af67
commit
6c96c490c6
@ -847,6 +847,7 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; }
|
|||||||
.cms-tree.multiple li > a > .jstree-icon { display: none; }
|
.cms-tree.multiple li > a > .jstree-icon { display: none; }
|
||||||
.cms-tree.multiple li > a > .jstree-icon.jstree-checkbox { display: inline-block; }
|
.cms-tree.multiple li > a > .jstree-icon.jstree-checkbox { display: inline-block; }
|
||||||
.cms-tree.multiple li#record-0 > a .jstree-checkbox { display: none; }
|
.cms-tree.multiple li#record-0 > a .jstree-checkbox { display: none; }
|
||||||
|
.cms-tree.jstree-loading li#record-0 > .jstree-icon { background: url(../images/throbber.gif) top left no-repeat; }
|
||||||
.cms-tree a.jstree-loading .jstree-icon { background-image: none !important; }
|
.cms-tree a.jstree-loading .jstree-icon { background-image: none !important; }
|
||||||
.cms-tree a.jstree-loading .jstree-pageicon { background: url(../images/throbber.gif) top left no-repeat; }
|
.cms-tree a.jstree-loading .jstree-pageicon { background: url(../images/throbber.gif) top left no-repeat; }
|
||||||
|
|
||||||
|
@ -46,8 +46,7 @@
|
|||||||
.jstree(this.getTreeConfig())
|
.jstree(this.getTreeConfig())
|
||||||
.bind('loaded.jstree', function(e, data) {
|
.bind('loaded.jstree', function(e, data) {
|
||||||
self.setIsLoaded(true);
|
self.setIsLoaded(true);
|
||||||
self.updateFromEditForm();
|
|
||||||
self.css('visibility', 'visible');
|
|
||||||
// Add ajax settings after init period to avoid unnecessary initial ajax load
|
// Add ajax settings after init period to avoid unnecessary initial ajax load
|
||||||
// of existing tree in DOM - see load_node_html()
|
// of existing tree in DOM - see load_node_html()
|
||||||
data.inst._set_settings({'html_data': {'ajax': {
|
data.inst._set_settings({'html_data': {'ajax': {
|
||||||
@ -61,6 +60,9 @@
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
}}});
|
}}});
|
||||||
|
|
||||||
|
self.updateFromEditForm();
|
||||||
|
self.css('visibility', 'visible');
|
||||||
|
|
||||||
// Only show checkboxes with .multiple class
|
// Only show checkboxes with .multiple class
|
||||||
data.inst.hide_checkboxes();
|
data.inst.hide_checkboxes();
|
||||||
@ -239,8 +241,8 @@
|
|||||||
* Creates a new node from the given HTML.
|
* Creates a new node from the given HTML.
|
||||||
* Wrapping around jstree API because we want the flexibility to define
|
* Wrapping around jstree API because we want the flexibility to define
|
||||||
* the node's <li> ourselves. Places the node in the tree
|
* the node's <li> ourselves. Places the node in the tree
|
||||||
* according to data.ParentID
|
* according to data.ParentID.
|
||||||
*
|
*
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* (String) HTML New node content (<li>)
|
* (String) HTML New node content (<li>)
|
||||||
* (Object) Map of additional data, e.g. ParentID
|
* (Object) Map of additional data, e.g. ParentID
|
||||||
@ -248,7 +250,7 @@
|
|||||||
*/
|
*/
|
||||||
createNode: function(html, data, callback) {
|
createNode: function(html, data, callback) {
|
||||||
var self = this,
|
var self = this,
|
||||||
parentNode = data.ParentID ? self.find('li[data-id='+data.ParentID+']') : false,
|
parentNode = data.ParentID ? self.getNodeByID(data.ParentID) : false,
|
||||||
newNode = $(html);
|
newNode = $(html);
|
||||||
|
|
||||||
this.jstree(
|
this.jstree(
|
||||||
@ -281,9 +283,9 @@
|
|||||||
updateNode: function(node, html, data) {
|
updateNode: function(node, html, data) {
|
||||||
var self = this, newNode = $(html), origClasses = node.attr('class');
|
var self = this, newNode = $(html), origClasses = node.attr('class');
|
||||||
|
|
||||||
var nextNode = data.NextID ? this.find('li[data-id='+data.NextID+']') : false;
|
var nextNode = data.NextID ? this.getNodeByID(data.NextID) : false;
|
||||||
var prevNode = data.PrevID ? this.find('li[data-id='+data.PrevID+']') : false;
|
var prevNode = data.PrevID ? this.getNodeByID(data.PrevID) : false;
|
||||||
var parentNode = data.ParentID ? this.find('li[data-id='+data.ParentID+']') : false;
|
var parentNode = data.ParentID ? this.getNodeByID(data.ParentID) : false;
|
||||||
|
|
||||||
// Copy attributes. We can't replace the node completely
|
// Copy attributes. We can't replace the node completely
|
||||||
// without removing or detaching its children nodes.
|
// without removing or detaching its children nodes.
|
||||||
@ -346,8 +348,22 @@
|
|||||||
updateNodesFromServer: function(ids) {
|
updateNodesFromServer: function(ids) {
|
||||||
if(this.getIsUpdatingTree() || !this.getIsLoaded()) return;
|
if(this.getIsUpdatingTree() || !this.getIsLoaded()) return;
|
||||||
|
|
||||||
var self = this, includesNewNode = false;
|
var self = this, i, includesNewNode = false;
|
||||||
this.setIsUpdatingTree(true);
|
this.setIsUpdatingTree(true);
|
||||||
|
self.jstree('save_selected');
|
||||||
|
|
||||||
|
var correctStateFn = function(node) {
|
||||||
|
// Duplicates can be caused by the subtree reloading through
|
||||||
|
// a tree "open"/"select" event, while at the same time creating a new node
|
||||||
|
self.getNodeByID(node.data('id')).not(node).remove();
|
||||||
|
|
||||||
|
self.jstree('deselect_all');
|
||||||
|
self.jstree('select_node', node);
|
||||||
|
// Similar to jstree's correct_state, but doesn't remove children
|
||||||
|
var hasChildren = (node.children('ul').length > 0);
|
||||||
|
node.toggleClass('jstree-leaf', !hasChildren);
|
||||||
|
if(!hasChildren) node.removeClass('jstree-closed jstree-open');
|
||||||
|
};
|
||||||
|
|
||||||
// TODO 'initially_opened' config doesn't apply here
|
// TODO 'initially_opened' config doesn't apply here
|
||||||
self.jstree('open_node', this.getNodeByID(0));
|
self.jstree('open_node', this.getNodeByID(0));
|
||||||
@ -367,15 +383,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var correctStateFn = function(node) {
|
|
||||||
self.jstree('deselect_all');
|
|
||||||
self.jstree('select_node', node);
|
|
||||||
// Similar to jstree's correct_state, but doesn't remove children
|
|
||||||
var hasChildren = (node.children('ul').length > 0);
|
|
||||||
node.toggleClass('jstree-leaf', !hasChildren);
|
|
||||||
if(!hasChildren) node.removeClass('jstree-closed jstree-open');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if node exists, create if necessary
|
// Check if node exists, create if necessary
|
||||||
if(node.length) {
|
if(node.length) {
|
||||||
self.updateNode(node, nodeData.html, nodeData);
|
self.updateNode(node, nodeData.html, nodeData);
|
||||||
@ -384,9 +391,20 @@
|
|||||||
}, 500);
|
}, 500);
|
||||||
} else {
|
} else {
|
||||||
includesNewNode = true;
|
includesNewNode = true;
|
||||||
self.createNode(nodeData.html, nodeData, function(newNode) {
|
|
||||||
correctStateFn(newNode);
|
// If the parent node can't be found, it might have not been loaded yet.
|
||||||
});
|
// This can happen for deep trees which require ajax loading.
|
||||||
|
// Assumes that the new node has been submitted to the server already.
|
||||||
|
if(nodeData.ParentID && !self.find('li[data-id='+nodeData.ParentID+']').length) {
|
||||||
|
self.jstree('load_node', -1, function() {
|
||||||
|
newNode = self.find('li[data-id='+nodeId+']');
|
||||||
|
correctStateFn(newNode);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.createNode(nodeData.html, nodeData, function(newNode) {
|
||||||
|
correctStateFn(newNode);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -399,7 +417,7 @@
|
|||||||
complete: function() {
|
complete: function() {
|
||||||
self.setIsUpdatingTree(false);
|
self.setIsUpdatingTree(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -596,6 +596,12 @@ a .jstree-pageicon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.jstree-loading {
|
||||||
|
li#record-0 > .jstree-icon {
|
||||||
|
background: url(../images/throbber.gif) top left no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Show the loading indicator on the page icon rather than the default
|
// Show the loading indicator on the page icon rather than the default
|
||||||
// jstree icon (which is only used for its dragging handles)
|
// jstree icon (which is only used for its dragging handles)
|
||||||
a.jstree-loading{
|
a.jstree-loading{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user