Arrange views programmatically with layouts

In Runtime version 30, we implemented new tools to help developers to dynamically arrange the layout of views to create more intuitive workflows for your users. These layout APIs allow developers to easily create new views adjacent to an existing view, column, or row, move/remove views within the tab groups, and create new tab groups (called TabStacks).

Starting in version 40, methods were added to operate on Views by accessing the Layout instance or module.

📘

Note

This article describes the Layout APIs that programmatically arrange views and TabStacks in a window. To understand LayoutConfig which saves the current arrangement of views in a window or restores a previous arrangement of views in a window, see Organize views with layouts.

A simple scenario

Let's say you are developing a wealth management system that displays in a large view. Below this large view, you want another view to show client portfolios, with one tab for each client.

Using the Layout APIs, this is a straightforward task to accomplish.

Note, however, you must be within a View context to call most of the layout API functions. You can check your context with fin.me.isView as well as use fin.View.wrapSync(view_identity) to be in the View context. Unless specified, all code samples work from within a View context.

First, let’s get the current TabStack of this view.

// must be in a View context
const myAppStack = await fin.me.getCurrentStack();

Next, let’s create a new adjacent TabStack with 3 new views in it.

const clientPortfolioStack = await myAppStack.createAdjacentStack([
  { name: 'Client 1', url: 'https://client-site-1.com/' },
  { name: 'Client 2', url: 'https://client-site-2.com/' },
  { name: 'Client 3', url: 'https://client-site-3.com/' },
], { position: 'bottom' });

Now, say you want to add another tab for Client 4, at the front of the new TabStack. We will use the addView function for this. It takes an index as the 2nd parameter, but if not provided it will default to 0, which is what we want to insert the new view at the front (the left) of the TabStack.

await clientPortfolioStack.addView({
  name: 'Client 4',
  url: 'https://client-site-4.com/'
});

Starting in version 40, you can also add a view via the Layout instance, with the layout.addView() method.

What is a Stack and a TabStack

We call the combination of a tab list and the currently visible view a "tab stack," or a TabStack.

By using a TabStack object, you can do several things:

  • Create adjacent tab stacks.

  • Add a view to the TabStack, remove a view, or move a view to a different TabStack.

  • Change what view is visible in the TabStack.

Note that TabStack is a "hot" reference, and not an instance of the tab stack layout object itself. The TabStack instance is not destroyed even when the underlying layout component is destroyed. For cases like this, use TabStack.exists() to verify whether the TabStack is still in the layout.

What is a ColumnOrRow

When a TabStack is the only one in the layout, it is the root item and the top-level container for all tabs. When there are multiple TabStacks, they are always contained within a ColumnOrRow. Both TabStack and ColumnOrRow share a subset of functions. See the API reference for ColumnOrRow and TabStack for more detailed class information.

What are some other functions to arrange views and TabStacks

addView

Add a view directly with the addView function.

You can find an example use of addView in Merge all stacks on the right with the current stack below.

removeView

Remove a specific view with the removeView function.

// Remove a view from its current TabStack.

// Wrap the view to get a fin.View instance.
const removedView = fin.View.wrapSync({ uuid: fin.me.uuid, name: 'view-to-remove' });

// Get the TabStack of that View.
const stack = await removedView.getCurrentStack();

// Remove the view using the identity of the view.
await stack.removeView(removedView.identity);

getParent

You can get the parent of the current item by using the getParent function.

You can an example use of getParent in Determine the parent of a view below.

Starting in version 40, you can get a View's tab stack via the Layout instance, using layout.getStackByViewIdentity().

getAdjacentStacks

You can get a list of all TabStacks that share an edge with the current TabStack or ColumnOrRow, with the getAdjacentStacks function. This allows you to detect and reuse an existing view on an edge if one already exists, or create a new view if a view does not exist on that edge.

You can find an example use of getAdjacentStacks in Merge all stacks on the right with the current view below.

What you can do with Layouts

The Layouts APIs are designed to be predictable and focused, to allow you to do powerful things with them.

Below are a few examples of what can be done with the Layouts API.

How to create a view below the current view

