extjs treepanel load children without expand - extjs

I use Extjs 3.4. I am working on TreePanel with checkbox solution.
What I need is: When I check father node, all childNodes also checked. It's easy, but it depends on extjs expand all childNodes.
If I do
tree.expandAll();
tree.collapseAll();
My check strategy will work, but I don't want the expand effect.
My extjs code(checkchange event) is something like the following:
var checkchange = function(node, flag) {
if (node.hasChildNodes()) {
node.cascade(function(node) {
node.attributes.checked = flag;
node.ui.checkbox.checked = flag;
return true;
});
}
var pNode = node.parentNode;
for (; pNode != null; pNode = pNode.parentNode) {
if (flag || tree.getChecked("checked", pNode).length - 1 == 0) {
pNode.attributes.checked = flag;
pNode.ui.checkbox.checked = flag;
}
}
};
var tree = new Ext.tree.TreePanel({
listeners: {
'checkchange': checkchange
},
})
How can I do? Thank every one for giving advice.
[ EDIT ]
I create A Demo In jsfiddle, that can be easily test.
(Since Extjs 3.4.0 cdn used by jsfiddle not work, I append another cdn extjs resource from https://cdnjs.com/libraries/extjs/3.4.1-1)

I am not sure whether you really want the whole tree to be loaded node by node when checking the root node. I would recommend to lazily check the child nodes when they are loaded for an already-checked parent node, by attaching to the load event. Something like this:
load:function(me, node) {
if(node && node.attributes.checked) node.cascade(
... [function to check all children]
)
}
Depending on your intentions for further processing and your tree size, this could be better than expanding the whole tree to render and check all checkboxes.
If you want the tree to be loaded directly, I would recommend to use preloadChildren:true on the TreeLoader. This is easier than a manual implementation of cascaded load.

I didn't really solve this problem. But I got an eclectic solution.
Only expand when needed
checkchange : function(node, flag){
node.cascade(function(node) {
// when you check, first expand, then child nodes can be checked too
if(node.expanded == false)
node.expand(true);
node.attributes.checked = flag;
node.ui.checkbox.checked = flag;
return true;
});
......
}
This will meet the precondition that all child nodes should have expanded. But also no need expanded when first loaded.
If a parent node checked when first loaded, all child nodes need to be expand
this.treeLoader = new Ext.tree.TreeLoader({
url : 'xxx',
baseParams : {
someparam: ""
},
listeners : {
'load' : function(tree,node,response) {
var res = Ext.util.JSON.decode(response.responseText);
if(res.success != undefined && !res.success) {
Ext.Msg.alert('Hint', res.message);
return;
}
node.cascade(function(node) {
if(node.attributes.checked == true) {
node.expand(true);
}
});
},
}
});
This two methods does solve my prolem though not very perfect.
Hope this can help others.

Related

ExtJS 7 - Column filter issue with locked column

I encountered this bug where the column filter is incorrect if the grid has a locked column
Here's the fiddle: sencha fillde
Steps to reproduce:
(Do not apply any filter)
Open the "Email" column menu
Open "Name" column menu (this is the locked column)
Open "Phone" column menu (notice that the filter menu is incorrect, it is showing the filter for "Email" column).
For grid that has no 'locked' columns the filter menu is working fine, thanks for anyone who can help!
Okay, this one was a bit tricky. It turns out that for a locked grid, the Ext.grid.filters.Filters:onMenuCreate gets hit twice... one for each side of the grid's menu that shows. The problem is that in the onMenuCreate, the framework doesn't account for the 2 menus. It only cares about the last menu that gets created and destroys the previous menu's listeners. In onMenuBeforeShow, the framework does account for each side of the grid, so I extended this idea into an override. I would encourage you to create a Sencha Support ticket for this, and if you don't have access, let me know, so I can submit one. Fiddle here.
Ext.override(Ext.grid.filters.Filters, {
onMenuCreate: function (headerCt, menu) {
var me = this;
// TODO: OLD BAD CODE... this would destroy the first menu's listeners
// if (me.headerMenuListeners) {
// Ext.destroy(me.headerMenuListeners);
// me.headerMenuListeners = null;
// }
// me.headerMenuListeners = menu.on({
// beforeshow: me.onMenuBeforeShow,
// destroyable: true,
// scope: me
// });
// Just like in the onMenuBeforeShow, we need to create a hash of our menus
// and their listeners... if we don't, we remove the 1st menu's listeners
// when the 2nd menu is created
if (!me.headerMenuListeners) {
me.headerMenuListeners = {};
}
var parentTableId = headerCt.ownerCt.id;
var menuListener = me.headerMenuListeners[parentTableId];
if (menuListener) {
Ext.destroy(menuListener);
me.headerMenuListeners[parentTableId] = null;
}
me.headerMenuListeners[parentTableId] = menu.on({
beforeshow: me.onMenuBeforeShow,
destroyable: true,
scope: me
});
},
destroy: function () {
var me = this,
filterMenuItem = me.filterMenuItem,
item;
// TODO: ADDING THIS AND REMOVING FROM THE Ext.destroy on the next line
var headerMenuListeners = this.headerMenuListeners;
Ext.destroy(me.headerCtListeners, me.gridListeners);
me.bindStore(null);
me.sep = Ext.destroy(me.sep);
for (item in filterMenuItem) {
filterMenuItem[item].destroy();
}
// TODO: ADDING THIS AND REMOVING FROM THE Ext.destroy on the next line
for (item in headerMenuListeners) {
headerMenuListeners[item].destroy();
}
this.callParent();
}
});

Change the way JSTree identifies leaf nodes

I want to change the way JSTree identifies leaf nodes. Today, it checks for children property of the node to see if it is boolean true or an array with at least one child node. Reference
I want to create a node property called isLeaf so that jsTree would understand that the node is openable for AJAX loading of the subtree.
I tried to override default is_parent method, like this:
$.jstree.core.prototype.is_parent = function(node) {
return !node.original.isLeaf;
};
But for some reason this method is not called for all tree nodes.
I am using ngJsTree
Does anyone know how can I achieve this behavior?
I managed to deal with it by iterating over AJAX response and changing every node without children to children = true.
success : function(nodeRoot) {
var validateChildrenArray = function(node) {
if (!node.children || node.children.length === 0) {
node.children = true;
}
else {
for (var i = 0; i < node.children.length; i++) {
validateChildrenArray(node.children[i]);
}
}
};
for (var i = 0; i < nodeRoot.length; i++) {
validateChildrenArray(nodeRoot[i]);
}
}
Waiting for a better solution...

CheckAll/UncheckAll issue with Subscribe ? Knockout

I been trying to do checkbox Checkall and UnCheckall using subscribe and i'm partially successful doing that but i am unable to find a fix in couple of scenarios when i am dealing with subscribe .
Using subscribe :
I am here able to checkAll uncheckAll but when i uncheck a child checkbox i.e test1 or test2 i need my parent checkbox name also to be unchecked and in next turn if i check test1 the parent checkbox should be checked i.e keeping condition both child checkboxes are checked .
For fiddle : Click Here
ViewModel :
self.selectedAllBox.subscribe(function (newValue) {
if (newValue == true) {
ko.utils.arrayForEach(self.People(), function (item) {
item.sel(true);
});
} else {
ko.utils.arrayForEach(self.People(), function (item) {
item.sel(false);
});
}
});
The same scenario can be done perfectly in easy way using computed but due some performance issues i need to use subscribe which is best way it wont fire like computed onload .
Reference : Using computed same thing is done perfectly check this Fiddle
I tried to use change event in individual checkbox binding but its a dead end till now.
Any help is appreciated .
Your subscription only applies to edits on the selectedAllBox. To do what you want, you'll need subscriptions on every Person checkbox as well, to check for the right conditions and uncheck the selectedAllBox in the right situations there.
It strikes me as odd that this would be acceptable but using computed() is not. Maybe you should reconsider that part of your answer. I would much rather compute a "isAllSelected" value based on my viewModel state, then bind the selectedAllBox to that.
I solved a similar problem in my own application a couple of years ago using manual subscriptions. Although the computed observable method is concise and easy to understand, it suffers from poor performance when there's a large number of items. Hopefully the code below speaks for itself:
function unsetCount(array, propName) {
// When an item is added to the array, set up a manual subscription
function addItem(item) {
var previousValue = !!item[propName]();
item[propName]._unsetSubscription = item[propName].subscribe(function (latestValue) {
latestValue = !!latestValue;
if (latestValue !== previousValue) {
previousValue = latestValue;
unsetCount(unsetCount() + (latestValue ? -1 : 1));
}
});
return previousValue;
}
// When an item is removed from the array, dispose the subscription
function removeItem(item) {
item[propName]._unsetSubscription.dispose();
return !!item[propName]();
}
// Initialize
var tempUnsetCount = 0;
ko.utils.arrayForEach(array(), function (item) {
if (!addItem(item)) {
tempUnsetCount++;
}
});
var unsetCount = ko.observable(tempUnsetCount);
// Subscribe to array changes
array.subscribe(function (changes) {
var tempUnsetCount = unsetCount();
ko.utils.arrayForEach(changes, function (change) {
if (change.moved === undefined) {
if (change.status === 'added') {
if (!addItem(change.value))
tempUnsetCount++;
} else {
if (!removeItem(change.value))
tempUnsetCount--;
}
}
});
unsetCount(tempUnsetCount);
}, null, 'arrayChange');
return unsetCount;
}
You'll still use a computed observable in your viewmodel for the the select-all value, but now it'll only need to check the unselected count:
self.unselectedPeopleCount = unsetCount(self.People, 'Selected');
self.SelectAll = ko.pureComputed({
read: function() {
return self.People().length && self.unselectedPeopleCount() === 0;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(value);
});
}
}).extend({rateLimit:0});
Example: http://jsfiddle.net/mbest/dwnv81j0/
The computed approach is the right way to do this. You can improve some performance issues by using pureComputed and by using rateLimit. Both require more recent versions of Knockout than the 2.2.1 used in your example (3.2 and 3.1, respectively).
self.SelectAll = ko.pureComputed({
read: function() {
var item = ko.utils.arrayFirst(self.People(), function(item) {
return !item.Selected();
});
return item == null;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(value);
});
}
}).extend({rateLimit:1});
http://jsfiddle.net/mbest/AneL9/98/

