0

How to detach / reattach MVVM windows?

asked 2012-12-20 11:54:46 +0800

cyiannoulis gravatar image cyiannoulis
1191 10

updated 2013-06-28 03:49:10 +0800

jimyeh gravatar image jimyeh
2047 1 4
ZK Team

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

delete flag offensive retag edit

22 Replies

Sort by ยป oldest newest

answered 2012-12-20 13:41:37 +0800

Senthilchettyin gravatar image Senthilchettyin flag of India
2611 3 8
http://emrpms.blogspot.in...

In the MainViewVM, Change as follows


@GlobalCommand
public void reattachSearchView() {
wDetailView.detach();
wSearchView = (Window) Executions.createComponents("search-view.zul", divWorkspace, null);
}

link publish delete flag offensive edit

answered 2012-12-20 13:48:46 +0800

cyiannoulis gravatar image cyiannoulis
1191 10

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

link publish delete flag offensive edit

answered 2012-12-20 13:52:32 +0800

Senthilchettyin gravatar image Senthilchettyin flag of India
2611 3 8
http://emrpms.blogspot.in...

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.

link publish delete flag offensive edit

answered 2012-12-20 14:15:47 +0800

Senthilchettyin gravatar image Senthilchettyin flag of India
2611 3 8
http://emrpms.blogspot.in...

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);		
	}
}

link publish delete flag offensive edit

answered 2012-12-20 15:17:08 +0800

cyiannoulis gravatar image cyiannoulis
1191 10

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

link publish delete flag offensive edit

answered 2012-12-20 15:49:17 +0800

Senthilchettyin gravatar image Senthilchettyin flag of India
2611 3 8
http://emrpms.blogspot.in...

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.

link publish delete flag offensive edit

answered 2012-12-20 15:51:00 +0800

Senthilchettyin gravatar image Senthilchettyin flag of India
2611 3 8
http://emrpms.blogspot.in...

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.

link publish delete flag offensive edit

answered 2012-12-20 16:44:53 +0800

cyiannoulis gravatar image cyiannoulis
1191 10

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

link publish delete flag offensive edit

answered 2013-05-25 01:57:38 +0800

rickcr gravatar image rickcr
704 7

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?

link publish delete flag offensive edit

answered 2013-06-26 14:24:46 +0800

cyiannoulis gravatar image cyiannoulis
1191 10

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

link publish delete flag offensive edit
Your reply
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!

[hide preview]

Question tools

Follow

RSS

Stats

Asked: 2012-12-20 11:54:46 +0800

Seen: 423 times

Last updated: May 27 '16

Support Options
  • Email Support
  • Training
  • Consulting
  • Outsourcing
Learn More