/* * This software is Copyright 2005,2006,2007,2008 Langdale Consultants. * Langdale Consultants can be contacted at: http://www.langdale.com.au */ package au.com.langdale.ui.plumbing; /** * A base class to supply form construction, refresh and update logic. * * Subclasses implement define() to return a Template instance. * These are canned layout and widget specifications. * (See Templates for an inventory of templates.) * * Clients call realise() to build a widget hierarchy conforming to * the Template returned by define(). realise() should only be called once. * It can be extended to further initialise the widgets. * * During realise(), most templates will register a widget or viewer instance * with putControl() or putViewer(). This enables the control to be accessed in * form update/refresh logic and unit tests via getControl() and getViewer(). * * Most templates will also hook their widgets to the fireWidgetEvent() * method via widget-specific event listeners. This is the main entry point * into the form event plumbing described below. * * Event plumbing is mediated by two interfaces. The Binding interface is * implemented by classes that transfer data to and from widgets and * interactively validate that data. See Binding for details. * * Binding methods for the form as a whole are implemented by stubs in * this class and may be overridden by subclasses. * * Binding methods for a specific widget or group of widgets may be implemented * by their template, which may be a subclass of BoundTemplate. * * The second plumbing interface is Observer, which is implemented by classes * that track form validation status and clean/dirty status. Observer methods * stubs are defined by this class and may be overridden by subclasses. * See interface Observer for details. * * When Binding and Observer methods are implemented as above they are * automatically hooked to the form event plumbing. (There is no need * to explicitly register the receiving objects with the event sources.) * * The form plumbing is responsible for delegating events to Binding * and Observer methods. The template Binding methods are called first, * then the whole-form Binding methods, and the Observer methods last. * The plumbing also triggers any necessary consequential events and * blocks widget event loops. * * Events are injected into the form plumbing by fireWidgetEvent() and its * counterpart doRefresh(). doRefresh() should be called following realise() * to initialise the widget values and again whenever the underlying data (the model) * changes. * * The event flow is influenced by the constructor's synchronous parameter. * In synchronous mode, the underlying data (the model) is kept synchronised * with the widgets by widget events. * * An additional method, fireUpdate() is used if synchronous mode * is not in effect. fireUpdate() explicitly commits user entries from * the widgets to the underlying data (the model). * */ public abstract class Plumbing implements Binding, ICanRefresh { private Controller controller; private Bindings bindings; private Plumbing parent; private Observer noObserver = new Observer() { public void markDirty() {} public void markInvalid(String message) {} public void markValid() {} }; /** * Construct a new set of form plumbing. * * The observer parameter is an object that will be notified * of the state of the assembly after each refresh or update cycle. * * The synchronous parameter indicates that the underlying model (data) * will be kept synchronised with the widgets by widget events. * If false, then fireUpdate() must be explicitly called * to transfer widget values to the underlying model. * */ public Plumbing(Observer observer, boolean synchronous) { bindings = new Bindings(); bindings.push(this); controller = new Controller(bindings, observer != null? observer: noObserver, synchronous); } /** * Construct a branch of a given parent Plumbing. */ public Plumbing(Plumbing parent) { controller = parent.controller; bindings = new Bindings(); bindings.push(this); parent.bindings.push(bindings); } /** * Release resources and registrations. */ public void dispose() { if( parent != null ) parent.bindings.remove(bindings); } /** * Default implementation of Binding. */ public void reset() {} /** * Default implementation of Binding. */ public void refresh() {} /** * Default implementation of Binding. */ public void update() {} /** * Default implementation of Binding. */ public String validate() { return null; } /** * The method should be called following realise() to initialise * the widget values and again whenever the underlying data (the model) * changes. * * This triggers refresh() and validate() while suppressing events that * would trigger update(). */ public final void doRefresh() { controller.doRefresh(); } /** * Initialise the widgets with default values. * This triggers reset() and validate(). * * If synchronous mode, update() and refresh() are also triggered. */ public final void doReset() { controller.doReset(); } /** * Commit user entries from the widgets to the underlying data (the model). * This triggers update(), refresh() and validate(). */ public final void fireUpdate() { controller.fireUpdate(); } /** * Trigger processing of a user action or entry. * * This method is normally hooked to widget events by the * various widget templates. * * Subclasses and unit tests might explicitly call this * if widget events are not sufficient to monitor widget state. * * This triggers validate(). If synchronous mode, * update() and refresh() are also triggered. */ public final void fireWidgetEvent() { controller.fireWidgetEvent(); } /** * Trigger validation. */ public final void fireValidate() { controller.fireValidate(); } /** * Add a delegate Binding to the top of the binding stack. * It will receive events before all previously added delegates. */ public void addBinding(Binding bind) { bindings.push(bind); } /** * Add a delegate Binding at a specific position in the stack. * It will receive events immediately after the given reference. */ public void addBinding(Binding bind, Object after) { bindings.push(bind, after); } }