mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
3.92k stars 1.19k forks source link

[TreeView] Support parent / children selection relationship #12883

Open flaviendelangle opened 2 months ago

flaviendelangle commented 2 months ago

Related issues

Current behavior

In the current implementation of the Tree View component (both SimpleTreeView and RichTreeView), selecting an item does not select its children and selecting all children of an item does not select its parent.

The behavior is exactly the same with checkbox selection.

Selecting a parent (single select)

image

Model value: "A"

Selecting a leaf (single select)

image

Model value: "A1"

Selecting a parent (multi select)

Here is what the result is when you click on "A":

image

Model value: ["A"]

Selecting a leaf (multi select)

Here is what the result is when you click on "A1":

image

Model value: ["A1"]

Selecting all leaves of an item

Here is what the result is when you click on "A1" then click on "A3" while pressing Shift:

image

Model value: ["A1", "A2", "A3"]

Alternatives model

Model contains the selected leaves and the parent selection is deduced from their descendants

Based on https://github.com/mui/mui-x/pull/11452#issuecomment-2009687309

The following model can only work in multi selection. The idea here is to only apply the selection on the leaves and then deduce the visual selection of the parents based on the selection status of their descendants:

Selecting a parent (multi select)

Here is what the result is when you click on "A":

image

Model value: ["A1", "A2", "A3"]

Selecting a leaf (multi select)

Here is what the result is when you click on "A1":

image

Model value: ["A1"]

Selecting all leaves of an item

Here is what the result is when you click on "A1" then click on "A3" while pressing Shift:

Model value: ["A1", "A2", "A3"]

image

Keep the model as is but visually select a children if one of its ancestors is selected

Based on https://github.com/mui/mui-x/pull/11452#issuecomment-2071989301

The following model can work in both single and multi selection. The idea here is to visually select an item if it is selected in the model or if one of its ancestor is selected.

It might not work well with the checkbox where people probably expect the parent checkbox to change its status when selecting the children.

Selecting a parent (single select)

image

Model value: "A"

Selecting a leaf (single select)

image

Model value: "A1"

Selecting a parent (multi select)

Here is what the result is when you click on "A":

image

Model value: ["A"]

Selecting a leaf (multi select)

Here is what the result is when you click on "A1":

image

Model value: ["A1"]

Selecting all leaves of an item

Here is what the result is when you click on "A1" then click on "A3" while pressing Shift:

image

Model value: ["A1", "A2", "A3"]

Benchmarks

Search keywords:

flaviendelangle commented 2 months ago

If you have other approaches that you saw on other Tree View (or similar component) I would be super interested. When we will have a good understanding of what exists, we will be in a better position to decide what we want to implement (it might be several approaches and let people switch between them).

I still need to explore how the various approaches work with lazy loading (which we want to support eventually).

LukasTy commented 2 months ago

I'll just re-post the community members' suggested example: https://primereact.org/treeselect/#check IMHO, it's a good role model checkbox selection behavior-wise.

Regarding our implementation, I think it should be highly flexible to cover various use cases.

Aberkati commented 2 months ago

I'll just re-post the community members' suggested example: https://primereact.org/treeselect/#check IMHO, it's a good role model checkbox selection behavior-wise.

Regarding our implementation, I think it should be highly flexible to cover various use cases.

It's a very good example, hopefuly we get a similar example with MUIX

flaviendelangle commented 2 months ago

I'll just re-post the community members' suggested example: https://primereact.org/treeselect/#check

In term of behaviors, this looks a lot like

If I understand correctly, with this approach you change the structure of the model to look something like "Model contains the selected leaves and the parent selection is deduced from their descendants" described above BUT with both the children and the parent represented in the model.

{
    '0-0': {
        partialChecked: false,
        checked: true
    }
}

With this approach, if I understand correctly, the model describes what is selected (fully selected, partially selected or not selected) item by item without any parent / children relationship. And the UI actions (clicking on an item, clicking on a checkbox, etc...) updates the model and selects children and/or parent based on the selection rule we want (might have some parent / children relationship, might not).


That could be an interesting approach. I'm not a huge fan of the shape of the model (having a boolean when not using checkbox and this two-properties object when using them) but this could be adapter.

One could say that this model structure is super flexible since it lets the model remain the same with different selection behaviors. You always have the same model with every item selected and based on some prop you can change which item are added to the model when selecting an item.

The only weakness I can see is that if someone control the model, there is no guarantee that he passes a model value that is coherent with the selection mechanism in place in his app. For instance, he configures the Tree View to automatically selects the children when selecting a parent. So when you click A, it calls the onSelectedItemsChange with ["A", "A1", "A2", "A3"]. But based on some external UI he updates props.selectedItems and passes ["A1", "A2", "A3"] to the Tree View. This would visually select "A1", "A2" and "A3" which is a selection state you can never achieve through interactions with the Tree View.

As long as we agree that this is not a problem, this looks like a very flexible approach.

flaviendelangle commented 2 months ago

