Interop; a system for running multiple UI5 Application instances simultaneously
Interop
Routing and Event System for UI5
This is a library for UI5 that allows many instances of UI5 applications to run simultaneously, integrated or independently of each other in sub-containers (side by side, or embedded)
Container applications can register their container locations so that any app can navigate to a chosen container (eg: Left/Right/Header/Footer/Main/Master/Other)
It also provides methods for communicating between those applications (simplified version of EventBus, for ease of use)
What can you use it for?
Use your imagination! but here’s a few examples, you can use this to compare two client records (instantiate the same fiori view twice, side by side, with different route params) Or even run completely different applications independently in the same window (for example, your Inbox in a side or top panel, so you can keep track of your job queue at all times)
Why not just use ComponentContainers?
Flexibility, Simplicity, and Routing.
Even if you were to use ComponentContainers, how do you route them? how do you maintain two route url’s on the one page? these problems are solved in my Interop solution.
And all apps that run this way, can be built in the usual UI5 way with standard routers. So all of your existing UI5 apps can be run in containers, and any new apps you build using Interop can still run as standalone applications when necessary. (aka: no dependency or tight coupling!)
Why did I build this?
I’ve seen a few attempts at building ‘frameworks’ that could dynamically call upon different UI5 applications, and I thought I could do better, so I gave it a shot. What bugged me most about these solutions I saw, was that any app or view used in them would follow a very custom pattern and was usually tightly-coupled. I wanted to do it in a reliable way, a standardised way, one that didn’t require re-training of staff to be able to use it, so any ui developer could write a standard app and it would work within it.
I also thought it would be a very cool thing, to be able to run an Inbox application as a slide in panel, while also being able to compare two records side-by-side, and have process flows tracked in a header, etc etc. So… here we are.
Limitations / Assumptions
It it assumed that all applications using Interop use two word namespaces. eg: “product.app” if this isn’t the case you will run into trouble. This is the namespace convention I’m used to working with and have personally seen the most, which is why I’ve built it in this way. It could be made configurable, but you’d still need to at least be consistent (eg: 3 names is fine, but ALL apps would need to be 3 names)
Download
The Interop Library can be found on GitHub: https://github.com/LordOfTheStack/UI5-Interop-System
Now, Let’s jump right in…
Ok, so that’s the introduction / text-book stuff out of the way! I’ll show you what this little thing can do.
I’ve created a sample container application, which is included in the Git repository. It is fully featured, I’ve not even utilised all the panels I’ve setup in it, so it should serve well as a starting point for most use cases.
In my example I’ve created a split-app, and one other additional app, and am demonstrating how views from the split-app can be instantiated multiple times side by side for record comparisons.
Here’s a few screenshots of me playing around with the panels.
select a record
expand the right hand panel
click ‘move’ button on the right hand panel
tick duplicate and apply (so that content on the main panel will be duplicated into the right side as a new instance)
select a different record in the master list (which will update just our main content panel)
click an action that performs a navigation, and see that only the relevant panel is affected
I could go on and on, but I think this is enough to demonstrate what’s going on.
Additionally to what i’ve done here, it should be noted that demoapp1 (this master/detail app) can be run in standalone mode, from its own index page. Even the inter-app routing has fallback routines to facilitate this. Try it for yourself.
Live copy (HCP account required) https://ui5interopsystem-p1942037524trial.dispatcher.hanatrial.ondemand.com/sample/container/index.html
How it works
Merged URL Route Patterns
You may not have noticed it at first, but the URL in this app has a querystring of “?nav=”
This parameter is generated by Interop, and is a combined version of every panels current route pattern (for example, there’s two client records, so each instance of that view needs different parameters fed into its onRouteMatched event handler).
You’ll also notice that it’s hard to read… that’s because I’ve implemented string compression, to keep it from getting too long. It also has the added advantage of discouraging users from messing with the url. However, if you would like it to be more readable, you can turn off Url Compression.
These constructed URL’s allow the users session to be maintained, so if they hit F5 to refresh the page, it will remember what to load. Likewise they can copy&paste the URL into an e-mail and the correct configuration of views will be loaded when the recipient clicks it.
While this could’ve been done automatically, I have designed this to be triggered by the container application, for reasons that will become obvious soon..
In the container app you’ll notice these little pieces of code for maintaining sessions
//this allows the Interop system to manage browser history
Interop.setRestoreOnBrowserBack(true);
var sessionResult = Interop.restoreSession();
if(!sessionResult.restored) {
//no existing route info, this is a blank run
updateUrl: function() {
if(this._loaded) {
//save the session, and add additional session states (the state of expansion of the panels)
//by doing this, we enable the page to be refreshed with f5 or the url to be copied & pasted into another browser, without losing track of what's in each panel
Interop.saveSession({
me: window._masterOpen || false,
re: this.oLayoutData.RightPanelOpen,
rw: this.oLayoutData.RightPanelBig
});
}
},
You may have already gathered, that the container gets the opportunity to add its own custom session variables, these will be encoded into the ?nav= URL string along with the base information. (in this sample, it is used to remember the expanded state of the master and right hand panels)
The container also controls what to do if a session isn’t present (in this case, my sample container looks for normal ui5 route parameters to see if an app has been specified, and load it, if not, it defaults to demoapp1)
The setRestoreOnBrowserBack(true) is simply a necessity because browser events will be hooked when this is called. This allows the user to press the back button in their browser and have their session restored automatically by Interop
Virtual Router Objects
To allow legacy routing (apps running standalone OR within containers) without further development effort inside the app, virtual (or proxy) routers are created on the fly by Interop to simulate commonly used functionality of the standard SAP router classes.
This means that when you call this.getRouter() you are getting a dummy object, that will simply translate your standard .navTo requests into Interop navigateContainer calls automatically.
These virtual routers also handle onRouteMatch events, and will parse in all the same parameters you would usually expect, along with a few new ones (like “container”, telling you which container you’re in)
To use virtual routers, simply inherit the dalrae.ui5.BaseController in all your view controllers. Or, copy its .getRouter function into your own base.
onBeforeRendering: function() {
//fallback routing support (allows apps to run standalone and still call .navigateContainer) -PhillS
if(dalrae.ui5.routing.Interop.UseFallbackRouting) {
dalrae.ui5.routing.Interop.mFallbackRouter = this.getRouter();
}
},
// supports our Interop component containers method of routing -PhillS
getRouter: function() {
//UPDATED to work with manifest style apps -PhillS
var router = sap.ui.core.UIComponent.getRouterFor(this);
if(!router || dalrae.ui5.routing.Interop._cc ) {
if(dalrae.ui5.routing.Interop._cc[dalrae.ui5.routing.Interop.StandardContainer.Main] || !router) {
var router2 = dalrae.ui5.routing.Interop.getRouterFor(this);
if(router2) {
return router2;
}
}
}
if(dalrae.ui5.routing.Interop.UseFallbackRouting) {
//fallback routing support (allows apps to run standalone and still call .navigateContainer) -PhillS
dalrae.ui5.routing.Interop.mFallbackRouter = router;
}
return router;
},
Essentially, the standard routers are totally overwritten by Interops own routing code, which is designed to emulate the sap router as closely as possible. Of course, some methods might be missing from my version of the router, and if this is an issue for your implementation you may need to extend the class yourself (or let me know).
Globally Registered Containers
Each area of the screen that is route-able, is registered as a container.
This means that the container app, and the contained app, do not need to know each other.
They simply need to know container id’s, such as “main” and “master” to interact, so neither are coupled or dependant upon each other to operate.
The easiest way to do this is to just use the dalrae.ui5.routing.Container element in your XML view and assign it one of the standard id’s.
However, you can use any UI element that has a <content> aggregation. To do so, you can call Interop.registerContainer()
Interop.registerContainer( Interop.StandardContainer.Main , oPanel );
or
<mvc:View xmlns:routing="dalrae.ui5.routing">
<routing:Container name="master" />
Container Navigation
Inter-app navigation is highly simplified. rather than constructing deeplink urls, you can navigate a container to a completely different applications view by simply using the navTo parameters for that view.
//demonstrating an Interop cross app navigation.
Interop.navigateContainer( Interop.StandardContainer.Main , {
app: "sample.demoapp2",
nav: [
"main",
{ example: "an example parameter" }
]
});
The only addition is the container ID, and the app namespace.
Although it is also worth noting, that namespaces must be registered before being used.
This is needed in Gateway/Launchpad systems, which use SemanticObjects. But in Hana you’ll still need to do it (until I come up with a clever way to autodetect Hana systems that is)
//Registering a namespace in Hana
Interop.registerNamespace("sample.demoapp1");
//Registering a namespace in Gateway
Interop.registerNamespace("sample.demoapp1","ZDEMOAPP1");
The added perk of doing this, is that the master list can remain loaded as app1, while the main content panel can navigate off to app2, so the user doesn’t need to go back a screen in order to access another record from app1, which is far less disruptive to the user flow of the program.
Global Event System
As mentioned, a replacement to the EventBus system is provided. It works in basically the same way. My main motivation for doing this was to make the event signatures consistent with all other elements of UI5, which the event bus isn’t.
To use it, you can register event handlers in any view of any app, and fire those events from anywhere as well.
I haven’t really used much of these in my sample, but they are useful for when you’d like your applications to actually be able to talk to each other. There’s plenty of things you can achieve by having panels communicate. You could for example, have an event fired each time the main panel opens a client record, and have the header panel pop down with alerts or available actions for that client number, regardless of which app that client is being viewed in.
Also, the Interop navigation fires off a few inbuilt events that you can take advantage of, and these, are in fact being used in my Sample so you can see them in action.
OnNavigate: This event is fired when any container is navigated anywhere. So any program listening to this event will see the navigation of every other program. This is good for container applications which may want to show or hide certain panels.
Parameters:
app : app namespace (eg: “sample.demoapp1”)
name : route name
arguments : routing parameters
container : ID of the container (eg: “main”)
sessionRestore : true/false
OnContainerCleared: This event is fired when any container has its content cleared via clearContainer, clearAllContainers, or a session restore.
Parameters:
container : ID of the container (eg: “main”)
sessionRestore : true/false
OnSessionRestored: This event is fired when a route pattern is successfully or unsuccessfully restored from a URL or custom source via the restoreSession() call
Parameters:
restored : true/false
navs : internal data of the session
extraStateInfo : custom values included in the session
Flexible Layout
Just to re-iterate… you can create any container application layout you like.
Just be sure to use the standard container id’s (or if making your own, be consistent) and it will work just fine. You can even have multiple container apps if you like. The sample I have provided is precisely that, a sample, and I haven’t even used it to it’s full potential in this example.
It actually has many active panels ready to go (diagram below)
But as stated, just a sample, so go nuts, make your own, go crazy!
Conclusion
I don’t know how many of you will utilise this system, but I thought it was worth sharing, as it’s the kind of functionality I’d like to see available out-of-the-box.
Being able to compare two client records, and giving users the choice to move content around, is something I’m eager to see, there’s many cool things I can think of that become possible. Additionally, the applications I’ve tried running in this way, have (in my opinion) run much smoother, since you never need to reload the entire screen.
I’ve uploaded the code to GitHub with an MIT license, so you are free to use it yourself
Download
The Interop Library can be found on GitHub: https://github.com/LordOfTheStack/UI5-Interop-System
Phillip, great blog. It's exactly what I need.
Just trying to understand how to implement interop. May need is a one container in which I can start serveral different applications depending on say for example which button I press in the container app. Is it possible to point me in the right direction.
Regards Erik
Hi Erik,
I'm not sure exactly what scenario you're talking about so I'm sorry if I miss the mark here. I assume you have an application already running in the main area (within a container) and when you perform an action (press button A), you want to open another application in another panel?
There's a few ways, the simplest way which seems adequate in your case is to just call the navigateContainer function, and have the container automatically open the target panel when it detects something navigated there (onNavigate)
So for example:
Then in the container app, you'll see in the sample that there's a handler of onNavigate, this will fire globally, when anything is navigated anywhere, so we use this to know that something has navigated that Right hand panel, and do something about it (expand it out so it's visible, presumably)
In more complex scenarios, you might want to setup custom events for your applications to fire, and let global logic in your controller completely handle what to do about those events.
I hope that helps
Phillip Thanks that worked.:)
Iam now trying to start an app that is deployed on a abap stack. The question i have is it possible to navigate by using the semanticObject.
As the app that i want to start is used on the FioriLaunchpad.
Below you see how we now navigate from one app to the another one.
var hash = oCrossAppNavigator.hrefForExternal({
target: {
semanticObject: "PMApplication",
action: "CreateOrder"
},
params: {
"Guid": oData.Guid
}
});
The downside is it opens in a new tab of the browser.
We would like to open it within the current application as you do with the interop libs.
Any ideas what we are doing wrong.
Thanks Erik
Hi Erik,
Glad to hear it!
I haven't written up a navigate function to read semantic objects yet (it was something I thought I might do in future as an enhancement). Though it isn't actually necessary for running on the abap stack. I have run this solution on a CRM system successfully, the navigation doesn't change, all that changes is the registerNamespace call you make.
What I mean is, when you want to navigate to your new app, you will still just use its namespace "your.app1" and not its semantic object. What you will have to setup beforehand though, is its BSP name. The reason for this, is Interop looks directly at the internal addresses of the BSP's rather than the launchpad generated alias.
so when your app starts (preferably in a common base class somewhere), you need to register all the applications you might possibly navigate to, doing so links their BSP and Namespace for Interop, like so
Now when you ask Interop to load "your.app1" it knows how to construct a url to find it, because you've registered the BSP name
So now you just do the navigateContainer the same as before.
Now what this doesn't solve for you, is your use of an action "CreateOrder". Interop currently does not parse action/intent. Personally I avoid using them but obviously it's what you've implemented so you'd need to either move that to a parameter of your route, or enhance Interop to facilitate parsing action through somehow. I no longer have easy access to an abap system so I can't be of much more assistance I'm sorry.
Hey Phillip,
By implementing it as you said before(see code below) it works
The navigation works and when debuggen I see that the second application "plannus" is loaded and Iam walking through the component of the app which then loads the appropriate view as stated in the manifest.
I enter the controller of the view where the existing code doesn't find the component any more.
this statement errors as the ownerid is undefined
this._oComponent = sap.ui.component(sap.ui.core.Component.getOwnerIdFor(this.getView()));
How do i locate the correct component
cheers Erik Thanks for you're Help
Hi Erik,
Take a look at the BaseController I've setup, it has methods for getting the router and component (which will work inside Interop as well as Standalone), implementing these functions in your own base controller would be the fastest way to get up and running I believe, and use this.getComponent().
It is unfortunate that you will have to change that line of code in your application, since the idea is to not have to change any apps, but this is the exception to the rule. As long as everything is utilising a base controller you should be able to keep everything tidy and neutral
Hope that helps
Hi Phillip,
Thanks for the reply. That worked for me.
I agree it is a shame to change the app that is called but as you mentioned the change is minor.
cheers Erik