All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gwtplatform.mvp.client.PresenterWidget Maven / Gradle / Ivy

There is a newer version: 1.6
Show newest version
/**
 * Copyright 2011 ArcBees Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.gwtplatform.mvp.client;

import com.google.web.bindery.event.shared.EventBus;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.GwtEvent.Type;
import com.google.web.bindery.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.HasHandlers;
import com.google.gwt.user.client.ui.Widget;

import com.gwtplatform.mvp.client.proxy.ResetPresentersEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A presenter that does not have to be a singleton. Pages from your
 * application will usually be singletons and extend the {@link Presenter} class.
 * 

* Choosing between a {@link Presenter} and {@link PresenterWidget} is a design decision that * requires some thought. For example, a {@link PresenterWidget} is useful when * you need a custom widget with extensive logic. For example, a chat box that can be instantiated * multiple times and needs to communicate with the server would be a good candidate for * a {@link PresenterWidget}. The drawback of a {@link PresenterWidget} is that it is managed by its * parent presenter, which increases coupling. Therefore, you should use a {@link Presenter} when * the parent is not expected to know its child. Moreover, only {@link Presenter} can be attached * to name tokens in order to support browser history. *

* {@link PresenterWidget}s and {@link Presenter}s are organized in a hierarchy. * Internally, parent presenters have links to their currently attached children presenters. A * parent {@link Presenter} can contain either {@link Presenter}s or {@link PresenterWidget}s, * but a {@link PresenterWidget} can only contain {@link PresenterWidget}s. *

* To reveal a {@link PresenterWidget} you should insert it within a {@link HasSlots slot} of its * containing presenter using one of the following methods: *

    *
  • {@link #setInSlot(Object, PresenterWidget)} *
  • {@link #setInSlot(Object, PresenterWidget, boolean)} *
  • {@link #addToSlot(Object, PresenterWidget)} *
  • {@link #addToPopupSlot(PresenterWidget)} *
  • {@link #addToPopupSlot(PresenterWidget, boolean)} *
* Revealing a {@link Presenter} is done differently, refer to the class documentation for more details. *

* To hide a {@link PresenterWidget} or a {@link Presenter} you can use {@link #setInSlot} to place * another presenter in the same slot, or you can call one of the following methods: *

    *
  • {@link #removeFromSlot(Object, PresenterWidget)} *
  • {@link #clearSlot(Object)} *
  • {@link PopupView#hide()} if the presenter is a popup or a dialog box. *
* Hide a {@link Presenter} using these methods, but *

* A presenter has a number of lifecycle methods that you can hook on to: *

    *
  • {@link #onBind()} *
  • {@link #onReveal()} *
  • {@link #onReset()} *
  • {@link #onHide()} *
  • {@link #onUnbind()} *
* Revealing or hiding a {@link PresenterWidget} triggers an internal chain of events that result in * these lifecycle methods being called. For an example, here is what happens following * a call to {@link #setInSlot(Object, PresenterWidget)}: *
    *
  • If a presenter already occupies this slot it is removed.
  • *
    • If the presenter owning the slot is currently visible then * {@link #onHide()} is called on the removed presenter and, recursively, * on its children (bottom-up: first the children, then the parent)
    • *
    • If the parent is not visible and is a {@link Presenter}, it asks to be * set in one of its parent slot by firing a * {@link com.gwtplatform.mvp.client.proxy.RevealContentEvent RevealContentEvent}. * For more details, see the documentation for {@link Presenter}.
    • *
    *
  • If, at this point, the presenter owning the slot is not visible, then the * chain stops. Otherwise, {@link #onReveal()} is called on the {@link PresenterWidget} that * was just added.
  • *
  • {@link #onReveal()} is called recursively on that presenter's children * (top-down: first the parent, then the children).
  • *
  • If {@link #setInSlot(Object, PresenterWidget, boolean)} was called with {@code false} * as the third parameter then the process stops. Otherwise, {@link #onReset()} is * called on all the currently visible presenters (top-down: first the parent, then * the children).
  • *
* * @param The {@link View} type. * * @author Philippe Beaudoin * @author Christian Goudreau * @author Denis Labaye */ public abstract class PresenterWidget extends HandlerContainerImpl implements HasHandlers, HasSlots, HasPopupSlot { private final EventBus eventBus; private final V view; boolean visible; /** * This map makes it possible to keep a list of all the active children in * every slot managed by this {@link PresenterWidget}. A slot is identified by an * opaque object. A single slot can have many children. */ private final Map>> activeChildren = new HashMap>>(); /** * The parent presenter, in order to make sure this widget is only ever in one * parent. */ private PresenterWidget currentParentPresenter; /** * This list tracks all the active children in popup slots managed by this * {@link PresenterWidget}. A slot is identified by an opaque object. A * single slot can have many children. */ private final List> popupChildren = new ArrayList>(); /** * Creates a {@link PresenterWidget} that is not necessarily using automatic * binding. Automatic binding will only work when instantiating this object using * Guice/GIN dependency injection. See * {@link HandlerContainerImpl#HandlerContainerImpl(boolean)} for * more details on automatic binding. * * @param autoBind {@code true} to request automatic binding, {@code false} otherwise. * @param eventBus The {@link EventBus}. * @param view The {@link View}. */ public PresenterWidget(boolean autoBind, EventBus eventBus, V view) { super(autoBind); this.eventBus = eventBus; this.view = view; } /** * Creates a {@link PresenterWidget} that uses automatic binding. This will * only work when instantiating this object using Guice/GIN dependency injection. * See {@link HandlerContainerImpl#HandlerContainerImpl()} for more details on * automatic binding. * * @param eventBus The {@link EventBus}. * @param view The {@link View}. */ public PresenterWidget(EventBus eventBus, V view) { this(true, eventBus, view); } @Override public final void addToPopupSlot( final PresenterWidget child) { addToPopupSlot(child, true); } @Override public final void addToPopupSlot( final PresenterWidget child, boolean center) { if (child == null) { return; } child.reparent(this); // Do nothing if the content is already added for (PresenterWidget popupPresenter : popupChildren) { if (popupPresenter == child) { return; } } final PopupView popupView = child.getView(); popupChildren.add(child); // Center if desired if (center) { popupView.center(); } // Display the popup content if (isVisible()) { popupView.show(); // This presenter is visible, its time to call onReveal // on the newly added child (and recursively on this child children) monitorCloseEvent(child); if (!child.isVisible()) { child.internalReveal(); } } } @Override public final void addToSlot(Object slot, PresenterWidget content) { if (content == null) { return; } content.reparent(this); List> slotChildren = activeChildren.get(slot); if (slotChildren != null) { slotChildren.add(content); } else { slotChildren = new ArrayList>(1); slotChildren.add(content); activeChildren.put(slot, slotChildren); } getView().addToSlot(slot, content.getWidget()); if (isVisible()) { // This presenter is visible, its time to call onReveal // on the newly added child (and recursively on this child children) content.internalReveal(); } } @Override public final void clearSlot(Object slot) { List> slotChildren = activeChildren.get(slot); if (slotChildren != null) { // This presenter is visible, its time to call onHide // on the children to be removed (and recursively on their children) if (isVisible()) { for (PresenterWidget activeChild : slotChildren) { activeChild.internalHide(); } } slotChildren.clear(); } getView().setInSlot(slot, null); } @Override public void fireEvent(GwtEvent event) { getEventBus().fireEventFromSource(event, this); } /** * Returns the {@link View} for the current presenter. * * @return The view. */ public V getView() { return view; } /** * Makes it possible to access the {@link Widget} object associated with that * presenter. * * @return The Widget associated with that presenter. */ public Widget getWidget() { return (getView() == null) ? null : getView().asWidget(); } /** * Verifies if the presenter is currently visible on the screen. A presenter * should be visible if it successfully revealed itself and was not hidden * later. * * @return {@code true} if the presenter is visible, {@code false} otherwise. */ public boolean isVisible() { return visible; } @Override public final void removeFromSlot(Object slot, PresenterWidget content) { if (content == null) { return; } content.reparent(null); List> slotChildren = activeChildren.get(slot); if (slotChildren != null) { // This presenter is visible, its time to call onHide // on the child to be removed (and recursively on itschildren) if (isVisible()) { content.internalHide(); } slotChildren.remove(content); } getView().removeFromSlot(slot, content.getWidget()); } // TODO This should be final but needs to be overriden in {@link // TabContainerPresenter} // We should be able to do this once we switch to an event-based mechanism for // highlighting tabs @Override public void setInSlot(Object slot, PresenterWidget content) { setInSlot(slot, content, true); } @Override public final void setInSlot(Object slot, PresenterWidget content, boolean performReset) { if (content == null) { // Assumes the user wants to clear the slot content. clearSlot(slot); return; } content.reparent(this); List> slotChildren = activeChildren.get(slot); if (slotChildren != null) { if (slotChildren.size() == 1 && slotChildren.get(0) == content) { // The slot contains the right content, nothing to do return; } if (isVisible()) { // We are visible, make sure the content that we're removing // is being notified as hidden for (PresenterWidget activeChild : slotChildren) { activeChild.internalHide(); } } slotChildren.clear(); slotChildren.add(content); } else { slotChildren = new ArrayList>(1); slotChildren.add(content); activeChildren.put(slot, slotChildren); } // Set the content in the view getView().setInSlot(slot, content.getWidget()); if (isVisible()) { // This presenter is visible, its time to call onReveal // on the newly added child (and recursively on this child children) if (!content.isVisible()) { content.internalReveal(); } if (performReset) { // And to reset everything if needed ResetPresentersEvent.fire(this); } } } /** * Registers an event handler towards the {@link EventBus} and * registers it to be automatically removed when {@link #unbind()} * is called. This is usually the desired behavior, but if you * want to unregister handlers manually use {@link #addHandler} * instead. * * @see #addHandler(com.google.gwt.event.shared.GwtEvent.Type, EventHandler) * * @param The handler type. * @param type See {@link com.google.gwt.event.shared.GwtEvent.Type}. * @param handler The handler to register. */ protected final void addRegisteredHandler( Type type, H handler) { registerHandler(addHandler(type, handler)); } /** * Registers an event handler towards the {@link EventBus}. * Use this only in the rare situations where you want to manually * control when the handler is unregistered, otherwise call * {@link #addRegisteredHandler(com.google.gwt.event.shared.GwtEvent.Type, EventHandler)}. * * @param The handler type. * @param type See {@link com.google.gwt.event.shared.GwtEvent.Type}. * @param handler The handler to register. * @return The {@link HandlerRegistration} you should use to unregister the handler. */ protected final HandlerRegistration addHandler( Type type, H handler) { return getEventBus().addHandler(type, handler); } /** * Access the {@link EventBus} object associated with that presenter. * You should not usually use this method to interact with the event bus. * Instead call {@link #fireEvent}, {@link #addRegisteredHandler} or * {@link #addHandler}. * * @return The EventBus associated with that presenter. */ protected final EventBus getEventBus() { return eventBus; } /** * Lifecycle method called whenever this presenter is about to be * hidden. *

* Important: Make sure you call your superclass {@link #onHide()} if * you override. Also, do not call directly, see {@link PresenterWidget} * for more details on lifecycle methods. *

* You should override this method to dispose of any object * created directly or indirectly during the call to {@link #onReveal()}. *

* This method will not be invoked a multiple times without {@link #onReveal()} * being called. *

* In a presenter hierarchy, this method is called bottom-up: first on the * child presenters, then on the parent. */ protected void onHide() { } /** * Lifecycle method called on all visible presenters whenever a * presenter is revealed anywhere in the presenter hierarchy. *

* Important: Make sure you call your superclass {@link #onReset()} if * you override. Also, do not call directly, fire a {@link ResetPresentersEvent} * to perform a reset manually. See {@link PresenterWidget} for more details on * lifecycle methods. *

* This is one of the most frequently used lifecycle method. This is usually a good * place to refresh any information displayed by your presenter. *

* Note that {@link #onReset()} is not called only when using * {@link #addToSlot(Object, PresenterWidget)}, {@link #addToPopupSlot(PresenterWidget)} * or #setInSlot(Object, PresenterWidget, boolean)} with {@code false} as the third * parameter. *

* In a presenter hierarchy, this method is called top-down: first on the * parent presenters, then on the children. */ protected void onReset() { } /** * Lifecycle method called whenever this presenter is about to be * revealed. *

* Important: Make sure you call your superclass {@link #onReveal()} if * you override. Also, do not call directly, see {@link PresenterWidget} * for more details on lifecycle methods. *

* You should override this method to perform any action or initialisation * that needs to be done when the presenter is revealed. Any initialisation * you perform here should be taken down in {@link #onHide()}. *

* Information that needs to be updated whenever the user navigates should * be refreshed in {@link #onReset()}. *

* In a presenter hierarchy, this method is called top-down: first on the * parent presenters, then on the children. */ protected void onReveal() { } /** * Internal method called to hide a presenter. * See {@link PresenterWidget} for ways to hide a presenter. */ void internalHide() { assert isVisible() : "Hide() called on a hidden presenter!"; for (List> slotChildren : activeChildren.values()) { for (PresenterWidget activeChild : slotChildren) { activeChild.internalHide(); } } for (PresenterWidget popupPresenter : popupChildren) { popupPresenter.getView().setCloseHandler(null); popupPresenter.internalHide(); popupPresenter.getView().hide(); } visible = false; onHide(); } /** * Internal method called to reveal a presenter. * See {@link PresenterWidget} and {@link Presenter} for ways to reveal a * presenter. */ void internalReveal() { assert !isVisible() : "notifyReveal() called on a visible presenter!"; onReveal(); visible = true; for (List> slotChildren : activeChildren.values()) { for (PresenterWidget activeChild : slotChildren) { activeChild.internalReveal(); } } for (PresenterWidget popupPresenter : popupChildren) { // This presenter is visible, its time to call onReveal // on the newly added child (and recursively on this child children) popupPresenter.getView().show(); monitorCloseEvent(popupPresenter); popupPresenter.internalReveal(); } } /** * Detaches this presenter from its current parent and attaches it * to a new parent. * * @param newParent The new parent {@link PresenterWidget}. */ void reparent(PresenterWidget newParent) { if (currentParentPresenter != null && currentParentPresenter != newParent) { currentParentPresenter.detach(this); } currentParentPresenter = newParent; } /** * Internal method called to reset a presenter. Instead of using that method, * fire a {@link ResetPresentersEvent} to perform a reset manually. */ void internalReset() { onReset(); for (List> slotChildren : activeChildren.values()) { for (PresenterWidget activeChild : slotChildren) { activeChild.internalReset(); } } for (PresenterWidget popupPresenter : popupChildren) { popupPresenter.internalReset(); } } /** * Called by a child {@link PresenterWidget} when it wants to detach itself * from this parent. * * @param childPresenter The {@link PresenterWidget}. It should be a child of this presenter. */ private void detach(PresenterWidget childPresenter) { for (List> slotChildren : activeChildren.values()) { slotChildren.remove(childPresenter); } popupChildren.remove(childPresenter); } /** * Monitors the specified popup presenter so that we know when it * is closing. This allows us to make sure it doesn't receive * future messages. * * @param popupPresenter The {@link PresenterWidget} to monitor. */ private void monitorCloseEvent( final PresenterWidget popupPresenter) { PopupView popupView = popupPresenter.getView(); popupView.setCloseHandler(new PopupViewCloseHandler() { @Override public void onClose() { if (isVisible()) { popupPresenter.internalHide(); } removePopupChildren(popupPresenter); } }); } /** * Go through the popup children and remove the specified one. * * @param content The {@link PresenterWidget} added as a popup which we want to remove. */ private void removePopupChildren(PresenterWidget content) { int i; for (i = 0; i < popupChildren.size(); ++i) { PresenterWidget popupPresenter = popupChildren .get(i); if (popupPresenter == content) { (popupPresenter.getView()).setCloseHandler(null); break; } } if (i < popupChildren.size()) { popupChildren.remove(i); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy