-
FEATURED COMPONENTS
First time here? Check out the FAQ!
I really need help with the following scenario. We are migrating an application from MVC to MVVM and i think we are stuck. Any help would be much appreciated. Sample source code at the end of this message.
1. The main-view.zul contains a workspace Div.
2. The main-view creates the 'search-view.zul' with parent the workspace div.
3. The search-view sends the global command 'openDetailView' to the main-view.
4. The main-view detaches the search-view window and creates the 'detail-view.zul' with parent the workspace div.
5. The detail-view sends the global command 'reattachSearchView' to the main-view.
6. The main-view detaches the detail-view window and re-sets the search-view with parent the workspace div.
At this point the search-view.zul stops responding and sends all commands to the main-view generating the following message:
"cannot find any method that is annotated for the command close with @Command in MainViewVM"
I understand that the whole thing has to do with the lifecycle of each viewmodel's binder but i cannot find anything useful in the documentation.
The same functionality could be achieved easily using SelectorComposers and the old data binder.
The only alternative i have found is to make the windows visible/invisible instead of detach/setParent but this is not what we want.
<?page title="Main View" contentType="text/html;charset=UTF-8"?> <zk> <window id="winMainView" title="Main View" border="normal" height="100%" width="100%" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('test.MainViewVM')" > <borderlayout> <north height="48px" border="none"> <vlayout> <label value="This is the Main View" /> </vlayout> </north> <center border="none"> <div id="divWorkspace" vflex="true" /> </center> </borderlayout> </window> </zk>
package test; import org.zkoss.bind.annotation.AfterCompose; import org.zkoss.bind.annotation.ContextParam; import org.zkoss.bind.annotation.ContextType; import org.zkoss.bind.annotation.GlobalCommand; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.select.Selectors; import org.zkoss.zk.ui.select.annotation.Wire; import org.zkoss.zul.Div; import org.zkoss.zul.Window; public class MainViewVM { @Wire private Div divWorkspace; /* * The window representing the 'search' sub-view */ private Window wSearchView; /* * The window representing the 'detail' sub-view */ private Window wDetailView; @AfterCompose public void init(@ContextParam(ContextType.VIEW) Component view) { Selectors.wireComponents(view, this, false); /* * create the search-view */ wSearchView = (Window) Executions.createComponents("/test/search-view.zul", divWorkspace, null); } @GlobalCommand public void openDetailView() { wSearchView.detach(); wDetailView = (Window) Executions.createComponents("/test/detail-view.zul", divWorkspace, null); } @GlobalCommand public void reattachSearchView() { wDetailView.detach(); wSearchView.setParent( divWorkspace ); } }
<?page title="Search" contentType="text/html;charset=UTF-8"?> <zk> <window id="winSearch" title="Search" border="normal" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('test.SearchViewVM')" > <vlayout> <label value="This is the 'search' view embedded inside the main div" /> <button label="Close 'search' view and open the 'detail' view" onClick="@command('close')" /> </vlayout> </window> </zk>
package test; import org.zkoss.bind.BindUtils; import org.zkoss.bind.annotation.Command; public class SearchViewVM { @Command public void close() { BindUtils.postGlobalCommand(null, null, "openDetailView", null); } }
<?page title="Detail" contentType="text/html;charset=UTF-8"?> <zk> <window id="winDetail" title="Search" border="normal" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('test.DetailViewVM')" > <vlayout> <label value="This is the 'detail' view embedded inside the main div" /> <button label="Close 'detail' view and try to re-attach the 'search view" onClick="@command('close')" /> </vlayout> </window> </zk>
package test; import org.zkoss.bind.BindUtils; import org.zkoss.bind.annotation.Command; public class DetailViewVM { @Command public void close() { BindUtils.postGlobalCommand(null, null, "reattachSearchView", null); } }
thanks to all
/costas
In the MainViewVM, Change as follows
@GlobalCommand
public void reattachSearchView() {
wDetailView.detach();
wSearchView = (Window) Executions.createComponents("search-view.zul", divWorkspace, null);
}
I 'm afraid this is not what i want. The search view contains already some results. We don't want to re-create the page. What we want is to restore the previous page "as it was" right before detaching. This could be achieved using a simple setParent() in a SelectorComposer but in MVVM ?
/costas
Then may be the way how you handled is wrong. In My project, i have used lot MVVM and Border Layout. But with different way.
Try this
I changed MainViewVM, search-view.zul, detail-view.zul
<?page title="Detail" contentType="text/html;charset=UTF-8"?> <zk> <window id="winDetail" title="Detail View" border="normal" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('test.DetailViewVM')" > <vlayout> <label value="This is the 'detail' view embedded inside the main div" /> <button label="Close 'detail' view and try to re-attach the 'search view" onClick="@command('close')" /> </vlayout> </window> </zk>
<?page title="Search" contentType="text/html;charset=UTF-8"?> <zk> <window id="winSearch" title="Search View" border="normal" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('test.SearchViewVM')"> <groupbox id="grpbox"> <vlayout> <label value="This is the 'search' view embedded inside the main div" /> <button label="Close 'search' view and open the 'detail' view" onClick="@command('close')" /> </vlayout> </groupbox> </window> </zk>
package test; import org.zkoss.bind.annotation.AfterCompose; import org.zkoss.bind.annotation.ContextParam; import org.zkoss.bind.annotation.ContextType; import org.zkoss.bind.annotation.GlobalCommand; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.select.Selectors; import org.zkoss.zk.ui.select.annotation.Wire; import org.zkoss.zul.Div; import org.zkoss.zul.Groupbox; import org.zkoss.zul.Window; public class MainViewVM { @Wire private Div divWorkspace; /* * The window representing the 'search' sub-view */ private Window wSearchView; /* * The window representing the 'detail' sub-view */ private Window wDetailView; private Groupbox grpbox; @AfterCompose public void init(@ContextParam(ContextType.VIEW) Component view) { Selectors.wireComponents(view, this, false); /* * create the search-view */ wSearchView = (Window) Executions.createComponents("search-view.zul", divWorkspace, null); } @GlobalCommand public void openDetailView() { grpbox = (Groupbox) wSearchView.getFellow("grpbox"); grpbox.setVisible(false); wSearchView.setTitle(""); wSearchView.setBorder(false); wDetailView = (Window) Executions.createComponents("detail-view.zul", wSearchView, null); wDetailView.doEmbedded(); } @GlobalCommand public void reattachSearchView() { wDetailView.detach(); grpbox.setVisible(true); wSearchView.setTitle("Search View"); wSearchView.setBorder(true); } }
Thanks for your time Sen but i was hoping there is another alternative than playing with the component's visibility. One disantvage using this technique is that the client's DOM remains loaded with ALL previous windows. This means that in a sophisticated application scenario:
1. Search page to display invoices
2. Select an invoice and open the detail view.
3. Edit the selected Vendor's details.
(3 complicate views all stored in the same dom)
Also, you may notice that if you have many "heavy" invisible at the same time, the application may appear delays. Of course someone may say why don't you use dialogs? Personaly i hate modal dialogs for edit or view. I am fan of a single screen per task like in most today's ERP systems.
thanks again
/costas
You can store the Search query in the session and clear the search view and load the detail view. Then detail view is closed, apply the query again.
Another way of doing is, load the pages in the tab. i.e after search, when the user click any of the invoice, open the invoice in the next tab on the same center area . Now both are avail. Look at zk sample 2 application for this example.
Yes, tabs is a solution which is acceptable by many users.
Pros: You may open multiple similar documents in one view.
Con: It is the user's responsibility to close uneeded tabs.
So, about my initial question: Is there anyway to handle the Binder's lifecycle? I have noticed that you may retrieve the binder's instance of a detached window just by doing: component.getAttribute("binder")
The problem is that as soon as you detach the window, the binder's "bindings" collection is cleared. Any other idea?
/costas
To me this is a big weakness in ZK and I've pointed out numerous times. The issue is the framework makes it extremely cumbersome to maintain basic navigation. For example think of a list of navigational items on the left side and you want to open content in the main section. There should be a way to handle this in some generic way without having to use Executions.createComponents(...)
In the meantime, I might not be understanding your full requirements but maybe this will help...
My apps typically have an "outer layout" that is backed by a view model. Rather than attach and detach, I end up having them added to the main section and made visible/invisible. This might not be the most efficient approach but I haven't seen noticeably bad behavior.
This view model listens for selections (from a menu) that are then suppose to display content in a center section.. however, if that content was previously displayed than we simply reattached that content. In essence, this ends up behaving sort of like a tab layout but without the tab layout manager.
private Map<String,Component> pages = new HashMap<String, Component>();
private String currentPageName;
//triggered by menu item link click
@Command
@NotifyChange({"currentPageName"})
public void onShowContent(@BindingParam("page") String page) {
logger.debug("onShowContent for page {}", page);
if(pages.get(currentPageName) != null) {
//turns off the existing content
pages.get(currentPageName).setVisible(false);
}
if(pages.get(page) == null) {
//if we don't find thie page in our map we create it and save it for
//future use when it's selected
Component comp = Path.getComponent("/mainWindow/contentHolder");
pages.put(page, Executions.createComponents(page, comp, null));
}
//find our content and display it
pages.get(page).setVisible(true);
currentPageName = page;
}
Let me know if something like the above works for you.
If some devs are reading this I hope some kind of generic component could be provided that would handle this. Maybe you register with it your possible page names and the content section you want your pages to open in?
This is fun... I havent seen your answer on time and in the meantime we have build a small framework implementing more or less the philosophy you mention above.
We have a desktop.zul playing the role of the main container. The main Div of the desktop acts as a workspace. Each module opens having parent the desktop Div. We have implemented also some more sophisticated classes to hide/unhide or 'recycle' forms just by keeping a stack of the hidden forms. I think it works very smooth.
Thank you anyway /costas
Asked: 2012-12-20 11:54:46 +0800
Seen: 450 times
Last updated: May 27 '16
Databinding and auto-complete on combobox
Composite component and bind in ZK 6
Is there a way to resolve view model properties as input to client side javascripts?
How can I synchronize data in a ListBox in MVVM ? [closed]
MVVM Validator: class not found ? [closed]
How to Call Child ViewModel Method from Parent Window? [closed]
[Solved]Aplikasi Hibernate Spring [closed]
zk mvvm > what's the best component to make LOV (list of value) of a master data [closed]