SAP UI5 issue, render Calling two time in Custom Control - sapui5

In view
var oData = {
"SavedSearch" : [
{ name : "Save1" },
{ name : "Save2" },
{ name : "Save3" },
{ name : "Save4" },
{ name : "Save5" }
]
}
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData(oData);
sap.ui.getCore().setModel(oModel,"ModelData");
var oTest = new test({
manageSavedSearch :{
path : "ModelData>/SavedSearch",
template: new sap.ui.commons.Button({
text : "{ModelData>name}"
})
},
});
oTest.placeAt(this);
In Custom Control
sap.ui.core.Control.extend("test",{
metadata:{
properties : {},
aggregations:{
manageSavedSearch : {type : "sap.ui.commons.Button", multiple :true },
layout : {type : "sap.ui.layout.VerticalLayout", multiple :false }
}
},
init : function(){
},
renderer : {
render : function(oRm, oControl) {
// Come two Times Here
var manageSavedSearch = oControl.getManageSavedSearch();
var oSavedButtonHLyt = new sap.ui.layout.VerticalLayout();
for(var index = 0 ; index < manageSavedSearch.length ; index++){
oSavedButtonHLyt.addContent(manageSavedSearch[index]);
}
oControl.setAggregation("layout",oSavedButtonHLyt);
oRm.renderControl(oControl.getAggregation("layout"));
}
}
},
onAfterRendering: function(){}
});
If I don't use any layout then It doesn't come two time in render otherwise it comes two time in render. This issues is happening from version 1.24.4. please Can I have any guidance here.

Changing aggregation(e.g. add/remove/insert/set/removeAll) invalidates the control. You should never invalidate a control during the rendering. In your case it can be an infinite loop.
To debug rendering invalidation there is a url parameter test.html?sap-ui-xx-debugRendering=true then you can see the rendering stack trace and who is responsible for the rendering.
In your code sample, there are two aggregations update. setAggregation and addContent.
Aggregation mutatators uses 3rd parameter to suppress invalidation. So following will insert the aggregation but suppress the invalidation since whole control will be rendered at the end it will not be a problem.
oControl.setAggregation("layout",oSavedButtonHLyt, true); // suppress invalidate
I guess you assume same should work for addContent
oSavedButtonHLyt.addAggregation("content", manageSavedSearch[index], true);
But it does not because here manageSavedSearch[index], so your template clone's parent is oTest initially, but you are changing the parent with addAggregation because layout will be the parent. But UI5 cannot determine automatically for the previous parent's suppress since its aggregation will be moved to somewhere else. So we go with
oControl.removeAggregation("manageSavedSearch", manageSavedSearch[index], true);
oSavedButtonHLyt.addAggregation("content", manageSavedSearch[index], true);
Here is the jsbin http://jsbin.com/rezayebada/1/edit?js,output
BTW, as you mentioned this is just an example do not take this as a reference for building composite controls.
Basically, property/aggregation/association changes invalidates the control if the control does not overwrite its mutator methods. I see it is not easy to understand what invalidates when but sap-ui-xx-debugRendering=true helps you to understand with stack trace.

This problem I was also facing, you can do one thing try using local resources
rather than online resources.

Related

SAPUI5 using Popover as a tooltip

I'm trying to use the sap.m.Popover as a "rich tooltip" for some controls. This is as per recommendation from SAP because the sap.ui.commons library is now deprecated. We have too much text we want to add to the standard string tooltip. I haven't figured out a way to use the popover directly as a tooltip, which is why I've extended the TooltipBase control to handle the popover.
I've got the popover working fine, However when I interact with my control, I get the following error:
Uncaught Error: failed to load 'myNewToolTip/controls/TooltipBaseRenderer.js' from ../controls/TooltipBaseRenderer.js: 404 - Not Found
I see from these threads that it is because the TooltipBase class is an abstract class and therefore doesn't have a renderer. However, because I'm already using the popover, I don't need to render anything. I've tried to add the TooltipBaseRenderer.js and just have an empty render class. But UI5 really doesn't like that either.
My question is what should I do, I see two options:
There is probably a simple way to use the popover as a tooltip, which I'm just too stupid to figure out (Bear in mind, I'd prefer to bind it directly in the XML view).
Figure out a way to suppress the renderer call as I don't need it.
This is my current source code for the custom control:
sap.ui.define([
"sap/m/Popover"
], function (Popover) {
"use strict";
return sap.ui.core.TooltipBase.extend("myNewToolTip.TooltipBase", {
metadata: {
properties: {
title : {}
},
events: {
"onmouseover" : {},
"onmouseout" : {}
}
},
oView: null,
setView: function(view) {
this.oView = view;
},
onmouseover : function(oEvent) {
var that = this;
if (!this.delayedCall){
this.delayedCall = setTimeout(function() {
if (!that.oPopover){
that._createQuickView();
}
}, 500);
}
},
onmouseout: function(oEvent) {
if (this.oPopover){
this.closePopover();
this.delayedCall = undefined;
}
else{
clearTimeout(this.delayedCall);
this.delayedCall = undefined;
}
},
_createQuickView: function() {
var sTitle = this.getTitle();
this.oPopover = new Popover({
title: sTitle
});
this.oPopover.openBy(this.getParent());
},
closePopover: function(){
this.oPopover.close();
this.oPopover = undefined;
}
});
});
There is no need to create a custom control just to display a popover on mouseover. As you said, there is a simpler way: Adding event delegates.
One of the events that delegates can listen to is onmouseover which can be achieved like this:
this.byId("myTargetControl").addEventDelegate({
onmouseover: function () {
// Open popover here
}
});
Demo: https://embed.plnkr.co/jAFIHK
Extending sap.ui.core.TooltipBase
If you still consider extending TooltipBase (without Popover), take a look at this example: https://embed.plnkr.co/33zFqa?show=control/MyCustomTooltip.js,preview
Keep in mind, though, that tooltips in general shouldn't contain critical information due to its lack of discoverability as Fiori Design Guideline mentions
Tooltips (...) should never contain critical information. They should also not contain redundant information.
Just as a friendly reminder :) Don't make people hover to find things.

