PDA

View Full Version : Tri-state tree



roger.spall
2 Jun 2011, 11:47 AM
I guess it will take time for the 3.3 extemsions to catch up with ExtJs 4.0.1...

But I would still like to know if anyone can point me to an ExtJS 4 tri-state tree?

Thanks,

Roger

roger.spall
16 Jun 2011, 11:06 AM
I have included my solution below and would appreciate any feedback / suggestions / comments from others...

USAGE:


var permissionsTree = Ext.create('Ext.tree.Panel', {
title : 'Permissions',
region : 'east',
width: '100%',
hideHeaders : true,
rootVisible : false,
animate : false,
store : functionStore,
viewConfig : {
plugins : [ tristate ],
listeners : {
'beforetristate' : confirmPermissionsModified,
'tristate' : permissionsModified
}
},
columns : [ {
xtype : 'tristatetreecolumn',
flex : 2,
sortable : true,
dataIndex : 'name'
} ], buttons:[{text:"Reset", disabled: true,id:'reset', handler: resetPermissions},{text:"Save", disabled: true,id:'save', handler: savePermissions}]
});



EXTENSION IMPLEMENTATION:




Ext.define('tristate.Plugin', {
init : function(view) {
view.updateParent = function(node) {
if (node != null) {
var tristate = -1;
node.eachChild(function(rec) {
var siblingTristrate = rec.get('tristate');
if (tristate == -1) {
tristate = siblingTristrate;
} else {
if (siblingTristrate != tristate) {
tristate = 2;
return false;
}
}
});
if (tristate != -1) {
node.set('tristate', tristate);
}
this.updateParent(node.parentNode);
}
};
view.onCheckboxChange = function(e, t) {
var item = e.getTarget(this.getItemSelector(), this.getTargetEl()), record, value, newValue;
if (item) {
record = this.getRecord(item);
value = record.get('tristate');
newValue = value == 0 ? 1 : 0;
var shouldContinue=this.fireEvent('beforetristate', record, newValue);
if (shouldContinue){
var affectedRecords=[];
record.cascadeBy(function(rec) {
rec.set('tristate', newValue);
affectedRecords[affectedRecords.length]=rec;
});
record.set('tristate', newValue);
this.updateParent(record.parentNode);
this.fireEvent('tristate', record, newValue, affectedRecords);
}
}
};
view.setTristate = function(record, value) {
record.cascadeBy(function(rec) {
rec.set('tristate', value);
});
record.set('tristate', value);
this.updateParent(record.parentNode);
};
view.setAllTristate = function(status) {
this.getStore().each(function(rec) {
rec.cascadeBy(function(rec) {
rec.set('tristate', status);
});
}, true);
};
view.getChecked = function() {
this.getChecked(false);
};
view.getChecked = function(partialMeansChecked) {
var checked = [];
this.node.cascadeBy(function(rec) {
var ts = rec.get('tristate');
if (ts > 0 && (partialMeansChecked || ts == 1)) {
checked.push(rec);
}
});
return checked;
};
view.getCheckedLeafs = function() {
var checked = [];
this.node.cascadeBy(function(rec) {
if (rec.isLeaf() && rec.get('tristate') > 0) {
checked.push(rec);
}
});
return checked;
};
}
});
Ext.define('tristate.Column', {
extend : 'Ext.grid.column.Column',
alias : 'widget.tristatetreecolumn',
initComponent : function() {
var origRenderer = this.renderer || this.defaultRenderer, origScope = this.scope || window;
this.renderer = function(value, metaData, record, rowIdx, colIdx, store, view) {
var buf = [], format = Ext.String.format, depth = record.getDepth(), treePrefix = Ext.baseCSSPrefix + 'tree-', elbowPrefix = treePrefix + 'elbow-', expanderCls = treePrefix + 'expander', imgText = '<img src="{1}" class="{0}" />', checkboxText = '<input type="button" role="checkbox" class="{0}" {1} />', formattedValue = origRenderer.apply(origScope, arguments), href = record
.get('href'), target = record.get('hrefTarget'), cls = record.get('cls');
while (record) {
if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
if (record.getDepth() === depth) {
buf.unshift(format(imgText, treePrefix + 'icon ' + treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) + (record.get('iconCls') || ''), record.get('icon') || Ext.BLANK_IMAGE_URL));
var tristate = record.get('tristate');
if (tristate != null) {
buf.unshift(format(checkboxText, (treePrefix + 'checkbox') + (tristate == 1 ? ' ' + treePrefix + 'checkbox-checked' : (tristate == 2 ? ' ' + treePrefix + 'checkbox-partial-checked' : '')), record.get('checked') ? 'aria-checked="true"' : ''));
if (tristate > 0) {
metaData.tdCls += (' ' + Ext.baseCSSPrefix + 'tree-checked');
}
}
if (record.isLast()) {
if (record.isLeaf() || (record.isLoaded() && !record.hasChildNodes())) {
buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
} else {
buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
}
} else {
if (record.isLeaf() || (record.isLoaded() && !record.hasChildNodes())) {
buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
} else {
buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
}
}
} else {
if (record.isLast() || record.getDepth() === 0) {
buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
} else if (record.getDepth() !== 0) {
buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
}
}
}
record = record.parentNode;
}
if (href) {
formattedValue = format('<a href="{0}" target="{1}">{2}</a>', href, target, formattedValue);
}
if (cls) {
metaData.tdCls += ' ' + cls;
}
return buf.join("") + formattedValue;
};
this.callParent(arguments);
},
defaultRenderer : function(value) {
return value;
}
});

