/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2011 Geosparc nv, http://www.geosparc.com/, Belgium.
*
* The program is available in open source according to the GNU Affero
* General Public License. All contributions in this program are covered
* by the Geomajas Contributors License Agreement. For full licensing
* details, see LICENSE.txt in the project root.
*/
package org.geomajas.widget.utility.smartgwt.client.wizard;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.geomajas.annotation.FutureApi;
import com.google.gwt.user.client.ui.Widget;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
/**
* <p>
* Wizard class that lets a user step through a set of pages to accomplish a certain task. In addition to the classical
* NEXT, BACK, CANCEL and FINISH buttons, this wizard provides direct page navigation by means of a button per page
* (typically shown in a vertical navigation bar). This abstract class handles all the navigation logic and state
* handling of the buttons. The NEXT and BACK button are handled directly by this class. The CANCEL and FINISH button
* should be handled by concrete subclasses by implementing the abstract {@link onCancel()} and {@link onFinish()}
* methods.
* </p>
*
* <p>
* The view is abstracted through the {@link WizardView} interface. A default implementation, called
* {@link WizardWidget} is provided.
* </p>
*
* <p>
* Individual pages can be created by sub-classing the {@link WizardPage} class. This class has a template parameter
* DATA, which represents the type of the data context that is passed to all pages on startup.
* </p>
*
* <p>
* Typical usage is as follows:
* </p>
* <code>
* <pre>
* Wizard<MyData> wizard = new MyWizard(new WizardWidget<MyData>());
* wizard.addPage(new MyPage1());
* wizard.addPage(new MyPage2());
* wizard.addPage(new MyPage3());
* // add the view to your application
* layout.addMember(wizard.getView());
* // pass the data context and show the first page
* wizard.start(new MyData());
*
* </pre>
* </code>
*
* @param <DATA> data type
*
* @author Jan De Moerloose
*/
@FutureApi
public abstract class Wizard<DATA> {
private List<WizardPage<DATA>> pages;
private WizardPage<DATA> currentPage;
private WizardView<DATA> wizardView;
private boolean started;
/**
* Create a wizard with the specified view.
*
* @param wizardView
* the view part
*/
public Wizard(WizardView<DATA> wizardView) {
this.wizardView = wizardView;
pages = new ArrayList<WizardPage<DATA>>();
}
/**
* Returns the view part of this wizard.
*
* @return the view part.
*/
public WizardView<DATA> getView() {
return wizardView;
}
/**
* Starts or restarts this wizard by clearing all pages and presenting the first page to the user. This method
* should only be called after all pages have been added. After this method has been called, no more pages can be
* added.
*
* @param wizardData
*/
public void start(DATA wizardData) {
if (!started) {
initHandlers();
}
for (WizardPage<DATA> page : pages) {
page.clear();
page.setWizardData(wizardData);
}
ListIterator<WizardPage<DATA>> iterator = pages.listIterator();
if (iterator.hasNext()) {
setCurrentPage(iterator.next());
}
}
/**
* Returns the currently visible page.
*
* @return the current page
*/
public WizardPage<DATA> getCurrentPage() {
return currentPage;
}
/**
* Sets the current page of this wizard. As this method forces the current page, it should only be used after
* checking validity of the previous pages.
*
* @param newPage
* the page to be set
*/
protected void setCurrentPage(WizardPage<DATA> newPage) {
currentPage = newPage;
updateState();
wizardView.setCurrentPage(currentPage);
currentPage.show();
}
/**
* Adds a page to this wizard. The page order is determined by the order in which pages are added.
*
* @param page
* the page to be added
*/
public void addPage(WizardPage<DATA> page) {
if (!started) {
WizardPage<DATA> lastPage = pages.size() > 0 ? pages.get(pages.size() - 1) : null;
if (lastPage != null) {
lastPage.setNextPage(page);
page.setBackPage(lastPage);
}
pages.add(page);
wizardView.addPageToView(page);
}
}
/**
* Called when the user presses the CANCEL button.
*/
protected abstract void onCancel();
/**
* Called when the user presses the FINISH button.
*/
protected abstract void onFinish();
private void initHandlers() {
for (WizardButton<DATA> button : wizardView.getButtons()) {
switch (button.getType()) {
case PREVIOUS:
button.addClickHandler(new BackHandler());
break;
case CANCEL:
button.addClickHandler(new CancelHandler());
break;
case FINISH:
button.addClickHandler(new FinishHandler());
break;
case NEXT:
button.addClickHandler(new NextHandler());
break;
case PAGE:
button.addClickHandler(new GoToHandler(button.getPage()));
break;
}
}
started = true;
}
private void updateState() {
for (WizardButton<DATA> button : wizardView.getButtons()) {
switch (button.getType()) {
case PREVIOUS:
if (currentPage.getBackPage() != null) {
button.setEnabled(true);
} else {
button.setEnabled(false);
}
break;
case CANCEL:
button.setEnabled(true);
break;
case FINISH:
if (currentPage.getNextPage() == null) {
button.setEnabled(true);
} else {
button.setEnabled(false);
}
break;
case NEXT:
if (currentPage.getNextPage() != null) {
button.setEnabled(true);
} else {
button.setEnabled(false);
}
break;
case PAGE:
button.setActive(button.getPage() == currentPage);
button.setEnabled(button.getPage().canShow());
Widget widget = button.getPage().asWidget();
if (widget != null) {
widget.setVisible(button.getPage() == currentPage);
}
break;
}
}
}
/**
* Performs FINISH operation.
*
* @author Jan De Moerloose
*
*/
class FinishHandler implements ClickHandler {
public void onClick(ClickEvent event) {
onFinish();
}
}
/**
* Performs CANCEL operation.
*
* @author Jan De Moerloose
*
*/
class CancelHandler implements ClickHandler {
public void onClick(ClickEvent event) {
onCancel();
}
}
/**
* Performs BACK operation.
*
* @author Jan De Moerloose
*
*/
class BackHandler implements ClickHandler {
public void onClick(ClickEvent event) {
setCurrentPage(currentPage.getBackPage());
}
}
/**
* Performs NEXT operation.
*
* @author Jan De Moerloose
*
*/
class NextHandler implements ClickHandler {
public void onClick(ClickEvent event) {
if (currentPage.validate()) {
setCurrentPage(currentPage.getNextPage());
}
}
}
/**
* Performs page operation.
*
* @author Jan De Moerloose
*
*/
class GoToHandler implements ClickHandler {
private WizardPage<DATA> page;
public GoToHandler(WizardPage<DATA> page) {
this.page = page;
}
public void onClick(ClickEvent event) {
// must check all previous pages and stop if we reach an invalid page
// because user may have changed page info behind our back !
Iterator<WizardPage<DATA>> it = pages.iterator();
while (it.hasNext()) {
WizardPage<DATA> p = it.next();
if (p == page || !p.validate()) {
setCurrentPage(p);
break;
}
}
}
}
}