Side note, I am not a fan of array model at all (for performance reasons mainly and because it looks like it ordered when in fact we don't enforce any order on the model). So if the parent / children selection relationship is an opportunity to move to an object model I'm all in favor of doing it.

joserodolfofreitas commented 2 months ago

DataGrid is also aiming to support this feature soon, so it'd be good for us to keep aligned on the DevEx.

oliviertassinari commented 1 month ago

(coming from seeing the tweet https://twitter.com/MUI_hq/status/1793654965996823022, initially I wanted to raise the intermediate checkbox state missing, but I got it, it's a broader discussion)

I suspect that the "Checkbox selection" should be a separate feature from "Selection". I mean to have a separate state. I can see the use case for end-users to check items, the items they want to delete, while having another item selected to be expanded on a right panel.

If we benchmark from https://www.notion.so/mui-org/mui-x-Tree-View-component-c283f4a957474b79b293e994881b1ee8.

It seems that those behaving like it (independent). This is what I was expecting:

I see those with separate states, but where selection triggers checkbox selection and checkbox selection triggers selection:

I see those that have selection disabled when checkbox selection is enabled:

I see those that have checkbox selection meaning selection but selection still being independent.

Those that behave closely to #11452 in the sense that have checkbox selection and selection merged into a single state, however, also have the parent checkbox relationship selection correct:

I see behaving like in #11452:

I'll just re-post the community members' suggested example: https://primereact.org/treeselect/#check IMHO, it's a good role model checkbox selection behavior-wise.

This feels more: https://github.com/mui/mui-x/issues/514

johnnyreilly commented 1 month ago

Awesome to see this looks like it's getting given native support! If anyone wants to this behaviour now, it is hand rollable and I've documented it here:

https://johnnyreilly.com/mui-react-tree-view-check-children-uncheck-parents

Once implemented, behaviour looks like this:

image

Brilliant to see that this is likely to be obsolete very soon! Thanks for pointing me to this @oliviertassinari

A thing that can't be handled by the approach I'm employing, is where parent nodes have a visual state that indicates where some, but not all, nodes are selected. It would be tremendous if that could be considered for the native design. (An example of this behaviour is https://www.jstree.com/ )

It looks like you're already considering this given this image in the initial description:

Aberkati commented 1 month ago

Awesome to see this looks like it's getting given native support! If anyone wants to this behaviour now, it is hand rollable and I've documented it here:

https://johnnyreilly.com/mui-react-tree-view-check-children-uncheck-parents

Once implemented, behaviour looks like this:

image image

Brilliant to see that this is likely to be obsolete very soon! Thanks for pointing me to this @oliviertassinari

A thing that can't be handled by the approach I'm employing, is where parent nodes have a visual state that indicates where some, but not all, nodes are selected. It would be tremendous if that could be considered for the native design. (An example of this behaviour is https://www.jstree.com/ )

It looks like you're already considering this given this image in the initial description:

defaultSelectedItems is not working in your demo example, I cannot selectItems when component in mounted

johnnyreilly commented 1 month ago

I can't see your code @Aberkati, but maybe it's worth you playing with this live demo on stackblitz?

https://stackblitz.com/edit/mui-react-tree-view-check-children-uncheck-parents?file=Demo.tsx

Aberkati commented 1 month ago

This is my code : https://stackblitz.com/edit/mui-react-tree-view-check-children-uncheck-parent-jivweq?file=Demo.tsx

I added defaultSelectedItems with an array with ids but seems not working..

johnnyreilly commented 1 month ago

I should tell you that I'm on my phone in the South of France and I can't obviously tell what the issue is that you're facing - but it seems to work for me on my phone šŸ˜…

Aberkati commented 1 month ago

I should tell you that I'm on my phone in the South of France and I can't obviously tell what the issue is that you're facing - but it seems to work for me on my phone šŸ˜…

Oh really ? When you refresh using an id of existent ID on the array it automaticly checked when your component is mounted ?

johnnyreilly commented 1 month ago

defaultSelectedItems is for uncontrolled components I think? My example is using the controlled component behaviour for selection. Do you want to try setting the initial state using setSelectedIds instead?

flaviendelangle commented 1 month ago

defaultSelectedItems is for un-controlled components For controlled ones, you can use selectedItems and onSelectedItemsChange

Skykingeagle commented 1 month ago

defaultSelectedItems is for un-controlled components For controlled ones, you can use selectedItems and onSelectedItemsChange

How would I limit these checkboxes to certain nodes? For example if I only wanted the checkbook selection to be true for the leaf nodes only, how would I be able to do that?

flaviendelangle commented 1 month ago

Right now we have no way of doing it easily. The data grid has a isRowSelectable prop that we could replicate on the Tree View components to limit the selection to certain rows. But it's not the topic being discussed in this issue, I would advise you to open a new issue so that we can prioritize your needs

johnnyreilly commented 1 month ago

Just a heads up that I found and fixed a bug in my handrolled logic last night. I've updated the blog post and the StackBlitz

If anyone is curious as to the fix, see here:

https://github.com/johnnyreilly/blog.johnnyreilly.com/commit/f32576210635e53b10f4b596068b0f417fedda03

johnnyreilly commented 3 weeks ago

And one more fix! (previous code didn't walk the tree all the way to the top when reselecting from a leaf node). Again I've updated the blog post and the StackBlitz

The fix for this issue can been seen in this commit:

https://github.com/johnnyreilly/blog.johnnyreilly.com/commit/908bdf0c9f42faa20ffe4119baf4be16fb51f6b9

Aberkati commented 3 weeks ago

And one more fix! (previous code didn't walk the tree all the way to the top when reselecting from a leaf node). Again I've updated the blog post and the StackBlitz

The fix for this issue can been seen in this commit:

johnnyreilly/blog.johnnyreilly.com@908bdf0

Great!! I'm trying to add a input field on top of this component to filter my nodes but seems to be hard, have you an idea how to achieve it please ? this an example demo link : https://stackblitz.com/edit/bgcued-tebqqr?file=src%2FApp.jsx,package.json Thank you!

Aberkati commented 3 weeks ago

defaultSelectedItems is for un-controlled components For controlled ones, you can use selectedItems and onSelectedItemsChange

Yes I've tried it and it works, but the problem is when I select a parent id as selected onmount his leaf are uncheked.. Please take a look here : image