zk

How to Set View Model Class Programmatically when Creating Components


I am experimenting with ZK framework and look for ways to load a zul-defined view programmatically via a utility class that uses Executions to load a view definition programmatically.

I do however fail to find a way to set the view model class programmatically, which would be very convenient and would allow some very simple way of reusing a view with different view model classes.

I.e. the code to load the view looks like this:

    public static Component loadComponent(Class<?> modelClz, String zulFile, Component parent, Map<String,Object> params) {
        Execution exec = Executions.getCurrent();
        PageDefinition page = exec.getPageDefinitionDirectly(
            new InputStreamReader(modelClz.getResourceAsStream(zulFile)), 
            null
        );
        return exec.createComponents(
            page,
            // no (previous parent)
            parent,
            params
        );
    }

I thought about "forcing" the view model by setting annotations programmatically on the top component info from the page definition, like so:

    public static Component loadComponent(Class<?> modelClz, String zulFile, Component parent, Map<String,Object> params) {
        Execution exec = Executions.getCurrent();
        PageDefinition page = exec.getPageDefinitionDirectly(
            new InputStreamReader(modelClz.getResourceAsStream(zulFile)), 
            null
        );
        if (!page.getChildren().isEmpty()) {
            ComponentInfo top = (ComponentInfo) page.getChildren().get(0);
            AnnotationMap annotationMap = top.getAnnotationMap();
            String viewModel = "viewModel";
            if (annotationMap==null || !annotationMap.getAnnotatedProperties().contains(viewModel)) {
                // no view model set on top declaration, 
                // force ours
                Map<String,String[]> id = new HashMap<>();
                id.put(null,  new String[]{"vm"});
                top.addAnnotation("viewModel","id",id, null);
                Map<String,String[]> init = new HashMap<>();
                init.put(null,  new String[]{String.format("%s", modelClz.getName())});
                top.addAnnotation("viewModel","init",init, null);
                top.enableBindingAnnotation();
            }
        }
        return exec.createComponents(
            page,
            // no (previous parent)
            parent,
            params
        );
    }

This did not work however. Maybe it was too late in the process. Or there is some really simple way of doing this but I missed it. Or maybe I should "apply" some BindComposer, but I am not sure how to do that.

Any helpful idea would be great!


Solution

  • Just to make sure I've understood:

    Does that sound correct?

    If that's the case, you can make this way simpler The attribute can be written as viewModel="@id('yourVmId')@init(aReferenceToAnAlreadyInstantiatedObject)".

    Important note here: Notice that I have NOT put quotes around the object in the @init declaration. I'm passing an actual object, not a string containing a reference to a class to be instantiated.

    When you invoke execution.createComponents() you may pass a map<String, Object> of arguments to the created page. You can then use the name of the relevant passed object when you create bind the VM.

    have a look at this fiddle (bit rough, but it should make sense): https://zkfiddle.org/sample/2jij246/4-Passing-an-object-through-createComponents-as-VM#source-2

        HashMap<String, Object> args = new HashMap<String, Object>();
        args.put("passedViewModel", new GenericVmClass("some value in the passed VM here"));
        Executions.createComponents("./fragment.zul", e.getTarget().getPage(),null, args);  
    

    FYI if you are using ZK shadow-elements, you can also pass that object to the fragment from an apply with a source in pure MVVM pattern. The <apply> shadow element for example can pass objects to the created content with a variable name, and you can use that variable name when initializing the VM.

    Regarding BindComposer: You need to instantiate BindComposer up to ZK 7.X

    In ZK 8.X and above, BindComposer will be instantiated automatically when you use the viewModel="..." attribute on a ZK component.