POC: Drag, Drop, Resize, Stack Dash layout

Hi @benn0,
I made an update to your usage.py example that incorporates the ability of saving and loading the current layout.
I also added 3button in order to make use of the “addTabWithDragAndDrop”, “addTabWithDragAndDropIndirect” and “addTabToTabSet” methods but I did not find any wrapper inside your code. Is this correct? Is it possible to implement this methods?

import dash
import json
from dash import Input, Output, State, html

import dash_flexlayout as dfl


app = dash.Dash(__name__, suppress_callback_exceptions=True)

config = {
    
    "global": {
        "tabEnableClose":True, 
        "tabEnableDrag": True,
        "tabEnableRename": True,
        "tabEnableFloat": True, 
        "enableEdgeDock": True
        },
    "borders":[
        {
            "type": "border",
            "id": 'bottom-border',
            "location":"bottom",
            "selected": 0,
            "size": 120,
            "children": [
                {
                    "type": "tab",
                    "name": "Four",
                    "component": "text",
                    "id": "tab-4",
                }
            ]
        },
        {
            "type": "border",
            "id": 'left-border',
            "location":"left",
            "selected": 0,
            "size": 118,
            "children": [
                {
                "type": "tab",
                "name": "Utils",
                "component": "text",
                "id": "tab-utils",
                }
            ]
        }
    ],
    "layout": {
        "type": "row",
        "weight": 100,
        "children": [
            {
                "type": "tabset",
                "weight": 25,
                "children": [
                    {
                        "type": "tab",
                        "name": "One",
                        "component": "text",
                        "id": "tab-1",
                    }
                ]
            },
            {
                "type": "tabset",
                "weight": 75,
                "children": [
                    {
                        "type": "tab",
                        "name": "Two",
                        "component": "text",
                        "id": "tab-2",
                    },
                    {
                        "type": "tab",
                        "name": "Three",
                        "component": "text",
                        "id": "tab-3",
                    },
                ]
            }
        ]
    }
}


nodes = [
    dfl.Tab(id="tab-utils", children=[
            "This is UTILS tab",
            html.Button(id="button-1", children="Click Me"),
            html.Button(id="button-2", children="Add Drag"),
            html.Button(id="button-3", children="Add to Tab"),
            html.Button(id="button-4", children="Add Indirect"),
            html.Button(id="button-save", children="Save Layout"),
            html.Button(id="button-load", children="Load Layout"),
        ]
    ),
    dfl.Tab(id="tab-1", 
        children=[
            "This is TAB 1",
            html.Div(id="button-1-output"),
        ]
    ),
    dfl.Tab(id="tab-2", 
        children=[
            "This is the NODES layout",
            html.Div(id="layout-nodes"),
        ]
    ),
    dfl.Tab(id="tab-3",
        children=[
            "This is the ACTUAL layout",
            html.Div(id="layout-actual"),
        ]
    ),
    dfl.Tab(id="tab-4",
        children=[
            "This is the SAVED layout",
            html.Div(id="layout-saved"),
        ]
    ),
]

app.layout = dfl.FlexLayout(id='flex-layout', model=config, children=nodes, useStateForModel=False)

@app.callback(
    Output("layout-nodes", "children"),
    [Input("flex-layout", "children")]
)
def show_children_layout(children):
    return str(children)


@app.callback(
    Output("layout-model", "children"),
    [Input("flex-layout", "model")]
)
def show_model_layout(layout):
    return str(layout)


@app.callback(
    Output('button-1-output', 'children'),
    [
        Input("button-1", "n_clicks"),
    ],
)
def update_button_1_output(n_clicks):
    return f"Clicked {n_clicks} times!"


@app.callback(
    Output('layout-actual', 'children'),
    [
        Input("flex-layout", "model"),
    ],
)
def actual_layout(*args):
    return json.dumps(args[-1])


@app.callback(
    Output('layout-saved', 'children'),
    [
        Input("button-save", "n_clicks"),
    ],
    [
        State("flex-layout", "model")
    ],
    prevent_initial_call=True,

)
def save_layout(*args):
    return json.dumps(args[-1])


@app.callback(
    Output('flex-layout', 'model'),
    [
        Input("button-load", "n_clicks"),
    ],
    [
        State("flex-layout", "model"),
        State("layout-saved", "children")
    ],
    prevent_initial_call=True,
)
def load_layout(*args):
    if args[-1] is None: return args[-2]
    return json.loads(args[-1])




if __name__ == '__main__':
    app.run_server(debug=True)


