I have a component in a list in a sapui5 XML view and I want to set multiple properties of that component with one function. E.g. I want to set text, status, tooltip and icon of an ObjectStatus together, because the values of those are all different facets of the same data. The issue is that i have to calculate the values to set to those properties from the model with the same relatively time-heavy function. If I write a separate formatter for each of those properties, it has to run the same function for each property. Instead of this I would like to write one function that runs this time-heavy function once and sets a value to all those properties at the same time.
To accomplish this, I have tried creating a sapui5 fragment that could be placed in the list and filled with different information by the createContent function for each instance of that fragment. However I cannot figure out how to do this.
In the view definitions I'm trying to instantiate the fragment like this:
<core:Fragment fragmentName="QuantificationParameter" type="JS" path="{project>}"/>
And then I'm trying to set different content to each instance of the fragment:
sap.ui.jsfragment("namespace.fragments.QuantificationParameter", {
createContent: function(oParentController) {
//Get the object bound to this list item
var derived; //Calculate some intermediate information from this object
return new sap.m.ObjectStatus({
icon: derived.icon,
text: derived.text,
state: derived.state,
tooltip: derived.tooltip
});
}
});
While debugging it seems that the createContent function of the fragment is run only once and I cannot figure out any way to access the data that I'm trying to bind to the fragment. Is there any way I can render different content to each instance of the fragment?
What you are searching for is called databinding.
But first of all: we do not use JS Fragments, due to the same reason we do not use JS views. Here s a little Blog written on that topic.
https://blogs.sap.com/2018/05/01/why-do-we-use-xml-views-rather-js-views-in-sapui5/
Now the databinding part:
I asume, that Fragment will have the same controlls for each instance and you just want the values to change. To do just that you need to create a JSONModel either in your BaseController or component.js. In this Model you store i.e. your Labels text.
Inside your Fragmet you bind that property to the label. Since JSONModels bindingmode is two way by default the Label will change dynamically if you update the model. You can update the model i.e. everytime the user clicks on one of your list items.
Framgmet example:
<core:FragmentDefinition
xmlns="sap.m"
xmlns:f="sap.ui.layout.form"
xmlns:core="sap.ui.core">
<f:SimpleForm
editable="true">
<f:content>
<Input
value="{baseModel>/inputA}"
type="Text"
placeholder="{i18n>placeHolder}"/>
<TextArea
value="{baseModel>/textA}"/>
<TextArea
editable="false"
value="{baseModel>/textB}"/>
</f:content>
</f:SimpleForm>
</core:FragmentDefinition>
creation of the model i.e in component.js:
var oBaseModel = new JSONModel({
inputA: "",
textA: "",
textB: ""
});
this.setModel(oBaseModel, "baseModel");
example for your press lit item funtion:
(should be in the controller of the view your list is located in)
onListPress: function (oEvent) {
var oLine = oEvent.getSource().getBindingContext("yourRemoteService").getObject();
this._oBaseModel.setProperty("/inputA", oLine.ListPropertyA);
this._oBaseModel.setProperty("/textA", oLine.ListPropertyb);
this._oBaseModel.setProperty("/textB", oLine.ListPropertyC);
}
You should really give that tutorial a go:
https://sapui5.hana.ondemand.com/#/topic/e5310932a71f42daa41f3a6143efca9c
Related
My Fiori application has a view for which two models are set. The first model is set by default in manifest.json (OData), the second model I set in the controller.
The data of the second model are displayed in sap.m.TextArea. If I try to get its binding context, the output is undefined.
This is how I set second model:
onInit: function() {
var oLocalModel = {
Text: "test"
};
// JSONModel required from "sap/ui/model/json/JSONModel"
var oModel = new JSONModel(oLocalModel);
this.getView().setModel(oModel, "localData");
},
This is how I try to get the binding context of the TextArea:
onSendMail: function(oEvent) {
var oLocalContext = oEvent.getSource().getBindingContext("localData"); // undefined
var oLocalContext = this.byId("zgutMailFormText").getBindingContext("localData"); // undefined too
},
<TextArea id="zgutMailFormText" value="{localData>/Text}">
<layoutData>
<l:GridData span="XL2 L3 M3 S8" />
</layoutData>
</TextArea>
How to get the context depends on your application code which I don't see much in your question. But since the localData model is a client-side model, and since the binding path is already known (absolute path), you could simply create the context with the method createBindingContext.
onSendMail: function(oEvent) {
const oLocalContext = this.getView().getModel("localData").createBindingContext("/");
// ...
},
There could be many reasons why a control returns no context:
The control has simply no context. Contexts are created by the framework automatically for resolving relative binding paths. Since your TextArea binds data with an absolute path (localData>/Text), no context needs to be created. The path can be resolved immediately.
The control you accessed (e.g. this.byId("zgutMailFormText")) is part of a template control. Templates don't contain any contexts. Only the rendered clones do. See this answer for more explanation.
Parent controls themselves have no contexts bound, so there is no context to propagate.
From the API reference: sap/ui/model/Context:
The Context is a pointer to an object in the model data. A relative binding needs a context as a reference point in order to resolve its path; without a context, a relative binding is unresolved and does not point to model data. Context instances can, for example, be created in the following ways:
by a sap.ui.model.ListBinding for each list entry,
as the single context associated with a sap.ui.model.ContextBinding,
by calling sap.ui.model.Model#createBindingContext.
I would like to know how to get the content of TextArea, assign the value to a variable, set it to a model, and then set the variable to another TextArea in another view. I have coded some examples and it works, but not on TextArea.
Here is the example code:
// In init of the Component.js
this.setModel(new JSONModel(), "TransportModel"); // JSONModel required from "sap/ui/model/json/JSONModel"
// In *.controller.js
this.getView().getModel("TransportModel").setProperty("/", {
"Serial": this.byId("mat_serial").getValue() // "mat_serial" == id of the Input box in XML view
});
In the last step, I set the Text from a different View (also XML and Input Box) with the Value of the Model Element.
<Text text="{TransportModel>/Serial}" />
That worked pretty well.
But how to do the same with the TextArea? How can I do it based on this model? The value that I want to use from the first TextArea should also be on a TextArea in another view.
UI5 supports two-way data binding. I.e. if the user changes something in the UI (e.g. user types something in the text area), that change will be reflected automatically in other bindings that listen to the change.
<!-- In view 1 -->
<TextArea value="{TransportModel>/Serial}" />
<!-- In view 2 -->
<Text text="{TransportModel>/Serial}" />
No need to get input values by hand. Simply let the framework synchronize the value.
How to use a local json model:
Create
initItemViewModel: function () {
return new JSONModel({
Serial: ""
});
}
this._oViewModel = this.initItemViewModel();
this.setModel(this._oViewModel, "TransportModel");
Using
this.getView().getModel("TransportModel").setProperty("/Serial", serial);
<Text text="{TransportModel>/Serial}" width="auto" maxLines="1"/>
I am working on a SAPUI5 application. I have an XML view which contains an XML Fragment and a Button to save.
The fragment contains a few controls like drop-down, text field and a table.
When I press on the save button, I need to get all the rows in the table and call an OData update service.
The problem is in the onSave method in view controller. I get an error while accessing the table using its ID. Can anyone help me and advice how can I access controls used in fragments by their ID in the controller?
Here is the code snippet:
View:
<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns:core="sap.ui.core" xmlns:form="sap.ui.layout.form" xmlns="sap.m">
<Page>
...
<form:SimpleForm>
<core:Fragment id ="fr1" fragmentName="first" type="XML" />
<Button id="id1" press="onSave" />
</form:SimpleForm>
</Page>
</mvc:View>
Fragment definition:
<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
<Table id="tab1" mode="MultiSelect">
...
</Table>
</core:FragmentDefinition>
Controller:
sap.ui.controller("view", {
onSave: function() {
//var tab = this.getView().byId("tab1"); // Not working
var tab = sap.ui.getCore().byId("tab1"); // Not working
},
// ...
});
Accessing controls inside a fragment depends on how your fragment was created in the first place. Here is a list of cases with respective API to use to get the control reference.
Given:
this as a reference to the current controller instance
Fragment required from the module sap/ui/core/Fragment
<MyControl id="controlId"/> in the fragment definition
API to choose
👉this.byId("controlId");
... if the fragment was created with the view ID (either indirectly or directly):
this.loadFragment({ name: "..." }); // id: view ID given by default, API since 1.93
<!-- In the view embedding the fragment declaratively: -->
<core:Fragment fragmentName="..." type="XML"/><!-- id = view ID given by default -->
Fragment.load({ // API since 1.58
id: this.getView().getId(),
name: "...",
controller: this,
});
sap.ui.xmlfragment(this.getView().getId(), "...", this); // Deprecated
Resulting global ID: "componentId---viewId--controlId" *
👉this.byId(Fragment.createId("fragmentId", "controlId"));
... if a fragment ID was given with the view ID combined:
this.loadFragment({ id: this.createId("fragmentId"), name: "..." });
<core:Fragment id="fragmentId" fragmentName="..." type="XML"/>
Fragment.load({
id: this.createId("fragmentId"),
name: "...",
controller: this,
});
sap.ui.xmlfragment(this.createId("fragmentId"), "...", this); // Deprecated
Resulting global ID: "componentId---viewId--fragmentId--controlId" *
👉Fragment.byId("fragmentId", "controlId");
... if only the fragment ID was given without combining with the view ID:
this.loadFragment({
id: "fragmentId",
name: "...",
autoPrefixId: false, // Explicitly disabled view ID as prefix
});
Fragment.load({
id: "fragmentId",
name: "...",
controller: this,
});
sap.ui.xmlfragment("fragmentId", "...", this); // Deprecated
Resulting global ID: "fragmentId--controlId" *
👉sap.ui.getCore().byId("controlId");
... if no ID to prefix was given. The below settings are not recommended as all control IDs within the fragment definition will be registered globally without any prefix. The uniqueness of the IDs is not guaranteed!
this.loadFragment({ name: "...", autoPrefixId: false }); // Not recommended if no id
Fragment.load({ name: "...", controller: this }); // Not recommended
sap.ui.xmlfragment("demo.view.MyFragment", this); // Deprecated
Resulting global ID: "controlId"
* Do not rely on the resulting global ID, for example, concatenating ID parts manually in your application. Always use the dedicated APIs mentioned above such as byId and createId. See Stable IDs: All You Need to Know.
Favor model-first approach over byId
Instead of accessing the fragment controls directly, consider manipulating the UI via data binding. Changes in the model will be reflected in the UI automatically, and, if two-way binding is enabled, user inputs from the UI will be stored in the model directly.
SAP Fiori elements guidelines
When developing Fiori elements extensions, make sure to adhere to the documented compatibility guidelines, especially regarding byId:
[...] Don't access or manipulate SAP Fiori elements' internal coding.
[...] Must not access any UI elements that are not defined within your view extensions.
âš Caution
If you do not adhere to this guideline, your app may not work with future SAPUI5 versions because SAP Fiori elements might exchange controls for new ones that have a different API.
Looking at the OpenUI5 code at GitHub, it seems that the Fragment delegates the local ID generation to the containing view if the <Fragment/> itself does not have an explicit ID.
So your code this.getView().byId("tab1") should work as soon as you remove the id="fr1" attribute from your <Fragment/> element.
When using explicit IDs there is a static Fragment.byId method to retrieve the control. I guess you have to use it like this:
// Fragment required from "sap/ui/core/Fragment"
var fragmentId = this.getView().createId("fr1");
var tab = Fragment.byId(fragmentId, "tab1");
To make it work without explicit fragment ID and without static Fragment.byId() I used the following code snippet:
var prefix = this.getView().createId("").replace("--", "");
var fragment = sap.ui.xmlfragment(prefix, "-- XML fragment name --", this);
after this you can use this.getView().byId("tab1") as with any other control.
Given two XML Views:
<mvc:View
controllerName="my.namespace.controller.First"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<Button press=".onBtnPress" />
</mvc:View>
<mvc:View
controllerName="my.namespace.controller.Second"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">
<Button press=".onBtnPress" />
</mvc:View>
As expected, the press event is handled by First.controller.js or Second.controller.js.
Instead of duplicating the event handler code or implementing handlers in each Controller to chain/hand off the work, I want to declare a shared event handler.
According to docs this should be possible, using a naming convention for the handler:
Names starting with a dot ('.') are always assumed to represent a method in the controller.
Names containing a dot at a later position are assumed to represent global functions and are resolved by calling jQuery.sap.getObject with the full name.
So I change the handler and declare a shared object, like so:
First.view.xml:
<Button press="my.namespace.Shared.onBtnPress" />
Shared.js:
jQuery.sap.declare("my.namespace.Shared");
my.namespace.Shared = (function() {
var onBtnPress = function() {
console.log("button pressed");
};
return { onBtnPress : onBtnPress };
}());
Warning logged (debug sources) during view initialisation:
sap.ui.core.mvc.XMLView#__xmlview1: event handler function "my.namespace.Shared.onBtnPress" is not a function or does not exist in the controller. -
Calling jQuery.sap.getObject("my.namespace.Shared") yields undefined
Same issue when using sap.ui.define to make the object known.
Since UI5 1.69, it has become easier to share JS modules in XML view and fragment.doc
Here is an example: https://embed.plnkr.co/5G80I5HWObCuM5cG
<mvc:View controllerName="..."
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:core="sap.ui.core"
core:require="{ onButtonPress: 'my/shared/onButtonPress' }">
<Button text="Press" press="onButtonPress" />
</mvc:View>
As we can see, each button displays a different message depending on the view, even though the handler itself isn't included in the controller definition.
The this context is still the controller instance as documented in the topic Handling Events in XML Views:
As long as no event handler parameters are specified and regardless of where the function was looked up, it will be executed with the controller as the context object (this).
Your shard object looks weird
Try something like this:
sap.ui.define([], function() {
return sap.ui.base.Object.extend("my.namespace.Shared", function() {
onBtnPress : function() {
console.log("button pressed");
}
};
});
Also remember to put the object in the right directory.
I was able to copy-paste your sap.ui.define and verified it was created correctly jQuery.sap.getObject.
Make sure that your sap.ui.define has been called prior to your view being rendered.
You can set a breakpoint in the XMLTemplateProcessor where the event callbacks are handled for XML views for further debugging/timing issues.
If you look in the _resolveEventHandler function it should take you here which will perform the jQuery.sap.getObject.
I have a button into a bar in my XML view:
<Button xmlns="sap.m" id="idMenuBarSoc" text="{flagSocietyBar}" visible="true" icon="sap-icon://filter" press="handlePressSocFilter"/>
in the controller I write (in the init method):
this.getView().setModel('Oracle-Society', 'flagSocietyBar');
but if i test my application the button not show any text... ('')
What should I write in text="{?????????}" ?
That's not going to work... You haven't defined the model type (I think you want to use JSONModel?) and you haven't set the data to the model.
By the look of your code, I think you wanted to define a property 'flagSocietyBar' with value 'Oracle-Society', am I correct?
However, the setModel(oModel, sName) method is used incorrectly here. According to the API, oModel cannot be of type string but should be of type sap.ui.model.Model.
Modify your code to the following:
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData({flagSocietyBar : "Oracle-Society"});
this.getView().setModel(oModel);
and your button should then bind to text="{/flagSocietyBar}"
If you need named models, specify it as such:
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData({flagSocietyBar : "Oracle-Society"});
sap.ui.getCore().setModel(oModel, "myModel");
and your button should then bind to text="{myModel>/flagSocietyBar}"