InteractiveBarChart: How to Bind Model - sapui5

I can't find any example about binding a JSON model to an InteractiveBarChart in an XML view.
My view is the following:
<core:View
xmlns:core="sap.ui.core"
xmlns:m="sap.m"
xmlns="sap.f"
xmlns:f="sap.f"
xmlns:smartFilterBar="sap.ui.comp.smartfilterbar"
xmlns:mc="sap.suite.ui.microchart"
xmlns:layout="sap.ui.layout"
xmlns:customData="http://schemas.sap.com/sapui5/extension/sap.ui.core.CustomData/1"
controllerName="webui.controller.Logger.HomeLogger"
height="100%"
>
<DynamicPage id="dynamicPage"
navButtonPress="onNavBack"
showNavButton="true"
>
<title>
<f:DynamicPageTitle>
<f:heading>
<m:Title text="Methode Logger" />
</f:heading>
<f:expandedContent>
<m:Text text="System analysis" />
</f:expandedContent>
<f:actions>
<m:ToolbarSpacer />
<m:Button
icon="sap-icon://action-settings"
binding="{securitySystem>/}"
press="onSettingsPress"
visible="{securitySystem>enabledApplications/enableSettingsAdmin}"
/>
</f:actions>
</f:DynamicPageTitle>
</title>
<header>
<DynamicPageHeader>
<content>
<layout:Grid defaultSpan="XL6 L6 M6 S12">
<m:FlexBox
width="20rem"
height="10rem"
alignItems="Center"
class="sapUiSmallMargin"
>
<m:items>
<mc:InteractiveBarChart
labelWidth="25%"
selectionChanged="chartSelectionChanged"
press="press"
bindBars="{/}"
>
<mc:bars>
<mc:InteractiveBarChartBar
label="{key}"
value="{value}"
/>
</mc:bars>
</mc:InteractiveBarChart>
</m:items>
</m:FlexBox>
</layout:Grid>
</content>
</DynamicPageHeader>
</header>
</DynamicPage>
</core:View>
I've to bind the InteractiveBarChart to my model, an array set in the controller. By SAP reference, I've to use bindBars method, but I can't make it work.