Create a view that contains three client tabs below the current view.

From a View context, get the TabStack of the current view:

const myAppStack = await fin.me.getCurrentStack();

Then, create a stack that is to the bottom of the current view with 3 additional views:

const clientPortfolioStack = await myAppStack.createAdjacentStack([
  { name: 'Client 1', url: 'http://developers.openfin.co/' },
  { name: 'Client 2', url: 'http://developers.openfin.co/' },
  { name: 'Client 3', url: 'http://developers.openfin.co/' },
], { position: 'bottom' });

The first parameter of createAdjacentStack contains an array of views. This can be a single view identity that already exists, a new view you want to create, or an array of mix of both, new views and existing views.

The second parameter is an options object which contains a position element to choose the relative position for the adjacent TabStack, 'top', 'bottom', 'left', and 'right'.

Add a view to an existing TabStack

// Add a view to an existing TabStack.

// Get the current stack.
const myAppStack = await fin.me.getCurrentStack();

// Get the first stack to the right.
const rightAdjacentStacks = await myAppStack.getAdjacentStacks('right');
const clientPortfolioStack = rightAdjacentStacks[0];

// Add a new view to that stack on the right.
await clientPortfolioStack.addView({ name: 'Client 4', url: 'https://developers.openfin.co/' });

Close a view

You can close a view programmatically, either from the Platform provider, or (starting in v40) from the Layout instance.

Determine the parent of a view

// Obtains the parent of a view.

const myStack = await fin.me.getCurrentStack();
const owningColumnOrRow = await myStack.getParent(myStack);

Add a new TabStack to the top-most level of the layout window

Note: This code assumes you are within the Platform Provider context or the Layout window, not within a View context.

// Add a new TabStack to the top-most level of the layout window.

// Get the layout from the window context (ie., not from a view).
const layout = fin.Platform.Layout.getCurrentSync();

// Get the root item, which is a TabStack.
const rootTabStack = await layout.getRootItem();

// Add a new TabStack above.
const newStack = await rootTabStack.createAdjacentStack([
  { name: 'important-tab', url: 'http://important-content.com/' }
], { position: 'top' });

Alternatively (starting in version 40), from a View's identity, you can get its Layout instance with Layout.getLayoutByViewIdentity().
With the layout instance and view identity, you can get the view's tab stack, using layout.getStackByViewIdentity().

Create an adjacent TabStack and move an existing view at the same time

// Create an adjacent TabStack and move an existing view at the same time.

// Wrap an existing view
const viewFromSomewhere = fin.View.wrapSync(someView.identity);

// Get the stack this view belongs to
const stack = await viewFromSomewhere.getCurrentStack();

// Create an adjacent stack with a new view, and the existing view identity, resulting in the view 'moving' to this new stack
const newStack = await stack.createAdjacentStack([
  viewFromSomewhere,
  { name: 'another-new-view', url: 'http://developers.openfin.co/' }
], { position: 'right' });

Merge all stacks on the right with the current stack

// Merge all stacks on the right with the current stack.

// Get the current stack.
const currentStack = await fin.me.getCurrentStack();

// Get the adjacent stacks to the right.
const rightStacks = await currentStack.getAdjacentStacks('right');

// Gather all the views from each stack into a flat array.
const rightViewArrays = await Promise.all(
    rightStacks.map(s => s.getViews())
);
const rightViews = rightViewArrays.flat();

// Use addView to move every view to the first stack. This removes 
// the view from its original stack. After the last view is removed,
// the stack is automatically destroyed.
await Promise.all(rightViews.map(view => currentStack.addView(view)));

Retrieve the root item from the Layout instance

If you are within the Platform Provider context or the Layout window, not within a View context, you can retrieve the top-level item using Layout.getRootItem like this:

// Retrieve the root item from the Layout instance.

// Get the current Layout instance
const layout = await fin.Platform.Layout.getCurrentSync();

// Get the root item - returns TabStack if it’s the only stack in the layout, otherwise it will be a ColumnOrRow if there are multiple tab groups in the layout.
const tabStackOrColumnOrRow = await layout.getRootItem();