When doing cross application navigation in Fiori, that is navigating between applications. On return, rather than picking up from where you left off, a new instance of the application is created. Meaning the app is in an initial state and not in the state when you left it. To solve this we need to persist the state between calls. Store any filters, search options, interactions the user has applied, then reapply them on return.

There has not been a documented standard approach for solving this. As a result we see and hear many different ways UI5 developers are solving this by themselves. If you study some of the standard SAP applications, there is an approach for managing state using existing Fiori services. In this blog I will highlight how it works, providing an easy way to do it yourself using generic reusable SAP code.

In the video above we see a variety of different filters being applied to a work list. We then see navigation to a second application. On return all those filters and interactions are reapplied.

Behind the scenes

Prior to navigation the application state is persisted on the server.

We can see below that the API /UI2/INTEROP/GlobalContainers is called and values are posted to the Layered Repository (LRep) table /uif/lrepdcontcd on the Gateway server.

The key for retrieving this data is added as a parameter to the URL.
On return the key is read from the URL, the data is retrieved and the app state is restored.
Because the data is persisted on the server, you can bookmark the URL, reopen in a browser and restore the state of the application. You can share bookmarks between different makes of browsers and different users. However the state key is recreated each time the data is loaded.

How does it work?

1. Prior to navigation save the app state
// create a new Application state (oAppState) for this Application instance
oAppState = sap.ushell.Container
            .getService("CrossApplicationNavigation")
            .createEmptyAppState(this);
oAppState.setData(oStateToSave); // object of values needed to be restored
oAppState.save();

2. Update the URL hash with the key

var oHashChanger = sap.ui.core.routing.HashChanger.getInstance();
var sOldHash = oHashChanger.getHash();
var sNewHash = sOldHash + "?" + "sap-iapp-state=" + this.oAppState.getKey();
oHashChanger.replaceHash(sNewHash);

3. Navigate to another Fiori application

sap.ushell.Container
	.getService("CrossApplicationNavigation")
	.toExternal({
		target: {
	   		semanticObject: sSemanticObject,
           		action: sAction
 		}, 
		params: oParams,
		appStateKey : oAppState.getKey()
});

4. On return read the key from the URL, retrieve the data and restore the app

var sHash = oHashChanger.getHash()
var sAppStateKey = /(?:sap-iapp-state=)([^&=]+)/.exec(sHash)[1]; 

sap.ushell.Container
 .getService("CrossApplicationNavigation")
 .getAppState(sAppStateKey)
 .done(function (oSavedAppState) {
       << code for restoring app state >>
});

Things you need to consider doing it this way, AppState instances are immutable, means the key will change, the URL may include routing and parameters in the hash.

Alternative Approach

The last couple of months I have been helping customers investigate and adopt the ABAP Programming Model for SAP Fiori, in preparation for thier S/4 HANA transformation. Part of this has been Developing Apps with SAP Fiori Elements. Fiori Elements uses the sap.ui.generic.app library, which has a very easy to reuse standalone NavigationHandler which abstracts away a lot of the complexity.

NOTE: This option is only available on SDK version 1.36.X and higher. There are other alternatives which work like sap/suite/ui/generic/template/ListReport/nav/NavigationHandler on 1.34 and below.

To implement

1. Import the Navigation handler into your controller

