Home

Awesome

FlexLayout

GitHub npm npm

FlexLayout is a layout manager that arranges React components in multiple tab sets, tabs can be resized and moved.

FlexLayout Demo Screenshot

Run the Demo

Try it now using JSFiddle

API Doc

Screenshot of Caplin Liberator Explorer using FlexLayout

FlexLayout's only dependency is React.

Features:

Installation

FlexLayout is in the npm repository. install using:

npm install flexlayout-react

Import FlexLayout in your modules:

import {Layout, Model} from 'flexlayout-react';

Include the light, underline, gray or dark theme by either:

Adding an additional import:

import 'flexlayout-react/style/light.css';  

or by adding the css to your html:

<link rel="stylesheet" href="node_modules/flexlayout-react/style/light.css" />

Usage

The <Layout> component renders the tab sets and splitters, it takes the following props:

Required props:

PropDescription
modelthe layout model
factorya factory function for creating React components

Additional optional props

The model is tree of Node objects that define the structure of the layout.

The factory is a function that takes a Node object and returns a React component that should be hosted by a tab in the layout.

The model can be created using the Model.fromJson(jsonObject) static method, and can be saved using the model.toJson() method.

Example Configuration:

var json = {
    global: {},
    borders: [],
    layout: {
        type: "row",
        weight: 100,
        children: [
            {
                type: "tabset",
                weight: 50,
                children: [
                    {
                        type: "tab",
                        name: "One",
                        component: "button",
                    }
                ]
            },
            {
                type: "tabset",
                weight: 50,
                children: [
                    {
                        type: "tab",
                        name: "Two",
                        component: "button",
                    }
                ]
            }
        ]
    }
};

Example Code

const model = Model.fromJson(json);

function App() {

  const factory = (node) => {
    var component = node.getComponent();

    if (component === "button") {
      return <button>{node.getName()}</button>;
    }
  }

  return (
    <Layout
      model={model}
      factory={factory} />
  );
}

The above code would render two tab sets horizontally each containing a single tab that hosts a button component. The tabs could be moved and resized by dragging and dropping. Additional grids could be added to the layout by sending actions to the model.

Try it now using JSFiddle

A simple Typescript example can be found here:

https://github.com/nealus/FlexLayout_cra_example

The model json contains 4 top level elements:

The layout element is built up using 3 types of 'node':

The layout structure is defined with rows within rows that contain tabsets that themselves contain tabs.

The optional borders element is made up of border nodes

The tree structure for the JSON model is well defined as Typescript interfaces, see JSON Model

Each type of node has a defined set of requires/optional attributes.

Weights on rows and tabsets specify the relative weight of these nodes within the parent row, the actual values do not matter just their relative values (ie two tabsets of weights 30,70 would render the same if they had weights of 3,7).

NOTE: the easiest way to create your initial layout JSON is to use the demo app, modify one of the existing layouts by dragging/dropping and adding nodes then press the 'Show Layout JSON in console' button to print the JSON to the browser developer console.

By changing global or node attributes you can change the layout appearance and functionality, for example:

Setting tabSetEnableTabStrip:false in the global options would change the layout into a multi-splitter (without tabs or drag and drop).

 global: {tabSetEnableTabStrip:false},

Model Actions

Once the model json has been loaded all changes to the model are applied through actions.

You apply actions using the Model.doAction() method.

This method takes a single argument, created by one of the action generators (typically accessed as FlexLayout.Actions.<actionName>):

Actions doc

Examples

model.doAction(FlexLayout.Actions.updateModelAttributes({
    splitterSize:40
}));

The above example would increase the size of the splitters, this could be used to make adjusting the layout easier on a small device.

model.doAction(FlexLayout.Actions.addNode(
    {type:"tab", component:"grid", name:"a grid", id:"5"},
    "1", FlexLayout.DockLocation.CENTER, 0));

This example adds a new grid component to the center of tabset with id "1" and at the 0'th tab position (use value -1 to add to the end of the tabs).

