Skip to content

Automatically select children on parent selection #90

@JanNolten

Description

@JanNolten

I am trying to select/deselect all children recursively when their parent gets selected/deselected.
I wrote a script for this in python, but because the node.selected property is sync, everytime I want to change it with
node.selected = selected
it syncs to the Javascript frontend and then directly syncs back to the python backend again.
So this: node.selected = selected
is extremly inefficient.
I would like to have this behaviour as an option for the ipytree or have the ability to change multiple selections of nodes and sync only once after.

This is the solution that works for me for now:
`
def on_selected_change_tree(change, do_not_select_leafs=True):
"""
Selects/Deselects all Childs on Parent select/deselect

    If you want to listen to the change of selected_nodes, then listen to
    jupyphant_entity.on_selected_neo_objects_changed with add_listener(self, fn)
    """

    """
    Calling node.selected is extremly inefficient, because it makes a trip from Python -> Javascript -> Python
    So there need to be as less calls as possible.
    However by doing that, the ipytree does not store the correct selected nodes anymore, so the python now stores the truth
    about which node is selected
    """
    jupyphant_entity.ipytree_of_neo_objects.unobserve(on_selected_change_tree, names='selected_nodes')
    old_selected_nodes = change['old']
    new_selected_nodes = change['new']

    # old_selected_nodes and new_selected_nodes are lists of Node objects
    old_set = set(old_selected_nodes)
    new_set = set(new_selected_nodes)

    # Nodes that were newly selected
    just_selected = new_set - old_set
    just_deselected = old_set - new_set

    def parent_selected(child):
        for node in jupyphant_entity.selected_neo_objects:
            if child in getattr(node, 'nodes', []):
                return True
        return False


    # Function to propagate selection iteratively
    def propagate(nodes, selected, all_selected_in_neo):
        fire_event = False
        stack = list(nodes)
        while stack:
            node = stack.pop()
            if node in visited or (not selected and all_selected_in_neo and node in just_selected):
                continue
            visited.add(node)

            children = getattr(node, 'nodes', [])
            is_leaf = not children

            # Only update node.selected if not a leaf
            if not (is_leaf and do_not_select_leafs):
                if node.selected != selected:
                    node.selected = selected

            # Keep selected_neo_objects in sync
            if selected:
                jupyphant_entity.selected_neo_objects.add(node)
                fire_event = True
            else:
                # Since leafs never get selected in the UI it is more intuative, that they are always selected, when parent is selected
                if not is_leaf or node not in just_deselected or not parent_selected(node):
                    jupyphant_entity.selected_neo_objects.discard(node)
                    fire_event = True
            stack.extend(children)
        return fire_event

    all_selected_in_neo = just_selected.issubset(jupyphant_entity.selected_neo_objects)
    none_deselected_in_neo = just_deselected.isdisjoint(jupyphant_entity.selected_neo_objects)

    visited = set()  # Keep track of processed nodes

    fire_event = False
    if all_selected_in_neo:
        if not none_deselected_in_neo:
            fire_event = propagate(just_deselected, False, all_selected_in_neo) or fire_event
    else:
        fire_event = propagate(just_selected, True, all_selected_in_neo) or fire_event
        if not none_deselected_in_neo:
            fire_event = propagate(just_deselected, False, all_selected_in_neo) or fire_event
    if fire_event:
        jupyphant_entity.on_selected_neo_objects_changed.fire()

    jupyphant_entity.ipytree_of_neo_objects.observe(on_selected_change_tree, names='selected_nodes')

`

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions