-
FEATURED COMPONENTS
First time here? Check out the FAQ!
Hello everybody. I'm trying to extend a Grid to implements some new features I need but, unfortunately, I'm having some problems. Due to the complexity of the project I'm going to expose you the problem step by step.
A feature I'm trying to implement is to be able to change the columns position programmatically, at run time, regardless of their position in the zul.
Here is the grid:
<grid use="test.misc.GridExt" model="@bind(vm.persons) @template('all')" visibleColumns="@load(vm.visibleColumns)"> <columns> <column id="firstName" label="Name" /> <column id="lastName" label="Surname" /> <column id="birthDate" label="Birth Date" /> <column id="homeCity" label="City" /> </columns> <rows> <template name="all"> <row> <label value="${each.firstName}" /> <label value="${each.lastName}" /> <label value="${each.birthDate}" /> <label value="${each.home.city}" /> </row> </template> </rows> </grid>
"visibleColumns" is the list of columns id, in the order I want to see them. So, referring to the example, if visibleColumns = [lastName, firstName, homeCity, birthDate], its exactly the position of the columns I want.
In GridExt, I've thought to override the "redraw" method. Keeping in mind the Drag&Drop implementation, I have to move both the Column and the Components of each Row. Here is my first implementation:
@Override public void redraw(Writer out) throws IOException { Columns columns = this.getColumns(); for (String columnId : visibleColumns) { for (Component comp : this.getRows().getChildren()) { // search in Columns.getChildren() int sourceIndex = getColumnIndex(columnId); Row row = (Row) comp; Component c = row.getChildren().get(sourceIndex); row.removeChild(c); row.appendChild(c); } // search in Columns.getChildren() Column c = getColumn(columnId); columns.removeChild(c); columns.appendChild(c); } super.redraw(out); }
It works. So we can make next step.
I need a paging Grid, so I change the zul above:
<grid use="test.misc.GridExt" model="@bind(vm.persons) @template('all')" visibleColumns="@load(vm.visibleColumns)" mold="paging" pageSize="3"> . . . </grid>
Obviously my redraw method can't work now. It visits all rows (they are 7) but only first 3 are complete. To solve quickly this problem, I catch the wrong index error to let the cycle going to the end. The modified code:
@Override public void redraw(Writer out) throws IOException { Columns columns = this.getColumns(); for (String columnId : visibleColumns) { for (Component comp : this.getRows().getChildren()) { // search in Columns.getChildren() int sourceIndex = getColumnIndex(columnId); Row row = (Row) comp; try { Component c = row.getChildren().get(sourceIndex); row.removeChild(c); row.appendChild(c); } catch (Exception ex) { } } // search in Columns.getChildren() Column c = getColumn(columnId); columns.removeChild(c); columns.appendChild(c); } super.redraw(out); }
In this way, when I reload the zul page I see the grid with the first 3 row in the correct order but when I click the next page button, on the row.removeChild(c) instruction I get this exception: UI can't be modified in the rendering phase.
I've tried everything to understand the reason of this error; why my code works for the first 3 rows but not on the next call to the same redraw method. Nor I understand the real meaning of the error message. Empty handed.
Thanx for any idea or suggestion to solve the problem or for a new way to realize what I need.
I think you shouldn't manipulate the component in render phase (redraw).
you should do this in the event phase as last as possible (after any your program logic)
in method of visibleColumns() post a "onOrderColumn" event to comp self with lower priority then default (0)
oh, you should also add a listener in component constructor to listen to "onOrderColumn" also and do the re-order processing in the listener.
But, how do you process the order in row?
Hi dennis,
I'm going to investigate the event phase, as you propose.
About your secondo question, the way is this one. For each column id I find in visibleColumns, I look for the index of the column that has that id (examining Columns children). With the index, I can get the corresponding cell in the row. Then I remove the cell from the row and I append it again to the row. In this way, I place the visibleColumn disposition in the grid. It's based on the Drag & Drop implementation.
After this, I swap the columns (in the Columns object) position.
Obviously, with paging it works only for the first page, because when I show the second page, I can't use the disposition of the columns. To solve, probably i'm going to keep the placement of the columns of each page and the original placement, to keep track of changes.
But, before this, I have to solve the first problem. I keep you up to date.
Only as a hint. We do such a thing with a listBox.
- we get the properties of a bean (included related beans) with the java reflection api and store the needed fields and their order in a db table.
- we get these stored field definition data in the beginning of the creation of the listheaders.
- for rendering we have written a rendererItem for each java type (Integer, String...)
sorry, no opensource codes at time only three pics.
best
Stephan
1. Listbox in background and the column selector in front
2. Select a property from the related bean ('user.country.code2')
3. After selection a refresh with the new column happens.
Hi ,Terrytornado,
i am also looking same thing can u please post online example or code for that.
Thanks
Hi coleriv,
I suggest update grid by rowRenderer, please refer to the sample below:
test.zul
<zk> <div apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('test.TestVM')"> <!-- use chosenbox to control the show/hide and order of rows update grid with onSelect event --> <chosenbox model="@load(vm.chosenModel)" selectedObjects="@bind(vm.selected)" onSelect="@command('updateGrid')" width="300px" /> <grid model="@load(vm.model)" rowRenderer="@load(vm.rowRenderer)" width="500px"> <columns> <column id="columnOne" label="@load(vm.headerOne)" visible="@load(vm.headerOneVisible)" /> <column id="columnTwo" label="@load(vm.headerTwo)" visible="@load(vm.headerTwoVisible)" /> <column id="columnThree" label="@load(vm.headerThree)" visible="@load(vm.headerThreeVisible)" /> <column id="columnFour" label="@load(vm.headerFour)" visible="@load(vm.headerFourVisible)" /> </columns> </grid> </div> </zk>
TestVM.java
package test; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.zkoss.bind.annotation.Command; import org.zkoss.bind.annotation.NotifyChange; import org.zkoss.zul.Label; import org.zkoss.zul.ListModel; import org.zkoss.zul.ListModelList; import org.zkoss.zul.Row; import org.zkoss.zul.RowRenderer; public class TestVM { private List _data; private ListModelList _model; private RowRenderer _rowRenderer; private Set _selected; private ListModelList _chosenModel; public ListModel getModel () { if (_model == null) { _model = new ListModelList(getData()); } return _model; } public ListModel getChosenModel () { if (_chosenModel == null) { List l = new ArrayList(); l.add("firstName"); l.add("lastName"); l.add("birthDate"); l.add("homeCity"); _chosenModel = new ListModelList(l); } return _chosenModel; } // bind to chosenbox#selectedObjects public Set getSelected () { if (_selected == null) { _selected = new LinkedHashSet(); // default selections _selected.add("firstName"); _selected.add("lastName"); } return _selected; } // bind to chosenbox#selectedObjects public void setSelected (Set selected) { if (_selected == null) _selected = new LinkedHashSet(); _selected.clear(); if (selected != null) _selected.addAll(selected); } // render labels (cells) based on _selected public RowRenderer getRowRenderer () { if (_rowRenderer == null) { _rowRenderer = new RowRenderer() { public void render(Row row, Object data, int index) throws Exception { final DataBean dataBean = (DataBean)data; if (_selected != null) { for (Object o : _selected) { if ("firstName".equals(o)) new Label(dataBean.getFirstName()).setParent(row); else if ("lastName".equals(o)) new Label(dataBean.getLastName()).setParent(row); else if ("birthDate".equals(o)) new Label(dataBean.getBirthDate()).setParent(row); else if ("homeCity".equals(o)) new Label(dataBean.getHomeCity()).setParent(row); } } } }; } return _rowRenderer; } // assign headers based on _selected public String getHeaderOne () { return getHeader(1); } public String getHeaderTwo () { return getHeader(2); } public String getHeaderThree () { return getHeader(3); } public String getHeaderFour () { return getHeader(4); } // show/hide headers based on _selected public boolean getHeaderOneVisible () { return getHeaderVisible(1); } public boolean getHeaderTwoVisible () { return getHeaderVisible(2); } public boolean getHeaderThreeVisible () { return getHeaderVisible(3); } public boolean getHeaderFourVisible () { return getHeaderVisible(4); } private String getHeader (int i) { if (_selected != null) { Object[] arr = _selected.toArray(); if (arr.length >= i) { return (String)arr[i - 1]; } } return null; } private boolean getHeaderVisible (int i) { if (_selected != null) { Object[] arr = _selected.toArray(); if (arr.length >= i) { return true; } } return false; } public List getData () { if (_data == null) { _data = new ArrayList(); for (int i = 0; i < 10; i++) { _data.add(new DataBean("firstName_" + i, "lastName_" + i, "birthDate_" + i, "homeCity_" + i)); } } return _data; } // nothing to do, simply trigger the update @Command @NotifyChange({"headerOne", "headerTwo", "headerThree", "headerFour", "headerOneVisible", "headerTwoVisible", "headerThreeVisible", "headerFourVisible" ,"model"}) public void updateGrid () { } class DataBean { private String _firstName; private String _lastName; private String _birthDate; private String _homeCity; public DataBean (String firstName, String lastName, String birthDate, String homeCity) { _firstName = firstName; _lastName = lastName; _birthDate = birthDate; _homeCity = homeCity; } public void setFirstName (String firstName) { _firstName = firstName; } public void setLastName (String lastName) { _lastName = lastName; } public void setBirthDate (String birthDate) { _birthDate = birthDate; } public void setHomeCity (String homeCity) { _homeCity = homeCity; } public String getFirstName () { return _firstName; } public String getLastName () { return _lastName; } public String getBirthDate () { return _birthDate; } public String getHomeCity () { return _homeCity; } } }
Regards,
Ben
Hi Benbai,
I have something same question can i use your example with DataList
thanks
Hi Benbai,
Thanks for your example i am able to work with ListBox with the help of your code.
Sample is Below
test.zul
<zk> <div apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('org.test.TestVM')"> <!-- use chosenbox to control the show/hide and order of rows update grid with onSelect event --> <chosenbox model="@load(vm.chosenModel)" selectedObjects="@bind(vm.selected)" onSelect="@command('updateGrid')" width="300px" ></chosenbox> <reorderListbox model="@load(vm.model)" emptyMessage=" Emply List" itemRenderer="@load(vm.itemRenderer)"> <listhead> <listheader label="@load(vm.headerOne)" visible="@load(vm.headerOneVisible)" ></listheader> <listheader id="columnTwo" label="@load(vm.headerTwo)" visible="@load(vm.headerTwoVisible)"> </listheader> <listheader id="columnThree" label="@load(vm.headerThree)" visible="@load(vm.headerThreeVisible)"> </listheader> <listheader id="columnFour" label="@load(vm.headerFour)" visible="@load(vm.headerFourVisible)"> </listheader> </listhead> <template name="model" var="item"> <listitem> <listcell id="columnOne" value="@load(vm.headerOne)" visible="@load(vm.headerOneVisible)" ></listcell> <listcell value="@load(vm.headerTwo)" visible="@load(vm.headerTwoVisible)" ></listcell> <listcell value="@load(vm.headerThree)" visible="@load(vm.headerThreeVisible)" ></listcell> <listcell value="@load(vm.headerFour)" visible="@load(vm.headerFourVisible)" ></listcell> </listitem> </template> </reorderListbox> </div> </zk>
TestVM.java
package org.test; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import org.zkoss.bind.annotation.Command; import org.zkoss.bind.annotation.NotifyChange; import org.zkoss.zul.ItemRenderer; import org.zkoss.zul.Label; import org.zkoss.zul.ListModel; import org.zkoss.zul.ListModelList; import org.zkoss.zul.Listcell; import org.zkoss.zul.Listitem; import org.zkoss.zul.ListitemRenderer; import org.zkoss.zul.Row; import org.zkoss.zul.RowRenderer; public class TestVM { private List _data; private ListModelList _model; private ListitemRenderer _rowRenderer; private Set _selected; private ListModelList _chosenModel; public ListModel getModel () { if (_model == null) { _model = new ListModelList(getData()); } return _model; } public ListModel getChosenModel () { if (_chosenModel == null) { List l = new ArrayList(); l.add("firstName"); l.add("lastName"); l.add("birthDate"); l.add("homeCity"); _chosenModel = new ListModelList(l); } return _chosenModel; } // bind to chosenbox#selectedObjects public Set getSelected () { if (_selected == null) { _selected = new LinkedHashSet(); // default selections _selected.add("firstName"); _selected.add("lastName"); } return _selected; } // bind to chosenbox#selectedObjects public void setSelected (Set selected) { if (_selected == null) _selected = new LinkedHashSet(); _selected.clear(); if (selected != null) _selected.addAll(selected); } // render labels (cells) based on _selected public ListitemRenderer getItemRenderer() { if (_rowRenderer == null) { _rowRenderer = new ListitemRenderer() { public void render(Listitem row, Object data, int index) throws Exception { final DataBean dataBean = (DataBean)data; if (_selected != null) { for (Object o : _selected) { if ("firstName".equals(o)) new Listcell(dataBean.getFirstName()).setParent(row); //new Label(dataBean.getFirstName()).setParent(row); else if ("lastName".equals(o)) new Listcell(dataBean.getLastName()).setParent(row); else if ("birthDate".equals(o)) new Listcell(dataBean.getBirthDate()).setParent(row); else if ("homeCity".equals(o)) new Listcell(dataBean.getHomeCity()).setParent(row); } } } }; } return _rowRenderer; } // assign headers based on _selected public String getHeaderOne () { return getHeader(1); } public String getHeaderTwo () { return getHeader(2); } public String getHeaderThree () { return getHeader(3); } public String getHeaderFour () { return getHeader(4); } // show/hide headers based on _selected public boolean getHeaderOneVisible () { return getHeaderVisible(1); } public boolean getHeaderTwoVisible () { return getHeaderVisible(2); } public boolean getHeaderThreeVisible () { return getHeaderVisible(3); } public boolean getHeaderFourVisible () { return getHeaderVisible(4); } private String getHeader (int i) { if (_selected != null) { Object[] arr = _selected.toArray(); if (arr.length >= i) { return (String)arr[i - 1]; } } return null; } private boolean getHeaderVisible (int i) { if (_selected != null) { Object[] arr = _selected.toArray(); if (arr.length >= i) { return true; } } return false; } public List getData () { if (_data == null) { _data = new ArrayList(); for (int i = 0; i < 10; i++) { _data.add(new DataBean("firstName_" + i, "lastName_" + i, "birthDate_" + i, "homeCity_" + i)); } } return _data; } // nothing to do, simply trigger the update @Command @NotifyChange({"headerOne", "headerTwo", "headerThree", "headerFour", "headerOneVisible", "headerTwoVisible", "headerThreeVisible", "headerFourVisible" ,"model"}) public void updateGrid () { } class DataBean { private String _firstName; private String _lastName; private String _birthDate; private String _homeCity; public DataBean (String firstName, String lastName, String birthDate, String homeCity) { _firstName = firstName; _lastName = lastName; _birthDate = birthDate; _homeCity = homeCity; } public void setFirstName (String firstName) { _firstName = firstName; } public void setLastName (String lastName) { _lastName = lastName; } public void setBirthDate (String birthDate) { _birthDate = birthDate; } public void setHomeCity (String homeCity) { _homeCity = homeCity; } public String getFirstName () { return _firstName; } public String getLastName () { return _lastName; } public String getBirthDate () { return _birthDate; } public String getHomeCity () { return _homeCity; } } }
Thanks again for your help :)
Hi Benbai,,
Can it possible we can make above code universal means user call only datalist in zul and given model and other it automatically do everything (Reusable). I am talking about my last post
thanks
Hi sjoshi,
Yes, you can let DataBean to control everything then the code of zul and vm maybe more robust (but DataBean should do more thins), for example:
test.zul
<zk> <div apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('test.TestVM')"> <!-- use chosenbox to control the show/hide and order of rows update grid with onSelect event --> <chosenbox model="@load(vm.chosenModel)" selectedObjects="@bind(vm.selected)" onSelect="@command('updateGrid')" width="300px" ></chosenbox> <grid model="@load(vm.model)" rowRenderer="@load(vm.rowRenderer)" width="500px"> </grid> </div> </zk>
TestVM.java
package test; import java.util.LinkedHashSet; import java.util.Set; import org.zkoss.bind.annotation.Command; import org.zkoss.bind.annotation.NotifyChange; import org.zkoss.zul.Column; import org.zkoss.zul.Columns; import org.zkoss.zul.Grid; import org.zkoss.zul.Label; import org.zkoss.zul.ListModel; import org.zkoss.zul.ListModelList; import org.zkoss.zul.Row; import org.zkoss.zul.RowRenderer; public class TestVM { private ListModelList _model; private RowRenderer _rowRenderer; private Set _selected; private ListModelList _chosenModel; public ListModel getModel () { if (_model == null) { _model = new ListModelList(DataBean.getData()); } return _model; } public ListModel getChosenModel () { if (_chosenModel == null) { _chosenModel = new ListModelList(DataBean.getChosenData()); } return _chosenModel; } // bind to chosenbox#selectedObjects public Set getSelected () { if (_selected == null) { _selected = DataBean.getDefaultColumns(); } if (_selected == null) { _selected = new LinkedHashSet(); } return _selected; } // bind to chosenbox#selectedObjects public void setSelected (Set selected) { if (_selected == null) _selected = new LinkedHashSet(); _selected.clear(); if (selected != null) _selected.addAll(selected); } // render labels (cells) based on _selected public RowRenderer getRowRenderer () { if (_rowRenderer == null) { _rowRenderer = new RowRenderer() { public void render(Row row, Object data, int index) throws Exception { if (index == 0) { // render columns Grid grid = row.getGrid(); if (grid.getColumns() != null) grid.getColumns().detach(); Columns columns = new Columns(); columns.setParent(grid); for (Object obj : _selected) { new Column((String)obj).setParent(columns); } } final DataBean dataBean = (DataBean)data; if (_selected != null) { for (Object o : _selected) { Object value = dataBean.getValue(o); if (value != null) { new Label((String)o).setParent(row); } } } } }; } return _rowRenderer; } // nothing to do, simply trigger the update @Command @NotifyChange({"column", "columnVisible", "model"}) public void updateGrid () { } }
DataBean.java
package test; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class DataBean { private String _firstName; private String _lastName; private String _birthDate; private String _homeCity; public static List getData (/* maybe some param like ID */) { List _data = null; // or try get from cache if (_data == null) { _data = new ArrayList(); for (int i = 0; i < 10; i++) { _data.add(new DataBean("firstName_" + i, "lastName_" + i, "birthDate_" + i, "homeCity_" + i)); } } return _data; } public static List getChosenData() { List l = new ArrayList(); l.add("firstName"); l.add("lastName"); l.add("birthDate"); l.add("homeCity"); return l; } public Object getValue (Object selectedItem) { if ("firstName".equals(selectedItem)) return getFirstName(); if ("lastName".equals(selectedItem)) return getLastName(); if ("birthDate".equals(selectedItem)) return getBirthDate(); if ("homeCity".equals(selectedItem)) return getHomeCity(); return null; } public static Set getDefaultColumns() { Set defaultColumns = new LinkedHashSet(); defaultColumns.add("firstName"); defaultColumns.add("lastName"); return defaultColumns; } public DataBean (String firstName, String lastName, String birthDate, String homeCity) { _firstName = firstName; _lastName = lastName; _birthDate = birthDate; _homeCity = homeCity; } public void setFirstName (String firstName) { _firstName = firstName; } public void setLastName (String lastName) { _lastName = lastName; } public void setBirthDate (String birthDate) { _birthDate = birthDate; } public void setHomeCity (String homeCity) { _homeCity = homeCity; } public String getFirstName () { return _firstName; } public String getLastName () { return _lastName; } public String getBirthDate () { return _birthDate; } public String getHomeCity () { return _homeCity; } }
Regards,
Ben
Asked: 2012-07-25 19:41:02 +0800
Seen: 879 times
Last updated: Oct 04 '12