org.activityinfo.ui.client.page.NavigationHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.activityinfo.ui.client.page.NavigationHandler.java

Source

package org.activityinfo.ui.client.page;

/*
 * #%L
 * ActivityInfo Server
 * %%
 * Copyright (C) 2009 - 2013 UNICEF
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */

import com.extjs.gxt.ui.client.event.EventType;
import com.extjs.gxt.ui.client.event.Listener;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.activityinfo.legacy.client.AsyncMonitor;
import org.activityinfo.legacy.shared.Log;
import org.activityinfo.ui.client.EventBus;
import org.activityinfo.ui.client.inject.Root;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Coordinates navigation between pages.
 * <p/>
 * PageManager listens for NavigationEvents, fired either by an individual
 * component, or from the HistoryManager, and
 */
@Singleton
public class NavigationHandler {

    private final EventBus eventBus;
    private final Frame root;
    private final Map<PageId, PageLoader> pageLoaders = new HashMap<PageId, PageLoader>();

    public static final EventType NAVIGATION_REQUESTED = new EventBus.NamedEventType("NavigationRequested");
    public static final EventType NAVIGATION_AGREED = new EventBus.NamedEventType("NavigationAgreed");

    private NavigationAttempt activeNavigation;

    @Inject
    public NavigationHandler(final EventBus eventBus, final @Root Frame root) {
        this.eventBus = eventBus;
        this.root = root;

        eventBus.addListener(NAVIGATION_REQUESTED, new Listener<NavigationEvent>() {
            @Override
            public void handleEvent(NavigationEvent be) {
                onNavigationRequested(be);
            }
        });
        Log.debug("PageManager: connected to EventBus and listening.");

        Window.addWindowClosingHandler(new Window.ClosingHandler() {
            @Override
            public void onWindowClosing(Window.ClosingEvent event) {
                if (activeNavigation != null && activeNavigation.currentPage != null) {
                    event.setMessage(activeNavigation.currentPage.beforeWindowCloses());
                }
            }
        });
    }

    public EventBus getEventBus() {
        return eventBus;
    }

    private void onNavigationRequested(NavigationEvent be) {
        if (activeNavigation == null || !activeNavigation.getPlace().equals(be.getPlace())) {
            activeNavigation = new NavigationAttempt(be.getPlace());
            activeNavigation.go();
        }
    }

    public void registerPageLoader(PageId pageId, PageLoader loader) {
        pageLoaders.put(pageId, loader);
        Log.debug("PageManager: Registered loader for pageId '" + pageId + "'");
    }

    private PageLoader getPageLoader(PageId pageId) {
        // Looks for an ID separator
        int index = pageId.toString().indexOf('!');
        if (index != -1) {
            // Removes the ID from the PageId in order to retrieves the correct
            // PageLoader from the map.
            pageId = new PageId(pageId.toString().substring(0, index));
        }

        PageLoader loader = pageLoaders.get(pageId);
        if (loader == null) {
            Log.debug("PageManager: no loader for " + pageId);
            throw new Error("PageManager: no loader for " + pageId);
        }
        return loader;
    }

    /**
     * Encapsulates a single navigation attempt.
     */
    public class NavigationAttempt {
        private final PageState place;

        private Iterator<PageId> pageHierarchyIt;
        private Frame frame;
        private Page currentPage;
        private PageId targetPage;

        private AsyncMonitor loadingPlaceHolder;

        public NavigationAttempt(PageState place) {
            this.place = place;
        }

        public PageState getPlace() {
            return place;
        }

        public void go() {
            startAtRoot();
            confirmPageChange();
        }

        private void startAtRoot() {
            assertViewPathIsNotEmpty();
            pageHierarchyIt = place.getEnclosingFrames().iterator();
            currentPage = root;
            descend();
        }

        private void descend() {
            assertPageIsFrame(currentPage);
            frame = (Frame) currentPage;
            targetPage = pageHierarchyIt.next();
            currentPage = frame.getActivePage();
        }

        /**
         * After each asynchronous call we need to check that the user has not
         * requested to navigate elsewhere.
         * <p/>
         * For example, a page loader may make an asynchronous call, which means
         * that an additional JavaScript fragment has to be downloaded from the
         * server and parsed before we can continue. During that time, the user
         * may have grown tired of waiting and hit the back button or chosen
         * another place to go to.
         */
        private boolean isStillActive() {
            return this == activeNavigation;
        }

