Layouts arrange widgets on the screen, following their building by a WidgetBuilder and processing by any WidgetProcessors. This section covers Layouts in general. For in-depth documentation of individual Layouts see Chapter 8, Layouts.
All Layouts must implement the Layout interface. This is a simple interface that defines only one method:
void layoutWidget(W widget,String elementName,Map<String,String> attributes,C container,M metawidget);
Where W is a widget type (such as JComponent or UIComponent) and M is a Metawidget type (such as SwingMetawidget or UIMetawidget).
Where W is a widget type (i.e. Control, JComponent etc), C is widget container type (i.e. Composite, JComponent etc) and M is a Metawidget type (i.e. SwtMetawidget, SwingMetawidget etc). Don't be put off by having so many parameterized types! Metawidget needs them so that it can present a consistent API across many architectures. However this complication doesn't impact your own custom plugins because you're able to substitute concrete, platform-specific values for each parameter (i.e. Control, Composite, SwtMetawidget ).
layoutWidget is called for each widget. Layouts should add the given widget as a child of the given container, according to the given elementName (which is typically just 'property' or 'action' from the inspection-result) and the various attributes (as parsed from the inspection-result). They can use the given metawidget to access additional services if needed (such as state saving beween invocations, see Section 2.6.7, “Implementing Your Own Layout”).
layoutWidget is called 'in-place' | |
---|---|
layoutWidget is called immediately after WidgetBuilder.buildWidget and WidgetProcessor.processWidget, and before the next widget is generated. An alternate design would be to 'collect' all widgets generated by buildWidget and processWidget, then iterate over them separately for the layout. If you prefer this approach, you can simulate it by having layoutWidget do nothing but 'remember' each widget, then iterate over them in endContainerLayout (see Section 2.6.2, “Advanced Interface”). However not all UI frameworks allow this approach, because they do not suport widgets being instantiated independent of a layout, nor moved between layouts (e.g. SWT). |
The Layout interface only has a single method. However for those needing more control over the Layout lifecycle there is an extended interface AdvancedLayout. This interface defines four additional methods:
void onStartBuild(M metawidget); void startContainerLayout(W container, M metawidget); void endContainerLayout(W container, M metawidget); void onEndBuild(M metawidget);
Figure 2.10. startContainerLayout and endContainerLayout are called for each container, layoutWidget is called for each widget
The first method, onStartBuild, is called at the start of the widget building process, before the WidgetBuilder is called. Layouts may wish to act on this event to initialize themselves ready for processing, or to perform 'outermost-container-only' processing, such as adding toolbar facets. However it is acceptable to do nothing.
The second method, startContainerLayout, is called to initialize the given container. It is acceptable to do nothing.
The third method, endContainerLayout, is called to finish the given container. It is acceptable to do nothing.
The last method, onEndBuild, is called at the end of the widget building process, after all widgets have been built and added to the Layout. Layouts may wish to act on this event to clean themselves up following processing, or to perform 'outermost-container-only' processing, such as adding status bar facets. However it is acceptable to do nothing.
LayoutDecorator allows you to combine multiple Layouts together in a heirarchy. Conceptually, this is similar to CompositeInspector or CompositeWidgetBuilder, but Layouts are fundamentally different in that most are 'end points' that cannot sensibly be composed into sequential lists (i.e. what should happen if you try to combine a GridBagLayout with a FlowLayout?).
Rather, Layouts must be combined heirarchically, with an 'outer' Layout delegating to a single 'inner' Layout. LayoutDecorator is an abstract class that can be extended in order to decorate other Layouts. For example, GridBagLayout can be decorated using TabbedPaneLayoutDecorator to add tabbed section functionality.
<metawidgetLayout> <tabbedPaneLayoutDecorator xmlns="java:org.metawidget.swing.layout" config="TabbedPaneLayoutDecoratorConfig"> <layout> <gridBagLayout /> </layout> </tabbedPaneLayoutDecorator> </metawidgetLayout>
A LayoutDecorator can also decorate another LayoutDecorator to provide fine-grained control over nested sections. For example, the domain object...
public class Person { @UiSection( { "Person", "Name" } ) public String firstname; public String surname; @UiSection( { "Person", "Contact Detail" } ) public String telephone; }
...could be rendered using nested TabbedPaneLayoutDecorators...
<metawidgetLayout> <tabbedPaneLayoutDecorator xmlns="java:org.metawidget.swing.layout" config="TabbedPaneLayoutDecoratorConfig"> <layout> <tabbedPaneLayoutDecorator config="TabbedPaneLayoutDecoratorConfig"> <layout> <gridBagLayout /> </layout> </tabbedPaneLayoutDecorator> </layout> </tabbedPaneLayoutDecorator> </metawidgetLayout>
...as shown in Figure 2.11.
Alternatively, it could use a TabbedPaneLayoutDecorator nested within a SeparatorLayoutDecorator...
<metawidgetLayout> <separatorLayoutDecorator xmlns="java:org.metawidget.swing.layout" config="SeparatorLayoutDecoratorConfig"> <layout> <tabbedPaneLayoutDecorator config="TabbedPaneLayoutDecoratorConfig"> <layout> <gridBagLayout /> </layout> </tabbedPaneLayoutDecorator> </layout> </separatorLayoutDecorator> </metawidgetLayout>
...as shown in Figure 2.12.
Or the opposite - a SeparatorLayoutDecorator nested within a TabbedPaneLayoutDecorator...
<metawidgetLayout> <tabbedPaneLayoutDecorator xmlns="java:org.metawidget.swing.layout" config="TabbedPaneLayoutDecoratorConfig"> <layout> <separatorLayoutDecorator config="SeparatorLayoutDecoratorConfig"> <layout> <gridBagLayout /> </layout> </separatorLayoutDecorator> </layout> </tabbedPaneLayoutDecorator> </metawidgetLayout>
...as shown in Figure 2.13.
Unless explicitly specified, each Metawidget will instantiate a default Layout. This default behaviour can be overridden either in code:
metawidget.setLayout( new MyLayout() );
Or via metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):
<swingMetawidget xmlns="java:org.metawidget.swing"> <layout> <myLayout xmlns="java:com.myapp"/> </layout> </swingMetawidget>
This allows easy plugging in of alternate Layouts.
All Metawidgets have default Layouts. You can see the default by looking in the Metawidget JAR for the file metawidget-xxx-default.xml (where xxx is your target platform, such as swing or struts).
For reference, the defaults are:
Platform | Default |
---|---|
Android | <textViewLayoutDecorator config="TextViewLayoutDecoratorConfig"> <layout> <tableLayout /> </layout> </textViewLayoutDecorator> |
GWT | LabelLayoutDecorator around a FlexTableLayout |
JavaScript (incl. AngularJS and JQuery UI) | new metawidget.layout.HeadingTagLayoutDecorator( new metawidget.layout.TableLayout() ) |
JSF | <outputTextLayoutDecorator config="OutputTextLayoutDecoratorConfig"> <layout> <simpleLayout/> </layout> </outputTextLayoutDecorator> |
JSP | <headingTagLayoutDecorator config="HeadingTagLayoutDecoratorConfig"> <layout> <htmlTableLayout/> </layout> </headingTagLayoutDecorator> |
Spring | <headingTagLayoutDecorator config="HeadingTagLayoutDecoratorConfig"> <layout> <springTableLayout/> </layout> </headingTagLayoutDecorator> |
Struts | <headingTagLayoutDecorator config="HeadingTagLayoutDecoratorConfig"> <layout> <htmlTableLayout/> </layout> </headingTagLayoutDecorator> |
Swing | <separatorLayoutDecorator config="SeparatorLayoutDecoratorConfig"> <layout> <gridBagLayout/> </layout> </separatorLayoutDecorator> |
SWT | <separatorLayoutDecorator config="SeparatorLayoutDecoratorConfig"> <layout> <gridLayout/> </layout> </separatorLayoutDecorator> |
Vaadin | <headingTagLayoutDecorator config="HeadingTagLayoutDecoratorConfig"> <layout> <formLayout/> </layout> </separatorLayoutDecorator> |
All Layouts are required to be immutable. This means you only need a single instance of a Layout for your entire application. If you are using metawidget.xml then ConfigReader takes care of this for you, but if you are instantiating Layouts in Java code you should reuse instances.
Note that immutable only means Layouts cannot be changed once instantiated - it does not mean they cannot be configured. Many Layouts have corresponding xxxConfig classes that allow them to be configured prior to instantation in a type-safe way. For example, an HtmlTableLayout can be configured in code:
metawidget.setLayout( new HtmlTableLayout( new HtmlTableLayoutConfig().setNumberOfColumns( 2 ));
Or in metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):
<htmlTableLayout xmlns="java:org.metawidget.jsp.tagext.html.layout" config="HtmlTableLayoutConfig"> <numberOfColumns> <int>2</int> </numberOfColumns> </htmlTableLayout>
Here is an example of a custom Layout that arranges components in a bulleted HTML list. It could be useful for, say, arranging action elements that were represented by HTML anchor tags:
package com.myapp; import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; import org.metawidget.jsp.*; import org.metawidget.jsp.tagext.*; import org.metawidget.layout.iface.*; public class HtmlListLayout implements AdvancedLayout<Tag, MetawidgetTag> { public void onStartBuild( MetawidgetTag metawidgetTag ) {} public void startContainerLayout( Tag containerTag, MetawidgetTag metawidgetTag ) { try { JspWriter writer = metawidgetTag.getPageContext().getOut(); writer.write( "<ul>" ); } catch ( Exception e ) { throw LayoutException.newException( e ); } } public void layoutChild( Tag tag, String elementName, Map<String, String> attributes, Tag containerTag, MetawidgetTag metawidgetTag ) { try { JspWriter writer = metawidgetTag.getPageContext().getOut(); writer.write( "<li>" ); writer.write( JspUtils.writeTag( metawidgetTag.getPageContext(), tag, containerTag, null ) ); writer.write( "</li>" ); } catch ( Exception e ) { throw LayoutException.newException( e ); } } public void endContainerLayout( Tag containerTag, MetawidgetTag metawidgetTag ) { try { JspWriter writer = metawidgetTag.getPageContext().getOut(); writer.write( "</ul>" ); } catch ( Exception e ) { throw LayoutException.newException( e ); } } public void onEndBuild( MetawidgetTag metawidgetTag ) {} }
Like Inspectors, InspectionResultProcessors, WidgetBuilders and WidgetProcessors, Layouts are required to be immutable. However they will generally need to store some internal state, such as tracking the current row in a table layout. This can be achieved in two ways:
For state that will remain constant throughout the life of the Layout, such as a CSS class to put on a generated HTML table, use xxxLayoutConfig classes. For example:
public class HtmlTableLayoutConfig { private String mTableStyle; public HtmlTableLayoutConfig setTableStyle( String tableStyle ) { mTableStyle = tableStyle; return this; } public String getTableStyleClass() { return mTableStyleClass; } // ...must override equals and hashCode too... }
These xxxLayoutConfig classes are then passed to the Layout at construction time, and stored internally:
public class HtmlTableLayout { private String mTableStyle; public HtmlTableLayout() { this( new HtmlTableLayoutConfig() ); } public HtmlTableLayout( HtmlTableLayoutConfig config ) { mTableStyle = config.getTableStyle(); } }
This mechanism can then be controlled either programmatically:
metawidget.setLayout( new HtmlTableLayout( new HtmlTableLayoutConfig().setTableStyleClass("foo")));
Or in metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):
<htmlTableLayout xmlns="java:org.metawidget.jsp.tagext.html.layout" config="HtmlTableLayoutConfig"> <tableStyleClass> <string>foo</string> </tableStyleClass> </htmlTableLayout>
Config classes must override equals and hashCode | |
---|---|
If you want your configurable Layout to be cacheable and reusable by ConfigReader and metawidget.xml, the xxxLayoutConfig class must override equals and hashCode. |
Generate an XML Schema | |
---|---|
If you intend your Layout to be configurable via metawidget.xml, consider defining an XML Schema for it. This is optional, but allows users to validate their use of your Layout in their metawidget.xml at development time. There is an Ant task, org.metawidget.config.XmlSchemaGeneratorTask, provided in the source distribution that can help with this by auto-generating the schema. All the existing Metawidget schemas are generated using this Ant task. |
For state that will change during laying out, such as tracking the current row, store it in the Metawidget or container that is passed to onStartBuild, startContainerLayout, layoutChild, endContainerLayout and onEndBuild. You may want to further wrap the state in a small helper class, for example:
public void layoutChild( JComponent component, String elementName, Map<String, String> attributes, JComponent container, SwingMetawidget metawidget ) { getState( container ).currentRow++; } private State getState( SwingMetawidget metawidget ) { State state = (State) container.getClientProperty( getClass() ); if ( state == null ) { state = new State(); container.putClientProperty( getClass(), state ); } return state; } static class State { int currentRow; ...other state variables... }
You could also consider storing the state in a ThreadLocal. If you do this, be aware that because Layouts can decorate each other, they can be called in a re-entrant fashion inside the same Thread.