0

How to detach / reattach MVVM windows?

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

cyiannoulis gravatar image cyiannoulis
1201 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 2013-06-26 14:41:18 +0800

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

I am afraid to implement rickcr solution because, at one point of time there will be lot of forms in the memory in the invisible mode. cyiannoulis also pointed this saying "Also, you may notice that if you have many "heavy" invisible at the same time, the application may appear delays."

Assume that, there are 50 Master screen which typically contain a search (listing) page and add/edit page. So assume that every login, users randomly using 20 Master screens, then all those will be in the invisible mode ?

cyiannoulis Can you please sample code the way how you implemented.

link publish delete flag offensive edit

answered 2013-06-26 14:49:43 +0800

rickcr gravatar image rickcr
704 7

@Senthilchettyin Yes, the memory is a concern, however, wouldn't a tabbox be acting in the same way "under the covers?"

When you switch tabs it doesn't appear ZK is destroying the old tab content, so think how many applications are out there using a tabbox for navigation and potentially running into the same memory issue?

If someone from the ZK team reads this, can you reply if there is any talk about providing some sort of component that handles this "navigation issue."

It would sem like all that would be needed is a component that can take a "zul to load" and "div id to load into" and the controller takes care of the destroying the old page and loading in the new one.

link publish delete flag offensive edit

answered 2013-06-26 15:12:28 +0800

cyiannoulis gravatar image cyiannoulis
1201 10

updated 2013-06-26 15:19:11 +0800

@Senthilchettyin Yes you are right... and wrong. I was thinking exactly the same thing when we started to implement this small framework but in the end i discovered that it is not very wise to open too many 'master' forms within the same zk desktop. The maximum depth of pages hiding one-another that i have reached is 5. But i do not open two different modules in the same desktop. For example, i do not open Accounting related pages in the same workspace with Administration pages. If the user wants to have opened at the same Accounting module and Administration module then he/she opens them in separate browser tabs.

Also, another problem we solved using this way was how to distinct between @GlobalCommands with the same name but in different pages. For example, lets say you have a @GlobalCommand('update') in two different pages making completely different things. If you fire this command from a third page, the system will try to execute both. But wait! All you need is to executed the global command only in the first one. How do you achieve this?

Well, i will try to find some time to post this small framework to the zk community.

/costas

link publish delete flag offensive edit

answered 2013-06-26 15:13:14 +0800

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

Yes rickcr. Tab also have the same problem. Still this kind of problem exists in my project. And also, we are not only hiding the forms which contains components, but also, we hiding the data. For example, in my current project, we have given different search parameters to retrieve the record from the master and then select any one of the record to update.

So in this case, the result set also in the invisible mode.

link publish delete flag offensive edit

answered 2013-06-26 15:17:17 +0800

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

Hi cyiannoulis

Thank you for your reply. But we cannot open in browser tab, because our project is on the patient medical record. So security is more important here. Because after x number of idle seconds/min, we need to close the application or logoff the application. If it is one desktop, then it is easy to handle, same will be difficult if forms are showing in different tabs of the browser.

link publish delete flag offensive edit

answered 2013-06-26 16:36:28 +0800

rickcr gravatar image rickcr
704 7

@Costas in reference to your point "For example, lets say you have a @GlobalCommand('update') in two different pages making completely different things. If you fire this command from a third page, the system will try to execute both"

Couple things concerning this... 1) I "try" to keep my commands unique. (I also use a Constants class to hold them and use this constants class when firing them... otherwise it gets real messy using a hard coded string name .. I mention it here If you fire this command from a third page, the system will try to execute both)

2) Where I DO run into your problem is when I'm using a layout and on the outer layout there is a "submit" or "update" button. For example, think of a south region of border layout and in the south is your update/submit button, but in the middle section you're swapping out zul pages. What I do is I keep track of the current view I am on by an id/name in a Spring desktop session. Each view model has their own id. When the global update/submit is picked up by each View Model it compares its ViewModel id to the "active one" (the one in desktop session scope) so it simply returns form the global command if the ids don't match and the global command is ignored. (Note this is only an issue when you're keeping ViewModels alive by hiding the view as described in this thread. If you're creating your view fresh each time this won't be an issue since the other view models are destroyed.)

(I suppose there is a way you could load up the command name dynamically for that south region but it would be really messy - you'd end up having to fire a global command that your outer navigation would listen to and then have to set the command name.)

link publish delete flag offensive edit

answered 2013-06-26 18:04:17 +0800

cyiannoulis gravatar image cyiannoulis
1201 10

@rickr It feels great to see that we are walking the same way.. I will try to explain in short what we are doing and maybe you can propose something more advance.

So, in our framework, each "module" is represented by a separate "workspace". For us, "module" is a logical set of forms and view models responsible to handle a certain business topic.

Only a single workspace may be active at a time in the zk desktop.

The workspace is capable to swap forms (.zul) as needed using the hide/unhide mechanism.

All workspaces inherit from the Workspace VM class which is responsible to know 2 things: (1) The stack of the opened sub forms and (2) A HashMap of registered global commands.

The @GlobalCommand in each sub form is declared like the following example:

@GlobalCommand(_FORM_ID + "update")
public void onUpdate(@BindingParam.....)

The FORMID is a unique final property for each sub form. Now, each time the workspace opens a new sub form, the sub form registers to the parent workspace the global commands she needs to hear. For example the sub form (A) registers to the workspace that she needs to hear the global command "update". Similar, the sub form (B) registers to the workspace that she needs to hear the command "update". By this way, the workspace has a map of @GlobalCommand names mapped to FORMIDs.

The final thing is to post a global command. So, when the sub form ( C ) wants to post the global command "update", invokes the parent workspace:

publishEvent("update", params)

Then, the workspace is responsible to post the received global command to all target forms who have declared interest on this event. Before posting the global command, the workspace prefixes the global command name with the target FORMID.

It seems quite similar to what you do already...

/costas

link publish delete flag offensive edit

answered 2013-06-26 19:31:44 +0800

rickcr gravatar image rickcr
704 7

@Costas Seems like a pretty cool approach although I'd like to see it in a simple Hello World application. Maybe you could set one up at github? (with a maven build.) I'd love to check it out.

I'd like to link the approach as tutorial from a the site I host http://www.learntechnology.net/ I'm starting to add various approaches I'm taking to solving some ZK issues.

link publish delete flag offensive edit

answered 2013-06-26 21:43:41 +0800

cyiannoulis gravatar image cyiannoulis
1201 10

Cool! I will try to setup a sample app as soon as i have a little time.

/costas

link publish delete flag offensive edit

answered 2013-06-27 02:53:11 +0800

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

Hi rickcr

Just had a quick look on your site. Good Job. Expecting more stuff like that to help beginner like me :)

Senthil

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: 450 times

Last updated: May 27 '16

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