0

Problem with Zk + Modal (aka popus) windows + Spring DelegatingVariableResolver

asked 2011-05-10 12:17:58 +0800

chulai gravatar image chulai
9 1

Hi All!

I'm experiencing problems with modal windows and the way variable resolvers work in Zk. Not sure if this is a bug or I'm doing something wrong.

I have MasterComposer and DetailComposer defined as Spring-managed beans with the following annotations:

@Controller("masterComposer")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)

and

@Controller("detailComposer")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)


Then on top of my ZUML pages I declare <?variable-resolver class="com.twg.wls.ui.controller.resolver.DelegatingVariableResolverWithCache"?>

These are all the files I have:

master.zul:

<?page contentType="text/html;charset=UTF-8"?>
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<?variable-resolver class="com.twg.wls.ui.controller.resolver.DelegatingVariableResolverWithCache"?>

<zk>
	<window id="winMaster" border="none" width="100%" height="100%" apply="${masterComposer}" xmlns:h="http://www.w3.org/1999/xhtml">
	
		<div>
			<label value="First name: " />
			<textbox cols="15" maxlength="25" value="@{masterComposer.customerNameCriterion}" />

			<label value="Last name: " />
			<textbox cols="15" maxlength="25" value="@{masterComposer.customerLastNameCriterion}" />

			<button id="searchButton" label="Search" image="/img/icons/application_form_magnify.gif" />
			<separator />
			<div>
			Enter John, Joe or Jane for firstname.
			Enter Simpson for lastname.
			</div>
		</div>
		<separator />
		<separator />
		
		<groupbox id="customersInfo" mold="3d">
			<caption label="Customers" />
			<grid fixedLayout="true">
				<rows>
					<row>
						<vbox align="center" width="100%">
						<listbox id="listCustomers" model="@{masterComposer.customers, load-after='searchButton.onClick, winMaster.onOK'}" selectedItem="@{masterComposer.customer}" 
							width="100%" rows="0" checkmark="true" mold="paging" pageSize="5">
							<listhead sizable="true">
								<listheader sort="auto(customerId)" label="Customer Id" width="9%" />
								<listheader sort="auto(lastName)" label="Last name" />
								<listheader sort="auto(firstName)" label="First name" />
								<listheader sort="auto(phone)" label="Phone" width="10%" />
								<listheader sort="auto(postalCode)" label="Postal code" width="9%" />
							</listhead>
						    <listitem self="@{each=c}" selected="true">
						        <listcell label="@{c.customerId}" />
						        <listcell label="@{c.lastName}" />
						        <listcell label="@{c.firstName}" />
						        <listcell label="@{c.phone}" />
						        <listcell label="@{c.postalCode}" />
						    </listitem>
						</listbox>	
						<hbox>
							<button id="updateCustomerButton" label="Update" visible="@{masterComposer.canUpdateCustomer, load-after='searchButton.onClick, winMaster.onOK, listCustomers.onSelect'}" />
							<button id="deleteCustomerButton" label="Delete" visible="@{masterComposer.canDeleteCustomer, load-after='searchButton.onClick, winMaster.onOK, listCustomers.onSelect'}" />
						</hbox>
					</vbox>
					</row>
				</rows>
			</grid>
		</groupbox>	

	</window>
</zk>


MasterComposer.java:

package com.twg.crm.process.ui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.util.ComposerExt;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Window;

