Presentation Tier
We use jdal-swing to make the presentation layer of the library application. Before begining coding the presentation classes, we need to know how jdal-swing resolves the common problems of presentation programming. Those problems are:
- Creating views to show model data to user and edit them
- Binding from UI controls to models.
- Validating data on presentation tier
This is done using (oh! surprise) Views, Validators and Binders. Jdal swing library tries to make this process as simple as possible, increasing the productivity by developing this layer.
Views
The interface View<T> contains the basic operations for model Views:
- T getModel(): Gets the model associated with the View.
- String getName(): Returns the View name.
- setModel(T model): Sets de model
- refresh(): Update visual controls from model
- update(): Update model from visual controls
- getPanel(): Gets the JComponent container
- validateView(): Performs model validation
- clear(): Clear the form data to their default values.
AbstractView is a Template (GoF) that support automatic binding the UI controls to model properties. The template defines four callbacks we only need to implement one of them. the buildPanel()
- void onRefresh(): Called after refresh to let you add operations that are not supported by AbstractView
- void onUpdate(): similar to onRefresh() for updates.
- onSetModel(): called to report that the model change
- JCompnent buildPanel (): abstract method that must be implemented to build the JComponent form.
Lets go to implement the three Views: AuthorView, BookView and FilterView.
AuthorView
To create AuthorView we extend AbstractView <Author> and implement two methods:
- init(): Configure this method as init-method in applicationContext-view, so spring container call them after property injection. Use that to bind model properties and the two JTextFields using the AbstractView supplied bind(component, propertyName) method.
- buildPanel(): Create a JComponent with the help of BoxFormBuilder, an index based component builder using javax.swing.Box.
AbstractView finds the appropiate binder (TexComponentBinder) using the configured BinderFactory. So usually, we´ll only need to call bind method with the control and the property name that we want to bind on the model.
public class AuthorView extends AbstractView<Author> { private JTextField name = new JTextField(25); private JTextField surname = new JTextField(25); public AuthorView() { this(new Author()); } public AuthorView(Author author) { setModel(author); refresh(); } public void init() { bind(name, "name"); bind(surname, "surname"); } @Override public JComponent buildPanel() { BoxFormBuilder fb = new BoxFormBuilder(); fb.add("Name: ", name); fb.row(); fb.add("Surname: ", surname); JComponent form = fb.getForm(); form.setBorder(FormUtils.createTitledBorder("Author")); return form; } }
follow the spring context configuration of AuthorView in applicationContext-view.xml
<!-- Abstract bean definition for Views --> <bean id="view" abstract="true"> <property name="binderFactory" ref="binderFactory" /> <property name="messageSource" ref="messageSource" /> </bean> <!-- AuthroView --> <bean id="authorView" class="org.jdal.samples.library.ui.AuthorView" scope="prototype" parent="view" /> <!-- AuthorDialgo that save Author on AcceptAction --> <bean id="authorDialog" class="info.joseluismartin.gui.ViewDialog" scope="prototype"> <property name="view" ref="authorView" /> <property name="acceptAction" ref="acceptAction" /> <property name="cancelAction" ref="cancelAction" /> </bean> <!-- Generic AcceptAction for dialogs, save models using <Object> persistentService --> <bean id="acceptAction" class="info.joseluismartin.gui.action.ViewSaveAction" scope="prototype"> <property name="icon" value="/images/16x16/dialog-ok.png" /> <property name="service" ref="persistentService" /> <property name="name" value="Accept" /> </bean> <!-- Generic CancelAction for ViewDialogs, close dialog --> <bean id="cancelAction" class="info.joseluismartin.gui.action.DialogCancelAction" scope="prototype"> <property name="icon" value="/images/16x16/dialog-cancel.png" /> <property name="name" value="Cancel" /> </bean>
BookView
For BookView we´ll follow the same strategy as for the author. Although this view is more complicated, the procedure is essentially the same.
In this case it is necessary to load in the JcomboBox the list of categories that we are going to use to select/display the category of a book. The ComboBoxModel ListComboBoxModel is used as a list container.
We also need the GuiFactory instance, a simple wrapper for the Spring ApplicationContext, to get instances of the bean "authorDialog" in order to add an author that is not already in the database.
Finally we add autocompletion to author combo using the hibernate filter that we showed earlier in the Author class. We create an instance of FilterAutoCompletionListener and set the author persistent service.
public class BookView extends AbstractView<Book> { private static final String ADD_ICON = "/images/16x16/list-add.png"; private JTextField name = new JTextField(); private JTextField isbn = new JTextField(); private JComboBox author = FormUtils.newCombo(25); private JCalendarCombo published = FormUtils.newJCalendarCombo(); private JComboBox category = FormUtils.newCombo(25); private String authorEditor = "authorEditor"; private GuiFactory guiFactory; private PersistentService<Category, Long> categoryService; private AuthorService authorService; public BookView() { this(new Book()); } public BookView(Book book) { setModel(book); } public void init() { autobind(); // bind controls to model by property name } @Override protected JComponent buildPanel() { // fill category combo with data from database. category.setModel(new ListComboBoxModel(categoryService.getAll())); // Add auto-completion to author combo, limit max results to 1000 and order data by surname FilterAutoCompletionListener acl = new FilterAutoCompletionListener(author, 1000, "surname"); acl.setPersistentService(authorService); author.setEditable(true); // Create a Box with author combo and add button Box authorBox = Box.createHorizontalBox(); authorBox.add(author); authorBox.add(Box.createHorizontalStrut(5)); authorBox.add(new JButton(new AddAuthorAction(FormUtils.getIcon(ADD_ICON)))); // Build Form with a BoxFormBuilder BoxFormBuilder fb = new BoxFormBuilder(); fb.add("Title: ", name); fb.row(); fb.add("Author: ", authorBox); fb.row(); fb.add("ISBN: ", isbn); fb.row(); fb.add("Published Date:", published); fb.row(); fb.add("Category", category); JComponent form = fb.getForm(); form.setBorder(FormUtils.createTitledBorder("Book")); return form; } private class AddAuthorAction extends AbstractAction { public AddAuthorAction(Icon icon) { putValue(Action.SMALL_ICON, icon); } public void actionPerformed(ActionEvent e) { ViewDialog dlg = (ViewDialog) guiFactory.getDialog(authorEditor); dlg.setModal(true); dlg.setVisible(true); if (dlg.getValue() == ViewDialog.OK) { getModel().setAuthor((Author) dlg.getModel()); refresh(); } } } // Getters and Setters ... }
BookFilterView
Last view refers to BookFilter. Again. we´ll bind the controls and properties in init() method and create the JComponent in buildPanel() method
public class BookFilterView extends AbstractView<BookFilter> { private JTextField name = new JTextField(20); private JTextField authorName = new JTextField(20); private JTextField authorSurname = new JTextField(20); private JCalendarCombo before = FormUtils.newJCalendarCombo(); private JCalendarCombo after = FormUtils.newJCalendarCombo(); private JComboBox category = FormUtils.newCombo(20); private PersistentService<Category, Long> categoryService; public BookFilterView() { this(new BookFilter()); } public BookFilterView(BookFilter filter) { setModel(filter); } public void init() { bind(name, "name"); bind(authorName, "authorName"); bind(authorSurname, "authorSurname"); bind(before, "before"); bind(after, "after"); bind(category, "category"); } @Override protected JComponent buildPanel() { BoxFormBuilder fb = new BoxFormBuilder(); fb.add("Title: ", name); fb.add("Author Name: ", authorName); fb.add("Author Surname: ", authorSurname); fb.row(); fb.add("Category: ", category); fb.add("Published Before: ", before); fb.add("Published After: ", after); JComponent box = fb.getForm(); box.setBorder(FormUtils.createTitledBorder("Book Filter")); return box; } @Override public void doRefresh() { List<Category> categories = categoryService.getAll(); categories.add(0, null); category.setModel(new ListComboBoxModel(categories)); } // Getters and Setters... }
This view finished the Java programming in the applicaton. We still need three tables, but we can get them from the library by configuration only.
Pageable Table
PageableTable it's a swing JPanel containig a JTable, a PaginatorView, a ListTableModel and a PageableDataSource.
The class ListTableModel is a TableModel that allows easy configuration from context configuration files. The method setComlumns(Columns columns) on ListTableModel allows to set column definitions as follows:
<bean id="bookTableModel" class="info.joseluismartin.gui.ListTableModel" scope="prototype"> <property name="modelClass" value="org.jdal.samples.library.model.Book"/> <property name="columns"> <list value-type="info.joseluismartin.gui.ColumnDefinition"> <bean class="info.joseluismartin.gui.ColumnDefinition"> <property name="name" value="name"/> <property name="displayName" value="Name"/> </bean> <bean class="info.joseluismartin.gui.ColumnDefinition"> <property name="name" value="author"/> <property name="displayName" value="Author"/> <property name="sortProperty" value="author.name"/> </bean> <bean class="info.joseluismartin.gui.ColumnDefinition"> <property name="name" value="category"/> <property name="displayName" value="Category"/> <property name="sortProperty" value="category.name"/> </bean> <bean class="info.joseluismartin.gui.ColumnDefinition"> <property name="name" value="isbn"/> <property name="displayName" value="ISBN"/> </bean> </list> </property> <property name="usingActions" value="true"/> <property name="usingChecks" value="true"/> </bean>
In the inner bean Column we can set up this properties:
- name: the property name that this column has in the model.
- displayName: The String to show in the TableHeader.
- editable: true if the cell is editable.
- width: the prefered width to set in TableColumn.
- sortProperty: the name of the property when sorting this column. for example 'author.name'.
- renderer: the TableCellRenderer to configure in TableColumn.
- editor: the TableCellEditor to configure in TableColumn.
- clazz: the property class.
By using the actions property we can set the RowActions. On the other hand,the usingChecks property let us show a column of checkboxes in order to select rows in the table.
Table Panel
TablePanel is a JPanel with a PageabeTable, a button panel and a filterPanel as show in following figure:
The button panel is filled with TablePanelActions that are simply Actions with a TablePanel reference. Writing Actions to show in the TablePanel is easy, you only have to extend TablePanelAction, implement the actionPerformed method, and add the Action to ActionList in TablePanel bean definition. The library provides defaults action for adding, deleteting, finding, hiding/showing filters, as shown before.
The following box show the configuration context details of the sample book TablePanel.
<!-- TablePanel abstract configuration -->" <bean name="tablePanel" abstract="true"> <!-- Default Actions for TablePanel --> <property name="actions"> <list value-type="java.awt.Action"> <bean class="info.joseluismartin.gui.table.AddAction" /> <bean class="info.joseluismartin.gui.table.SelectAllAction" /> <bean class="info.joseluismartin.gui.table.DeselectAllAction" /> <bean class="info.joseluismartin.gui.table.RemoveAllAction" /> <bean class="info.joseluismartin.gui.table.HideShowFilterAction" /> <bean class="info.joseluismartin.gui.table.ApplyFilterAction" /> <bean class="info.joseluismartin.gui.table.ClearFilterAction" /> </list> </property> </bean> <!-- Paginator for PageableTable --> <bean id="paginatorView" class="info.joseluismartin.gui.PaginatorView" scope="prototype"> <property name="firstIcon" value="images/table/22x22/go-first.png" /> <property name="lastIcon" value="images/table/22x22/go-last.png" /> <property name="nextIcon" value="images/table/22x22/go-next.png" /> <property name="previousIcon" value="images/table/22x22/go-previous.png" /> <property name="pageSizes" value="10,20,30,40,50,100,All" /> </bean> <!-- Pageable Table of Books --> <bean id="bookTable" class="info.joseluismartin.gui.PageableTable"> <property name="dataSource" ref="bookService" /> <property name="tableModel" ref="bookTableModel" /> <property name="paginatorView" ref="paginatorView" /> </bean> <!-- Book TablePanel --> <bean id="bookTablePanel" class="info.joseluismartin.gui.table.TablePanel" parent="tablePanel"> <property name="table" ref="bookTable" /> <property name="filterView" ref="filterView" /> <!-- bean name of model editor, show it on double-clicks on rows --> <property name="editorName" value="bookDialog" /> <property name="guiFactory" ref="guiFactory" /> <property name="persistentService" ref="bookService" /> </bean> <bean id="filterView" class="org.jdal.samples.library.ui.BookFilterView" parent="view"> <property name="categoryService" ref="categoryService" /> </bean>
List Panel
Finally, we set up a ListPane (a JComponent container using a JList similar to JTabbedPane) to hold the book table panel and the autor and category table editors. ListPane accept only PanelHolders as components, so we need to wrap the Views and JComponents with an inner bean definition of appropiate PanelHolder:
<bean id="listPanel" class="info.joseluismartin.gui.ListPane"> <property name="panels"> <list value-type="info.joseluismartin.gui.PanelHolder"> <bean class="info.joseluismartin.gui.JComponentPanelHolder"> <property name="name" value="Books" /> <property name="component" ref="bookTablePanel" /> </bean> <bean class="info.joseluismartin.gui.ViewPanelHolder"> <property name="view" ref="authorEditor" /> </bean> <bean class="info.joseluismartin.gui.ViewPanelHolder"> <property name="view" ref="categoryEditor" /> </bean> </list> </property> </bean>