Data Binding

Data binding refers to the process of automatically populating or extracting data from a set of user interface elements. In Pivot, data binding is driven primarily by two methods of the Component class: load() and store(). Each method takes an Object argument called the "context". The context is either an instance of a Java bean class or an instance of org.apache.pivot.collections.Dictionary. Calling load() causes data from the context to be "loaded" into the component; calling store() performs the reverse operation and "stores" data from the component into the context. A third method, clear() allows a caller to reset any bindings.

Components that support data binding provide "key" properties that allow a caller to associate a property value with a value in the bind context. For example, the Label class provides a "textKey" property that maps the label's "text" property to a value provided by the context.

The following application demonstrates data binding. It allows the user to load a form with address data either from a JSON file or from a Java bean object, as well as clear the form. Note that binding to a Java bean is accomplished by wrapping the bean in an instance of org.apache.pivot.beans.BeanAdapter before passing it to the load() method:

The BXML simply sets up the form structure and the bind keys:

            
            <databinding:DataBinding title="Data Binding" maximized="true"
                xmlns:bxml="http://pivot.apache.org/bxml"
                xmlns:databinding="org.apache.pivot.tutorials.databinding"
                xmlns="org.apache.pivot.wtk">
                <Border styles="{padding:6}">
                    <BoxPane orientation="vertical" styles="{spacing:10, fill:true}">
                        <Form bxml:id="form" styles="{showFlagIcons:false}">
                            <Form.Section>
                                <Label bxml:id="sourceLabel" Form.label="Source" styles="{font:{italic:true}}"/>

                                <Label Form.label="ID" textKey="id"/>
                                <TextInput Form.label="Name" textKey="name"/>

                                <BoxPane Form.label="Address" orientation="vertical">
                                    <TextInput textKey="address.street" prompt="Street"/>
                                    <BoxPane>
                                        <TextInput textKey="address.city" prompt="City"/>
                                        <TextInput textKey="address.state" textSize="6" prompt="State"/>
                                        <TextInput textKey="address.zip" textSize="6" prompt="Zip"/>
                                    </BoxPane>
                                </BoxPane>

                                <TextInput Form.label="Phone" textKey="phoneNumber"/>
                                <TextInput Form.label="Email" textKey="emailAddress"/>

                                <BoxPane Form.label="IM">
                                    <TextInput textKey="imAccount.id"/>
                                    <ListButton selectedItemKey="imAccount.type"
                                        listData="['AIM', 'Jabber', 'Yahoo']"/>
                                </BoxPane>
                            </Form.Section>
                        </Form>

                        <Separator/>

                        <BoxPane styles="{horizontalAlignment:'right'}">
                            <PushButton bxml:id="loadJSONButton" buttonData="Load JSON"/>
                            <PushButton bxml:id="loadJavaButton" buttonData="Load Java"/>
                            <PushButton bxml:id="clearButton" buttonData="Clear"/>
                        </BoxPane>
                    </BoxPane>
                </Border>
            </databinding:DataBinding>
            
        

The windows's initialize() method defines the button press listeners that load or clear the form:

            
            package org.apache.pivot.tutorials.databinding;

            import java.io.InputStream;
            import java.net.URL;

            import org.apache.pivot.beans.BeanAdapter;
            import org.apache.pivot.beans.Bindable;
            import org.apache.pivot.collections.Map;
            import org.apache.pivot.json.JSONSerializer;
            import org.apache.pivot.util.Resources;
            import org.apache.pivot.wtk.Button;
            import org.apache.pivot.wtk.ButtonPressListener;
            import org.apache.pivot.wtk.Form;
            import org.apache.pivot.wtk.Label;
            import org.apache.pivot.wtk.PushButton;
            import org.apache.pivot.wtk.Window;

            public class DataBinding extends Window implements Bindable {
                private Form form = null;
                private PushButton loadJavaButton = null;
                private PushButton loadJSONButton = null;
                private PushButton clearButton = null;
                private Label sourceLabel = null;

                private static final Contact CONTACT = new Contact("101", "Joe User",
                    new Address("123 Main St.", "Cambridge", "MA", "02142"),
                    "(617) 555-1234", "joe_user@foo.com",
                    new IMAccount("juser1234", "AIM"));

                @Override
                public void initialize(Map<String, Object> namespace, URL location, Resources resources) {
                    form = (Form)namespace.get("form");
                    loadJavaButton = (PushButton)namespace.get("loadJavaButton");
                    loadJSONButton = (PushButton)namespace.get("loadJSONButton");
                    clearButton = (PushButton)namespace.get("clearButton");
                    sourceLabel = (Label)namespace.get("sourceLabel");

                    loadJavaButton.getButtonPressListeners().add(new ButtonPressListener() {
                        @Override
                        public void buttonPressed(Button button) {
                            form.load(new BeanAdapter(CONTACT));
                            sourceLabel.setText("Java");
                        }
                    });

                    loadJSONButton.getButtonPressListeners().add(new ButtonPressListener() {
                        @Override
                        public void buttonPressed(Button button) {
                            JSONSerializer serializer = new JSONSerializer();
                            InputStream inputStream = getClass().getResourceAsStream("contact.json");

                            try {
                                form.load(serializer.readObject(inputStream));
                                sourceLabel.setText("JSON");
                            } catch(Exception exception) {
                                System.err.println(exception);
                            }

                            button.setEnabled(true);
                        }
                    });

                    clearButton.getButtonPressListeners().add(new ButtonPressListener() {
                        @Override
                        public void buttonPressed(Button button) {
                            form.clear();
                            sourceLabel.setText("");
                        }
                    });
                }
            }
            
        

