I am trying to set the size on sap.ui.layout.SplitPane on the left side as following:
<Page title="Where used">
<l:ResponsiveSplitter defaultPane="default">
<l:PaneContainer>
<l:SplitPane requiredParentWidth="400">
<l:layoutData>
<l:SplitterLayoutData size="20%"/>
</l:layoutData>
<Panel height="100%">
<Label text="Hello"/>
</Panel>
</l:SplitPane>
<l:SplitPane requiredParentWidth="400">
<Panel height="100%">
<Label text="Hello"/>
</Panel>
</l:SplitPane>
</l:PaneContainer>
</l:ResponsiveSplitter>
</Page>
As you can see on the code, I tried to achieve with
<l:SplitterLayoutData size="20%"/>
But the size does not apply at all. What am I doing wrong?
I looked at this example: https://github.com/SAP/openui5/blob/master/src/sap.ui.layout/test/sap/ui/layout/ResponsiveSplitter.html#L106, [Result]
Update: The fix should be available as of UI5 version 1.60. Otherwise, please keep reading..
Just figured out that it only works if the aggregation <l:layoutData> comes after the default aggregation.
<l:SplitPane requiredParentWidth="400">
<Panel height="100%">
<Label text="Panel 1"/>
</Panel>
<l:layoutData>
<l:SplitterLayoutData size="20%"/>
</l:layoutData>
</l:SplitPane>
.. which shouldn't actually matter. I'll analyse the source code why it matters and update the answer later.[1] But this is how you can "fix" it so that it works for now.
Source: https://embed.plnkr.co/0yo35xOiSmF5eG6R?show=V.view.xml,preview
[1]: It turns out that SplitPane has an overwritten mutator for the layoutData aggregation:
SplitPane.prototype.setLayoutData = function(oLayoutdata) {
var oContent = this.getContent();
if (oContent) {
return oContent.setLayoutData(oLayoutdata);
} else {
return this;
}
};
source
As you can see, it applies the layout data not to itself but to its content. If the content doesn't exist at that moment, the <layoutData> is just ignored. And since XML nodes are evaluated sequentially one by one, the Panel (content) had to come first before the SplitterLayoutData.
IMO this is clearly a bug in the framework since the framework has to assure that the outcome is always the same no matter in which order aggregations were defined (content <--> layoutData).
Please check this: JS Fiddle
oResponsiveSplitter = new sap.ui.layout.ResponsiveSplitter({
defaultPane: "defaultPane",
rootPaneContainer: [
new sap.ui.layout.PaneContainer({
orientation: "Horizontal",
panes: [
new sap.ui.layout.PaneContainer({
// orientation: "Horizontal",
panes: [
new sap.ui.layout.SplitPane({
demandPane: true,
content: new sap.m.Panel({
headerText: "1",
content: new sap.m.Text({text: lorem + lorem})
}),
requiredParentWidth: 400
}),
new sap.ui.layout.SplitPane({
demandPane: false,
content: new sap.m.Panel({
headerText: "2",
content: new sap.m.Text({text: lorem + lorem})
}),
requiredParentWidth: 400,
layoutData: new sap.ui.layout.SplitterLayoutData({
size: "20%"
})
}),
new sap.ui.layout.SplitPane({
demandPane: true,
content: new sap.m.Panel({
headerText: "3",
content: new sap.m.Text({text: lorem + lorem})
}),
requiredParentWidth: 400,
}),
]
})
]
})
]
});
I think it answers your problem.
Also, I understand that here SplitterLayoutData size works as expected when the layout is horizontal.
Related
According to the Use Stable IDs article, it is recommended to use stable IDs for the UI5 elements. At the same time, I've reviewed multiple samples of sap.tnt.NavigationListItem implementation and I've paid attention that in case of sap.tnt.NavigationListItem no id is used but key, e.g.:
<tnt:NavigationListItem text="{name}" icon="{icon}" key="{controller}" select="onItemSelect" />
I've also tried to assign id to tnt:NavigationListItem and then access it from the oEvent-object via the standard oEvent.getProperty("%MY_ID%") approach but no success.
My questions:
When should I use id and when key for UI5-items in XML-templates?
Is it particular sap.tnt.NavigationListItem case that key is preferred over id or I just missed something important?
Based on your comments, I made a small example.
If your items are hard-coded definitely go for the key in the
XML definition.
If your items come over a model. You can do the same
as everywhere else. Use the data to trigger the right action.
Use the ID if you need to identify the UI element e.g. buttons in a test or if you add User Assistance Help.
sap.ui.controller("view1.initial", {
onInit: function(oEvent) {
this.getView().setModel(
new sap.ui.model.json.JSONModel({
items: [{
title : "1",
subItems: [
{ keyInData: "1", title : "1-a" },
{ keyInData: "2", title : "1-b" },
{ keyInData: "3", title : "1-c" },
{ keyInData: "4", title : "1-d" }
]
}],
}));
},
onItemSelect: function(oEvent) {
const item = oEvent.getParameter("item")
console.log("key over key binding: " + item.getKey() )
console.log("key over databinding: " + item.getBindingContext().getObject().keyInData )
}
});
sap.ui.xmlview("main", { viewContent: jQuery("#view1").html() } ).placeAt("uiArea");
<script id="sap-ui-bootstrap"
src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-theme="sap_bluecrystal"
data-sap-ui-xx-bindingSyntax="complex"
data-sap-ui-compatVersion="edge"
data-sap-ui-libs="sap.m"></script>
<div id="uiArea"></div>
<script id="view1" type="ui5/xmlview">
<mvc:View controllerName="view1.initial" xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns:tnt="sap.tnt">
<tnt:NavigationList
id="navigationList"
width="320px"
selectedKey="subItem3" items="{/items}">
<tnt:NavigationListItem text="{title}" items="{ path:'subItems', templateShareable:false}" icon="sap-icon://employee" >
<tnt:NavigationListItem text="{title} - {keyInData}" key="{keyInData}" select="onItemSelect" />
</tnt:NavigationListItem>
</tnt:NavigationList>
</mvc:View>
</script>
I am pretty new to the SAP environment and couldn't find a suitable solution to the following problem in the community forum or using the samples/templates/API docs or SAP books, thus I am asking this question.
My Problem: I created a CDS-View with Annotations (for a table and a chart) and linked it using my service to an own SAPUI5 application. While the SmartTable is created without any problems, the SmartChart is giving me the following error:
TypeError: can't convert undefined to object RoleFitter.js
I looked up the error and it seems like something is not correct in the annotations file. However, I couldn't find an error and since it is auto-generated from the CDS-View Annotations I am a little confused (the SmartTable is working and I can add/change UserText Annotations without any problem).
Furthermore, when I use the created CDSView wit a standard SAP template (e.g. in cards in an Overview Page Template) it works just fine.
For any solutions or hints I am super thankful since this is bothering me a lot and I don't understand why the SmartTable is working but the chart isn't.
Thanks in advance!
CDSView:
#AbapCatalog.sqlViewName: 'ZYSS20MSGITEST3'
#AbapCatalog.compiler.compareFilter: true
#AccessControl.authorizationCheck: #NOT_REQUIRED
#EndUserText.label: 'Test view 3'
#UI: {headerInfo: {
typeName:'Test3',
title:{
type: #STANDARD,
label: 'Suche'
}
}
}
#UI.chart: [{
qualifier: 'OccupiedSeats',
chartType: #COLUMN,
dimensions: [ 'FlightCode' ],
measures: [ 'MaximumCapacity', 'EconomyOccupiedSeats', 'BusinessOccupiedSeats', 'FirstOccupiedSeats' ]
}]
define view ZY_SS20_MSGI_TEST3
as select from sflight
{
#UI.lineItem: [ { position: 10 } ]
key concat(carrid, connid) as FlightCode,
#UI.lineItem: [ { position: 30 } ]
seatsmax as MaximumCapacity,
#UI.lineItem: [ { position: 40 } ]
seatsocc as EconomyOccupiedSeats,
#UI.lineItem: [ { position: 50 } ]
seatsocc_b as BusinessOccupiedSeats,
#UI.lineItem: [ { position: 60 } ]
seatsocc_f as FirstOccupiedSeats
}
Dashboard.controller.js:
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
"use strict";
return Controller.extend("ZY_SS20_MSGI_TESTN.controller.Dashboard", {
onInit: function() {
this._oModel = new sap.ui.model.odata.ODataModel("/sap/opu/odata/sap/ZY_SS20_MSGPROGRESSSRV_SRV/", true);
this.getView().setModel(this._oModel);
}
});
Dashboard.view.xml:
<mvc:View controllerName="ZY_SS20_MSGI_TESTN.controller.Dashboard" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc" xmlns:smartChart="sap.ui.comp.smartchart"
xmlns:data="http://schemas.sap.com/sapui5/extension/sap.ui.core.CustomData/1" xmlns:smartFilterBar="sap.ui.comp.smartfilterbar" xmlns:smartTable="sap.ui.comp.smarttable"
displayBlock="true" xmlns="sap.m">
<App>
<pages>
<Page title="{i18n>title}">
<content>
<VBox fitContainer="true">
<smartFilterBar:SmartFilterBar
id="smartFilterBar" entitySet="ZY_SS20_MSGI_TEST3" />
<smartTable:SmartTable id="mySmartTable"
entitySet="ZY_SS20_MSGI_TEST3" smartFilterId="smartFilterBar"
tableType="Table" useExportToExcel="true"
useVariantManagement="false" useTablePersonalisation="false"
header="Throughput" showRowCount="true" enableAutoBinding="true"
initiallyVisibleFields="FlightCode,MaximumCapacity,EconomyOccupiedSeats,BusinessOccupiedSeats,FirstOccupiedSeats">
</smartTable:SmartTable>
<smartChart:SmartChart entitySet="ZY_SS20_MSGI_TEST3"
enableAutoBinding="true" id="mySmartChart" useVariantManagement="false"
useChartPersonalisation="false" header="Test"></smartChart:SmartChart>
</VBox>
</content>
</Page>
</pages>
</App>
</mvc:View>
The problem seems to be that you are providing a qualifier to the chart annotation but not specifying the same to the SmartChart control. Try adding this property to your SmartChart tag:
data:chartQualifier="OccupiedSeats"
I have an "Add New…" screen with multiple sap.m.Input fields. Everything is working. I submit the form and the values are stored in the DB. But once I re-open this "Add New…" screen, I get the form with previously entered values.
Currently, I can solve the issue iterating over all sap.m.Input fields with sap.ui.core.Element, resetting the values:
Element.registry.forEach(el => {
if (el.isA("sap.m.Input") && el.sId.includes(inputFieldsMask)) {
sap.ui.getCore().byId(el.sId).setValue("");
}
});
Where inputFieldsMask is a mask for all input fields of the relevant screen.
As far as I understand, Element.registry.forEach iterates over all controls in the app, therefore I'm not sure that, from a performance point of view, it's an optimal approach to clean up the fields.
Is there a better way to reset input fields from the previously entered values?
There are several ways to reset the control values depending on what kind of approach you took to create the new entry. Generally, we can make use of the following APIs:
Pass the context to the target container.
In case of working with client-side models: targetContainer.bindElement("newItemPath").
Otherwise, call myV2ODataModel.createEntry("/ThatSet", {...}), which returns a new context, and then pass it to targetContainer.setBindingContext(context, "modelName").
This resolves all the relative bindings in the target container.
<user enters some values and submits ...>
targetContainer.unbindElement("modelName") after the edit was successfully stored.
By unbinding element, relatively bound control values are reset automatically.
Example (using client-side model):
sap.ui.getCore().attachInit(() => sap.ui.require([
"sap/ui/core/mvc/XMLView",
"sap/ui/model/json/JSONModel",
"sap/base/util/uid",
], (XMLView, JSONModel, createPseudoUniqueID) => XMLView.create({
definition: `<mvc:View xmlns:mvc="sap.ui.core.mvc" height="100%">
<App xmlns="sap.m">
<Page backgroundDesign="List" title="Resetting inputs via client-side Model and Context">
<headerContent>
<Button id="addBtn" text="Add Item" type="Emphasized" />
</headerContent>
<List id="myList" growing="true" items="{
path: '/myItems',
key: 'key',
templateShareable: false
}">
<StandardListItem title="{value}" info="Key: {key}"/>
</List>
</Page>
<dependents>
<Dialog id="myDialog"
icon="sap-icon://ui-notifications"
title="New Item"
draggable="true"
class="sapUiResponsiveContentPadding"
>
<Input id="myInput"
placeholder="<New value>"
valueLiveUpdate="true"
value="{
path: 'value',
type: 'sap.ui.model.type.String',
constraints: {
minLength: 1
}
}"
/>
<beginButton>
<Button
text="Submit"
enabled="{= !!%{value} && !%{messages>/}.length}"
/>
</beginButton>
</Dialog>
</dependents>
</App>
</mvc:View>`,
models: {
undefined: new JSONModel({
"myItems": [],
}),
"messages": sap.ui.getCore().getMessageManager().getMessageModel()
},
afterInit: function() {
sap.ui.getCore().getMessageManager().registerObject(this, true);
this.byId("addBtn").attachPress(handleAddPress.bind(this));
this.byId("myInput").attachSubmit(handleSubmit.bind(this));
this.byId("myDialog").setEscapeHandler(onESCPress.bind(this))
.attachAfterClose(onAfterClose.bind(this))
.getBeginButton().attachPress(handleSubmit.bind(this));
function handleAddPress(event) {
const dialog = this.byId("myDialog");
const listBinding = this.byId("myList").getBinding("items");
listBinding.suspend(); // Do not update the list yet
this._currentItems = this.getModel().getProperty("/myItems"); // temp in case user cancels
dialog.getModel().setProperty("/myItems", this._currentItems.concat({})); // new empty item
dialog.bindElement("/myItems/" + listBinding.getLength()); // enable data synchronization via TwoWay binding
dialog.open();
}
function onESCPress(promise) {
const model = this.getModel();
model.setProperty("/myItems", this._currentItems, /*context*/null, /*async*/true);
return promise.resolve(); // continue closing dialog
}
function onAfterClose(event) {
handleAfterClose(event.getSource(), this.byId("myList").getBinding("items"));
}
function handleAfterClose(dialog, listBinding) {
dialog.unbindElement(); // reset data
dialog.setBusy(false);
listBinding.resume();
}
function handleSubmit() {
const dialog = this.byId("myDialog");
if (!dialog.getBeginButton().getEnabled()) return; // something is wrong
dialog.setBusy(true);
if (!this._isStillRequesting) {
this._isStillRequesting = true;
/* send request */setTimeout(mySuccessHandler.bind(this), 3000)
};
}
function mySuccessHandler(newKeyFromServer = createPseudoUniqueID()) {
const dialog = this.byId("myDialog");
this._isStillRequesting = false;
if (!dialog.isOpen()/* request was aborted e.g. by pressing ESC */) {
return; // exit early
}
const context = dialog.getBindingContext();
const value = context.getProperty("value");
dialog.getModel().setProperty(context.getPath("key"), newKeyFromServer);
dialog.close();
sap.ui.require([
"sap/m/MessageToast"
], MT => window.requestAnimationFrame(() => MT.show(`${value} created`)));
}
},
}).then(view => view.placeAt("content"))));
<script id="sap-ui-bootstrap" src="https://ui5.sap.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.ui.core,sap.m"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-async="true"
data-sap-ui-compatversion="edge"
data-sap-ui-excludejquerycompat="true"
data-sap-ui-xx-waitForTheme="init"
></script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>
As explained above, binding and unbinding element also applies to server-side models such as v2.ODataModel.
Benefits
✅ Reduced overload: no need to iterate over all existing controls. Reset only those automatically that need to be reset.
✅ Control agnostic: does not rely on control specific APIs such as myInput.setValue, mySwitch.setState, etc..
✅ Reduced maintenance costs: no need to maintain list of model properties in controller that application needs to reset manually.
Best practice is to use a model to store your application data and to bind any input field to that model. I added an example here. For the sake of simplicity the model data is cleared when the button is pressed.
In a real world application you would place any setup of the model to the onRouteMatched handler to ensure that the data is in an initial state.
onRouteMatched : function(event) {
this.getView().getModel().setData({
"firstName": "",
"lastName": ""
});
}
Bind all your control values to a model. Then reset this model after you've successfully saved the data.
Example:
control1.bindProperty("value", "/controlValues/control1Value"); // Binding
// control1.bindProperty("value", "/controlValues/name");
// <Input value="{/controlValues/name}" /> // <-- ideal binding in xml view
this.getView().getModel().setProperty("/controlValues", this.resetFormData()); // Clear Model
resetFormData: function () {
var emptyControlValues = {
"control1Value": "", // "name": "", <-- bind to control
"control2Value": 0, // "age": 0,
"control3Value": "", // "address": "",
"control4Value": "" // "tel": ""
};
return emptyControlValues;
};
I'm using Sap.m.SelectDialog to display the value request(F4 help in abap).When i do it with JS view all is good,but when do it with XML view by creating the fragment, binding is not happening with the "items" aggregation.
please let me know what went wrong.
var mydata = { data : [
{
info: "SAP UI5",
name: "Sam",
},
{
info: "SAP UI5",
name: "Ram",
},
{
info: "SAP UI5",
name: "Prem",
}
]};
Js view logic:
var oDialog = new sap.m.SelectDialog({
title: "Select an item",
items: {
path:"/data",
template : new sap.m.StandardListItem({
title: "{name}"
})
}
})
oDialog.open();
}
XML fragment :
<SelectDialog xmlns="sap.m"
id ="id3"
title="Select a product">
<items id="ls3"
path="/data">
<StandardListItem title="{name}">
</StandardListItem>
</items>
.
Reslut of JS view
Result of Xml view
Try XML fragment this way:
<SelectDialog xmlns="sap.m"
id ="id3"
items="{/data}"
title="Select a product">
<items>
<StandardListItem title="{name}"/>
</items>
</SelectDialog>
I'm trying to create stacked column chart as on the picture attached, but I would like to remove "Effort" text highlighted in yellow.
The chart should display (per day) how many hours people worked on the project.
Stacked column chart
I have and XML view:
<core:View controllerName="sap.ui.demo.myFiori.view.Main"
xmlns:core="sap.ui.core" xmlns:viz="sap.viz.ui5.controls"
xmlns:viz.feeds="sap.viz.ui5.controls.common.feeds" xmlns:viz.data="sap.viz.ui5.data"
xmlns="sap.m">
<App>
<Page title="Reporting">
<viz:VizFrame xmlns:viz="sap.viz.ui5.controls" id="idVizFrame"
uiConfig="{applicationSet:'fiori'}" vizType="stacked_column" width="100%">
<viz:dataset>
<dataSet:FlattenedDataset xmlns:dataSet="sap.viz.ui5.data" data="{/Time}">
<dataSet:dimensions>
<dataSet:DimensionDefinition name="Date" value="{date}">
</dataSet:DimensionDefinition>
<dataSet:DimensionDefinition name="Name" value="{name}">
</dataSet:DimensionDefinition>
</dataSet:dimensions>
<dataSet:measures>
<dataSet:MeasureDefinition name="Effort" value="{real_effort}">
</dataSet:MeasureDefinition>
</dataSet:measures>
</dataSet:FlattenedDataset>
</viz:dataset>
<viz:feeds>
<feed:FeedItem xmlns:feed="sap.viz.ui5.controls.common.feeds"
uid="valueAxis" type="Measure" values="Effort"/>
<feed:FeedItem xmlns:feed="sap.viz.ui5.controls.common.feeds"
uid="categoryAxis" type="Dimension" values="Date" />
<feed:FeedItem xmlns:feed="sap.viz.ui5.controls.common.feeds"
uid="color" type="Dimension" values="Name" />
</viz:feeds>
</viz:VizFrame>
</Page>
</App>
</core:View>
In the controller in the onInit function I tried to set vizProperties. But when I set hidSubLevels of label of categoryAxis to true, then dates disappear. I don't understand why "Effort" is displayed as category, because it is defined as measure, not category. Could someone please advise how to do such chart without category "Effort" (yellow-highlighted on the picture attached)?
var oVizFrame = this.getView().byId("idVizFrame");
oVizFrame.setVizProperties({
interaction: {
behaviorType: null
},
plotArea: {
dataLabel: {
visible: false
}
},
valueAxis: {
title: {
visible: true,
},
},
categoryAxis: {
title: {
visible: false,
text: 'Category text'
},
label: {
hideSubLevels: false
},
},
title: {
visible: true,
text: 'Project'
},
tooltip: {
visible: true,
}
});
Setting the visibility of the categoryAxis to false seems like the correct approach. I took your code and put it into a Plunker and it works there.
Can you please remove as many as other possible configurations and try it again. If that doesn't help, can you please update my Plunker and create a runnable version of your issue? Maybe it's also a UI5 version issue. You could also try out to upgrade to a newer version.
Here's my Plunker:
https://embed.plnkr.co/td2ANcu0F2lZVgd7dfTC/