SAP UI5 Dynamically switch between fragments - sapui5

How do I dynamically switch between two fragments using click / press event?
I have the following XML fragment which in turn has nested two fragments:
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core">
<IconTabFilter
id="containerFrag"
text="{name}"
key="{id}">
<dependents>
<core:Fragment
fragmentName="com.example.fragments.fragment1"
type="XML" />
<core:Fragment
fragmentName="com.example.fragments.fragment2"
type="XML" />
</dependents>
</IconTabFilter>
</core:FragmentDefinition>
Assuming each fragment has a button who's handler has the following
buttonPress: function(oEvent) {
let oView = this.getView();
let showFrag1 = oEvent.getParameter("arguments"); //showFrag1 = true / false
let fragToShow = showFrag1 ? oView.byId("frag1Id").clone() : oView.byId("frag2Id").clone()
let container = oView.byId("containerFrag");
container.destroyContent();
container.addContent(fragToShow);
}
When I debug using chrome, fragToShow updates with the correct frag depending on the showFrag1 argument, but the view doesn't get updated - container.addContent(fragToShow) seems to have no effect except if I reload the page

Not sure why this works but I had to go one up from the container and insert the new fragment there like so:
let container = oView.byId("containerFrag");
container.getParent().destroyContent();
container.getParent().addContent(fragToShow);
This example also helped: https://ui5.sap.com/#/entity/sap.ui.layout.form.Form/sample/sap.ui.layout.sample.Form354 - though they use removeAllContent and insertContent, both seem to work just fine.
Thanks #D. Seah and #Ethan Jewett

Related

SAPUI5 sap.m.list in a popover is emtpy when populated using bindElement

I have a table (sap.m.table) with different columns. One columns contains a link and when the user clicks on the link a popover fragment opens and shows some details in a list (sap.m.list). The data is coming from an oData Service. One Entity feeds the table and through a navigation property the data for the popover is fetched.
I have a working example for this scenario where I create the the list template in the controller. But I believe that this should also be possible just by xml and a bindElement in the controller. What is the mistake in my second scenario?
First Scenario (which is working fine):
Popover XML:
<Popover
showHeader="false"
contentWidth="320px"
contentHeight="300px"
placement="Bottom"
ariaLabelledBy="master-title">
<Page
id="master"
class="sapUiResponsivePadding--header"
title="Aktionen">
<List
id="AktionList">
</List>
</Page>
</Popover>
Calling the Popover in the controller file (sPath is /TableEntity('123456')/AktionSet):
if (!this._oPopover) {
Fragment.load({
id: "popoverNavCon",
name: "bernmobil.ZPM_STOERUNG_FDA.view.AktionPopover",
controller: this
}).then(function(oPopover){
this._oPopover = oPopover;
this.getView().addDependent(this._oPopover);
var oList = Fragment.byId("popoverNavCon", "AktionList");
var oItemTemplate = this._BuildItemTemplate();
oList.bindAggregation("items", sPath, oItemTemplate);
this._oPopover.openBy(oControl);
}.bind(this));
} else {
var oList = Fragment.byId("popoverNavCon", "AktionList");
var oItemTemplate = this._BuildItemTemplate();
oList.bindAggregation("items", sPath, oItemTemplate);
this._oPopover.openBy(oControl);
}
_BuildItemTemplate: function(){
var oItemTemplate = new sap.m.ObjectListItem({
title:"{AktionsBez}",
type: "Inactive"
});
oItemTemplate.addAttribute(new sap.m.ObjectAttribute({
text : "{Aktionstext}"
}));
oItemTemplate.addAttribute(new sap.m.ObjectAttribute({
text : "{path: 'ChangedAt', type: 'sap.ui.model.type.DateTime'}"
}));
return oItemTemplate;
}
And this is the idea of the second scenario which is calling the oDataService but not displaying any data:
Instead of having only the List definition in XML also the ObjectListItem is defined in the XML:
<List
id="AktionList"
items="{AktionSet}">
<ObjectListItem
title="{AktionsBez}"
type="Active">
<ObjectAttribute text="{Aktionstext}" />
<ObjectAttribute text="{ChangedAt}" />
</ObjectListItem>
</List>
And in the controller instead of building the template and doing the bindAggretation there is just:
var oList = Fragment.byId("popoverNavCon", "AktionList");
oList.bindElement(sPath);
How do I get the second scenario displaying data in the list?
I would suggest a third solution:
When pressing on your table row, get the binding context of the current row using oContext = oClickedRow.getBindingContext() or something similar. This context should point to /TableEntity('123456').
Apply this context to your Popover using oPopover.setBindingContext(oContext).
Now your Popover has the same context as your table row. The XML should look like in your second scenario.
Using the path is an extra step imo. I think it also makes more sense to bind the complete Popover and not just the List. Has the neat side effect that you can use a property of your TableEntity as the title for the Popover. It also completely avoids working with controls directly (which is one of the seven deadly sins) and you should be able to remove all those IDs.
Here is how the relevant part of the view should look:
<List
id="AktionList"
items="{AktionSet}">
<dependents>
<ObjectListItem
id="oObjectListItem"
title="{AktionsBez}"
type="Active">
<ObjectAttribute text="{Aktionstext}" />
<ObjectAttribute text="{ChangedAt}" />
</ObjectListItem>
</dependents>
</List>
And in the controller:
var oList = sap.ui.core.Fragment.byId(this.getView().createId("popoverNavCon"), "AktionList");
oList.bindItems({
path: sPath,
template: sap.ui.core.Fragment.byId(this.getView().createId("popoverNavCon"), "oObjectListItem")
});
Check dependents in sap.m.List aggregation section for details.

