`sap.m.HeaderContainer` triggers rendering twice - sapui5

Why are the rendering events of the view (such as onBeforeRendering and onAfterRendering) in the scenario 1 triggered twice?
Scenario 1
Sample.view.xml
<mvc:View xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
controllerName="my.controller.Sample">
<Page id="EmpStat" showHeader="false">
<HeaderContainer>
<!-- ... -->
</HeaderContainer>
</Page>
</mvc:View>
Sample.controller.js
onAfterRendering: function() {
alert("Test"); // called twice
},
Scenario 2
Sample.view.xml
<mvc:View xmlns:mvc="sap.ui.core.mvc"
xmlns:layout="sap.ui.layout"
xmlns="sap.m"
controllerName="my.controller.Sample">
<Page>
<!-- No <HeaderContainer> -->
<layout:Grid>
<!-- ... -->
</layout:Grid>
</Page>
</mvc:View>
Sample.controller.js
onAfterRendering: function() {
alert("Test"); // called once.
},

HeaderContainer has a borrowed class onAfterRendering from sap.ui.core.control, hence in scenario one headerContainer calls this method once and sap.m.Page calls the method another time.
The only way is it to handle the scenario is bypassing the execution of your code a second time either by using event parent source check or by using the logic I have given below as in most of the cases the event gets called only twice.
You can create a counter in onInit() and then update the value in onAfterRendering() and execute the corresponding code only when the condition is matched with the counter value.
onInit:function(){
this.afterRenderingCount = 0;
},
onAfterRendering: function (oEvent) {
if(this.afterRenderingCount === 1){
this.afterRenderingCount =0;
//method call
this.exampleMethod();
}
this.afterRenderingCount++;
}

Related

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

ColumnListItem - Event Handler "press" NOT Triggered

I am using ColumnListItem to display a list of Sales Orders in Overview.view.xml. When the user clicks on an item of the list (of Sales Orders), the App should navigate to the Detail.view.xml.
I have defined the onPress event handler in Overview.Controller.js. But the App did not execute the function (I put an alert() there and it was not triggered). Why the onPress() is not triggered? How do I debug?
<Table items="{myOdata>/SalesOrderSet}">
<ColumnListItem type="Navigation" detailPress=".onPress">
<!-- ... -->
</ColumnListItem>
<columns>
<!-- ... -->
</columns>
</Table>
onPress: function (oEvent) {
//This code was generated by the layout editor.
alert("In");
var loOverview = "Data from Overview";
var oItem = oEvent.getSource();
var loRouter = sap.ui.core.UIComponent.getRouterFor(this);
loRouter.navTo("Detail", {
value: oItem.getBindingContext("oModel").getPath().substr(1)
});
},
The press function is not working as you have not written the correct handler for it. As per your code, the handler is written for detailPress. Just a typo, change the handler to press and it should just work.
Current:
<ColumnListItem type="Navigation" detailPress=".onPress">
Change required:
<ColumnListItem type="Navigation" press=".onPress">
the property that you should bind on the Table control is itemPress and your ColumnListItem need to have the type equals to Navigation
Can you check those?

Method getPath called in onBeforeRendering returns undefined

