Skip to Content

Hi all,

I’m a big fan of the SAP javascript framework called OpenUI5. It’s a big change when you come from WDA but it has a lot more advantages. Besides SAP, there are other Javascript frameworks like AngularJS, BackboneJS, EmberJS, DurandalJS, … . Javascript frameworks are a growing trend in the web/mobile technology.

The idea

I’ve tested a lot of these frameworks. Most of the time I enjoyed working with OpenUI5 of course 🙂 . One of the AngularJS plugins that I really like is the Angular UI-Router: https://github.com/angular-ui/ui-router

This plugin has a feature which can change multiple components in the view while navigating as you can see here: https://github.com/angular-ui/ui-router#multiple–named-views

The concept

For example, when surfing to your Javascripte website with the URL pattern #/home, different subviews/components will be loaded in different places (header, menu, content and footer). When you for example navigate to the “Company” page, you will see that the page stays the same. Only the different subviews/components will change.

/wp-content/uploads/2014/02/pic1_388560.png

This is in my opinion a really nice feature! Navigate and change multiple components. Some of the advantages:

  • Reusable components/views
  • One master page with the design
  • Separation of code logic

Standard OpenUI5

I thought this could be a nice feature for OpenUI5. Wouldn’t it be nice if you could navigate in OpenUI5 without changing the whole page but only some parts of it? Yes, indeed. I’ve been looking at the OpenUI5 SDK and I’ve found an OpenUI5 Router component 🙂 .

Do you want to take a look? Open UI5 SDK: resources\sap\ui\core\routing.

That’s exactly what I was looking for! Of course this component was not working exactly the same as the AngularJS UI Router, so I did some changes.

I started analyzing the standard OpenUI5 Router component and found out following issues, to have the same feature as in AngularJS Routing you will need to change the following:

  • Only one view definition for an URL pattern possible
  • You can use an URL pattern only once
  • Views have to be created in a parent OpenUI5 component

So in every view you will have to add the required content when maybe you want some parts from the previous page…

/wp-content/uploads/2014/02/pic2_388561.png

An example of how to use a default OpenUI5 Router:


var shell = new sap.ui.ux3.Shell("shell", {
  appTitle:'SAPUI5 Routing Example',
  showLogout: false,
  showLogoutButton: false,
  showSearchTool: false,
  showInspectorTool: false,
  showFeederTool: false,
});
shell.placeAt('content');
var oRouter = new sap.ui.core.routing.Router([{
  pattern: "",
  name: "_main",
  view: "customsapui5.main",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  targetControl: "shell",
  targetAggregation: "content",
  clearTarget: true
},
{
  pattern: "secondPage",
  name: "_secondPage",
  view: "customsapui5.secondView",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  targetControl: "shell",
  targetAggregation: "content",
  clearTarget: true
}]);
oRouter.register("appRouter");
oRouter.initialize();










You have to define a parent OpenUI5 component where the router component will place the views. This can be for example the shell component:

https://openui5.hana.ondemand.com/#test-resources/sap/ui/ux3/demokit/Shell.html

Or you could use layout components like HorizontalLayout:

https://openui5.hana.ondemand.com/#test-resources/sap/ui/layout/demokit/HorizontalLayout.html

Customizing OpenUI5

So what if you want to place your views directly in a DIV container? What if you want to use multiple DIV containers that have to change when you navigate to another page? Why the shell or another parent component?

The Shell component of OpenUI5 is nice, but I like to have some more freedom for creativity.

I’ve started by taking a copy of the OpenUI5 Router component which you can find under the folder:

resources\sap\ui\core\routing –> file: Router-dbg.js

In my OpenUI5 project I’ve created a folder “components”. I placed a copy of the “Router-dbg.js” in it and renamed it to “RouterCustom.js”.

With just one easy change I already had achieved my first goal, I just added changed the parameter “greedy” from “false” to with the value “true” and I was able to navigate multiple views:

/wp-content/uploads/2014/02/pic3_388625.png

The this._oRouter object is an instance of the thirdparty library crossroads. By adding the parameter greedy with value “true”, it will function differently. By default this value is false in the crossroads object:

