JavaScript Tutorial
Introduction
This tutorial is a collection of hands-on exercises, in which we will be building a web app taking advantage of many of the io.Connect features available today. As our fictitious users start using our app, we will be getting a lot of change requests to improve the app in various ways and we will be using io.Connect to accomplish this. We will start by creating an app called Client Portfolio which will (initially) consist of two windows:
- a Clients window - showing a list of clients (ID and Name);
- a Portfolio window - showing the list of equities the selected client owns (Symbol and Quantity but also the Best Bid and Offer prices, if available);
When the user clicks on a client in the Clients window, the selected client's portfolio will be loaded in the Portfolio window. As we progress through the tutorial, we will be prompted to add many more cool features like support for tab windows, handling and triggering window events, custom search and notifications, dynamic method discovery and many more.
Prerequisites
This tutorial assumes you are a JavaScript developer or a full stack web developer with sufficient knowledge of core JavaScript (ECMAScript 5 is sufficient). No knowledge of modern web frameworks such as React or Angular is assumed or required. No prior experience with io.Connect is expected but we advise you to take a look at the following resources, which we consider useful to developers:
And a few resources related to core JavaScript, if not already familiar with these:
We will be working with our latest development drop at the time of writing this tutorial, but the information here will be updated as new features are implemented.
There is also a .NET version of the tutorial available here. A Java tutorial can be produced on demand.
Tutorial Structure
The tutorial code is available for download at GitHub. Clone the repo and you will get the latest version of the tutorial code. Checkout the branch named GD3-Start to begin.
The tutorial directory is organized as follows:
- app - put the files you will be working on here. More on that in Chapter 1;
- lib - all of the browserified
io.js
files; - resources - a sleek Tick42 icon;
- solution - the full tutorial solution;
- start - the startup app skeleton;
- support - all of the support apps you will need to complete the tutorial, bundled with the corresponding configuration files;
- util - a simple image to base64 converter;
io.Connect for JavaScript is also available as versioned NPM packages (and also runs in Node.js), but to keep things simple, in this tutorial we are going to use the latest development version, browserified and ready to be included in your HTML pages. The tutorial is broken into several parts, each demonstrating different io.Connect capabilities, and each part will depend on the completion of the previous ones. We will try to minimize the amount of boilerplate code not related to io.Connect, so we will have all HTML, CSS and some JavaScript files already coded and ready to use. Plain HTML and JavaScript are used throughout the tutorial to avoid confusion with other technologies. The coding you are required to do is marked with TUTOR_TODO
comments, so make sure you search for all of these in all parts of the tutorial. Even though each chapter depends on the previous one, we have provided all the necessary support files, apps and code from the beginning, so you can start and complete the tutorial with your own additions to the starter skeleton, without losing progress.
Each part will try to introduce you to just one io.Connect concept, in as little detail as necessary to complete it. You can get a lot more detailed information about the io.Connect APIs in the API guides and references.
Naturally, we have also included a full tutorial solution, which follows the app structure set in the startup skeleton. Also included are partial tutorial solutions at the start of each task to get you going. If you get stuck and wish to start with certain tasks completed, simply checkout the branch with name corresponding to the chapter and task number you want to start from.
1. Setting Up
1.1. Getting Started with the Tutorial Files
Getting started with the io.Connect for Javascript tutorial is very easy. The first thing you need to do is get all of the tutorial code. The latest tutorial version is available at GitHub, so just clone the repo and you will get everything you need to get started. Next, run npm install
to get all the tutorial dependencies:
cd /*path-to-tutorial*
npm install
As we will be building a web app, we will need a server. Actually, we will need two servers - one will host our apps and one will supply us with fake data, so we can have something to work with. We have set this up for you, so just run npm start
. This will launch a simple HTTP server, which will host the tutorial directory at http://localhost:22909/
. You will also get a simple REST server at port 22910. After that, you need to start up io.Connect Desktop. The io.Connect Desktop setup is outside the scope of this tutorial, so we assume you already have it installed and configured. If you don't have it yet, download and install it from here.
1.2. Deploying and Publishing Your App
In the start
folder you will find all the files (HTML and JavaScript) you will be working on. Together they form the tutorial startup skeleton. Begin by copying these files into the app
directory.
It is a good idea to read more about io.Connect initialization and configuration before you start with this part of the tutorial.
Now, we need to initialize io.Connect. It is very important how you configure io.Connect. The defaults (e.g. passing an empty configuration object) should be sufficient for most apps. However, we have prepared an io.Connect configuration object in lib/tick42-glue/tick42-glue-config.js
. You will find TUTOR_TODO Chapter 1.2 Task 1
in portfolio.html
and TUTOR_TODO Chapter 1.2 Task 2
in clients.html
, where you will be prompted to include the io.Connect configuration object and the io.Connect library. Once you have included the configuration and the library in both HTML files, you will be able to initialize io.Connect and add the variable io
to the global namespace. To do this, go to clients.js
, find the TUTOR_TODO Chapter 1.2 Task 3
and:
- call the io.Connect factory function
- pass the config object from
lib/tick42-glue/tick42-glue-config.js
- when the
Promise
is resolved, assign the receivedio
instance to the window object (global namespace) - call these five functions, which will bootstrap the app and define the skeleton of the app:
checkGlueConnection()
;setUpUi()
;setupClients()
;registerGlueMethods()
;trackTheme()
;
- Finally, don't forget to catch and log any errors.
Now find TUTOR_TODO Chapter 1.2 Task 4
in portfolio.js
and do the same. It is pretty much identical, apart from the functions you need to call in the then()
statement.
Publishing the App
The app has initialized io.Connect and is deployed (http-server
), now you should publish it, so it is available to your users in the io.Connect launcher. In this tutorial, we will explain and work with the file configuration mode.
We have a session dedicated to on-boarding your apps, where we will show you how to prepare and manage configurations in the io.Connect Configuration Manager using an utility and a set of use case oriented batch files.
You can read more about app definition in the configuration documentation which explains in detail how the io.Connect Desktop configurations work. For this part of the tutorial, all you need to know is that:
- io.Connect Desktop components (windows and native executables) are configured in JSON files, where the most important part is the URL (or path) of the component.
- The configuration files are stored in
%LocalAppData%/interop.io/io.Connect Desktop/Desktop/config
.
Note: Configuration changes are detected in real-time, so once you change a configuration file, you don't need to restart io.Connect Desktop to detect the changes.
To publish the app:
- Copy the
tutorial-*-applications.json
files (from/start/configs/chapter-1
) to the app definition folder (%LocalAppData%/interop.io/io.Connect Desktop/Desktop/config/apps
). - If you have followed the above instructions and copied the files to the
/app
directory, you wouldn't need to make any changes. If, on the other hand, you decided not to place them in/app
, then you need to modify thetutorial-*-applications.json
files so that they contain the correct Clients and Portfolio windows URLs.
Once you publish your apps to io.Connect Desktop, you should be able to launch any of them. Open the Developer Console (F12
) and type io
or io.info
to verify io.Connect has been loaded.
You should verify that everything works:
- Make sure both Clients and Portfolio appear in the io.Connect launcher.
- Click on both apps to launch the windows.
- Verify that Clients loads a sample list of clients.
- Both windows should have a green field in the top left corner saying Glue is available.
Here are a few things that might have gone wrong:
- "I don't see the apps": make sure you have copied the JSON files to the correct configuration folder and the files from
/start
to/app
. - "I see the apps, but when I click them, I see a 'Page Not Found' message": make sure you have run
npm install
andnpm start
, which will fire up the server you need. Also, double check the URLs in the configuration files - the apps are trying to reach the files in the/app
directory of the tutorial. Make sure to either put your files there or change the URL to look in/start
. - "I have the apps and both windows are loading, but Clients doesn't display any clients": you need to run
npm start
, which will start the REST server that provides the fake data. - "I don't have an io.Connect launcher": you need to run the io.Connect Desktop app.
- "Pages load but nothing works": hit
F12
and have a look at the console output for any errors. - "Something else happened": let us know, and we will update this tutorial section as we resolve it.
2. Interop Methods
Overview
Now that we have the apps defined, it is time to glue them together before we show them to our users. In this part, we will "listen" for party changes in the Portfolio window by registering an Interop method and will trigger such a change from the Clients window by invoking an Interop method. In order to complete this part, you should have the Interop documentation and reference at hand.
2.1. Registering Methods
We will start with the Portfolio window, where we will demonstrate registering an Interop method (SetParty()
), which, when called, will trigger the window to re-load the portfolio for the selected party (client).
Since different apps might be using different party identifiers, when we define a party, we will define it as a Composite
and use the party ID's as members of this composite argument. We also need to check if this hasn't been already defined, and either re-use it directly, if it contains all we need, or consult with the other developer teams to include our party ID.
Assuming we are the first to propose the party object, our method registration will have:
- name:
"SetParty"
; - display name:
"Set Party"
; - description:
"Switches the app window to work with the specified party"
; - will accept a
Composite
argument calledparty
with 2 optionalparty
identifiers (strings) -pId
andsalesForceId
, of which we will only use thepId
in this tutorial;
Note: Only the name property and the method handler are required during registration but the best practice is to always populate the display name and description, because as we will see later, the display name can actually be shown in a context menu, and the description can be used both as a tool tip, and as documentation for our colleagues.
Your task is to register SetParty()
- in portfolio.js
, look for TUTOR_TODO Chapter 2.1
and do that. Once your method is invoked, call loadPortfolio()
and pass the pId
of party
. You may want to read about Interop Method Definition before you start implementing this part.
Note: You don't need to move to the Clients window and code the method invocation there in order to test the method. Verify that it works by invoking SetParty()
from the Developer Console. Simply typing SetParty()
in the console won't work, because SetParty()
isn't a method that is defined in the global namespace. SetParty()
is a special Interop method and should be invoked via the API:
const invokeArgs = {
party: client
};
io.interop.invoke("SetParty", invokeArgs);
This means that once you have agreed upon the io.Connect interface with your colleagues, you can test your windows without having to wait for the rest of the windows to be developed.
2.2. Invoking Methods
Now that our Portfolio window responds correctly to party changes, let's switch to the Clients window and trigger these party changes by invoking the Interop method SetParty()
and passing the selected client as a party
object.
Open clients.js
and find TUTOR_TODO Chapter 2.2
to add the code which performs the method invocation. You should pass the client
object for the party
argument. You may need to check the Interop Method Invocation for more information.
A common mistake developers make when trying to invoke a method with a Composite
argument typically looks like this:
const party = { pId: 123, salesForceId: 456 };
io.interop.invoke("SetParty", party);
The problem is that the invocation expects an object containing the method arguments, so passing the party
object really instructs Interop to send pId
and salesForceId
as the two arguments of the invocation. You need to pass the party
argument wrapped in the request object, like so:
io.interop.invoke("SetParty",
{ // start of arguments
party: { // just one argument - party, which is a composite
pId: 123,
salesForceId: 456
}
});
You should test whether the method invocation works. Again, you don't need to run the Portfolio window to do so. All you need is this method to be available (but don't care which app offers it), so you can register a mock method implementation in the Developer Console (F12
). Try that, run the Clients window, register the method in the Developer Console and check if the invocation works.
Now that we have tested whether SetParty()
is correctly registered in the Portfolio window, and whether we are invoking it correctly from the Clients window, we can expect that the windows can interoperate. Launch both windows and verify that clicking a client from the Clients window loads and displays the client's portfolio in the Portfolio window.
By default, io.Connect Desktop windows are sticky (which can be controlled by the configuration setting or at runtime, when opening a window), so try sticking the two windows together.
2.3. Object Types and Method Discovery
Some of our users have Thomson Reuters Eikon installed, while others have Bloomberg Terminal. Both are software products providing a set of financial tools used to see news and various charts about certain instruments. Users say they have to constantly switch between apps, copying and pasting ticker symbols from our app to Eikon or Bloomberg. Both terminal apps have an io.Connect add-in which registers methods like Show News and Show Chart and we can easily offer these in our Portfolio window, but we would rather not hardcode the logic to show these actions in the Portfolio window of our app, especially since some users may not have any of these apps installed.
In io.Connect, the concept of context-sensitive methods works by setting up a context (which we call Object Type) when you register a method. The idea is that one app registers a method(s) tagged with one or more object types, and then another app(s) can dynamically discover (at runtime) the method(s) for a particular object type.
We will extend the Portfolio window to use object type method discovery to figure out which methods are available for a particular instrument (the object type will be "Instrument").
Search for the TUTOR_TODO Chapter 2.3 Task 1
in portfolio.js
. Your first task is to extend the logic of the row.onclick
event. When a row (instrument) is clicked, you should find all registered methods (io.interop.methods().filter()
) with an "Instrument" object type and then invoke addAvailableMethods(*found methods*, rowData.RIC, rowData.BPOD)
. Now when an instrument is clicked, a modal window will appear listing the currently registered methods.
Your second task is to locate TUTOR_TODO Chapter 2.3 Task 2
in the invokeAgmMethodByName()
and implement the logic for invoking an Interop method with the passed methodName
and params
. You may need the documentation for Object Type Targeted Method Invocation.
To verify that everything works, we have registered two methods with an Instrument
object type in the provided code. You should be able to see them and call them when clicked.
Now, when users click on an instrument in the Portfolio window, we are going to show them the display names of the available methods, and when they click on a method, we will invoke the method, passing the selected instrument.
We are excited we have a decent proof of concept, so we are going to show it to our clients and see what they have to say.
3. Interop Streams
In this part we will use the Interop Streaming API to add real-time market data to our app.
Our users say they mostly use Eikon to see real-time market data. They say that if we can bring the best bid and offer (BBO) prices in our app, they will have a lot more screen real estate.
For the purpose of this tutorial, we have provided a Sample Price Publisher (/support/sample-price-publisher
), which streams fake bid and offer prices. Before you proceed, make sure you copy the config file (/support/tutorial-sample-price-publisher-applications.json
) to the app definition folder and launch SamplePricePublisher from the io.Connect launcher, just like you did with Portfolio and Clients.
Your task is to implement the subscribeBySymbol()
and the unsubscribeSymbolPrices()
functions. Open portfolio.js
and find TUTOR_TODO Chapter 3 Task 1
. In subscribeBySymbol()
you need to subscribe to a stream named T42.MarketStream.Subscribe
(created by the Sample Price Publisher), which expects a single subscription argument called Symbol
(string).
When the Promise
returned by interop.subscribe()
is resolved, get the subscription and subscribe to its onData
event, and upon receiving the data, simply invoke the callback passed to subscribeBySymbol()
, which will do the rest.
Next, in portfolio.js
find TUTOR_TODO Chapter 3 Task 2
. In unsubscribeSymbolPrices()
you need to traverse the currently active subscriptions and close each one. This is necessary, because when we load a new portfolio, we want to clear the existing symbol subscriptions and subscribe to the new ones.
As a result, the Bid and Ask cells should be updated every second with a new value.
You may need the Interop Stream Subscription documentation. You can also have a look at the Stream Publishing API and our implementation of the Sample Price Publisher.
4. Window Management
Overview
io.Connect Windows can be extensively customized via:
- io.Connect Window configuration - at io.Connect Desktop deployment time;
- io.Connect Window Groups configuration - at io.Connect Desktop deployment time;
- App definition - at app deployment time;
Once created, the developer is in full control of the windows using the Window Management API.
4.1. Opening Windows at Runtime
The users are starting to like our app, but they are saying it is inconvenient having to push two buttons in the io.Connect launcher, then grab the Portfolio window and stick it to the Clients one. They also say that they always put the Portfolio window below the Clients one.
In this chapter we will first remove Portfolio from the io.Connect launcher, then add a button on the Clients window, and finally use the Window Management API to open the Portfolio window and stick it below the Clients window. You may need the Windows Management API guide and reference in order to implement this part.
Your task is to remove the Portfolio app from the io.Connect launcher and use the Window Management API to open it directly from the Clients window, instructing io.Connect Desktop to position and stick it below the Clients window.
Begin by modifying the Portfolio JSON file in the io.Connect Desktop configuration folder:
{
"hidden": true // this hides the Portfolio app from the io.Connect launcher
}
Next, in clients.html
find TUTOR_TODO Chapter 4.1 Task 1
and uncomment the button tag. In clients.js
, find TUTOR_TODO Chapter 4.1 Task 2
in the setUpUi()
function. Invoke the openWindow()
, pass as arguments a name for the window ("Portfolio", for example), reference to the current window (io.windows.my()
) and a direction ("bottom"
). It is best to use this function, because we will be calling it from multiple places throughout this tutorial. Then, in openWindow()
, find TUTOR_TODO Chapter 4.1 Task 3
and create an options
object, which you will use to set three important settings:
{
mode: "flat"
relativeTo: myWin.id
relativeDirection: direction
}
The combination of relativeTo
and relativeDirection
allows us to stick the newly created Portfolio window to the bottom of the current window.
Note: We will use the window id
as the value for the relativeTo
property, not the entire window reference.
Finally, use the Window Management API to open a new Portfolio window:
io.windows.open(
windowName,
window.location.href.replace("clients.html", "portfolio.html"),
options);
Note: Opening windows using the Window Management API is OK, but, ideally, you would want to use the App Management API to manage your apps and subscribe to app/instance related events. We will take a deeper look at the App Management API in Chapter 5.
4.2. Window Styles
The users loved that but said they still see two separate windows on the screen and when they close the Clients window, the Portfolio one will still hang there. They asked us if we could make it close together with the Clients window. Also, they complain that they can shrink the windows as much as they want and would prefer, if we restricted both windows to a certain minimum size. Finally, they don't like the "white spot" on the window which stays until the data is loaded.
In this chapter we will use Window Styles to convert the windows to borderless HTML windows, and we will remove the window controls (minimize, restore and close) from the Portfolio window. We will set sizing restrictions on both windows, and we will also start listening for io.Connect lifecycle events (io.windows.windowRemoved()
) in the Portfolio window so that when the Clients window is closed, we will close the Portfolio one as well.
Finally, we will use loader animations to show the default loading animation (feel free to change it) while we are loading data.
The first thing you need to do, is to go to the Clients app JSON file (in the io.Connect Desktop configuration folder) and add or modify:
{
"details": {
"minHeight": 400,
"minWidth": 600,
"mode": "html",
}
}
Then open the Portfolio app JSON file and add or modify the following:
{
"details": {
"allowClose": false,
"allowCollapse": false,
"allowMaximize": false,
"allowMinimize": false,
"minHeight": 400,
"minWidth": 600,
"mode": "html",
}
}
These settings will make our app feel more like a single app, instead of two separate apps and will also set the window restrictions we were asked to do.
In clients.js
find TUTOR_TODO Chapter 4.2 Task 1
- you will need to tweak the options
object to set various allow
attributes and dimensions. Your task is to make sure that the newly created Portfolio window:
- is in HTML mode;
- can't be minimized, maximized, collapsed or closed;
- has minimum height of 400 and minimum width of 600 pixels;
The window settings documentation may come in handy here.
Important note!
You may have noticed that we are configuring the Portfolio window twice (once in the config file and once from the options
object in our code). The reason is that for the time being we are using the Window Management API to open a new window/app. When we do this, the app config file isn't taken into account. The proper way to open an app is by using the App Management API, which we will take a look at in Chapter 5. When we start using the App Management API, our settings in the config file will be taken into account, but we could still override them with our options
object.
Finally, in order to detect when the Clients window is closed, you will use a Window Management API event (windowRemoved()
), so that when Clients is closed, you will close the Portfolio window as well.
But how would you know which window is the Clients window? The user could be running multiple instances, so you can't simply test whether the name of the window starts with clients_window
. So, we need to pass the instance (window id
) of the Clients window to the Portfolio. One way would be to pass it in the URL, but what if we want to pass more complex data (objects, arrays or a mix of them)?
io.Connect allows one window to pass a context (JSON-serializable object, a non-cyclic directed graph) to another window, via configuration (in the JSON file) and programmatically (in the io.windows.open()
call). So, you will need to pass your window id
as the initial context to the Portfolio window you are opening, so that in the Portfolio window you can subscribe for io.windows.windowRemoved()
and close the Portfolio window when the Clients window is closed. Create a context
attribute (object) in the options
object, and set any properties you want, e.g. parentWindowId: yourWindowId
.
After that, in portfolio.js
find TUTOR_TODO Chapter 4.2 Task 2
in the setUpWindowEventsListeners()
function. You have to subscribe for io.windows.onWindowRemoved()
and in the callback, check whether the id
attribute of the passed window object matches the id
which you passed in the initial window context, and if they do, close the Portfolio window (io.windows.my().close()
).
As a last touch, go to clients.js
and add the loader animations in the getClients()
function, where you will find TUTOR_TODO chapter 4.2 Task 3
- for showing the loader, and TUTOR_TODO chapter 4.2 Task 4
- for hiding it. Show the loader before you initiate the request (io.windows.my().showLoader()
), and hide it (io.windows.my().hideLoader()
) in the always event of the $.ajax
Promise
.
4.3. Tabbed Windows
The users say it would be great if they can see multiple client portfolios at once. They aren't sure how this will work, because they worry about screen real estate.
In this chapter we will use the Tabbed Windows feature to extend our app. We will keep the option to open a generic Portfolio window below the Clients window, but we will add the option to stack individual Portfolio tabs to the right of the Clients window. The idea here is to create a tab frame the first time we select a client, put the Portfolio window in it and every time the user clicks on a different client, we will add another window as a tab in the same tab frame. We are also going to name them after the selected client by using the setTitle()
method on the window object, and also demonstrate passing context from the Clients to the Portfolio window, something which is unique to io.Connect Desktop.
Now, find the TUTOR_TODO Chapter 4.3 Task 1
in clients.js
, inside the openTabWindow()
function. We call this function every time a client (row) is clicked (the same way we invoke an Interop method) and pass the selected client. You should also check if the client's tab is already opened - if so, you should activate()
it.
Opening a tab window means that you will have to use a different window mode in the window style attributes - tab
. In order to put all tabs in a single tab frame, you will also need to set the tabGroupId
to the same value for all tab windows - it could be any string, e.g. PortfolioTabs
.
{
mode: "tab",
tabGroupId: "PortfolioTabs"
}
To put the Portfolio windows on the right of the Clients window, make sure you pass the Clients window id
in the relativeTo
attribute, and set relativeDirection
to "right"
. Only the first Portfolio tab needs the relativeTo
and relativeDirection
attributes. Provided that consequent tabs specify the same tabGroupId
, they will be grouped together. The documentation for finding windows may help you here.
Finally, in order to pass the selected party
to the newly created Portfolio tab, use the context
object to pass the party
object.
Next, in portfolio.js
, navigate to TUTOR_TODO chapter 4.3 Task 2
in the setUpAppContent()
method. Up until now we registered the Interop methods, but now we may not need to do that. If the current Portfolio window is created as a tab, we shouldn't register the method, because this will break our idea of individual portfolio windows. Achieving this effect is pretty easy:
- Get the window context.
- Check if it contains the passed
party
object. - If it doesn't, this means that the Portfolio window was opened by clicking the button at the bottom of the Clients window, in which case let the code register the Interop method for handling
party
changes. - If it does have a
party
object, this means that this Portfolio window is for a specific client, and in that case you don't want to listen for changes, because, if you do, all tabs will display the same data. Instead:- get the
party
objectpreferredName
attribute and use that to set the title of the tab (usingio.windows.my().setTitle
); - assign the same title using
document.getElementById("title").textContent()
; - call
loadPortfolio()
passing thepId
attribute of theparty
object;
- get the
4.4. Custom Window Frame Buttons
Our users loved the tabs so much that they want us to extend this functionality. First of all, they would like a bit more flexibility when positioning the tabs and the Portfolio window. Preferably, as windows open, the app should "just stack them to the bottom" of the Clients window, but if there is no space, it should "just stack them to the right" of the window. Also, our users hate the big Portfolio button and want something more subtle which doesn't take that much space. Furthermore, they said that it would be great if we could somehow allow them to scatter the tabs as individual windows so that they can move them around freely, but also have a convenient way to gather them back in a frame and stick this frame back to Clients.
Yes, that is too much. But io.Connect can enable your app to do all of the above (and more), so let's tackle this problem one piece at a time.
Adding a "Subtle" Button to Open the Selected Client's Portfolio
The Window Management API supports adding what we call frame buttons. You can add, remove and handle clicks on any number of buttons. A frame button is called so, because it is added to the frame (top-most, non-content portion) of the current window.
In clients.js
, inside the setUpUi()
function, find TUTOR_TODO Chapter 4.4 Task 1
and create a frame button. Then subscribe to frame button click events and open a Portfolio window when your newly created button is clicked. The image for the button must be in base64 and if you are wondering how to convert an image to base64, you can use the util/convert-image-to-base64.html
"utility" page to create one. If you don't feel like creating your own button image, you can just uncomment and use the provided buttonOptions
object.
Now would be a good time to delete or comment-out the big blue Portfolio button from clients.html
, because we have a much nicer-looking frame button.
Stacking Portfolio Windows
The trickier part now is to start stacking Portfolio windows either at the bottom or at the right of the current window. Stacking is easy, you could use snap
or relativeTo
, but first you have to check whether the window will be off the bounds of the screen.
The idea here is to snap the Portfolio window or tab group to the bottom of the Clients window, if there is enough space on the screen, otherwise stick it to the right of the Clients window.
The io.Connect Browser injects several useful objects into each window. One of them is iodesktop.monitors
. This returns an array of objects describing each physical monitor on the user's desktop. For the tutorial, assume the user is running on the primary one (the isPrimary
property of the monitor
object is set to true
). Once you have identified the primary monitor, you can use the workingAreaHeight
property, which gives you the usable height of the monitor (minus the task bar).
Now, you can check if the bottom of your window (top + height) + the height of the Portfolio window (the default height is 400
but we will expose an originalHeight
on the window object) is less than the working area height.
The Window Management API offers a property called bottomNeighbours
(returns an array of all windows attached to the bottom of the current window) for each window object, so for the purpose of this tutorial, simply check if the Clients window has any bottom neighbors. If it doesn't and if it passes the above check, snap the Portfolio window or tab group (depending on what is clicked) to the bottom (you can re-use the openWindow()
function and pass "bottom"
), otherwise pass "right"
.
Locate TUTOR_TODO Chapter 4.4 Task 2
in getWindowDirection()
in clients.js
to get the primary monitor and return a direction
. Then, in TUTOR_TODO Chapter 4.4 Task 3
pass the direction
to openTabWindow()
as a second argument, instead of the hardcoded "right"
.
In TUTOR_TODO Chapter 4.4 Task 4
use the Window Management API for handling frame button clicks to handle a frame button click, check the id
and open a Portfolio window. Get the direction and pass it as a third argument to openWindow()
.
Gather and Scatter Windows
This is a bit more involved, but cool as well. Check out the GIF at the end of this section to get a better idea of the task at hand.
The idea here is to add a frame button to the Portfolio tab window, which will either scatter the grouped tabs into separate windows, or will gather them into a tab frame, if they are scattered. So this actually requires two frame buttons, and you will be showing only one at a time, depending on whether you have more than one Portfolio tab in the tab frame. If there are two or more tabs in the group, you need to show the Extract button, otherwise show the Gather button.
In portfolio.js
, in the setUpTabControls()
function, find TUTOR_TODO Chapter 4.4 Task 5
. You should first check if your window is tabbed (io.windows.my().mode
is tab
) and if not, you shouldn't do anything else.
In TUTOR_TODO Chapter 4.4 Task 6
, if the window is a tab, you should create the Gather and Extract buttons, similarly to the way we created the frame button in the Clients app. Simply uncomment the buttons. You should also subscribe to the following events:
Event | Description |
---|---|
onWindowAttached() |
A window (not the current one) has been attached to the current window tab frame. |
onWindowDetached() |
A window (not the current one) has been detached from the current window tab frame. |
onAttached() |
The current window has been "tabbed" |
onDetached() |
The current window has been "untabbed" |
You should handle all these events and decide which button (Scatter or Gather) to create or remove in each scenario. Also, before adding or removing a button check if it is present or not.
In TUTOR_TODO Chapter 4.4 Task 7
you should implement logic to decide which button was clicked and find a way to keep track of detached tabs.
In TUTOR_TODO Chapter 4.4 Task 8
you should use the Window Management API and decide which button should be rendered - Gather or Scatter.
Now, for the actual implementation of scattering/gathering windows. The idea is simple:
- When the Scatter button is clicked, we should go through all tabbed windows and call
detachTab()
on each of them, and we also need to keep track of all windows that were detached (so we can gather them later). - When the Gather button is clicked, we should go through all detached windows and gather them in a tab frame - technically, this means snapping the first window to the Clients window, then attaching the rest of the detached windows to this first tab.
So, where do we keep the state - the windows that were detached and that we will later need to be re-attached? This needs to happen at a "central" place, but until you have learned about Shared Contexts, we can keep them in the Clients window itself, or more specifically - in its context.
Yes, in addition to passing a context to a window during creation, you can also update a window context at runtime by calling window.updateContext()
. So, you can keep the detached windows (only their id
's) in an object with a single property detachedTabs
, or whatever you like, which will be an array holding the window id
's of the detached tabs.
You already have the Clients window id
, because in the previous chapters you passed it upon creating the Portfolio window. Now, the io.windows.findById()
method will come in handy for getting a reference to the window itself by its id
.
So, Scatter is clicked, go over all tabs (io.windows.my().tabs()
), add them to an array and detach them. Then store the array in the Clients window context.
Finally, upon clicking Gather, get the detached windows from the Clients window context, find the first window, snap it to the right of the Clients window, and then go over the rest of the detached windows and call firstWindow.attachTab(detachedWindowId)
.
5. App Management
We are making a good progress with our app, but now is a good moment to take a step back and improve on something. Up until this moment we have been using the Window Management API to open apps. This works fine, but it isn't the proper way to manage apps in io.Connect Desktop. Now, we are going to take a look at the App Management API and see how and why we should use it to open our apps.
As we previously discussed, opening apps via the Window Management API completely bypasses the settings in the JSON file. This leads to some confusion and possibly mistakes. Opening an app via the App Management API ensures that our settings will be taken into account and we can still override them in our code (by providing a windowSettings
object).
By using the App Management API you don't need to know the URL of the app, which, in cases where there are multiple environments and regions, is very helpful and ensures you will launch the correct app.
Furthermore, the user might not be entitled to open a given app and using the Window Management API bypasses the entitlement checks done by io.Connect, only to fail later when the newly opened window loads.
Modifying your code to use the App Management API is quite easy. In clients.js
locate the TUTOR_TODO Chapter 5 Task 1
inside openWindow()
. Here you will need to make two objects: one is the context
and the other one is the windowSettings
object. Basically, the second one should contain everything we currently have in the options
object, apart from the context
itself. After that, call the start of an app using the proper API: io.appManager.application("Portfolios").start(context, windowSettings)
.
You can also remove the window style properties like minHeight
and allowMinimize
and set them in the configuration file.
Next, in the openTabWindow()
function find TUTOR_TODO Chapter 5 Task 2
and go through the same process as in Task 1
.
You can't re-use the filtering you (likely) did in openTabWindow()
, because you can't set the window name. So, you need to get creative. What else is unique to the tabs that we can use as a filtering condition?
6. io.Connect Search Service
⚠️ Note that this chapter refers to the deprecated legacy search service and not the currently available Search API. You can skip this chapter until the tutorial is updated.
Our users say some instruments don't have a ticker symbol, but either CUSIP, or ISIN, or SEDOL, and they would be very interested in a solution which lets them search by any instrument identification code when they are managing a client's portfolio.
In this chapter we will use the io.Connect Search Service (GSS) and Client Search APIs to offer this functionality. We don't have the actual search backend yet, but that is fine because we have sample Java and JavaScript GSS publishers which we can feed with sample data. Switching search providers is a matter of configuration, so our app is actually oblivious of what the real data source is.
Your task here is to allow the user to search for instruments and add them to the portfolio. For this, you will be using GSS.
In this tutorial we will be using the GSS simple wrapper API.
You will need a supporting app which is the sample JavaScript Instrument GSS provider. The configuration file is named tutorial-instrument-provider-applications
, it is located in the support
directory and you should copy it to %LocalAppData%/interop.io/io.Connect Desktop/Desktop/config/apps
.
The Instrument GSS Provider will start automatically when you launch io.Connect Desktop and it should just display "Sample GSS Publisher".
Now, switch to your code and add the gss-client-search.js
(located in lib/tick42-gss
) to your portfolio.html
in TUTOR_TODO Chapter 6 Task 1
.
Find TUTOR_TODO Chapter 6 Task 2
in the initInstrumentSearch()
function. You need to:
- create a search client -
new gssClientSearch.create(gssOptions)
; - create a query and save it -
createdClient.createQuery("Instrument")
; - subscribe to the query
onData
event and pass the received data todisplayResult()
;
Finally, in the search()
function you need to find TUTOR_TODO Chapter 6 Task 3
and use the created query, call its search method and pass the searchValue
.
When you are ready, your apps should look like the example below. Type part of a symbol which you know exists (e.g., we are using "B" in the example, because we know there is "Barclays" and "BMW") and hit the Search button (no auto-completion is required for the tutorial). You should see a pop up with the GSS results displayed. Click on a search result symbol and it will be added to the portfolio.
You are ready. Try playing with the ClientSearch
options to see how data comes in and out.
7. io.Connect Notification Service
⚠️ Note that this chapter refers to the deprecated legacy notification service and not the currently available notifications and Notifications API. You can skip this chapter until the tutorial is updated.
The market data team is now sending GNS notifications when a major financial event occurs, which requires advisors to call clients who can lose (or gain) a lot of money, because of a sudden market or instrument price change (e.g., Apple buys Microsoft, or a Grexit/Brexit). Our users are very happy with the notifications, but say that as we own the client portfolios, we surely know who the affected clients are, and wonder if we can do something about it.
We ask the market data team to add io.Connect Routing to their GNS notifications so we can trap these on the desktop, instead of in the generic GNS notifications UI. They will route the notification so that when it arrives on the user's desktop, the user will still be able to see the notification toast, but when they click it, it will take them to our new "who-to-call" window instead of the generic notification details window.
In this chapter, your task is to handle notifications from your app by registering an io.Connect Routing handler for GNS notifications in your app.
In order to complete this task, you will need a supporting app (GNS publisher) which is called Market Data Monitor. From it you can manually raise notifications which you will see in the GNS UI. The configuration file tutorial-market-data-monitor-applications.json
can be found in the support
directory. You should copy it to the io.Connect Desktop app config folder (%LocalAppData%/interop.io/io.Connect Desktop/Desktop/config/apps
). Then, in order to register Market Data Monitor as a GNS publisher, you should copy this configuration to %LocalAppData%/Tick42/GnsDesktopManager/Config/overrides.gnsDesktopManager.LOCAL-GD3-GLUE4OFFICE.properties
, but be careful not to replace any of its previous contents.
gns.providersRepository.MarketData.type=AgmServer
gns.providersRepository.MarketData.enabled=true
gns.providersRepository.MarketData.agmServer.serverIdentity.machineName=
gns.providersRepository.MarketData.agmServer.serverIdentity.applicationName=MdMonitorJsAgmGnsServer
gns.providersRepository.MarketData.agmServer.serverIdentity.user=
gns.providersRepository.MarketData.agmServer.serverIdentity.environment=
gns.providersRepository.MarketData.agmServer.serverIdentity.region=
gns.providersRepository.MarketData.query.types=
gns.providersRepository.MarketData.query.attributes=
gns.providersRepository.MarketData.query.states=
gns.providersRepository.MarketData.query.limit=
gns.providersRepository.MarketData.query.lastSequenceId=
gns.providersRepository.MarketData.query.lastModifiedSince=
gns.providersRepository.MarketData.query.includePayload=
You should check out the source code of this app, after you are done with the demo, to understand how you can create a GNS provider on the desktop.
The Market Data Monitor will start automatically. That is one of the features of io.Connect Desktop.
You should now implement the TUTOR_TODO Chapter 7
in clients.js
. In the handler of g42.FindWhoToCall()
, open the provided symbolPopup.html
using io.windows.open()
, passing the symbol
attribute from the invocation arguments in the context.
When you are done, launch the GNS UI and raise a notification from the Market Data Monitor. You should see a toast at the bottom right corner of your screen, and also see the notification in the GNS UI.
Double-click the notification. Instead of showing the details of the notification, the GNS UI will follow the routing and call your g42.FindWhoToCall()
method. When this happens, your task is done and your app will show a popup with the affected clients (hardcoded), which looks like this:
8. io.Connect Outlook Adapter
This tutorial assumes that you already have io.Connect Desktop Office Adapters installed, but you can find the documentation here.
Knowing our users spend a considerable amount of time working with Outlook, where they usually have to copy and paste data from our apps, we will try to surprise them by adding the ability to compose an e-mail at the click of a button, right from our web app.
First, you should include glue4office-dev.js
(located in lib/tick42-g4oe
) in portfolio.html
with TUTOR_TODO Chapter 8 Task 1
. Then you need to uncomment the Send as e-mail
button from TUTOR_TODO Chapter 8 Task 2
, which, when clicked, will send the data the user is looking at in a new message, along with an attachment of the raw data, ready to be imported and analyzed into other apps, such as Excel.
Next, you will need to enable the io.Connect Outlook Adapter by running _EnableGlueOutlook.cmd
in %LocalAppData%/Tick42/GlueOutlook
. Exit Outlook before attempting to install the Adapter.
You should then implement the TUTOR_TODO
's in portfolio.js
. There are some instructions and you can also view the Glue4Office Outlook API.
Begin by initializing Glue4Office
in TUTOR_TODO Chapter 8 Task 3
and TUTOR_TODO Chapter 8 Task 4
. This is done pretty much the same way as when initializing io.Connect itself, but with additional options, which you will find in portfolio.js
. The important part here is to pass the current io.Connect instance to Glue4Office
, because otherwise it will create its own which will cause conflicts. The resolved Promise
will return a Glue4Office
instance, which you should assign to the window object, just like you did with io.Connect.
You don’t need to actually build a portfolio table using HTML, just prepare some HTML, and some CSV data for the attachment. We have prepared all the email content for you, but feel free to explore it.
Finally, in TUTOR_TODO Chapter 8 Task 5
, compose a new email (g4o.outlook.newEmail()
) passing the content
object.
Now run Outlook. The Adapter runs within Outlook, and it will start it for you. You can read about how to configure an app to start automatically with io.Connect Desktop in the app definition guide.
When you are ready, you should be able to compose a new email from io.Connect within Outlook.
9. io.Connect Excel Adapter
Some of our users are power Excel users and they have asked us if we could somehow let them export the data from our app into Excel for some last minute modifications and then get it back into our app to be submitted to the backend. We can use the io.Connect Excel Adapter which gives us exactly that functionality.
Your first task, TUTOR_TODO Chapter 9 Task 1
, is to uncomment the Send to Excel
button in portfolio.html
. Next, in portfolio.js
you need to enable Excel in the Glue4Office
config object in TUTOR_TODO Chapter 9 Task 2
. Then in TUTOR_TODO Chapter 9 Task 3
create an Excel spreadsheet (g4o.excel.openSheet()
), passing the supplied config object.
As a finishing touch, when the Promise
is resolved, you will receive a reference to the new sheet so you can subscribe to its onChanged()
event. When that is invoked, pass the new data to the loadPortfolioFromExcel()
method.
As a result, your app will be able to send the current portfolio to Excel when the button is clicked and will update the portfolio when the user modifies and saves the data in Excel.
10. Shared Contexts
Some of our users are looking at the Reuters Eikon all day and it has a dark background. They are complaining that our app, and apps from other teams, are giving them a headache because they are using a light theme. They would be very thankful if we did something about it.
After some brainstorming with developers from the other teams, we have decided to implement a light and a dark theme. However, users want to switch to either the dark or the light theme in all apps at the same time and would hate to do that in each app.
Using io.Connect Shared Contexts, we will broadcast the theme change to all apps on the user's desktop. When our apps start, they will query the theme name by reading the shared context and will automatically switch to the currently used theme.
In this assignment, you will need to add theme support in your app. We have already developed the theme switching app for you. It isn't a big deal, just a couple of calls, but feel free to check out its code.
- Deploy the theme switching app in io.Connect Desktop (copy the
tutorial-theme-chooser-applications.json
file from thesupport
directory) to the io.Connect Desktopconfig
folder. - Switch to your code and find
TUTOR_TODO Chapter 10 Task 1
inclients.js
andTUTOR_TODO Chapter 10 Task 2
inportfolio.js
. Subscribe for context changes (the context event is calledtheme
) in both tasks. You need to check thename
property in the context change you receive - if it isdark
, you will need to callsetTheme("bootstrap-dark.min.css")
, and if it islight
, callsetTheme("bootstrap.min.css")
. - When you are ready, you will be able to switch the theme from the theme switching app (so make sure you run that first).
Here is what your app will look like after you have implemented the functionality and have pressed the “Dark” button in the theme switching app.
11. Metrics and Logging
⚠️ Note that due to changes in the Metrics API, some of the methods are no longer available. You should skip those until the tutorial is updated.
An operative just emailed us about a pilot user claiming that when he selects a client, either the client's portfolio won't load at all or will take too long to load. He can't remember which client that was.
In this chapter, using the Metrics API and reference, we will make our app:
- add an Error Count metric to record the number of times our AJAX requests have failed;
- add a Time Span metric to capture the latency of the requests;
- create a Composite metric to record the client ID, the time and error message of the last service call exception, if any. Here, the initial value defines the shape (template) of the metric;
- put the above metrics under a metrics sub-system and set its state to RED if service goes down, AMBER if service is taking too long, and GREEN if everything is well;
We will also put some logging around the service call, which (for the purposes of this tutorial) will log to the developer console (F12
). However, you can easily log to a file on the user's desktop so that we don't need to run analysis on the recorded metrics, if we happen to reproduce this on our end.
In order to complete this chapter you need to create a sub-logger instance and use it in the loadPortfolio()
method - write to the console (info) every time the request is successful. Find TUTOR_TODO Chapter 11 Task 1
in portfolio.js
and create the sub-logger. Then in TUTOR_TODO Chapter 11 Task 2
log with logger.info
and the provided logMessage
.
Create a metrics instance, a sub-system, and set the state to GREEN
in TUTOR_TODO Chapter 11 Task 3
. Assign it to serviceMetricsSystem
.
Prepare the error count metric in TUTOR_TODO Chapter 11 Task 4
and assign it to serviceErrorCount
. Check out the Metrics API and see what countMetric()
does.
In TUTOR_TODO Chapter 11 Task 5
assign a new objectMetric
to lastServiceError
.
Lastly, assign to serviceLatency
a new timespanMetric
in TUTOR_TODO Chapter 11 Task 6
.
Now that our metrics are set up, we should use them to change system state, if needed, increment error counts and so on:
- As the portfolio starts loading, we should start the latency metric. Do this in
TUTOR_TODO Chapter 11 Task 7
by calling itsstart()
method. - The latency metric should be stopped as soon as the load succeeds (
TUTOR_TODO Chapter 11 Task 8
) or fails (TUTOR_TODO Chapter 11 Task 9
). - Now, assuming the load was successful, we need to alert if the load was too slow. If so, set the system state to AMBER in
TUTOR_TODO Chapter 11 Task 10
. Otherwise, set the system state to GREEN inTUTOR_TODO Chapter 11 Task 11
. - Assuming the loading failed - we have already stopped the latency metric, but we have yet to increment the error count. Do this in
TUTOR_TODO Chapter 11 Task 12
. If the loading failed, we should also update thelastServiceError
inTUTOR_TODO Chapter 11 Task 13
and set the state of the system to RED inTUTOR_TODO Chapter 11 Task 14
.
Congratulations!
You should now have the foundation to start working on interop-enabling your real apps.
Next step?
Build awesome interop-enabled apps!!