sap.ui.define([
..
    "sap/ui/generic/app/navigation/service/NavigationHandler",
    "sap/ui/generic/app/navigation/service/NavType"
], function(.. NavigationHandler, NavType) {

 

2. Instantiate the navigation handler and provide a callback for after navigation

onInit: function() {
..
// create an instance of the navigation handler
this.oNavigationHandler = new NavigationHandler(this);

// on back navigation, the previous app state is returned in a Promise
this.oNavigationHandler
   .parseNavigation()
   .done(this.onNavigationDone.bind(this));

// the app state which needs persisting
this._oAppState = {
    selectedTabFilter: "all",
    searchText: "",
    selectedContextPaths: [],
    selectedCategories: [],
    selectedSuppliers: []
};

3. Do the cross app navigation

onPress: function(oEvent) {
    .
//(sSemanticObject, sActionName, vParameters?, oAppData?, fnError?)
this.oNavigationHandler
   .navigate("Navigation", "sample",{},{ customData: this._oAppState });

4. Restore the application state

/**
 * if navigated back with appstate enabled then rehydrate the page using the
 * stored data
 * @param {Object} oAppData data persisted via iAppState
 * @param {Object} oURLParameters paramters passed in
 * @param {String} sNavType type of navigation
 */
onNavigationDone: function(oAppData, oURLParameters, sNavType) {
switch (sNavType) {
case NavType.initial:
    break;
case NavType.iAppState:
    this._oAppState = oAppData.customData;
    // set the previously selected icon filter tab
    this.byId("iconTabBar").setSelectedKey(oAppState.selectedTabFilter);

    // apply previous filters to table
    this._applyFilters();

    // set the previous search state
    this.byId("searchField").setValue(oAppState.searchText);

    // set the previously selected multi combo tokens
    this.byId("categories").setSelectedKeys(oAppState.selectedCategories);
    this.byId("suppliers").setSelectedKeys(oAppState.selectedSuppliers);

    // select previously selected rows
    this.byId("table")
        .setSelectedContextPaths(oAppState.selectedContextPaths);
    break;
}

Want More?

Demo Application in video

Source Code for Demo Application

Not on S/4 Hana yet or want to know more about the “ABAP Programming Model for SAP Fiori” a best practice for rapidly building meta-driven SAP Fiori apps on top of ABAP OData Services?

Come to my session at SAP TECHED Las Vegas

Ten tips from Using the ABAP Programming Model for SAP Fiori on AnyDB

 

To report this post you need to login first.

12 Comments

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

  1. Mike Doyle

    Great blog as always, John.  I hadn’t come across NavigationHandler before, and it looks very interesting.

    Regarding the first method, am I right in thinking that the replaceHash method will trigger events in the router, such as onRouteMatched?  That’s not really want we want in this case, is it?

     

     

    (0) 
    1. John Patterson Post author

      Hi Mike

      Thanks,

      AFAICS a Fiori hash is made up of multiple parts,

      shellhash /  intent / appSpecficRoute / params etc.

      i have a feeling param changes are ignored and (route) events are fired for app-specific parts only

      you would have to debug  sap.ushell.services.ShellNavigationHashChanger to see what really happens

      JSP

      (0) 
      1. John Patterson Post author

        I just debugged

        sap.ui.core.routing.HashChanger.getInstance() returns

        sap.ushell.services.ShellNavigationHashChanger when u run in FLP

        sap.ushell.services.ShellNavigationHashChanger.prototype.replaceHash – doesnt replace the browser history, therefore no route change, I debugged to confirm

        JSP

        (0) 
    1. John Patterson Post author

      Hi Nabi

      Mate please do publish, in my case by writing the blog I have significantly increased my understanding, if others do likewise we can fill more gaps.

      Cheers

      JSP

       

      (1) 
  2. Sai Byreddy

    This is nice, works similar to the Saved searches option in FPM etc..

    If you want to store data across applications, you can simply store it on the Window, so in this case you can store the Search Filters as an object and also the search results on the Window as a JS object (JSON)

    Simply write “window.myName = ‘Sai’ ” and it should persist in the same browser session. SAP stores all its framework objects this way.. See pic.

     

     

    (0) 
  3. Daniel Ruiz

    hi John,

     

    Good article. I’d like to hear your thoughts about storing client state in the server.. isn’t this exactly the problem we tried to solve using stateless apps? – As far I can see (in fact, as far the payload is concerned) a “session” is sort of created and you chunk the session reference in the URL, not too different from a jsessionid.

     

    I understand limitations that a URL may have, but why not qs the same payload into the URL and relieve the server from a request while also removing all the issues the same approach used by SAP will cause?

     

    Because now you’ve bound the access URL to a session, the session must remain “infinite” in the server… that table which saves each JSON can never be deleted anymore neither cleared, and if it is, then you broke your resource locator and you made a valid resource become an invalid resource.

     

    I’d also suggest you add a version identifier so when you modify applications and later on a bookmarked resource comes back in, you can fail gracefully – not always necessary but often good to keep track.

     

    Would like to understand how many times have you faced the limit of 2048 when dropping state into parameters?

     

     

    Cheers,
    Dan.

    (0) 
  4. John Patterson Post author

    Hi Dan

    Thanks

    This is a standard SAP approach, as much as I like writing code, I would rather reuse other peoples.

    The appstate is persisted to the back end, however it is not retrieved from the back end if found in the FLP session memory.

    I am in two minds about the backend state persistence, but can see some wins for users.

    There are standard jobs for purging LRep data. One of many jobs for Fiori cache and data management.

    If you debug the sample application provided you will see it uses Local Storage, it delegates to Fiori Personalization service, which supports Session, Local Storage, Server side caching and DB options.

    I have faced the URL limitation a few times, not with this technique though.

    Cheers
    JSP

     

     

     

    (0) 
  5. Tom Van Doorslaer

    But wait, there’s more!

    You can also pass an appstate to your navigation target:

    var oNav = sap.ushell.Container.getService("CrossApplicationNavigation");
    oNav.toExternal({ target: { shellHash: sIntent + "?sap-xapp-state=" + encodeURIComponent(JSON.stringify(oJson)) + sAction } });
    
    (example: 
    sIntent: "#Notificaton",
    sAction: "Create",
    oJson: {type:"D1", text:"a really long text here", image="data:image/png,base64...." }
    )

    This allows you to pass large JSON objects to your target, rather than just an ID.

    Example: you want to create a follow on document, which contains the entire text from the original document. A large text cannot be passed via the URL… so you add the text into a json object

    { text: sText}

    and you pass it as an x-app-state. This saves the object and generates a key.

    In the target app, you load your app state (ui5 will automatically use the key from the url) and hey, presto! you now have access to the json object.

    (0) 

Leave a Reply