ZK8 Features for MVC - Shadow Elements - Part 1
Robert Wenzel, Engineer, Potix Corporation
August 2016
ZK 8.0
Introduction
ZK 8 added several new Features mainly improving Component control when using the MVVM design pattern. The question I sometimes heard since was: (How) Can we benefit from these features in our MVC application ?
To answer the question I'll introduce the most prominent features which are:
- shadow elements
- form proxies (later article)
Let's have a look where they can be used in the MVC world.
If you are missing a feature let us know in the comments section below.
Shadow Elements in MVC
Some of the Shadow Elements LINK ME are more/less beneficial in MVC than others here a short summary
- <if>
- most useful when used with dynamic @load expressions in MVVM
- in MVC you can attach/detach components directly (so this is nothing new for MVC)
- used statically you can render parts of your UI based on a conditional EL expression
<if test="${some.condition}">...</if>
- <choose>, <when>, <otherwise>
- similar to <if> no real benefit for MVC
- <forEach>
- very useful this will be the topic in next smalltalk
- <apply>
- I'll show a few usage scenarios today ... just read on.
Some background
If you already know the details and differences between <include> and Macro, you can just skip this paragraph.
Why a new shadow element? Let's compare what's been there previously.
For static UI composition you'd classically use an <include> component such as this:
<include src="customerDetails.zul" customerId="123"/>
Or a macro component:
<?component name="my-macro" macroURI="customerDetails.zul"?>
<my-macro customerId="123"/>
These 2 examples roughly will give the same output. Comparing the generated DOM we'll see that the <include> creates:
<div class="z-include">...</div>
and the Macro generates:
<div class="z-macro">...</div>
So far so good. You might ask: "What's the difference?" "Which is better?" - The answer is often not that trivial and depends on what you need - and sometimes neither option fulfills all requirements.
If you don't need/want the additional <div> (e.g. because it breaks your layout, or you are inside a component hierarchy that only allows certain child components e.g. grid>rows>row) the case seems clear: use an inline macro LINK ME (which in return has its own technical limitations). Main impact is the root component of the macro being detached from the component tree - losing all its properties/custom-attributes, which you might want to refer to inside the included zul template (this gets additionally painful when using MVVM and dynamic bind annotations - not the topic in this MVC dedicated article). At the same time include doesn't have an inline mode.
If you need a different DOM element other than <div> you can use the enclosingTag property LINK ME of the macro (since 5.0.3) or <include/> since (7.0.4) LINK ME.
Often surprising (even if documented) are the side effects when using an <include> component with a url containing a query string ... while Macros simply ignore the query string. The query string with a changing parameter is often used as a workaround to reload the same included page.
<include src="customerDetails.zul?customerId=123"/>
Suddenly the <include> component changes to "defer" mode LINK ME resulting in a different component creation life cycle and a nested page. While necessary in certain cases this is usually more than you expect/need if you simply want to insert a zul fragment.
Last but not least there is a Template Composition LINK ME pattern which works again in a very unique way (... let's not go into details right now, we can do the same with <apply> without learning a separate syntax)
I assume you see where I am getting ... these grown components/features have diverged over time, causing quite some confusion and discussion about which to use, in which scenario and how to work around the limitations in various cases.
<apply> for static layout composition
In order to unify the approaches described above and to reduce the unexpected side effects and limitations the <apply> shadow element was introduced in ZK 8.
To enable it you have to add the zuti.jar to your project LINK ME.
Why "Shadow"? - Because it works behind the scenes (similar to a Shadow-DOM):
- it represents a position inside the component tree used as an injection point for a <template>
- it does not affect the parent-child-sibling relations between the real components LINK TO JUMPER'S article
- it does not create any DOM element by itself - it is totally transparent to the client side (no more unwanted <div>)
- it will even disappear automatically if not needed anymore to save memory (this can be disabled on demand)
Static usage examples
Like an include / macro to insert a zul file (optionally with parameters)
<apply templateURI="customerDetails.zul" customerId="123"/>
Insert inline templates
<apply template="${currentUser.hasEditPermission ? 'editable' : 'readonly'}>
<template name="readonly">
<label value="${person.name}"/>
</template>
<template name="editable">
<textbox value="${person.name}"/>
</template>
</apply>
Insert external templates
<apply template="${currentUser.hasEditPermission ? 'editable' : 'readonly'}>
<template name="readonly" src="personView.zul"/>
<template name="editable" src="personEdit.zul"/>
</apply>
Inject contents templates into a layout template (example of a nested usage)
pageLayout.zul (a reusable layout template with nested <apply> elements to inject content)
<div sclass="wrapper">
...
<div sclass="header">
<apply template="headerContent"/>
</div>
<div sclass="content">
<apply template="pageContent"/>
</div>
...
</div>
Use the pageLayout.zul anywhere and define the templates to be injected (inline or external zul file)
<apply templateURI="pageLayout.zul">
<template name="headerContent">
My Application Overview <button label="logout"/>
</template>
<template name="pageContent" src="applicationOverviewPage.zul"/>
</apply>
This greatly helps to reduce the size of your zul pages, and split them into smaller maintainable fragments.
As a syntax sugar you can define your own tags in a way very similar to macro components (inline or inside a lang-addon.zul). Internally the <apply> tag is extended adding a default templateURI (also other properties can be predefined).
Inline definition using the component processing-instruction LINK ME
<?component name="sidebar" templateURI="/WEB-INF/zul/layout/sidebar.zul"/>
<?component name="footer" templateURI="/WEB-INF/zul/layout/footer.zul"/>
<zk>
<div sclass="wrapper">
<sidebar/>
<apply template="someContent.zul" />
<footer/>
</div>
</zk>
or make the tags globally available via lang-addon.zul LINK ME
<component>
<component-name>sidebar</component-name>
<template-uri>/WEB-INF/zul/layout/sidebar.zul</template-uri>
</component>
<component>
<component-name>footer</component-name>
<template-uri>/WEB-INF/zul/layout/footer.zul</template-uri>
</component>
RESUME HERE RESUME HERE RESUME HERE RESUME HERE
... can be replaced by an <apply> element:
<apply templateURI="customerDetails.zul" customerId="123"/>
The difference is that an <apply> does less things "under the hood" compared to <include>:
- it does not create an additional
in the resulting DOM
- -> in fact it does not exist at the client side at all
- it does not create a new idspace (you can control that yourself as needed)
- it does not have this automatic behavior of switching between "instant" and "defer" mode like an include
- it does not create a nested Page object
- it only supports zul/zhtml templates
- parameters in the URI (e.g. some.zul?id=abc) are accessible via ${arg.id} variable instead of ${param.id}
Simpy spoken behaves like a call to Executions.createComponents(...).
To save memory the <apply> shadow element is even removed completely from the server side component (shadow) tree when used purely statically. The shadow elements are not removed when used with dynamic parameters. E.g.:
- in MVVM using the ZKBIND anotations (@load/@init/@ref)
- in MVC by adding the special attribute dynamicValue="true"
This is required if you want to wire the shadow element into your composer, e.g. to set the templateURI or parameters dynamically. LINK EXAMPLE BELOW
<div>
<apply id="panelContent"/>
</div>
- Line 2: assign an id in the zul file
@Wire("::shadow#panelContent")
private Apply panelContent;
- Line 1: wire by id using a shadow selector
Still in pure java you could just call Executions.createComponents(...) LINK ME
helps to compose the UI from ZUL fragments (more flexible than <include> or Macro components)
<apply> with Templates
On top of the above <apply> can be used to insert templates at any point of your page. This can be useful when refactoring larger zul files in order to remove duplicate layout structures.
Here a repeated layout only differing by customer object and a label.
<zk>
<div>
<div sclass="customerBox">
<div>Current Customer id: ${sessionScope.currentCustomer.id}</div>
<div>Current Customer name: ${sessionScope.currentCustomer.name}</div>
</div>
<div sclass="customerBox">
<div>Last Customer id: ${sessionScope.lastCustomer.id}</div>
<div>Last Customer name: ${sessionScope.lastCustomer.name}</div>
</div>
</div>
</zk>
The repeated layout can be avoided by defining a template (customerBox) and apply the same template using different parameters.
<zk>
<div>
<apply template="customerBox" customer="${sessionScope.currentCustomer}" label="Current Customer"/>
<apply template="customerBox" customer="${sessionScope.lastCustomer}" label="Last Customer"/>
</div>
<template name="customerBox">
<div sclass="customerBox">
<div>${label} id: ${customer.id}</div>
<div>${label} name: ${customer.name}</div>
</div>
</template>
<!-- alternatively define the template content in a separate file
<template name="customerBox" src="/WEB-INF/templates/customerBox.zul"/>
-->
</zk>
As described above you can also apply the template directly using the templateURI.
<apply templateURI="/WEB-INF/templates/customerBox.zul" customer="${sessionScope.currentCustomer}" label="Current Customer"/>
Or as a syntax sugar you can define a custom tag inside the page.
<?component name="customerBox" templateURI="/WEB-INF/templates/customerBox.zul"?>
<zk>
<div>
<customerBox customer="${sessionScope.currentCustomer}" label="Current Customer"/>
<customerBox customer="${sessionScope.lastCustomer}" label="Last Customer"/>
</div>
</zk>
Finally you can declare the custom element globally in a lang-addon.xml, and omit the <?component ... ?> directive in the page.
<component>
<component-name>customerBox</component-name>
<template-uri>/WEB-INF/templates/customerBox.zul</template-uri>
</component>
ForEach
https://www.zkoss.org/wiki/ZK_Developer's_Reference/MVC/Controller/Wire_Components#Shadow_Selectors
CollectionTemplate
ZUL Template
Java Based Template
Using ZK8 Form Proxies in MVC
Creating a Simple CRUD Template
Summary
Download
- The source code for this article can be found on github.
Running the Example
Clone the repository
git clone [email protected]:zkoss-demo/mvc-shadow.git cd mvc-shadow git checkout master
The example war file can be built with maven:
mvn clean package
Execute using jetty:
mvn jetty:run
Then access the overview page http://localhost:8080/mvc-shadow/
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |