Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix titles for many steps #28

Open
wants to merge 10 commits into
base: vaadin7
Choose a base branch
from
121 changes: 72 additions & 49 deletions wizards-for-vaadin/src/main/java/org/vaadin/teemu/wizards/Wizard.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
package org.vaadin.teemu.wizards;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.vaadin.teemu.wizards.event.WizardCancelledEvent;
import org.vaadin.teemu.wizards.event.WizardCompletedEvent;
import org.vaadin.teemu.wizards.event.WizardProgressListener;
import org.vaadin.teemu.wizards.event.WizardStepActivationEvent;
import org.vaadin.teemu.wizards.event.WizardStepSetChangedEvent;

import com.vaadin.server.Page;
import com.vaadin.server.Page.UriFragmentChangedEvent;
import com.vaadin.server.Page.UriFragmentChangedListener;
Expand All @@ -24,16 +11,27 @@
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.vaadin.teemu.wizards.event.WizardCancelledEvent;
import org.vaadin.teemu.wizards.event.WizardCompletedEvent;
import org.vaadin.teemu.wizards.event.WizardProgressListener;
import org.vaadin.teemu.wizards.event.WizardStepActivationEvent;
import org.vaadin.teemu.wizards.event.WizardStepSetChangedEvent;

/**
* Component for displaying multi-step wizard style user interface.
*
*
* <p>
* The steps of the wizard must be implementations of the {@link WizardStep}
* interface. Use the {@link #addStep(WizardStep)} method to add these steps in
* the same order they are supposed to be displayed.
* </p>
*
*
* <p>
* The wizard also supports navigation through URI fragments. This feature is
* disabled by default, but you can enable it using
Expand All @@ -42,24 +40,26 @@
* override these with your own identifiers, you can add the steps using the
* overloaded {@link #addStep(WizardStep, String)} method.
* </p>
*
*
* <p>
* To react on the progress, cancellation or completion of this {@code Wizard}
* you should add one or more listeners that implement the
* {@link WizardProgressListener} interface. These listeners are added using the
* {@link #addListener(WizardProgressListener)} method and removed with the
* {@link #removeListener(WizardProgressListener)}.
* </p>
*
*
* @author Teemu Pöntelin / Vaadin Ltd
*/
@SuppressWarnings("serial")
public class Wizard extends CustomComponent implements
UriFragmentChangedListener {

protected final List<WizardStep> steps = new ArrayList<WizardStep>();
protected final Map<String, WizardStep> idMap = new HashMap<String, WizardStep>();
private final Map<WizardStep, ScrollPosition> scrollPositions = new HashMap<WizardStep, ScrollPosition>();
protected final Map<String, WizardStep> idMap
= new HashMap<String, WizardStep>();
private final Map<WizardStep, ScrollPosition> scrollPositions
= new HashMap<WizardStep, ScrollPosition>();

protected WizardStep currentStep;
protected WizardStep lastCompletedStep;
Expand All @@ -77,6 +77,7 @@ public class Wizard extends CustomComponent implements

private Component header;
private boolean uriFragmentEnabled;
private WizardProgressBar progressBar;

private static final Method WIZARD_ACTIVE_STEP_CHANGED_METHOD;
private static final Method WIZARD_STEP_SET_CHANGED_METHOD;
Expand All @@ -87,16 +88,16 @@ public class Wizard extends CustomComponent implements
try {
WIZARD_COMPLETED_METHOD = WizardProgressListener.class
.getDeclaredMethod("wizardCompleted",
new Class[] { WizardCompletedEvent.class });
new Class[]{WizardCompletedEvent.class});
WIZARD_STEP_SET_CHANGED_METHOD = WizardProgressListener.class
.getDeclaredMethod("stepSetChanged",
new Class[] { WizardStepSetChangedEvent.class });
new Class[]{WizardStepSetChangedEvent.class});
WIZARD_ACTIVE_STEP_CHANGED_METHOD = WizardProgressListener.class
.getDeclaredMethod("activeStepChanged",
new Class[] { WizardStepActivationEvent.class });
new Class[]{WizardStepActivationEvent.class});
WIZARD_CANCELLED_METHOD = WizardProgressListener.class
.getDeclaredMethod("wizardCancelled",
new Class[] { WizardCancelledEvent.class });
new Class[]{WizardCancelledEvent.class});
} catch (final java.lang.NoSuchMethodException e) {
// This should never happen
throw new java.lang.RuntimeException(
Expand All @@ -105,6 +106,7 @@ public class Wizard extends CustomComponent implements
}

private static final class ScrollPosition {

int scrollTop;
int scrollLeft;

Expand All @@ -114,6 +116,9 @@ public ScrollPosition(int scrollTop, int scrollLeft) {
}
}

/**
* Default constructor.
*/
public Wizard() {
setStyleName("wizard");
init();
Expand Down Expand Up @@ -149,20 +154,23 @@ private void init() {
private void initControlButtons() {
nextButton = new Button("Next");
nextButton.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
next();
}
});

backButton = new Button("Back");
backButton.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
back();
}
});

finishButton = new Button("Finish");
finishButton.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
finish();
}
Expand All @@ -171,14 +179,16 @@ public void buttonClick(ClickEvent event) {

cancelButton = new Button("Cancel");
cancelButton.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
cancel();
}
});
}

private void initDefaultHeader() {
WizardProgressBar progressBar = new WizardProgressBar(this);
progressBar = new WizardProgressBar(this);
progressBar.setMaxStepsDisplayed(getDisplayedMaxTitles());
addListener(progressBar);
setHeader(progressBar);
}
Expand All @@ -199,10 +209,9 @@ public boolean isUriFragmentEnabled() {
/**
* Sets a {@link Component} that is displayed on top of the actual content.
* Set to {@code null} to remove the header altogether.
*
* @param newHeader
* {@link Component} to be displayed on top of the actual content
* or {@code null} to remove the header.
*
* @param newHeader {@link Component} to be displayed on top of the actual
* content or {@code null} to remove the header.
*/
public void setHeader(Component newHeader) {
if (header != null) {
Expand All @@ -222,14 +231,14 @@ public void setHeader(Component newHeader) {
/**
* Returns a {@link Component} that is displayed on top of the actual
* content or {@code null} if no header is specified.
*
*
* <p>
* By default the header is a {@link WizardProgressBar} component that is
* also registered as a {@link WizardProgressListener} to this Wizard.
* </p>
*
*
* @return {@link Component} that is displayed on top of the actual content
* or {@code null}.
* or {@code null}.
*/
public Component getHeader() {
return header;
Expand All @@ -240,11 +249,10 @@ public Component getHeader() {
* must be unique or an {@link IllegalArgumentException} is thrown. If you
* don't wish to explicitly provide an identifier, you can use the
* {@link #addStep(WizardStep)} method.
*
*
* @param step
* @param id
* @throws IllegalStateException
* if the given {@code id} already exists.
* @throws IllegalStateException if the given {@code id} already exists.
*/
public void addStep(WizardStep step, String id) {
if (idMap.containsKey(id)) {
Expand Down Expand Up @@ -272,7 +280,7 @@ public void addStep(WizardStep step, String id) {
* automatically. If you wish to provide an explicit identifier for your
* WizardStep, you can use the {@link #addStep(WizardStep, String)} method
* instead.
*
*
* @param step
*/
public void addStep(WizardStep step) {
Expand Down Expand Up @@ -307,9 +315,8 @@ public List<WizardStep> getSteps() {

/**
* Returns {@code true} if the given step is already completed by the user.
*
* @param step
* step to check for completion.
*
* @param step step to check for completion.
* @return {@code true} if the given step is already completed.
*/
public boolean isCompleted(WizardStep step) {
Expand All @@ -318,9 +325,8 @@ public boolean isCompleted(WizardStep step) {

/**
* Returns {@code true} if the given step is the currently active step.
*
* @param step
* step to check for.
*
* @param step step to check for.
* @return {@code true} if the given step is the currently active step.
*/
public boolean isActive(WizardStep step) {
Expand Down Expand Up @@ -429,9 +435,9 @@ protected void activateStep(String id) {
// check that we don't go past the lastCompletedStep by using the id
int lastCompletedIndex = lastCompletedStep == null ? -1 : steps
.indexOf(lastCompletedStep);
int stepIndex = steps.indexOf(step);
int index = steps.indexOf(step);

if (lastCompletedIndex < stepIndex) {
if (lastCompletedIndex < index) {
activateStep(lastCompletedStep);
} else {
activateStep(step);
Expand Down Expand Up @@ -537,9 +543,8 @@ public void uriFragmentChanged(UriFragmentChangedEvent event) {
* Removes the given step from this Wizard. An {@link IllegalStateException}
* is thrown if the given step is already completed or is the currently
* active step.
*
* @param stepToRemove
* the step to remove.
*
* @param stepToRemove the step to remove.
* @see #isCompleted(WizardStep)
* @see #isActive(WizardStep)
*/
Expand All @@ -559,9 +564,8 @@ public void removeStep(WizardStep stepToRemove) {
* Removes the step with given id from this Wizard. An
* {@link IllegalStateException} is thrown if the given step is already
* completed or is the currently active step.
*
* @param id
* identifier of the step to remove.
*
* @param id identifier of the step to remove.
* @see #isCompleted(WizardStep)
* @see #isActive(WizardStep)
*/
Expand All @@ -585,4 +589,23 @@ public void removeStep(String id) {
}
}

/**
* Get amount of steps to display on the header at a time. A value of -1
* means all will be displayed.
*
* @return the displayedMaxTitles
*/
public int getDisplayedMaxTitles() {
return progressBar.getMaxStepsDisplayed();
}

/**
* Set amount of steps to display on the header at a time. A value of -1
* means all will be displayed.
*
* @param displayedMaxTitles the displayedMaxTitles to set
*/
public void setDisplayedMaxTitles(int displayedMaxTitles) {
progressBar.setMaxStepsDisplayed(displayedMaxTitles);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
package org.vaadin.teemu.wizards;

import java.util.List;

import org.vaadin.teemu.wizards.event.WizardCancelledEvent;
import org.vaadin.teemu.wizards.event.WizardCompletedEvent;
import org.vaadin.teemu.wizards.event.WizardProgressListener;
import org.vaadin.teemu.wizards.event.WizardStepActivationEvent;
import org.vaadin.teemu.wizards.event.WizardStepSetChangedEvent;

import com.vaadin.annotations.StyleSheet;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.VerticalLayout;
import java.util.List;
import org.vaadin.teemu.wizards.event.WizardCancelledEvent;
import org.vaadin.teemu.wizards.event.WizardCompletedEvent;
import org.vaadin.teemu.wizards.event.WizardProgressListener;
import org.vaadin.teemu.wizards.event.WizardStepActivationEvent;
import org.vaadin.teemu.wizards.event.WizardStepSetChangedEvent;

/**
* Displays a progress bar for a {@link Wizard}.
Expand All @@ -27,6 +25,8 @@ public class WizardProgressBar extends CustomComponent implements
private final ProgressBar progressBar = new ProgressBar();
private final HorizontalLayout stepCaptions = new HorizontalLayout();
private int activeStepIndex;
//Amount of steps displayed at a time in the header.
private int maxStepsDisplayed = -1;//Show all

public WizardProgressBar(Wizard wizard) {
setStyleName("wizard-progress-bar");
Expand Down Expand Up @@ -54,10 +54,16 @@ private void updateProgressBar() {
private void updateStepCaptions() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I merged your functionality in my project but prefered more sofisticated algorithm for choosing the titles to display:
// Rules when advancing: keep right pages after this one as much as possible
// This way, user will see in priority the next pages he is going to navigate
// in this direction (advancing)
// Rules when navigating back: keep left pages as much as possible
// This way, user will see in priority the next pages he is going to navigate
// in this direction (going back)

private void updateStepCaptions()
{
    stepCaptions.removeAllComponents();
    boolean advancing = (activeStepIndex > activeStepIndexPrevious);
    int nbDisplayed=0;

    if( advancing )
    {
        // Rules when advancing: keep right pages after this one as much as possible
        // This way, user will see in priority the next pages he is going to navigate
        // in this direction (advancing)
        int index = 0;
        for(WizardStep step : wizard.getSteps())
        {
            if( index++<activeStepIndex ) continue; // loop until current page
            if
            (
                // No need to test further if there's no limitation
                maxStepsDisplayed < 0
                ||
                nbDisplayed<maxStepsDisplayed
            )
            {
                Label label = createCaptionLabel(index, step);
                stepCaptions.addComponent(label);
                nbDisplayed++;
            }
        }
        // Let's see if we can add pages to the left
        for
        (
            index = activeStepIndex;
            index>=0;
            index--
        )
        {
            if( index==activeStepIndex ) continue; // current page was done already
            if
            (
                // No need to test further if there's no limitation
                maxStepsDisplayed < 0
                ||
                nbDisplayed<maxStepsDisplayed
            )
            {
                WizardStep step=null;
                for(WizardStep s : wizard.getSteps())
                    if(wizard.getSteps().indexOf(s)==index) step=s;
                Label label = createCaptionLabel(index+1, step);
                // we loop in reverse order... so add on top (e.g. to the left)
                stepCaptions.addComponentAsFirst(label);
                nbDisplayed++;
            }
        }
    }
    else
    {
        // Rules when navigating back: keep left pages as much as possible
        // This way, user will see in priority the next pages he is going to navigate
        // in this direction (going back)            
        int index = 0;
        // We first add pages to the left
        for
        (
            index = activeStepIndex;
            index>=0;
            index--
        )
        {
            if
            (
                // No need to test further if there's no limitation
                maxStepsDisplayed < 0
                ||
                nbDisplayed<maxStepsDisplayed
            )
            {
                WizardStep step=null;
                for(WizardStep s : wizard.getSteps())
                    if(wizard.getSteps().indexOf(s)==index) step=s;
                Label label = createCaptionLabel(index+1, step);
                // we loop in reverse order... so add on top (e.g. to the left)
                stepCaptions.addComponentAsFirst(label);
                nbDisplayed++;
            }
        }
        // now let see if we still have room for right pages
        index = 0;
        for(WizardStep step : wizard.getSteps())
        {
            if( index++<(activeStepIndex+1) ) continue; // loop until current page + 1 (because it's already done)
            if
            (
                // No need to test further if there's no limitation
                maxStepsDisplayed < 0
                ||
                nbDisplayed<maxStepsDisplayed
            )
            {
                Label label = createCaptionLabel(index, step);
                stepCaptions.addComponent(label);
                nbDisplayed++;
            }
        }

    }

}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If to be implemented... forgot to mention it requires remembering last navigation step.
WizardProgressBar class must have activeStepIndexPrevious member

private int activeStepIndexPrevious = -1;
private int activeStepIndex = -1;

And backup previous events in it inside activeStepChanged Override

@Override
public void activeStepChanged(WizardStepActivationEvent event)
{
    List<WizardStep> allSteps = wizard.getSteps();
    activeStepIndexPrevious = activeStepIndex;
    activeStepIndex = allSteps.indexOf(event.getActivatedStep());
    updateProgressAndCaptions();
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it change the API in any way or should it be a drop in replacement?

stepCaptions.removeAllComponents();
int index = 1;
//Just show 3 at a time
for (WizardStep step : wizard.getSteps()) {
Label label = createCaptionLabel(index, step);
stepCaptions.addComponent(label);
if (index > activeStepIndex) {
Label label = createCaptionLabel(index, step);
stepCaptions.addComponent(label);
}
index++;
if (maxStepsDisplayed > 0 && index > activeStepIndex + maxStepsDisplayed) {
break;
}
}
}

Expand Down Expand Up @@ -109,4 +115,27 @@ public void wizardCompleted(WizardCompletedEvent event) {
public void wizardCancelled(WizardCancelledEvent event) {
// NOP, no need to react to cancellation
}

/**
* Get amount of steps to display on the header at a time. A value of -1
* means all will be displayed.
*
* @return the maxStepsDisplayed
*/
public int getMaxStepsDisplayed() {
return maxStepsDisplayed;
}

/**
* Set the maximum amount of steps to display on the header at a time to
* avoid them being overlapping.
*
* A value of -1 means all will be displayed.
*
* @param maxStepsDisplayed the maxStepsDisplayed to set
*/
public void setMaxStepsDisplayed(int maxStepsDisplayed) {
this.maxStepsDisplayed = maxStepsDisplayed;
updateStepCaptions();
}
}