The JSON representation of the sample contact record is defined as follows (note that, while JSON is used to represent the data in this example, any class that implements the Dictionary interface, including HashMap, can be used):

            
            {   id: 101,
                name: "Joe User",

                address: {
                    street: "123 Main St.",
                    city: "Cambridge",
                    state: "MA",
                    zip: "02142"
                },

                phoneNumber: "(617) 555-1234",
                emailAddress: "joe_user@foo.com",

                imAccount: {
                    id: "juser1234",
                    type: "AIM"
                }
            }
            
        

The Java bean version, which represents the same data, is composed of the following classes:

            
            package org.apache.pivot.tutorials.databinding;

            public class Contact {
                private String id;
                private String name;
                private Address address;
                private String phoneNumber;
                private String emailAddress;
                private IMAccount imAccount;

                public Contact(String id, String name, Address address, String phoneNumber,
                    String emailAddress, IMAccount imAccount) {
                    this.id = id;
                    this.name = name;
                    this.address = address;
                    this.phoneNumber = phoneNumber;
                    this.emailAddress = emailAddress;
                    this.imAccount = imAccount;
                }

                public String getID() {
                    return id;
                }

                public String getId() {
                    return getID();
                }

                public String getName() {
                    return name;
                }

                public Address getAddress() {
                    return address;
                }

                public String getPhoneNumber() {
                    return phoneNumber;
                }

                public String getEmailAddress() {
                    return emailAddress;
                }

                public IMAccount getIMAccount() {
                    return imAccount;
                }

                public IMAccount getImAccount() {
                    return getIMAccount();
                }
            }
            
        
            
            package org.apache.pivot.tutorials.databinding;

            public class Address {
                private String street;
                private String city;
                private String state;
                private String zip;

                public Address() {
                    this(null, null, null, null);
                }

                public Address(String street, String city, String state, String zip) {
                    this.street = street;
                    this.city = city;
                    this.state = state;
                    this.zip = zip;
                }

                public String getStreet() {
                    return street;
                }

                public String getCity() {
                    return city;
                }

                public String getState() {
                    return state;
                }

                public String getZip() {
                    return zip;
                }
            }
            
        
            
            public class IMAccount {
                private String id;
                private String type;

                public IMAccount() {
                    this(null, null);
                }

                public IMAccount(String id, String type) {
                    this.id = id;
                    this.type = type;
                }

                public String getID() {
                    return id;
                }

                public String getId() {
                    return getID();
                }

                public String getType() {
                    return type;
                }
            }
            
        

This application's data binding requirements are fairly straightforward. It is load-only, and all of the data is simply presented as-is. However, many real-world applications may want to transform the data in some way before presenting it to the user; for example, an application may want to apply currency formatting to a numeric value, or convert an encoded date string to an instance of org.apache.pivot.util.CalendarDate. Components that support data binding provide a "bind mapping" interface to facilitate such transformations. Bind mappings are outside the scope of this example but are demonstrated in the QueryServlet section as well as the Stock Tracker tutorial.

Also, though it is not shown in this example, bindable components allow a caller to control the bind direction via a "bind type" property. The bind type is specified by an instance of the org.apache.pivot.wtk.BindType enum, which defines the following values:

  • LOAD - binding will only occur during a load() operation
  • STORE - binding will only occur during a store() operation
  • BOTH - binding occur during both load() and store() operations

Next: Property Binding