Hi @davidpsq , that’s correct - I haven’t added in any of the drag and drop methods at the moment. It’s a little unclear to me how that workflow should fit in with the dash architecture. I’d appreciate your ideas here. My current thinking is that the updating of the layout should happen within a callback, in the same way that the ‘add tab’ button handles it - however it might run into issues with duplicate callback outputs to do it that way.

Another way would be to handle all of the layout management functions on the client side, which would probably require some javascript to be passed to the client to dictate how it should respond to these types of actions.

The UI I am looking to create has tabs that are a fixed height. I want to be able to stack them in the layout and then have a scrollbar to be able to scroll my tabs (and not compress them to fit the current screen. Is this possible to do here?

Hi, I thick that Flexlayout does what you say out of the box.
Try the demo:
Flex Layout (rawgit.com)
the tabs are not compressed or fit the space, if they are wider with respect of the tabset, they are put in a dropdown and you can scroll over the tabset to find the one you want.

perhaps I wasnt clear in my explanation. I want a vertical list of tabsets, where each tabset has a fixed height. so if i have 50 tabsets in a “row” stacked vertically, i want the row to be scrollable and not to resize my tabsets to 100 / num tabsets. Perhaps this feature is an anti-pattern for the layout

Is it possible to use the feature of onRenderTabSet to add custom control buttons to the tab?

I want to move the button I have to toggle my sidebar of my content to be inside the header row. Is this possible?

tab-icon

I do not understand your question. it works already like that. You have to apply the same size of the tab title for all tabs in the tabset.
Can you share your example?

Any chance of someone implementing Golden Layout any time soon?

1 Like

Hi @benn0 ,
did you have time to think about the drag&drop method?
I do agree to try the first method you explained, at list it sounds easier. I think the process can start with the creation of the new object and then managed as it is already for draggable tabs which have been already created.
Regarding the Header I do not have any issue with your modification.
Right now I am experiencing with nested layouts and I am already quite happy, I will post an example soon.

Can you please work on the theme? I am not into CSS and JS theming in general, I was wondering if you can make an example that can be used with dark themes, i.e. bootstrap or material design or whatever.

Thank you so much!

Hi @davidpsq , great to hear that you are making progress with how you are using it. I haven’t been able to think too much further about the drag&drop, but I will have some time in a couple of weeks to start experimenting on this. In terms of theming, I’m not great on that side but will try something out. I mostly use Mantine components so I will likely start trying to theme in that style. I’ll keep you posted on the progress I make.

1 Like

That sounds awesome!!
btw Mantine is one of my preferred package that I use most!

Hi, can you please share your POC, it has nice features, I can try to MOD it.
Thank you and kind regards!

Hi @davidpsq , I’ve started to take a look at ways of incorporating the drag-to-create functionality. I have a PoC where it partially works, however there are some complications getting this working within Dash. You can play around with it in the feature/drag-for-new-tab branch here. There is a simple demo of it working, however it has a few glaring flaws.

For some background, there is an undocumented option useStateForModel in the Dash FlexLayout component. The original intent was:

  • when false, changes to the layout (resizing, deleting tabs etc.) would call setProps and you could respond to these in dash callbacks.
  • when true, the component would manage the model layout in react state, which potentially had some advantages as there is a little extra overhead in managing it through dash setProps.

Now, when implementing the drag and drop functionality, my idea was to use the new DragFrom component as the drag start. You could give it properties that the new tab would take (in the PoC it’s just name), and when you drag onto the layout, the new tab would be created with the name taken from the DragFrom component (allowing you to have multiple drag objects that would create tabs with different properties).

Now, this currently works in the sense that you can initiate the drag and drop, and a new tab will be created. However, when dash is used to manage the layout useStateForModel=False, I haven’t figured out how to inherit the properties that you gave the DragFrom - in the current code it will just be given a New Tab name. You can however listen for layout changes and do something within dash to manage that. When useStateForModel=True, the tab will take on the correct name, but it’s kind of useless because Dash doesn’t know anything about the new tab.

At any rate, I’d be keen to take feedback on the minimal example here, and what you would need to fit it into your workflow. One approach I am thinking of would be to break apart what is currently stored in the model, where the important data about what tabs exist is mixed in with the layout. Separating the two might be better, where dash would see events concerning changes to the tabs (add/remove), and a separate property tracks the layout. At some point though, my fear is that friction between FlexLayout and Dash starts to cause problems with this approach, and there might be another base component that is better aligned to what I’m trying to achieve.

Also, I’m going to be out of contact for the next 3 weeks, so if I don’t respond to any follow-up I’m not being rude :slight_smile:

dash flex drag and drop

I started a new Topic for the Dash FlexLayout Component