OpenUI5 SDK: resources\sap\ui\thirdparty –> crossroads-dbg.js

/wp-content/uploads/2014/02/greedy_390315.png

By using two DIV’s and a VerticalLayout Component for every DIV as parent I could use multiple parents. With following code I could change multiple components while navigating


var oLayout = new sap.ui.layout.VerticalLayout("oLayout");
oLayout.placeAt('header');
var oLayout2 = new sap.ui.layout.VerticalLayout("oLayout2");
oLayout2.placeAt('content');
var oRouter = new sap.ui.core.routing.Router([{
  pattern: "",
  name: "_header1",
  view: "customsapui5.header1",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  targetControl: "oLayout",
  targetAggregation: "header",
  clearTarget: true
},{
  pattern: "",
  name: "_content1",
  view: "customsapui5.content1",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  targetControl: "shell",
  targetAggregation: "content",
  clearTarget: true
},
{
  pattern: "secondPage",
  name: "_header2",
  view: "customsapui5.header2",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  targetControl: "oLayout",
  targetAggregation: "content",
  clearTarget: true
},
{
  pattern: "secondPage",
  name: "_content2",
  view: "customsapui5.content2",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  targetControl: "oLayout2",
  targetAggregation: "content",
  clearTarget: true
}]);









This is going good but still not exactly what I want. It would be better when you just have to define one pattern for multiple views and to add views directly to a DIV without the parent OpenUI5 component!


While looking deeper in the Router component I came in the “sap.ui.core.routing.Route” component.  Because this component changes the views I definitely needed this one. Took a copy of it and placed it in my OpenUI5 project under the folder components.

resources\sap\ui\core\routing –> file: Route-dbg.js

I also renamed it to “RouteCustom.js”

/wp-content/uploads/2014/02/pic4_388626.png

After some research I came to the conclusion that the most important part for the navigation is  in the method: “_routeMatched”

In this method I’ve added following code:


if(oConfig["views"]){
  $.each(oConfig.views,function(key,value){
  var sViewName = value.view;
  if (value.viewPath) {
  sViewName = value.viewPath + "." + sViewName;
  }
  oView = oRouter.getView(sViewName, value.viewType);
  if(oView && oView["getController"] && oView.getController()["onBeforeShow"]){
  oView.getController().onBeforeShow();
  }
  oView.placeAt(value.div,"only");
  });
}else








With this code I check if there are multiple views for one pattern. If that’s the case I do the following:

  1. loop over the views
  2. get the view name
  3. get an instance of the view by using the Router object
  4. If the view has a method “onBeforeShow” go to that method
    1. This is something I have implemented to do some manipulations before showing the view. This could be handy in some cases, but is not required!
  5. Place the view in the div which you can now add to the configuration of the pattern
    1. The OpenUI5 standard logic of the Route component uses the following statement for placing the div: “oTargetControl[oAggregationInfo._sMutator](oView);” . With this statement you always needed a parent OpenUI5 component. In my code this is replaced by “oView.placeAt(value.div,”only”);” so we just need a DIV as parent.

In the “RouterCustom.js” I’ve changed the reference to my custom route component.

/wp-content/uploads/2014/02/pic5_388627.png

Now I could use the Router just the way I wanted


