ZK how to add grid based on user input - zk

I'm working in a web application using ZK. I'm struggling with a window where I need to render a grid with as much rows as the user specifies. It should be something like:
<grid>
<columns>
<column width="150px"/>
<column width="350px"/>
</columns>
<rows>
<row>
<cell>
<label value="Rows to add"/>
</cell>
<cell>
<textbox id="txt_rowamount"/>
</cell>
</row>
</rows>
</grid>
<grid id="grid">
<columns>
<column width="100px" label="Field 1" align="center"/>
<column width="100px" label="Field 2" align="center"/>
<column width="100px" label="Field 3" align="center"/>
</columns>
<rows id="rows" forEach="${rowModelList}">
<row>
<cell>
<textbox value="${each.field1}" />
</cell>
<cell>
<textbox value="${each.field2}" />
</cell>
<cell>
<textbox value="${each.field3}" />
</cell>
</row>
</rows>
</grid>
<button id="btn_generate" label="Generate Rows"/>
So, if you type 4 in txt_rowamount a grid with 4 row components and 3 textbox components in each row. The textboxes should be empty and when I complete every textbox the user input should be binded to fieldN of every rowModel item, do I make myself clear?
I'm trying with something like this in the composer:
private ListModel<RowModel> rowModelList;
#Autowired
private Grid grid;
#Override
public void doAfterCompose(final Window component) throws Exception {
super.doAfterCompose(component);
rowModelList = new ListModelList<>();
grid.setVisible(false);
}
public void onClick$btn_generate() {
// TODO Add four empty elements to rowModelList
grid.setVisible(true);
}
Being RowModel like
public class RowModel {
private String field1;
private String field2;
private String field3;
// ommited constructors, getters and setters
}
I believe that the approach should be MVVM instead of the current MVC but I don't know how to do this. Besides, I don't know if you can mix the approachs in the same application and what are the advantages and disadvantages of doing that.
I've seeing the ZK grid demos but all of them are using already populated objects to render the tables, so it's not useful to me.
If someone can give me a hand in this issue, it would be greatly appreciated. If you need more information on the code or the requirements for this problem, please comment it out.
Thanks in advance for your answers.

It seems there's a lot missing in your current attempt, so I'll try to fill the gaps.
If impatient here a runnable example on zkfiddle.
Since you were talking about binding component attributes to model values I'd suggest using ZK's DataBinding which integrates nicely into the MVVM development pattern (sorry for the link, but that's not a one-liner).
In any case the example illustrates how to combine ZK features such as (ListModelList with templates, Property-/Command-Binding) into a simple dynamic UI.
As you can see in code, adding, (re)moving a row inside the model requires rudimentary operations (the grid will update its child-rows automatically):
rows.add(new RowModel(...));
rows.remove(row);
Since the ListModelList is assigned to the <grid> component via #init(vm.rows), the grid will listen to changes to the ListModel and add remove grid-rows accordingly. It uses the <template name="model" var="rowModel"> to render new rows when added to the model. The template variable rowModel variable represents the rowModel object. (more on that in our documentation)
I hope this information is not overwhelming ... if so please let me know.
BTW: the forEach-attribute you were trying to use previously is used for static ui composition (at zul parse time) where no dynamic control is required. For dynamic cases use ListModel(List) in combination with templates.
Robert

Related

MultiComboxBox within a table closes after selection

I have tried to resolve the problem for quite sometime but have not been successful.
Inside a table, I have a sap.m.MultiComBox. The drop down in the multicombobox closes after selecting the first value. If not inside the table, the multicombobox works fine as expected (popover does not close). One additional behavior I observed is that if I don't have the selectedKeys bound, then it works fine.
Any reasons or suggestions?
<Table
growing="true"
items="{employee>/EmployeeCollection}">
<columns>
<Column width="10%" />
<Column width="55%" />
<Column width="35%"/>
</columns>
<ColumnListItem>
<Image id="image" src="{employee>dataURI}" class="sapOB_Assign_Usercircle" />
<Text text="{employee>Name}" class="tableText" />
<VBox>
<MultiComboBox id="mcb1"
selectedKeys="{employee>roles}"
items="{
path: 'roles>/BusinessRoles',
templateShareable: false
}">
<core:Item key="{roles>id}" text="{roles>name}" />
</MultiComboBox>
<HBox />
</VBox>
</ColumnListItem>
</Table>
You must be using UI5 version 1.66 or below with growing="true" on the Table. In that case, the dropdown closes immediately after the selection due to a focus loss caused by rerendering of the list item (Its DOM element being rewritten completely). The rerendering itself is caused by two-way bound selectedKeys which explains why it "works" if the property is not bound.
Normally, two-way binding should not be used together with growing. According to the API reference:
Note: Growing must not be used together with two-way binding.
But it's still unclear whether the above constraint is still valid today (See issue #3013).
In order to keep two-way binding with growing="true", add the attribute key* to the list binding info:
<Table
growing="true"
items="{
path: 'employee>/EmployeeCollection',
key: 'employeeId'
}"
>
Here is a working example: https://jsbin.com/ciyosir/edit?js,output. As you can see, the dropdown is kept open even after selection because the list item is not rerendered thanks to the extended change detection.
Additionally, I'd suggest to upgrade to the latest stable UI5 version in order to benefit from many controls having migrated to the new semantic rendering.
* The key awaits a property name from the model of which the value is unique. For more information, see topic Extended Change Detection.