Navigate to same Detail View with Animation in SplitApp

I'm using a Split App which has
Master list
3 Detail Pages
MAster List shows a list of items ( here for example, bikes and cars list)
These are 3 detail pages:
Message page: for not found/welcome page when nothing is clicked on master list.
BikeProperties Page: which shows details if Bike is clicked.
CarProperties Page: which shows if Car is clicked.
Now, the issue is when I click on Car Product, there is an animation shown which navigates from say welcome page to car page.
However, if I click again a car product, binding is updated with no animation.
Similarly, if I select a Bike at this point ( after selecting car), the navigation happens with animation.
So, to summarize,
No animation is shown if same page is shown again in detail page.
Animation is shown when different detail page is loaded.
However, what I want is , irrespective of which detail page is currently shown, the navigation should happen again with animation so consistency with animation is maintained.
Also, please note that I cannot use hash based routing as this split app needs to be displayed in a dialog box.
Below is the dummy code:
App.view.xml
<mvc:View controllerName="com.sap.SplitApp.controller.App" xmlns:mvc="sap.ui.core.mvc" xmlns:core="sap.ui.core" displayBlock="true"
xmlns="sap.m">
<Shell id="shell">
<App>
<SplitApp id="app">
<masterPages>
<Page>
<List items='{/items}' selectionChange="handleNavigate" mode='SingleSelectMaster'>
<items>
<StandardListItem title='{name}' info='{type}'/>
</items>
</List>
</Page>
</masterPages>
<detailPages>
<MessagePage title='Hello!' text='Select a Product'></MessagePage>
<core:Fragment fragmentName="com.sap.SplitApp.fragments.BikeProperties" type="XML"/>
<core:Fragment fragmentName="com.sap.SplitApp.fragments.CarProperties" type="XML"/>
</detailPages>
</SplitApp>
</App>
</Shell>
App.controller.js
onInit: function () {
var items = [
{
name: 'Thunderbird 500cc',
type:'Bike'
},
{
name: 'Swift',
type:'Car'
},
{
name: 'Bullet 350cc',
type:'Bike'
},
{
name: 'Polo',
type:'Car'
}
];
var oModel = new sap.ui.model.json.JSONModel({ items: items});
this.getView().setModel(oModel);
},
handleNavigate: function(oEvent) {
var oBindingContext = oEvent.getParameter("listItem").getBindingContext();
var oSplitApp = this.byId("app");
var oDetailPage = null;
if (oBindingContext.getProperty("type") === "Bike") {
oDetailPage = this.byId('bikePage');
} else {
oDetailPage = this.byId('carPage');
}
oDetailPage.setBindingContext(oBindingContext)
oSplitApp.toDetail(oDetailPage);
}
BikeProperties.fragment.xml
<core:FragmentDefinition
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:l="sap.ui.layout"
xmlns:f="sap.ui.layout.form"
xmlns:core="sap.ui.core"
>
<Page id='bikePage' title='Bike'>
<Title text='{name}' />
</Page>
</core:FragmentDefinition>
CarProperties.fragment.xml
<core:FragmentDefinition xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" xmlns:l="sap.ui.layout" xmlns:f="sap.ui.layout.form"
xmlns:core="sap.ui.core">
<Page id='carPage' title='Car'>
<Title text='{name}'/>
</Page>
So since the API does not offer an obvious way to force an animaton, I looked at the source files.
Your problem is probably from this line from NavContainer.js:
var oFromPage = this.getCurrentPage();
if (oFromPage && (oFromPage.getId() === pageId)) { // cannot navigate to the page that is already current
Log.warning(this.toString() + ": Cannot navigate to page " + pageId + " because this is the current page.");
return this;
}
As you can see from the comment, it is not intended to navigate/animate to the same page that is currently displayed.
One possible solution would be to use a second fragment for both car and bike and to navigate to car2 if you were to be on car1 previously, then to car2 again and so on. This is the best workaround I found.
The following are just some things I found and might be worth to take a further look at, but I couldn't get it to work properly.
I found another line in the source that could be used but there is a catch. The page title bar does not slide as expected. It gets invisible as you can see with this snippet added to your controller in the handleNavigate function:
var oFromPage = oSplitApp.getCurrentDetailPage();
if (oBindingContext.getProperty("type") === "Bike") {
oDetailPage = this.byId("bikePage");
if (oFromPage === oDetailPage) {
sap.m.NavContainer.transitions.slide.to.call(this, this.byId("carPage"), this.byId("bikePage"), function callback() {});
}
} else {
oDetailPage = this.byId("carPage");
if (oFromPage === oDetailPage) {
sap.m.NavContainer.transitions.slide.to.call(this, this.byId("bikePage"), this.byId("carPage"), function callback() {});
}
}
I also noticed that styleClasses are being used for proper transition. The fromPage gets some styles and the toPage too. But since in your case fromPage and toPage are the same, the styleClasses cannot be applied/removed as needed.
oToPage.addStyleClass("sapMNavItemSliding").addStyleClass("sapMNavItemCenter").removeStyleClass("sapMNavItemRight");
oFromPage.addStyleClass("sapMNavItemSliding").removeStyleClass("sapMNavItemCenter").addStyleClass("sapMNavItemLeft");
Using only the "oFromPrage-styleClasses" for your detailsPage results in some sort of "bouncing" from the left side. Using all, one after another, results in wrong navigation.
Maybe you can make some use of these information but as already said, using two car fragments and two bike fragments was the best solution (user-experience-wise) I found.
Have you tried to pass the animation type to the.toDetailPage() method?
https://sapui5.hana.ondemand.com/#/api/sap.m.SplitContainer/methods/toDetail
It accepts a transitionname parameter, right after the page id, and I think this should be working.
Quote from the site:
"The type of the transition/animation to apply. This parameter can be omitted; then the default is "slide" (horizontal movement from the right). Other options are: "fade", "flip", and "show" and the names of any registered custom transitions.
None of the standard transitions is currently making use of any given transition parameters."

