User Objects in the @Model

We often get questions about a specific limitation of our ViewModels: The classes generated by the @Model annotation don’t allow custom inheritance. This means they cannot subclass an existing class or implement an interface. And more important, also the @Properties of the class can only be primitives, Strings, Enums or @Models.

These questions often come up, when somebody is porting an application from Swing or JavaFX to DukeScript. So our first example will model a typical scenario: We have a menu bar in our existing application. Each menu item typically has some MenuAction assigned that will be called when the user selects it. Let’s assume that in our original application these MenuActions implement an interface:

public interface MenuAction {
    public void performAction();
}

In DukeScript we cannot add a MenuAction Property, so one way to deal with it would be to create a registry that keeps references to the actions:

@Model(className = "MenuItem", properties = {
        @Property(name = "name", type = String.class),
        @Property(name = "id", type = String.class)})
    public static class MenuItemVMD {

        @Function
        static void performAction(MenuItem data) {
            ActionManager.getMenuAction(data.getId()).performAction();
        }
    }

But that’s ugly. We don’t want to set up registries like that for everything and keep Actions and their ids in sync. A better way to deal with it is the “instance” attribute of the @Model annotation. It allows you to keep private state for the instance of your generated Model (MenuItem) in the class that defines it (MenuItemVMD):

@Model(className = "MenuItem", instance = true, properties = {
        @Property(name = "name", type = String.class)})
    public static class MenuItemVMD {

        private MenuAction action;
        
        @ModelOperation void setAction(MenuItem data, MenuAction action){
            this.action = action;
        }
        
        @Function void performAction(MenuItem data) {
            action.performAction();
        }
    }
}

Notice how we’re no longer using static methods. This is because now we create one instance of MenuItemVMD per MenuItem. So every instance of MenuItem can now store it’s private state in an instance of MenuItemVMD. The @ModelOperation annotation generates a new method in the MenuItem. Now you can set the MenuAction like this:

MenuItem open = new MenuItem(strValue);
open.setAction(new MenuAction() {
    @Override
    public void performAction() {
        doSomething();
    }
});

Our second example is a Tree structure. Let’s assume we have a ViewModel for that:

@Model(className = "TreeNode", instance = true, properties = {
    @Property(name = "children", type = TreeNode.class, array = true)
    , @Property(name = "name", type = String.class)
})
public class TreeNodeVMD {

    UserObject userObject;
    
    @ModelOperation void setUserObject(TreeNode data, UserObject userObject){
            this.userObject = userObject;
    }

    @ComputedProperty boolean isLeaf(List<File> children){
        return children.isEmpty();
    }
}

Again we can now add methods that act directly on the userObject without any indirection or mapping involved.