Adding content to XMLView via "addContent" doesn't work - sapui5

I have the following XMLView:
<mvc:View
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:data="sap.chart.data"
xmlns:viz="sap.viz.ui5.controls"
xmlns:con="sap.suite.ui.commons"
controllerName="MY_NAMESPACE.controller.ChartView"
xmlns:html="http://www.w3.org/1999/xhtml"
>
<!-- Panel here -->
</mvc:View>
Now, in my controller, I want to dynamically add a sap.m.Panel to the view.
In my onInit function, I pass the object of the current view to the method that creates the Panel and adds it to the view.
onInit: function() {
var sUrl = "/sap/opu/odata/sap/MY_ODATA_SERVICE/",
oModel = new ODataModel(sUrl), // v2
oCurrentView = this.getView();
this.getView().setModel(oModel);
this._createPanel(oCurrentView);
this._createChartContainer();
this._initializeCharts();
this._showCharts();
},
_createPanel: function(currentView) {
var sId = this._globals.panelId;
var oViewPanel = new Panel(sId, {
width: "auto"
}).addStyleClass("sapUiSmallMarginBeginEnd");
this._globals.panelState = oViewPanel;
currentView.addContent(oViewPanel);
return currentView;
},
However, the Panel is never rendered:
But when I call the getContent function of the view, the panel is listed as an entry.
Clarification:
Creating a sap.m.Panel in the XMLView isn't a problem. Placing this bit of XML into the XMLView works.
<Panel id="chartPanel"
class="sapUiSmallMarginBeginEnd"
width="auto"
></Panel>
But, I need to create and append the sap.m.Panel object to the XMLView at runtime (in the controller), not in the XMLView.
Now, the problem:
With above posted controller code, the panel objects gets created. In fact, it even gets registered as a content aggregation of the XMLView, but it simply doesn't get rendered (see picture above).
Any suggestion on why and how this behaviour occurs are greatly appreciated.

Issue
this.getView().addContent(/*...*/) doesn't work.
Why
Currently, XMLView won't allow manipulating its content via APIs as the documentation warns:
Be aware that modifications of the content aggregation of this control are not supported due to technical reasons. This includes calls to all content modifying methods like addContent etc., but also the implicit removal of controls contained by the content aggregation. For example the destruction of a Control via the destroy method. All functions can be called but may not work properly or lead to unexpected side effects.
This is, at the time of writing (v1.64), still the case.
PS: The above limit applies only to XMLView. Other view types, such as JSView*, are not affected.
* sap.ui.core.mvc.JSView and sap.ui.jsview are deprecated. Use Typed Views instead (Applicable since v1.90).

try to put the Panel inside the XML view and give it a property visible="false".
<Panel id="panelId" visible="false">
</Panel>
In your function you could do something like this:
_createPanel: function(){
var oPanel = this.getView().byId("panelId");
oPanel.setVisible(true);
// Other Methods for Panel
}
With the oPanel instance you can execute all methods listed in the API:
https://sapui5.hana.ondemand.com/#/api/sap.m.Panel
Hope this helps :-)
Best regards

Related

How to proper bind async. loaded data from a news API to a view and display the data as content?

