package org.tessell.presenter; import java.util.ArrayList; import java.util.NoSuchElementException; import org.tessell.bus.AbstractBound; import org.tessell.gwt.user.client.ui.IsWidget; import org.tessell.model.dsl.Binder; /** A basic presenter that tracks bound handler registrations. */ public abstract class BasicPresenter<V extends IsWidget> extends AbstractBound implements Presenter { protected final Binder binder = new Binder(); protected final V view; private ArrayList<Presenter> children; public BasicPresenter(final V view) { this.view = view; } /** @return The view for the presenter. */ @Override public V getView() { // Previously we enforced that the presenter was bound, but this led to a lot // of false positives where: // 1. Presenter is bound, AJAX request fires in onBind() // 2. User clicks back, presenter is unbound // 3. AJAX response, fires onResult(), which tries to call getView() // 4. IllegalStateException on the presenter the user no longer cares about // // Perhaps ideally the AJAX command objects could be tied into the presenter // life cycle, and just immediately drop responses on the floor when on longer // bound. But it doesn't seem very harmful to let the now-disconnected onResult // update it's view, and then fairly soon get GC'd anyway. E.g. it should not // actively cause leaks. return view; } @Override protected void onBind() { super.onBind(); // allow handler registrations binder.bind(); // bind anybody that got added before we were bound if (children != null) { for (final Presenter child : children) { child.bind(); } } } @Override protected void onUnbind() { super.onUnbind(); if (children != null) { for (final Presenter child : children) { child.unbind(); } } binder.unbind(); } /** Adds {@code child} as a child presenter, and binds it if we're already bound. */ public <C extends Presenter> C addPresenter(final C child) { if (children().add(child)) { if (isBound()) { child.bind(); } } return child; } /** Removes {@code child} as a child presenter, and unbinds it. */ public void removePresenter(final Presenter child) { if (children().remove(child)) { child.unbind(); } else { throw new NoSuchElementException("Presenter was not a child of ours " + child); } } /** @return our list of child presenters, lazily instantiated. */ protected ArrayList<Presenter> children() { if (children == null) { children = new ArrayList<Presenter>(); } return children; } }