PDA

View Full Version : TreePanel / TreeStore sync method after drag/drop operation.



lotsofquestions
31 Oct 2011, 10:00 AM
We are implementing server side functionality for extjs trees. The tree is displayed using a Ext.tree.Panel and a Ext.data.TreeStore store. We are using drag and drop functionality by specifying
viewConfig: {
plugins: {
ptype: 'treeviewdragdrop'
}
}
in the panel configuration. After a drag and drop we call the sync method on the store. The messages that are sent to the server only describe the new index of the node moved, not all the indexs of the nodes changed by the moving node. Is this the correct behaviour of extjs or can we persuade it to tell us the new index's of all the nodes that have changed?

SamuraiJack1
31 Oct 2011, 10:05 AM
Indeed, it seems that "index" is a persistent field and maintaining it in correct state is part of the NodeInterface's contract. We had exactly the same problem and come up with this override:


(function() {
var original = Ext.data.NodeInterface.getPrototypeBody;

Ext.data.NodeInterface.getPrototypeBody = function() {
var retVal = original.apply(Ext.data.NodeInterface, arguments);

Ext.apply(retVal, {
updateInfo: function(silent) {
var me = this,
isRoot = me.isRoot(),
parentNode = me.parentNode,
isFirst = (!parentNode ? true : parentNode.firstChild == me),
isLast = (!parentNode ? true : parentNode.lastChild == me),
depth = 0,
parent = me,
children = me.childNodes,
len = children.length,
i = 0;

while (parent.parentNode) {
++depth;
parent = parent.parentNode;
}

me.beginEdit();
me.set({
isFirst: isFirst,
isLast: isLast,
depth: depth,
index: parentNode ? parentNode.indexOf(me) : 0,
parentId: parentNode ? parentNode.getId() : null
});
me.endEdit(silent);
if (silent) {
me.commit();
}

for (i = 0; i < len; i++) {
children[i].updateInfo(silent);
}

// update the `index` property of the siblings in the imperative style for performance reasons
var currentNode = me;
var nextSibling = currentNode.nextSibling;

while (nextSibling && nextSibling.get('index') !== currentNode.get('index') + 1) {

nextSibling.beginEdit();
nextSibling.set("index", currentNode.get('index') + 1);
nextSibling.endEdit(silent);

if (silent) {
nextSibling.commit();
}

currentNode = nextSibling;
nextSibling = currentNode.nextSibling;
}
},

/**
* Sorts this nodes children using the supplied sort function.
* @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
* @param {Boolean} recursive Whether or not to apply this sort recursively
* @param {Boolean} suppressEvent Set to true to not fire a sort event.
*/
sort : function(sortFn, recursive, suppressEvent) {
var cs = this.childNodes,
ln = cs.length,
i, n;

if (ln > 0) {
Ext.Array.sort(cs, sortFn);
for (i = 0; i < ln; i++) {
n = cs[i];
n.previousSibling = cs[i-1];
n.nextSibling = cs[i+1];
}

for (i = 0; i < ln; i++) {
n = cs[i];

if (i === 0) {
this.setFirstChild(n);
n.updateInfo();
}
if (i == ln - 1) {
this.setLastChild(n);
n.updateInfo();
}
if (recursive && !n.isLeaf()) {
n.sort(sortFn, true, true);
}
}

if (suppressEvent !== true) {
this.fireEvent('sort', this, cs);
}
}
}
});

return retVal;
};
})();

mszukajt
2 Nov 2011, 5:55 AM
We are implementing server side functionality for extjs trees. The tree is displayed using a Ext.tree.Panel and a Ext.data.TreeStore store. We are using drag and drop functionality by specifying
viewConfig: {
plugins: {
ptype: 'treeviewdragdrop'
}
}
in the panel configuration. After a drag and drop we call the sync method on the store. The messages that are sent to the server only describe the new index of the node moved, not all the indexs of the nodes changed by the moving node. Is this the correct behaviour of extjs or can we persuade it to tell us the new index's of all the nodes that have changed?


You can try to define "index" field in your model (as persistent) and then any change on this field will mark record as dirty and will be considered by sync(). NodeInterface has plenty of those "technical" fields which are not persistent in many cases and which is not well documented:


{name: idName, type: 'string', defaultValue: null},
{name: 'parentId', type: 'string', defaultValue: null},
{name: 'index', type: 'int', defaultValue: null},
{name: 'depth', type: 'int', defaultValue: 0},
{name: 'expanded', type: 'bool', defaultValue: false, persist: false},
{name: 'expandable', type: 'bool', defaultValue: true, persist: false},
{name: 'checked', type: 'auto', defaultValue: null},
{name: 'leaf', type: 'bool', defaultValue: false, persist: false},
{name: 'cls', type: 'string', defaultValue: null, persist: false},
{name: 'iconCls', type: 'string', defaultValue: null, persist: false},
{name: 'icon', type: 'string', defaultValue: null, persist: false},
{name: 'root', type: 'boolean', defaultValue: false, persist: false},
{name: 'isLast', type: 'boolean', defaultValue: false, persist: false},
{name: 'isFirst', type: 'boolean', defaultValue: false, persist: false},
{name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false},
{name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false},
{name: 'loaded', type: 'boolean', defaultValue: false, persist: false},
{name: 'loading', type: 'boolean', defaultValue: false, persist: false},
{name: 'href', type: 'string', defaultValue: null, persist: false},
{name: 'hrefTarget', type: 'string', defaultValue: null, persist: false},
{name: 'qtip', type: 'string', defaultValue: null, persist: false},
{name: 'qtitle', type: 'string', defaultValue: null, persist: false}


But you can use any of them in your model and NodeInterface will not overwrite your settings - but still such fields MUST be used in meaning of NodeInterface. It is quite well designed and gives you a lot of flexiblity of using tree nodes as you can return from remote side what is the leaf and what is not, block drag or drop for specific node or use some custom icon on each node.
It is quite well but not ideal. This doesn't allow to name those fields with developer's needs and there can be a names collision especially when you have some legacy model in DB.




And still there is a bug I posted here:
http://www.sencha.com/forum/showthread.php?152612-TreeStore-looses-changes-after-nodes-movement-in-the-tree
which "clears" many of your changes done by node movement before you synchronized it.


EDIT:
I did not notice it. It looks like "index" IS persistent field and you should get all of those records you want. I mark this field in my model as not persistent in my case to not get such kind of changes as I do not need them.




After some checking it looks like "index" field is not managed correctly. I have a luck "depth" field is ok as I use it.
And "clearing" bug "works" - you do not see all your changes after some node movements.

OliverColeman
22 Jan 2012, 4:33 PM
Thanks for the code example SamuraiJack1. I tried copying and pasting it into the init() function of a controller, minus the
(function() {
line at the top and the corresponding last line, but it didn't seem to work (the indexes of the next siblings didn't get updated). I tested it using the insertChild function of NodeInterface. Can you tell me what I may have done wrong (eg would removing the first and last line break it, or putting in the init() function for a Controller)? I have put other overrides (just using Ext.override directly) in the same init() function and they work.

On a side note, I've posted a new issue with a title explicitly about indexes of siblings not being updated: http://www.sencha.com/forum/showthread.php?176032-index-not-updated-on-siblings-when-adding-nodes-to-NodeInterface-Model-in-TreeStore&p=719178

SamuraiJack1
22 Jan 2012, 11:29 PM
@OliverColeman Try to put the override before of any Model declarations for tree.