I want to fetch data from News API (https://newsapi.org/) in my SAPUI5 application like done here (https://www.nathanhand.co.uk/blog/post/creating-a-news-app-using-ui5), but without express and Node.js. The fetching process itself works and I got the data from the API in JSON. The Problem seems to be the lifecycle of UI5 especially the asynchronous loading of the API data. I cannot display the data at the moment in my view, since it arrives to late it seems to be initialized with the view.
I have tried to work with the "attachRequestCompleted" event handler, to make sure the data is there and further actions are only taken when the data has arrived. But that did not solve the problem, the data gets properly bound to the view, but too late it seems.
return Controller.extend("newsapitest.newsapitest.controller.View1", {
onInit: function () {
var thisContext = this;
var articleModel = new JSONModel("https://newsapi.org/v2/top-headlines?country=DE&category=business&apiKey=*********");
articleModel.attachRequestCompleted(function(oEvt) {
var model = oEvt.getSource();
thisContext.getView().setModel(model, "articles");
});
}
});
<content>
<GenericTile backgroundImage="{articles>/1/urlToImage}"
frameType="TwoByOne" press="onArticlePress">
<TileContent footer="{articles>/1/publishedAt}">
<NewsContent contentText="{articles>/1/title}"
subheader="{articles>/1/description}" />
</TileContent>
</GenericTile>
</content>
So I was expecting that the tiles in my view will display the information for each article that is stored in the model. But at the moment there is just an empty tile and no data is shown there.
Solution
I did a mistake with the binding of the model to my control. That was one mistake. The other thing I changed is how the data gets loaded into my model.
return Controller.extend("newsapitest.newsapitest.controller.View1", {
onInit: function () {
var articleModel = new JSONModel();
articleModel.loadData("https://newsapi.org/v2/top-headlines?country=DE&category=business&apiKey=37a02aae93684d58810e0b996954f534");
this.getView().setModel(articleModel);
},
});
<content>
<GenericTile
backgroundImage="{/articles/0/urlToImage}"
frameType="TwoByOne" press="onArticlePress">
<TileContent footer="{/articles/0/publishedAt}">
<NewsContent
contentText="{/articles/0/title}"
subheader="{/articles/0/description}" />
</TileContent>
</GenericTile>
</content>
Did you check that your binding paths are correct? Anyway, the way you did the bindings will only create one tile with the information stored on the second position (position 1) of your array of articles.
If you want to create create a number of tiles dynamically depending on the number of positions of an array, I think you can't use the "Generic Tile" component, instead you could use the "Tile Container" as follows (It's a deprecated component but I think there's no other way to do so, at least on the view):
<TileContainer
tiles="{articles>/}">
<StandardTile
title="{articles>title}"
info="{articles>publishedAt}"
infoState="{articles>description}" />
</TileContainer>
It would be nice if someone else knows a way to do that without using a deprecated component :).

Cannot pass model data from aggregation binding to block for lazy loading in xml view