Fragment is destroyed when nav back from Fiori Launchpad?

I use 2 XML fragment, one for display data, the other for edit.
I switch the fragment using this method:
onAfterRendering : function () {
this._toggleForm("Display");
},
_toggleForm : function(sFragmentName) {
var oPage = this._detailPage;
//my detail page has an object header, a fragment form and a form in detail view.
if(oPage.getContent().length > 2) {
oPage.removeContent(1);
}
oPage.insertContent(this._getFormFragment(sFragmentName), 1);
},
_formFragments: {},
_getFormFragment: function (sFragmentName) {
var oFormFragment = this._formFragments[sFragmentName],
oView = this.getView();
if (oFormFragment) {
return oFormFragment;
}
oFormFragment = sap.ui.xmlfragment(oView.getId(), "namespace.fragment." + sFragmentName, this);
oView.addDependent(oFormFragment);
return this._formFragments[sFragmentName] = oFormFragment;
}
Everything works fine... BUT, if I call the app from the Fiori launchpad, the first call is ok, but the second time give me this error in insertContent :
The object with ID XXX-detail--general was destroyed and cannot be used anymore.
Display/Change fragment is destroyed after exit, but this._fromFragment still stored a reference, and returned this reference oFormFragment when _getFormFragment is called when I entered the second time, which caused this error.
Fixed by add:
onExit : function () {
for(var sPropertyName in this._formFragments) {
if(!this._formFragments.hasOwnProperty(sPropertyName)) {
return;
}
this._formFragments[sPropertyName].destroy();
this._formFragments[sPropertyName] = null;
}
}
Answer #AndriiNaumovych 's question:
It seems that only sap.ui.comp.smartform.SmartForm has a EditTogglable property, and it need a sap:updatable="true" in metadata.xml (I saw that in Explore, not specified in doc.)
I use sap.ui.layout.form.SimpleForm, editable seems not working in JSON model without metadata. So I use this example with fragment.

UI5 navContiner bindAggregation not loading the pages

i need to build the page by binding aggregation as below
view
<NavContainer
id="navCon"
class="navContainerControl sapUiSmallMarginBottom" height="50%">
</NavContainer>
controller
onInit : function()
{
var navCon = this.getView().byId("navCon");
navCon.bindAggregation('pages',{
path:'/pages',
factory : jQuery.proxy(this.createPages,this)
});
}
createPages : function(sid,context)
{
var eachpageData = context.getObject();
var grid = new sap.ui.layout.Grid({
defaultSpan:"L4 M6 S6"
});
var page = new sap.m.Page({
id : eachpageData.name,
title : eachpageData.name,
content : grid
});
grid.bindAggregation('content',{path:'data',factory :this.createPageContent});
return page;
},
But when i see from the debugger it has only one page
But when i call navto
handleNav : function(evt)
{
var navCon = this.getView().byId("navCon");
var target = evt.getSource().getText();
if (target) {
//var animation = this.getView().byId("animationSelect").getSelectedKey();
navCon.to(this.getView().byId(target));
} else {
navCon.back();
}
}
and if i see the navCon.getPages() will give 2 pages.
What mistake i have done here?
You are trying to pass the DOM element to the NavContainers.to(DOM) method. This is where its gone wrong.
But NavContainers.to() method can accept id(String) as parameter.
Change your handleNav method as follows it will work.
handleNav : function(evt)
{
var navCon = this.getView().byId("navCon");
var target = evt.getSource().getText();
if (target) {
navCon.to(target);
} else {
navCon.back();
}
}
NavContainers can display only one page. You can add more pages to the pages aggregation, but they will be visible only if a navigation event is fired with the proper parameters. After that, the layout of the new page is loaded and added to the DOM.
In case of SplitApp, application can display two pages (master and detail) if you see it on tablet or desktop; however it's implemented by the use of two NavContainers.
That's why the control inspector returns with one page before the navigation, second page is not part of the DOM until you navigates to it.
If you place a breakpoint into your code instead of using the control inspector, you can call the navCon.getPages() which should return with the number of pages in the aggregation.

