# 7. Views

Views are responsible for defining how the application looks like. View scripts are always executed in the context of an UberBuilder, which means that Views have access to all nodes, properties and methods contributed by builders configured in `Builder.groovy`.

Views can reference directly both the `model` and `controller` instances that belong directly to their own MVC group.

View scripts are where you would usually setup bindings with their corresponding model instances.

## 7.1 Views and Swing

Views are usually written as Groovy scripts that create the UI by composing elements using builder nodes. Griffon supports all nodes provided by SwingBuilder by default. A typical View looks like this

    package loginactions {
        action(id: 'loginAction',
            name: 'Login',
            enabled: bind{ model.enabled },
            closure: controller.login)
    }application(title: 'Some title', pack:true,
      locationByPlatform:true,
      iconImage: imageIcon('/griffon-icon-48x48.png').image,
      iconImages: [imageIcon('/griffon-icon-48x48.png').image,
                   imageIcon('/griffon-icon-32x32.png').image,
                   imageIcon('/griffon-icon-16x16.png').image]) {
        gridLayout(cols: 2, rows: 3)
        label 'Username:'
        textField columns: 20, text: bind('username', target: model)
        label 'Password:'
        passwordField columns: 20, text: bind('password', target: model)
        label ''
        button loginAction
    }

The resulting UI may look like this

It is pretty evident that changing layouts will greatly improve how this application looks. Additional nodes can be configured in `griffon-app/conf/Builder.groovy`, the Griffon runtime will make sure to setup the builder correctly. Here's an example with JideBuilder nodes used to setup a top banner. It also relies on MigLayout to arrange the components in a better way

    package loginimport java.awt.Coloractions {
        action(id: 'loginAction',
            name: 'Login',
            enabled: bind{ model.enabled },
            closure: controller.login)
    }application(title: 'Some title', pack:true,
      locationByPlatform:true,
      iconImage: imageIcon('/griffon-icon-48x48.png').image,
      iconImages: [imageIcon('/griffon-icon-48x48.png').image,
                   imageIcon('/griffon-icon-32x32.png').image,
                   imageIcon('/griffon-icon-16x16.png').image]) {
        migLayout(layoutConstraints: 'fill')    bannerPanel(constraints: 'span 2, growx, wrap',
          title: 'Login',
          subtitle: 'Please enter your credentials',
          titleIcon: imageIcon('/griffon-icon-48x48.png'),
          border: lineBorder(color: Color.BLACK, thickness: 1),
          subTitleColor: Color.WHITE,
          background: new Color(0,0,0,1),
          startColor: Color.WHITE,
          endColor: Color.BLACK,
          vertical: true)    label 'Username:', constraints: 'left'
        textField columns: 20, text: bind('username', target: model), constraints: 'wrap'
        label 'Password:', constraints: 'left'
        passwordField columns: 20, text: bind('password', target: model), constraints: 'wrap'
        button loginAction, constraints: 'span 2, right'
    }

You'll need to install 2 plugins if you intend to run this application: jide-builder and miglayout. Here's the rest of the application, first the model

    package loginimport groovy.beans.Bindable
    import griffon.transform.PropertyListener@PropertyListener(enabler)
    class LoginModel {
        @Bindable String username
        @Bindable String password
        @Bindable boolean enabled    private enabler = { evt ->
            if(evt.propertyName == 'enabled') return
            enabled = username && password
        }
    }

Then the controller

    package loginimport javax.swing.JOptionPaneclass LoginController {
        def model    def login = {
            JOptionPane.showMessageDialog(app.windowManager.windows[0],
                """
                   username = $model.username
                   password = $model.password
                """.stripIndent(14).toString())
        }
    }

There are many plugins that will contribute additional nodes that can be used on Views.

## 7.2 Special Nodes

The rule of thumb to find out the node name of a Swing class is this:

- drop the first `J` from the class name
- uncapitalize the next character

Examples

- `JButton` => `button`
- `JLabel` => `label`

This rules apply to all Swing classes available in the JDK. There are a few additional nodes that provide a special function, which will be explained next.

## 7.2.1 Application

Provided by: Griffon

This node defines a top level container depending on the current running mode. It it's `STANDALONE` or `WEBSTART` it will create a Window subclass according to the following rules:

- class name defined in `app.config.application.frameClass` (configured in `Application.groovy`)
- `JXFrame` if SwingX is available
- `JFrame` if all others fail

There's a slight change for the `APPLET` run mode, the container returned for the first invocation of the `application` node will be the applet itself, for all others the previous rules apply.

Of all the properties suggested by the default template you'll notice `iconImage` and `iconImages`. The first property is a standard property of `JFrame`. It's usually defines the icon to be displayed at the top of the frame (on platforms that support such setting). The second property (`iconImages`) is a Jdk6 addition to `java.awt.Window`. This property instructs the window to select the most appropriate icon according to platform preferences. Griffon ignores this setting if running in Jdk5. This property overrides the setting specified for `iconImage` if its supported in the current Jdk and platform.

## 7.2.2 Container

Provided by: SwingBuilder

This is a pass through node that accepts any UI component as value. This node allows nesting of child content. It's quite useful when what you need is to embed a custom component for which a node is not available, for example

    container(new MyCustomPanel()) {
        label 'Groovy is cool'
    }

## 7.2.3 Widget

Provided by: SwingBuilder

This is a pass through node that accepts any UI component as value. As opposed to `container`, this node does not allow nesting of child content. It's quite useful when what you need is to embed a custom component for which a node is not available, for example

    widget(new MyCustomDisplay(), title: 'Groovy') {

## 7.2.4 Bean

Provided by: SwingBuilder

This is a catch-all node, it allows you to set properties on any object using the builder syntax, for example setting up bindings on a model

    textField columns: 20, id: username
    bean(model, value: bind{ username.text })

The previous code is equivalent to

    textField columns: 20, text: bind('value', target: model)

## 7.2.5 Noparent

Provided by: SwingBuilder

Child nodes are always attached to their parents, there are times when you explicitly don't want that to happen. If that is the case then wrap those nodes with `noparent`

    panel {
        gridLayout(cols: 2, rows: 2)
        button('Click 1', id: b1')
        button('Click 2', id: b2')
        button('Click 3', id: b2')
        button('Click 4', id: b4')    // the following line will cause the buttons
        // to be reordered
        // bean(button1, text: 'Click 11')    noparent {
            // this is safe, buttons do not change places
            bean(button1, text: 'Click 11')
        }
    }

## 7.2.6 Root

Provided by: Griffon

Identifies the top level node of a secondary View script. View scripts are expected to return the top level node, however there may be times when further customizations prevent this from happening, for example wiring up a custom listener. When that happens the result has to be made explicit otherwise the script will return the wrong value. Using the `root()` node avoids forgetting this fact while also providing an alias for the node.

Secondary view script named "SampleSecondary"

    root(
        tree(id: 'mytree')
    )mytree.addTreeSelectionModel(new DefaultTreeSelectionModel() {
       …
    })

Primary view script named "SampleView"

    build(SampleSecondary)
    application(title: 'Sample') {
        borderLayout()
        label 'Options', constraints: NORTH
        widget root(SampleSecondary)
    }

This node accepts an additional parameter `name` that can be used to override the default alias assigned to the node. If you specify a value for this parameter when the node is built then you'll need to use it again to retrieve the node.