001    /*
002     * Apache License
003     * Version 2.0, January 2004
004     * http://www.apache.org/licenses/
005     *
006     * Copyright 2008-2010 by chenillekit.org
007     *
008     * Licensed under the Apache License, Version 2.0 (the "License");
009     * you may not use this file except in compliance with the License.
010     * You may obtain a copy of the License at
011     *
012     * http://www.apache.org/licenses/LICENSE-2.0
013     */
014    
015    package org.chenillekit.tapestry.core.components;
016    
017    import java.util.List;
018    import java.util.Locale;
019    
020    import org.apache.tapestry5.Binding;
021    import org.apache.tapestry5.BindingConstants;
022    import org.apache.tapestry5.ComponentResources;
023    import org.apache.tapestry5.FieldValidator;
024    import org.apache.tapestry5.MarkupWriter;
025    import org.apache.tapestry5.OptionModel;
026    import org.apache.tapestry5.SelectModel;
027    import org.apache.tapestry5.SelectModelVisitor;
028    import org.apache.tapestry5.ValidationException;
029    import org.apache.tapestry5.ValidationTracker;
030    import org.apache.tapestry5.ValueEncoder;
031    import org.apache.tapestry5.annotations.BeforeRenderTemplate;
032    import org.apache.tapestry5.annotations.Environmental;
033    import org.apache.tapestry5.annotations.Parameter;
034    import org.apache.tapestry5.corelib.base.AbstractField;
035    import org.apache.tapestry5.dom.Element;
036    import org.apache.tapestry5.ioc.annotations.Inject;
037    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
038    import org.apache.tapestry5.ioc.internal.util.TapestryException;
039    import org.apache.tapestry5.services.ComponentDefaultProvider;
040    import org.apache.tapestry5.services.Request;
041    import org.apache.tapestry5.util.EnumSelectModel;
042    
043    import org.chenillekit.tapestry.core.encoders.MultipleValueEncoder;
044    import org.chenillekit.tapestry.core.renderes.MultipleSelectModelRenderer;
045    
046    
047    /**
048     * Select a list of items from a list of values, using an [X]HTML multiple select element on the client side.
049     * <p/>
050     * the only diffrence to the original tapestry select component is that the "value" parameter expected a java.util.List object.
051     *
052     * @version $Id: MultipleSelect.java 729 2010-11-03 19:51:08Z homburgs $
053     * @link http://tapestry.apache.org/t5components/tapestry-core/component-parameters.html#orgapachetapestrycorelibcomponentsselect
054     */
055    public class MultipleSelect extends AbstractField
056    {
057            private class Renderer extends MultipleSelectModelRenderer
058            {
059                    public Renderer(MarkupWriter writer)
060                    {
061                            super(writer, encoder);
062                    }
063    
064                    @Override
065                    protected boolean isOptionSelected(OptionModel optionModel)
066                    {
067                            boolean hit = false;
068                            Object testValue = optionModel.getValue();
069    
070                            if (value != null)
071                            {
072                                    for (Object singleValue : value)
073                                    {
074                                            hit = testValue == singleValue || (testValue != null && testValue.equals(singleValue));
075                                            if (hit)
076                                                    break;
077                                    }
078                            }
079    
080                            return hit;
081                    }
082            }
083    
084            /**
085             * Allows a specific implementation of org.apache.tapestry5.ValueEncoder to be supplied.
086             * This is used to create client-side string values for the different options.
087             */
088            @Parameter
089            private MultipleValueEncoder encoder;
090    
091            /**
092             * The model used to identify the option groups and options to be presented to the user.
093             * This can be generated automatically for Enum types.
094             */
095            @Parameter(required = true)
096            private SelectModel model;
097    
098            /**
099             * should the component multi select able.
100             */
101            @Parameter(defaultPrefix = BindingConstants.PROP, value = "true")
102            @SuppressWarnings("unchecked")
103            private boolean multiple;
104    
105            @Parameter(defaultPrefix = BindingConstants.VALIDATE)
106            @SuppressWarnings("unchecked")
107            private FieldValidator<Object> validate;
108    
109            /**
110             * The list of value to read or update.
111             */
112            @Parameter(required = true, principal = true)
113            private List value;
114    
115            @Inject
116            private Locale locale;
117    
118            @Inject
119            private Request request;
120    
121            @Inject
122            private ComponentResources resources;
123    
124            @Environmental
125            private ValidationTracker tracker;
126    
127            @Inject
128            private ComponentDefaultProvider defaultProvider;
129    
130            @Override
131            protected void processSubmission(String elementName)
132            {
133                    String[] primaryKeys = request.getParameters(elementName);
134                    List selectedValues = primaryKeys != null ? encoder.toValue(primaryKeys) : CollectionFactory.newList();
135    
136                    try
137                    {
138                            for (Object selectedValue : selectedValues)
139                                    validate.validate(selectedValue);
140    
141                            if (validate.isRequired() && selectedValues.size() == 0)
142                                    throw new ValidationException("at least one selection is required");
143    
144                            value = selectedValues;
145                    }
146                    catch (ValidationException ex)
147                    {
148                            tracker.recordError(this, ex.getMessage());
149                    }
150            }
151    
152            void afterRender(MarkupWriter writer)
153            {
154                    writer.end();
155            }
156    
157            void beginRender(MarkupWriter writer)
158            {
159                    Element element = writer.element("select", "name", getControlName(), "id", getClientId());
160    
161                    if (multiple)
162                            element.attribute("multiple", "multiple");
163    
164                    resources.renderInformalParameters(writer);
165            }
166    
167            @SuppressWarnings("unchecked")
168            ValueEncoder defaultEncoder()
169            {
170                    return defaultProvider.defaultValueEncoder("value", resources);
171            }
172    
173            @SuppressWarnings("unchecked")
174            SelectModel defaultModel()
175            {
176                    Class valueType = resources.getBoundType("value");
177    
178                    if (valueType == null) return null;
179    
180                    if (Enum.class.isAssignableFrom(valueType))
181                            return new EnumSelectModel(valueType, resources.getContainerMessages());
182    
183                    return null;
184            }
185    
186            Binding defaultValidate()
187            {
188                    return defaultProvider.defaultValidatorBinding("value", resources);
189            }
190    
191            Binding defaultValue()
192            {
193                    return defaultProvider.defaultBinding("value", resources);
194            }
195    
196            @BeforeRenderTemplate
197            void options(MarkupWriter writer)
198            {
199                    SelectModelVisitor renderer = new Renderer(writer);
200                    if (model == null)
201                            throw new TapestryException("select model cannot be null", this, null);
202    
203                    model.visit(renderer);
204            }
205    
206            /**
207             * only for testing.
208             *
209             * @param model
210             */
211            public void setModel(SelectModel model)
212            {
213                    this.model = model;
214            }
215    
216            /**
217             * only for testing.
218             *
219             * @param value
220             */
221            public void setValue(List value)
222            {
223                    this.value = value;
224            }
225    
226            /**
227             * only for testing.
228             *
229             * @param encoder
230             */
231            public void setValueEncoder(MultipleValueEncoder encoder)
232            {
233                    this.encoder = encoder;
234            }
235    }