How get selected item from StandardListItem

I'm building a master-detail app. I'm using sap.m.StandardListItem for listing master objects. I want that selected master object appear on a detail page.
<List id="lstRequest" headerText="Custom Content" selected="true" items="{
path: '/Requests',
parameters: {
expand: 'RequestTypeDetails'
}
}">
<StandardListItem
title="{RequestTypeDetails/RequestType2} - {RequestCode}"
description="{TotalAdvance}"
icon="sap-icon://request"
iconDensityAware="false"
iconInset="false"
type="Navigation"
press="onSelectApprovation"
/>
</List>
I'm following the guide from here but it doesn't work in my case.
var source = event.getSource();
var bindingobject = event.getBindingContext("Requests");
bindingobject is undefined.
Inside onSelectApprovation do the following:
var oItem = oEvent.getParameter("listItem") || oEvent.getSource());
var oCtx = oItem.getBindingContext();
var requestCode = oCtx.getProperty("RequestCode");
By the way: sap.m.List does not have a property called "selected" of type boolean. However, it has a select event that could also be used instead of using the press event of the StandardListItem...
Give the logical name while setting json model to list like below.
this.getView().byId("lstRequest").setModel(oListJson,"List");
oListJson will be your array of data.
now use your code for accessing list object in your onSelectApprovation fuction as below
onSelectApprovation : function(oEvent){
var bindingobject = oEvent.getSource().getBindingContext("List");
}

