BXML is an XML-based markup language for simplifying the construction of Java object hierarchies. While it is most often used to define the user interface of an Apache Pivot application, it is not limited to user interface construction, and can actually be used to create hierarchies of any object type.
This document introduces the BXML language and explains how it can be used to create and configure a collection of Java objects.
BXMLSerializer
The org.apache.pivot.beans.BXMLSerializer class is used to load a structure defined in a BXML file. This class implements the org.apache.pivot.serialization.Serializer interface, which defines the readObject() method for reading an object value from an input stream:
public interface Serializer<T> { public T readObject(InputStream inputStream) throws IOException, SerializationException; public void writeObject(T object, OutputStream outputStream) throws IOException, SerializationException; public String getMIMEType(T object); }
For example, the following code snippet loads a Window object declared in a file named "my_window.bxml":
BXMLSerializer bxmlSerializer = new BXMLSerializer(); Window window = (Window)bxmlSerializer.readObject(getClass().getResource("my_window.bxml"));
BXMLSerializer does not implement writeObject(), but does provide some additional convenience methods for reading BXML as well as for localizing the deserialized structure using resource bundles.
Namespaces
In BXML, an XML namespace represents a Java package. Declaring a namespace associates the namespace prefix with the package, similar to how the import keyword is used in Java.
For example, the following simple BXML associates the package "com.foo" with the "foo" namespace prefix:
<foo:Bar xmlns:foo="com.foo"/>
If this file were deserialized using BXMLSerializer, an instance of com.foo.Bar would be returned by the call to readObject(). The following BXML, which maps the com.foo package to the default namespace, would produce the same results with slightly less verbose markup:
<Bar xmlns="com.foo"/>;
More complex examples may use classes defined in multiple packages; multiple namespace prefixes can be used for this purpose.
The "bxml" Namespace
The "bxml" namespace prefix is reserved and defines a number of elements and attributes that are used for internal processing. It is generally declared on the root element of a BXML document:
<Bar xmlns:bxml="http://pivot.apache.org/bxml" xmlns="com.foo"> ... </Bar>
The "bxml" namespace includes the following:
-
The bxml:id attribute, which is used to assign a variable name to an element declared in a BXML file. For example, the following markup would associate an instance of the Foo class with the ID "myFoo". This variable is added to the document's variable namespace (which is different from the XML namespace used to import Java packages), and can then be referenced elsewhere in the file or by code that deserializes the file, via the BXMLSerializer#getNamespace() method:
<Foo bxml:id="myFoo"/>
-
The <bxml:include> tag, which is used to include external resources, including nested BXML documents. For example, the following markup would embed the contents of the "my_include.bxml" file in the current document:
<bxml:include src="my_include.bxml"/>
BXML includes are often used for partitioning content into manageable pieces (for example, when working on large applications or with multiple developers, or when defining reusable content templates). By default, each include is assigned its own variable namespace to avoid naming collisions with ancestor documents; however, this behavior can be overridden by adding an "inline" attribute with a value of "true" to the <bxml:include> tag.
-
The <bxml:script> tag, which defines a block of script code within a BXML file. For example, the following script block defines a function named foo(), which can then be called from other script blocks in the document:
<bxml:script> function foo() { .... } </bxml:script>
The default scripting language is JavaScript, but any JVM-compatible scripting language can be used. The language processing instruction is used to specify a different language; for example, Groovy:
<?language groovy?>
Script blocks can also be defined in event handler attributes and elements. Scripting and event handling are discussed in more detail below.
-
The <bxml:define> tag, which is used to declare objects that will exist outside of the hierarchy but may be referred to elsewhere.
For example, the following define block declares an instance of Foo named "myFoo" that is not processed as part of the document flow (in other words, would not be added to its parent element, if it had one) but can be referred to by variable name later in the document:
<bxml:define> <Foo bxml:id="myFoo"/> </bxml:define>
-
The <bxml:reference> tag, used to dereference a page variable. For example, the Foo instance in the previous example could be dereferenced as follows elsewhere in the document:
<bxml:reference id="myFoo"/>
Wherever this tag appears, it will effectively be replaced by the value of the "myFoo" variable.
The variable deference operator ("$") can also be used to dereference page variables. This is discussed in more detail below.
Elements
In BXML, an XML element that does not begin with the reserved "bxml" namespace prefix represents one of the following:
A class instance
A property of a class instance
A "static" property
Each of these is discussed in more detail below.
Class Instances
If an element's tag name begins with an uppercase letter (and it is not a "static" property setter; see below), it is considered a class instance. When BXMLSerializer encounters such an element, it creates an instance of that class. As discussed above, the XML namespace prefix is used to determine the Java package to which the class belongs.
For example, the following BXML would produce an instance of the org.apache.pivot.wtk.Label class populated with the text "Hello, World!":
<Label text="Hello, World!" xmlns="org.apache.pivot.wtk" />
Class instance elements in a BXML file will often represent instances of Java bean types. Internally, BXMLSerializer uses an instance of org.apache.pivot.beans.BeanAdapter to wrap the instantiated class and invoke its setter methods. This class implements the org.apache.pivot.collections.Dictionary interface and allows a caller to get and set bean property values as key/value pairs.
However, if the element represents a type that already implements the Dictionary interface (such as org.apache.pivot.collections.HashMap), it is not wrapped and its dictionary methods are used directly. For example, the following BXML creates an instance of org.apache.pivot.collections.HashMap and sets its "foo" and "bar" values to "123" and "456", respectively:
<HashMap foo="123" bar="456"/>
How the "foo" and "bar" attributes are handled is discussed in more detail below.
Instance Properties
Elements whose tag names begin with a lowercase letter represent instance properties. An instance property element may represent one of the following:
A property setter
A read-only sequence
A read-only dictionary
An event listener list
Property Setters
If the element represents a property setter, the contents of the element (which must be either a text node or a nested class instance element) are passed as the value to the setter for the property. For example, the following BXML creates an instance of the Label class and sets the value of the label's "text" property to "Hello, World!":
<Label xmlns="org.apache.pivot.wtk"> <text>Hello, World!</text> </Label>
This produces the same result as the earlier example which used an attribute to set the "text" property:
<Label text="Hello, World!" xmlns="org.apache.pivot.wtk"/>
The following example creates an instance of ListView and sets the value of its "listData" property to an instance of org.apache.pivot.collections.ArrayList that has been populated with several instances of org.apache.pivot.wtk.content.ListItem:
<ListView xmlns="org.apache.pivot.wtk" xmlns:collections="org.apache.pivot.collections" xmlns:content="org.apache.pivot.wtk.content"> <listData> <collections:ArrayList> <content:ListItem text="A"/> <content:ListItem text="B"/> <content:ListItem text="C"/> </collections:ArrayList> </listData> </ListView>
Read-Only Sequences
If the property represents a read-only sequence (a bean property whose getter returns an instance of org.apache.pivot.collections.Sequence and has no corresponding setter method), the contents of the element are added to the sequence. For example, the "tabs" property of the org.apache.pivot.wtk.TabPane class is a read-only sequence representing the tab pane's tab components. Tabs can be added to a TabPane in BXML as follows:
<TabPane xmlns="org.apache.pivot.wtk"> <tabs> <Label text="Foo"/> <Label text="Bar"/> </tabs> </TabPane>
Read-Only Dictionaries
A property element may also represent a read-only dictionary (a bean property whose getter returns an instance of org.apache.pivot.collections.Dictionary but has no corresponding setter method). For example, the "userData" property of the org.apache.pivot.wtk.Component class represents a read-only dictionary:
<Label text="Hello, World!" xmlns="org.apache.pivot.wtk"> <userData foo="123" bar="456"/> </Label>
The attribute values are put into the dictionary using the attribute names as keys.
Listener Lists
Finally, the property may represent an event listener list (an instance of org.apache.pivot.util.ListenerList). If so, the sub-elements represent listeners of the appropriate type and are added to the listener list. This is discussed in more detail in the Scripting section.
Default Properties
A class may define a "default property" using the @DefaultProperty annotation defined in the org.apache.pivot.beans package. If present, the sub-element representing the default property can be omitted from the markup. For example, the TabPane component discussed above defines the "tabs" property as the default, so the <tabs>sub-element is not actually required:
<TabPane xmlns="org.apache.pivot.wtk"> <Label text="Foo"/> <Label text="Bar"/> </TabPane>
Taking advantage of default properties can significantly reduce the verbosity of BXML markup.
Static Properties
An element may also represent a "static" property setter (sometimes called an "attached property"). Static properties are properties that only make sense in a particular context. They are not intrinsic to the class to which they are applied, but are defined by another class (generally the parent container of a component).
Static properties are prefixed with the name of class that defines them. For example, The following BXML invokes the static setter for the TabPane class's "tabData" property:
<TabPane xmlns:content="org.apache.pivot.wtk.content" xmlns="org.apache.pivot.wtk"> <Label text="Tab 1"> <TabPane.tabData> <content:ButtonData text="First Tab"/> </TabPane.tabData> </Label> </TabPane>
This translates roughly to the following in Java:
TabPane tabPane = new TabPane(); Label label = new Label(); label.setText("Tab 1"); ButtonData buttonData = new ButtonData(); buttonData.setText("First Tab"); TabPane.setTabData(label, buttonData); tabPane.getTabs().add(label);
The call to TabPane.setTabData() attaches the "tabData" property to the Label instance. The tab pane then uses the value of this property as the button data for the label's tab in the tab pane's button bar. Other containers, including Accordion and TablePane, define similar properties.
Attributes
An attribute in BXML may represent one of the following:
A property of a class instance
A "static" property
An event listener
Instance Properties
If an attribute represents an instance property, the attribute value is passed as the argument to the setter method. If the type of the property is a string, the value is passed as-is; otherwise, BXMLSerializer attempts to convert the value to the appropriate type using the BeanAdapter#coerce() method. For example, given the following simple bean class:
package com.foo; public class MyBean { public String getFoo() { ... } public void setFoo(String foo) { ... } public int getBar() { ... } public void setBar(int bar) { ... } }
the following BXML would instantiate the bean and invoke the "foo" and "bar" setters, passing a string to setFoo() and an int to setBar():
<MyBean foo="hello" bar="123"/>
Note that, if the parent element represents an untyped class (a class that implements the Dictionary interface directly, such as org.apache.pivot.collections.HashMap), the type of the attribute cannot be determined, and no conversion takes place - the values are simply passed as strings.
Static Properties
Like elements, attributes may also represent static property setters. For example, The following BXML invokes the static setter for the TabPane class's "tabData" property:
<TabPane xmlns:content="org.apache.pivot.wtk.content" xmlns="org.apache.pivot.wtk"> <Label text="Tab 1" TabPane.tabData="First Tab"/> </TabPane>
This translates roughly to the following in Java:
TabPane tabPane = new TabPane(); Label label = new Label(); label.setText("Tab 1"); TabPane.setTabData(label, "First Tab"); tabPane.getTabs().add(label);
Event Listeners
Finally, an attribute may represent an event listener. Event listener attribute values contain script code that is executed in response to the event. This is discussed in more detail in the Scripting section.
Resolution Operators
Property setter attributes (either bean or static) in BXML support several resolution operators that extend their capabilities:
Object dereference
Resource resolution
URL resolution
Object Dereference
The object deference operator allows a caller to replace an attribute value with an instance of a named object before the corresponding setter method is invoked. Any attribute whose value begins with the "$" is considered an object reference.
For example, a table view header must be associated with an instance of TableView; in Java, this is done via the setTableView() method. In BXML, the object dereference operator is used. The following BXML defines an instance of ScrollPane, setting a TableView as its view component and a TableViewHeader as the column header. The table view is associated with the header via the "tableView" attribute:
<ScrollPane xmlns="org.apache.pivot.wtk" xmlns:bxml="http://pivot.apache.org/bxml"> <view> <TableView bxml:id="tableView"> ... </TableView> </view> <columnHeader> <TableViewHeader tableView="$tableView"/> </columnHeader> </ScrollPane>
Resource Resolution
In BXML, resource substitution can be performed at load time for localization purposes. When given an instance of org.apache.pivot.util.Resources, BXMLSerializer will replace instances of resource names with their locale-specific values. Resource names are identified by a "%" prefix, as shown below:
<Label text="%myText"/>
The associated resource file might contain something like the following:
{ myText:"This is my text!" }
producing a label containing the text "This is my text!".
URL Resolution
Attributes can also be used to specify URLs. An attribute that begins with the "@" character is converted to a URL whose path is interpreted as relative to the location of the BXML source file. For example, the following BXML would load an image from the same directory as the BXML file into an ImageView component. This BXML translates to a call to the ImageView#setImage(java.net.URL) method:
<ImageView image="@foo.png"/>
Without the "@" operator, bean properties would have no context by which to determine the path to such a resource.
Scripting
The <bxml:script> tag allows a caller to import scripting code into or embed script within a BXML file. Any JVM scripting language can be used, including JavaScript, Groovy, and Clojure, among others.
For example, the following BXML defines a JavaScript block that defines a variable named "foo". The value of this variable is used to populate the Label instance that is declared as the window's content:
<Window xmlns:bxml="http://pivot.apache.org/bxml" xmlns="org.apache.pivot.wtk"> <bxml:script> var foo = "Hello, World!"; </bxml:script> <Label text="$foo"/> </Window>
The script could also have been defined in an external file:
<Window xmlns:bxml="http://pivot.apache.org/bxml" xmlns="org.apache.pivot.wtk"> <bxml:script src="foo.js"/> <Label text="$foo"/> </Window>
In either case, any global variables declared in a script are added to the BXML file's variable namespace and become available for use by the object dereference operator, the <bxml:reference> tag, and to callers via BXMLSerializer#getNamespace(), discussed in more detail below.
Listener List Elements
Script code can also be used to define event handlers in BXML. Event handlers can often be defined more succinctly in script than in Java. For example, given the following BXML:
<PushButton xmlns="org.apache.pivot.wtk" xmlns:bxml="http://pivot.apache.org/bxml" bxml:id="pushButton" buttonData="Click Me!"/>
the Java code to obtain a reference to a PushButton and attach a button press listener to it might look like this:
PushButton pushButton = (PushButton)bxmlSerializer.getNamespace().get("pushButton"); pushButton.getButtonPressListeners().add(new ButtonPressListener() { public void buttonPressed(Button button) { // Handle event } });
While this is simple enough, it can become cumbersome in any non-trivial application where many such event are defined. A similar event handler might be defined in JavaScript as follows:
<PushButton xmlns="org.apache.pivot.wtk" xmlns:bxml="http://pivot.apache.org/bxml" buttonData="Click Me!"> <buttonPressListeners> function buttonPressed(button) { // Handle event } </buttonPressListeners> </PushButton>
This version is quite a bit easier to read, and creates a strong association between the button and the handler. It also doesn't require the button to have an ID.
When script is declared within a listener list element, BXMLSerializer creates a special scope that is local to the handler. As a result, any variables or functions defined within the script block do not pollute the page's global namespace and are only visible within the block. However, the script code can still see and access global variables declared elsewhere in the page. This is somewhat analogous to defining an anonymous inner class as a listener in Java.
Also, though it isn't obvious from this simple example, script-based event handlers are not required to provide implementations for every method defined by the listener interface. Any omitted methods are simply processed by a default no-op handler.
Event Listener Attributes
Event listeners can also be declared in attributes, using a syntax similar to that used for static property setters. The attribute name for an event listener consists of the name of the interface that defines the event plus the name of the event, separated by a period. Like listener list elements, a special scope is created for listener attributes that is local to the handler; any variables defined within the attribute are only visible within the handler.
For example, the above button press listener can be declared in an attribute as follows:
<PushButton xmlns="org.apache.pivot.wtk" xmlns:bxml="http://pivot.apache.org/bxml" buttonData="Click Me!" ButtonPressListener.buttonPressed="handleEvent(arguments[0])"/>
Note that the handler function is passed a value of arguments[0]. The arguments array contains the arguments that were originally passed to the event listener method, and only exists within the scope of the event handler. arguments[0] contains the first argument passed to the listener method, which in this case is a reference to the button that fired the event.
Attribute-based event handlers are well suited to short handler code that, ideally, fits on a single line. Longer event handler code may be better suited to an element-based listener list, or, depending on the level of complexity, implementation in a Java or other compiled language.
Accessing Named Objects
As previously discussed, the BXMLSerializer#getNamespace() method allows a caller to retrieve a named object instance from a BXML file once the root object has been loaded. For example, the following code loads a hypothetical BXML file containing a Window and a Label instance, obtains a reference to the label, changes its text to "Welcome to Pivot!", and opens the window:
public void startup(Display display, Map<String, String> properties) throws Exception { BXMLSerializer bxmlSerializer = new BXMLSerializer(); Window window = (Window)bxmlSerializer.readObject(getClass().getResource("window.bxml")); Label label = (Label)bxmlSerializer.getNamespace().get("label"); label.setText("Welcome to Pivot!"); window.open(display); }
getNamespace() returns a value that implements the Dictionary interface, so callers can also use the put() or remove() methods to modify the serializer's namespace before the BXML file is loaded (effectively "parameterizing" the BXML).
The Bindable Interface
The org.apache.pivot.beans.Bindable interface can be used to simplify integration between BXML markup and compiled code. Bindable defines a single method, initialize(), that is called on the root element of a BXML document once the document has been completely loaded. It allows the implementing class to get access to the document's namespace (i.e. page variables), the resources that were used to load it, and the location it was loaded from, to perform any necessary post-processing (for example, registering event listeners).
Note that only the root element will be called to initialize(), because the bindable properties (namespace, resources, and location) apply to the document as a whole, not to individual sub-elements. However, this includes the root elements of any BXML files included using the <bxml:include> tag, allowing Bindable implementations to effectively implement the "code behind" the markup of each BXML file used by an application.
@BXML
If any of the Bindable's member variables are tagged with the org.apache.pivot.beans.BXML annotation, they will be automatically populated with the corresponding variables defined in the BXML file. For example, given the following BXML:
<Window xmlns="org.apache.pivot.wtk" xmlns:bxml="http://pivot.apache.org/bxml"> <Label bxml:id="label" text="Hello, World!"/> </Window>
a Java member variable declared as follows will be automatically populated with the declared Label instance when the BXML file is deserialized:
@BXML private Label label;
As a result, the @BXML annotation can significantly simplify the process of working with loaded BXML data in Java code. However, it is important to note that, because BXML binding relies on reflection to set the member variables, it can only be used with trusted code or to set the values of public fields. For untrusted code, the namespace value passed to the initialize() method can be used to access named objects defined in a BXML file.
Summary
BXML provides a number of features that help simplify the process of building a user interface. It can be used to instantiate objects and set member variables as well as define script logic for working with those objects. It is a powerful and efficient way to construct the user interface of a Pivot application.