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...
Related
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.
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/
Is there a way to force angular to re-render things bound to a property, even though the property has not changed value? e.g:
$scope.size = 1;
<div some-prop="size"></div>
$scope.$needsRender('size') // psuedocode
$scope.$apply(); // re-renders the <div>
Unfortunately I can't manage the property entirely in angular for performance reasons, which is why I need this "reset".
Angular DOM beeing manipulated outside of angular is ugly. Well to say the truth, it's more than that. People doing this probably deserves to die. slowly. Painfully.
But anyway yes, it is possible.
Short answer : You can do this be forcing the execution of a scope's watcher to fire.
module.factory("scopeUtils", function($parse) {
var scopeUtils = {
/**
* Apply watchers of given scope even if a digest progress is already in process on another level.
* This will only do a one-time cycle of watchers, without cascade digest.
*
* Please note that this is (almost) a hack, behaviour may be hazardous so please use with caution.
*
* #param {Scope} scope : scope to apply watchers from.
*/
applyWatchers : function(scope) {
scopeUtils.traverseScopeTree(scope, function(scope) {
var watchers = scope.$$watchers;
if(!watchers) {
return;
}
var watcher;
for(var i=0; i<watchers.length; i++) {
watcher = watchers[i];
var value = watcher.get(scope);
watcher.fn(value, value, scope);
}
});
},
traverseScopeTree : function(parentScope, traverseFn) {
var next,
current = parentScope,
target = parentScope;
do {
traverseFn(current);
if (!(next = (current.$$childHead ||
(current !== target && current.$$nextSibling)))) {
while(current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
} while((current = next));
}
};
return scopeUtils;
});
Use it simply like that :
scopeUtils.applyWatchers(myScope);
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>
I have the issue described at http://wiki.apache.org/myfaces/Facelets_with_Trinidad. More specifically:
"There is an issue in the id generation for components when a PPR is executed. The symptom is that a click on an command does not execute the desired action but only reloads the whole page. Any subsequent click on any command succeeds.
To work around this issue manually set the id's for at least all commands on the affected pages.".
I already tried the above method but the problem keeps arising. Does anybody have any solution to this?
Finally, I solved the above problem by calling the function below on load page.
// Override function to solve ppr problems
function overrideFunc() {
TrPage.prototype._updateViewState = function(a59, a60, a61) {
var a62 = null;
if (a61)
a62 = a59.getElementById(a61);
for ( var i = 0; i < a59.forms.length; i++) {
a62 = a59.forms[i];
if (!a62)
return;
var a63 = a62.elements[TrPage._VIEW_STATE_ID];
if (!a63) {
a63 = a59.createElement("input");
a63.type = 'hidden';
if(_agent.isIE && _agent.version < 8) {
a63.id = TrPage._VIEW_STATE_ID;
}
a63.name = TrPage._VIEW_STATE_ID;
a62.appendChild(a63);
}
a63.value = TrPage._getTextContent(a60);
}
};
}