/*
* Copyright 2014 cruxframework.org.
*
* 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 org.cruxframework.crux.core.client.screen.views;
import java.util.logging.Logger;
import org.cruxframework.crux.core.client.Crux;
import org.cruxframework.crux.core.client.collection.Array;
import org.cruxframework.crux.core.client.collection.CollectionFactory;
import org.cruxframework.crux.core.client.collection.Map;
import org.cruxframework.crux.core.client.screen.InterfaceConfigException;
import org.cruxframework.crux.core.client.screen.views.View.RenderCallback;
import org.cruxframework.crux.core.client.screen.views.ViewFactory.CreateCallback;
import org.cruxframework.crux.core.client.utils.StringUtils;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.logging.client.LogConfiguration;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;
/**
* @author Thiago da Rosa de Bustamante
*
*/
public abstract class ViewContainer extends Composite implements HasViewActivateHandlers, HasViewLoadHandlers
{
protected static Logger logger = Logger.getLogger(ViewContainer.class.getName());
private static ViewFactory viewFactory;
protected Array<ViewActivateHandler> attachHandlers = null;
protected Array<ViewDeactivateHandler> detachHandlers = null;
protected Array<ViewLoadHandler> loadHandlers = null;
protected Array<ViewUnloadHandler> unloadHandlers = null;
protected Map<View> views = CollectionFactory.createMap();
private final boolean clearPanelsForDeactivatedViews;
private final Widget mainWidget;
/**
* Constructor
* @param mainWidget main widget on this container
*/
public ViewContainer(Widget mainWidget)
{
this(mainWidget, true);
}
/**
* Constructor
* @param mainWidget Main widget on this container
* @param clearPanelsForDeactivatedViews If true, makes the container clear the container panel for a view, when the view is deactivated.
*/
public ViewContainer(Widget mainWidget, boolean clearPanelsForDeactivatedViews)
{
this.mainWidget = mainWidget;
if (mainWidget != null)
{
this.mainWidget.addAttachHandler(new Handler()
{
@Override
public void onAttachOrDetach(AttachEvent event)
{
if (event.isAttached())
{
bindToDOM();
}
else
{
unbindToDOM();
}
}
});
initWidget(this.mainWidget);
}
this.clearPanelsForDeactivatedViews = clearPanelsForDeactivatedViews;
ViewHandlers.initializeWindowContainers();
}
/**
* Loads a new view into the container
* @param view View to be added
* @return
*/
public boolean add(View view)
{
return add(view, false, null);
}
/**
* Adds a new view into the container, but does not load the view.
* @param viewName Name of the View to be added
* @return
*/
public boolean addLazy(String viewName)
{
return addLazy(viewName, viewName);
}
/**
* Adds a new view into the container, but does not load the view.
* @param viewName Name of the View to be added
* @param viewId ID of the View to be added
* @return
*/
public boolean addLazy(String viewName, String viewId)
{
createView(viewName, viewId, new CreateCallback()
{
@Override
public void onViewCreated(View view)
{
addView(view, true, null);
}
});
return true;
}
/**
* Adds a new view into the container, but does not load the view.
* @param view View to be added
* @return
*/
public boolean addLazy(View view)
{
return addView(view, true, null);
}
@Override
public HandlerRegistration addViewActivateHandler(final ViewActivateHandler handler)
{
if (attachHandlers == null)
{
attachHandlers = CollectionFactory.createArray();
}
attachHandlers.add(handler);
return new HandlerRegistration()
{
@Override
public void removeHandler()
{
int index = attachHandlers.indexOf(handler);
if (index >= 0)
{
attachHandlers.remove(index);
}
}
};
}
@Override
public HandlerRegistration addViewDeactivateHandler(final ViewDeactivateHandler handler)
{
if (detachHandlers == null)
{
detachHandlers = CollectionFactory.createArray();
}
detachHandlers.add(handler);
return new HandlerRegistration()
{
@Override
public void removeHandler()
{
int index = detachHandlers.indexOf(handler);
if (index >= 0)
{
detachHandlers.remove(index);
}
}
};
}
@Override
public HandlerRegistration addViewLoadHandler(final ViewLoadHandler handler)
{
if (loadHandlers == null)
{
loadHandlers = CollectionFactory.createArray();
}
loadHandlers.add(handler);
return new HandlerRegistration()
{
@Override
public void removeHandler()
{
int index = loadHandlers.indexOf(handler);
if (index >= 0)
{
loadHandlers.remove(index);
}
}
};
}
@Override
public HandlerRegistration addViewUnloadHandler(final ViewUnloadHandler handler)
{
if (unloadHandlers == null)
{
unloadHandlers = CollectionFactory.createArray();
}
unloadHandlers.add(handler);
return new HandlerRegistration()
{
@Override
public void removeHandler()
{
int index = unloadHandlers.indexOf(handler);
if (index >= 0)
{
unloadHandlers.remove(index);
}
}
};
}
/**
* Remove all view inside this container
*/
public void clear()
{
Array<String> keys = views.keys();
for (int i=0; i< keys.size(); i++)
{
remove(getView(keys.get(i)), true);
}
}
/**
* Retrieve the view associated to viewId
* @param viewId View identifier
* @return The view
*/
public View getView(String viewId)
{
if (viewId == null)
{
return null;
}
return views.get(viewId);
}
/**
* Loads a view into the current container
*
* @param viewName View name
* @param render If true also render the view
*/
public void loadView(String viewName, final boolean render)
{
loadView(viewName, viewName, render);
}
/**
* Loads a view into the current container
*
* @param viewName View name
* @param viewId View identifier
* @param render If true also render the view
*/
public void loadView(final String viewName, final String viewId, final boolean render)
{
loadView(viewName, viewId, render, null);
}
/**
* Remove the view from this container
* @param view View to be removed
* @return
*/
public boolean remove(View view)
{
return remove(view, false);
}
/**
* Remove the view from this container
* @param view View to be removed
* @param skipEvents skip the events fired during view removal
* @return
*/
public boolean remove(View view, boolean skipEvents)
{
assert (view != null):"Can not remove a null view from the ViewContainer";
if (doRemove(view, skipEvents))
{
view.setContainer(null);
return true;
}
return false;
}
/**
* Render the requested view into the container.
* @param viewName View name
*/
public void showView(String viewName)
{
showView(viewName, viewName);
}
/**
* Render the requested view into the container.
* @param viewId View identifier
* @param viewId View name
*/
public void showView(String viewName, String viewId)
{
showView(viewName, viewId, null);
}
/**
* This method must be called by subclasses when any of your views is rendered.
* @param view
* @param containerPanel
* @param parameter
*/
protected boolean activate(final View view, Panel containerPanel, final Object parameter)
{
if (!view.isLoaded())
{
if (view.load(parameter))
{
fireLoadEvent(view, parameter);
}
}
view.render(containerPanel, new RenderCallback()
{
@Override
public void onRendered()
{
view.setActive(parameter);
}
});
return true;
}
/**
* Loads a new view into the container
* @param view View to be added
* @param render If true, call the render method
* @param parameter A parameter passed that will be bound to the view load and activate events
* @return true if the view is loaded into the container
*/
protected boolean add(View view, boolean render, Object parameter)
{
if (addView(view, false, parameter))
{
if (render)
{
renderView(view, parameter);
}
return true;
}
return false;
}
/**
*
* @param view
* @param lazy
* @return
*/
protected boolean addView(View view, boolean lazy, Object parameter)
{
assert (view != null):"Can not add a null view to the ViewContainer";
assert (getView(view.getId()) == null):"This container already contains a view with the given identifier ["+view.getId()+"].";
if (doAdd(view, lazy, parameter))
{
adoptView(view);
return true;
}
return false;
}
/**
*
* @param view
*/
protected void adoptView(View view)
{
view.setContainer(this);
}
/**
* This method must be called by subclasses when the container is attached to DOM
*/
protected void bindToDOM()
{
ViewHandlers.bindToDOM(this);
}
/**
* This method must be called by subclasses when any of your views currently rendered is removed from view.
*
* @param view
* @param containerPanel
* @param skipEvent
* @return True if view is deactivated
*/
protected boolean deactivate(View view, Panel containerPanel, boolean skipEvent)
{
if (view.isActive())
{
if (view.setDeactivated(skipEvent))
{
if (this.clearPanelsForDeactivatedViews)
{
containerPanel.clear();
}
return true;
}
return false;
}
return true;
}
/**
* Loads a new view into the container
* @param view View to be added
* @return
*/
protected boolean doAdd(View view, boolean lazy, Object parameter)
{
if (!views.containsKey(view.getId()))
{
views.put(view.getId(), view);
if (!lazy)
{
if (view.load(parameter))
{
fireLoadEvent(view, parameter);
}
}
return true;
}
return false;
}
/**
*
* @param view
* @param skipEvent
* @return
*/
protected boolean doRemove(View view, boolean skipEvent)
{
if (views.containsKey(view.getId()))
{
Panel containerPanel = getContainerPanel(view);
boolean active = view.isActive();
if (deactivate(view, containerPanel, skipEvent) && (skipEvent || view.unload()))
{
views.remove(view.getId());
if (active)
{
containerPanel.clear();
}
return true;
}
}
return false;
}
/**
* Fire the activate event
* @param event
*/
protected void fireActivateEvent(ViewActivateEvent event)
{
if (attachHandlers != null)
{
for (int i = 0; i < attachHandlers.size(); i++)
{
ViewActivateHandler handler = attachHandlers.get(i);
handler.onActivate(event);
}
}
}
/**
* Fire the deactivate event
* @param event
*/
protected void fireDeactivateEvent(ViewDeactivateEvent event)
{
if (detachHandlers != null)
{
for (int i = 0; i < detachHandlers.size(); i++)
{
ViewDeactivateHandler handler = detachHandlers.get(i);
handler.onDeactivate(event);
}
}
}
/**
* Fires the load event
*/
protected void fireLoadEvent(View view, Object parameter)
{
if (loadHandlers != null)
{
ViewLoadEvent event = new ViewLoadEvent(view, parameter);
for (int i = 0; i < loadHandlers.size(); i++)
{
ViewLoadHandler handler = loadHandlers.get(i);
handler.onLoad(event);
}
}
}
/**
* Fires the unload event.
* @return true if the view can be unloaded. If any event handler cancel the event,
* the view is not unloaded
*/
protected boolean fireUnloadEvent(ViewUnloadEvent event)
{
boolean canceled = false;
if (unloadHandlers != null)
{
for (int i = 0; i < unloadHandlers.size(); i++)
{
ViewUnloadHandler handler = unloadHandlers.get(i);
handler.onUnload(event);
if (event.isCanceled())
{
canceled = true;
}
}
}
return !canceled;
}
protected abstract Panel getContainerPanel(View view);
@SuppressWarnings("unchecked")
protected <T extends Widget> T getMainWidget()
{
return (T) mainWidget;
}
protected abstract void handleViewTitle(String title, Panel containerPanel, String viewId);
protected void loadAndRenderView(final String viewName, final String viewId, final Object parameter)
{
try
{
if (LogConfiguration.loggingIsEnabled())
{
logger.info(Crux.getMessages().viewContainerCreatingView(viewId));
}
createView(viewName, viewId, new CreateCallback()
{
@Override
public void onViewCreated(View view)
{
if (addView(view, false, parameter))
{
renderView(view, parameter);
}
else
{
Crux.getErrorHandler().handleError(Crux.getMessages().viewContainerErrorCreatingView(viewId));
}
}
});
}
catch (InterfaceConfigException e)
{
Crux.getErrorHandler().handleError(Crux.getMessages().viewContainerErrorCreatingView(viewId), e);
}
}
/**
* Loads a view into the current container
*
* @param viewName View name
* @param viewId View identifier
* @param parameter A parameter passed that will be bound to the view load and activate events
* @param render If true also render the view
*/
protected void loadView(final String viewName, final String viewId, final boolean render, final Object parameter)
{
try
{
if (LogConfiguration.loggingIsEnabled())
{
logger.info(Crux.getMessages().viewContainerCreatingView(viewId));
}
createView(viewName, viewId, new CreateCallback()
{
@Override
public void onViewCreated(View view)
{
if (!add(view, render, parameter))
{
Crux.getErrorHandler().handleError(Crux.getMessages().viewContainerErrorCreatingView(viewId));
}
}
});
}
catch (InterfaceConfigException e)
{
Crux.getErrorHandler().handleError(Crux.getMessages().viewContainerErrorCreatingView(viewId), e);
}
}
protected abstract void notifyViewsAboutHistoryChange(ValueChangeEvent<String> event);
protected abstract void notifyViewsAboutOrientationChange(String orientation);
protected abstract void notifyViewsAboutWindowClose(CloseEvent<Window> event);
protected abstract void notifyViewsAboutWindowClosing(ClosingEvent event);
protected abstract void notifyViewsAboutWindowResize(ResizeEvent event);
/**
* Render the view into the container
* @param view
* @param parameter
*/
protected boolean renderView(View view, Object parameter)
{
assert (view!= null && views.containsKey(view.getId())):"Can not render the view["+view.getId()+"]. It was not added to the container";
Panel containerPanel = getContainerPanel(view);
boolean activated = activate(view, containerPanel, parameter);
if (activated)
{
String title = view.getTitle();
if (!StringUtils.isEmpty(title))
{
handleViewTitle(title, containerPanel, view.getId());
}
}
return activated;
}
/**
* Render the requested view into the container.
* @param viewName View name
* @param parameter to be passed to activate event
*/
protected void showView(String viewName, Object parameter)
{
showView(viewName, viewName, parameter);
}
/**
* Render the requested view into the container.
* @param viewId View identifier
* @param viewId View name
* @param parameter to be passed to activate event
*/
protected void showView(String viewName, String viewId, Object parameter)
{
assert (!StringUtils.isEmpty(viewId)) : "View [" + viewName + "] must have an id.";
View view = getView(viewId);
if (view != null)
{
if (!view.isActive())
{
renderView(view, parameter);
}
}
else
{
loadAndRenderView(viewName, viewId, parameter);
}
}
/**
* This method must be called by subclasses when the container is detached from DOM
*/
protected void unbindToDOM()
{
ViewHandlers.unbindToDOM(this);
}
/**
* Creates the view referenced by the given name
*
* @param viewName View name
* @param callback Called when the view creation is completed.
*/
public static void createView(String viewName, CreateCallback callback)
{
getViewFactory().createView(viewName, callback);
}
/**
* Creates the view referenced by the given name and associate a custom identifier with the view created
*
* @param viewName View name
* @param viewId View identifier
* @param callback Called when the view creation is completed.
*/
public static void createView(String viewName, String viewId, CreateCallback callback)
{
getViewFactory().createView(viewName, viewId, callback);
}
/**
* Retrieve the views factory associated with this screen.
* @return
*/
public static ViewFactory getViewFactory()
{
if (viewFactory == null)
{
viewFactory = (ViewFactory) GWT.create(ViewFactory.class);
}
return viewFactory;
}
}