Wire Components"

From Documentation
Line 167: Line 167:
 
</source>
 
</source>
  
 +
===Shadow Selectors===
 +
Shadow selectors can only be used to select shadow related elements.
 +
 +
One pesudo class ''':host''' and one pseudo element '''::shadow''' has been added.
 +
* ''':host''' select all shadow hosts, which are non-shadow elements, hosting at least one shadow elements.
 +
* ''':host(selector)''' select all shadow hosts matching the additional selector given.
 +
* '''::shadow''' select all shadow roots, which are shadow elements, hosted by a non-shadow element.
 +
 +
<source lang="xml">
 +
<div id="host">
 +
<apply id="root" dynamicValue="true"><!-- set dynamicValue="true" to avoid being removed after render -->
 +
<if id="if1" test="@load(vm.showLabel)"><!-- using @load will also prevent this element from being removed -->
 +
<label id="lb1" value="some text here" />
 +
</if>
 +
<if id="if2" test="false"><!-- no dynamicValue or data binding expression, will be removed after render -->
 +
<label id="lb2" value="some more text here" /><!-- will not render because the if test equals false -->
 +
</if>
 +
</apply>
 +
</div>
 +
</source>
 +
 +
Here are some examples of using shadow selector with the above zul
 +
 +
<source lang="java">
 +
@Wire(":host") // wire to the div with id "host", as it is the only shadow host
 +
@Wire(":host(#div2)") // wire to nothing, no shadow host with the id "div2" exists
 +
@Wire("::shadow") // wire to the apply with id "root", as it is the only shadow root
 +
@Wire(":host if") // wire to nothing, cannot select from non-shadow(Div) into shadow(If) without using ::shadow
 +
@Wire(":host::shadow if") // wire to if with the id "if1", as "if2" will be removed after render, thus cannot be selected
 +
@Wire(":host::shadow if label") // wire to nothing, cannot select from shadow(If) to non-shadow(Label)
 +
@Wire(":host label") // wire to "lb1", as the host(Div) and the first label are non-shadow elements
 +
@Wire("#host::shadow#root #if1") // wire to if with the id "if1", with performance boost
 +
</source>
  
 
&nbsp;
 
&nbsp;
Line 211: Line 244:
  
 
&nbsp;
 
&nbsp;
 +
 
=Wiring Sequence=
 
=Wiring Sequence=
 
While extending from <javadoc>org.zkoss.zk.ui.select.SelectorComposer</javadoc>, the fields and methods with the proper annotations will be wired automatically. Here is the sequence of wiring:
 
While extending from <javadoc>org.zkoss.zk.ui.select.SelectorComposer</javadoc>, the fields and methods with the proper annotations will be wired automatically. Here is the sequence of wiring:

Revision as of 09:17, 18 January 2016

Wire Components

In SelectorComposer, when you specify a @Wire annotation on a field or setter method, the SelectorComposer will automatically find the component and assign it to the field or pass it into the setter method.

You can either give a string value, which is interpret as a component selector, as the matching criteria for wiring, or leave it empty to wire by component id. For example,

ZUL:

<window apply="foo.MyComposer">
	<textbox />
	<button id="btn" />
</window>

Controller:

	@Wire("window > textbox")
	Textbox tb; // wire to the first textbox whose parent is a window
	@Wire
	Button btn; // wire to the button with id "btn"


 

CSS3-like Selectors

The string value in @Wire annotation is a component selector, which shares an analogous syntax of CSS3 selector. The selector specifies matching criteria against the component tree under the component which applies to this composer.

Given a selector in @Wire annotation, the SelectorComposer will wire a field to the component of the first match (in a depth-first-search sense) if the data type of the field is a subtype of Component. Alternatively, if the field type is subtype of Collection, it will wire to an instance of Collection containing all the matched components.

The syntax element of selectors are described as the following:

Type

The component type as in ZUML definition, case insensitive.

	@Wire("button")
	Button btn; // wire to the first button.

Combinator

Combinator constraints the relative position of components.

	@Wire("window button")
	Button btn0; // wire to the first button who has an ancestor window
	@Wire("window > button") // ">" refers to child
	Button btn1; // wire to the first button whose parent is a window
	@Wire("window + button") // "+" refers to adjacent sibling (next sibling)
	Button btn2; // wire to the first button whose previous sibling is a window
	@Wire("window ~ button") // "~" refers to general sibling
	Button btn3; // wire to the first button who has an older sibling window