Passing Data from Master to Detail Page

I watched some tutorials about navigation + passing data between views, but it doesn't work in my case.
My goal is to achieve the follwing:
On the MainPage the user can see a table with products (JSON file). (Works fine!)
After pressing the "Details" button, the Details Page ("Form") is shown with all information about the selection.
The navigation works perfectly and the Detail page is showing up, however the data binding doesnt seem to work (no data is displayed)
My idea is to pass the JSON String to the Detail Page. How can I achieve that? Or is there a more elegant way?
Here is the code so far:
MainView Controller
sap.ui.controller("my.zodb_demo.MainView", {
onInit: function() {
var oModel = new sap.ui.model.json.JSONModel("zodb_demo/model/products.json");
var mainTable = this.getView().byId("productsTable");
this.getView().setModel(oModel);
mainTable.setModel(oModel);
mainTable.bindItems("/ProductCollection", new sap.m.ColumnListItem({
cells: [new sap.m.Text({
text: "{Name}"
}), new sap.m.Text({
text: "{SupplierName}"
}), new sap.m.Text({
text: "{Price}"
})]
}));
},
onDetailsPressed: function(oEvent) {
var oTable = this.getView().byId("productsTable");
var contexts = oTable.getSelectedContexts();
var items = contexts.map(function(c) {
return c.getObject();
});
var app = sap.ui.getCore().byId("mainApp");
var page = app.getPage("DetailsForm");
//Just to check if the selected JSON String is correct
alert(JSON.stringify(items));
//Navigation to the Detail Form
app.to(page, "flip");
}
});
Detail Form View:
<mvc:View xmlns:core="sap.ui.core" xmlns="sap.m" xmlns:f="sap.ui.layout.form" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mvc="sap.ui.core.mvc" controllerName="my.zodb_demo.DetailsForm">
<Page title="Details" showNavButton="true" navButtonPress="goBack">
<content>
<f:Form id="FormMain" minWidth="1024" maxContainerCols="2" editable="false" class="isReadonly">
<f:title>
<core:Title text="Information" />
</f:title>
<f:layout>
<f:ResponsiveGridLayout labelSpanL="3" labelSpanM="3" emptySpanL="4" emptySpanM="4" columnsL="1" columnsM="1" />
</f:layout>
<f:formContainers>
<f:FormContainer>
<f:formElements>
<f:FormElement label="Supplier Name">
<f:fields>
<Text text="{SupplierName}" id="nameText" />
</f:fields>
</f:FormElement>
<f:FormElement label="Product">
<f:fields>
<Text text="{Name}" />
</f:fields>
</f:FormElement>
</f:formElements>
</f:FormContainer>
</f:formContainers>
</f:Form>
</content>
</Page>
</mvc:View>
Detail Form Controller:
sap.ui.controller("my.zodb_demo.DetailsForm", {
goBack: function() {
var app = sap.ui.getCore().byId("mainApp");
app.back();
}
});
The recommended way to pass data between controllers is to use the EventBus
sap.ui.getCore().getEventBus();
You define a channel and event between the controllers. On your DetailController you subscribe to an event like this:
onInit : function() {
var eventBus = sap.ui.getCore().getEventBus();
// 1. ChannelName, 2. EventName, 3. Function to be executed, 4. Listener
eventBus.subscribe("MainDetailChannel", "onNavigateEvent", this.onDataReceived, this);)
},
onDataReceived : function(channel, event, data) {
// do something with the data (bind to model)
console.log(JSON.stringify(data));
}
And on your MainController you publish the Event:
...
//Navigation to the Detail Form
app.to(page,"flip");
var eventBus = sap.ui.getCore().getEventBus();
// 1. ChannelName, 2. EventName, 3. the data
eventBus.publish("MainDetailChannel", "onNavigateEvent", { foo : "bar" });
...
See the documentation here: https://openui5.hana.ondemand.com/docs/api/symbols/sap.ui.core.EventBus.html#subscribe
And a more detailed example:
http://scn.sap.com/community/developer-center/front-end/blog/2015/10/25/openui5-sapui5-communication-between-controllers--using-publish-and-subscribe-from-eventbus
Even though this question is old, the scenario is still valid today (it's a typical master-detail / n-to-1 scenario). On the other hand, the currently accepted solution is not only outdated but also a result of an xy-problem.
is there a more elegant way?
Absolutely. Take a look at this Flexible Column Layout tutorial: https://sdk.openui5.org/topic/c4de2df385174e58a689d9847c7553bd
No matter what control is used (App, SplitApp, or FlexibleColumnLayout), the concept is the same:
User clicks on an item from the master.
Get the binding context from the selected item by getBindingContext(/*modelName*/).
Pass only key(s) to the navTo parameters (no need to pass the whole item context).
In the "Detail" view:
Attach a handler to the patternMatched event of the navigated route in the Detail controller's onInit.
In the handler, create the corresponding key, by which the target entry can be uniquely identified, by accessing the event parameter arguments in which the passed key(s) are stored. In case of OData, use the API createKey.
With the created key, call bindObject with the path to the unique entry in order to propagate its context to the detail view.
The relative binding paths in the detail view can be then resolved every time when the detail page is viewed. As a bonus, this also enables deep link navigation or bookmark capability.
You can also set local json model to store your data, and use it in the corresponding views. But be sure to initialize or clear it in the right time.

How can I add sort items into a fragment dialog?

I have this fragment retrieve by this page https://openui5.hana.ondemand.com/explored.html#/sample/sap.m.sample.TableViewSettingsDialog/code
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core">
<ViewSettingsDialog
confirm="handleConfirm" id='viewSettingsDialogId'>
<sortItems id="sortItemsId">
<!-- <ViewSettingsItem text="Product" key="Name" selected="true" />
<ViewSettingsItem text="Supplier" key="SupplierName" />
<ViewSettingsItem text="Weight" key="WeightMeasure" />
<ViewSettingsItem text="Price" key="Price" /> -->
</sortItems>
</ViewSettingsDialog>
</core:FragmentDefinition>
I want insert manually the sortItems in the control (or by data-binding in the xml-View).
How can I do it?
I try to do it by code in my controller:
//IF CLICK ON SETTINGS BUTTON
handleViewSettingsDialogButtonPressed: function (oEvent) {
if (!this._oDialog) {
this._oDialog = sap.ui.xmlfragment("apps.appIntra.fragment.settingDialog", this);
}
// toggle compact style
jQuery.sap.syncStyleClass("sapUiSizeCompact", this.getView(), this._oDialog);
this._oDialog.open();
var element=sap.ui.getCore().byId("sortItemsId");
this.byId('sortItemsId').addSortItem(new sap.m.ViewSettingsItem({text:"field1", key:"Price"}));
this.byId('sortItemsId').addSortItem(new sap.m.ViewSettingsItem({text:"field2", key:"PLUTO"}));
},
But it not work...
I see this guide http://scn.sap.com/community/developer-center/front-end/blog/2014/02/20/sapui5-dialogwith-businesscard-as-xml-fragment-along-with-controller
but if I use
var element=sap.ui.getCore().byId("sortItemsId");
element value is undefined
The addSortItem is a method that works on the ViewSettingsDialog and not on sortItems.
Therefore, as you have already provided the id viewSettingsDialogId for the ViewSettingsDialog control, in you controller you can do the following,
var oViewSettingsDialog = sap.ui.getCore().byId("viewSettingsDialogId");
oViewSettingsDialog.addSortItem(new sap.m.ViewSettingsItem({text:"field1",
key:"Price"}));
/* and so on... */
This would add the sort items into the sortItems list.