package de.tud.kom.socom.web.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import de.tud.kom.socom.web.client.achievements.AchievementPresenter;
import de.tud.kom.socom.web.client.administration.AdministrationPresenter;
import de.tud.kom.socom.web.client.baseelements.AbstractMainPresenter;
import de.tud.kom.socom.web.client.baseelements.MainPresenter;
import de.tud.kom.socom.web.client.baseelements.Presenter;
import de.tud.kom.socom.web.client.content.ContentPresenter;
import de.tud.kom.socom.web.client.eventhandler.CommunicationFailureEventHandler;
import de.tud.kom.socom.web.client.events.CommunicationFailureEvent;
import de.tud.kom.socom.web.client.events.GameChangeEvent;
import de.tud.kom.socom.web.client.events.ViewChangePresenterEvent;
import de.tud.kom.socom.web.client.events.ViewChangeWithinPresenterEvent;
import de.tud.kom.socom.web.client.games.GamesPresenter;
import de.tud.kom.socom.web.client.influence.InfluenceListPresenter;
import de.tud.kom.socom.web.client.influence.InfluencePresenter;
import de.tud.kom.socom.web.client.login.LoginManager;
import de.tud.kom.socom.web.client.login.LoginOAuthWindowPresenter;
import de.tud.kom.socom.web.client.login.LoginPresenter;
import de.tud.kom.socom.web.client.navigation.NavigationPresenter;
import de.tud.kom.socom.web.client.profile.ProfilePresenter;
import de.tud.kom.socom.web.client.util.RequestInformation;
import de.tud.kom.socom.web.client.util.notfound.PageNotFoundPresenter;
/** central class that provides the access to Handlers and Manager instances to callers
*
* @author jkonert
*
*/
public class AppController implements ValueChangeHandler<String>, CommunicationFailureEventHandler {
public static final boolean DEBUG = true;
/** used for binding Presenters/Views to RootPanel IDs **/
protected enum RootIDs
{
// ids of "helper" elements for navigation etc. (not to be set by Presenters normally)
navigation,
login,
breadcrumb,
footerContent1,
footerContent2,
footerContent3,
// IDs of main interest for Presenters to set content to via AppController.setPageElement(..)
headerText,
headerLogo,
teaserImage,
main
}
/** these are the parts of the page/template that can be replaced, etc. by Presenters via AppController **/
public enum PageElementIDs
{
breadcrumb(RootIDs.breadcrumb),
headerText(RootIDs.headerText),
headerLogo(RootIDs.headerLogo),
teaserImage(RootIDs.teaserImage);
private RootIDs rootID;
PageElementIDs(RootIDs id)
{
this.rootID = id;
}
RootIDs getRootID()
{
return this.rootID;
}
}
/** used for Browser History event handling and parameters to/from AppController **/
public enum Presenters
{
games,
admin,
networklogin,
content,
influences, // make sure to always sort this list by having Presenters prefix-matches being "later" than the longer ones before...eg. influences before influence!
influence,
achievements,
profiles,
pagenotfound;
/** tries to find the AppParts value the given value starts with. If not found defaultValue as given is returned
*
* @param value the String value to check for prefix of one AppParts ID
* @param defaultValue to return in case value does not contain any AppPArts value as prefix
* @return the found AppParts or the default as given
*/
public static Presenters findAppPart(String value, Presenters defaultValue) {
if (value == null || value.equals("")) return defaultValue;
for (Presenters ap: Presenters.values())
{
if (value.startsWith(ap.name())) return ap;
}
return Presenters.pagenotfound;
}
}
//if it should easily be used the game part which was used before it can be identified with a dot instead
public static final String GAME_PART_USE_PREVIOUS = ".";
public static final String GAME_PART_DEFAULT = "all";
private static final Presenters appPartsDefault = Presenters.content;
private static final Presenters appPartsNotFound= Presenters.pagenotfound;
/* private members */
private HandlerManager eventHandler;
private ServerCallFactory serverCallFactory;
private LoginManager loginManager;
private RequestInformation requestInformation; // replaced with new instance each time a new website call / reload is setup.
private Presenter currentPresenter;
private HistoryToken currentHistoryValue;
public AppController(HandlerManager eventBus, ServerCallFactory serverCallFactory)
{
this.eventHandler = eventBus;
this.serverCallFactory = serverCallFactory;
this.eventHandler.addHandler(CommunicationFailureEvent.TYPE, this);
this.requestInformation = new RequestInformation();
this.loginManager = new LoginManager(this);
//this.accessRightsManager = new AccessRightsManager(rpcSocomService);
HistoryManager.addValueChangeHandler(this);
}
/* getter and setter */
public RequestInformation getRequestInformation() {
return requestInformation;
}
public HandlerManager getEventHandler()
{
return eventHandler;
}
/** a convenience object providing access to key/value pairs and substring parameters of HistoryToken parameters
* of pattern #AppPart/Module/Action/parameter1/parameter2/key1=value1|key2=value2|key3=value3/parameter3/...
*
* If you want to change and fire to a new History state, use HistoryManager instead!
* @return
*/
public HistoryToken getCurrentHistoryToken()
{
return currentHistoryValue;
}
public ServerCallFactory getRPCFactory()
{
return serverCallFactory;
}
public LoginManager getLoginManager()
{
return loginManager;
}
/** call this with an ID (a slot in the HTML page identified by a RootID) and the Widget to set there
* The widget is only set, if not already a child of the RootPanel with the RootID.
* widget can be null, then the PageElement with the given RootID is cleared of ALL childs!
* *
* @param widget
*/
@Deprecated private void setPageElement(RootIDs rootID, Widget widget)
{
RootPanel p = getRootPanel(rootID);
if (widget == null)
{
p.clear();
return;
}
if (p.getWidgetIndex(widget) < 0)
{
p.clear();
p.add(widget);
}
}
/** returns the first (and only?) Widget that is in this RootPanel with the given ID or null, if not Widget found
*
* @param rootID
* @return
*/
@Deprecated private Widget getPageElementWidget(RootIDs rootID)
{
RootPanel p = getRootPanel(rootID);
if (p.getWidgetCount() > 0)
{
return p.getWidget(0); // current this implementation only supports ONE widget to be child....
}
return null;
}
/** returns all key=value&key=value parts of the url (not decoded)
* In case of no parameters an empty String is returned
*
* This method is to prevent direct calls to Location.getHref()
* due to encapsulation and testing reasons
* @return
*/
public String getLocationParameterString() {
String full = Location.getHref();
int i = full.indexOf("?");
if (i >= 0) return full.substring(i+1);
return "";
}
/** simply returns the Location parameter value for the given key.
* This method is to prevent direct calls to Location.getParameter()
* due to encapsulation and testing reasons
*
* @param paramName
* @return
*/
public String getLocationParameter(String paramName) {
return Location.getParameter(paramName);
}
public PageElement getPageElement(PageElementIDs elementID)
{
return PageElement.getInstance(this, elementID, getRootPanel(elementID.getRootID()));
}
public void setPageElement(PageElement pageElement, IsWidget viewOrWirdget)
{
}
/** Same as getRootPanel(RootIDs ID), but removes all DOM-based html childs of element.
* Useful for removing all static template based childs of an element
*
* @param ID
* @return
*/
public static RootPanel getClearRootPanel(RootIDs ID)
{
RootPanel panel = getRootPanel(ID);
if (panel == null) return panel;
while(panel.getWidgetCount() > 0)
{
panel.remove(0);
}
// DOM based removal of (static template) elements...
Element e = panel.getElement();
while(e.hasChildNodes())
{
e.removeChild(e.getFirstChild());
}
return panel;
}
/** simply calls RootPanel.get() with String of the given ID
*
* @param ID RootID enum identifier used as # id element identifier
* @return
*/
private static RootPanel getRootPanel(RootIDs ID) {
return RootPanel.get(ID.toString());
}
private MainPresenter findOrCreatePresenter(Presenters appPart) {
// This could maybe be done more elegant with a Map associating each one with the instance
MainPresenter p = null;
switch (appPart)
{ // A-Z order
case games:
p = GamesPresenter.getInstance(this);
break;
case achievements:
p = AchievementPresenter.getInstance(this);
break;
case admin:
p = AdministrationPresenter.getInstance(this);
break;
case content:
p = ContentPresenter.getInstance(this);
break;
case influences:
p = InfluenceListPresenter.getInstance(this);
break;
case influence:
p = InfluencePresenter.getInstance(this);
break;
case networklogin:
p = LoginOAuthWindowPresenter.getInstance(this);
break;
case profiles:
p = ProfilePresenter.getInstance(this);
break;
case pagenotfound:
p = PageNotFoundPresenter.getInstance(this);
break;
default:
// Logger warning is better than Exception; needed to know that one case is NOT implemented...
throw new UnsupportedOperationException("Application Part '" + appPart + "' unknown");
}
if (p == null)
{
throw new UnsupportedOperationException("Application Part '" + appPart + "' cannot be loaded");
}
return p;
}
private void registerGWTUncaughtExceptionsHander() {
// copied and edited from http://code.google.com/p/gwt-voices/wiki/GettingStarted#A_More_Elaborate_Example
GWT.setUncaughtExceptionHandler(new GWT.UncaughtExceptionHandler() {
public void onUncaughtException(Throwable throwable) {
String text = "Uncaught exception: ";
while (throwable != null) {
StackTraceElement[] stackTraceElements = throwable.getStackTrace();
text += throwable.toString() + "\n";
for (int i = 0; i < stackTraceElements.length; i++) {
text += " at " + stackTraceElements[i] + "\n";
}
throwable = throwable.getCause();
if (throwable != null) {
text += "Caused by: ";
}
}
// DialogBox dialogBox = new DialogBox(true, false);
// //DOM.setStyleAttribute(dialogBox.getElement(), "backgroundColor", "#ABCDEF");
// System.err.print(text);
// text = text.replaceAll(" ", " ");
// dialogBox.setHTML("<pre>" + text + "</pre>");
// dialogBox.center();
}
});
}
/**
* This is the MAIN method on top-level that actually starts glueing everything together (called by EntryPoint).
* It calls every component to render/add the widgets (view) to the responding RootPanel parts
* @param rootPanel
*/
public void go() {
try
{
// Register a Handler for JS Exceptions
registerGWTUncaughtExceptionsHander();
// a new refresh of website means a new Request object
this.requestInformation = new RequestInformation();
// only handle an explicit go if application is called fresh without
// any state. otherwise wait/listen to History events
// do the basic initialization parts:
// 1. check userState
loginManager.checkIfLoggedIn(requestInformation);
// 2. get for each anchor in the template the RootPanel and call the appropriate components to render into
NavigationPresenter navBar = NavigationPresenter.getInstance(this);
navBar.go(getClearRootPanel(RootIDs.navigation));
// 3. Login Field
LoginPresenter loginPanel = LoginPresenter.getInstance(this);
loginPanel.go(getClearRootPanel(RootIDs.login));
// 3. Footer
// TODO JK: add footer support (JK)
// try to find out which "mode" of page request this is:
// 1. SocialMediaApplication redirect call (maybe in a popup?) to
// process token
// 2. normal page call
String stateParameter = getLocationParameter(getLoginManager().getNetworkLoginManager().getURLParameterForOAuthTokenProcessing());
boolean isNetworkLogin = stateParameter != null && stateParameter.startsWith(getLoginManager().getNetworkLoginManager().getURLParameterValueForOAuthTokenProcessing());
if (isNetworkLogin)
{
String gamePart = stateParameter.substring(stateParameter.lastIndexOf('.') + 1);
//do not remove game-part
HistoryToken token = new HistoryToken(gamePart, Presenters.networklogin);
HistoryManager.newItem(token);
}
else
{
if (HistoryManager.isTokenEmpty())
{
Presenters ap = appPartsDefault;
HistoryManager.newItem(new HistoryToken(GAME_PART_DEFAULT, ap));
}
}
HistoryManager.fireCurrentHistoryState();
} catch (Exception e)
{
@SuppressWarnings("unused")
Exception f = e; // only for breakpoint reasons.... int i =1;
}
}
/* implementation of EventHandler methods **/
@Override
public void onCommunicationFailureEvent(CommunicationFailureEvent event) {
Window.alert("Communication Failure\n"+event.getException());
}
/** the main method called each time a new side page or page part is triggered by history.
* This is as well the case when new page is (re)loaded. so nearly all rendering and putting together happens here.
*/
@Override
public void onValueChange(ValueChangeEvent<String> event) {
// determine by HistoryToken which presenter should be loaded in main-RootPanel.
// if this is a fresh page build, clear out all dummy content from template and add Presenter
// otherwise simply exchange the presenters (old out, new in)..
try
{
HistoryToken oldHistoryValue = currentHistoryValue;
HistoryToken newHistoryValue = HistoryToken.fromValueChangeEvent(event);
//the presented game changed in the meantime
if(currentHistoryValue == null || !currentHistoryValue.getGamePart().equals(newHistoryValue.getGamePart())) {
//apply game settings
if(!applyGame(newHistoryValue.getGamePart()))
return;
}
HistoryManager.newItem(newHistoryValue, false);
currentHistoryValue = newHistoryValue;
Presenters presenterPart = newHistoryValue.getPresenter(appPartsNotFound); // the state of app to build; contains values of AppParts enum
// 1. first find AppPart and ask the instance which components of page not to load...
MainPresenter presenter = findOrCreatePresenter(presenterPart);
// 1.b Set the Title
Document d = Document.get();
String titleToBe = presenter.getPageTitle(newHistoryValue);
if (d!= null && titleToBe != null && !titleToBe.equals(AbstractMainPresenter.DEFAULT_TITLE) && !titleToBe.equals(""))
{
d.setTitle(titleToBe);
}
// 2. check if any change is needed
if (presenter.equals(currentPresenter))
{
if (!newHistoryValue.equals(oldHistoryValue) && !oldHistoryValue.equals(null))
{
eventHandler.fireEvent(new ViewChangeWithinPresenterEvent(presenter, oldHistoryValue, newHistoryValue));
}
// do nothing?
return;
}
// 3. inform old presenter to cleanup
if (currentPresenter!= null)
{
eventHandler.fireEvent(new ViewChangePresenterEvent(currentPresenter, presenter));
}
// thanks to single threaded JavaScript we do not need to wait for any callback and exchange directly
RootPanel mainPanel = requestInformation.isCompletePageBuildMode()?getClearRootPanel(RootIDs.main):getRootPanel(RootIDs.main);
mainPanel.clear();
presenter.go(mainPanel);
currentPresenter = presenter;
// Main content area...handing over RootPanels for Subheader, Teaser-Image, Breadcrumb and content to AppPart
this.requestInformation.endCompletePageBuildMode();
}
catch (Throwable e)
{
Window.alert("Top-Level catch: " + e);
GWT.log("Top-Level catch", e);
}
}
private boolean applyGame(String gamePart) {
if(gamePart.equals(GAME_PART_USE_PREVIOUS)) {
/* overwrite dot with previous game */
if(requestInformation.getCurrentGame() != null){
gamePart = requestInformation.getCurrentGame();
HistoryToken historyToken = HistoryManager.getHistoryToken();
historyToken.setGamePart(gamePart, false);
HistoryManager.newItem(historyToken, true);
return false;
}
else {
//previous not set - use default
gamePart = GAME_PART_DEFAULT;
}
}
// if(gamePart.equals(GAME_PART_DEFAULT)) {
// requestInformation.setCurrentGame(gamePart);
// getEventHandler().fireEvent(new GameChangeEvent(gamePart));
// return true;
// }
//TODO template change
getEventHandler().fireEvent(new GameChangeEvent(gamePart));
requestInformation.setCurrentGame(gamePart);
/*
* removed async checking of gamepart since it can be changed by the user either way
*/
// final String gameIdent = gamePart;
// //update current game and validate
// getRPCFactory().getGameService().isGameValid(gameIdent, new AsyncCallback<Boolean>() {
//
// @Override
// public void onSuccess(Boolean valid) {
// if(!valid) {
// //if game string was not valid change to default and retry
// HistoryToken historyToken = HistoryManager.getHistoryToken();
// historyToken.setGamePart(GAME_PART_DEFAULT, false);
// HistoryManager.newItem(historyToken, true);
// requestInformation.setCurrentGame(GAME_PART_DEFAULT);
// } else {
// getEventHandler().fireEvent(new GameChangeEvent(gameIdent));
// }
// }
//
// @Override
// public void onFailure(Throwable caught) {
// Window.alert("Communication failure: " + caught.getLocalizedMessage());
// requestInformation.setCurrentGame(GAME_PART_DEFAULT);
// }
// });
return true;
}
}