0
votes

I am using ExtJs tree panel. Is there any in-build method or property which will deselect all the child nodes of a node when you select that node.

So consider the below image, assuming the nodes in yellow back color is already selected. If I select 1.1 now, system should automatically deselect 1.1.2 & if I selected node 1, it should deselect 1.1.2, .1.2.1, 1.2.2.

Please provide your suggestions

enter image description here

Ext.create('Ext.tree.Panel', {
    title: 'Simple Tree',
    renderTo: Ext.getBody(),
    width: 400,
    height: 400,

    store: {
        root: {
            expanded: true,
            children: [{
                text: "1 detention",
                expanded: true,
                "checked": false,
                children: [{
                    text: '1.1 foo',
                    leaf: true,
                    "checked": false
                }, {
                    text: '1.2 bar',
                    leaf: true,
                    "checked": false
                }]
            }, {
                text: "2 homework",
                expanded: true,
                "checked": false,
                children: [{
                    text: "2.1 book report",
                    leaf: true,
                    "checked": false
                }, {
                    text: "2.2 algebra",
                    expanded: true,
                    "checked": false,
                    children: [{
                        text: "2.2.1 buy lottery tickets",
                        leaf: true,
                        "checked": false
                    }, {
                        text: "2.2.2 buy lottery tickets 2",
                        leaf: true,
                        "checked": false
                    }]
                }]
            }, {
                text: "3 buy lottery tickets",
                leaf: true,
                "checked": false
            }]
        }
    },
    useArrows: false,
    rootVisible: false,
    selModel: {
        mode: 'SIMPLE'
    },
    listeners: {
        deselect: function (tree, record) {
            if (record.data.text === '1 detention') {

            }
        },
        select: function (tree, record) {
            var parentNode = record.parentNode;

            // Deselect children
            function deselectChildren(record) {
                tree.deselect(record.childNodes, false);
                record.eachChild(deselectChildren);
            }
            deselectChildren(record);

            // See if all siblings are selected now
            var allSiblingSelected = false;
            if (parentNode) {
                allSiblingSelected = parentNode.childNodes.reduce(function (previous, node) {
                    return previous && tree.isSelected(node)
                }, true);
            }

            if (allSiblingSelected) {
                tree.select(parentNode, true); // will trigger req 1
            }

            // Deselect ancestors
            else {
                while (parentNode) {
                    tree.deselect(parentNode);
                    parentNode = parentNode.parentNode;
                }
            }
        }
    }
});
1

1 Answers

2
votes

No, no such thing built-in. Oh, now that I think about it, there are those event things that are built into Ext. You can catch one and implement your own logic in there! In your case, that would be the select event.

Here's how I suggest to do exactly what you described (using the example tree panel from the doc):

Ext.create('Ext.tree.Panel', {
    title: 'Simple Tree',
    renderTo: Ext.getBody(),
    width: 400,
    height: 400,
    store: {
        root: {
            expanded: true,
            children: [
                { text: "detention", leaf: true },
                { text: "homework", expanded: true, children: [
                    { text: "book report", leaf: true },
                    { text: "algebra", expanded: true, children: [
                        { text: "buy lottery tickets", leaf: true }
                    ]}
                ] },
                { text: "buy lottery tickets", leaf: true }
            ]
        }
    },
    rootVisible: false,
    selModel: {
        mode: 'SIMPLE'
    },
    listeners: {
        select: function(tree, record) {
            function deselectChildren(record) {
                tree.deselect(record.childNodes);
                record.eachChild(deselectChildren);
            }
            deselectChildren(record);
        }
    }
});

But that gives us a quite weird user experience since you can select the child nodes again, once the parent one has been selected... And to trigger the behaviour (of deselecting child nodes by clicking on their ancestor again), you would have to first deselect the parent, and then reselect it... In a nutshell, IMO, that's confusing.

So, to make all that thing more predictable to the user, I would recommend either deselecting all ancestors when a node is selected by changing the select listener this way:

select: function(tree, record) {
    // Deselect children
    function deselectChildren(record) {
        tree.deselect(record.childNodes);
        record.eachChild(deselectChildren);
    }
    deselectChildren(record);

    // Deselect ancestors
    var parentNode = record.parentNode;
    while (parentNode) {
        tree.deselect(parentNode);
        parentNode = parentNode.parentNode;
    }
}