sap.m.ObjectListItem: Need to keep whitespace (tabs) in title

<ObjectListItem
title="{i18n>uBOMItem}: {Item} Component: {ComponentDesc}"
number="{ComponentNo}"
>
I am using sap.m.ObjectListItem. While binding, I need tab space between {Item} & {ComponentDesc}.
E.g. like \t
Currently, sap.m.ObjectListItem does not support rendering whitespace for title.
And I agree with alexP's answer that it's not clean to combine multiple labels ("{i18n>uBOMItem}:" and "Component:") into one.
That being said; if it's really necessary to do so, however, you'll need to extend ObjectListItem.
Example: https://embed.plnkr.co/WaMaP4wqMevsjMxX
Internally, ObjectListItem renders its title from sap.m.Text. The Text control has a public property called renderWhiteSpaceapi which we can use to allow tabs to be rendered.
ObjectListItem is only for one value. In your case it's better to use CustomListItem.
<CustomListItem>
<HBox width="100%">
<VBox>
<Label emphasized="true" text="{i18n>uBOMItem}: {Item}" />
</VBox>
<VBox class="sapUiSmallMarginBegin">
<Label emphasized="true" text="Component: {ComponentDesc}" />
</VBox>
<VBox width="50%" justifyContent="End">
<ObjectNumber number="{ComponentNo}" />
</VBox>
</HBox>
</CustomListItem>
It's not clean to output two data bindings from your model in one label, text or similar. You should split the output. In case of an i18n label and a data binding in one control i would make a exception.

How to make only one cell editable in smart table sapui5

I am using a sapui5 smart table to list down my products. It includes product code, product description and order quantity.
Among these three fields i want to update only the order quantity. It should be an inline editing in the table.
In my smart table i have enabled the property "editable" as "true". It makes the entire row is editable. Instead of making entire row editable, i want make only one cell to be editable.
Example
<smartFilterBar:SmartFilterBar id="smartFilterBar" entityType="ZDEMO_C_MyEntityType" persistencyKey="SmartFilter_Explored">
</smartFilterBar:SmartFilterBar>
<smartTable:SmartTable id="mySmartTable"
smartFilterId="smartFilterBar"
tableType="GridTable"
editable="true"
entitySet="ZDEMO_C_MyEntity"
useVariantManagement="false"
useTablePersonalisation="true"
header="My Products"
showRowCount="true"
useExportToExcel="true"
enableAutoBinding="true">
</smartTable:SmartTable>
I can see 2 ways:
Make use of "field controls" concept. It requires adding a special properties within your entity type, which define the state of the fields (cells). Also some annotations have to be introduced (in the metadata.xml by backend) to initiate the handling.
Here is a link where concept described using Form control as an example, but the same rules are applicable for Table as well:
https://blogs.sap.com/2017/06/06/dynamic-field-control-using-annotations-in-sapui5/
Redefine the table rows manually in your XML and bind the needed cell(s) against the property of the local JSON model, which could be changed depending on some conditions (e.g. Edit button press).
The 1st approach is better from the architectural perspective but requires some data model modifications (from the backend side).
The 2nd approach allows to do everything on UI and program some complex UI logic, which defines the cells state.
You choose.
You can add a sap ui table inside the smart table and add columns with customdata property. Follow these steps.
Make editable="true" as editable="false"
In your xml, make sure to add this namespace xmlns:core="sap.ui.core"
Within the smarttable tag add below.
<smartTable:SmartTable .................................
<Table>
<columns>
<Column>
<customData>
<core:CustomData key="p13nData" value='\{"columnKey": "OrderQty", "leadingProperty": "OrderQty", "columnIndex":"2"}'/>
</customData>
<Text text="Order Qty"/>
</Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Input value="{OrderQty}" type="Number" editable="true"/>
</cells>
</ColumnListItem>
</items>
</Table>
</smartTable:SmartTable>
Add below tag inside table to make your column editable
<table:Column sortProperty="Max_Capacity" filterProperty="Max_Capacity" id="maxCapCol">
<Label text="Max Capacity"/>
<table:template>
<Input text="{Max_Capacity}" />
</table:template>
</table:Column>

