Part 2 explores a more substantial application, and shows how Metawidget can be used to map the same back-end to multiple front-ends. We will develop an Address Book application with desktop-based, Web-based and mobile-based UIs.
This tutorial should take around 45 minutes. Before you begin, you will need to download the examples distribution from http://metawidget.org/download.php. This includes pre-built example applications with full source code. To save time, we will not focus on any one front-end framework in detail. For detailed framework-specific instructions, please see Chapter 3, Metawidgets.
The Desktop Address Book is essentially a larger version of the application developed in Part 1 - it just has more domain objects and more widgets.
Note | |
---|---|
If you are only interested in the JavaScript-based Metawidget, you should skip straight to Section 1.3.2, “Web Address Book”. |
The application is pre-built for you in examples/swing/addressbook-swing.jar. This is a self-executing JAR. For convenience, it has MANIFEST.MF dependencies hard-coded into it, so it's best not to move it to a different folder (if you do, you'll need to manually fix up your CLASSPATH).
Note | |
---|---|
There is also an SWT version of the Address Book sample in examples/swt/addressbook-swt.jar. You can mostly replace references to 'Swing' in this section with 'SWT' and follow along using SWT if preferred. |
Run the code by navigating to the examples/swing folder and typing:
java -jar addressbook-swing.jar
The opening screen displays a search filter (at the top) and lists existing Address Book entries (at the bottom) as in Figure 1.19 (Swing version) and Figure 1.20 (SWT version).
The three search filter fields (Firstname, Surname and Type) are created by SwingMetawidget based on the ContactSearch business class. This includes populating the Type dropdown based on the ContactType enum. The , and buttons are created by SwingMetawidget based on annotated action methods in the ContactDialog class. The rest of the screen - including the images, background and layout - are managed by regular Swing code and are not controlled by Metawidget: Metawidget does not try to 'own' the entire UI.
Note | |
---|---|
Full source code for all the examples, such as the code for the ContactSearch, ContactType and ContactDialog classes, is included under the src/examples folder of the examples distribution. |
Click Figure 1.21.
. The screen displays a form for filling out Personal Contact information as inAll the form fields are created by SwingMetawidget based on the PersonalContact business class. This class is itself derived from the Contact business class. It includes some Metawidget annotations for dropdown values and section headings.
Note the code only has one JDialog class (ContactDialog), but is capable of supporting both PersonalContact and BusinessContact UIs. The fields in the UI change depending on the object passed to ContactDialog at runtime. This is the Third Goal Of Metawidget:
Third Goal Of Metawidget | |
---|---|
Metawidget can perform inspection at runtime, detecting types and subtypes dynamically |
The Address property is created as a nested SwingMetawidget. This is the default behaviour when Metawidget encounters datatypes it does not know how to represent using any other UI widget. The Communications property has been overridden with a manually specified JTable.
In addition, JTable.setCellEditor uses SwingMetawidget to render single JComponents as CellEditors. This includes automatically populating dropdown values.
The Desktop Address Book uses Metawidget's setReadOnly(true) method to display read-only screens. Return to the main screen, and double-click on an existing contact (such as Homer Simpson). The same ContactDialog is used, but this time all the widgets are read-only labels as in Figure 1.22.
Click setReadOnly(false), as in Figure 1.23.
. The labels are transformed into editable widgets by using Metawidget'sThe data from the PersonalContact object is automatically inserted into the JComponents. It is also automatically saved back when clicking .
Swing does not define a JComponent to Object mapping mechanism, so by default SwingMetawidget only supplies setValue and getValue methods for manually fetching values. This situation is no worse than a normal Swing application, but Metawidget can do better.
SwingMetawidget directly supports third-party binding alternatives such as Apache BeanUtils and Beans Binding (JSR 295) via SwingMetawidget.addWidgetProcessor. These binding implementations automatically map JComponent values to Object values, including performing the necessary conversions, further reducing the amount of boilerplate code required.
SwtMetawidget does something similar using org.eclipse.core.databinding.Binding.
All text within the application has been localized to the org.metawidget.example.shared.addressbook.resource.Resources resource bundle. Text created manually (such as the buttons) uses typical Swing localization code (e.g. bundle.getString). Text created by SwingMetawidget uses SwingMetawidget.setBundle, which internally defers to Swing's bundle.getString.
Localization is very easy with Metawidget. For property names, if no resource bundle is supplied, Metawidget uses an 'uncamel-cased' version of the name (e.g. dateOfBirth becomes Date Of Birth). If a bundle is supplied, Metawidget uses the property name as the bundle key. For section headings, if a bundle is supplied, Metawidget uses a 'camel-cased' version of the headings as the key.
This means developers can initially build their UIs without worrying about resource bundles, then turn on localization support later.
As there are a large number of Web application frameworks to choose from, this example comes written in nine of the most popular: AngularJS, Google Web Toolkit (GWT), Java Server Faces (JSF) 1.x and 2.x (using Facelets), Java Server Pages (JSP), JQuery Mobile, Spring Web MVC, Struts, Vaadin and Web Components. We recommend you follow along using the one most relevant to you.
Web-based applications are inherently more difficult to setup and run than desktop-based applications because they require a server (this is true even for the pure client-side, JavaScript-based versions of the Address Book as they make REST calls). For this tutorial we recommend using Apache Tomcat as it is one of the easier containers to get running (the examples are tested against Tomcat 6.0.29). Tomcat can be downloaded from http://tomcat.apache.org.
Take a fresh install of Tomcat. The Address Book application is pre-built for you in either examples/js/angular/addressbook, examples/js/jquery/mobile/addressbook, examples/js/webcomponent/addressbook, examples/java/faces/addressbook-faces.war, examples/java/gwt/addressbook-gwt.war, examples/java/jsp/addressbook-jsp.war, examples/java/spring/addressbook-spring.war, examples/java/struts/addressbook-struts.war or examples/java/vaadin/addressbook-struts.war. Alternatively you can build it yourself by changing to the src/examples folder and typing:
mvn -pl org.metawidget.examples.faces:addressbook-faces -am integration-test
(replacing faces with gwt, jsp, spring, struts or vaadin as appropriate).
Note | |
---|---|
For most web environments, deploying Metawidget is as simple as adding metawidget-all.jar to WEB-INF/lib or metawidget-core.min.js to lib/metawidget. For GWT, you'll also need to include metawidget-all.jar and additional/gwt/metawidget-all-sources.jar in the CLASSPATH during your GWTCompiler step. |
Copy the application into Tomcat's webapps folder, start Tomcat, and open a Web browser to http://localhost:8080/addressbook-faces. The home page displays a search filter (at the top) and lists existing Address Book entries (at the bottom) as in Figure 1.24.
As with the Desktop Address Book, the search filter fields are created by Metawidget (this time AngularMetawidget, UIMetawidget, GwtMetawidget, HtmlMetawidgetTag, JQueryMobileMetawidget, SpringMetawidgetTag, StrutsMetawidgetTag or VaadinMetawidget) based on the ContactSearch business class:
<m:metawidget value="#{contact.search}"> ... </m:metawidget>
Again, this includes populating the Type dropdown and localizing the text. The UIMetawidget based on annotated methods in the ContactBean (for JSF).
, and buttons are either manually specified in the JSP page (for GWT, Spring and Struts) or created byNote | |
---|---|
As with the Desktop Address Book, full source code for the examples can be found under src/examples. |
The look of the Web page relies entirely on HTML and CSS technologies. The CSS classes to use are configured in metawidget.xml (or services.js for AngularJS):
<htmlMetawidget> <parameter> <string>tableStyleClass</string> <string>table-form</string> </parameter> <parameter> <string>columnClasses</string> <string>table-label-column,table-component-column,table-required-column</string> </parameter> ...
Only the layout of 'one column for the label, one column for the widget' is dictated by Metawidget, and that is again pluggable and configurable.
Click Figure 1.26.
. The page displays a form for filling out Personal Contact information as inAll the form fields are created by Metawidget based on the PersonalContact business class. The section headings are the same, but have this time been rendered as HTML.
The Address property is a nested Metawidget. The Communications property has been overridden in the page with a manually specified table. UIMetawidget understands a manually-specified widget to override an automatic one if it has the same value binding as the automatic widget would have (AngularMetawidget, GwtMetawidget, SpringMetawidgetTag, StrutsMetawidgetTag and VaadinMetawidget do something similar):
<m:metawidget value="#{contact.current}"> ... <h:dataTable value="#{contact.current.communications}"> ... </h:dataTable> ... <m:metawidget>
JSF has built-in support for executing actions on table rows. In order to use it, however, the Set returned by Contact.getCommunications must be wrapped into a DataModel. This is handled by ContactController.getCurrentCommunications, but this presents a problem: the mapping for the HtmlDataTable must be #{contact.currentCommunications}, but the mapping required to override UIMetawidget's automatic widget creation is #{contact.current.communications}.
UIMetawidget supplies UIStub for these situations. Stubs have a binding, but do nothing with it and render nothing. They can be used either to suppress widget creation entirely (a stub with an empty body) or to replace the automatic widget creation with one or more other widgets with different bindings:
<m:metawidget value="#{contact.current}"> ... <m:stub value="#{contact.current.communications}"> <h:dataTable value="#{contact.currentCommunications}"> ... </h:dataTable> </m:stub> ... <m:metawidget>
JSP, Spring, Struts lack some component-based features found in Swing and JSF. Specifically, whilst it is possible for tags to reference their parent (using TagSupport.findAncestorWithClass), they have no way to interrogate their children. Therefore, it is not possible to directly support arbitrary child tags within HtmlMetawidget, SpringMetawidgetTag and StrutsMetawidgetTag.
Instead, we wrap the overridden Communications property in Metawidget's Stub tag. Metawidget and its Stub tags have explicit support for co-ordinating the overriding of widget creation:
<m:metawidget property="contactForm"> ... <m:stub property="communications"> <table class="data-table"> ... </table> </m:stub> ... <m:metawidget>
GwtMetawidget uses stubs around GWT widgets like FlexTable, but can use the overriding widget directly if it supports the HasName interface (e.g. TextBox, CheckBox, etc) and has a name matching the name of the domain object property.
The section is specific to Spring/Struts.
Within the Communications table, implementing ActionForms per Action, so we are unable to combine PersonalContactForm with a CommunicationForm (as we did in the JSF). Spring has a similar limitation of not supporting multiple commandNames per form. Instead, we need to either:
calls for a design decision. Struts does not support multipleadd properties from Communication to PersonalContactForm, and ignore them when saving the PersonalContact; or
output plain HTML tags (i.e. independent of Spring and Struts) and handle them manually
Both approaches would be valid. For this tutorial, we choose the latter as it allows us to introduce HtmlMetawidget (a Metawidget for plain HTML/JSP webapps that don't use Struts or Spring) and demonstrate mixing two Metawidgets on the same page:
<m:metawidget property="contactForm"> ... <m:stub property="communications"> <table class="data-table"> ... <tr> <jsp:useBean id="communication" class="org.metawidget.example.shared.addressbook.model.Communication"/> <td><mh:metawidget value="communication.type" style="width: 100%" /></td> <td><mh:metawidget value="communication.value" style="width: 100%" /></td> </tr> ... </table> </m:stub> ... </m:metawidget>
The two different tag prefixes m: and mh: denote different tag libraries. HtmlMetawidget is very similiar to StrutsMetawidgetTag, but has to use jsp:useBean to manually instantiate the bean (rather than letting Struts do it). Within metawidget.xml, the default Layout for HtmlMetawidget has been set to org.metawidget.jsp.tagext.layout.SimpleLayout (i.e. a plain layout, without a label column)
This section does not apply to GWT.
In the Desktop Address Book, the title dropdown was populated by a static lookup attribute in metawidget-metadata.xml. JSP and JSF-based technologies can do better, because they have a built-in scope-based component model and Expression Language.
Contact.getTitle is annotated using @UiFacesLookup and @UiSpringLookup (and ContactForm.getTitle is annotated using @UiStrutsLookup). These are used at runtime to create dynamic lookups.
These annotations, unlike the ones we have used so far, are UI-framework specific so you may prefer to declare them in metawidget-metadata.xml. Before doing so, however, you should understand we are still not introducing runtime dependencies into our business classes: an important feature of annotations is they 'fall away gracefully' if their implementing class is not found. Annotations never throw ClassDefNotFoundError.
This section is specific to JSF 1.x.
Metawidget factors all widget creation into WidgetBuilders. Like Inspectors, multiple WidgetBuilders can be combined using a CompositeWidgetBuilder to support third-party component libraries. In this section we will override Metawidget's default and introduce a RichFacesWidgetBuilder alongside the standard JSF HtmlWidgetBuilder.
Go into Tomcat's webapps/addressbook-faces folder (the exploded WAR) and edit WEB-INF/metawidget.xml:
<metawidget xmlns="http://metawidget.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://metawidget.org http://metawidget.org/xsd/metawidget-1.0.xsd"> <htmlMetawidget xmlns="java:org.metawidget.faces.component.html"> ... <inspector> <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <propertyTypeInspector xmlns="java:org.metawidget.inspector.propertytype"/> <metawidgetAnnotationInspector xmlns="java:org.metawidget.inspector.annotation"/> <facesAnnotationInspector xmlns="java:org.metawidget.inspector.faces"/> <xmlInspector xmlns="java:org.metawidget.inspector.xml" config="XmlInspectorConfig"/> </array> </inspectors> </compositeInspector> </inspector> <widgetBuilder> <compositeWidgetBuilder xmlns="java:org.metawidget.widgetbuilder.composite" config="CompositeWidgetBuilderConfig"> <widgetBuilders> <array> <overriddenWidgetBuilder xmlns="java:org.metawidget.faces.component.widgetbuilder"/> <readOnlyWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder"/> <richFacesWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder.richfaces"/> <htmlWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder"/> </array> </widgetBuilders> </compositeWidgetBuilder> </widgetBuilder> <layout> <outputTextLayoutDecorator xmlns="java:org.metawidget.faces.component.html.layout" config="OutputTextLayoutDecoratorConfig"> <layout> <simpleLayout xmlns="java:org.metawidget.faces.component.layout"/> </layout> <styleClass> <string>section-heading</string> </styleClass> </outputTextLayoutDecorator> </layout> </htmlMetawidget> </metawidget>
Now restart Tomcat, refresh your Web browser and click on Homer Simpson. Notice how the Date of Birth field for Personal Contacts is now a RichFaces date picker widget, and the Number of Staff field for Business Contacts is a RichFaces slider widget.
Going futher, Metawidget's pluggable layouts make it easy to support third-party layout components. Edit WEB-INF/metawidget.xml again:
<metawidget xmlns="http://metawidget.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://metawidget.org http://metawidget.org/xsd/metawidget-1.0.xsd"> <htmlMetawidget xmlns="java:org.metawidget.faces.component.html"> ... <inspector> <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <propertyTypeInspector xmlns="java:org.metawidget.inspector.propertytype"/> <metawidgetAnnotationInspector xmlns="java:org.metawidget.inspector.annotation"/> <facesAnnotationInspector xmlns="java:org.metawidget.inspector.faces"/> <xmlInspector xmlns="java:org.metawidget.inspector.xml" config="XmlInspectorConfig"/> </array> </inspectors> </compositeInspector> </inspector> <widgetBuilder> <compositeWidgetBuilder xmlns="java:org.metawidget.widgetbuilder.composite" config="CompositeWidgetBuilderConfig"> <widgetBuilders> <array> <overriddenWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder"/> <readOnlyWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder"/> <richFacesWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder.richfaces"/> <htmlWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder"/> </array> </widgetBuilders> </compositeWidgetBuilder> </widgetBuilder> <layout> <tabPanelLayoutDecorator xmlns="java:org.metawidget.faces.component.html.layout.richfaces" config="TabPanelLayoutDecoratorConfig"> <layout> <simpleLayout xmlns="java:org.metawidget.faces.component.layout"/> </layout> </tabPanelLayoutDecorator> </layout> </htmlMetawidget> </metawidget>
Restart Tomcat, refresh your Web browser and click on Homer Simpson again. Notice how the Contact Details and Other sections are laid out as tabs within a RichFaces TabPanel as in Figure 1.27.
This demonstrates how easy it is to leverage widget libraries with Metawidget (this example cheats a bit, as we've pre-added the RichFaces JARs into WEB-INF/lib and some lines into web.xml, but you get the idea).
This section is specific to JSF 2.x.
As in the previous section, we can override Metawidget's default and introduce an alternate WidgetBuilder alongside the standard JSF HtmlWidgetBuilder. This time we will demonstrate using PrimeFaces.
Go into Tomcat's webapps/addressbook-faces2 folder (the exploded WAR) and edit WEB-INF/metawidget.xml:
<metawidget xmlns="http://metawidget.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://metawidget.org http://metawidget.org/xsd/metawidget-1.0.xsd"> <htmlMetawidget xmlns="java:org.metawidget.faces.component.html"> ... <inspector> <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <propertyTypeInspector xmlns="java:org.metawidget.inspector.propertytype"/> <metawidgetAnnotationInspector xmlns="java:org.metawidget.inspector.annotation"/> <facesAnnotationInspector xmlns="java:org.metawidget.inspector.faces"/> <xmlInspector xmlns="java:org.metawidget.inspector.xml" config="XmlInspectorConfig"/> </array> </inspectors> </compositeInspector> </inspector> <widgetBuilder> <compositeWidgetBuilder xmlns="java:org.metawidget.widgetbuilder.composite" config="CompositeWidgetBuilderConfig"> <widgetBuilders> <array> <overriddenWidgetBuilder xmlns="java:org.metawidget.faces.component.widgetbuilder"/> <readOnlyWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder"/> <primeFacesWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder.primefaces"/> <htmlWidgetBuilder xmlns="java:org.metawidget.faces.component.html.widgetbuilder"/> </array> </widgetBuilders> </compositeWidgetBuilder> </widgetBuilder> <layout> <tabViewLayoutDecorator xmlns="java:org.metawidget.faces.component.html.layout.primefaces" config="org.metawidget.layout.decorator.LayoutDecoratorConfig"> <layout> <simpleLayout xmlns="java:org.metawidget.faces.component.layout"/> </layout> </tabViewLayoutDecorator> </layout> </htmlMetawidget> </metawidget>
Restart Tomcat, refresh your Web browser and click on Charles Montgomery Burns. Notice how the Contact Details and Other sections are laid out as tabs within a PrimeFaces TabView and the Number of Staff field is a PrimeFaces Slider. See Figure 1.28.
This demonstrates how easy it is to leverage widget libraries with Metawidget (this example cheats a bit, as we've pre-added the PrimeFaces JARs into WEB-INF/lib and some lines into web.xml, but you get the idea).
For the Mobile Address Book we use the Android platform. Like Web-based applications, mobile applications require a container to run. If you have a physical Android device you can install the Mobile Address Book by downloading and opening http://metawidget.org/examples/android/addressbook-android.apk or scanning this QR Code:
Note | |
---|---|
The Android Address Book is a native mobile application. For an example of an HTML 5 hybrid mobile application see the JQuery Mobile Address Book in the previous section. |
Alternatively download the Android SDK from http://code.google.com/android/download.html (the examples are tested against Android 1.1). Then change to the installation directory (usually defined by ANDROID_HOME) and run the emulator by opening a command prompt and typing:
tools\android create avd -n my_avd -t 1
Replacing -t 1 with the Android version you're using (e.g. -t 2 for 1.5, -t 3 for 1.6, -t 4 for 2.0). Android 1.1 doesn't use AVDs, so you can skip this step. Next type:
tools\emulator -avd my_avd
The emulator may take a little while to start. Once finished, it will display the phone's desktop. The Address Book APK is pre-built for you in examples/android/addressbook-android.apk. Alternatively you can build it yourself by changing to the src/examples folder and typing:
mvn -pl org.metawidget.examples.android:addressbook-android -am integration-test
Next, open a second command prompt, change to the Android installation directory and type:
platform-tools\adb install <metawidget folder>\examples\android\addressbook-android.apk
This deploys the APK into the emulator. To run it, click the emulator's Menu button and then choose the Address Book application. The emulator displays a search filter (at the top) and lists existing Address Book entries (at the bottom) as in Figure 1.30.
As with the Desktop and Web Address Books, the three search filter fields are created by Metawidget (this time AndroidMetawidget) based on the ContactSearch business class. Again, this includes populating the Type dropdown.
Note | |
---|---|
As with the Desktop Address Book, full source code for the examples can be found under src/examples. To open it in Eclipse we recommend installing Maven Integration for Android Development Tools. |
The look of the screen relies entirely on Android XML layout files, styles and themes. Only the 'one column for the label, one column for the widget' layout is dictated by Metawidget, and that is pluggable and configurable.
Choose Figure 1.31.
from the Android menu. The page displays a form for filling out Personal Contact information as inUIs in Android are typically defined using XML layout files, though they can also be built programmatically. AndroidMetawidget supports both approaches. For example, the Personal Contact screen is defined in contact.xml, and contains a Metawidget defined in much the same way as in JSP (including configuring section style and overriding widget creation):
<view class="org.metawidget.android.widget.AndroidMetawidget" android:id="@+id/metawidget" config="@raw/metawidget"> <view class="org.metawidget.android.widget.Stub" tag="communications"> <ListView android:id="@id+/communications" ... /> <Button android:id="@+id/buttonAddCommunication" android:text="@string/addCommunication" ... /> </view> </view>
Within CommunicationDialog, a Metawidget is defined programatically in much the same way as in Swing:
mMetawidget = new AndroidMetawidget( activity );
mMetawidget.setConfig( R.raw.config );
...
mMetawidget.setToInspect( mCommunication );
This produces the dialog box in Figure 1.32.
That concludes the introductory tutorial. In summary, we have now:
seen how to build an application whose UI is largely dictated by its business classes, not by hand-written UI code
significantly reduced the amount of UI code needed in our applications
seen how to build an application that targets multiple platforms. If we were to add a new property to one of the business classes (say, numberOfChildren to PersonalContact), it would automatically appear and be functional on every platform.