Skip to Content

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

To report this post you need to login first.

7 Comments

You must be Logged on to comment or reply to a post.

  1. E. Palmen

    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

    (0) 
    1. Phillip Smith Post author

      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:

      //This is done in your main app that is running
      
      //do this when your button is pressed
      //first parameter is the panel (specifying the Right hand side panel)
      //second parameter is the target applications namespace (you need to register it beforehand)
      //third parameter is the routing parameters valif for that application (the same as what you would pare to a .navTo function). In this case a route called "detail" with a parameter of "bp" 123
      Interop.navigateContainer( Interop.StandardContainer.Right , {
      	app: "sample.demoapp1",
      		nav: [
      			"detail",
      			{
      				bp: "123"
      			}
      		]
      });

      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)

      //This is done in the Detail controller of the container
      
      onNavigate: function(oEvent) {
      	if(oEvent.getParameter("container") === Interop.StandardContainer.Right) {
      		//Right hand panel has had something loaded into it
      		this.openRight(); //open right hand panel
      	}
      },
      
      //in the case of this sample, the right hand panels state is bound to a layout model
      openRight: function() {
      	this.oLayoutData.RightPanelOpen = true;
      	this.oLayoutModel.refresh(true);
      	this.updateUrl();
      },
      
      //also in the sample, you can see how that onNavigate event was attached in the init statement
      Interop.attachEvent(Interop.StandardEvent.OnNavigate,this.onNavigate,this); 

       

      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

      (1) 
      1. E. Palmen

        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

         

        (0) 
        1. Phillip Smith Post author

          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

          //first param is namespace, second param is BSP name, as it appears in ABAP
          Interop.registerNamespace("your.app1", "ZYOURAPP1");
          Interop.registerNamespace("your.app2", "ZYOURAPP2");

          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.

          Interop.navigateContainer( Interop.StandardContainer.Right , {
          	app: "your.app1", //<- namespace  of app
          		nav: [
          			"order", //<- whatever the route is named goes here
          			{
          				“Guid”: oData.Guid
          			}
          		]
          });

          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.

          (0) 
  2. E. Palmen

     

    Hey Phillip,

    By implementing it as you said before(see code below) it works

    Interop.registerNamespace("com.name.plannus", "/sap/bc/ui5_ui5/sap/zpm_plan_nus/");	
    
    
    Interop.navigateContainer(Interop.StandardContainer.Right, {
    				app: "com.name.plannus",
    				nav: [
    					"overview", {}
    				]
    			});

     

    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

     

     

    (0) 
    1. Phillip Smith Post author

      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

      sap.ui.core.mvc.Controller.extend("dalrae.ui5.BaseController", {
          
          // supports our Interop component containers method of routing -PhillS
          getRouter: function() {
              //UPDATED to work with manifest style apps
              jQuery.sap.require("dalrae.ui5.routing.Interop");
              var router = sap.ui.core.UIComponent.getRouterFor(this);
              if(!router || dalrae.ui5.routing.Interop._cc ) {
                  var router2 = dalrae.ui5.routing.Interop.getRouterFor(this);
                  if(router2) {
                      return router2;
                  }   
              }
              return router;
          },
         
          // supports Interop container or standalone routing
          getComponent: function() {
              if(this.getRouter().getComponent) {
                  return this.getRouter().getComponent();
              } else {
                  return this.getOwnerComponent();
              }
          },

      Hope that helps

      (0) 
      1. E. Palmen

        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

        (0) 

Leave a Reply