navigation учебник - Учебное пособие по нескольким страницам в Google Web Toolkit(GWT)




что com.google.toolkit (8)

Я только начал изучать Google Web Toolkit (GWT). Как сделать разные HTML-страницы в моем приложении GWT?

Например, я хочу создать приложение для книжного магазина. В этом приложении у меня будет три страницы:

  1. Домашние страницы, на которых я буду приветствовать пользователя и предлагаю книги пользователя
  2. Страница для просмотра книг по категориям и просмотра деталей (используйте виджеты GWT)
  3. Посмотрите книги онлайн.

Конечно, могут быть другие страницы, такие как данные пользователя, добавить новую книгу и т. Д. Итак, что является лучшим способом сделать разные страницы в GWT и как я могу сделать навигацию со страницы на страницу? Есть ли примеры или учебные пособия? Или мне даже нужно создавать разные страницы, когда я могу создать целое приложение на одной странице?


Answers

Вы можете использовать MVP patern. Вот небольшая библиотека https://code.google.com/p/gwt-simple-mvp/wiki/GettingStarted .

И вы можете разделить код на более js-файлы. https://code.google.com/p/gwt-spliting/


Если вы хотите, чтобы это было FULL AJAXified (например, настольное приложение), вам понадобится только одна страница. Затем просто измените содержимое тела в зависимости от ссылки.

Кроме того, есть группа google для GWT, которая очень активна, и я знаю, что это было задано до этого, вам просто нужно использовать функцию поиска.