Primefaces tree checkbox single selection

I'm need primefaces tree in "checkbox" selection mode, but only one and only one node can be selected at a time. By default, primefaces tree selects all descendants when a node is selected and that is actually what I want to change.
Can anybody help me figure it out, please?
I finally found a way to realize this by looking at the Javascript source code of the tree. You can e.g. create a singleton that caches the previously selected node. By using the "onNodeClick" attribute of the tree you can call a Javascript function that unselects the previous node before the widget internally sets the new selected node (and potentially posts the new selection when using ajax events).
Update:
I modified the Facelet and the Javascript to search the tree for a preselected node on initialization. Be aware that the preselected node has to be visible to make it work correctly. See this answer for details about expanding the parent nodes.
Bean:
#Named
#ViewScoped
public class BackingBean implements Serializable {
private TreeNode rootTreeNode;
// IMPORTANT: Use array!
private TreeNode[] selected;
public TreeNode getRootTreeNode() {
if (rootTreeNode == null) {
rootTreeNode = new DefaultTreeNode("Root", null);
// init child tree nodes
}
return rootTreeNode;
}
public TreeNode[] getSelected() {
return selected;
}
public void setSelected(TreeNode[] selected) {
this.selected = selected;
}
}
Facelet:
<p:tree id="tree"
onNodeClick="TREE_SELECTION.updateSelection(node, event)"
propagateSelectionDown="false" propagateSelectionUp="false"
selection="#{backingBean.selected}" selectionMode="checkbox"
value="#{backingBean.rootTreeNode}"
var="data"
widgetVar="treeWidget">
<p:treeNode>
<h:outputText value="#{dataWrapper.label}" />
</p:treeNode>
</p:tree>
Javascript:
<script type="text/javascript">
// singleton for tree selection
var TREE_SELECTION = {
init: function (treeWidgetVar) {
this.treeWidget = PF(treeWidgetVar);
this.selectedNode = this.treeWidget.jq.find('.ui-treenode-selected');
},
treeWidget: null,
selectedNode: null,
updateSelection: function (node, event) {
// get the checkbox state of the clicked node
var checkbox = node.find('> .ui-treenode-content > .ui-chkbox'),
checked = checkbox.find('> .ui-chkbox-box > .ui-chkbox-icon').hasClass('ui-icon-check');
if (checked) {
// checkbox is going to be unchecked
this.selectedNode = null;
return;
}
// check for previously selected node
if (this.selectedNode !== null) {
// get the checkbox of the previously selected node
checkbox = this.selectedNode.find('> .ui-treenode-content > .ui-chkbox');
// update tree
this.treeWidget.uncheck(checkbox);
this.treeWidget.removeFromSelection(this.treeWidget.getRowKey(this.selectedNode));
}
// cache selected node
this.selectedNode = node;
}
};
// initialize singleton when document is loaded
$(function () {
TREE_SELECTION.init('treeWidget');
});
</script>