@Controller("masterComposer")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class MasterComposer extends GenericForwardComposer implements ComposerExt {
	
	public class CustomerDto {
		private String firstName;
		private String lastName;
		private String phone;
		private String postalCode;
		private String customerId;
		
		public CustomerDto() {
			super();
		}
	
		public String getFirstName() {
			return firstName;
		}
		
		public void setFirstName(String firstName) {
			this.firstName = firstName;
		}
		
		public String getLastName() {
			return lastName;
		}
		
		public void setLastName(String lastName) {
			this.lastName = lastName;
		}
		
		public String getPhone() {
			return phone;
		}
		
		public void setPhone(String phone) {
			this.phone = phone;
		}
		
		public String getPostalCode() {
			return postalCode;
		}
		
		public void setPostalCode(String postalCode) {
			this.postalCode = postalCode;
		}
		
		public String getCustomerId() {
			return customerId;
		}
		
		public void setCustomerId(String customerId) {
			this.customerId = customerId;
		}
		
		@Override
		public boolean equals(Object obj) {
			if(obj != null && obj instanceof CustomerDto) {
				CustomerDto other = (CustomerDto) obj;
				return getCustomerId().equals(other.getCustomerId());
			}
			return false;
		}
		
		@Override
		public int hashCode() {
			return getCustomerId().hashCode();
		}
	}

	private Map<String, CustomerDto> allCustomers;
	private List<CustomerDto> customers;
	private CustomerDto customer;
	
	private String customerNameCriterion;
	private String customerLastNameCriterion;
	
	private boolean canDeleteCustomer;
	private boolean canUpdateCustomer;

	@Override
	public void doBeforeComposeChildren(Component comp) throws Exception {
		super.doBeforeComposeChildren(comp);

		allCustomers = new HashMap<String, CustomerDto>();
		CustomerDto cust = null;
		
		cust = new CustomerDto();
		cust.setCustomerId("1");
		cust.setFirstName("John");
		cust.setLastName("Doe");
		cust.setPhone("555-4444-9999");
		cust.setPostalCode("12345");
		allCustomers.put(cust.getCustomerId(), cust);

		cust = new CustomerDto();
		cust.setCustomerId("2");
		cust.setFirstName("Joe");
		cust.setLastName("Sixpacks");
		cust.setPhone("111-2222-3333");
		cust.setPostalCode("67890");
		allCustomers.put(cust.getCustomerId(), cust);

		cust = new CustomerDto();
		cust.setCustomerId("3");
		cust.setFirstName("Foo");
		cust.setLastName("Bar");
		cust.setPhone("222-3333-6778");
		cust.setPostalCode("15478");
		allCustomers.put(cust.getCustomerId(), cust);

		cust = new CustomerDto();
		cust.setCustomerId("4");
		cust.setFirstName("Joe");
		cust.setLastName("Average");
		cust.setPhone("458-7888-7448");
		cust.setPostalCode("94578");
		allCustomers.put(cust.getCustomerId(), cust);

		cust = new CustomerDto();
		cust.setCustomerId("5");
		cust.setFirstName("Joe");
		cust.setLastName("Ordinary");
		cust.setPhone("124-9875-15478");
		cust.setPostalCode("45785");
		allCustomers.put(cust.getCustomerId(), cust);

		cust = new CustomerDto();
		cust.setCustomerId("6");
		cust.setFirstName("Jane");
		cust.setLastName("Ordinary");
		cust.setPhone("124-3322-1234");
		cust.setPostalCode("12345");
		allCustomers.put(cust.getCustomerId(), cust);

		cust = new CustomerDto();
		cust.setCustomerId("7");
		cust.setFirstName("Homer");
		cust.setLastName("Simpson");
		cust.setPhone("555-4444-9999");
		cust.setPostalCode("12345");
		allCustomers.put(cust.getCustomerId(), cust);

		cust = new CustomerDto();
		cust.setCustomerId("8");
		cust.setFirstName("Marge");
		cust.setLastName("Simpson");
		cust.setPhone("555-4444-9999");
		cust.setPostalCode("12345");
		allCustomers.put(cust.getCustomerId(), cust);

		comp.addEventListener(DetailComposer.ON_UPDATE_CUSTOMER, new UpdateCustomerListener());
	}

	private class UpdateCustomerListener implements EventListener {
		public void onEvent(Event event) throws Exception {
			onUpdateCustomer((CustomerDto) event.getData());
		}
	}

	public void onClick$searchButton() {
		canDeleteCustomer = false;
		canUpdateCustomer = false;
		
		customers = searchByCriteria(customerNameCriterion, customerLastNameCriterion);
	}

	public void onOK$winMaster(Event event) {
		onClick$searchButton();
	}
	
	public void onSelect$listCustomers() {
		canDeleteCustomer = true;
		canUpdateCustomer = true;
	}

	public void onUpdateCustomer(CustomerDto customerUpdated) {
		updateCustomer(customerUpdated);
	}

	public void onClick$updateCustomerButton() {
		Map<String, Object> arg = new HashMap<String, Object>();
		arg.put("customer", customer);
		final Window messageboxDlg = (Window) Executions.createComponents("/zk/process/detail.zul", self, arg);
		try {
			showModal(messageboxDlg, "700", "480");
			customers = searchByCriteria(customerNameCriterion, customerLastNameCriterion);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public void onClick$deleteCustomerButton() {
		int delete = showConfirmationDialog("Delete customer", "You are about to delete customer: " + customer.getLastName() + ". Proceed?");
		if(delete==Messagebox.OK) {
			deleteCustomer(customer);
			customers = searchByCriteria(customerNameCriterion, customerLastNameCriterion);
		}
	}

	public List<CustomerDto> getCustomers() {
		return customers;
	}

	public void setCustomers(List<CustomerDto> customers) {
		this.customers = customers;
	}
	
	public CustomerDto getCustomer() {
		return customer;
	}
	
	public void setCustomer(CustomerDto customer) {
		this.customer = customer;
	}

	public String getCustomerNameCriterion() {
		return customerNameCriterion;
	}

	public void setCustomerNameCriterion(String customerNameCriterion) {
		this.customerNameCriterion = customerNameCriterion;
	}
	
	public String getCustomerLastNameCriterion() {
		return customerLastNameCriterion;
	}
	
	public void setCustomerLastNameCriterion(String customerLastNameCriterion) {
		this.customerLastNameCriterion = customerLastNameCriterion;
	}

	public boolean isCanDeleteCustomer() {
		return canDeleteCustomer;
	}
	
	public boolean isCanUpdateCustomer() {
		return canUpdateCustomer;
	}
	
	private void showModal(final Window messageboxDlg, String width, String height) throws InterruptedException {
		messageboxDlg.setWidth(width);
		messageboxDlg.setHeight(height);
		messageboxDlg.setBorder("normal");
		messageboxDlg.setPosition("center,center");
		messageboxDlg.setFocus(true);
		messageboxDlg.setClosable(true);
		messageboxDlg.doModal();
	}
	
	private int showConfirmationDialog(String title, String message) {
		try {
			return Messagebox.show(message, title, Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return 0;
	}
	
	private void updateCustomer(CustomerDto customer) {
		allCustomers.put(customer.getCustomerId(), customer);
	}
	
	private void deleteCustomer(CustomerDto customer) {
		allCustomers.remove(customer);
	}
	
	private List<CustomerDto> searchByCriteria(String customerNameCriterion, String customerLastNameCriterion) {
		List<CustomerDto> customerResults = new ArrayList<CustomerDto>();
		if(allCustomers != null) {
			for(CustomerDto customer : allCustomers.values()) {
				if(customerNameCriterion != null && !customerNameCriterion.isEmpty() && customerLastNameCriterion != null && !customerLastNameCriterion.isEmpty()) {
					if(customer.getFirstName().equalsIgnoreCase(customerNameCriterion) && customer.getLastName().equalsIgnoreCase(customerLastNameCriterion)) {
						customerResults.add(customer);
					}
				} else if(customerNameCriterion != null && !customerNameCriterion.isEmpty()) {
					if(customer.getFirstName().equalsIgnoreCase(customerNameCriterion)) {
						customerResults.add(customer);
					}
				} else if(customerLastNameCriterion != null && !customerLastNameCriterion.isEmpty()) {
					if(customer.getLastName().equalsIgnoreCase(customerLastNameCriterion)) {
						customerResults.add(customer);
					}
				} else {
					customerResults.add(customer);
				}
			}
		}
		return customerResults;
	}
}


detail.zul:

<?page contentType="text/html;charset=UTF-8"?>
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" arg0="./winDetail" ?>
<?variable-resolver class="com.twg.wls.ui.controller.resolver.DelegatingVariableResolverWithCache"?>

<zk xmlns:h="http://www.w3.org/1999/xhtml">
	<window id="winDetail" width="450px" border="normal" position="center,center" focus="true" closable="true" apply="${detailComposer}">
		
		<groupbox mold="3d">
			<caption label="Update customer" />
			<grid fixedLayout="true">
				<columns width="99%">
					<column width="15%" />
					<column width="75%" />
				</columns>
				<rows>
					<row>
						<label value="First name: " />
 						<textbox cols="15" maxlength="25" value="@{detailComposer.customer.firstName}" />
					</row>
					<row>
						<label value="Last name: " />
 						<textbox cols="15" maxlength="25" value="@{detailComposer.customer.lastName}" />
					</row>
				</rows>
			</grid>
		</groupbox>
		<separator />
		<hbox>
			<button id="cancelButton" label="Cancel" />
			<button id="acceptButton" label="Accept" />
		</hbox>
	</window>
</zk>


DetailComposer.java:

package com.twg.crm.process.ui;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.ComposerExt;
import org.zkoss.zk.ui.util.GenericForwardComposer;

@Controller("detailComposer")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class DetailComposer extends GenericForwardComposer implements ComposerExt {

	//public static final String UPDATE_CUSTOMER_URL = "/zk/process/detail.zul";
	public final static String ON_UPDATE_CUSTOMER = "onUpdateCustomer";
	private MasterComposer.CustomerDto customer;

	@Override
	public void doBeforeComposeChildren(Component comp) throws Exception {
		super.doBeforeComposeChildren(comp);
		
		customer = (MasterComposer.CustomerDto) getArgument("customer");
	}
	
	public void close() {
		//self.getPage().removeVariableResolver(new DelegatingVariableResolverWithCache(UPDATE_CUSTOMER_URL));
		//self.getParent().getPage().removeVariableResolver(new DelegatingVariableResolverWithCache(UPDATE_CUSTOMER_URL));
		// close the pop-up window
		self.detach();
	}

	public void onClick$acceptButton() {
		Component parent = self.getParent();
		Events.sendEvent(new Event(ON_UPDATE_CUSTOMER, parent, customer));
		close();
	}
	
	public void onClick$cancelButton() {
		close();
	}
	
	public void onOK$winDetail() {
		onClick$acceptButton();
	}
	
	public void onClose$winDetail() {
		onClick$cancelButton();
	}
	
	public void onCancel$winDetail() {
		onClick$cancelButton();
	}

	public MasterComposer.CustomerDto getCustomer() {
		return customer;
	}
	
	public void setCustomer(MasterComposer.CustomerDto customer) {
		this.customer = customer;
	}

	private final Object getArgument(String name, Object defaultValue) {
		Object value = Executions.getCurrent().getArg().get(name);
		return value == null ? defaultValue : value;
	}

	private final Object getArgument(String name) {
		return getArgument(name, null);
	}
}


zk.xml:

<?xml version="1.0" encoding="UTF-8"?>
<zk>
	<client-config>
		<click-filter-delay>390</click-filter-delay>
	</client-config>

	<listener>
		<listener-class>com.aon.awg.wls.util.listeners.ServiceContextZkThreadInit</listener-class>
	</listener>

	<listener>
		<description>ThreadLocal Variables Synchronizer</description>
		<listener-class>org.zkoss.zkplus.util.ThreadLocalListener</listener-class>
	</listener>

	<preference>
		<name>ThreadLocal</name>
		<value>
			org.springframework.transaction.support.TransactionSynchronizationManager=resources,synchronizations,currentTransactionName,currentTransactionReadOnly,actualTransactionActive;
			org.springframework.orm.hibernate3.SessionFactoryUtils=deferredCloseHolder;
			org.springframework.transaction.interceptor.TransactionAspectSupport=transactionInfoHolder;
		</value>
	</preference>

	<system-config>
		<ui-factory-class>com.twg.wls.ui.WlsUiFactory</ui-factory-class>
		<id-generator-class>com.twg.wls.util.WlsZkIdGenerator</id-generator-class>
	</system-config>

	<language-config>
		<addon-uri>/WEB-INF/zk-lang-addon.xml</addon-uri>
		<!-- <addon-uri>/WEB-INF/datebox-addon.xml</addon-uri> -->
		<addon-uri>/WEB-INF/securedbutton-addon.xml</addon-uri>
	</language-config>

	<desktop-config>
		<theme-provider-class>com.twg.wls.ui.themes.ThemeProvider</theme-provider-class>
	</desktop-config>
	
</zk>


web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app id="WebApp">
	<display-name>test</display-name>

	<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>

	<servlet>
		<description>The servlet loads the DSP pages.</description>
		<servlet-name>dspLoader</servlet-name>
 		<servlet-class>org.zkoss.web.servlet.dsp.InterpreterServlet</servlet-class>
	</servlet>
	<servlet>
		<description>ZK loader for ZUML pages</description>
  		<servlet-name>zkLoader</servlet-name>
		<servlet-class>org.zkoss.zk.ui.http.DHtmlLayoutServlet</servlet-class>
		<init-param>
			<param-name>update-uri</param-name>
			<param-value>/zkau</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet>
		<description>The asynchronous update engine for ZK</description>
		<servlet-name>auEngine</servlet-name>
		<servlet-class>org.zkoss.zk.au.http.DHtmlUpdateServlet</servlet-class>
	</servlet>

	<servlet-mapping>
		<servlet-name>dspLoader</servlet-name>
		<url-pattern>*.dsp</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>zkLoader</servlet-name>
		<url-pattern>*.zul</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>zkLoader</servlet-name>
		<url-pattern>*.zhtml</url-pattern>
	</servlet-mapping>
	<servlet-mapping>
		<servlet-name>auEngine</servlet-name>
		<url-pattern>/zkau/*</url-pattern>
	</servlet-mapping>
</web-app>


WlsUiFactory:

com.twg.wls.ui.WlsUiFactory:

package com.twg.wls.ui;

import java.io.IOException;
import java.io.Reader;

import org.zkoss.idom.Document;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.Richlet;
import org.zkoss.zk.ui.http.SimpleUiFactory;
import org.zkoss.zk.ui.metainfo.ComponentDefinition;
import org.zkoss.zk.ui.metainfo.ComponentInfo;
import org.zkoss.zk.ui.metainfo.PageDefinition;
import org.zkoss.zk.ui.metainfo.VariableResolverInfo;
import org.zkoss.zk.ui.metainfo.impl.ComponentDefinitionImpl;
import org.zkoss.zk.ui.sys.RequestInfo;
import org.zkoss.zul.Script;

import com.twg.wls.ui.controller.resolver.DelegatingVariableResolverWithCache;

public class WlsUiFactory extends SimpleUiFactory {

	@Override
	public PageDefinition getPageDefinition(RequestInfo ri, String path) {
		return postProcessor(super.getPageDefinition(ri, path));
	}

	@Override
	public PageDefinition getPageDefinitionDirectly(RequestInfo ri,
			Document content, String extension) {
		return postProcessor(super.getPageDefinitionDirectly(ri, content,
				extension));
	}

	@Override
	public PageDefinition getPageDefinitionDirectly(RequestInfo ri,
			Reader reader, String extension) throws IOException {
		return postProcessor(super.getPageDefinitionDirectly(ri, reader,
				extension));
	}

	@Override
	public PageDefinition getPageDefinitionDirectly(RequestInfo ri,
			String content, String extension) {
		return postProcessor(super.getPageDefinitionDirectly(ri, content,
				extension));
	}
	
	private static final String COMPONENT_DEFINITION_COMBO_DELAY_ID = "deferredcbx"; 
	private static final String COMPONENT_DEFINITION_COMBO_DELAY_SOURCE = "/js/deferredcbx.js"; 


	private PageDefinition postProcessor(PageDefinition pd) {
		if ( (pd != null) && (pd.getId() != null) ) {
			if (pd.getComponentDefinition(COMPONENT_DEFINITION_COMBO_DELAY_ID, false) == null) {
				ComponentDefinition cd = new ComponentDefinitionImpl(null, pd,
						COMPONENT_DEFINITION_COMBO_DELAY_ID, Script.class);
				cd.addProperty("src", COMPONENT_DEFINITION_COMBO_DELAY_SOURCE);
				new ComponentInfo(pd, cd);
				pd.addComponentDefinition(cd);
			}
		}
		//pd.addVariableResolverInfo(createVariableResolverInfo(pd.getRequestPath()));
		return pd;
	}
	
	@Override
	public Page newPage(RequestInfo ri, PageDefinition pagedef, String path) {
		Page newPage = super.newPage(ri, pagedef, path);
		//newPage.addVariableResolver(new DelegatingVariableResolverWithCache(path));
		return newPage;
	}
	
	@Override
	public Page newPage(RequestInfo ri, Richlet richlet, String path) {
		Page newPage = super.newPage(ri, richlet, path);
		//newPage.addVariableResolver(new DelegatingVariableResolverWithCache(path));
		return newPage;
	}
	
	@Override
	public Component newComponent(Page page, Component parent, ComponentDefinition compdef, String clsnm) {
		// TODO Auto-generated method stub
		return super.newComponent(page, parent, compdef, clsnm);
	}
	
	@Override
	public Component newComponent(Page page, Component parent, ComponentInfo compInfo) {
		// TODO Auto-generated method stub
		return super.newComponent(page, parent, compInfo);
	}

	protected VariableResolverInfo createVariableResolverInfo(String path) {
		return new VariableResolverInfo(new DelegatingVariableResolverWithCache(path));
	}
}


DelegatingVariableResolverWithCache:

package com.twg.wls.ui.controller.resolver;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.zkoss.xel.VariableResolver;
import org.zkoss.zkplus.spring.SpringUtil;

public class DelegatingVariableResolverWithCache implements VariableResolver {

	protected String _path;
	protected ApplicationContext _ctx;
	protected final Map<String, Object> _vars = new HashMap<String, Object>();
	
	public DelegatingVariableResolverWithCache() {

	}
	
	public DelegatingVariableResolverWithCache(String path) {
		_path = path;
	}
	
	/**
	 * Get the spring application context.
	 */
	protected ApplicationContext getApplicationContext() {
		if (_ctx != null)
			return _ctx;
			
		_ctx = SpringUtil.getApplicationContext();
		_vars.put("springContext", _ctx);
		return _ctx;
	}
	
	/**
	 * Get the spring bean by the specified name.
	 */		
	public Object resolveVariable(String name) {
		
		if ("springContext".equals(name)) {
			return getApplicationContext();
		}
		
		Object o = _vars.get(name);
		
		if (o == null) {
			try {
				o = getApplicationContext().getBean(name);
			} catch (NoSuchBeanDefinitionException ex) {
				return null;
			}
			
			if (o != null)
				_vars.put(name, o);
		}
		
		return o;
	}
	
	public String getPath() {
		return _path;
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj != null && obj instanceof DelegatingVariableResolverWithCache) {
			DelegatingVariableResolverWithCache other = (DelegatingVariableResolverWithCache) obj;
			if(StringUtils.isNotEmpty(_path)) {
				return _path.equals(other.getPath());
			} else {
				return super.equals(obj);
			}
		}
		return false;
	}
	
	@Override
	public int hashCode() {
		if(StringUtils.isNotEmpty(_path)) {
			return _path.hashCode();
		} else {
			return super.hashCode();
		}
	}
}


Steps to reproduce the issue:

1) From master.zul enter "joe" as first name and click Search button.

This calls masterComposer.onClick$searchButton()

2) Select any customer from the Customers grid.

