Finsemble: Build An App Step By Step

Build A Finsemble App

A Step By Step Tutorial

This tutorial will take you through the process of building an application using the Finsemble Seed Project as a starting point. Please read gettingFinsemble and follow the instructions for downloading and installing the Finsemble Seed Project.

In this tutorial we will learn how to configure and code a Finsemble application. We'll start with a basic but common use case - Account Management. Many applications must provide lists accounts and then a way for a user to pick an account from the list to examine its details. This common use case provides a great example to show how Finsemble can provide simultaneous views into multiple accounts, avoiding a frustration that we've all encountered - having to flip back and forth when juggling between pieces of information.

Anatomy of a Finsemble Application

Finsemble Applications consist of several parts:

  • Components - In Finsemble, a "component" is a piece of UI that lives in an OpenFin window. A Finsemble application consists of multiple components operating simultaneously on a user's desktop. As a developer, you will spend the majority of your time writing components.
    • Core - Finsemble contains several core components which are automatically available to. These include the Toolbar, Linker and Authentication components.
    • Custom - Developers will build their own components as part of their application.
    • Pre-Packaged - Additional and third-party components are available to include in your application. ChartIQ provides Charting and Chat components as well as prebuilt market data components powered by Xignite.
  • Microservices - Microservices are processes that run behind the scenes in a Finsemble Application. Microservice functionality is made available to components through "Client APIs".
    • Core - Core microservices include "Window", "Launcher", "Docking", "Workspace", "Dialog", "Linker", "Storage" and "Router". Some services are configurable. For instance, the Storage service allows developers to implement their own scheme for storing data (i.e. saving to an external database).
    • Custom - Developers can write their own services when necessary.
    • Pre-Packaged - Some packaged components require additional services. For instance, the chat component uses a Chat microservice.
  • Clients APIs - Finsemble functionality is exposed to components through Client APIs. Most microservices provide a corresponding API. APIs can be accessed in a global FSBL object which is automatically made available to any component. You do not need to include APIs with script tags, require or any other means. They are always available!
  • Config files - Services, components, the application and the Finsemble/OpenFin runtimes all use JSON config files to manage behavior. The Finsemble build process merges the config files into the OpenFin manifest. Deployment involves hosting just that single manifest file.
  • Dev Runner - The Dev Runner uses npm to run a build process that packages all of these pieces into a complete Finsemble Application. The dev runner allows developers to quickly compile, run, and debug their code without any manual effort. A Command Line Interface (CLI) provides shortcuts for common tasks such as creating new components.

With this anatomy in mind, the basic skills necessary to build a Finsemble app are:

  • Adding, Implementing and Configuring components
  • Using built-in services such as Launcher, Workspace, Linker

This tutorial will provide background information about these concepts as well as instructions that will step you through the process of actually building a working application.

Step 1: Adding Finsemble Components

We will start by creating two initial components: accountList and accountDetail. The accountList component will contain a list of accounts for our fictional company. The accountDetail component will display detailed information about a selected account.

In Finsemble, every component resides in its own window. A Finsemble Application is an assembly of component windows that operate as peers. Inside the window, each component is essentially a web page, except that it has access to hooks that allows it to interact with its peers. These component windows all reside within a window management infrastructure that allows end-users to customize their work environment, by moving, resizing, snapping, saving and restoring component windows.

Since Finsemble Components are essentially web pages, they can literally contain anything that can be run in a web page. You can build Finsemble Components in Angular, React, Web Components, JQuery, or any other UI framework. In fact, a Finsemble Component doesn't even need to be written. You can wrap existing web pages (even those that you don't control) and turn them into Finsemble Components. *See usingMindControl.

For the sake of simplicity, our sample components will be rudimentary. We'll write them with JQuery using a simple imperative style of coding. This is only in order to convey the concepts as clearly as possible. Once you complete the tutorial, you will not have to limit yourself to this basic model.

Command Line Interface (CLI)

Finsemble comes with a command line interface that streamlines many common tasks. Finsemble Applications can grow large, with many components and services. Using the Command Line Interface (CLI) ensures that a common directory structure and naming style are maintained. It also saves you time and effort by generating consistent boilerplate for new components.

Open a command line or terminal (we recommend GNU Bash) and navigate into the directory where you've cloned or forked the seed project:

