MVVM Extension: Access UI Components Inside ViewModel

From Documentation

MVVM Extension: Access UI Components Inside View Model

Developing an application in MVVM pattern has several advantages including clear separation between View and backend, and easy to unit test. Sometime developers want more control of components like dynamically creating components, or custom loading on demand. Sometimes it's just a personal preference. This article tells you how to access UI components inside a View Model with a little bit extra work.

Extend Case Scenario: Popup Detail

In search example mentioned in previous article, users see an item's description in detail only when they click a item in the listbox (as the below image shows). It is more convenient to show the description when the mouse hover an item without clicking it. We'll implement this feature through MVVM with autowiring components.

Smalltalks-mvvm-in-zk6-view-example.png

Add Popup

Add a popup component to display item description.

	<popup id="msgPopup">
		<label id="msg"></label>
	</popup>

Enable Autowire of View Model

A View Model is a POJO basically, it doesn't have ability to wire UI components. We empower View Model to wire components with @init.

public class SearchAutowireVM{
	//other variables
	//UI component
	@Wire("#msgPopup")
	Popup popup;
	@Wire("#msg")
	Label msg;

	@Init
	public void init(BindContext ctx){
		//Returns associated root component of the binder
		Component component = ctx.getBinder().getView();
		Selectors.wireVariables(component, this);
		//wire event listener
//		Selectors.wireEventListeners(component, this);
	}
	//...
}

We annotate a method public void init(BindContext) with @Init, then the binder will invoke this method and pass in BindContext when it initializes the View Model. Inside the method, Selectors.wireVariables(component, this) will wire those variables with @Wire. Selectors.wireEventListeners(component, this) wires those methods with annotation @Listen as event listeners. In this example, we don't use it. Here we use "wire by selector", "#msgPopup" means wiring a component with id "msgPopup". This feature is provided by SelectorComposer mentioned by another small talk.


Popup Message

Manipulate popup component to display a popup message at the end of listitem.

	@Command
	public void popupMessage(@Param("target")Component target, @Param("content")String content){
		msg.setValue(content);
		popup.open(target,"end_before");
	}

@Param("target") declares that the binder shall ...a argument with key "target"

<listbox model="@load(vm.items)" selectedItem="@bind(vm.selected)" hflex="true" height="300px">
	<listhead>
		<listheader label="Name"/>
		<listheader label="Price" align="center" />
		<listheader label="Quantity" align="center"  />
	</listhead>
	<template name="model" var="item">
		<listitem onMouseOver="@command('popupMessage', target=self, content=item.description)">
			<listcell label="@bind(item.name)"/>
			<listcell label="@bind(item.price)@converter('formatedNumber', format='###,##0.00')"/>
			<listcell label="@bind(item.quantity)" sclass="@bind(item.quantity lt 3 ?'red':'')"/>
		</listitem>
	</template>
</listbox>