This calls masterComposer.onSelect$listCustomers()

3) Click Update button.

This calls updateCustomerButton():

public void onClick$updateCustomerButton() {
Map<String, Object> arg = new HashMap<String, Object>();
arg.put("customer", customer);
final Window messageboxDlg = (Window) Executions.createComponents("/zk/process/detail.zul", self, arg);
try {
showModal(messageboxDlg, "700", "480");
customers = searchByCriteria(customerNameCriterion, customerLastNameCriterion);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

4) On the modal window (detail.zul) click Cancel button to close it with no changes.

This calls onClick$cancelButton():

public void onClick$cancelButton() {
close();
}

public void close() {
// close the pop-up window
self.detach();
}

5) Keeping the same search query, click again Search button.

6) No results are loaded in the Customers grid. Neither the update and delete buttons works.

I have been debugging the ZK framework for a while to find the source of this issue. I found that after I detach my popup, the DelegatingVariableResolverWithCache of the detail.zul ZUML is kept on the _resolvers list of the Page object. This being a LinkedList it adds the VariableResolvers in a FILO order. So it's like an stack. Then when I click the search button, the master.zul page tries to resolve the @{masterComposer} references by looking at the first custom resolvers on the stack. And this is the resolver of the popup not the parent screen, where the masterComposer does NOT exist. Then Spring creates a second instance of this bean because it has a scope "prototype" and add it to the variableResolver of the now supposedly destroyed popup ZUML.