Expand node not calling callback

I am testing my ExtJs App with Jasmine. So there are no views involved.
I want to expand several nodes, but first my root node. But even expanding that node fails.
I create my TreeStore, then:
Code:
// oStore is my treestore, everything OK
// Here, I also tried calling oStore.load(); see below
var oNode = oStore.getNodeById( 'root' );
// oNode.isExpanded() says false
// oNode.hasChildNodes() says false
oNode.expand(false, function(oChildren) {
// it never gets here
console.log( "hello?");
});
My listener for load gets called, but the listener for beforeexpand does not get called.
If I call oStore.load() at the beginning after store creation, then when the on load listener gets called it says, oNode.childNodes.length has 3 children...
So why is expand()'s callback not called? How can I get that node to expand?
Why are the children of the node already loaded when calling oStore.load()? After that expand() is also not fired.
EDIT:
Starting my root node with expanded: true makes my store collapse correctly on collapse(), but after it expand() does nothing.
oNode.collapse(false, function() {
console.log( "collapse()" ); // works, but expand() afterwards does not
});
Found the solution. I need to wait for the store to get loaded. Is is a Jasmine issue:
runs( function() {
oStore.on( 'load', function() {
bStoreLoaded = true;
});
});
waitsFor( function() {
return bStoreLoaded;
}, " store to be loaded", 5000 );
runs( function() {
oNode.expand(false, function( aoChildren ) {
aoChildrenRoot = aoChildren;
bRootExpanded = true;
});
});
There is some convoluted logic in the source to tell the node whether or not it needs to expand or be expandable. Take a look at the code below that I ended up resorting to when I tried to manipulate the tree programmatically:
if (!parentNode.isExpanded() && !parentNode.isLeaf()) {
parentNode.expand(false, function () {// deal with open or closed paths
console.log('expanded parent to pick up new item');
});
} else {
parentNode.callStore('suspendAutoSync');
if (parentNode.isLeaf()) { // Parts that are leafs
parentNode.set("leaf", false); //must be set to work properly
parentNode.appendChild(newPart);
parentNode.expand(); //expand to show newly created child
} else {
parentNode.insertChild(0, newPart);
}
parentNode.callStore('resumeAutoSync');
}

Resources