Skip to Content

XML templating is a powerful tool to fine tune your SAPUI5 app appearance. It can help to substantially reduce your code and layout by tailoring it to the metadata definitions of your OData service and increase the HTML page performance. The main idea of templating is a preprocessing the XML definition source at runtime just before creating the SAPUI5 control tree. Sometimes you can change complex binding expressions which would be recalculated every time the control is rendered into a constant string, or instead of hiding some control you can remove it by template conditional expressions.

At some point at my recent project the problem appeared: different parts of my application use quite a similar popup dialog with a considerable common part in layout and coding. The common coding is an easy part: I created an intermittent controller implementation with common functions and made it an ancestor instead of SAP generated BaseController in all end-view controllers which supposed to use that popup Dialog. Yes, the Dialog is declared as an XML fragment. The differences in layout of the Dialog probably could be resolved by binding visible attribute of controls to some JSON model property, however, these invisible parts anyway would be present in the SAPUI5 control tree and the view DOM.

It’s probably OK with small layout parts. But when you use quite a complex Object Page Layout with lots of dynamic blocks, it’s a better idea to somehow reduce the DOM and control tree, and XML templating would a good tool.

And here is the problem: XML templating is supposed to be used with XML views, not fragments. Yes, if the fragment is included into a view, then it will be preprocessed as an inline text together with the rest of the view definition. However, I could not find any references on how to use templates with standalone fragments.

So, it’s time to dive into the debugging of SAP code and try to figure out if it’s possible to run the XML preprocessor manually.

Loading the source

The first problem is how to load an XML fragment source, and this is an easy one (well… sort of): it appears that the SAP runtime loads fragment source with function XMLTemplateProcessor.loadTemplate, and XMLTemplateProcessor is part of sap.ui.core library. The usage sample of the function XMLTemplateProcessor.loadTemplate you can find in the sap.ui.core.Fragment module code. Long story short, the code below does the trick:

var oFragment = XMLTemplateProcessor.loadTemplate(sFragmentName, "fragment");

Here, sFragmentName is the same name you pass to sap.ui.xmlfragment function with dot-notation “foo.view.fragment.MyFragment”. The XMLTemplateProcessor.loadTemplate function first resolves the fragment name to “foo.view.fragment.MyFragment.fragment.xml”. The second parameter is a so called extension, and if you omit it, then the name of the fragment would be resolved to “foo.view.fragment.MyFragment.view.xml”. On the next step of the process function synchronously loads the XML source and returns an object representing the DOM structure.

Running the standard preprocessor

The second part of investigation took a bit longer time: at the end I discovered that there is a module sap.ui.core.util.XMLPreprocessor and it has a function process which accepts the XML source (either in from of a string, or as a DOM hierarchy) and preprocessing parameters as described in the documentation, and it is the one which does the preprocessing returning transformed XML source.

And now what about data source for all the preprocessor instructions? The documentation states that you should use so called Meta model as a data source for XML preprocessing instructions. The meta model can be obtained from OData model object (V2, or V4) by calling getMetaModel function. Meta model is a tool to access metadata definitions and annotations and then use them for transforming source XML. However, what if we use not a meta model but just some JSON model which generally you have after generating an application in WebIDE, something like ViewSettings. Looking into the meta model source it seems that there are no differences between a common Model and a Meta model preventing from using either of them in the XML preprocessing.

Making the code

And so it goes, that to apply XML templating to XML fragments we should first load its source, then run the XML preprocessor. Something like this:

var oFragment = XMLTemplateProcessor.loadTemplate(sFragmentName, "fragment");
oFragment = XMLPreprocessor.process(oFragment, {
	caller: "XML-Fragment-templating"
}, {
	bindingContexts: {
		meta: oMetaModel.createBindingContext("/")
	},
	models: {
		meta: oMetaModel
	}
});

In the code snippet above oMetaModel can be an arbitrary model. Word meta defines the meta model name which can be used in preprocessing instructions. Suppose you have property SomeProperty at the root context of the model. In that case you can use the property in the if preprocessing instruction:

<template:if test="{= ${meta>SomeProperty} === 'SomeValue'}">
<!-- Here you have a piece of code which will remain 
in the resulting source only if SomeProperty === 'SomeValue' -->
</template:if>

As you can see in test attribute of the if instruction, we have exactly the same expression syntax as in our good old binding expression.
More details on the preprocessing instructions are  here. Note reference to the context of a model with name meta.

Polishing

To make coding a bit more comfortable we can accurately redefine sap.ui.xmlfragment function, thanks to the Javascript freedom. To do this we need to add 3 modules to the dependency list of the module where the redefinition resides:

sap.ui.define([
    "sap/ui/core/XMLTemplateProcessor",
    "sap/ui/core/util/XMLPreprocessor",
    "sap/ui/core/Fragment"
    ...

A good place to redefine the function should be the code point which is executed only once and at the very beginning of the app. I have chosen onInit lifecycle method of the SAP generated App controller. The code looks as below:

sap.ui.xmlfragment = (function(fnXMLFragment) {
	return function(sId, vFragment, oController) {

		if (typeof(sId) === "string") { // calling original function
			return fnXMLFragment(sId, vFragment, oController);
		} else if (sId.fragmentName && sId.preprocessors && sId.preprocessors.xml) {
			// synchronously loading XML fragment source
			var oFragment = XMLTemplateProcessor.loadTemplate(sId.fragmentName, "fragment");
			if (oFragment) {
				// Now calling XML preprocessor
				oFragment = XMLPreprocessor.process(oFragment, {
					caller: "XML-Fragment-templating"
				}, sId.preprocessors.xml);
				// calling original function with transformed source
				return fnXMLFragment({
					fragmentContent: oFragment
				}, vFragment);
			}
		} else { // calling original function sap.ui.xmlfragment
			return fnXMLFragment(sId, vFragment, oController);
		}
	};
})(sap.ui.xmlfragment);

After such redefinition the resulting code of creating control from a fragment with standard XML preprocessing can look as follows:

var oDialog = sap.ui.xmlfragment({
	fragmentName: sFragmentName,
	preprocessors: {
		xml: {
			bindingContexts: {
				meta: oMetaModel.createBindingContext("/")
			},
			models: {
				meta: oMetaModel
			}
		}
	}
}, this);

Enjoy.

P.S. I used SAPUI5 version 1.52.

To report this post you need to login first.

Be the first to leave a comment

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

Leave a Reply