001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.corelib.components;
014
015import org.apache.tapestry5.*;
016import org.apache.tapestry5.annotations.Parameter;
017import org.apache.tapestry5.annotations.Property;
018import org.apache.tapestry5.commons.util.CollectionFactory;
019import org.apache.tapestry5.corelib.base.AbstractField;
020import org.apache.tapestry5.dom.Element;
021import org.apache.tapestry5.http.services.Request;
022import org.apache.tapestry5.ioc.annotations.Inject;
023
024import java.util.Collections;
025import java.util.List;
026import java.util.Set;
027
028/**
029 * A list of checkboxes, allowing selection of multiple items in a list.
030 *
031 * For an alternative component that can be used for similar purposes, see
032 * {@link Palette}.
033 *
034 * @tapestrydoc
035 * @see Form
036 * @see Palette
037 * @since 5.3
038 */
039public class Checklist extends AbstractField
040{
041
042    /**
043     * Model used to define the values and labels used when rendering the
044     * checklist.
045     */
046    @Parameter(required = true)
047    private SelectModel model;
048
049    /**
050     * The list of selected values from the
051     * {@link org.apache.tapestry5.SelectModel}. This will be updated when the
052     * form is submitted. If the value for the parameter is null, a new list
053     * will be created, otherwise the existing list will be cleared. If unbound,
054     * defaults to a property of the container matching this component's id.
055     */
056    @Parameter(required = true, autoconnect = true)
057    private List<Object> selected;
058
059    /**
060     * A ValueEncoder used to convert server-side objects (provided from the
061     * "source" parameter) into unique client-side strings (typically IDs) and
062     * back. Note: this component does NOT support ValueEncoders configured to
063     * be provided automatically by Tapestry.
064     */
065    @Parameter(required = true, allowNull = false)
066    private ValueEncoder<Object> encoder;
067
068    /**
069     * The object that will perform input validation. The validate binding prefix is generally used to provide
070     * this object in a declarative fashion.
071     */
072    @Parameter(defaultPrefix = BindingConstants.VALIDATE)
073    @SuppressWarnings("unchecked")
074    private FieldValidator<Object> validate;
075
076    @Inject
077    private Request request;
078
079    @Inject
080    private FieldValidationSupport fieldValidationSupport;
081
082    @Property
083    private List<Renderable> availableOptions;
084
085    private final class RenderCheckbox implements Renderable
086    {
087        private final OptionModel model;
088
089        private RenderCheckbox(final OptionModel model)
090        {
091            this.model = model;
092        }
093
094        public void render(MarkupWriter writer)
095        {
096            final String clientValue = encoder.toClient(model.getValue());
097
098            writer.element("label");
099
100            final Element checkbox = writer.element("input",
101                    "type", "checkbox",
102                    "name", getControlName(),
103                    "value", clientValue);
104
105            if (getSelected().contains(model.getValue()))
106            {
107                checkbox.attribute("checked", "checked");
108            }
109
110            if (isDisabled()) {
111                writer.attributes("disabled", "disabled");
112            }
113
114            writer.write(model.getLabel());
115            writer.end();
116
117            writer.end();
118
119        }
120    }
121
122    void setupRender()
123    {
124        availableOptions = CollectionFactory.newList();
125
126        final SelectModelVisitor visitor = new SelectModelVisitor()
127        {
128            public void beginOptionGroup(final OptionGroupModel groupModel)
129            {
130            }
131
132            public void option(final OptionModel optionModel)
133            {
134                availableOptions.add(new RenderCheckbox(optionModel));
135            }
136
137            public void endOptionGroup(final OptionGroupModel groupModel)
138            {
139            }
140
141        };
142
143        model.visit(visitor);
144    }
145
146    @Override
147    protected void processSubmission(final String controlName)
148    {
149
150        final String[] parameters = request.getParameters(controlName);
151
152        List<Object> selected = this.selected;
153
154        if (selected == null)
155        {
156            selected = CollectionFactory.newList();
157        } else
158        {
159            selected.clear();
160        }
161
162        if (parameters != null)
163        {
164            for (final String value : parameters)
165            {
166                final Object objectValue = encoder.toValue(value);
167
168                selected.add(objectValue);
169            }
170
171        }
172
173        putPropertyNameIntoBeanValidationContext("selected");
174
175        try
176        {
177            fieldValidationSupport.validate(selected, this.resources, this.validate);
178
179            this.selected = selected;
180        } catch (final ValidationException e)
181        {
182            validationTracker.recordError(this, e.getMessage());
183        }
184
185        removePropertyNameFromBeanValidationContext();
186    }
187
188    Set<Object> getSelected()
189    {
190        if (selected == null)
191        {
192            return Collections.emptySet();
193        }
194
195        return CollectionFactory.newSet(selected);
196    }
197
198    /**
199     * Computes a default value for the "validate" parameter using
200     * {@link org.apache.tapestry5.services.FieldValidatorDefaultSource}.
201     */
202
203    Binding defaultValidate()
204    {
205        return this.defaultProvider.defaultValidatorBinding("selected", resources);
206    }
207
208    @Override
209    public boolean isRequired()
210    {
211        return validate.isRequired();
212    }
213
214    void beginRender(MarkupWriter writer) {
215        writer.element("div", "id", getClientId());
216    }
217
218    void afterRender(MarkupWriter writer) {
219        writer.end();
220    }
221}
222