> cd <seed-project-location

CLI commands should be run from the root directory of your project. Let's start by adding our two components:

> finsemble-cli add component accountList

> finsemble-cli add component accountDetail

These commands will create boilerplate JavaScript, CSS, and HTML files for each component. You'll find this boilerplate in your-repo/src/components. A JSON entry for each component is also added to your-repo/configs/components.json. Note, your components.json file will already include a default entry for the Finsemble Documentation component. You can remove this of course!.

Now run the app!

> npm run dev

Note, it may take a few seconds for the app to start and if this is the first time running, it will have to download external assets such as the OpenFin environment.

A toolbar should appear at the top of your monitor to indicate that the app has started. Choose "accountList" or "accountDetail" from the "Apps" section of the toolbar. An empty component window will appear. You can exit the application by choosing "Quit" under the "File" menu of the toolbar.

Note, "ctrl-c" in your command tool will stop the dev runner but this isn't necessary after making code changes. The dev runner automatically watches for changes and recompiles your application. After making a change, simply right click on the toolbar and select "Reload app and restart children". You will need to restart the dev runner after making any config changes.

Configuring Components

The file your-repo/configs/components.json contains configuration entries that tell Finsemble which components to load and how they should be configured. Entries are added and removed from this file when you run CLI commands. You can also edit this file directly.

Example components.json entry :

"accountDetail": {
            "window": {
                "url": "/components/accountDetail/accountDetail.html",
                "width": 800,
                "height": 600
            },
            "component": {
            },
            "foreign": {
                "services": {
                    "dockingService": {
                        "canGroup": true,
                        "isArrangable": true
                    }
                },
                "components": {
                    "App Launcher": {
                        "launchableByUser": true
                    },
                    "Window Manager": {
                        "FSBLHeader": true,
                        "persistWindowState": true
                    },
                    "Toolbar": {
                        "iconClass": "accountDetail"
                    }
                }
            }
        }

The Config File for a component must contain a url. It doesn't need to include anything more than that; however, the CLI automatically adds some default entries. For instance, a "foreign" subsection in the config provides a place for the component to offer up parameters to other services and components. In the JSON above, foreign->components->App Launcher->launchableByUSer tells the built in App Launcher component that accountDetail should appear in the user's apps menu.

Further along in this tutorial we'll show some of the configurations that you would typically add to this file.

Step 2: Create Some Component Code

Before going further, for this tutorial we've gone ahead and created code that you can cut & paste into your component files. Inside your-repo/src/tutorials you will find accountList and accountDetails directories. Go ahead and copy these into your-repo/src/components folder.

If you open the example code in your editor, you will see sections that have been commented out. As we step through this tutorial, we will ask you to uncomment each section, as we will provide an explanation for what that section of code is doing. For now, go ahead and run the application again. Our new components can be launched from the toolbar just to make sure that they are working correctly.

We've preloaded the accountList component with some dummy data. In real-life, you would fetch this data from a file or network service. You do not need to use any specific Finsemble methodology for fetching data. For instance, you can make Ajax requests, or WebSockets, just like any other web application. Later, we will discuss some advanced cases where Finsemble can provide specific benefits.

Step 3: Modifying and Accessing Component Configuration

Let's first set up a custom configuration option and learn how to access that information programmatically. Open up your-repo/configs/components.json in an editor. Add the following line:

{
    "accountList": {
        ...
        "component": {
            "account-type": "vip", <------------------ Add this line (but not this comment)
            ...
        }
        ...
    }
}

Now open your component's JavaScript file in your editor: your-repo/src/components/accountList/accountList.js