So this code on the master.zul ZUML doesn't work anymore:

<listbox id="listCustomers" model="@{masterComposer.customers, load-after='searchButton.onClick, winMaster.onOK'}" selectedItem="@{masterComposer.customer}" width="100%" rows="0" checkmark="true" mold="paging" pageSize="5">


My questions are:

I) Why ZK doesn't destroy all the references to the DetailComposer and detail.zul after I call self.detach()?
II) Why it doesn't, at least, remove its variable resolver from the stack? There is a Page.removeVariableResolver() method but the framework never calls it. I checked Zk 3.6.x and Zk 5.x.
III) Am I doing something wrong?


I have tried several options with no success:

1) remove variable-resolver directive from detail.zul. This resulted in master.zul to work as expected but I cannot open the same modal window twice. self.getParent() and self.getPage() on DetailComposer becomes null on further calls.
2) remove variable-resolver directive from detail.zul. Add the variable resolver programatically in WlsUiFactory (UiFactory implementation) on getPageDefinition and getPageDefinitionDirectly methods. Then remove it after/before detailComposer.detach(). This is not working. It seems the variable resolver still exists in the PageDefinition and I don't know how to remove it from there.
3) Call createComponents("/zk/process/detail.zul", null, arg) with a null parent hoping this would create a second Page object but it doesn't.


