001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.extensions.wizard;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Set;
024
025import org.apache.wicket.Component;
026import org.apache.wicket.extensions.wizard.dynamic.DynamicWizardModel;
027import org.apache.wicket.markup.html.basic.Label;
028import org.apache.wicket.markup.html.form.Form;
029import org.apache.wicket.markup.html.form.FormComponent;
030import org.apache.wicket.markup.html.form.validation.IFormValidator;
031import org.apache.wicket.markup.html.panel.Panel;
032import org.apache.wicket.model.CompoundPropertyModel;
033import org.apache.wicket.model.IModel;
034import org.apache.wicket.model.Model;
035
036
037/**
038 * default implementation of {@link IWizardStep}. It is also a panel, which is used as the view
039 * component.
040 * 
041 * <p>
042 * And example of a custom step with a panel follows.
043 * 
044 * Java (defined e.g. in class x.NewUserWizard):
045 * 
046 * <pre>
047 * private final class UserNameStep extends WizardStep
048 * {
049 *      public UserNameStep()
050 *      {
051 *              super(new ResourceModel(&quot;username.title&quot;), new ResourceModel(&quot;username.summary&quot;));
052 *              add(new RequiredTextField(&quot;user.userName&quot;));
053 *              add(new RequiredTextField(&quot;user.email&quot;).add(EmailAddressValidator.getInstance()));
054 *      }
055 * }
056 * </pre>
057 * 
058 * HTML (defined in e.g. file x/NewUserWizard$UserNameStep.html):
059 * 
060 * <pre>
061 *  &lt;wicket:panel&gt;
062 *   &lt;table&gt;
063 *    &lt;tr&gt;
064 *     &lt;td&gt;&lt;wicket:message key=&quot;username&quot;&gt;Username&lt;/wicket:message&gt;&lt;/td&gt;
065 *     &lt;td&gt;&lt;input type=&quot;text&quot; wicket:id=&quot;user.userName&quot; /&gt;&lt;/td&gt;
066 *    &lt;/tr&gt;
067 *    &lt;tr&gt;
068 *     &lt;td&gt;&lt;wicket:message key=&quot;email&quot;&gt;Email Address&lt;/wicket:message&gt;&lt;/td&gt;
069 *     &lt;td&gt;&lt;input type=&quot;text&quot; wicket:id=&quot;user.email&quot; /&gt;&lt;/td&gt;
070 *    &lt;/tr&gt;
071 *   &lt;/table&gt;
072 *  &lt;/wicket:panel&gt;
073 * </pre>
074 * 
075 * </p>
076 * 
077 * @author Eelco Hillenius
078 */
079public class WizardStep extends Panel implements IWizardStep
080{
081        /**
082         * Wraps form validators for this step such that they are only executed when this step is
083         * active.
084         */
085        private final class FormValidatorWrapper implements IFormValidator
086        {
087
088                private static final long serialVersionUID = 1L;
089
090                private final List<IFormValidator> validators = new ArrayList<>();
091
092                /**
093                 * Adds a form validator.
094                 * 
095                 * @param validator
096                 *            The validator to add
097                 */
098                public final void add(final IFormValidator validator)
099                {
100                        validators.add(validator);
101                }
102
103                /**
104                 * @see org.apache.wicket.markup.html.form.validation.IFormValidator#getDependentFormComponents()
105                 */
106                @Override
107                public FormComponent<?>[] getDependentFormComponents()
108                {
109                        if (isActiveStep())
110                        {
111                                Set<Component> components = new HashSet<>();
112                                for (IFormValidator v : validators)
113                                {
114                                        FormComponent<?>[] dependentComponents = v.getDependentFormComponents();
115                                        if (dependentComponents != null)
116                                        {
117                                                int len = dependentComponents.length;
118                                                components.addAll(Arrays.asList(dependentComponents).subList(0, len));
119                                        }
120                                }
121                                return components.toArray(new FormComponent[components.size()]);
122                        }
123                        return null;
124                }
125
126                /**
127                 * @see org.apache.wicket.markup.html.form.validation.IFormValidator#validate(org.apache.wicket.markup.html.form.Form)
128                 */
129                @Override
130                public void validate(final Form<?> form)
131                {
132                        if (isActiveStep())
133                        {
134                                for (IFormValidator v : validators)
135                                {
136                                        v.validate(form);
137                                }
138                        }
139                }
140
141                /**
142                 * @return whether the step this wrapper is part of is the current step
143                 */
144                private boolean isActiveStep()
145                {
146                        return (wizardModel.getActiveStep().equals(WizardStep.this));
147                }
148        }
149
150        /**
151         * Default header for wizards.
152         */
153        private final class Header extends Panel
154        {
155                private static final long serialVersionUID = 1L;
156
157                /**
158                 * Construct.
159                 * 
160                 * @param id
161                 *            The component id
162                 * @param wizard
163                 *            The containing wizard
164                 */
165                public Header(final String id, final IWizard wizard)
166                {
167                        super(id);
168                        setDefaultModel(new CompoundPropertyModel<>(wizard));
169                        add(new Label("title", WizardStep.this::getTitle));
170                        add(new Label("summary", WizardStep.this::getSummary));
171                }
172        }
173
174        private static final long serialVersionUID = 1L;
175
176        /**
177         * Marks this step as being fully configured. Only when this is <tt>true</tt> can the wizard
178         * progress. True by default as that works best with normal forms. Clients can set this to false
179         * if some intermediate step, like a file upload, needs to be completed before the wizard may
180         * progress.
181         */
182        private boolean complete = true;
183
184        /**
185         * A summary of this step, or some usage advice.
186         */
187        private IModel<String> summary;
188
189        /**
190         * The title of this step.
191         */
192        private IModel<String> title;
193
194        /**
195         * The wizard model.
196         */
197        private IWizardModel wizardModel;
198
199        /**
200         * The wrapper of {@link IFormValidator}s for this step.
201         */
202        private FormValidatorWrapper formValidatorWrapper = new FormValidatorWrapper();
203
204        /**
205         * Construct without a title and a summary. Useful for when you provide a custom header by
206         * overiding {@link #getHeader(String, Component, IWizard)}.
207         */
208        public WizardStep()
209        {
210                super(Wizard.VIEW_ID);
211        }
212
213        /**
214         * Creates a new step with the specified title and summary. The title and summary are displayed
215         * in the wizard title block while this step is active.
216         * 
217         * @param title
218         *            the title of this step.
219         * @param summary
220         *            a brief summary of this step or some usage guidelines.
221         */
222        public WizardStep(final IModel<String> title, final IModel<String> summary)
223        {
224                this(title, summary, null);
225        }
226
227        /**
228         * Creates a new step with the specified title and summary. The title and summary are displayed
229         * in the wizard title block while this step is active.
230         * 
231         * @param title
232         *            the title of this step.
233         * @param summary
234         *            a brief summary of this step or some usage guidelines.
235         * @param model
236         *            Any model which is to be used for this step
237         */
238        public WizardStep(final IModel<String> title, final IModel<String> summary,
239                final IModel<?> model)
240        {
241                super(Wizard.VIEW_ID, model);
242
243                this.title = wrap(title);
244                this.summary = wrap(summary);
245        }
246
247        /**
248         * Creates a new step with the specified title and summary. The title and summary are displayed
249         * in the wizard title block while this step is active.
250         * 
251         * @param title
252         *            the title of this step.
253         * @param summary
254         *            a brief summary of this step or some usage guidelines.
255         */
256        public WizardStep(final String title, final String summary)
257        {
258                this(title, summary, null);
259        }
260
261        /**
262         * Creates a new step with the specified title and summary. The title and summary are displayed
263         * in the wizard title block while this step is active.
264         * 
265         * @param title
266         *            the title of this step.
267         * @param summary
268         *            a brief summary of this step or some usage guidelines.
269         * @param model
270         *            Any model which is to be used for this step
271         */
272        public WizardStep(final String title, final String summary, final IModel<?> model)
273        {
274                this(new Model<String>(title), new Model<>(summary), model);
275        }
276
277        /**
278         * Adds a form validator.
279         * 
280         * @param validator
281         */
282        public final void add(final IFormValidator validator)
283        {
284                formValidatorWrapper.add(validator);
285        }
286
287        /**
288         * @see org.apache.wicket.extensions.wizard.IWizardStep#applyState()
289         */
290        @Override
291        public void applyState()
292        {
293        }
294
295        /**
296         * @see org.apache.wicket.extensions.wizard.IWizardStep#getHeader(java.lang.String,
297         *      org.apache.wicket.Component, org.apache.wicket.extensions.wizard.IWizard)
298         */
299        @Override
300        public Component getHeader(final String id, final Component parent, final IWizard wizard)
301        {
302                return new Header(id, wizard);
303        }
304
305        /**
306         * Gets the summary of this step. This will be displayed in the title of the wizard while this
307         * step is active. The summary is typically an overview of the step or some usage guidelines for
308         * the user.
309         * 
310         * @return the summary of this step.
311         */
312        public String getSummary()
313        {
314                return (summary != null) ? summary.getObject() : null;
315        }
316
317        /**
318         * Gets the title of this step.
319         * 
320         * @return the title of this step.
321         */
322        public String getTitle()
323        {
324                return (title != null) ? title.getObject() : null;
325        }
326
327        /**
328         * @see org.apache.wicket.extensions.wizard.IWizardStep#getView(java.lang.String,
329         *      org.apache.wicket.Component, org.apache.wicket.extensions.wizard.IWizard)
330         */
331        @Override
332        public Component getView(final String id, final Component parent, final IWizard wizard)
333        {
334                return this;
335        }
336
337        /**
338         * Called to initialize the step. When this method is called depends on the kind of wizard model
339         * that is used.
340         * 
341         * The {@link WizardModel static wizard model} knows all the steps upfront and initializes themm
342         * when starting up. This method will be called when the wizard is {@link #init(IWizardModel)
343         * initializing}.
344         * 
345         * The {@link DynamicWizardModel dynamic wizard model} initializes steps every time they are
346         * encountered.
347         * 
348         * This method sets the wizard model and then calls template method
349         * {@link #onInit(IWizardModel)}
350         * 
351         * @param wizardModel
352         *            the model to which the step belongs.
353         */
354        @Override
355        public final void init(final IWizardModel wizardModel)
356        {
357                this.wizardModel = wizardModel;
358                onInit(wizardModel);
359        }
360
361        /**
362         * Checks if this step is compete. This method should return true if the wizard can proceed to
363         * the next step. This property is bound and changes can be made at anytime by calling
364         * {@link #setComplete(boolean)} .
365         * 
366         * @return <tt>true</tt> if the wizard can proceed from this step, <tt>false</tt> otherwise.
367         * @see #setComplete
368         */
369        @Override
370        public boolean isComplete()
371        {
372                return complete;
373        }
374
375        /**
376         * Marks this step as compete. The wizard will not be able to proceed from this step until this
377         * property is configured to <tt>true</tt>.
378         * 
379         * @param complete
380         *            <tt>true</tt> to allow the wizard to proceed, <tt>false</tt> otherwise.
381         * @see #isComplete
382         */
383        public void setComplete(final boolean complete)
384        {
385                this.complete = complete;
386        }
387
388        /**
389         * Sets summary.
390         * 
391         * @param summary
392         *            summary
393         */
394        public void setSummaryModel(final IModel<String> summary)
395        {
396                this.summary = wrap(summary);
397        }
398
399        /**
400         * Sets title.
401         * 
402         * @param title
403         *            title
404         */
405        public void setTitleModel(final IModel<String> title)
406        {
407                this.title = wrap(title);
408        }
409
410        /**
411         * @see org.apache.wicket.Component#detachModel()
412         */
413        @Override
414        protected void detachModel()
415        {
416                super.detachModel();
417                if (title != null)
418                {
419                        title.detach();
420                }
421                if (summary != null)
422                {
423                        summary.detach();
424                }
425        }
426
427        @Override
428        protected void onInitialize()
429        {
430                super.onInitialize();
431
432                Form<?> form = findParent(Form.class);
433                form.add(formValidatorWrapper);
434        }
435
436        /**
437         * Template method that is called when the step is being initialized.
438         * 
439         * @param wizardModel
440         * @see #init(IWizardModel)
441         */
442        protected void onInit(final IWizardModel wizardModel)
443        {
444        }
445
446        /**
447         * @return wizard model
448         */
449        public IWizardModel getWizardModel()
450        {
451                return wizardModel;
452        }
453}