Note: you can get the id of a node (e.g., the node returned by the addNode action) using the method node.getId(). If an id wasn't assigned when the node was created, then one will be created for you of the form #<uuid> (e.g. #0c459064-8dee-444e-8636-eb9ab910fb27).

Note: You can intercept actions resulting from GUI changes before they are applied by implementing the onAction callback property of the Layout.

Optional Layout Props

There are many optional properties that can be applied to the layout:

Layout Properties doc

JSON Model Definition

The JSON model is well defined as a set of TypeScript interfaces, see the doc for details of all the attributes allowed:

Model Config Attributes

Model Attributes doc

Global Config Attributes

Global Attributes doc

Row Config Attributes

Row Attributes doc

Tab Set Config Attributes

Tab set Attributes doc

Note: tab sets will be dynamically created as tabs are moved, and deleted when all their tabs are removed (unless enableDeleteWhenEmpty is false).

Tab Config attributes

Tab Attributes doc

Border Config attributes

Border Attributes doc

Layout Component Methods to Create New Tabs

There are methods on the Layout Component for adding tabs:

Layout Methods doc

Example:

layoutRef.current.addTabToTabSet("NAVIGATION", {type:"tab", component:"grid", name:"a grid"});

This would add a new grid component to the tab set with id "NAVIGATION" (where layoutRef is a ref to the Layout element, see https://reactjs.org/docs/refs-and-the-dom.html ).

Tab Node Events

You can handle events on nodes by adding a listener, this would typically be done when the component is mounted in a useEffect method:

Example:

    function MyComponent({node}) {

      useEffect(() => {
        // save subject in flexlayout node tree
        node.setEventListener("save", () => {
             node.getConfig().subject = subject;
           };
        }, []);
    }

EventparametersDescription
resize{rect}called when tab is resized during layout, called before it is rendered with the new size
closenonecalled when a tab is closed
visibility{visible}called when the visibility of a tab changes
savenonecalled before a tabnode is serialized to json, use to save node config by adding data to the object returned by node.getConfig()

Popout Windows

Tabs can be rendered into external browser windows (for use in multi-monitor setups) by configuring them with the enablePopout attribute. When this attribute is present an additional icon is shown in the tab header bar allowing the tab to be popped out into an external window.

For popouts to work there needs to be an additional html page 'popout.html' hosted at the same location as the main page (copy the one from examples/demo). The popout.html is the host page for the popped out tab, the styles from the main page will be copied into it at runtime.

Because popouts are rendering into a different document to the main layout any code in the popped out tab that uses the global document or window objects for event listeners will not work correctly (for example custom popup menus where the code uses document.addEventListener(...)), they need to instead use the document/window of the popout. To get the document/window of the popout use the following method on one of the elements rendered in the popout (for example a ref or target in an event handler):

    const currentDocument = selfRef.current.ownerDocument;
    const currentWindow = currentDocument.defaultView!;

In the above code selfRef is a React ref to the toplevel element in the tab being rendered.

Note: libraries may support popout windows by allowing you to specify the document to use, for example see the getDocument() callback in agGrid at https://www.ag-grid.com/javascript-grid-callbacks/

Limitations of Popouts

See this article about using React portals in this way: https://dev.to/noriste/the-challenges-of-rendering-an-openlayers-map-in-a-popup-through-react-2elh

Running the Examples and Building the Project

First install dependencies:

pnpm install

Compile the project and run the examples:

pnpm start

Open your browser at http://localhost:8080/examples/ to show the examples directory, click on the examples to run them.

The 'pnpm start' command will watch for changes to flexlayout and example source, so you can make changes to the code and then refresh the browser to see the result.

To build the npm distribution run 'pnpm build', this will create the artifacts in the dist dir.

Alternative Layout Managers

NameRepository
rc-dockhttps://github.com/ticlo/rc-dock
Dockviewhttps://dockview.dev/
luminohttps://github.com/jupyterlab/lumino
golden-layouthttps://github.com/golden-layout/golden-layout
react-mosaichttps://github.com/nomcopter/react-mosaic