Control with bound property doesn't display model data in the UI - sapui5

I'm migrating my app to new version of OpenUI5 (1.48) and have some problems with model bindings. I am using sap.ui.getCore().setModel(oModel, "myModel") for model declaration and when I'm trying to bind some controls to values from this model like this ...
<Text text="{local>/count}" />
... the value isn't displayed.
But if I get this model, set it to view in controller ...
var oModel = sap.ui.getCore().getModel("local");
this.getView().setModel(oModel);
<Text text="{/count}" />
... everything would work fine.
Maybe somebody faced a similar problem or has an idea what is wrong with my code?

You must be using a Component in your app. In that case, core models are not automatically propagated to the children of the ComponentContainer which is why your Text control doesn't know the model "local".
The reason why "{/count}" works is because you set the model explicitly on the view without any model name. If the model doesn't have a name, it's a default model and > has to be omitted in the binding path.
To learn more about where to set models, take a look at my answer to a similar question: https://stackoverflow.com/a/42251431/5846045

I think problem may be how you creating the JSON model!,
try this one.
Controller
sap.ui.define(["sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",],
function(Controller,JSONModel) {
"use strict";
return Controller.extend("com.stackoverflow.testUI5", {
onInit:function(){
var oData = {
count:"1"
};
var oModel = new JSONModel(oData);
sap.ui.getCore().setModel(oModel , "local")
//this.getView().setModel(oModel ,"local");
}
});
});
XML View
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<mvc:View controllerName="com.stackoverflow.testUI5"
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core" xmlns="sap.m" >
<Text text="{local>/count}"/>
</mvc:View>
this snippet will work.

Related

How to synchronize control values within different views

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"/>

Setting multiple properties of a component with one function

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

How to Access Elements from XML Fragment by ID

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.

How to start XML preprocessor without return a view component

I'm trying to create a launchpad for a set of applications that we use here. One of my problems is that I need to add different tiles in a tile container (slide,custom,standard, etc) and what I think that may be a solution is use XML templating to do that. What I want to achieve is something like that:
<TileContainer id="tileList"
allowAdd="true"
tileDelete="onDelete"
tiles="{path:'Atalhos>/' ,sorter:{path:'Atalhos>TileText',group:false}}">
<template:if test="{path:'Atalhos>TileCode', operator:'EQ',value1:'teste1'}">
<template:then>
<core:Fragment fragmentName="pelissari.soficom.launchpad.view.StandardTile" type="XML"/>
</template:then>
<template:else>
<core:Fragment fragmentName="pelissari.soficom.launchpad.view.StandardTile" type="XML"/>
</template:else>
</template:if>
</TileContainer>
but the problem is that I'm having this error when I try to do that.
UIComponent-dbg.js:52 Uncaught Error: failed to load
'http://schemas/sap/com/sapui5/extension/sap/ui/core/template/1/if.js'
from
resources/http://schemas/sap/com/sapui5/extension/sap/ui/core/template/1/if.js:
404 - Not found
I know that I need to start the preprocessor to use preprocessing instructions but all the examples that I found makes me more confuse that I was before.
My project is based on the sapui5 tutorial "WalkThrough where I have a component that starts an app view configured in the manifest and this view is navigate to the launchpad view by routing configuration again in the mainfest. all the examples create a view in the component CreateComponent function or in some controller function that loads other view. I just need to start the preprocessor for the list of tiles that I load from the entity set "/TileSet".
I found another way to do what I want. Now I'm using factory function to create the tiles as I need.
tileFactory: function(sId, oContext) {
var atalho = oContext.getProperty(oContext.sPath)
var oUIControl = null;
if (atalho.TileCode == 'teste2') {
oUIControl = new sap.m.StandardTile(sId, {
title: atalho.TileText
});
oUIControl.addStyleClass('tileSize3');
} else {
oUIControl = new sap.m.StandardTile(sId, {
title: atalho.TileText
});
oUIControl.addStyleClass('tileSize1');
}
oUIControl.attachPress(this.onPress, this);
oUIControl.addStyleClass('tile');
return oUIControl;
}
<Page id="tileGroup" showHeader="true"
content="{path:'Atalhos>/' ,sorter:{path:'Atalhos>TileOrder',group:false},factory:'.tileFactory'}">

Shared event handler for XML views with different controllers

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.