var oRouter = new sap.ui.core.routing.Router([{
  pattern: "",
  name: "_main",
  views[{
  view: "customsapui5.header1",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"header"
  },{
  view: "customsapui5.content1",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"content"
  }]
},
{
  pattern: "secondPage",
  name: "_secondPage",
  views:[{
  view: "customsapui5.header2",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"header"
  },
  {
  view: "customsapui5.content2",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"content"
  }]
});








In my customized router component I can add multiple views to one pattern!

The full package

To get a better picture of how it works and what’s the benefits of this changes are, here an example of how to use.

In my example, I’ve created six views:

/wp-content/uploads/2014/02/pic6_388628.png

One view will be used for the navigation which always will be on the page. All the others I will use for content of the page.

I will create two pages:

First page will exist out of following views/components:

  • Header widget: TitleWidget
  • Left content: GeneralWidget
  • Right content: main

On the second page I will add the following views/components:

  • Header widget: TitleWidget
    • For the title we want to keep the same content as on the first page.
  • Left content: WeatherWidget
  • Right content: WeatherContent

To do this I coded the following in the index.html:


<script>
  jQuery.sap.registerModulePath("sap.ui.core.samples.routing", "./");
     jQuery.sap.registerModulePath('sap.custom.routing.Router', 'components/RouterCustom/');
  jQuery.sap.require("sap.custom.routing.Router");
sap.ui.localResources("customsapui5");
var oRouter = new sap.ui.core.routing.Router([{
  pattern: "",
  name: "_main",
  views:[{
  view: "customsapui5.main",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"content"
  },
  {
  view: "customsapui5.GeneralWidget",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"contentWidget"
  },
  {
  view: "customsapui5.TitleWidget",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"headerWidget"
  }]
  }
  ,
  {
  pattern: "weather",
  name: "_weather",
  views:[{
  view: "customsapui5.WeatherContent",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"content"
  },
  {
  view: "customsapui5.WeatherWidget",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"contentWidget"
  },
  {
  view: "customsapui5.TitleWidget",
  viewType: sap.ui.core.mvc.ViewType.HTML,
  div:"headerWidget"
  }]
  }
  ]);
oRouter.register("appRouter");
  oRouter.initialize();
var oModel = new sap.ui.model.json.JSONModel({ title: "Nested UI Routing in SAPUI5" });
  sap.ui.getCore().setModel(oModel);
  jQuery(function() {sap.ui.template();});
  var view = sap.ui.view({id:"idmenu1", viewName:"customsapui5.menu", type:sap.ui.core.mvc.ViewType.HTML});
  view.placeAt("menu");
</script>









In this code I start loading my customized components, create a router object , set title in my model and add the menu to the menu DIV.

In the index.html I also created my layout with the required DIV’s  for navigation:


<body class="sapUiBody" role="application">
  <div id="overview" class="center">
       <div id="header">
            <div id="headerImage" class="left"><img src="images/sapui5.png"></div>
            <div id="subheader" class="right">
                 <div id="headerTitle" data-type="text/x-handlebars-template"><h1>{{text path="/title"}}</h1></div>
                 <div id="headerWidget" ></div>
            </div>
       </div>
       <div id="menu"></div>
       <div id="main">
            <div id="contentWidget" class="left"></div>
            <div id="content" class="right"></div>
       </div>
  </div>
  </body>




The navigation itself is being handled in the controller of the menu view. With the name of the route, which is defined in the router object, it will navigate to the second page and change all the different parts (as defined in the router object).


goToPage: function(oEvent){
  var oRouter = sap.ui.core.routing.Router.getRouter("appRouter");
  oRouter.navTo(oEvent.getParameter("item").getKey());
  }







Because I use the “NavigationBar” OpenUI5 component for the menu and I use the same name for a route as for the key of “NavigationItem”, the key is in the event.


Menu HTML View:


<div data-sap-ui-type="sap.ui.ux3.NavigationBar" data-select="goToPage">
  <div data-sap-ui-type="sap.ui.ux3.NavigationItem" data-key="_main" data-text="User information" ></div>
  <div data-sap-ui-type="sap.ui.ux3.NavigationItem" data-key="_weather" data-text="Weather information" ></div>
  </div>






Result

For the layout I also added some basic CSS..

All the views contain some dummy content just for showing you the principal. In the view “main” I implemented the “onBeforeShow” method, just for showing.

/wp-content/uploads/2014/02/pic7_388629.png

User information page

/wp-content/uploads/2014/02/pic8_388630.png

Weather information page

/wp-content/uploads/2014/02/pic9_388637.png

The title on the index page and the titleWidget are both connected to the same model. Changing the title in the titleWidget will also change the title in the index page.

It looks like it’s a new page but actually there are just three components changed.

/wp-content/uploads/2014/02/pic10_388638.png

You can find the full example on github: lemaiwo/Nested-UI-Routing-OpenUI5 · GitHub

I also added the index and custom components of the router to the attachements

When working with this concept, you are able to do some CSS on the master page (index.html). Therefore you could use other UI frameworks as defined in the following blog:

http://scn.sap.com/community/developer-center/front-end/blog/2014/02/18/openui5-with-bootstrap-for-the-design

Hope it’s useful!

Kind regards,

Wouter

To report this post you need to login first.

10 Comments

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

  1. HP Seitz

    Hi Wouter,

    great post.

    I want to add the UI5 Router to the UI5 Boilerplate, this is very good post about this feature within UI5.

    Thanks, HP

    (0) 
  2. John Patterson

    Hi Wouter

    Nice blog, I really like how you brought your experiences from other libraries like Anglarjs into meeting and explaining to us this requirement.

    On the weekend I listened to the following podcast https://player.fm/series/javascript-jabber/095-jsj-angularui-with-dean-sofer this episode was centered around the angularui community and there is an interesting discussion on the origins of the angular ui-router.

    My takeway, “all design is redesign”  if you take the experiences of others and combine this with the best bits of other libraries like Emberjs, Ruby etc you can come up with an even better solution  – interesting (long) read on how the design evolved. Design · Issue #1 · angular-ui/ui-router · GitHub

    One question for you, what version of UI5 are you using?

    I ask this because i have quite a few versions and could not find this._oRouter.greedy in any of the Router.js files, for example here is the latest version https://sapui5.netweaver.ondemand.com/resources/sap/ui/core/routing/Router-dbg.js just wondering looking at the test qUnit Page for sap.ui.core.routing.Router whether this change is still needed.

    cheers

    jsp

    (0) 
    1. Wouter Lemaire Post author

      Thanks John!

      I’m using the latest version. You have to add the “this._oRouter.greedy” parameter. This parameter is used in the third party library “crossroads”. With the object in “this._oRouter” you can change the “crossroads” object.

      I have mentioned it wrong in my blog, thanks for reporting ! I’ll update the blog!

      Kind regards,

      Wouter

      (0) 
  3. Marco Allegretti

    Hi Wouter,


    very nice post!

    I was trying to do something similar, and you saved me a lot od time!


    After having downloaded your repo and played a bit with it, I have one question. I need to pass some parameters in the url (e.g. weather/:cityName: –> index.html#/weather/NewYork). Looking around I noticed that, with the standard library, the solution is to write in the onInit method of the controller the following code:


         var oRouter = sap.ui.core.routing.Router.getRouter(“router”);

         oRouter.attachRouteMatched(function (oEvent) {

              …   

              oEvent.getParameter(“cityName”);

              …

         });


    I tried this solution but I cannot execute the code within the method attachRouteMatched.

    Actually, I found a workaround to extract url parameters from the oRouter. Nonetheless, I would like to know if there is a better (and easy) way to do it.


    Thanks,

    Marco

    (0) 
    1. Wouter Lemaire Post author

      Hi Marco,

      Thx!

      I think you should use following code to get your parameter:

      var cityName = evt.getParameter(“arguments”).cityName;

      Kind regards,

      Wouter

      (0) 
      1. Marco Allegretti

        Hi Wouter,

        thanks for the quick reply.

        I’ve just tried your solution but it does not work. Here below my solution:

            

             // ids of the params defined in the pattern

             var paramsIds = oRouter._oRouter._prevRoutes[0].route._paramsIds;

             // values of the params in the url

             var params = oRouter._oRouter._prevRoutes[0].params;

             // object to be filled with ‘paramId: value’ pair

             var oParams = {};

             paramsIds.forEach(function(paramId, i) {

                  oParams[paramId] = params[i];

             });

        Furthermore, I’ve put this piece of code in the onBeforeRendering function. In fact, if you put it in the onInit one, it is fired only the first time.

        Maybe this is not the best or the most elegant solution.. but it works 🙂

        Let me know if you (or anyone else) has a better one!

        Best,

        Marco

        (0) 

Leave a Reply