I'm building an SAPUI5 Fiori application from the project template "SAP Fiori Master-Detail Application" in SAP Web IDE. I connect to an OData Service that gives me this nested structure (bold text represents a navigation property):
File
Properties...
ToRegister (returns collection of Registers)
Properties...
ToDocumentType (returns collection of DocumentTypes)
Properties...
ToDocument (returns collection of Documents)
Properties...
Displaying data in the Detail View is very slow, so I'm trying to use sap.uxap.ObjectPageLayout's lazy loading feature. For that, I have to extract parts of my view and put them into a custom block. This is accomplished by deriving from sap.uxap.BlockBase, according to this example (code here). Unfortunately, I couldn't find any examples that use aggregations/aggregation bindings.
I use XML Views. My Detail View looks like this:
<mvc:View
xmlns="sap.uxap"
xmlns:mvc="sap.ui.core.mvc"
xmlns:m="sap.m">
<ObjectPageLayout sections="{ToRegister}">
<sections>
<ObjectPageSection title="{RegisterName}" subSections="{ToDocumentType}">
<subSections>
<ObjectPageSubSection title="{Description}" >
<blocks>
<m:List items="{ToDocument}">
<m:CustomListItem>
...code for displaying properties...
</m:CustomListItem>
</m:List>
</blocks>
</ObjectPageSubSection>
</subSections>
</ObjectPageSection>
</sections>
</ObjectPageLayout>
</mvc:View>
This code does not use custom blocks. This is the slow version, but it works and displays the data correctly. Notice that the bold navigation properties are placed in curly braces (e.g. <m:List items="{ToDocument}">).
For my custom block, I extracted the <m:List> part into a seperate view:
<mvc:View
xmlns="sap.uxap"
xmlns:mvc="sap.ui.core.mvc"
xmlns:m="sap.m">
<m:List items="{Documents}">
<m:CustomListItem>
...code for displaying properties...
</m:CustomListItem>
</m:List>
</mvc:View>
Notice here that the property in the curly braces is not {ToDocument} anymore but {Documents}. That's because of the model mapping that has to be introduced in the original Detail View for this to work (see section Model Mapping in this article). I modified the Detail View like this:
<mvc:View
xmlns="sap.uxap"
xmlns:mvc="sap.ui.core.mvc"
xmlns:m="sap.m"
xmlns:attachmentblock="pft7.blocks.FileAttachmentList">
<ObjectPageLayout sections="{ToRegister}">
...
<blocks>
<attachmentblock:Block mode="Expanded">
<ModelMapping
externalModelName="ToDocumentType"
internalModelName="Documents"
externalPath="/ToDocument" />
</attachmentblock:Block>
</blocks>
...
</ObjectPageLayout>
</mvc:View>
It's pretty much the same. I added the xml namespace attachmentblock (which points to my custom block) and used it to replace the <m:List> child of <blocks>. Notice the attribute internalModelName of the <ModelMapping> node. It can be freely chosen and just has to be the same as the model name used in the block's view.
Finally, the problem: With this modification, my Detail View does not display the document properties anymore. Instead, it just displays the text "No data". I added some dummy text and it was properly displayed in my Detail View, so the actual inclusion of the custom block itself seems to work.
I suspect that I got the <ModelMapping> part wrong, but I don't know how to set the attributes correctly. I couldn't find any examples that use aggregations and navigation properties for this, so I'm pretty clueless. The console does not log any errors.
Can you check whether it works if you use an “Object Page with LazyLoading without blocks” as seen in this sample here?
In addition to this, you might also want to check on the reasons for the slow “Display of Detail View” via OData service.
Very often this is caused by long database access times.
If your backend is an R/3-ABAP system, you might want to check your OData service with an SQL-Trace (transaction ST05)
for long database response times caused by unnecessary requests or missing indexes.

mergeDuplicates="true" did not work well in MultiSelect mode

I want to achieve mergeDuplicates in Multi Select table, just like guideline said:
But in my DEMO, there is still border-top in my checkbox, what should I do? I don't want to overwrite UI5 CSS.
<Table
id="table"
mode="MultiSelect"
growingScrollToLoad="true">
<columns>
<Column mergeDuplicates="true"><Text text="column1"/></Column>
<Column><Text text="column2"/></Column>
<Column><Text text="column3"/></Column>
</columns>
</Table>
IMHO, the concept of mergeDuplicates is bound to the cell content and therefore doesn't extend to the selector cell. But obviously, the guideline and the control concept then don't fully match.
I would suggest to slightly modify Ash Kander's proposal. As the table might render individual ColumnListItems individually and at different points in time, attaching to the onAfterRendering of the table won't help.
Instead, attach to the onAfterRendering of the items by using a delegate. To make this fully work, you have to do this early enough on the template for the items, before data binding starts cloning that template.
In your DEMO, this is easily possible in onInit before you create and attach the model (I gave the template the id "cli"):
this.byId("cli").addEventDelegate({
onAfterRendering: function(e) {
var $dom = e.srcControl.$();
if ( $dom.has(".sapMListTblCellDup") ) {
$dom.find("td.sapMListTblSelCol").css("border-topcolor",
"transparent");
}
}
});
See http://plnkr.co/edit/eNb83KvF1BpAp5eGSpOS?p=preview .
Seems like a bug in the table renderer. No way you can address it NOW without touching the CSS.
This will work, but this overrides the css (in your controller):
onAfterRendering: function() {
$('.sapMListTblSelCol').each(function(index, col) {
if ($(col).next().hasClass('sapMListTblCellDup')) {
$(col).css('border-top-color', 'transparent')
}
});
},