I have a List Report which, once selected an item, prompts to an Object Page. I am using no FIORI Elements, everything was created from scratch.
The object page has a static header, but its body changes from item to item. In essence, the body uses different fragments that depends on a field (Position Type) of the selected item. In other words:
Pos Type 1 ---> fragment A
Pos Type 2 ---> fragment B
To do all this, on the controller of the object page, I have implemented the following withing the onBeforeRendering lifecycle method:
onBeforeRendering: function() {
// // Set Fragment to be used
var oLayout = this.getView().byId("ObjectPageLayout"),
oFragment = sap.ui.xmlfragment(this._fragmentName());
oLayout.addSection(oFragment);
},
_fragmentName: function() {
var oModel = this.getView().getModel();
var sPosType = oModel.getProperty(this.getView().getObjectBinding().getPath() + "/PositionType");
var sFragment;
if (sPosType === "1") {
sFragment = "A";
} else if (sPosType === "2") {
sFragment = "B";
}
return sFragment;
},
The problem I am facing is that this code is throwing the following error message: "Uncaught (in promise) TypeError: Cannot read property 'getPath' of undefined"
The only way I found to make this to work is by, instead of using method onBeforeRendering, I used onInit. This way, getPath() works fine. But if the user goes back to the List Report, and then selects an item of a different Position Type, then the Object Page displays the same fragment used in the previous item selected.
In case you wonder, bellow you will find the object view:
<mvc:View height="100%" xmlns="sap.uxap" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns:m="sap.m" xmlns:semantic="sap.m.semantic"
xmlns:forms="sap.ui.layout.form" xmlns:layout="sap.ui.layout" controllerName="objectview.controller"
xmlns:aud="sap.uxap.sample.SharedBlocks.fragmentblocks">
<semantic:FullscreenPage id="page" navButtonPress="onNavBack" showNavButton="true" title="{i18n>ObjectPageTitle}">
<m:Page title="Object Page Title">
<m:content>
<ObjectPageLayout id="ObjectPageLayout">
<headerTitle>
<ObjectPageHeader id="ItemTitle" objectTitle="Item Title">
<actions>
Some actions defined
</actions>
</ObjectPageHeader>
</headerTitle>
<headerContent>
Some Header Content
</headerContent>
<sections>
</sections>
</ObjectPageLayout>
</m:content>
<m:footer>
<m:Bar>
<m:contentRight>
Buttons added to the Footer
</m:contentRight>
</m:Bar>
</m:footer>
</m:Page>
</semantic:FullscreenPage>
What happens is that each time you go to the page you add a new section on the container of sections.
You can remove all the existent sections before adding the new one.
oLayout.removeAllSections();
oLayout.addSection(oFragment);

How to reload Json service every 10 seconds SAPUI5

