Wire Components

From Documentation

Wire Components

In a controller that extends SelectorComposer, when you specify a @Wire annotation on a field or setter method, the SelectorComposer will automatically find the target component and assign it to the field or pass it into the setter method. It only finds the target component among the applied component and its child components.

Wire with ID selector by Default

You can either specify component selector syntax, as the matching criteria for wiring, or leave it empty to wire by component id (default). For example,

ZUL:

<window apply="foo.MyComposer">
	<textbox />
	<button id="btn" />
</window>
  • For this case, MyComposer can only wire <window> and its child components.

Controller:

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

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 a subtype of Collection, it will wire to an instance of Collection containing all the matched components.

The syntax elements 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 the 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 refers 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 the 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 parameters 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 commas refer 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

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

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

  • :host select all shadow hosts, which are non-shadow elements, hosting at least one shadow element.
  • :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 selectors 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 a 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 a 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 it to the field or pass it to a 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 ids 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 to maximize the performance gain from the algorithm.


 




Last Update : 2024/02/05

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