How to add many <row> components in ZK in specific position

I'm working in a ZK application and I need to add <row>'s components based on user input. The zul is like this:
<grid id="mygrid">
<rows id="rows">
<row>
<cell>
<label value="Rows to add 1:"/>
</cell>
<cell>
<textbox id="txt_addRows1"/>
</cell>
</row>
<!-- Here I need to add rows specified in txt_addRows1 -->
<row>
<cell>
<label value="Rows to add 2:"/>
</cell>
<cell>
<textbox id="txt_addRows2"/>
</cell>
</row>
<!-- Here I need to add rows specified in txt_addRows2 -->
<row align="right">
<cell colspan="2">
<button id="btn_generate" label="Generate"/>
</cell>
</row>
</rows>
</grid>
In the composer I can do something like:
Row someRow = new Row();
row.setParent(rows);
My question is: how do I specify that the new row (generated programmatically in the composer) to be rendered in the specified places and not others?
Any suggestion/guide is also welcomed. Thanks in advance for your answers.
There are 2 ways to insert something in the DOM.
First way is to set the parent, the second one is adding a child.
If you check the AbstractComponent api. you see they also speak of appendChild and insertBefore(Component newChild, Component refChild)
So the code would look like :
rows.insertBefore(newRow,rowWhatComesAfterYourRow);

SAPUI5 - How do I aggregate with formElement?

I previously implemented aggregation with VBox. This get all the 'questions' and creates a Text box for each....
<VBox items="{path: 'view>questions', templateShareable: true}">
<items>
<VBox class="sapUiTinyMargin" templateShareable="true">
<Text text="Question {view>OrderSequence}"/>
</VBox>
</items>
</VBox>
I need to do the same, but for formElements. Can this be done?
<f:formElements>
<f:FormElement label="{i18n>radioLabel}">
<f:fields>
<Input value="{viewmodel>radioLabel}" id="__redioLabel"/>
</f:fields>
</f:FormElement>
</f:formElements>
It doesn't seem to work with 'items'
In UI5 elements have several characteristics:
Properties: generally scalar attributes, like "title" or "width".
Events: which are fired when something happens, like "press" or "close".
Aggregations: collections of child entities, like the "items" of a list.
Associations: related controls, like the "label" of a input field.
You can find how these relate to the concept of data binding in the official documentation here.
In your case, the "formElements" is an aggregation of the FormContainer element. Based on the documentation:
Aggregation binding can be used to automatically create child controls according to model data. This can be done either by cloning a template control, or by using a factory function. Aggregations can only be bound to lists defined in the model, that is, to arrays in a JSON model or a collection in the OData model.
This implies that ANY aggregation can be used, no matter how it is named. Now, to go back to you example, the reason why "items" does not work, is because the FormContainer parent element has no aggregation with that name. Instead, you must use the "formElements" aggregation.
<f:FormContainer formElements="{viewmodel>/my/path/to/list}">
<f:formElements>
<f:FormElement label="{i18n>radioLabel}">
<f:fields>
<Input value="{viewmodel>radioLabel}"/>
</f:fields>
</f:FormElement>
</f:formElements>
</f:FormContainer>
Also, note that usually, you do not need to give an ID to the template or any of its children (the Input in your example has an ID), that is because that element specifically will not be part of the resulting control tree. It is just used to be cloned to create the "real" elements based on the model list.
Lastly, you have a "templateShareable" property on the VBox in your first example. VBox has no such property, so it does nothing (you actually use it correctly inside the binding specification for the "items" of the parent VBox).
Solution was...
An aggregation on the form...
<f:Form id="formCustomRadio" editable="true" visible="true" formContainers="{viewmodel>answers}">
Thanks a lot
You can use the VBox and then put the f:formElements inside the items.
Or else use Standard List.
FormElements or containers don't have aggregation items.