Any control in UI5 has the same concepts of data binding: property and aggregation bindings.
If control is aimed to show multiple things (i.e. table or list), it will have the so called aggregation.
In InteractiveBarChart control there is an aggregation "bars".
Any control can be bound against model via the unified binding syntax.
For programmatic binding is the following template: "bind{NAME OF AGGREGATION}". So in this case it will be "bindBars" method, which takes the same list of arguments as any aggregation binding;
For declaration binding in XML, you have to do 2 things:
tell the control about data source. In your case you should set the control's property with the name of aggregation "bars" to the binding string "{/}", in case you store the raw array in the root property of a default model (i.e. model without name)
define a template, which will be used as a basis of creation of a list of bars (you've already done it correctly)

Related

Why to wrap a `Dialog` with `FragmentDefinition`?

An UI5 dialog can be defined directly as a Dialog:
<Dialog
xmlns = "sap.m"
id = "helloDialog"
title = "Hello {/recipient/name}">
<beginButton>
<Button
text = "{i18n>dialogCloseButtonText}"
press = ".onCloseDialog" />
</beginButton>
</Dialog>
Or can be wrapped by a FragmentDefinition:
<core:FragmentDefinition
xmlns:core = "sap.ui.core"
xmlns = "sap.m">
<Dialog
id = "helloDialog"
title = "Hello {/recipient/name}">
<beginButton>
<Button
text = "{i18n>dialogCloseButtonText}"
press = ".onCloseDialog" />
</beginButton>
</Dialog>
</core:FragmentDefinition>
As far as I understand, a FragmentDefinition provides a higher degree of reuse since it doesn't depend on any view's controller but can be initialized with a custom controller using sap.ui.core.Fragment.load():
this._oDialog = await Fragment.load({
controller: fragmentController,
id: oView.getId(),
name: "webapp.view.MyDialog"
});
However, according to the documentation, starting UI5 1.93, the loadFragment() function is available on every controller instance extending sap.ui.core.mvc.Controller and this API has several advantages over the generic sap.ui.core.Fragment.load() function.
If I use a loadFragment(), should I still wrap a Dialog with FragmentDefinition? I've tried both implementations, both of them work and I see a dialog on a view, so what are the benefits of using FragmentDefinition if I still can directly call a Dialog with loadFragment()?
The <core:FragmentDefinition> is a runtime artifact that is not part of the DOM but serves only the purpose of wrapping multiple XML root nodes in *.fragment.xml documents. I.e.:
Fragment with multiple root nodes
From the topic "Fragments with Multiple Root Nodes"
<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core"> <!--mandatory-->
<Label text="..." />
<Input />
<Button text="..." />
</core:FragmentDefinition>
As XML documents need to have exactly one root node, to achieve XML fragments with multiple root nodes, an additional <FragmentDefinition> tag needs to be added as root element.
Fragment with a single root node
From the sample sap.m.ActionSheet
<!-- No need to wrap this single root node with <FragmentDefinition> -->
<ActionSheet id="actionSheet"
xmlns="sap.m"
xmlns:core="sap.ui.core"
core:require="{ MessageToast: 'sap/m/MessageToast' }"
title="Choose Your Action"
showCancelButton="true"
placement="Bottom">
<Button
text="Accept"
icon="sap-icon://accept"
press="MessageToast.show('Selected action is ' + ${$source>/text})" />
<!-- ... -->
<Button
text="Other"
press="MessageToast.show('Selected action is ' + ${$source>/text})"
/>
</ActionSheet>
As such, it is simply unnecessary for <Dialog> fragments too to use <core:FragmentDefinition>.
Note
The above applies only to XML fragments. JS fragments, for example, do not need FragmentDefinition. FragmentDefinition is not even a module you can require.
Whether the fragment was created via this.loadFragment, Fragment.load(), ...etc doesn't matter. <FragmentDefinition> plays a role only for the definition of fragments.

Strange behavior of IconTabBar with GenericTile

Why does the XML tree on the picture looks like shown on the second picture? MessageStrip tries to get into the content area of IconTabBar even jumping over 4 elements and tiles get out by any means. There are no restrictions in documentation on what can placed in the IconTabBar or in the IconTabFilter. GenericTile is not a layout which is supposed to take the whole place on the screen. How to put tiles into the content of IconTabBar?
Here's the code of the view:
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
<IconTabBar id="idTopLevelIconTabBar" class="sapUiResponsiveContentPadding">
<items>
<IconTabFilter id="start" icon="sap-icon://hint">
<GenericTile class="sapUiMediumMarginBeginEnd sapUiLargeMarginTop tileLayout"
header="Sales Fulfillment Application Title"
subheader="Subtitle"
>
<TileContent unit="EUR" footer="Current Quarter">
<ImageContent src="sap-icon://home-share"/>
</TileContent>
</GenericTile>
<GenericTile class="sapUiMediumMarginBeginEnd sapUiLargeMarginTop tileLayout"
header="Manage Activity Master Data Type"
subheader="Subtitle"
>
<TileContent />
</GenericTile>
<GenericTile class="sapUiMediumMarginBeginEnd sapUiLargeMarginTop tileLayout"
header="Manage Activity Master Data Type With a Long Title Without an Icon"
subheader="Subtitle Launch Tile" mode="HeaderMode"
>
<TileContent unit="EUR" footer="Current Quarter" />
</GenericTile>
<GenericTile class="sapUiMediumMarginBeginEnd sapUiLargeMarginTop tileLayout"
header="Jessica D. Prince Senior Consultant"
subheader="Department"
>
<TileContent/>
</GenericTile>
<MessageStrip
type="Information"
showIcon="true"
text="Another IconTabFilter"
/>
</IconTabFilter>
<IconTabFilter id="layouts" icon="sap-icon://bookmark">
<!-- ... -->
</IconTabFilter>
</items>
</IconTabBar>
</mvc:View>
The solution was to remove my custom CSS (e.g. tileLayout) and to add any margin class (e.g. "sapUiLargeMarginTop") to the Message Strip so that Generic Tile fits inside an IconTabFilter.
The result

List Binding is not bound against a list for /JSONDataSet

I have a JSON model, which I build from a Metadata set.
So I created that JSON array and did the following:
var oModel = new JSONModel({
JSONDataSet: oJSONDataArray
});
this._oFragment.setModel(oModel);
In my fragment, I have a table:
<Table id="tableId" items="{ path:'/JSONDataSet' }">
<columns>
<Column>
<Text text="HeaderColumn1"/>
</Column>
<!-- ... -->
</columns>
<ColumnListItem>
<Text text="{Value1}"/>
<!-- ... -->
</ColumnListItem>
</Table>
Now everything works fine on my fragment. In my list, I'll see all that data from my JSON model, but I still receive this weird error in my console:
List Binding is not bound against a list for /JSONDataSet
How can I solve this issue?
List Binding is not bound against a list for ...
The above error occurs only in ODataListBinding.js and is thrown when the module fails to find the entityset name within the service $metadata document or if the resulting multiplicity is not "*". source
In your case, the framework assumes that JSONDataSet is some entity set name defined in the $metadata which obviously cannot be found. In order to prevent framework to search for that in $metadata, you'll need to tell that JSONDataSet is not from the unnamed default model (ODataModel) but from another model (JSONModel).
Try to give it a name, and assign the name in the binding definitions like this:
const oModel = new JSONModel({
JSONDataSet: /*some data*/
});
this._oFragment.setModel(oModel, "anotherModel");
<Table id="tableId" items="{anotherModel>/JSONDataSet}">
<!-- ... -->
<ColumnListItem>
<Text text="{anotherModel>Value1}"/>
<!-- ... -->
</ColumnListItem>
</Table>
The framework won't try to resolve anotherModel>/JSONDataSet until that model is registered and set to the fragment. The error will be gone since the framework now knows that it's not initializing ODataListBinding but a client ListBinding.
If you take a look at the browser console, probably you have already an error telling you that "the template or factory function was not provided" or something similar.
In the following code, there is something missing
<Table id="tableId" items="{ path:'/JSONDataSet' }">
<columns>
.....
<columns>
</Table>
if you do items="{ path:'/JSONDataSet' }", it means that you want the items in your list to be created dynamically based on the path /JSONDataSet from your model. This path should point to an array of some kind (usually an array of objects). Using UI5 terms, you are trying to use an aggregation binding.
However, how do you want the items in your Table to be created?
That's why you need to provide a template item, declaring an example item inside your table:
<Table id="tableId" items="{ path:'/JSONDataSet' }">
<columns>
.....
<columns>
<items>
<ColumnListItem>
<cells>
<ObjectIdentifier
title="{a}"
text="{b}"/>
<Text
text="{c}" />
</cells>
</ColumnListItem>
</items>
</Table>
See more examples in the UI5 documentation.
In the code above, a, b and c are proprierties found in every object inside you array.
In the end, if you array contains 10 items, 10 rows will be created in your table. If you want to create columns dynamically, just provide a single Column example and use columns="{ path:'/JSONDataSet'} instead.

How can I put icon and text in select items

I have an sap.m.Select control for a list of countries and I need to put flag near everyone. How can I do it? In XML, if it's possible.
Here is my XML code:
<m:Label text="{i18n>COUNTRY}" />
<m:Select width="100px"
fieldWidth="60%"
class="xcuiInputNoMargin"
enabled="{Edit>/EditOn}"
items="{countryList>/}"
>
<core:Item
key="{countryList>Country}"
text="{countryList>Country} - {countryList>Name}"
/>
</m:Select>
The sap.m.Select Object is restricted to display text (or Like #Jasper_07 said) icon only.
I think that the best solution for your problem is to use another object instead of your select. You can use Select Dialog and put inside whatever you want, like listItem with image.
This is an example:
<SelectDialog
noDataText="No Products Found"
title="Select Product"
search="handleSearch"
confirm="handleClose"
close="handleClose"
items="{
path: '/ProductCollection'
}" >
<StandardListItem
title="{Name}"
description="{ProductId}"
icon="{ProductPicUrl}"
iconDensityAware="false"
iconInset="false"
type="Active" />
</SelectDialog>
see link bellow
As of UI5 version 1.62, the following controls support displaying the icon on the left side.
sap.m.Select
sap.m.SelectList
And other controls based on the above mentioned ones, such as sap.m.ComboBox.
Here is an example:
sap.ui.getCore().attachInit(() => sap.ui.require([
"sap/ui/core/mvc/XMLView",
], XMLView => XMLView.create({
definition: `<mvc:View xmlns:mvc="sap.ui.core.mvc" height="100%">
<Select xmlns="sap.m" xmlns:core="sap.ui.core" class="sapUiTinyMargin">
<core:ListItem text="Paper plane" icon="sap-icon://paper-plane" />
<core:ListItem text="Stop Watch" icon="sap-icon://fob-watch" />
<core:ListItem text="Umbrella" icon="sap-icon://umbrella" />
</Select>
</mvc:View>`
}).then(view => view.placeAt("content"))));
<script id="sap-ui-bootstrap"
src="https://openui5nightly.hana.ondemand.com/resources/sap-ui-core.js"
data-sap-ui-libs="sap.ui.core, sap.m"
data-sap-ui-preload="async"
data-sap-ui-async="true"
data-sap-ui-theme="sap_belize"
data-sap-ui-compatversion="edge"
data-sap-ui-xx-waitfortheme="true"
data-sap-ui-xx-xml-processing="sequential"
></script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>
Keep in mind to use sap.ui.ListItem as an aggregation child in this case, instead of sap.ui.core.Item.
Limitation
Currently, the icon property only allows resource paths from "sap-icon://*". I.e. images, that are not icons such as country flags, are not possible. A possible workaround would be to make use of emoji flags as additionalText.
Otherwise, I'd recommend to look for alternative controls as shmoolki suggested.
from the documentation of sap.m.Select
The URI to the icon that will be displayed only when using the
IconOnly type
seems limiting, but try
<m:Select
type="sap.m.SelectType.IconOnly"
icon="sap-icon://cart">
</m:Select>

How to dynamically load an XML fragment in XML view?

Suppose I have the following XML view:
<mvc:View xmlns:mvc="sap.ui.core.mvc" ...>
<Page>
<content>
<l:VerticalLayout>
<l:content>
<core:Fragment fragmentName="my.static.Fragment" type="XML" />
</l:content>
</l:VerticalLayout>
</content>
</Page>
</mvc:View>
The fragment my.Fragment is statically loaded. However, I now want to dynamically change the to-be-loaded fragment (ideally using data binding the fragmentName property, but any other means should be ok as well), ie. something like this:
<mvc:View xmlns:core="sap.ui.core.mvc" ...>
<Page>
<content>
<l:VerticalLayout>
<l:content>
<core:Fragment fragmentName="{/myDynamicFragment}" type="XML" />
</l:content>
</l:VerticalLayout>
</content>
</Page>
</mvc:View>
However, the latter does not work, and the Fragment definitions don't allow for data binding... I might have missed something, but how should I dynamically change the Fragment in my XML view based on a parameter/model property/etc?
For now, I have resorted to a custom control instead of directly using a fragment in my view, and have that control do the dispatching to the appropriate Fragment, but I feel there should be an easier, out-of-the-box way...
I think the only solution will be initialization of fragment from onInit method of controller:
sap.ui.controller("my.controller", {
onInit : function(){
var oLayout = this.getView().byId('mainLayout'), //don't forget to set id for a VerticalLayout
oFragment = sap.ui.xmlfragment(this.fragmentName.bind(this));
oLayout.addContent(oFragment);
},
fragmentName : function(){
return "my.fragment";
}
});
The fragment name can also result from a binding, including an expression binding which evaluates to a constant. As formatter functions return strings, and not booleans, === 'true' has been added in the following example:
Example: Dynamic Fragment Name
<core:Fragment fragmentName="{= ${path: 'facet>Target', formatter: 'sap.ui.model.odata.AnnotationHelper.isMultiple'} === 'true'
? 'sap.ui.core.sample.ViewTemplate.scenario.TableFacet'
: 'sap.ui.core.sample.ViewTemplate.scenario.FormFacet' }" type="XML"/>