You can have any number of levels of combinators:

	@Wire("window label + button")
	Button btn4; // wire to the first button whose previous sibling is a label with an ancestor window.

ID

The component id.

	@Wire("label#lb")
	Label label; // wire to the first label of id "lb" in the same id space of root component
	@Wire("#btn")
	Button btn; // wire to the first component of id "btn", if not a Button, an exception will be thrown.

Unlike CSS3, the id only refer to the component in the same IdSpace of the previous level or root component. For example, given zul

<window apply="foo.MyComposer">
	<div>
		<window id="win">
			<div>
				<button id="btn" /><!-- button 1 -->
				<textbox id="tb" /><!-- textbox 1 -->
			</div>
		</window>
		<button id="btn" /><!-- button 2 -->
	</div>
</window>
	@Wire("#btn")
	Button btnA; // wire to button 2
	@Wire("#win #btn")
	Button btnB; // wire to button 1
	@Wire("#win + #btn")
	Button btnC; // wire to button 2
	@Wire("#tb")
	Textbox tbA; // fails, as there is no textbox of id "tb" 
	             // in the id space of the root window (who applies to the composer).
	@Wire("#win #tb")
	Textbox tbB; // wire to textbox 1

Class

The sclass of component. For example,

<window apply="foo.MyComposer">
	<div>
		<button /><!-- button 1 -->
	</div>
	<span sclass="myclass">
		<button /><!-- button 2 -->
	</span>
	<div sclass="myclass">
		<button /><!-- button 3 -->
	</div>
</window>
	@Wire(".myclass button")
	Button btnA; // wire to button 2
	@Wire("div.myclass button")
	Button btnB; // wire to button 3

Attribute

The attributes on components, which means the value obtained from calling corresponding getter method on the component.

  • Note: [id="myid"] does not restrict id space like #myid does, so they are not equivalent.
	@Wire("button[label='submit']")
	Button btn; // wire to the first button whose getLabel() call returns "submit"

Pseudo Class

A pseudo class is a custom criterion on a component. There are a few default pseudo classes available:

	@Wire("div:root") // matches only the root component
	@Wire("div:first-child") // matches if the component is the first child among its siblings
	@Wire("div:last-child") // matches if the component is the last child among its siblings
	@Wire("div:only-child") // matches if the component is the only child of its parent
	@Wire("div:empty") // matches if the component has no child
	@Wire("div:nth-child(3)") // matches if the component is the 3rd child of its parent
	@Wire("div:nth-child(even)") // matches if the component is an even child of its parent
	@Wire("div:nth-last-child(3)") // matches if the component is the last 3rd child of its parent
	@Wire("div:nth-last-child(even)") // matches if the component is an even child of its parent, counting from the end

The nth-child and nth-last-child pseudo classes parameter can also take a pattern, which follows CSS3 specification.

Asterisk

Asterisk simply matches anything. It is more useful when working with combinators:

	@Wire("*")
	Component rt; // wire to any component first met, which is the root.
	@Wire("window#win > * > textbox")
	Textbox textbox; // wire to the first grandchild textbox of the window with id "win"
	@Wire("window#win + * + textbox")
	Textbox textbox; // wire to the second next sibling textbox of the window with id "win"

Multiple Selectors

Multiple selectors separated by comma refers to an OR condition. For example,

	@Wire("grid, listbox, tree")
	MeshElement mesh; // wire to the first grid, listbox or tree component
	@Wire("#win timebox, #win datebox")
	InputElement input; // wire to the first timebox or datebox under window with id "win"

Shadow Selectors

Shadow selectors can only be used to select shadow related elements.

One pesudo class :host and one pseudo element ::shadow has been added.

  • :host select all shadow hosts, which are non-shadow elements, hosting at least one shadow elements.
  • :host(selector) select all shadow hosts matching the additional selector given.
  • ::shadow select all shadow roots, which are shadow elements, hosted by a non-shadow element.
	<div id="host">
		<apply id="root" dynamicValue="true"><!-- set dynamicValue="true" to avoid being removed after render -->
			<if id="if1" test="@load(vm.showLabel)"><!-- using @load will also prevent this element from being removed -->
				<label id="lb1" value="some text here" />
			</if>
			<if id="if2" test="false"><!-- no dynamicValue or data binding expression, will be removed after render -->
				<label id="lb2" value="some more text here" /><!-- will not render because the if test equals false -->
			</if>
		</apply>
	</div>