beavx420
16 Jun 2011, 11:39 AM
Roger... can you wrap your post in code tags, so the forums will format it correctly? Thanks!!

morfeusz
17 Jun 2011, 1:49 AM
Can it be used for simply form`s checkBox?

roger.spall
17 Jun 2011, 8:50 AM
No, it is strictly intended for grid trees

koblass
15 Oct 2011, 8:17 AM
Hi,

I've tried your code without any success...
Do you have a running example ?

Best
Daniel

x10
4 Nov 2011, 4:58 AM
Does it work? I have also tried the code without any success.

roger.spall
8 Nov 2011, 4:43 AM
It definitely works and is in production at a client:



Ext.define('common.admin.DealerUserPermissionsTreePanel', {
extend : 'Ext.tree.Panel',
requires : [ "common.TristatePlugin" ],
title : 'Permissions',
width : '100%',
height: '100%',
hideHeaders : true,
rootVisible : false,
constructor : function(config) {
Ext.define('FunctionModel', {
extend : 'Ext.data.Model',
fields : [ 'oid', 'name' ]
});

this.functionStore = Ext.create('Ext.data.TreeStore', {
model : 'FunctionModel',
sorters : [ {
property : 'name',
direction : 'ASC'
} ],
proxy : {
type : 'memory'
},
folderSort : true
});
var tristate = Ext.create('common.TristatePlugin');
this.callParent([ {
store : this.functionStore,
viewConfig : {
plugins : [ tristate ],
listeners : {
'beforetristate' : {
fn : this.confirmPermissionsModified,
scope : this
},
'tristate' : {
fn : this.permissionsModified,
scope : this
}
}
},
columns : [ {
xtype : 'tristatetreecolumn',
flex : 2,
sortable : true,
dataIndex : 'name'
} ],
buttons : [ {
text : "Save",
disabled : true,
scope : this,
id : 'save',
handler : this.savePermissions
},
{
text : "Reset",
disabled : true,
scope : this,
id : 'reset',
handler : this.resetPermissions
}
]

}]);
this.loadFunctions();
},
resetPermissions : function() {
this.loadPermissions(this.userOid,this.userId);
},
savePermissions : function() {
var functions = this.getView().getCheckedLeafs();
var permissions = [];
for ( var i = 0; i < functions.length; i++) {
var oid = functions[i].get("oid");
if (Ext.Array.indexOf(permissions, oid) == -1) {
permissions[permissions.length] = oid;
}
}
Ext.Ajax.request({
url : getDataRequestURL('userPermissions', 'setPermissions'),
params : {
userOid : this.userOid,
permissions : permissions
},
scope : this,
success : this.successfulSave,
failure : this.failedSave
});
},
successfulSave : function(response, request) {
this.setModified(false);
Ext.Msg.alert('Success','Saved');
},
failedSave : function(response, request) {
Ext.Msg.alert('Failed','Failed to save: "' + response.status + " - " + response.statusText + '"');
},
loadPermissions : function(userOid, userId) {
this.userOid = userOid;
this.userId = userId;
Ext.Ajax.request({
url : getDataRequestURL('userPermissions', 'getPermissions'),
params : {
userOid : userOid
},
scope : this,
success : this.setPermissions,
failure : function(response, request) {
Ext.Msg.alert('Failed', response.responseText);
}
});
},
setPermissions : function(response, request) {
if (response.status == 200) {
var security = Ext.decode(response.responseText);
this.setTitle("Permissions for user '" + this.userId+ "'");
this.setCurrentUserPermissions(security.permissions);
this.setModified(false);
} else {
Ext.Msg.alert('Failed','Failed to Set Threads from server response');
}
},
setCurrentUserPermissions : function(permissions) {
var view = this.getView();
view.setAllTristate(0);
permissions.sort();
var root = this.functionStore.getRootNode();
root.cascadeBy(function(rec) {
if (rec.isLeaf()) {
var oid = rec.get("oid");
var pos = Ext.Array.indexOf(permissions, oid);
if (pos > -1) {
rec.set("tristate", 1);
view.updateParent(rec);
}
}
});
},
loadFunctions : function(){
//this.getEl().mask('Loading data...');
Ext.Ajax.request({
url : getDataRequestURL('userPermissions', 'getFunctions'),
scope : this,
success : this.setFunctions
});

},
setFunctions : function(response, request) {
//this.getEl().unmask();
if (response.status == 200) {
var result = Ext.decode(response.responseText);
var root = this.functionStore.getRootNode();
root.removeAll();
for ( var i = 0; i < result.items.length; i++) {
this.buildNode(root, result.items[i]);
}
this.modified = false;
} else {
alert("Failed to Set Functions from server response");
}
},
buildNode : function(root, items) {
if (items[0].length) {
var parent = root.appendChild({
name : items[items.length - 1],
leaf : false,
tristate : 0
});
for ( var i = 0; i < items.length - 1; i++) {
this.buildNode(parent, items[i]);
}
} else {
root.appendChild({
oid : items[0],
name : items[1],
leaf : true,
tristate : 0
});
}
},
confirmPermissionsModified : function(record, value) {
if (!this.userOid){
showError("Must Select a User first");
return false;
}
},
permissionsModified : function(record, value, affected) {
var affectedIds = [];
var affectedOids = [];
for ( var i = 0; i < affected.length; i++) {
var rec = affected[i];
if (rec.isLeaf()) {
affectedIds[affectedIds.length] = rec.id;
affectedOids[affectedOids.length] = rec.get("oid");
}
}
affectedIds.sort();
affectedOids.sort();
var view = this.getView();
this.functionStore.getRootNode().cascadeBy(function(rec) {
if (Ext.Array.indexOf(affectedOids, rec.get("oid")) > -1 && Ext.Array.indexOf(affectedIds, rec.id) == -1) {
view.setTristate(rec, value);
}
});
this.setModified(true);
},
setModified : function(modified) {
this.query("#save")[0].setDisabled(!modified);
this.query("#reset")[0].setDisabled(!modified);
}

});

x10
8 Nov 2011, 5:58 AM
Thanks Roger. It works, but not 'definitely'.
1. I have splitted your 'extension code' from 2nd post into 2 files/classes.
2. I have correctly namespaced them (and registered into my Ext.Loader 'paths' config)
3. I had to define field 'tristate' in my Model of TreeStore. It was not working without it.
4. I have disabled the test 'shouldContinue', because i don't have any listener for 'beforetristate'. IMO the cascade should be always recalculated. (?)
5. Now it is working, but the style for tristate==2 ('checkbox-partial-checked') is never being rendered. I see only standard on/off checkboxes. (?)

? Is your code in 2nd post still valid ?

x10
8 Nov 2011, 6:47 AM
5. Now it is working, but the style for tristate==2 ('checkbox-partial-checked') is never being rendered. I see only standard on/off checkboxes. (?)


SOLVED:


.x-tree-checkbox-partial-checked {
background-position:0 -26px;
}

roger.spall
8 Nov 2011, 10:21 AM
Thanks x10...

I forgot that the only thing definite in life is death and taxes!

Hope you find it useful.

wemerson.januario
12 Feb 2012, 3:53 PM
any working example?

how should be the json?

dedoz
8 Jun 2012, 5:35 AM
cant make this to work D:
added a tristate field to my treeStore model.
Commented the 'shouldContinue' if.
still not working D:

----
nvm this thread is too old :d i tho it was for 4.1 and its for 4.0.1 :D

apastakia
2 Jul 2012, 11:52 AM
Updated and tested for TreePanel
Unable to change style on checkbox mouseover, event object target is always inner div

The TreeStore fields must have 'tristate' included for it to work

Example


var treeStore = Ext.create('Ext.data.TreeStore', {
fields: ['id', 'name', 'type', 'children', 'checked', 'expanded', 'tristate'],
sorters:[{property:'name', direction:'ASC'}],
autoLoad: true,
root: {
expanded: true,
name: "My Root",
children: _stores.getCallCenterList()
},
folderSort: true
});
var tree = Ext.widget({
lines: false,
width:300, height:400,
xtype:'treepanel',
plugins: [tristate],
displayField: 'name',
rootVisible: false,
store: treeStore
})


CSS


.x-tree-checkbox {
background:url('../themes/images/slate/form/checkbox.gif') no-repeat 0 0;
height:13px;
width:13px;
vertical-align:middle;
}
.x-tree-node-over .x-tree-checkbox {
background-position:-13px 0;
}
.x-tree-node-down .x-tree-checkbox {
background-position:-26px 0;
}
.x-tree-node-disabled .x-tree-checkbox {
background-position:-39px 0;
}
.x-tree-node-checked .x-tree-checkbox {
background-position:0 -13px;
}
.x-tree-node-checked-over .x-tree-checkbox {
background-position:-13px -13px;
}
.x-tree-node-checked-down .x-tree-checkbox {
background-position:-26px -13px;
}
.x-tree-node-checked-disabled .x-tree-checkbox {
background-position:-39px -13px;
}
.x-tree-node-grayed .x-tree-checkbox {
background-position:0 -26px;
}
.x-tree-node-grayed-over .x-tree-checkbox {
background-position:-13px -26px;
}
.x-tree-node-grayed-down .x-tree-checkbox {
background-position:-26px -26px;
}
.x-tree-node-grayed-disabled .x-tree-checkbox {
background-position:-39px -26px;
}


TriState Plugin


Ext.define('Ext.tree.plugin.TriState', {
alias: 'plugin.tristate',
init : function(view) {
view.updateParent = function(node) {
if (node != null) {
var tristate = -1;
node.eachChild(function(rec) {
var siblingTristrate = rec.get('tristate');
if (tristate == -1) {
tristate = siblingTristrate;
} else {
if (siblingTristrate != tristate) {
tristate = 2;
return false;
}
}
});
if (tristate != -1) {
node.set('tristate', tristate);
}
node.set('checked', tristate==1);
node.set('cls', (tristate==0 ? 'x-tree-node' : (tristate==1 ? 'x-tree-node-checked' : 'x-tree-node-grayed')));
this.updateParent(node.parentNode);
}
};
view.on({
itemmouseenter: function(view, node, item, idx, e) {
var tristate = node.get('tristate');
if(e.getTarget().tagName.toUpperCase()=='INPUT')
node.set('cls', (tristate==0 ? 'x-tree-node' : (tristate==1 ? 'x-tree-node-checked' : 'x-tree-node-grayed'))+'-over');
else
node.set('cls', (tristate==0 ? 'x-tree-node' : (tristate==1 ? 'x-tree-node-checked' : 'x-tree-node-grayed')));
},
itemmouseleave: function(view, node, item, idx, e) {
var tristate = node.get('tristate');
node.set('cls', (tristate==0 ? 'x-tree-node' : (tristate==1 ? 'x-tree-node-checked' : 'x-tree-node-grayed')));
},
beforeselect: function(view, node) {
return false;
},
itemclick: function(view, node) {
node.set('checked', !node.get('checked'));
this.fireEvent('checkchange', node, node.get('checked'));
},
checkchange: function(node, checked) {
value = node.get('tristate');
newValue = checked ? 1 : 0;
var shouldContinue=this.fireEvent('beforetristate', node, newValue);
if (shouldContinue){
var affectedRecords=[];
node.cascadeBy(function(rec) {
rec.set('checked', checked);
rec.set('tristate', newValue);
rec.set('cls', checked ? 'x-tree-node-checked' : 'x-tree-node');
affectedRecords[affectedRecords.length]=rec;
});
node.set('checked', checked);
node.set('tristate', newValue);
node.set('cls', checked ? 'x-tree-node-checked' : 'x-tree-node');
this.updateParent(node.parentNode);
this.fireEvent('tristate', node, newValue, affectedRecords);
}
}
});
view.setTristate = function(record, value) {
record.cascadeBy(function(rec) {
rec.set('checked', value==1);
rec.set('tristate', value);
});
record.set('checked', value==1);
record.set('tristate', value);
this.updateParent(record.parentNode);
};
view.setAllTristate = function(status) {
this.getStore().each(function(rec) {
rec.cascadeBy(function(rec) {
rec.set('checked', status==1);
rec.set('tristate', status);
});
}, true);
};
view.getChecked = function() {
this.getChecked(false);
};
view.getChecked = function(partialMeansChecked) {
var checked = [];
this.getRootNode().cascadeBy(function(rec) {
var ts = rec.get('tristate');
if (ts > 0 && (partialMeansChecked || ts == 1)) {
checked.push(rec);
}
});
return checked;
};
view.getCheckedLeafs = function() {
var checked = [];
this.getRootNode().cascadeBy(function(rec) {
if (rec.isLeaf() && rec.get('tristate') > 0) {
checked.push(rec);
}
});
return checked;
};
}
});

lfs2008
21 Jun 2013, 3:12 AM
Please find my Ext.ux.grid.TriStateTree here

http://www.sencha.com/forum/showthread.php?266308-Ext.ux.grid.TriStateTree

http://wap7.ru/folio/ext-tri-state-tree/ (http://www.sencha.com/forum/showthread.php?266308-Ext.ux.grid.TriStateTree)