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.
Related
I am trying to display few tiles in a tile container which fetches data from a dummy JSON file. I have coded exactly shown in this sample. But my page appears empty. Also it doesn't show any errors in the console. Below are the snippets of my code.
View1.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function(Controller) {
"use strict";
return Controller.extend("AdminMovie.controller.View1", {
});
});
View1.view.xml
<mvc:View
displayBlock="true"
controllerName="AdminMovie.controller.View1"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
>
<Page showHeader="false" enableScrolling="false">
<mvc:XMLView viewName="AdminMovie.view.TileContainer"/>
<footer>
<OverflowToolbar id="otbFooter">
<ToolbarSpacer/>
<Button type="Accept" text="Add New Movie"/>
</OverflowToolbar>
</footer>
</Page>
</mvc:View>
TileContailner.view.xml
<mvc:View
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
controllerName="AdminMovie.controller.TileContainer"
>
<App>
<pages>
<Page
showHeader="false"
enableScrolling="false"
title="Stark"
>
<TileContainer id="container"
tileDelete="handleTileDelete"
tiles="{/MovieCollection}"
>
<HBox>
<StandardTile
icon="{icon}"
type="{type}"
number="{number}"
numberUnit="{numberUnit}"
title="{title}"
info="{info}"
infoState="{infoState}"
/>
</HBox>
</TileContainer>
<OverflowToolbar>
<Toolbar>
<ToolbarSpacer/>
<Button
text="Edit"
press=".handleEditPress"
/>
<ToolbarSpacer/>
</Toolbar>
</OverflowToolbar>
</Page>
</pages>
</App>
</mvc:View>
TileContainer.js
sap.ui.define([
"jquery.sap.global",
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel"
], function(jQuery, Controller, JSONModel) {
"use strict";
return Controller.extend("AdminMovie.controller.TileContainer", {
onInit: function(evt) {
// set mock model
var sPath = jQuery.sap.getModulePath("AdminMovie", "/MovieCollection.json");
var oModel = new JSONModel(sPath);
this.getView().setModel(oModel);
},
handleEditPress: function(evt) {
var oTileContainer = this.byId("container");
var newValue = !oTileContainer.getEditable();
oTileContainer.setEditable(newValue);
evt.getSource().setText(newValue ? "Done" : "Edit");
},
handleTileDelete: function(evt) {
var tile = evt.getParameter("tile");
evt.getSource().removeTile(tile);
}
});
});
Cause
The root view is missing a root control or the height of the parent HTML elements is not set to 100%. The child elements cannot be rendered in full size.
Resolution
For Standalone or Top-Level Applications
Add sap.m.App (or sap.m.SplitApp in case of a master-detail layout) once in the entire application project to your root view:
<!-- Root view (typically "App.view.xml") -->
<mvc:View controllerName="..."
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
displayBlock="true"
>
<App id="topLevelApp"> <!-- Not in any other views! -->
<pages>
<!-- ... -->
</pages>
</App>
</mvc:View>
Root controls such as sap.m.App, sap.m.SplitApp, and sap.m.Shell write:
a bunch of properties into the header of the HTML document e.g. the viewport meta tag via sap/ui/util/Mobile.init.
height: 100% to all its parent elements by default (unless isTopLevel is disabled). src
The reason why the linked sample is working, is that the control sap.m.App was already added in index.html. The samples shown in the Demo Kit, however, often miss index.html in the code page to be shown which can be confusing.
For Nested or Embedded Applications
If you're developing an app that is to be rendered within an an existing app, keep in mind that the top-level app might come already with one root control (sap.m.App with isTopLevel enabled or sap.m.SplitApp) in its root view. I.e. in this case:
Add height="100%" to the View node of the root view definition, and
Either use <App isTopLevel="false"> or replace the <App> with <NavContainer>.
<!-- Root view (typically "App.view.xml") -->
<mvc:View controllerName="..."
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
displayBlock="true"
height="100%"
> <!-- ↑ Set height="100%" -->
<App isTopLevel="false"> <!-- or:
<NavContainer> if the UI5 version is below 1.91 -->
<pages>
<!-- ... -->
</pages> <!--
</NavContainer> -->
</App>
</mvc:View>
Otherwise, not using the isTopLevel="false" will cause the footer area of the embedded app to be pushed out of the viewport. This is a common issue e.g. for the views that are intended to extend the standard SAP Fiori app "My Inbox" since the app already contains one root control as a top-level UI element. Refer to SAP KBA #3218822.
⚠️ For other readers: if you're not using a TileContainer, see my previous answer for general solutions → https://stackoverflow.com/a/50951902/5846045
Causes for empty TileContainer
Unexpected child control
Besides the missing root control, the <TileContainer> in your code contains a list of HBoxes.
<TileContainer>
<HBox> <!-- ❌ Wrong aggregation child! -->
<StandardTile />
The default aggregation of TileContainer is, however, a control that is derived from sap.m.Tile.
Hence, you should be getting the following error message in the browser console:
Uncaught Error: "Element sap.m.HBox#__hbox1" is not valid for aggregation "tiles" of Element sap.m.TileContainer#__xmlview1--container
Please, remove the <HBox> as the binding template:
<TileContainer>
<StandardTile /> <!-- ✔️ -->
TileContainer contains only one Tile
There was a bug (issue #1813) in the TileContainer which failed making the aggregation visible in Chrome (works in Firefox) if there was only a single Tile. The fix is delivered with OpenUI5 version 1.56+, 1.54.2+, 1.52.8+, 1.50.9+, 1.48.19+, and 1.46.2+.
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)
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"/>
I am creating a SAP Fiori application. I have input in a dialog box in that I have to fetch the input value. I am defining the dialog in fragment view.
When I try to give the id for input I am getting an error as adding element with duplicate id.
------ Fragment View------
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:app="http://schemas.sap.com/sapui5/extension/sap.ui.core.CustomData/1">
<Dialog title="Title" class="sapUiPopupWithPadding" >
<content>
<HBox>
<items>
<Text text="Name"></Text>
<Input value="" id="myId" > </Input>
</items>
</HBox>
</content>
<beginButton>
<Button text="Ok" press="DialogButton" />
</beginButton>
</Dialog>
---Controller Code---
DialogButton:function(oEvent) {
var myIdValue=sap.ui.getCore().byId("myId").getValue();
console.log("ID Value :::"+ myIdValue);
oDialogFragment.close();
}
You create a new dialog fragment instance every time you need to open the dialog .
This will cause the duplicated ID issue. Please keep a dialog fragment instance in your controller.
Please see the sample code:
DialogButton:function(oEvent) {
if(!this.oDialog) {
this.oDialog = sap.ui.xmlfragment("you.dialog.id", this );
}
this.oDialog.open();
}
take a look at the following help
IDs in Declarative XML or HTML Fragments you need to add an ID when the fragment is instantiated, that way the control has a prefix which is unique
It is also a good idea to add your fragment as dependant to your main view. This way it will be destroyed when the main view is destroyed. And you will not get duplicate id error when navigating away from your view and back.
DialogButton:function(oEvent) {
if(!this.oDialog) {
this.oDialog = sap.ui.xmlfragment("you.dialog.id", this );
this.getView().addDependent(this.oDialog);
}
this.oDialog.open();
}
This is my fragment,
The fragment not have a controller
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core">
<Dialog
title="Invio report via mail">
<content>
<FlexBox
alignItems="Start"
justifyContent="Center">
<items>
<TextArea id="idMailReport" value="themail.mail.it" rows="1" cols="50" />
</items>
</FlexBox>
</content>
<beginButton>
<Button text="Ok" press="onDialogOkButton" />
</beginButton>
<endButton>
<Button text="Close" press="onDialogCloseButton" />
</endButton>
</Dialog>
</core:FragmentDefinition>
Ho I can set che value of TextArea element?
I try to set it from a controller where I call the fragment:
var dialogFrafment = sap.ui.xmlfragment(
"dialogFragment",
"appIntra.fragment.dialog",
this // associate controller with the fragment
);
this.getView().addDependent(dialogFrafment);
dialogFrafment.open();
this.byId("idMailReport").setValue("initial.mail.com");
Can you help me?
please try
sap.ui.getCore().byId("idMailReport").setValue("initial.mail.com");
it should work.
Here is the official document.
I solve it!
var dialogFrafment = sap.ui.xmlfragment(
"appIntra.fragment.dialog",
this.getView().getController() // associate controller with the fragment
);
my problem is that i had set a name of a fragment: "dialogFragment"
Whitout it all work! ;)
A bit outdated, but might be useful for others. When you reuse your fragment inside a view, giving it unique id like:
var dialogFrafment = sap.ui.xmlfragment(
"dialogFragment", // this is fragment's ID
"appIntra.fragment.dialog",
...
);
retrieving id of your nested control goes like:
this.byId(sap.ui.getCore().Fragment().createId("dialogFragment", "idMailReport"));
That way, your prepend fragment's id to your control's id.