Get passed data on next page after calling "to" from NavContainer

I am on my way building a Fiori like app using SAPUI5. I have successfully built the Master page, and on item click, I pass the context and navigate to Detail page.
The context path from Master page is something like /SUPPLIER("NAME"). The function in App.controoler.js is as follows:
handleListItemPress: function(evt) {
var context = evt.getSource().getBindingContext();
this.myNavContainer.to("Detail", context);
// ...
},
But I would like to know how I can access this context in the Detail page. I need this because I need to use $expand to build the URL and bind the items to a table.
There is an example in the UI5 Documentation on how to deal with this problem using an EventDelegate for the onBeforeShow function which is called by the framework automatically. I adapted it to your use case:
this.myNavContainer.to("Detail", context); // trigger navigation and hand over a data object
// and where the detail page is implemented:
myDetailPage.addEventDelegate({
onBeforeShow: function(evt) {
var context = evt.data.context;
}
});
The evt.data object contains all data you put in to(<pageId>, <data>). You could log it to the console to see the structure of the evt object.
Please refer the "shopping cart" example in SAP UI5 Demo Kit.
https://sapui5.hana.ondemand.com/sdk/test-resources/sap/m/demokit/cart/index.html?responderOn=true
Generally, in 'Component.js', the routes shall be configured for the different views.
And in the views, the route has to be listened to. Please see below.
In Component.js:
routes: [
{ pattern: "cart",
name: "cart",
view: "Cart",
targetAggregation: "masterPages"
}
]
And in Cart.controller.js, the route has to be listened. In this example, cart is a detail
onInit : function () {
this._router = sap.ui.core.UIComponent.getRouterFor(this);
this._router.attachRoutePatternMatched(this._routePatternMatched, this);
},
_routePatternMatched : function(oEvent) {
if (oEvent.getParameter("name") === "cart") {
//set selection of list back
var oEntryList = this.getView().byId("entryList");
oEntryList.removeSelections();
}
}
Hope this helps.

how to make qooxdoo effects work?

I'm trying figure out how to make something like this work:
qx.Class.define("effects.Application",
{
extend : qx.application.Standalone,
members :
{
main : function()
{
// Call super class
this.base(arguments);
// Enable logging in debug variant
if (qx.core.Environment.get("qx.debug"))
{
// support native logging capabilities, e.g. Firebug for Firefox
qx.log.appender.Native;
// support additional cross-browser console. Press F7 to toggle visibility
qx.log.appender.Console;
}
var button = new qx.ui.form.Button("First Button");
var fadeToggle = new qx.fx.effect.core.Fade(button.getContainerElement().getDomElement());
fadeToggle.set({
from : 1.0,
to : 0.0
});
var doc = this.getRoot();
doc.add(button);
button.addListener("execute", function() {
fadeToggle.start();
},this);
}
}
});
This is the entire Application.js
Just trying to do an effect on something without luck.. It's like qooxdoo is ignoring the effects
The problem is the DOM element. As you execute
button.getContainerElement().getDomElement()
it has not yet appeared in the DOM tree. So the return value of the function is null. Qooxdoo has a rendering queue, so the manifestation of what you do in the program is mostly delayed a bit. Use the 'appear' event to work around this:
var button = new qx.ui.form.Button("First Button").set({
enabled: false
});
var doc = this.getRoot();
button.addListener('appear',function(){
var fadeToggle = new qx.fx.effect.core.Fade(
button.getContainerElement().getDomElement()
).set({
from : 1.0,
to : 0.0
});
button.addListener('execute',function(){
fadeToggle.start();
});
button.setEnabled(true);
});
The bit with disabling and enabling the button is just to show off ... it will be so fast that no one will notice.
There are also several *.flush() methods in the framework where you can force the rendering to happen immediately, so calling them (the right ones :-)) might also be an option ... but since JS should be written asynchronously whenever possible, the above is probably the right thing todo.
You also might want to look at
the corresponding manual page
the code of the animation demos, e.g. this (although I concede they mostly hoook the animation to user actions)