0

Create component from ViewModel

asked 2022-09-15 19:52:03 +0800

ImanolRP gravatar image ImanolRP
150 4

Hello everyone, ill try to be concise with my problem/doubt.

I have a ViewModel wich with one input param recover info from DB and mount dinamically grids with client info. I need move this VM to a commons .jar for use it on other projects of the same client.

Right now im reusing that VM with a "include" tag and the SRC + input param of vm's zul.

PROBLEM/QUESTION

Its possible declare on lang.xml that VM for use it with custom tag? Its for make it more legible in the future and eaisly difference between other "include" tag.

EXAMPLE

  • NOW

<include src="@load(vm.srcVM_1)" inputParam="@load(vm.inputParam_1)">

<include src="@load(vm.srcVM_2)" inputParam="@load(vm.inputParam_2)">

<include src="@load(vm.srcVM_1)" inputParam="@load(vm.inputParam_1)">

  • EXPECTED

<include src="@load(vm.srcVM_1)" inputParam="@load(vm.inputParam_1)">

<custom-component inputParam="@load(vm.inputParam_2)">

<include src="@load(vm.srcVM_1)" inputParam="@load(vm.inputParam_1)">

EXTRA INFO

I was reading about how create custom components but only find examples for create it extendieng of GenericForwardComposer and SelectorComposer but seems like that is for MVC architecture cant fing anything for MVVM.

delete flag offensive retag edit

5 Answers

Sort by ยป oldest newest most voted
0

answered 2022-09-16 18:56:22 +0800

ImanolRP gravatar image ImanolRP
150 4

updated 2022-09-16 19:01:41 +0800

UPDATE of the investigation (for help to resolve the doubt)

In the last hours i was searching about how create custom components with only an ViewModel without succes. In the only place i have found anything remotely similar to my case its in the oficial wiki of zk. The page has been updated: 2022/08/08 then sould be "reliable".

https://www.zkoss.org/wiki/ZKClient-sideReference/Language_Definition/component

The example im talking about its on the point "9.template-uri > 9.2Define a Custom Component"

That point sais you can define on lang-addon a component like:

<component>
    <component-name>megamenu</component-name>
    <template-uri>/uiComposing/megaMenuParameterized.zul</template-uri>
</component>

And then invoque it from the zul with the custom tag:

<megamenu>

I have copy/pasted that example on my own project creating a simple zul (not provided by the wiki):

megamenu.zul

<zk>
    <label value="megamenu"/>
</zk>

But when i'm deploying fails reading the lang-addon in that component and don't recognize me that tag on .zul files.

Server deploying logs

ERROR [org.zkoss.zk.ui.metainfo.DefinitionLoaders] (ServerService Thread Pool -- 73) Failed to load addon: file:/P:/jboss-eap-7.1.0/standalone/tmp/vfs/temp/temp391cd92af805a4c7/content-d31b2ec3d5f014f7/WEB-INF/lang-addon.xml: java.lang.NullPointerException
        at org.zkoss.zk.ui.metainfo.DefinitionLoaders.parseLang(DefinitionLoaders.java:446)
        at org.zkoss.zk.ui.metainfo.DefinitionLoaders.loadLang(DefinitionLoaders.java:241)

Anyone have tryed this example or others like this with succes?

link publish delete flag offensive edit
0

answered 2022-09-20 00:15:30 +0800

MDuchemin gravatar image MDuchemin
2298 1 5
ZK Team

Hey there,

If you want to do this type of templating in MVVM, I'd suggest extending macrocomponent instead of declaring a custom comp directly.

there is a good sample here: http://books.zkoss.org/zk-mvvm-book/8.0/advanced/bindingannotationforacustom_component.html

The only tricky part is to keep the direction of the data flows you have in mind. The macro component has getters and setters, which can be accessed by your databinding (@bind, @load, @save) and it can send events which can be used to trigger @command on your VM

HOWEVER You need to declare these fields as accessible by the binder. See the "Declare Data Binding in Language Addon XML" in the doc above.

Have a look and let me know if you get stuck

link publish delete flag offensive edit

Comments

that link above talks above the MVVM specific macrocomponent stuff. For the general doc, have a look here instead: https://www.zkoss.org/wiki/ZKDeveloper'sReference/UIComposing/MacroComponent#DefineMacroComponent

MDuchemin ( 2022-09-20 00:17:07 +0800 )edit

The main point is, the macrocomponent acts as an interface between the databinder on your outside page, and the children that you instantiate from the macrocomponent template

MDuchemin ( 2022-09-20 00:18:01 +0800 )edit

it pre-solves a lot of binding questions. You can implement your own component yourself, but you will still need to declare the lang-addon for save / load ACCESS (no need to declare bind, bind is "both" save and load)

