6 Models and Binding - Reference Documentation
Authors: Andres Almiray
Version: 1.2.0
6 Models and Binding
This section describe models and all binding options.6.1 Models
Models are very simple in nature. Their responsibility is to hold data that can be used by both Controller and View to communicate with each other. In other words, Models are not equivalent to domain classes.Models can be observable by means of the @Bindable AST Transformation. This actually simplifies setting up bindings so that changes in the UI can automatically be sent to model properties and vice versa.@Bindable will inject ajava.beans.PropertyChangeSupport
field and all methods required to make the model an observable class. It will also make sure that a PropertyChangeEvent
is fired for each observable property whenever said property changes value.The following is a list of all methods added by @Bindable
void addPropertyChangeListener(PropertyChangeListener listener)
void addPropertyChangeListener(String propertyName, PropertyChangeListener listener)
void removePropertyChangeListener(PropertyChangeListener listener)
void removePropertyChangeListener(String propertyName, PropertyChangeListener listener)
PropertyChangeListener[] getPropertyChangeListeners()
PropertyChangeListener[] getPropertyChangeListeners(String propertyName)
void firePropertyChange(String propertyName, Object oldValue, Object newValue)
void addVetoableChangeListener(VetoableChangeListener listener)
void addVetoableChangeListener(String propertyName, VetoableChangeListener listener)
void removeVetoableChangeListener(VetoableChangeListener listener)
void removeVetoableChangeListener(String propertyName, VetoableChangeListener listener)
VetoableChangeListener[] getVetoableChangeListeners()
VetoableChangeListener[] getVetoableChangeListeners(String propertyName)
void fireVetoableChange(String propertyName, Object oldValue, Object newValue)
PropertyChangeListeners
without so much effort. The following codeimport griffon.transform.PropertyListener import groovy.beans.Bindable@PropertyListener(snoopAll) class MyModel { def controller @Bindable String name @Bindable @PropertyListener({controller.someAction(it)}) String lastname def snoopAll = { evt -> … } }
import groovy.beans.Bindable import java.beans.PropertyChangeListenerclass MyModel { def controller @Bindable String name @Bindable String lastname def snoopAll = { evt -> … } MyModel() { addPropertyChangeListener(snoopAll as PropertyChangeListener) addPropertyChangeListener('lastname', { controller.someAction(it) } as PropertyChangeListener) } }
- in-place definition of a closure
- reference of a closure property defined in the same class
- a List of any of the previous two
6.2 Binding
Binding in Griffon is achieved by leveraging Java Beans'PropertyChangeEvent
and their related classes, thus binding will work with any class that fires this type of event, regardless of its usage of @Bindable or not.
6.2.1 Syntax
These are the three options for writing a binding using thebind
node
- Long
bean1.prop1
to bean2.prop2
bind(source: bean1, sourceProperty: 'prop1', target: bean2, targetProperty: 'prop2')
- Contextual
bean1.prop1
to bean2.prop2
- Implicit source
bean(bean1, prop1: bind(target: bean2, targetProperty: 'prop2'))
- Implicit target
bean(bean2, prop2: bind(source: bean1, sourceProperty: 'prop1'))
sourceProperty:
or targetProperty:
can be omitted; the bind node's value will become the property name, in other wordsbean(bean1, prop1: bind('prop2', target: bean2))
- Short
bean(bean2, prop2: bind{ bean1.prop1 })
6.2.2 Additional Properties
The following properties can be used with either the long or contextual binding syntax- mutual:
true
then a bidirectional binding will be created instead.import groovy.beans.Bindable import groovy.swing.SwingBuilderclass MyModel { @Bindable String value }def model = new MyModel() def swing = new SwingBuilder() swing.edt { frame(title: 'Binding', pack: true, visible: true) { gridLayout(cols: 2, rows: 3) label 'Normal' textField(columns: 20, text: bind('value', target: model)) label 'Bidirectional' textField(columns: 20, text: bind('value', target: model, mutual: true)) label 'Model' textField(columns: 20, text: bind('value', source: model)) } }
- converter:
import groovy.beans.Bindable import groovy.swing.SwingBuilderclass MyModel { @Bindable String value }def convertValue = { val -> '*' * val?.size() }def model = new MyModel() def swing = new SwingBuilder() swing.edt { frame(title: 'Binding', pack: true, visible: true) { gridLayout(cols: 2, rows: 3) label 'Normal' textField(columns: 20, text: bind('value', target: model)) label 'Converter' textField(columns: 20, text: bind('value', target: model, converter: convertValue)) label 'Model' textField(columns: 20, text: bind('value', source: model)) } }
- reverseConverter:
- validator:
false
or null
.import groovy.beans.Bindable import groovy.swing.SwingBuilderclass MyModel { @Bindable String value }def isNumber = { val -> if(!val) return true try { Double.parseDouble(val) } catch(NumberFormatException e) { false } }def model = new MyModel() def swing = new SwingBuilder() swing.edt { frame(title: 'Binding', pack: true, visible: true) { gridLayout(cols: 2, rows: 3) label 'Normal' textField(columns: 20, text: bind('value', target: model)) label 'Converter' textField(columns: 20, text: bind('value', target: model, validator: isNumber)) label 'Model' textField(columns: 20, text: bind('value', source: model)) } }
This type of validation is not suitable for semantic validation (a.k.a. constraints in domain classes). You would want to have a look at the Validation plugin.
- sourceEvent:
PropertyChangeEvent
.
- sourceValue:
sourceevent
.import groovy.beans.Bindable import groovy.swing.SwingBuilderclass MyModel { @Bindable String value }def model = new MyModel() def swing = new SwingBuilder() swing.edt { frame(title: 'Binding', pack: true, visible: true) { gridLayout(cols: 2, rows: 3) label 'Text' textField(columns: 20, id: 'tf1') label 'Trigger' button('Copy Text', id: 'bt1') bind(source: bt1, sourceEvent: 'actionPerformed', sourceValue: {tf1.text}, target: model, targetProperty: 'value') label 'Model' textField(columns: 20, text: bind('value', source: model)) } }
ActionEvent
s pumped by the button.