        protected void confirmPageChange() {
            if (thereIsNoCurrentPage()) {
                proceedWithNavigation();

            } else if (targetPageIsAlreadyActive()) {
                // ok, no change required.
                // descend if necessary

                if (hasChildPage()) {
                    descend();
                    confirmPageChange();
                } else {
                    proceedWithNavigation();
                }
            } else {
                askPermissionToChange();
            }
        }

        /**
         * We need to give the current page an opportunity to cancel the
         * navigation. For example, the user may have made changes to the page
         * and we don't want to navigate away until we're sure that they can be
         * saved.
         */
        private void askPermissionToChange() {
            currentPage.requestToNavigateAway(place, new NavigationCallback() {
                @Override
                public void onDecided(boolean allowed) {
                    if (allowed) {
                        if (isStillActive()) {
                            proceedWithNavigation();
                        }
                    } else {
                        Log.debug("Navigation to '" + place.toString() + "' refused by " + currentPage.toString());
                    }
                }
            });
        }

        private boolean hasChildPage() {
            return pageHierarchyIt.hasNext();
        }

        private boolean targetPageIsAlreadyActive() {
            return currentPage.getPageId().equals(targetPage);
        }

        private boolean thereIsNoCurrentPage() {
            return currentPage == null;
        }

        private void proceedWithNavigation() {
            if (isStillActive()) {
                fireAgreedEvent();
                startAtRoot();
                changePage();
            }
        }

        private void fireAgreedEvent() {
            eventBus.fireEvent(new NavigationEvent(NAVIGATION_AGREED, place));
        }

        protected void changePage() {
            /*
             * First see if this view is already the active view, in wehich case
             * we can just descend in the path
             */
            if (!thereIsNoCurrentPage() && targetPageIsAlreadyActive() && currentPage.navigate(place)) {

                changeChildPageIfNecessary();

            } else {
                shutDownCurrentPageIfThereIsOne();
                showPlaceHolder();
                schedulePageLoadAfterEventProcessing();
            }
        }

        private void shutDownCurrentPageIfThereIsOne() {
            if (!thereIsNoCurrentPage()) {
                currentPage.shutdown();
            }
        }

        private void showPlaceHolder() {
            loadingPlaceHolder = frame.showLoadingPlaceHolder(targetPage, place);
        }

        /**
         * Schedules the loadPage() after all UI events in the browser have had
         * a chance to run. This assures that the loading placeholder has a
         * chance to be added to the page.
         */
        private void schedulePageLoadAfterEventProcessing() {
            if (GWT.isClient()) {
                DeferredCommand.addCommand(new Command() {
                    @Override
                    public void execute() {
                        if (isStillActive()) {
                            loadPage();
                        }
                    }
                });
            } else {
                loadPage();
            }
        }

        /**
         * Delegates the creation of the Page component to a registered page
         * loader.
         */
        private void loadPage() {
            PageLoader loader = getPageLoader(targetPage);
            loader.load(targetPage, place, new AsyncCallback<Page>() {
                @Override
                public void onFailure(Throwable caught) {
                    onPageFailedToLoad(caught);
                }

                @Override
                public void onSuccess(Page page) {
                    if (isStillActive()) {
                        onPageLoaded(page);
                    }
                }
            });
        }

        private void onPageFailedToLoad(Throwable caught) {
            loadingPlaceHolder.onConnectionProblem();
            Log.error("PageManager: could not load page " + targetPage, caught);
        }

        private void onPageLoaded(Page page) {
            makeCurrent(page);
            changeChildPageIfNecessary();
        }

        private void makeCurrent(Page page) {
            frame.setActivePage(page);
            currentPage = page;
        }

        private void changeChildPageIfNecessary() {
            if (hasChildPage()) {
                descend();
                changePage();
            }
        }

        private void assertViewPathIsNotEmpty() {
            assert place.getEnclosingFrames().size() != 0 : "PageState " + place.toString()
                    + " has an empty viewPath!";
        }

        private void assertPageIsFrame(Page page) {
            assert page instanceof Frame : "Cannot load page " + pageHierarchyIt.next() + " into " + page.toString()
                    + " because " + page.getClass().getName() + " does not implement the PageFrame interface.";
        }
    }
}