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:

  1. Organized project
  2. A dialog should contain his own logic to have his own lifecycle
  3. It enables us to re-use the fragment but also the logic behind the fragment
  4. Less code in the controller of the view from where the fragment is called
  5. 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

To report this post you need to login first.

2 Comments

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

  1. Tony Tocco

    Nice, I was looking for better implementations around controller isolation for fragments and here it is.  Most modern frameworks go out of their way to provide a clean architecture and best practices for code separation. I hope the UI5 team implements something similar or borrows some of the great ideas that already exist in other libraries.

    • How about also adding a test/call for onInit() in the fragment controller?  It’s called as part of the life-cycle when bound to a view but ignored when applied to a fragment.
    • The setTimeout is worrisome. Were you experiencing some kind of race condition that led you to include it?

    Thanks!

     

    (1) 
    1. Wouter Lemaire Post author

      Hi Tony,

      Great idea for an onInit function, can be added to the implementation.

       

      The setTimeout is not required. I just found it in an example for opening fragments. It works perfect without that.

      Kr,

      Wouter

      (0) 

Leave a Reply