Hi UI5 Developers,
Introduction
In UI5 we can use fragments to split up a view in small pieces which you can reuse in other views. These fragments mainly contain parts of the UI and can be called from in a View or controller. When calling/opening a fragment you can also add a controller to this fragment, like for example in this blog:
https://blogs.sap.com/2015/05/21/use-of-fragments-in-sapui5-reusability/
I tried this out and noticed that the behavior, of the controller that we add to a fragment, is not the same as a normal controller of a view. You cannot use this.getModel( ) or this.getView( ), “this” is not a reference to the fragment… For that reason I’ve created a function in my baseController that opens fragments with some added features.
In this blog I explain when I add a controller to a fragment, why I’m doing this and I will explain how my function works.
When?
I would not always add a controller to a fragment. I would only (or most of the time) do this when my fragment contains a dialog. I put all my dialogs in fragments. This gives me a good overview of all the dialogs in my project. You could also create a dialog using JavaScript but it’s hard to maintain in large projects.
Why?
This brings me to the why. Why would I want to add a controller to a fragment (containing a dialog)? There are a few reasons why:
- Organized project
- A dialog should contain his own logic to have his own lifecycle
- It enables us to re-use the fragment but also the logic behind the fragment
- Less code in the controller of the view from where the fragment is called
- Better organization of your coding
How?
I created a generic function in my baseController to open fragments. In this function I’ve added a few parameters:
- Name: name of the fragment (and controller)
- Model: option to pass a model from the view where it’s called (optional)
- updateModelAlways: let the function know if it has to update the model every time it opens the dialog or only the first time.
- callback: a function in the controller from where it’s called which can be executed from the fragment controller
- data: pass information from the main view to the fragment
The function will also create a controller if it can find one in the controller folder. The folder structure in the controller folder must be the same as in the view folder. If the function finds a controller and it’s not the current controller then it will call an onbeforeshow function in this controller. The onbeforeshow is comparable to the onRouteMatched function in a view. It will be called just before it opens the dialog to do some initialization.
The function:
openFragment: function(sName, model, updateModelAlways, callback, data) {
if (sName.indexOf(".") > 0) {
var aViewName = sName.split(".");
sName = sName.substr(sName.lastIndexOf(".") + 1);
} else { //current folder
aViewName = this.getView().getViewName().split("."); // view.login.Login
}
aViewName.pop();
var sViewPath = aViewName.join("."); // view.login
if (sViewPath.toLowerCase().indexOf("fragments") > 0) {
sViewPath += ".";
} else {
sViewPath += ".fragments.";
}
var id = this.getView().getId() + "-" + sName;
if (!_fragments[id]) {
//create controller
var sControllerPath = sViewPath.replace("view", "controller");
try {
var controller = sap.ui.controller(sControllerPath + sName);
} catch (ex) {
controller = this;
}
_fragments[id] = {
fragment: sap.ui.xmlfragment(
id,
sViewPath + sName,
controller
),
controller: controller
};
if (model && !updateModelAlways) {
_fragments[id].fragment.setModel(model);
}
// version >= 1.20.x
this.getView().addDependent(_fragments[id].fragment);
}
var fragment = _fragments[id].fragment;
if (model && updateModelAlways) {
fragment.setModel(model);
}
if (_fragments[id].controller && _fragments[id].controller !== this) {
_fragments[id].controller.onBeforeShow(this, fragment, callback, data);
}
setTimeout(function() {
fragment.open();
}, 100);
}
I also created a function to get a control from in the fragment. This is different than in a view, it’s a combination of the view id, fragment id and the control id. The fragment id is something I’ve added in my function for opening the fragment.
getFragmentControlById: function(parent, id) {
var latest = this.getMetadata().getName().split(".")[this.getMetadata().getName().split(".").length - 1];
return sap.ui.getCore().byId(parent.getView().getId() + "-" + latest + "--" + id);
}
This function can be used like this:
var label = this.getFragmentControlById(this.parent,"label1");
Last but not least I have a function for closing all the fragments . This makes it easy for a close button in each fragment. It just calls this function and it will close the open fragments. (In case the fragment contains a dialog.)
closeFragments: function() {
for (var f in _fragments) {
if (_fragments[f]["fragment"] && _fragments[f].fragment["isOpen"] && _fragments[f].fragment.isOpen()) {
_fragments[f].fragment.close();
}
}
}
Now I can open a fragment from each controller like this:
this.openFragment(
"Demo1",
null,
true,
this.onFragmentCallback, {
title: "Fragment Demo"
}
);
And I can handle the moment that it closes with the callback function:
onFragmentCallback: function() {
MessageToast.show("Back in controller");
}
I use this almost in each UI5 project and it already helped me a lot. Hope it helps others or become part of the standard baseController
🙂
You can find a live demo on github:
https://rawgit.com/lemaiwo/SAPUI5Fragments/master/webapp/index.html
Source code:
https://github.com/lemaiwo/SAPUI5Fragments
Kind regards,
Wouter Lemaire