BTW, I'm using zk-3.6.2 version


Thanks for any help,

Julian

delete flag offensive retag edit

4 Replies

Sort by » oldest newest

answered 2011-05-12 07:46:54 +0800

chulai gravatar image chulai
9 1

Can anybody help me with this issue? Any thoughts or workarounds? I need to find a solution for this ASAP.

Thanks,

Julian

link publish delete flag offensive edit

answered 2011-05-16 20:18:33 +0800

chulai gravatar image chulai
9 1

updated 2011-05-16 20:19:18 +0800

Ok, I found a simple workaround myself: just set a variable reference of the composer on the composer.doBeforeComposeChildren() like this:

component.setVariable("composer", this, true);

then we can replace any reference on the ZUML page like:
@{masterComposer.customer}

with:
@{composer.customer}

ignoring the Spring's variable resolver.


Anyhow, I filed a bug report at https://sourceforge.net/tracker/?func=detail&aid=3303077&group_id=152762&atid=785191 for what I think is a bug on the variable resolvers not being delete them from the stack after closing a modal window.

Regards,

Julián

link publish delete flag offensive edit

answered 2012-10-25 21:49:36 +0800

jastonitas gravatar image jastonitas
3

Hello,
I have the same problem but extending of the class GenericForwardComposer<Component>, and I have not resolved yet.

link publish delete flag offensive edit

answered 2012-10-29 11:04:16 +0800

dennis gravatar image dennis
3669 1 6
http://www.javaworld.com....

updated 2012-10-29 11:06:26 +0800

use <?variable-resolver will add a resolver to a page, it will not be removed automatically.
I create a tracker for component-scope resolver, http://tracker.zkoss.org/browse/ZK-1435

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: 2011-05-10 12:17:58 +0800

Seen: 1,210 times

Last updated: Oct 29 '12

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