I am new in SAPUI5. I want to reload json service every 10 seconds.My controller code is.
modelServices :function()
{
var oModeldata = new sap.ui.model.json.JSONModel("https://my-example.com/status/");
this.getView().setModel(oModeldata, "datalake");
},`
i call this method from init function of controller and my view is
<TileContainer id="lstDataLakeView" tiles="{datalake>/collectionStatus}">
<CustomTile class="sapMTile" borderVisible="true">
<l:VerticalLayout class="sapUiContentPadding" width="100%">
<l:content>
<core:Icon src="sap-icon://database" class="size2" color="#55acee">
<core:layoutData>
<FlexItemData growFactor="1"/>
</core:layoutData>
</core:Icon>
<FlexBox alignItems="End" justifyContent="End">
<items>
<Text text="Size {datalake>size}" tooltip="Size"/>
</items>
</FlexBox>
</l:content>
</l:VerticalLayout>
</CustomTile>
</TileContainer>
I want to refresh this tile after every 10 seconds. I know there is a method in javascript setTimeInterval(function,time); or setTimeOut() but I'm now sure here how can i use it.
Please be careful not to call setInterval() in it's callback. Otherwise you will have 2 timers running after the first callback and double that every 10 seconds.
Also you should ensure that you stop the timer if your view is no longer displayed.
modelServices :function()
{
var oModeldata = new sap.ui.model.json.JSONModel();
this.getView().setModel(oModeldata, "datalake");
this.intervalHandle = setInterval(function() {
//No need to create and assign a new model each time. Just load the data.
oModeldata. loadData("https://my-example.com/status/");
}, 10000); //Call setInterval only once
},
onExit:function() {
// You should stop the interval on exit.
// You should also stop the interval if you navigate out of your view and start it again when you navigate back.
if (this.intervalHandle)
clearInterval(this.intervalHandle) ;
}
modelServices: function(){
var oModeldata = new sap.ui.model.json.JSONModel("https://my-example.com/status/");
this.getView().setModel(oModeldata, "datalake");
setInterval( function() {
this.modelServices();
}.bind(this), 10000 );
},

onAfterRendering hook for smartform in UI5

In my app i have an XML view that consists of a smartform. I have a need to access an input element(via sap.ui.getCore().byId()) that becomes available after the smartform is parsed and rendered.
The onAfterRendering in the controller for my view triggers as soon as the view is rendered(i get all my non-smartform elements like title etc.), but before the smartform is parsed and rendered. A rudimentary test via an alert also proved this visually.
Is there any event that is triggered after the smartform has rendered which i can hook into to access my input element?
The developer guide walkthrough is extending the smartform and thus has its init method, but in my case i am extending the basecontroller and my init is for the page view.
Thanks for any pointers.
My View:
<mvc:View
controllerName="myns.controller.Add"
xmlns:mvc="sap.ui.core.mvc"
xmlns:semantic="sap.m.semantic"
xmlns:smartfield="sap.ui.comp.smartfield"
xmlns:smartform="sap.ui.comp.smartform"
xmlns="sap.m">
<semantic:FullscreenPage
id="page"
title="{i18n>addPageTitle}"
showNavButton="true"
navButtonPress="onNavBack">
<semantic:content>
<smartform:SmartForm
id="form"
editable="true"
title="{i18n>formTitle}"
class="sapUiResponsiveMargin" >
<smartform:Group
id="formGroup"
label="{i18n>formGroupLabel}">
<smartform:GroupElement>
<smartfield:SmartField
id="nameField"
value="{Name}" />
</smartform:GroupElement>
</smartform:Group>
</smartform:SmartForm>
</semantic:content>
<semantic:saveAction>
<semantic:SaveAction id="save" press="onSave"/>
</semantic:saveAction>
<semantic:cancelAction>
<semantic:CancelAction id="cancel" press="onCancel"/>
</semantic:cancelAction>
</semantic:FullscreenPage>
My Controller:
sap.ui.define([
"myns/controller/BaseController",
"sap/ui/core/routing/History",
"sap/m/MessageToast"
],function(BaseController, History, MessageToast){
"use strict";
return BaseController.extend("myns.controller.Add",{
onInit: function(){
this.getRouter().getRoute("add").attachPatternMatched(this._onRouteMatched, this);
},
onAfterRendering: function(){
//I tried my sap.ui.getCore().byId() here but does not work
//An alert shows me this triggers before the smartform is rendered but
//after all the other non-smartform elements have rendered
},
_onRouteMatched: function(){
// register for metadata loaded events
var oModel = this.getModel();
oModel.metadataLoaded().then(this._onMetadataLoaded.bind(this));
},
_onMetadataLoaded:function(){
//code here....
},
onNavBack: function(){
//code here....
}
});
});
You can look for when SmartForm is added to the DOM with DOMNodeInserted event of jQuery.
For this you can use it's id to identify the SmartForm has been added to the DOM.
Every UI5 element gets some prefix after it has been added to the DOM.
for e.g. __xmlview0--form.
So to make sure required form is added you can split the id of added element, then compare it with id which you have given.
Although it's not optimal solution, but you can try.
onAfterRendering: function() {
$(document).bind('DOMNodeInserted', function(event) {
var aId = $(event.target).attr("id").split("--");
var id = aId[aId.length - 1];
if (id) {
if (id == "form") {
// smart form fields are accessible here
$(document).unbind("DOMNodeInserted");
}
}
})
}
My final solution (for now and uses the accepted answer provided by #Dopedev):
(in controller for the nested view containing the smartform)
onAfterRendering: function() {
$(document).bind('DOMNodeInserted', function(event) {
var elem = $(event.target);
var aId = elem.attr("id").split("--");
var id = aId[aId.length - 1];
if (id) {
if (id == "nameField") {
elem.find("input").on({
focus: function(oEvent) {
//code here;
},
blur: function(oEvent) {
//code here;
}
});
/*
elem.find("input").get(0).attachBrowserEvent("focus", function(evt) {
//code here
}).attachBrowserEvent("blur", function(ev) {
//code here
});
*/
$(document).unbind("DOMNodeInserted");
}
}
});
}