UI5 Component Loader – Technical details of the App
At the last UI5con 2021 on air, I presented the UI5 Component Loader. A solution that takes reusability of UI5 Components to the next level. In a series of blog posts, I want to share the details of my session with some more technical insights starting from the beginning:
- A high-level overview of the UI5 Component Loader
- All technical details of the UI5 Component Loader App (current blog post)
- Run the component loader on your system or locally
The full session is available on YouTube: https://youtu.be/V6xOig4H-aA
Full playlist of UI5con ON AIR 2021: https://www.youtube.com/playlist?list=PLHUs_FUbq4dXRIbRPiXmzXmV2BAcA8zAR
In this blog post, I will share as much as possible technical details on how the Component Loader app itself works. As well the simple things as the more complex ones. The app itself is very simple and will be easy to understand but it contains some parts that might require some explanation to understand the need of it.
The app (Component Loader) contains only one route which is handled by one view that contains the flexible column layout. The route comes with 3 optional parameters to know the current state of the flexible column layout. On the initial startup it makes sense that it has to load the first component and put it in the master aggregation with OneColumn as layout. This makes all parameters in the route optional.
Loading the components in the correct aggregation is all done by the app controller. Based on the view (which actually means aggregation), level and layout, it will load the component for that configuration and add it into the correct aggregation. Router parameters:
- View: aggregation, can be beginColumnPages (which I have defined as master), midColumnPages (defined as detail), endColumnPages (defined as detail detail )
- Level: sequence of a component in a scenario. It doesn’t mean that the second level should be in midcolumnpages or the third in endcolumnpages.
We need both level and aggregation because level two does not always mean that the second component will be loaded in the detail aggregation. It could be possible that we want to show the second component full screen, which then could be loaded in the master aggregation with the OneColumn layout. Or that the third page (of another component) is loaded in detail instead of detailDetail aggregation. We need both fields to support different combinations.
- The Layout parameter is needed to adjust the flexible column layout into the right state.
In reality we have two routes, both pointing to the same view. One is needed to support Fiori Elements. Fiori Elements will add a parameter to store the state and requires to be handled in a different way:
One could do the job for all but it might be useful in the future to differentiate when Fiori Element is loaded.
The Component Loader Library
The component loader comes with a library which is integrated in the component loader itself but also to make the integration for components easier. The library contains an enumeration which is used by the component loader itself to handle all supported/implemented actions (like navigation, close component, update, back…). The reusable code in this library is intended for the components to make the integration with the component loader easier, it provides pre-configured UIComponent and a BaseController in this library. (more details on the pre-configured objects will come in the section “Code”)
It’s not required to use these reusable objects but it is the easiest way is to integrate a component into the component loader.
The enumeration of actions is a list of events that can be triggered from a component. The component loader will react on it and knows what to do based on the type of action. Every action that is implemented in the component loader is listed in this enumeration. The enumeration can be used by the developer of a component to know all possible action that can be triggered. The supported actions:
- as the name of the action already sounds, it triggers navigation. The component loader will then open the next page and show the component. The first time it will create the component and show it, the second time it will get the component from its local cache and simple show the component. This is done to improve performance!
- For Fiori elements, it will always reload the component to make it work. Somehow this the second load is really fast. Looks like fiori element has some built-in cache as well.
- Together with the navigation, a component can send data to the next component. For example, when the master component is a list and the next component needs to know the selected item.
- This will close the current component and go back to the previous layout. When a master and detail page are visible, it will close the detail page and go back to OneColumn layout.
- Refresh will trigger a refresh of the default models of all components
- RefreshPrevious will trigger a refresh of the default model of the previous component. In case this is triggered from a detail component, it will update the default model in the master component.
- None => doesn’t do anything… Still in the list from the very first test 😊
The enumeration of actions is the glue to make the communication work (more details in the next section “Communication”).
The component loader library is shared on GitHub + contains other stuff I use often: https://github.com/lemaiwo/UI5ComponentLoaderLib
How does the communication between the component loader and components work? First of all, the component loader will receive parameters with the scenario id and type from the Fiori tile. Those parameters will be used to get the configured components for the current scenario. This configuration will be used for loading the component.
The communication can be divided in two types from component point of view:
- This is the data that comes from the component loader received by the component.
- Earlier I mentioned that the component loader will only create a component the first time but after that only show it in the right place. Because of this the component loader will pass the input data object with the creation of the component. The second time, it will simply update the data in the component.
- The input object contains the following properties:
- Data: data passed from action of previous component, could be the selected item from a list in the master component which is needed in the detail component. In the master component this will be empty, it can be empty.
- Config: configuration of a component from the ZCL_PARAM table. This can be useful to make component behave different in other scenarios.
- Level: Current level of the Component Loader
- Scenario: full scenario configuration from ZCL_CONFIG with ZCL_PARAM. This in combination with level gives a developer access to the configuration of a component. It also keeps the “data” property for each component. This gives us access on to the data of every component in any level.
- This is communication in the other direction, events triggered by a component on which the component loader will react.
- Any action that can be executed by the component loader needs to be triggered from the component itself with the properties:
- Type: action from the enumeration in the library
- Data: in case the next component needs to know some information of the current component.
A lot of theory, time for some code to help better understanding the theory.
Like explained earlier, the UI5 Component Loader comes with a library which contains a preconfigured UIComponent and BaseController. Every component that you want to use in the Component Loader should extend from those objects to make the integration easier.
This means you have to change the “Component.js” of your UI5 component to extend from the UIComponent of the library.
And the same for every controller:
This will allow triggering actions from the component to the Component Loader. An action can contain multiple properties as defined earlier. In this example the component contains a list that will trigger the action “Navigate” to the UI5 Component Loader and passes the data object of the selected item:
In another component, which is used as detail page, the function “onUpdateInput” will be triggered when it is created or updated with data object from the component that triggered the navigation. Any component that expects incoming data has to implement this function. The input object contains multiple properties which are defined earlier in this blog post.
For making the communication in a component possible the library contains reusable objects for developing components. It comes with a preconfigured UIComponent and BaseController:
The UIComponent contains preconfigured metadata for receiving data from the UI5 Component Loader and sending action to it. The input is of type “object” to keep it as open as possible. In case the UI5Component Loader wants to send more properties, none of the components require a change.
In the init function of the UIComponent it will look for the input object and call the set function.
The “setInput” function will publish the incoming data to the eventbus of the current component. This will allow any controller to subscribe to it and receive input updates.
The first time the UI5 Component Loader will pass the data at creation time which will call the init function. After that, it will not recreate the component and directly update the component by calling the “setInput” function from in the UI5 Component Loader:
The basecontroller in his turn comes with the code that subscribes to the input publish event. It check if the controller that extends from this basecontroller has the function “onUpdateInput” (this is just a name I took 😊 ). It will use this function as an eventhandler to subscribe to the input event. The first time the subscribe is not in time and it will call the “onUpdateInput” function directly.
Time to dive into some parts of the UI5 Component Loader itself. I won’t go into all details; this would take it too far but some interesting parts. It is a simple app but as you might know, I like to split apps in an organized way. The app itself looks like this:
Most of the logic is in the controller and the component object in the model folder.
Let me zoom in on the Component object:
The function “getConfig” converts ZCL_PARAM to json. If one parameters has multiple values with a sequence number, it will be converted to an array. Otherwise it will be a simple property with key value.
As mentioned in the theory, I defined a default “LayoutType” based on the view that’s open. This is defined in the “getLayout” function:
“getViewAggregation” provides a mapping between the aggregation type in the config table with aggregation of the FlexibleColumnLayout:
The loading of the component happens in the “getUIComponent”:
During the creation it will pass the input object and startupparameters for fiori elements. As soon the component is loaded, it will load the resourcemodels manually for Fiori Elements. This uses the wrong path in the component create function.
One of the important functions in the App controller is the “showComponent”:
showComponent function will get the component (from the component object in the model folder) and set it to the configurated aggregation. The logic is different for:
- initialization of a component:
- only happens first time
- it will attach the component to the function “onAction” to catch events from components
- wrap it into a component container
- set it to the configured aggregation of the flexible column layout
- updating a component
- if a component was already loaded before it will simply update the input object with new data from the previous component in the scenario
- updating a component which was replaced.
- It can happen that multiple components use the same aggregation which means they can be replaced. In this case it will not recreate the component but it will be wrapped again in a componentcontainer and set to the aggregation
- It will also update the input object.
Most of the code in this function is commented.
Another interesting function in the App controller is the “onAction” function:
The “onAction” will be triggered in case a component triggers an action. It will use the enumeration of the library to know what to do.
In case “Navigate” is triggered, it will use the approuter to navigate to the next component and page. It can happen that “Navigate” is triggered but the route stays the same, when master is a list and the user selects another item. In this case the “navTo” won’t do anything because the route doesn’t change. Therefore, this function will directly update the next component and trigger the navigation.
A simplified version of the resources are available on GitHub:
- ABAP Backend Resources (ABAPGIT): https://github.com/lemaiwo/UI5ComponentLoaderBackend
- UI5 Component Loader: https://github.com/lemaiwo/UI5ComponentLoader
- UI5 Component Loader Library: https://github.com/lemaiwo/UI5ComponentLoaderLib
- Example List Component: https://github.com/lemaiwo/listcomponent
- Example Detail Component: https://github.com/lemaiwo/detailcomponent
- Generic Detail Component: https://github.com/lemaiwo/generic-detail-component
I hope this makes some thing clear now 🙂