Or preventing selection of nodes that have an ancestor selected:

listeners: {
    select: function(tree, record) {
        function deselectChildren(record) {
            tree.deselect(record.childNodes);
            record.eachChild(deselectChildren);
        }
        deselectChildren(record);
    }
    // Returning false from this event handler will prevent selection (see the doc)
    ,beforeselect: function(tree, record) {
        function isAncestorSelected(record) {
            var parentNode = record.parentNode;
            return tree.isSelected(record) || parentNode && isAncestorSelected(parentNode);
        }
        return !isAncestorSelected(record);
    }
}

But, if you want my opinion, that second behaviour is kinda kinky too. I would use the first one.

Now, if you want, you can pack all this in a ux (user extension), publish it in the Sencha market, download it from there, and use it in your code... That way, you'll have your behaviour built into something ;)

Edit

Code for requirement 1 + 2 + 3

Ext.create('Ext.tree.Panel', {
    title: 'Simple Tree',
    renderTo: Ext.getBody(),
    width: 400,
    height: 400,
    store: {
        root: {
            expanded: true,
            children: [
                { text: "detention", leaf: true },
                { text: "homework", expanded: true, children: [
                    { text: "book report", leaf: true },
                    { text: "algebra", expanded: true, children: [
                        { text: "buy lottery tickets", leaf: true }
                    ]}
                ] },
                { text: "buy lottery tickets", leaf: true }
            ]
        }
    },
    rootVisible: false,
    selModel: {
        mode: 'SIMPLE'
    },
    listeners: {
        select: function(tree, record) {
            var parentNode = record.parentNode;

            // Deselect children
            function deselectChildren(record) {
                tree.deselect(record.childNodes);
                record.eachChild(deselectChildren);
            }
            deselectChildren(record);

            // See if all siblings are selected now
            var allSiblingSelected = false;
            if (parentNode) {
                allSiblingSelected = parentNode.childNodes.reduce(function(previous, node) {
                    return previous && tree.isSelected(node)
                }, true);
            }

            if (allSiblingSelected) {
                // EDIT3: adding true for second argument keepExisting
                tree.select(parentNode, true); // will trigger req 1
            }

            // Deselect ancestors
            else {
                while (parentNode) {
                    tree.deselect(parentNode);
                    parentNode = parentNode.parentNode;
                }
            }
        }
    }
});

Edit 4

With checkboxes:

Ext.create('Ext.tree.Panel', {
    title: 'Simple Tree',
    renderTo: Ext.getBody(),
    width: 400,
    height: 400,
    store: {
        root: {
            expanded: true,
            children: [
                { checked: false, text: "1 detention", expanded: true, children: [
                    {checked: false, text: '1.1 foo', leaf: true},
                    {checked: false, text: '1.2 bar', leaf: true}
                ] },
                { checked: false, text: "2 homework", expanded: true, children: [
                    { checked: false, text: "2.1 book report", leaf: true },
                    { checked: false, text: "2.2 algebra", expanded: true, children: [
                        { checked: false, text: "2.2.1 buy lottery tickets", leaf: true },
                        { checked: false, text: "2.2.2 buy lottery tickets 2", leaf: true }
                    ]}
                ] },
                { checked: false, text: "3 buy lottery tickets", leaf: true }
            ]
        }
    },
    rootVisible: false,
    disableSelection: true,
    //selModel: {mode: 'SIMPLE'},
    listeners: {
        checkchange: function(record, checked, opts) {
            if (!checked) return;
            var parentNode = record.parentNode;

            // Deselect children
            function deselectChildren(record) {
                record.eachChild(function(record) {
                    record.set('checked', false);
                    deselectChildren(record);
                });
            }
            deselectChildren(record);

            // See if all siblings are selected now
            var allSiblingSelected = false;
            if (parentNode) {
                allSiblingSelected = parentNode.childNodes.reduce(function(previous, node) {
                    return previous && node.get('checked');
                }, true);
            }

            if (allSiblingSelected) {
                parentNode.set('checked', true);
                // Apparently won't fire on its own
                this.fireEvent('checkchange', parentNode, true, opts);
            }

            // Deselect ancestors
            else {
                while (parentNode) {
                    parentNode.set('checked', false);
                    parentNode = parentNode.parentNode;
                }
            }
        }
    }
});