Kendo UI Hierarchical datagrid - How to access root viewModel from detail grid editor template MVVM

I have a grid within a grid, where parent grid is constructed in MVVM, child grid initialized on its data-detail-init http://jsbin.com/kuvejuw
<div data-role="grid"
data-columns="[
{ 'field': 'FirstName'},
{ 'field': 'LastName'}
]"
data-bind="source: dataSource"
data-detail-init="viewModel.detailInit"
>
</div>
If have a custom property (e.g. Text here) on the viewModel, and in the popup editor of the child grid, I would like to bind to this property. So e.g. in more complex scenarios I can populate a dropdownlist with a range of values by having an array (or observable array) on the viewModel.
var viewModel = kendo.observable({
dataSource: new kendo.data.DataSource ... // everything works here,
detailInit: detailInit,
Text: "This text should be displayed in editor in detail's grid",
});
kendo.bind(document.body, viewModel);
The problem is that this property (or overall viewModel) is not detectable in the template of the detail grid's editor:
function detailInit(e){
...
editable: {
mode: "popup",
template: kendo.template($("#child-editor-template").html())
}
...
}
Template is built like this:
<script type="text/x-kendo-template" id="child-editor-template">
<span data-bind="text: Text"></span>
</script>
but I also tried data-bind="text:viewModel.Text". I tried various solutions, setting the Text property on viewModel in detailGrid's edit event, or setting it on viewModel bind, but it does not work with this jsBin (3.2016 version).
Now funny thing is that I actually able able to access this property with a 2015v3 Kendo UI in my local project, but I cannot replicate it in this jsBin.
In my local project though I still cannot access the events in ViewModel e.g. I could do text: Text, but could not do events: {select: onSelect}.
Accessing the events would be ultimately the reason for asking this question once this thing is sorted, I'm looking for some hints to understand what's going on, if I'm expecting too much from mvvm.
EDIT:
I'm looking forward to this type of functionality that would be enabled in the popup editor of the child grid http://jsbin.com/canomux
Try like this,
I just make changes in your template,
<script type="text/x-kendo-template" id="child-editor-template">
<input name="ShipCountry"/>
</script>
http://jsbin.com/levenacari/edit?html,js,output
It seems the way of retrieving the data from API was somewhat unexpected, so with change of:
options.success(e.data.Orders.results.toJSON());
to
options.success(e.data.Orders.results);
the binding of text works.
With the events binding it is not working - it seems it's not something to do with detailGrid but in general with grid, which is described
here

Data attribute in XML View elements

I'm trying to add data attributes to elements in a XML View as below:
<core:FragmentDefinition
xmlns="sap.m"
<VBox data-help-id="Some.String.Here">
...
</VBox>
</core:FragmentDefinition>
but couldn't find how to do it, unless I assign them via Controller.
Tried using CustomData namespace, but it only adds data, without adding the HTML attribute to the DOM element.
Any idea?
Thanks!
actually you can do something very close and associate data to your xmlView. This is available for xml views and more. Check this url for more details: Custom Data - Attaching Data Objects to Controls
What you would need to do is add a custom namespace to your xmlView:
xmlns:dataHelp="http://schemas.sap.com/sapui5/extension/sap.ui.core.CustomData/1"
...
<core:FragmentDefinition
xmlns="sap.m"
<VBox dataHelp:id="Some.String.Here" id="myBox"
...
</VBox>
</core:FragmentDefinition>
you are then able to set and consume this attribute in your binding and javascript/controller/event handler:
sap.ui.getCore().byId("myBox").data("id") // = Some.String.Here
You can only influence the attributes written to the DOM using the standard control properties. If the standard properties don't provide you with a way to set the right HTML attibutes, and you still want to get your own HTML attributes in the DOM, you'll need to subclass the control and write your own renderer. When you write your own renderer, you have full control over what's written to the DOM.
You can find more information on writing custom controls in Step 34 of the SAPUI5 Walkthrough.