001package gwt.material.design.addins.client.stepper;
002
003/*
004 * #%L
005 * GwtMaterial
006 * %%
007 * Copyright (C) 2015 GwtMaterialDesign
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import com.google.gwt.core.client.GWT;
024import com.google.gwt.dom.client.Document;
025import com.google.gwt.event.logical.shared.SelectionEvent;
026import com.google.gwt.event.logical.shared.SelectionHandler;
027import com.google.gwt.event.shared.HandlerRegistration;
028import com.google.gwt.user.client.ui.Widget;
029import com.google.gwt.view.client.SelectionChangeEvent;
030import com.google.gwt.view.client.SelectionChangeEvent.Handler;
031import com.google.gwt.view.client.SelectionChangeEvent.HasSelectionChangedHandlers;
032import gwt.material.design.addins.client.MaterialAddins;
033import gwt.material.design.client.MaterialDesignBase;
034import gwt.material.design.client.base.HasAxis;
035import gwt.material.design.client.base.HasError;
036import gwt.material.design.client.base.MaterialWidget;
037import gwt.material.design.client.base.mixin.CssNameMixin;
038import gwt.material.design.client.constants.Axis;
039import gwt.material.design.client.ui.MaterialLoader;
040import gwt.material.design.client.ui.animate.MaterialAnimator;
041import gwt.material.design.client.ui.animate.Transition;
042import gwt.material.design.client.ui.html.Div;
043import gwt.material.design.client.ui.html.Span;
044
045//@formatter:off
046
047/**
048 * Steppers convey progress through numbered steps. They may also be used for navigation.
049 *
050 * <h3>XML Namespace Declaration</h3>
051 * <pre>
052 * {@code
053 * xmlns:ma='urn:import:gwt.material.design.addins.client'
054 * }
055 * </pre>
056 *
057 * <h3>UiBinder Usage:</h3>
058 * <pre>
059 * {@code
060 *  <ma:stepper.MaterialStepper ui:field="stepper">
061 *      <ma:stepper.MaterialStep step="1" title="Name of Step 1">
062 *          <m:MaterialPanel width="100%" height="300px" backgroundColor="grey lighten-2"/>
063 *          <m:MaterialButton ui:field="btnContinue1" text="Continue to Step 2" grid="l4" marginTop="12" backgroundColor="blue" textColor="white" waves="DEFAULT"/>
064 *          <m:MaterialButton ui:field="btnPrev1" text="Cancel" grid="l4" marginTop="12" type="FLAT" waves="DEFAULT"/>
065 *      </ma:stepper.MaterialStep>
066 *      &lt;!-- Other Step components here -->
067 *  </ma:stepper.MaterialStepper>
068 * }
069 * </pre>
070 *
071 * @author kevzlou7979
072 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#steppers">Material Steppers</a>
073 */
074// @formatter:on
075public class MaterialStepper extends MaterialWidget implements HasAxis, HasError, SelectionHandler<MaterialStep>, HasSelectionChangedHandlers {
076
077    static {
078        if(MaterialAddins.isDebug()) {
079            MaterialDesignBase.injectCss(MaterialStepperDebugClientBundle.INSTANCE.stepperDebugCss());
080        } else {
081            MaterialDesignBase.injectCss(MaterialStepperClientBundle.INSTANCE.stepperCss());
082        }
083    }
084
085    private int currentStepIndex = 0;
086    private Div divFeedback = new Div();
087    private Span feedback = new Span();
088    private boolean stepSkippingAllowed = true;
089
090    private final CssNameMixin<MaterialStepper, Axis> axisMixin = new CssNameMixin<>(this);
091
092    public MaterialStepper (){
093        super(Document.get().createDivElement(), "stepper");
094        divFeedback.setStyleName("feedback");
095        divFeedback.add(feedback);
096    }
097
098    @Override
099    protected void onLoad() {
100        super.onLoad();
101        if(getChildren().size() != 0) {
102            goToStep(currentStepIndex + 1);
103        }
104    }
105
106    /**
107     * Specific method to add {@link MaterialStep}s to the stepper.
108     */
109    public void add(MaterialStep step) {
110        this.add((Widget)step);
111        step.setAxis(getAxis());
112        step.addSelectionHandler(this);
113    }
114
115    /**
116     * Go to next Step , used by linear stepper.
117     */
118    public void nextStep() {
119        if(currentStepIndex >= getWidgetCount() - 1){
120            GWT.log("You have reach the maximum step.");
121        }else{
122            Widget w = getWidget(currentStepIndex);
123            if(w instanceof MaterialStep){
124                MaterialStep step = (MaterialStep) w;
125                step.setActive(false);
126                step.setSuccess(step.getDescription());
127
128                // next step
129                int nextStepIndex = getWidgetIndex(step) + 1;
130                if (nextStepIndex >= 0){
131                    for(int i = nextStepIndex; i < getWidgetCount(); i++){
132                        w = getWidget(i);
133                        if (!(w instanceof MaterialStep)){
134                            continue;
135                        }
136                        MaterialStep nextStep = (MaterialStep) w;
137                        if (nextStep.isEnabled() && nextStep.isVisible()){
138                            nextStep.setActive(true);
139                            setCurrentStepIndex(i);
140                            break;
141                        }
142                    }
143                }
144            }
145        }
146    }
147
148    /**
149     * Go to previous step , used by linear stepper.
150     */
151    public void prevStep() {
152        if(currentStepIndex > 0) {
153            Widget w = getWidget(currentStepIndex);
154            if (w instanceof MaterialStep) {
155                MaterialStep step = (MaterialStep) w;
156                step.setActive(false);
157
158                // next step
159                int prevStepIndex = getWidgetIndex(step) - 1;
160                if (prevStepIndex >= 0){
161                    for(int i = prevStepIndex; i >= 0; i--){
162                        w = getWidget(i);
163                        if (!(w instanceof MaterialStep)){
164                            continue;
165                        }
166                        MaterialStep prevStep = (MaterialStep) w;
167                        if (prevStep.isEnabled() && prevStep.isVisible()){
168                            prevStep.setActive(true);
169                            setCurrentStepIndex(i);
170                            break;
171                        }
172                    }
173                }
174            }
175        }else{
176            GWT.log("You have reach the minimum step.");
177        }
178    }
179
180    /**
181     * Go to specific step manually by setting which step index you want to go.
182     */
183    public void goToStep(int step){
184        for(int i = 0; i < getWidgetCount(); i++){
185            Widget w = getWidget(i);
186            if(w instanceof MaterialStep){
187                ((MaterialStep) w).setActive(false);
188            }
189        }
190
191        Widget w = getWidget(step - 1);
192        if(w instanceof MaterialStep){
193            ((MaterialStep) w).setActive(true);
194        }
195        setCurrentStepIndex(step - 1);
196    }
197
198    /**
199     * Go to the specfic {@link MaterialStep}.
200     */
201    public void goToStep(MaterialStep step){
202        for(int i = 0; i < getWidgetCount(); i++){
203            Widget w = getWidget(i);
204            if(w instanceof MaterialStep){
205                MaterialStep materialStep = (MaterialStep) w;
206                boolean active = materialStep.equals(step);
207                materialStep.setActive(active);
208                if (active){
209                    setCurrentStepIndex(i);
210                }
211            }
212        }
213    }
214
215    /**
216     * Go to the step with the specified step id.
217     *
218     * @see MaterialStep#getStep()
219     */
220    public void goToStepId(int id){
221        for(int i = 0; i < getWidgetCount(); i++){
222            Widget w = getWidget(i);
223            if(w instanceof MaterialStep){
224                MaterialStep materialStep = (MaterialStep) w;
225                boolean active = materialStep.getStep() == id;
226                materialStep.setActive(active);
227                if (active){
228                    setCurrentStepIndex(i);
229                }
230            }
231        }
232    }
233
234    /**
235     * Reset the Stepper to initial step (first step).
236     */
237    public void reset() {
238        goToStep(1);
239        clearErrorOrSuccess();
240    }
241
242    /**
243     * Called internally when the index is changed. Fires a {@link SelectionChangeEvent}
244     * when the current index changes.
245     */
246    protected void setCurrentStepIndex(int currentStepIndex) {
247        if (this.currentStepIndex != currentStepIndex){
248            this.currentStepIndex = currentStepIndex;
249            SelectionChangeEvent.fire(this);
250        }
251    }
252
253    public int getCurrentStepIndex() {
254        return currentStepIndex;
255    }
256
257    @Override
258    public void setAxis(Axis axis) {
259        axisMixin.setCssName(axis);
260        for(int i = 0; i < getWidgetCount(); i++){
261            Widget w = getWidget(i);
262            if(w instanceof MaterialStep){
263                ((MaterialStep) w).setAxis(axis);
264            }
265        }
266    }
267
268    @Override
269    public Axis getAxis() {
270        return axisMixin.getCssName();
271    }
272
273    /**
274     * Gets the current step component.
275     */
276    public MaterialStep getCurrentStep() {
277        if(currentStepIndex > getWidgetCount() - 1 || currentStepIndex < 0){
278            return null;
279        }
280        Widget w = getWidget(currentStepIndex);
281        if (w instanceof MaterialStep){
282            return (MaterialStep) w;
283        }
284        return null;
285    }
286
287    @Override
288    public void setError(String error) {
289        getCurrentStep().setError(error);
290    }
291
292    @Override
293    public void setSuccess(String success) {
294        getCurrentStep().setSuccess(success);
295    }
296    
297    @Override
298    public void setHelperText(String helperText) {
299        getCurrentStep().setDescription(helperText);
300    }
301
302    @Override
303    public void clearErrorOrSuccess() {
304        for(int i = 0; i < getWidgetCount(); i++){
305            Widget w = getWidget(i);
306            if(w instanceof MaterialStep){
307                ((MaterialStep) w).clearErrorOrSuccess();
308            }
309        }
310    }
311
312    /**
313     * Get feedback message.
314     */
315    public String getFeedback() {
316        return feedback.getElement().getInnerHTML();
317    }
318
319    /**
320     * Show feedback message and circular loader on body container
321     */
322    public void showFeedback(String feedbackText) {
323        feedback.setText(feedbackText);
324        MaterialAnimator.animate(Transition.FADEINUP, feedback, 500);
325        MaterialLoader.showLoading(true, getCurrentStep().getDivBody());
326        add(divFeedback);
327    }
328
329    /**
330     * Hide feedback message and circular loader on body container.
331     */
332    public void hideFeedback() {
333        divFeedback.removeFromParent();
334    }
335
336    /**
337     * Sets whether the user is allowed to skip steps by clicking on the step title.
338     * The default is <code>true</code>.
339     */
340    public void setStepSkippingAllowed(boolean stepSkippingAllowed) {
341        this.stepSkippingAllowed = stepSkippingAllowed;
342    }
343
344    /**
345     * Returns whether the user is allowed to skip steps by clicking on the step title.
346     * The default is <code>true</code>.
347     */
348    public boolean isStepSkippingAllowed() {
349        return stepSkippingAllowed;
350    }
351
352    /**
353     * Called when a step title is clicked.
354     */
355    @Override
356    public void onSelection(SelectionEvent<MaterialStep> event) {
357        if (stepSkippingAllowed){
358            goToStep(event.getSelectedItem());
359        }
360    }
361
362    @Override
363    public HandlerRegistration addSelectionChangeHandler(final Handler handler) {
364        return addHandler(new Handler() {
365            @Override
366            public void onSelectionChange(SelectionChangeEvent event) {
367                if(isEnabled()){
368                    handler.onSelectionChange(event);
369                }
370            }
371        }, SelectionChangeEvent.getType());
372    }
373}