Here are some examples of using shadow selector with the above zul

	@Wire(":host") // wire to the div with id "host", as it is the only shadow host
	@Wire(":host(#div2)") // wire to nothing, no shadow host with the id "div2" exists
	@Wire("::shadow") // wire to the apply with id "root", as it is the only shadow root
	@Wire(":host if") // wire to nothing, cannot select from non-shadow(Div) into shadow(If) without using ::shadow
	@Wire(":host::shadow if") // wire to if with the id "if1", as "if2" will be removed after render, thus cannot be selected
	@Wire(":host::shadow if label") // wire to nothing, cannot select from shadow(If) to non-shadow(Label)
	@Wire(":host label") // wire to "lb1", as the host(Div) and the first label are non-shadow elements
	@Wire("#host::shadow#root #if1") // wire to if with the id "if1", with performance boost

 

Wiring by Method

You can either put the @Wire annotation on field or method. In the latter case, it is equivalent to call the method with the matched component as the parameter. This feature allows a more delicate control on handling auto-wires.

	@Wire("grid#users")
	private void initUserGrid(Grid grid) {
		// ... your own handling
	}

In the example above, the SelectorComposer will find the grid of id "users" and call initUserGrid with the grid as parameter.

  • If the method is static or has wrong signature (more than one parameter), an exception will be thrown.
  • Wiring by method requires a selector on @Wire annotation, otherwise an exception will be thrown.
  • If the component is not found, the method is still called, but with null value passed in.
  • Do not confuse @Wire with @Listen, while the latter wires to events.


 

Wiring a Collection

You can also wire all matched components to a Collection field or by method, if the field is of Collection type or the method takes a Collection as the parameter.

  • If the field starts null or uninitialized or wiring by method, SelectorComposer will try to construct an appropriate instance and assign to the field or pass to method call.
  • If the field starts with an instance of Collection already, the collection will be cleared and filled with matched components.
  • If it wires by method and the selector matches no components, an empty collection will be passed into the method call.
	@Wire("textbox")
	List<Textbox> boxes; // wire to an ArrayList containing all matched textboxes
	@Wire("button")
	Set<Button> buttons; // wire to a HashSet containing all matched buttons
	@Wire("grid#users row")
	List<Row> rows = new LinkedList<Row>(); // the LinkedList will be filled with matched row components.
	@Wire("panel")
	public void initPanels(List<Panel> panels) {
		// ...
	}


 

Wiring Sequence

While extending from SelectorComposer, the fields and methods with the proper annotations will be wired automatically. Here is the sequence of wiring:

  • Before onCreate event of the component which applies to the composer, the SelectorComposer will attempt to wire the null fields and methods again, for some of the components might have been generated after doAfterCompose() call.


 

Performance Tips

The selector utility is implemented by a mixed strategy. In a selector sequence, the first few levels with id specified are handled by Component.getFellow(Component), and the rest are covered by depth first search (DFS). In brief, the more ids you specify in the first few levels of a selector string, the more boost you can obtain in component finding. For example,

	@Wire("#win #hl > #btn") // fast, as it is entirely handled by getFellow()

	@Wire("window hlayout > button") // slower, entirely handled by DFS

	@Wire("#win hlayout > button") // first level is handled by getFellow(), other handled by DFS

	@Wire("window #hl > #btn") // slower, as the first level has no id, all levels are handled by DFS
  • Note: specifying id via attribute (for instance, [id='myid']) does not lead to the same performance boost.

In the case of multiple selectors, only the first few identical levels with ids enjoy the performance gain.

	@Wire("#win #hl > button, #win #hl > toolbarbutton") 
	// the first two levels have boost

	@Wire("#win #hl > #btn, #win #hl > #toolbtn")
	// the first two levels have boost

	@Wire("#win + #hl > #btn, #win #hl > #btn") 
	// only the first level has boost, as they differ in the first combinator

	@Wire("#win hlayout > #btn, #win hlayout > #toolbtn") 
	// only the first level has boost, as the second level has no id specified

In brief, it is recommended to specify id in selector when you have a large component tree. If possible, you can specify id on all levels, which maximize out the performance gain from the algorithm.


 

Version History

Last Update : 2016/01/18


Version Date Content
6.0.0 February 2012 @Wire was introduced.



Last Update : 2016/01/18

Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.