Search for FSBL.addEventListener("onReady". Components should always wait for this event before running any Finsemble code. This ensures that Finsemble (and OpenFin) are fully initialized.

In the callback uncomment this line of code:

alert(FSBL.Clients.WindowClient.options.customData.component["account-type"]);

Now run the app again.

> npm run dev

Note, once the app is running, you can right click and reload a component after making changes. There is no need to restart the dev runner between changes.

Once the app has started, choose "accountList" from the "Apps" section of the toolbar. Your component window will appear and you should see "vip" pop up as an alert. This line of code demonstrates how a component can retrieve initial configuration settings from the components.json.

Go ahead and add the comment back. We don't want that alert to annoy us!

Step 4: Removing components from the toolbar App List

Now that you've created these components, let's remove accountDetail from the App List. For the sake of this demo, we won't want users to launch an accountDetail directly; instead they will be clicking on accounts from the accountList to spawn it!

Open your_repo/configs/components.json in your favorite editor. Search for launchableByUser in the accountDetail section. Set this to "false".

For this change to take effect you will need to restart your application. Choose "Quit" from the application toolbar. Then "ctrl-c" in your command tool to stop the dev runner. Now type npm run dev to relaunch your application. accountDetail will no longer be available from the App List.

Step 5: Using the Launcher service to spawn new components

Open up your_repo/src/components/accountList/accountList.js in your preferred editor. Search for the function launchAccountDetail. Go ahead and uncomment the code inside of this function. This code will spawn a new accountDetail component when the user clicks on a customer from the accountList.

FSBL.Clients.LauncherClient.spawn is the method that spawns new windows. FSBL.Clients is a global static object that contains all client APIs. In this case, spawn() leverages the Launcher service to create a new component.

You can control many aspects of a component's window by setting parameters to spawn(). In this case we decided that the accountDetail should adjacent to its parent window. So we set left: "adjacent" to tell the Launcher to place our new window so that it's left edge is adjacent to its parent. You could have placed it anywhere and set it to have any width or height. The spawn() method mimics CSS, so if you are familiar with relative and absolute positioning of DOM windows then you will immediately understand how to use spawn().

You can also send data to the child window. This is useful for bootstrapping the child. In our example, we've sent the current customer to the child window so that it initializes with the data the user has clicked on.

Example, send data to spawned client in second parameter to spawn() :

data: {
    "accountNumber": accountNumber
}

The child window can use WindowClient#getSpawnData to retrieve this information. You can see this is done inside accountDetail.js.

Spawning component windows is an asynchronous event, and so a callback is available where you can run commands after the component has launched. Finsemble follows node.js conventions of using the first parameter to indicate whether an error has occurred. The second parameter, response, contains information about the new component including its identity (windowIdentifier), its config (windowDescriptor), and a reference to an OpenFin window object (finWindow). These objects can be used to keep track of the window and further manipulate it.

For instance, the accountList parent may decide that it wants to relocate its child. {@LauncherClient#showWindow} can be used to change a window's location.

FSBL.Clients.LauncherClient.showWindow(accountDetailSpawnResponse.windowIdentifier,
    {
        left:"aligned",
        top:"adjacent"
    });

Clicking on the "relocate" button will move our accountDetail component from being left adjacent to being top adjacent (sitting underneath its parent window). As a user of course you can go ahead and move the window wherever you want!

Finally, we can set the title of the window dynamically with FSBL.Clients.WindowClient.setWindowTitle(). In our accountDetail.js, we set the title to be the currently enabled account.

Step 6: Advanced Spawning

The only problem with the current spawning code is that it creates a new window every time you click on a customer. Ideally, what we want is to spawn a component only if it doesn't already exist. If the component does exist, then just tell it to switch customers.

Uncomment the code in the launchAccountDetailAdvanced() function. (Note, we're using a little flag "advancedIsRunning" just for the purposes of this tutorial. You can ignore that).

You might be tempted to simply create a local variable to remember if you've spawned the accountDetail window - but that is dangerous. A user could have closed the window. In other use cases, a user may have opened the window manually, or it could have restored from a saved workspace.

LauncherClient#showWindow provides a useful way to deal with this scenario. When the parameter spawnIfNotFound is set, then the component will be spawned only if it doesn't exist. In this case, we need to pass a LauncherClient~windowIdentifier that uniquely identifies our component. Most often, components are assigned a unique window name when they are spawned, but in this case we explicitly set the windowName so that we can be sure that there is one and only one child for this accountList:

var windowIdentifier={
    componentType: "accountDetail",
    windowName: FSBL.Clients.WindowClient.options.name + ".accountDetail"
};

If the component is newly spawned then it will get its account number using getSpawnData() just like in the previous example, but if the component is already open then we need to tell it to load the new customer. We do so by sending a message using FSBL.Clients.RouterClient.transmit(). The first parameter to transmit() is a channel name. To create a unique channel we use a trick. By using the accountDetail's window name, we effectively create a private communication with that component. If you check accountDetail.js, you'll see that we've set up a listener on this "private" channel to accept inbound customer changes.

Note, Finsemble also supports "pub/sub" which is an advanced form of uni-directional communication. A pub/sub channel uses publish/subscribe instead of transmit/listen. The main difference is that pub/sub maintains state. New subscribers are guaranteed to receive the most recently transmitted message, even if it was published before the subscriber comes on line. See RouterClientConstructor for complete details.

Note: there is no such thing as truly private communications within a Finsemble Application. The components within an application are assumed to trust one another. If you need to establish true secure communication you would need to use encryption just as you would with any network communication.

Step 7: Communicating between components

In the previous example, we demonstrated the most basic form of communication within a Finsemble app, which is a simple transmit and receive on a known channel. Here we will introduce a more advanced type of communication - query/response. We'll use this type of communication to enable the "Next Customer" button on the accountDetail component.

Search for the function communicateBetweenComponents and uncomment the code inside of this function in both accountDetail.js and accountList.js. This code will allow communication between the account Detail and accountList components.

In accountList, the parent, we set up a "responder" to listen on a new channel called "accountTraversal". Responders act like miniature servers. They accept inbound requests and then return responses. Our simple responder accepts requests to iterate through the list of customers. It responds to requests with the next account number.

In accountDetail, the child, we make a "query" on the accountTraversal channel whenever the user clicks on the "next" button. Then, when we receive the new customer, we display it.

This is a contrived example, but it shows the basic premise, that components can be programmed to interact with one another.

Step 8: Linking components

Components can be synchronized by linking them. Linking is generally a user level activity, but as a developer you need to specify what data points your component can link to/from. Your component also need to respond to linkage changes made by the user.

In our example, we'll create a third component called "accountStatement" which will show the customer's fictional billing statement. Users will typically want to link up the accountDetail and accountStatement components so that they stay synchronized. We'll step you through how to enable linking at the component level, and then how to create the linkage from a user level.

You may be wondering why we wouldn't just use events to link the components together like we did with accountList and accountDetail. This is certainly possible but it prevents the user from choosing whether they wish to link the components. User choice becomes critical once you give users the ability to open many of the same component. An accountDetail window could be a slave to any number of parent windows for instance.

Stop your app ("Quit" from the toolbar; "ctrl-c" from your terminal window). Now add a new component:

> finsemble-cli add component accountStatement

Copy src/tutorials/accountStatement to src/components/accountStatement

Next, let's enable the linker for the accountDetail and accountStatement components. Edit configs/components.json. For each of these components add the following line to the foreign/components/Window Manager section:

"showLinker": true

Now restart your app (npm run dev). You can open up an accountStatement component from the Apps menu. Notice that it has a linker icon in the top left of the title bar. If you click on a customer in the accountList to open an accountDetail, you should now have a linker icon here as well. If you click on the icon, you'll see a string of colors. Two components can be linked simply by choosing the same color for each component.

It's pretty simple, and the accountDetail.js and accountStatement.js files are already rigged up so you can try it out. Start the app and open an accountDetail window. Choose the same color from the Linker dropdown for both accountDetail and accountStatement. Now, anytime the account changes in the accountDetail component, the accountStatement will also change.

How does it work? In accountStatement.js you will see this line:

FSBL.Clients.LinkerClient.subscribe("account", function(obj)... - This subscribes to linkages for the dataType called "account". The dataType is arbitrary, it's just a string that you specify (think of it as a channel or topic). Whenever an event comes in, our callback is executed. It's very similar to how we handle RouterClient events, except of course that the end-user establishes the linkages.

Meanwhile, in accountDetail.js you will see this line:

FSBL.Clients.LinkerClient.publish("account", accountNumber); - this publishes the account number. We've put it inside of setAccountNumber so that anytime the number changes, from anywhere, the linkage is triggered.

Note: Linkages can be made bi-directional by adding both publish and subscribe calls to your component.

Step 9: Making components aware of Workspaces

You may have noticed that whenever you restart your app, the accountList component remains on the screen in the same location where you left it. This is because component windows belong to a "workspace". The last known state of all components in your application is saved into a special workspace called "activeWorkspace" which is automatically restored when an app is restarted. Users can also save their desktop to a named workspace, so that they can switch between workspaces on the fly.

Finsemble automatically keeps track of window locations and does the work necessary to restore components to their proper position on the screen. However, if you want the actual inner state of your component to be restored, then you will need to provide workspace hooks in your component code. Whenever the state of your app changes you will need to inform the Workspace service, and when your component start, you will need to set its initial state.

Uncomment the functions getState() and setState() in accountList.js and accountDetail.js. These methods are used to save and restore the state of the component. We make a call to setState() to report any state changes. These changes are captured and saved to the active workspace. We call getState() when the component starts, to initialize the state of the component. You can save any number of fields, and the value of the field can be either a complex object or a primitive (here we're just using a string to represent the account number).

Within these functions you'll see that we use the API commands FSBL.Clients.WindowClient.saveComponentState and FSBL.Clients.WindowClient.getComponentState. You can save objects or primitives. The state data gets stored with the component configuration in the workspace JSON. If it's convenient, you can store multiple pieces of state data under different field names. Please note that the get method is asynchronous.

Now if you quit and restart your application, the account numbers in each window should be the same as where you left off.

Setting Up A Default Workspace

By default, a Finsemble Application will start up with just the toolbar. For first time users, you may want your application to automatically launch some components. The way to accomplish this is to set up a default workspace.

Open your_repo/configs/defaultWorkspaces.json. Change it to the following code:

{
    "workspaces": [
        {
            "name": "Default Workspace with No Windows",
            "windows": [],
            "components": [
                {
                    "type": "accountList",
                    "options": {
                        "top": 50,
                        "left": 50
                    }
                }
            ]
        }
    ]
}

This config tells Finsemble to open up an accountList component on initialization. This takes the same parameters that are accepted by LauncherClient#spawn. In this case, we've provided pixel locations for the window. You can add as many windows as you'd like, just include them in the array. As you might have guessed, this configuration is merged (extended) into the general configuration for your components from components.json.

In order to test your default workspace, you'll need to clear out your localStorage. With your app running, right click any component to open a debugger window. From the console, type localStorage.clear(). Now quit your application without moving any windows. Restart your dev runner. Your application should start up and display a single accountList component on the screen.

The default workspace is only used once. After loading the application for the first time, Finsemble saves to activeWorkspace which supercedes the default.

Step 10: Working With Hosted Components

So far we've been working with local components - those that you build and bundle directly into your application. Finsemble can load components dynamically, from servers on your network or from the Internet. This is useful in several circumstances:

  • You can include web pages in your Finsemble Application
  • You can modify your existing web applications to be Finsemble aware
  • You can load third party components

Currently, ChartIQ has made available the following advanced components:

  • Advanced Charting - Provides an example of ChartIQ's advanced charting incorporated into a component and made Finsemble aware. Charts remember their state and can be linked together.

    finsemble.chartiq.com/fin/components/premium/chart/chartiq-desktop.html

  • Chat - Provides an example chat component. This component is currently integrated with Slack. If you have a slack account you can use it right out of the box.

    finsemble.chartiq.com/fin/components/premium/chat/chat.html

Over time, this library will expand. If you'd like to see these components integrated with live market data in a sample application, please contact finsemble@chartiq.com and we can provide access!

Let's start by setting up a chart component!

Add the following code to your components.json config file:

        "Chart": {
            "window": {
                "url": "http://finsemble.chartiq.com/fin/components/premium/chart/chartiq-desktop.html",
                "width": 500,
                "height": 500,
                "options":{
                    "minWidth": 75
                }
            },
            "component": {
            },
            "foreign": {
                "services": {
                    "dockingService": {
                        "isArrangable": true
                    }
                },
                "components": {
                    "App Launcher": {
                        "launchableByUser": true
                    },
                    "Toolbar": {
                        "iconClass": "ff-chart-advanced"
                    },
                    "Window Manager": {
                        "FSBLHeader": true,
                        "showLinker": true,
                        "persistWindowState": true
                    }
                }
            }
        }

Restart your app and then go ahead and open a couple of chart windows. Try using the linker to link them. Charts are linked by symbol, so when you change the stock symbol for one chart the other will follow. You can set one chart to be 1D and another to be 1m in order to see short term and long term views simultaneously as you flip through stocks.


Next Steps

For more advanced reading