MDuchemin ( 2022-09-20 00:18:15 +0800 )edit

First of all, thank you for your answer.

This urls resolve some big doubts but generate other doubts/problems, specifically two.

I'll put both like diferent anwer of the question for facility the answer of both individually in their own comments section.

ImanolRP ( 2022-09-20 20:49:05 +0800 )edit
0

answered 2022-09-20 21:13:22 +0800

ImanolRP gravatar image ImanolRP
150 4

First problem

How acces to ViewModelComponent data and how resolve "UiException: Not unique in the ID space of <Window>."

First of all i have replicated the case of the first example with the point of view of a view model. But that generate me a problem accesing the data of the variables.

The declaration on the lang-addon its the same but the zul and class are a bit different.

test-component.zul

<zk>
    <label value="@load(test.inputParam)"/>
</zk>

TestComponent.java

public class TestComponent extends HtmlMacroComponent {

  private String inputParam;

  public TestComponent() {
    setMacroURI("~./zul/testcomponent/test-component.zul");
    setId("test");
  }

  public void setInputParam(String inputParam) {
    this.inputParam = inputParam;
  }

  public String getInputParam() {
    return inputParam;
  }

}

With that aproach i cant acces to the value of inputParam without adding an ID to the component because we loose the viewModel attribute on the zul declaration. On the example we have "test" like the component ID.

This sounds me like the programatically translation of "@id('vm') @init('org.example.vm.TestVM')", drama becomes when i try to use 2 times the same component in the same .zul and get the error:

UiException: Not unique in the ID space of <Window>

This a normal error of duplicate ID and i understand why occurs but, how can solve it? can i create a dinamic id for use on the zul? have tryed implementing the interface IdSpace without succes...

link publish delete flag offensive edit
0

answered 2022-09-20 21:54:14 +0800

ImanolRP gravatar image ImanolRP
150 4

Second problem

Command/Event binding inside templates.

For this case i need a bit more complex example for illustri the problem, ill explain the use case evolution for make more easy to undertand or for find erros on the thinking evolution.

The problem started when the @Commands usually used for trigger events inside the "VM", are triggered on the parent "VM" and not inside the ViewModelComponent. This can be easily resolved changing @Command for @Listen annotations pointing by id or class deppending the case.

The problem is, that @Listen annotations seems like are builded before the template generation. And if we have the use case:

test-component.zul

<zk>
    <button sclass="btnCommandTest" label="btn.commandTest"/>
</zk>

TestComponent.java

@Listen("onClick = .btnCommandTest")
public void btnCommandTest() {
  // OnClick
}

The event are listen without problems, but if we have this other use case:

test-component.zul

<grid model="@load(test.model)"
      emptyMessage="label.no_results"
      hflex="1">
    <!-- COLUMNS -->
    <columns children="@load(test.columnList)">
        <template name="children" var="column">
            <column label="@load(column.columnLabel)"/>
        </template>
    </columns>
    <!-- AUXHEAD -->
    <auxhead children="@load(test.columnList)">
        <template name="children" var="column">
            <auxheader>
                <hlayout hflex="1" spacing="0">
                    <button iconSclass="z-icon-search-plus"
                            sclass="btnCommandTest"/>
                    <textbox hflex="1"
                             value="@bind(column.filter.value)"/>
                </hlayout>
            </auxheader>
        </template>
    </auxhead>
    <!-- ROWS -->
    <rows>
        <template name="model" var="item">
            <row children="@load(test.columnList)">
                <template name="children" var="column">
                    <textbox hflex="1" readonly="true"
                             value="@load(item.params[column.columnOrder])"
                             tooltiptext="@load(item.params[column.columnOrder])"/>
                </template>
            </row>
        </template>
    </rows>
</grid>

The buttons inside the Grid > AuxHead > Template > AuxHeader never trigger the same component method.

I have tryed with the AfterForward events without succes...

link publish delete flag offensive edit
0

answered 2022-09-26 17:08:28 +0800

MDuchemin gravatar image MDuchemin
2298 1 5
ZK Team

Hey there, regarding your two problems:

1 - the idspace question:

An ID must be unique inside of an Idspace. An ID space is "all of the descendants of a space owner. If one of the descendant is a space owner themselves, then their own descendants belong to their own ID Space, not to the first ID space" https://www.zkoss.org/wiki/ZKDeveloper'sReference/UIComposing/IDSpace

In this case, the HtmlMacroComponent class that you need to extend is automatically an IDspace. This means that you can have components holding IDs inside of your MacroComponents, and they will not collide with other instances of the same components located in other instances of the same macroComponent in the same page.

Consider a macro component "myTextInput" defined by the following zul:

<div>
    <textbox id="textInput"/>
</div>

