I have created a flexible column layout and unfortunately it does not work properly.
When I want to expand the left part, I have to click on arrow twice instead once:
I am trying to figure out, but unfortunately could not find the error.
The view of Flexible Column Layout:
<mvc:View xmlns="sap.f" xmlns:mvc="sap.ui.core.mvc" xmlns:m="sap.m" displayBlock="true" controllerName="io.example.fclpoc.controller.App"
height="100%">
<FlexibleColumnLayout id="fcl" stateChange="onStateChanged" layout="{/layout}" backgroundDesign="Solid"></FlexibleColumnLayout>
</mvc:View>
and the controller:
sap.ui.define([
"sap/ui/model/json/JSONModel",
"sap/ui/core/ResizeHandler",
"sap/ui/core/mvc/Controller",
"sap/f/FlexibleColumnLayout"
], function (JSONModel, ResizeHandler, Controller, FlexibleColumnLayout) {
"use strict";
return Controller.extend("io.example.fclpoc.controller.App", {
onInit: function () {
this.oRouter = this.getOwnerComponent().getRouter();
this.oRouter.attachRouteMatched(this.onRouteMatched, this);
this.oRouter.attachBeforeRouteMatched(this.onBeforeRouteMatched, this);
},
onBeforeRouteMatched: function (oEvent) {
var oModel = this.getOwnerComponent().getModel();
var sLayout = oEvent.getParameters().arguments.layout;
// If there is no layout parameter, query for the default level 0 layout (normally OneColumn)
if (!sLayout) {
var oNextUIState = this.getOwnerComponent().getHelper().getNextUIState(0);
sLayout = oNextUIState.layout;
}
// Update the layout of the FlexibleColumnLayout
if (sLayout) {
oModel.setProperty("/layout", sLayout);
}
},
_updateLayout: function (sLayout) {
var oModel = this.getOwnerComponent().getModel();
// If there is no layout parameter, query for the default level 0 layout (normally OneColumn)
if (!sLayout) {
var oNextUIState = this.getOwnerComponent().getHelper().getNextUIState(0);
sLayout = oNextUIState.layout;
}
// Update the layout of the FlexibleColumnLayout
if (sLayout) {
oModel.setProperty("/layout", sLayout);
}
},
onRouteMatched: function (oEvent) {
var sRouteName = oEvent.getParameter("name"),
oArguments = oEvent.getParameter("arguments");
this._updateUIElements();
// Save the current route name
this.currentRouteName = sRouteName;
},
onStateChanged: function (oEvent) {
var bIsNavigationArrow = oEvent.getParameter("isNavigationArrow"),
sLayout = oEvent.getParameter("layout");
this._updateUIElements();
// Replace the URL with the new layout if a navigation arrow was used
if (bIsNavigationArrow) {
this.oRouter.navTo(this.currentRouteName, {
layout: sLayout
}, true);
}
},
// Update the close/fullscreen buttons visibility
_updateUIElements: function () {
var oModel = this.getOwnerComponent().getModel();
var oUIState = this.getOwnerComponent().getHelper().getCurrentUIState();
oModel.setData(oUIState);
},
onExit: function () {
this.oRouter.detachRouteMatched(this.onRouteMatched, this);
this.oRouter.detachBeforeRouteMatched(this.onBeforeRouteMatched, this);
}
});
});
I looked also in the debug console:
However no errors occur. I have also compare my code with https://sapui5.hana.ondemand.com/#/entity/sap.f.FlexibleColumnLayout/sample/sap.f.sample.FlexibleColumnLayoutWithTwoColumnStart/code/webapp/controller/FlexibleColumnLayout.controller.js and could not find differences.
What am I doing wrong?
The app can be found here https://github.com/softshipper/fclpoc
Update
I have run the app in my edge browser and it does not have any extension installed. The behavior is the same.
Here is the console output of edge:
This is less a direct answer to the question "why does my app do that". It's more of a help to self-help.
Basically, if you put a break point in each of the methods in your App controller, you will see that the layout is moving in the correct position first, then it is moving back in the incorrect position (it happens so fast that you dont see without debugger).
The layout is being set several times in the whole process. sometimes changing nothing, sometimes not. In the end, one of your methods sets the wrong layout.
PS: you have a semantic error, not a syntactic one (the app does what you asked it to do), so there are no errors in the console.
Related
I created a popover which is called on mouseover via addEventDelegate.
The popover has a text element with binding to my Model.
The fragment:
<Popover xmlns="sap.m"
id="myCardPopover"
showHeader="false"
contentWidth="260px"
placement="Bottom">
<Text id="myCardPopoverText" text="{propertyModel>Tooltip}" />
</Popover>
Register the browser events:
attachCardPopoverOnMouseover: function () {
var rootHBox = this.byId("myReportCardContent");
rootHBox.addEventDelegate({
onmouseover: function () {
this.timeId = setTimeout(() => that.onOpenCardPopover(this), 600);
},
onmouseout: function () {
clearTimeout(this.timeId) || that.onCloseCardPopover(this);
}
}, rootHBox);
},
The event listener:
onOpenCardPopover: function (oControl) {
this.oCardTooltipPopover.then(function (oPopover) {
var oContext = oControl.getBindingContext("propertyModel");
oPopover.setBindingContext(oContext);
oPopover.openBy(oControl);
});
},
The Popover itself is a dependent of multiple controls created from an aggregation binding and is created in onAfterRendering.
// Fragment required from "sap/ui/core/Fragment"
createCardPopover: function () {
var oView = this.getView();
var oRootHBox = this.byId("myReportCardContent");
if (!this.oCardTooltipPopover) {
this.oCardTooltipPopover = Fragment.load({
id: oView.getId(),
name: "namespace.view.CardPopup"
}).then(function (oPopover) {
oRootHBox.addDependent(oPopover);
return oPopover;
});
}
},
When I hover over one of my controls, I only get an empty popover. The bound text isn't shown.
However, when I lookup the created popover in the UI5 debug tools, the binding seems correct and also the text is shown up there.
The text's <span> element in the DOM is also empty.
[...] multiple controls created from an aggregation binding
With this.byId("myReportCardContent"), you're accessing the template control rather than the actual rendered ones. A template in aggregation binding is usually a static control (i.e. without propagated models and contexts) that the ManagedObject will clone for each data object. After cloning, the parent models and contexts are propagated to those clones, and not to the internal template control.
So if you add the popover to the dependent aggregation of the template (oRootHBox), there is no context to propagate. The binding paths in the fragment definition stay unresolved.
How the issue can be circumvented is up to you. There are many approaches to show something on mouseover. Just avoid manipulating the template after rendering.
I have implemented editable AG-Grid. In the grid, one of the column displays country of the player as shown below:
Now in last column, when user click on the cell, I want to display list of available countries as a dropdown.
Here by default AG-Grid displays normal dropdown. Which I want to replace with Bootstrap-select.
To achieve this, I have implemented custom selector and using Bootstrap-select library.
But when cell is clicked, Dropdown is not being rendered. I am not getting any error though in console.
Here is the code of my custom cell-editor:
var selectCellEdior = function () { };
selectCellEdior.prototype = {
init: function (params) {
selector = document.createElement('select');
for(var i = 0; i < params.values.length; i++) {
var option = params.values[i];
$('<option />', { value: option, text: option }).appendTo(selector);
}
this.cellSelector = selector;
$(this.cellSelector).selectpicker({ size: 'auto' });
},
getValue: function () {
return $(this.cellSelector).find('.btn-text').text();
},
getGui: function () {
return this.cellSelector;
},
destroy: function () {
$(this.cellSelector).remove();
}
};
Here is the Sample Code
I dont understand what is the issue.
The problem is the selectpicker jQuery method seems to add a display: none to the select element. Ag-Grid is adding it to the DOM you just can't see it. Additionally the getValue function is not returning the selected option's text.
The following changes to getValue and getGui will make the grid work:
...
getValue: function () {
return $(this.cellSelector).val();
},
getGui: function () {
this.cellSelector.style.display = 'block';
return this.cellSelector;
},
...
Here is the modified Plunker
For anyone looking at this now.... Ag-grid has a method afterGuiAttached that is called after the GUI has been attached to the Grid's cell. If you're noticing that the bootstrap-select still isn't "quite right" after following the instructions above, put ALL of the calls to selectpicker in afterGuiAttached instead of init or getGui. This will place the bootstrap-select into a div with the right classes it needs (dropdown, bootstrap-select, etc), and create all of the bootstrap-select elements that are properly shown/hidden.
While navigating from one screen to another onExit has to be called to free resources. However it is not triggered. This is my code:
function (BaseController, JSONModel, formatter, Filter, FilterOperator) {
"use strict";
return BaseController.extend("app.controller.Worklist", {
_oCatalog: null,
_oResourceBundle: null,
onInit: function() {
this._oView = this.getView();
},
onThirdScreen : function (oEvent) {
this.getRouter().navTo("thirdscreen", {});
},
onExit: function() {
if (this._Dialog) {
this._Dialog.destroy(true);
}
}
});
});
As #mattbtt has mentioned, views are not destroyed each time you switch to another view so the "exit" event will not be triggered. What you can do, however, is to handle the onBeforeHide/onAfterHide events from the view instead.
onInit: function() {
this._oView = this.getView();
this._oView.addEventDelegate({
onBeforeHide: function(oEvent) {
debugger;
},
onAfterHide: function(oEvent) {
debugger;
}
}, this)
}
This is the expected behavior as navigating via the routing mechanism does not destroy the corresponding view. This makes a lot of sense as views normally do not change during the lifespan of their parent component. Furthermore it would slow down the application if views need to be repeatedly instantiated if the corresponding route matches.
The control sap.m.MessagePopover has an attribute _oPopover (containing sap.m.Popover inside).
Using this attribute, I could set the popover width:
messagePopover._oPopover.setContentWidth("450px");
However, as SAP attributes starting from _ should not be used, does anybody know a cleaner way?
As of UI5 version 1.46, a more flexible control sap.m.MessageView can be used instead of the old sap.m.MessagePopover.
There is no need to access internal properties or apply custom CSS style classes to manipulate the width as you can put MessageView anywhere you want (Still, Fiori Guideline recommends to use it only within a responsive popover or a dialog).
const popover = new ResponsivePopover({
contentWidth: "450px",
contentHeight: "450px",
content: [
/*myMessageView*/
],
});
// ...
popover.openBy(...);
Compared to MessagePopover, MessageView can group items and more.
Internally, MessagePopover uses MessageView too.
Another solution would be to use CSS class. However, there is a catch. As you can see from below generated DOM of the message popover, inline styling has been used :( .
Only way to override inline-style is by using !important in CSS which is again not recommended approach. However, considering inline CSS has been used, I would go with using !important keyword. Below is the working code:
XML Code ( for adding Class):
<MessagePopover id='myMP' class='myPopoverClass'>
<items>
<MessagePopoverItem title='Title' subTitle='SubTitle'></MessagePopoverItem>
</items>
</MessagePopover>
CSS:
.myPopoverClass {
width:100rem;
}
.myPopoverClass .sapMPopoverCont {
width:100% !important;
}
You can play around with how much width you need for message Popover.
EDIT: This is from the source code:
MessagePopover.prototype.init = function () {
var that = this;
var oPopupControl;
this._oResourceBundle = sap.ui.getCore().getLibraryResourceBundle("sap.m");
this._oPopover = new ResponsivePopover(this.getId() + "-messagePopover", {
showHeader: false,
contentWidth: "440px",
placement: this.getPlacement(),
showCloseButton: false,
modal: false,
afterOpen: function (oEvent) {
that.fireAfterOpen({openBy: oEvent.getParameter("openBy")});
},
afterClose: function (oEvent) {
that._navContainer.backToTop();
that.fireAfterClose({openBy: oEvent.getParameter("openBy")});
},
beforeOpen: function (oEvent) {
that.fireBeforeOpen({openBy: oEvent.getParameter("openBy")});
},
beforeClose: function (oEvent) {
that.fireBeforeClose({openBy: oEvent.getParameter("openBy")});
}
}).addStyleClass(CSS_CLASS);
I have a main screen with a tile the user can press to go to another page. The onInit for this second page works fine in getting/setting the model and the data shows correctly.
If I 'go back' to the first page (after I have made changes on the second screen), and then click the tile to go to the second page, it doesn't call the onInit this second time and so the data reflects the changes that were made and not what I want (the true initialized data). I tried changing the onInit to onBeforeRedendering hoping that it would re-initialize the model/data but it doesn't seem to reset everything correctly.
Is there a way on going back to do something to force the onInit to be called the next time the page is called? I think, if I can make it so the onInit is called each time the page is called, that it would fix my problem.
Here is the portion of my controller for the onInit and 'go back'....
sap.ui.define([
'sap/ui/core/mvc/Controller',
'sap/ui/model/json/JSONModel',
'sap/viz/ui5/controls/common/feeds/FeedItem',
'sap/m/MessageBox',
'sap/viz/ui5/data/FlattenedDataset'
], function(Controller, JSONModel, FeedItem, MessageBox, FlattenedDataset) {
"use strict";
var ColumnController = Controller.extend("controllers.Quarter", {
onInit: function(oEvent) {
var oRouter = sap.ui.core.routing.Router.getRouter("router");
var myView = this.getView();
var today = new Date();
var year = today.getFullYear();
var yr = year.toString();
var mnth = today.getMonth();
var qtr = Math.floor((mnth / 3));
this.makeYearList(yr);
var mthis = this;
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData({
yr: yr
});
sap.ui.getCore().setModel(oModel);
myView.byId("mySelectMenu").setSelectedKey(yr);
myView.byId("mySelectMenu").attachChange(function() {
yr = this.getSelectedKey();
mthis.checkYr(yr, qtr);
mthis.recList(myView, yr, qtr);
});
myView.byId("selQtr").attachChange(function() {
qtr = this.getSelectedKey();
mthis.checkYr(yr, qtr);
mthis.recList(myView, yr, qtr);
});
oRouter.attachRouteMatched(function(oEvent) {
mthis.checkYr(yr, qtr);
mthis.recList(myView, yr, qtr);
});
},
goBack: function() {
var oHistory = sap.ui.core.routing.History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();
var oView = this.getView();
if (sPreviousHash) {
window.location.replace("#/" + sPreviousHash);
} else {
window.location.replace("#");
}
},
});
return ColumnController;
});
I'd appreciate any advice.
Put the logic to reset the model data into the route matched handler.
The better way to overcome this problem is, use shell for your page one and two. Shell will automatically destroy your content(if you are in page two then page one content would be destroyed and vice versa). else, you need to destroy the content manually to overcome duplicate id issue, u need to destroy by your own and call the controller wherever you want.