Я бы использовал класс HyperLink и History. Хорошая вещь о классе Hyperlink заключается в том, что он устанавливает этот токен (например, # foobar), и все, что вам нужно сделать, - это захватить событие, которое вызывается при изменении значения токена (ValueChangeEvent). В случае с Handler вы затем замените страницы.

Пример: адрес приветствуемой страницы: www.yourpage.com/#home на этой странице будет ссылкой на страницу «browse book», когда будет нажата ссылка, новый адрес будет выглядеть примерно так: www.yourpage.com /#просматривать

И вот код:


public class MainEntryPoint implements EntryPoint, ValueChangeHandler {
    VerticalPanel panel = new VerticalPanel();
    Label label=new Label();
    public void onModuleLoad() {
        Hyperlink link1 = new Hyperlink("books", "browse");
        Hyperlink link2 = new Hyperlink("user details", "details");
        panel.add(link1);
        panel.add(link2);
        panel.add(label);
        RootPanel.get().add(panel);
        History.addValueChangeHandler(this);
        //when there is no token, the "home" token is set else changePage() is called.
        //this is useful if a user has bookmarked a site other than the homepage.
        if(History.getToken().isEmpty()){
            History.newItem("home");
        } else {
            changePage(History.getToken());
        }
    }

public void onValueChange(ValueChangeEvent event) {
    changePage(History.getToken());
}
public void changePage(String token) {
    if(History.getToken().equals("browse")) {
        label.setText("Here would be some books");
    } else if (History.getToken().equals("details")) {
        label.setText("Here would be the user details");
    } else {
        label.setText("Welcome page");
    }
}

}


То, что я обычно делаю в таких ситуациях, это сначала создать структуру веб-страницы. У меня будет div для заголовка, бокового меню и нижнего колонтитула. У меня также будет div в моем HTML для основного контента.

Пример:

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta name='gwt:module' content='org.project.package.Core=org.project.package.Core'>
    </head>
    <body>
        <!-- Load the JavaScript code for GWT -->
        <script language="javascript" src="ui/org.project.package.ui.Core.nocache.js"></script>

        <!-- For some unknown reason in Internet Explorer you have to have cellpadding/spacing ON THE ELEMENT and not on the STYLE if it is in the body tag like this -->
        <table id="wrapper" cellpadding="0" cellspacing="0" style="width: 100%;height: 100%;">

             <!-- Header row -->
             <tr style="height: 25%;">
                 <td colspan="2" id="header"></td>
             </tr>

             <!-- Body row and left nav row -->
             <tr style="height: 65%;">
                 <td id="leftnav"></td>
                 <td id="content"></td>
             </tr>

             <!-- Footer row -->
             <tr style="height: 10%;">
                <td colspan="2" id="footer"></td>
             </tr>

        </table>

        <!-- This iframe handles history -->
        <iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>
    </body>
</html>

(Если вам нравятся макеты на основе <div>, не стесняйтесь использовать их.)

Затем вы создаете свою точку входа (в моем случае Core.java ), как обычно, устанавливая каждый из элементов по мере необходимости.

RootPanel.get("header").add(new Header());
RootPanel.get("leftnav").add(new NavigationMenu());
RootPanel.get("footer").add(new Footer());

Конечно, возможно иметь статический нижний колонтитул и заголовок, но это ни здесь, ни там.

У меня также есть абстрактный класс под названием «Контент». Объекты контента расширяют «Композитный» и будут иметь различные методы для упрощения создания и компоновки новой страницы. Каждая страница, которую я создаю для этого приложения, будь то экран справки, экран поиска, корзина для покупок или что-то еще, имеет тип Content .

Теперь я создаю класс под названием «ContentContainer». Это singleton, который отвечает за управление элементом «content». Он имеет один метод «setContent», который принимает объекты типа «Содержимое». Затем он в основном удаляет что-либо внутри «content» <td> и заменяет его любым виджетами (Composite), которые вы назначаете с помощью метода setContent. Метод setContent также обрабатывает управление заголовками и заголовками. В основном ContentContainer служит для объединения всех различных точек привязки, которые могут возникнуть, если каждый контент страницы должен «знать» обо всех функциях, которые он должен выполнять.

Наконец, вам нужен способ добраться до этой страницы, не так ли? Это просто:

ContentContainer.getInstance().setContent(new Search());

Поместите вышеприведенное в случайное событие где-то, и вы золотой.

Единственное, что нужно связать с вашими другими виджетами, это ContentContainer и тип контента, который они добавляют.

Недостатки, которые я вижу в подходе ChrisBo, заключаются в том, что у вас есть список, который должен поддерживаться в токенах -> страницах. Другой недостаток, который я вижу, - это то, что я не вижу, как вы можете иметь реальную систему истории с помощью этого метода.

Одна вещь, которую он предлагает по моему подходу, состоит в том, что все варианты страниц довольно централизованы. Я бы использовал какой-то Enum или, по крайней мере, статический класс со значениями String, чтобы не допустить перекрещивания ссылок.

В любом случае, я думаю, что этот пункт можно суммировать следующим образом: обмен содержимым какого-либо элемента центральной страницы на основе того, что ваш пользователь нажимает на действия, выполняемые вашим пользователем (ами).


Я использовал Chloe S. ответ (сочетающий ответ Криса Р. с Chris Boesing's), чтобы построить этот App Controller для рабочего веб-приложения GWT. Версия в производстве протестирована (и работает% 100), но эта отредактированная версия ниже должна быть изменена для интеграции с вашим собственным приложением (начните с переименования ключей страницы в пункты меню).

AppController.java :

/**
 * This App Controller utilizes two static inner-classes (Pages and External) 
 * to manage and server multiple pages with multiple sub-page (through their presenters) 
 * via String key constants which also serve as the literal text for the menu items.
 * 
 * Pages are added as menu commands in their respective views:
 *  // Add menu items to the menu with commands:
 *  menuItems.put(Pages.PAGE1, mainMenu.addItem(Pages.PAGE1, new Command() {
 *      public void execute() {
 *          History.newItem(Pages.PAGE1);
 *      }
 *  }));
 * 
 * Pages are fired as History tokens (from entry point java class):
 * 
 *   **
 *   * Receives history events and pushes them to the AppController using a deferred command.
 *   * Changes the cursor to show waiting.
 *   * @param the value change token
 *   *
 *  public void onValueChange(ValueChangeEvent<String> e) {
 *      // check token to cover first historical "back" navigation:
 *      if(!History.getToken().isEmpty()) {
 *          AppController.waitCursor.execute(); // cursor is reset in page attach method
 *      }
 *      Scheduler.get().scheduleDeferred(new ScheduledCommand() {
 *          public void execute() {
 *              AppController.go(History.getToken());
 *          }
 *      });
 *  }
 *  
 * Wait cursors are implemented as CSS:
 *
 *  body.wait, body.wait * {
 *      cursor: wait !important;   
 *  }
 * 
 * NOTE: This page swapping implementation technique (based on the  solution 
 * found here: [http://.com/questions/1061705/multiple-pages-tutorial-in-google-web-toolkit-gwt][1]) 
 * differs from the obtuse and ancient 2010 GWT framework documentation in that the App Controller manages / handles 
 * adding the widget to the container, and therefore all the Presenters must implement the 
 * "AppControlPresenter" or "AppControlContainerPresenter" interface to give it access to their containers.
 * (thus eliminating "public void go(final HasWidgets container);" method in all presenter architecture except for 'MainAppPresenter')
 * There is also no event bus; static method calls are used for any needed interactivity.
 *
 * Includes a popup for pages still under construction.
 */

package com.;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DecoratedPopupPanel;
import com.google.gwt.user.client.ui.Frame;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.UIObject;

/**
 * 
 */
public class AppController {
    /** */
    public final static String DEFAULT_INITIAL_PAGE1_SUB_PAGE = Pages.PAGE_1A;
    /** Singleton instance for the AppController */
    private static AppController instance = new AppController();
    /** Presenter for the main app */
    private static MainAppPresenter mainAppPresenter;
    /** container for the different views */
    private static LayoutPanel container;
    /** sub-container for the different sub-views */
    private static LayoutPanel page1Container;
    /** */
    private static DecoratedPopupPanel popup;
    /** constant for Style-Dependent names for menu items (see menu-style.css) */
    public final static String MENU_ACTIVE_STYLE = "active";
    /** constant for Style-Dependent class name in css */
    public final static String CURSOR_WAIT_CLASS = "wait";  
    /** */
    public final static String POPUP_DEMO_ID = "popupDemo";
    /** */  
    public final static int DEMOP_POPUP_VERTICAL_OFFSET = 0;
    /** */
    public final static String POPUP_DEMO_STATEMENT = "<span class='text'>This page is under construction</span>"
                                                    + "<span class='char'>&hellip;</span>";
    /** */
    public static ScheduledCommand waitCursor = new ScheduledCommand() {
        @Override
        public void execute() { 
            AppController.waitCursor(true); 
        }
    };

    /** */
    public static ScheduledCommand normalCursor = new ScheduledCommand() {
        @Override
        public void execute() { 
            AppController.waitCursor(false); 
        }
    };

    /** Flag for determining if the page was reloaded */
    private static boolean reloaded = false;

    private static final LoginServiceAsync loginRpcService = GWT.create(LoginService.class);

    /**
     * Called on the resize event to set the position of the demo popup
     * window to be adjusted to the correct dimensions (size and positoin)
     * regardless of screen size.
     */
    private static ScheduledCommand resetPopupDimensions = new ScheduledCommand() {
        @Override
        public void execute() {
            if(!UNDER_CONSTRUCTION || popup == null) {
                return;
            }
            int demoWidth = Math.round(Window.getClientWidth() / MainApp.PHI),
                demoYPosition = Window.getClientHeight() / 2 - Math.round(popup.getOffsetHeight() / 2);
            popup.setWidth(String.valueOf(demoWidth) + "px");
            if(popup.getOffsetWidth() >= Window.getClientWidth()) {
                popup.setWidth("100%");
                popup.setPopupPosition(0, demoYPosition);
            } else {
                popup.setPopupPosition(Window.getClientWidth() / 2 - (popup.getOffsetWidth() / 2), demoYPosition);
            }
        }
    };

    /** */
    private static final String LOGIN_OBJECT_NAME = "Login Presenter Object";

    /**
     * static inner-class for external websites 
     */
    public static class External {
        /** The frame to contain the website */
        private static Frame frame;
        /**  */
        public static final String EXTERNAL_URL_1 = "http://";
        /**  */
        public static final String EXTERNAL_URL_2 = "http://";

        /**
         * @returns true if the name of the token is equal to one of the URLs
         * @param token the name to check
         */
        public static boolean has(String token) {
            return token.equalsIgnoreCase(EXTERNAL_URL_1)      ||
                   token.equalsIgnoreCase(EXTERNAL_URL_2);
        }

        /**
         * Gets the external Frame object
         * @param url
         * @return Frame
         */
        public static Frame get(String url) {
            if(frame == null) {
                frame = new Frame(url);
                frame.addAttachHandler(new AttachEvent.Handler() {
                    @Override
                    public void onAttachOrDetach(AttachEvent event) { 
                        // hide the popup:
                        showPopup(false);
                        Scheduler.get().scheduleFinally(resetPopupDimensions);
                        Scheduler.get().scheduleFinally(normalCursor);
                    }
                });
            }
            else if(!frame.getUrl().equalsIgnoreCase(url)) {
                frame.setUrl(url);
            }
            return frame;
        }
    }

    /**
     * static inner-class for holding pages activated by the app's main menu commands
     */
    public static class Pages {
        /** */
        public static final String PAGE1 = "foo";
        /** */
        public static final String PAGE2 = "bar";
        /** */
        public static final String PAGE_1A = "baz";
        /** */
        public static final String PAGE_1B = "qux";
        /** */
        public static String lastPage;
        /** */
        public static String lastPage1SubPage;
        /** */
        public static String unsavedMessage;
        /** */
        private static HashMap<String, AppControlPresenter> pageMap;
        /** */
        private static AppControlPresenter presenter;
        /** */
        private static Composite view;

        /**
         * initializes the hashmap of pages  
         */
        public static void init() {
            pageMap = new HashMap<String, AppControlPresenter>();
        }

        /**
         * @returns true if the name of the token is equal to one of the pages
         * @param token the name to check
         */
        public static boolean has(String token) {
            return token.equalsIgnoreCase(PAGE1)            ||
                   token.equalsIgnoreCase(PAGE2)            ||
                   token.equalsIgnoreCase(PAGE_1A);
        }


        /**
         * Gets the correct page container to display as a Composite
         * @param page the token name of the page
         * @return Composite page
         */
        public static Composite get(String page) {
            view = null;
            presenter = null;
            if(page.equalsIgnoreCase(PAGE1)) {
                if(pageMap.get(PAGE1) == null) {
                    pageMap.put(PAGE1, new Page1Presenter(PAGE1)); 
                    page1Container = ((AppControlContainerPresenter) pageMap.get(PAGE1)).getContentPane();
                }
                presenter = pageMap.get(PAGE1);
                lastPage = page;
                mainAppPresenter.setCurrentMenuItem(page);
            }
            else if(page.equalsIgnoreCase(PAGE_1A) ||
                    page.equalsIgnoreCase(PAGE_1B) {
                if(pageMap.get(PAGE1) == null) {
                    pageMap.put(PAGE1, new Page1Presenter(PAGE1)); 
                    page1Container = ((AppControlContainerPresenter) pageMap.get(PAGE1)).getContentPane();
                }
                presenter = pageMap.get(PAGE1);
                lastPage1SubPage = page;
                view = ((AppControlContainerPresenter)presenter).setCurrentPage(page);
            }
            else if(page.equalsIgnoreCase(PAGE2)) {
                if(pageMap.get(PAGE2) == null) {
                    pageMap.put(PAGE2, new Page2Presenter(PAGE2));
                }
                presenter = pageMap.get(PAGE2);
                lastPage = PAGE2;
                mainAppPresenter.setCurrentMenuItem(page);
            }
            else if(External.has(page)) {
                throw new Error("App Controller Error -- Use 'External' inner-class for: " + page);
            }
            else {
                throw new Error("App Controller Error -- Page name not found: " + page);
            }
            if(view == null) {
                view = (Composite)presenter.view();
            }
            view.addAttachHandler(new AttachEvent.Handler() {
                @Override
                public void onAttachOrDetach(AttachEvent event) {
                    AppController.showPopup(false);
                    presenter.updateAttachOrDetach(event);
                    Scheduler.get().scheduleFinally(resetPopupDimensions);
                    Scheduler.get().scheduleFinally(normalCursor);
                }
            });
            return view;
        }

        /**
         * Gets the current AppControlPresenter for the last page.
         * @returns the current AppControlPresenter  
         */
        public static AppControlPresenter getCurrentPresenter() {
            return presenter;
        }

        /**
         * Gets an AppControlPresenter from the pageMap.
         * @param token the name of the presenter
         * @returns the AppControlPresenter  
         */
        public static AppControlPresenter getPresenter(String token) {
            return pageMap.get(token);
        }

        /**
         * Returns true if the page is already loaded.
         * @param token name of the page
         */
        public static boolean alreadyLoaded(String token) {
            MainApp.debug(1, "[already loaded: " + presenter.toString() + " (token: " + token + ")");
            return presenter.toString().equalsIgnoreCase(token);
        }

        /**
         * Returns true if the page is visible
         * @param page the token name of the page
         */
        public static boolean isVisible(String page) {
            UIObject component = pageMap.get(page).view();
            return !(component.getOffsetHeight() == 0 && component.getOffsetWidth() == 0);
        }

        /**
         * Returns true if the page is visible
         * @param presenter the AppControlPresenter instance
         */
        public static boolean isVisible(AppControlPresenter presenter) {
            UIObject component = presenter.view();
            return !(component.getOffsetHeight() == 0 && component.getOffsetWidth() == 0);
        }

        /**
         * Returns true if the application has unsaved data.
         * Iterates through all the pages and checks each presenter.
         */
        public static boolean unsavedData() {
            if(pageMap.isEmpty()) return false;
            boolean unsaved = false;
            for(Map.Entry<String, AppControlPresenter> entry : pageMap.entrySet()) {
                AppControlPresenter presenter = entry.getValue();
                if(presenter != null && presenter.unsavedData()) {
                    MainApp.debug(1, "(!) " + presenter.toString() + " has unsaved data");
                    unsavedMessage = presenter.dataDescription();
                    unsaved = true;
                    break; // just need to know one exists for now (window closing event)
                }
            }
            return unsaved;
        }

        /**
         * Called on a resize event on the window. Iterates through all the pages
         * and tells their presenters to resize their content.
         */
        public static void resize() {
            for(Map.Entry<String, AppControlPresenter> entry : pageMap.entrySet()) {
                AppControlPresenter presenter = entry.getValue();
                if(presenter != null && isVisible(presenter)) {
                    presenter.resize();
                }
            }
        }
    } //end class Pages

    /**
     * @returns true if the history token is equal to any of the pages in the app 
     */
    public static boolean hasHistory() {
        String token = History.getToken();
        return External.has(token) || Pages.has(token);
    }

    /**
     * Starts the login view at the root layout level
     */
    public static void goLoginScreen() {
        //check for reload:
        if(hasHistory()) {
            MainApp.debug(1, "(!) AppController has History on Login");
            reloaded = true;
        }
        else {
            reloaded = false;
        }
        RootLayoutPanel.get().clear();
        RootLayoutPanel.get().add(new LoginPresenter(LOGIN_OBJECT_NAME).view());
    }

    /**
     * @returns the last "Page1" page
     */
    public static String getLastPage1Page() {
        if(Pages.lastPage1SubPage == null || Pages.lastPage1SubPage.isEmpty()) {
            Pages.lastPage1SubPage = DEFAULT_INITIAL_PAGE1_SUB_PAGE;
        }
        return Pages.lastPage1SubPage;
    }

    /**
     * Tells the app to start with the Page1 page.
     * @param username the username of the person logged-in
     */
    public static void goMainApp(String username) {
        //hide the login background:
        RootPanel.getBodyElement().getStyle().setProperty("background", "none");
        mainAppPresenter = new MainAppPresenter(username);
        RootLayoutPanel.get().clear();
        mainAppPresenter.go(RootLayoutPanel.get());
        //get the center panel:
        container = mainAppPresenter.getContainer();
        //check for reload:
        //NOTE: the token will be empty if the user refreshes 
        //      and navigates all the way back to the zero-state 
        //      from the login screen. 
        //NOTE: this logic may change after user-persistence is implemented
        if(hasHistory() || History.getToken().isEmpty()) {
            // reset the reloaded flag:
            reloaded = false;
            if(History.getToken().isEmpty()) {
                //land on the first page:
                History.newItem(AppController.Pages.PAGE1);
            }
            else {
                MainApp.debug(2, "(!) AppController has History on reload: " + History.getToken());
                History.fireCurrentHistoryState();
            }
        }
        else {
            //land on the first page:
            History.newItem(AppController.Pages.PAGE1);
        }

    }

    /**
     * 
     */
    public static void checkIfSessionActive() { 
        loginRpcService.loginFromSession(new AsyncCallback<LoginSummary>() {
            @Override
            public void onFailure(Throwable throwable) {
                goLoginScreen();
            }

            @Override
            public void onSuccess(LoginSummary loginSummary) {
                if (loginSummary.getErrorString() != null)
                    goLoginScreen();
                else
                    goMainApp(loginSummary.getUser().getName());
            }
        });
    }

    /**
     * 
     */
    public static void sessionLogout() {
        DialogBoxWidget.confirm(200,
                "Logout",
                "Are you sure you want to log out?",
                new ConfirmDialogCallback() {
                    @Override
                    public void onAffirmative() {

                        loginRpcService.logout(new AsyncCallback<Void>() {
                            @Override
                            public void onFailure(Throwable throwable) {
                                goLoginScreen();
                            }

                            @Override
                            public void onSuccess(Void aVoid) {
                                goLoginScreen();
                            }
                        });
                    }

                    @Override
                    public void onCancel() {
                    }
                });

    }

    /**
     * Shows or hides the "Under Construction" popup if UNDER_CONSTRUCION is true.
     * @param show true to show and false to hide
     */
    public static void showPopup(boolean show) {
        if(MainApp.UNDER_CONSTRUCTION && popup != null) {
            if(show) {
                popup.show();
            }
            else {
                popup.hide();
            }
        }
    }

    /**
     * Called by every history event fired (including the back and forward buttons).
     * Ignores the login and empty index historically.
     * @param token the name of the page to load
     */
    public static void go(String token) {
        if(reloaded) {
            normalCursor.execute();
        }
        if(token == null || token.isEmpty() || reloaded == true) return;
        MainApp.debug("<history changed> - AppController.go()-> " + token);
        // build the popup message for all unfinished pages:
        if(MainApp.UNDER_CONSTRUCTION) {
            if(popup == null) {
                popup = new DecoratedPopupPanel(false);
                popup.ensureDebugId(POPUP_DEMO_ID);
                popup.addStyleDependentName(POPUP_DEMO_ID);
                popup.setWidget(new HTML(new Image("images/workingman.png") + POPUP_DEMO_STATEMENT + new Image("images/workingmanFLIP.png")));
            }
        }
        // check token for which page to return:
        if(token.equalsIgnoreCase(External.EXTERNAL_URL_1)) {
            MainAppPresenter.clearActiveMenuItems();
            setExternalContentURL(External.get(token));
        }
        else if(token.equalsIgnoreCase(External.EXTERNAL_URL_2)) {
            MainAppPresenter.clearActiveMenuItems();
            setExternalContentURL(External.get(token));
        }
        else if(token.equalsIgnoreCase(Pages.PAGE1)) {
            setContent(Pages.get(Pages.PAGE1));
            setPage1Content(Pages.get(getLastPage1Page()));
        }
        else if(token.equalsIgnoreCase(Pages.PAGE_1A) ||
                token.equalsIgnoreCase(Pages.PAGE_1B)) {
            setContent(Pages.get(Pages.PAGE1));
            setPage1Content(Pages.get(token));
        }
        else if(token.equalsIgnoreCase(Pages.PAGE2)) {
            setContent(Pages.get(Pages.PAGE2));
        }
        else { // default behavior for a page not described:
            MainApp.debug(2, "(!) Unknown page: " + token);
            setContent(Pages.get(token));
        }
    }

    /**
     * Called by MainApp on a window resize event.
     * @param e the ResizeEvent
     */
    public static void resize(ResizeEvent e) {
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            @Override
            public void execute() {
                if(mainAppPresenter != null) {
                    mainAppPresenter.resize();
                }
                Pages.resize();
                Scheduler.get().scheduleFinally(resetPopupDimensions);
            }
        });
    }

    /**
     * Changes the cursor to "wait" or "auto" depending on the parameter
     * @param wait true to set the cursor to waiting
     */
    private static void waitCursor(Boolean wait) {
        if(wait) {
            RootPanel.getBodyElement().addClassName(CURSOR_WAIT_CLASS);
        }
        else {
            RootPanel.getBodyElement().removeClassName(CURSOR_WAIT_CLASS);
        }
    }
    /**
     * Private Constructor which initializes the Pages object.
     */
    private AppController() {
        Pages.init();
    }

    /**
     * Sets the content of the main app container to one of the "Pages."
     * @param c the Composite widget to be added
     */
    private static void setContent(Composite c) {
        container.clear();
        container.add(c.asWidget());
    }

    /**
     * Sets the content of the main app container an external URL.
     * @param f the Frame by which external web sites are added
     */ 
    private static void setExternalContentURL(Frame f) {
        container.clear();
        container.add(f);
        // must reset the width and height every time:
        f.getElement().getStyle().setWidth(100, Unit.PCT);
        f.getElement().getStyle().setHeight(100, Unit.PCT);
    }

    /**
     * Sets the content of the Page1 container to one of the sub pages.
     * @param c the Composite widget to be added
     */
    private static void setPage1Content(Composite c) {
        page1Container.clear();
        page1Container.add(c.asWidget());
    }
}

AppControlPresenter.java:

package com.*;

import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.user.client.ui.Composite;

/**
 * Base interface for all 'Presenters' used by AppController.java
 * NOTE: classes that implement this interface do not launch the presenter's view 
 * into the provided container; rather, the view is retrieved and used by the 
 * AppController instance by calling the 'view()' method
 */
public interface AppControlPresenter {

    /**
     * Gets the view (for use in AppController.java)
     */
    public Composite view();

    /**
     * Indicates if current search data is present and unsaved.
     * @returns true to if a search is still active  
     */
    public boolean unsavedData();

    /**
     * Called on resize event to notify presenters with visible
     * components that need resizing for different screen sizes.
     * @returns true if elements were resized
     */
    public boolean resize();

    /**
     * Called on attachEvents to tell the presenter to update.
     * @param event the AttachEvent
     */
    public void updateAttachOrDetach(AttachEvent event);

    /**
     * Gets the message to display for unsaved data.
     * @returns a message String describing the data
     */
    public String dataDescription();

    /**
     *  Gets a fully qualified name for use in comparisons
     * @return the name of this presenter used by the <code>AppController</code>
     */
    public String toString();
}

AppControlContainerPresenter.java:

package com.*;

import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.LayoutPanel;

/**
 */
public interface AppControlContainerPresenter extends AppControlPresenter {

    /**
     * 
     * @return
     */
    public LayoutPanel getContentPane();

    /**
     * 
     * @param pageName
     * @return
     */
    public Composite setCurrentPage(String pageName);
}

GWT Multipage - простая структура для многостраничных GWT-приложений.


Потрясающие! Я объединил ответ Криса Р. с Крисом Босингом, чтобы придумать это:

Это начальная страница индекса

public class Index implements EntryPoint, ValueChangeHandler<String> {
    public void onModuleLoad() {
        History.addValueChangeHandler(this);
        if (History.getToken().isEmpty()) History.newItem("index");
        Composite c = new Login(); 
        FlowControl.go(c);
    }

    public void onValueChange(ValueChangeEvent<String> e) {
        FlowControl.go(History.getToken());
    }
}

Это контроллер или ContentContainer в соответствии с Крисом Р.

public class FlowControl {
private static FlowControl instance;
private FlowControl() {}
public static void go(Composite c) {
    if (instance == null) instance = new FlowControl(); // not sure why we need this yet since everything is static.
    RootPanel.get("application").clear();
    RootPanel.get("application").getElement().getStyle().setPosition(Position.RELATIVE); // not sure why, but GWT throws an exception without this. Adding to CSS doesn't work.
    // add, determine height/width, center, then move. height/width are unknown until added to document. Catch-22!
    RootPanel.get("application").add(c);
    int left = Window.getClientWidth() / 2 - c.getOffsetWidth() / 2; // find center
    int top = Window.getClientHeight() / 2 - c.getOffsetHeight() / 2;
    RootPanel.get("application").setWidgetPosition(c, left, top);
    History.newItem(c.getTitle()); // TODO: need to change and implement (or override) this method on each screen
}

public static void go(String token) {
    if (token == null) go(new Login());
    if (token.equals("cart")) go(new Cart());
    if (token.equals("login")) go(new Login());
    // Can probably make these constants in this class
}

Затем вы можете перенести гиперссылки и кнопки в любом месте вашего кода. (Еще не пробовал Гиперссылки.)

    Button submit = new Button("Submit");
    submit.addClickHandler(new ClickHandler() {
        public void onClick(ClickEvent event) {
            FlowControl.go(new MyScreen());             
        }           
    });

Я добавил div в свой HTML

<!-- This is where the application will reside within. It is controlled by FlowControl class. -->
<div id="application"></div>

И теперь все экраны должны вызывать initWidget () в конструкторе вместо добавления в RootPanel, так как теперь это класс Composite, например

    initWidget(myPanel); // all composites must call this in constructor

Проверьте gwtXP на http://www.gdevelop.com/w/gwtxp/ .

gwtXP позволяет объявлять элементы интерфейса, привязывать данные и обработчики действий в XML. Примеры:

  • Объявить элементы пользовательского интерфейса: <g: label text = "Имя пользователя:" />
  • Объявить привязку данных (использует привязку данных Eclipse): <g: textBox text = "$ {user.userName}" />
  • Поддерживайте привязку значения и привязку списка.
  • Объявить обработчики действий: <g: button text = "Сохранить" onClick = "# {saveUser}" />

Обновлено 06 марта 20010: см. Сообщение на http://www.gdevelop.com/w/blog/2010/03/06/data-binding-in-gwt/