If you instantiate it multiple time in a page, such as:

<zk>
    <myTextInput value="..."/>
    <myTextInput value="..."/>
<zk>

Essentially, you are writing:

<zk>
    <div> <!-- the macroComponent root element which is just an extended DIV-->
        <div>
            <textbox id="textInput" value="..."/>
        </div>
    </div>
    <div> <!-- the macroComponent root element which is just an extended DIV-->
        <div>
            <textbox id="textInput" value="..."/>
        </div>
    </div>
</zk>

You can see that as a result, you have multiple components holding the same ID in the same page, HOWEVER this is not a problem, because your macroComponent root elements acts as IDspace owners, and they isolates the IDs located inside, making this a valid page.

<zk>
    <div> <!-- the macroComponent root element which is just an extended DIV-->
        <!-- ID space 1 start -->
        <div>
            <textbox id="textInput" value="..."/>
        </div>
        <!-- ID space 1 ends -->
    </div>
    <div> <!-- the macroComponent root element which is just an extended DIV-->
        <!-- ID space 2 start -->
        <div>
            <textbox id="textInput" value="..."/>
        </div>
        <!-- ID space 2 ends -->
    </div>
</zk>

Important note: See how the ID space is located INSIDE of the space owner, but the space owner itself is not in that space. That means that if you have multiple of your macroComponent inside the same parent space, you need to give them different IDs, otherwise you break the rule of no duplicated IDs per ID space. For example, this is not OK:

<zk>
    <myTextInput id="test" value="..."/>
    <myTextInput id="test" value="..."/>
<zk>

In your code, since you declared an ID during component constructor:

setId("test");

This cause every single instances of your component to have the same ID.

With all that said, the main question is why does your component needs an ID? :)

In general, in MVVM you are not supposed to lookup a component by ID or act on a component directly. In this pattern, the component is agnostic of the viewModel. The communication between them should only happen through value binding and events (which are passed as commands).

For example, instead of reading vm.myValue from inside of the component class, you would bind it in zul.

<mycomponent compvalue="@load(vm.myValue)"/>

As a result, the binder will call myComponent#setCompValue everytime a notifyChange on vm.myValue gets triggered. (and also during first binding when the component is created)

See here a sample of how the individual component and the VM communicate:

https://github.com/patchypanel/zkbooks/blob/master/developersreference/developersreference/src/main/java/org/zkoss/reference/developer/mvvm/advance/EditableLabelXml.java https://github.com/patchypanel/zkbooks/blob/5a365271f12d500f340dc8ad3c01ca3561d47585/developersreference/developersreference/src/main/webapp/advance/custom-component.zul

(it is using xml for binder declaration, but the logic would be the same with annotations: https://github.com/patchypanel/zkbooks/blob/5a365271f12d500f340dc8ad3c01ca3561d47585/developersreference/developersreference/src/main/resources/metainfo/zk/lang-addon.xml)

2 - Regarding the command / listen problem

There is a pattern shift here between the outer page using MVC and the MacroComponent content using MVC. Since the MacroComponent is designed to use MVC pattern, with event listeners and direct internal component access through wiring, the logic works differently inside and outside of the macro component.

In this case, you can consider that the MacroComponent itself actually acts as a MVC composer (all of the features @Listen, @Wire, etc are substantially similar).

This is not an issue in the normal case, since the macroComponent isolates its content from the rest of the page and acts as the intermediate during MVVM operations. In practice, that means the MacroComponent receiving a value change from the binder, then doing MVC calls to update its own descendants, etc.

In the case of listening to events on components generated by templates, that's something you can do in MVC as well :)

https://www.zkoss.org/wiki/ZKDeveloper'sReference/EventHandling/EventForwarding

Event forwarding is one way to do it. On the created component itself, you can define a forwarding event name and target. For example, in this case you would forward from the header button to grid onHeaderButtonClick event Then in the MacroComponent, you can listen to @Listen("onHeaderButtonClick=#myGrid") (assuming you gave the grid an ID). From there, the event you are receiving is a forwardEvent, which you can do event.getOrigin() to retrieve the original click event and the original target button.

Important side-note:

If you want to do MVVM all the way down (not doing a logic shift from inside and outside of the macroComponent), you can consider ZK EE zuti dependency's shadow elements.

For example the <apply> shadow elements is a functional upgrade to the <include> component, which lets you target specific templates of your choice and pass attributes / commands. http://books.zkoss.org/zk-mvvm-book/9.5/syntax/apply.html

It is in the EE package, but it might also be matching what you are trying to do.

link publish delete flag offensive edit
Your answer
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
2 followers

RSS

Stats

Asked: 2022-09-15 19:52:03 +0800

Seen: 15 times

Last updated: Sep 26

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