001    // Copyright May 4, 2006 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    package org.apache.tapestry.dojo.form;
015    
016    import java.util.ArrayList;
017    import java.util.Collection;
018    import java.util.HashMap;
019    import java.util.Iterator;
020    import java.util.List;
021    import java.util.Map;
022    
023    import org.apache.tapestry.IDirect;
024    import org.apache.tapestry.IJSONRender;
025    import org.apache.tapestry.IMarkupWriter;
026    import org.apache.tapestry.IRequestCycle;
027    import org.apache.tapestry.IScript;
028    import org.apache.tapestry.PageRenderSupport;
029    import org.apache.tapestry.Tapestry;
030    import org.apache.tapestry.TapestryUtils;
031    import org.apache.tapestry.engine.DirectServiceParameter;
032    import org.apache.tapestry.engine.IEngineService;
033    import org.apache.tapestry.engine.ILink;
034    import org.apache.tapestry.form.ValidatableField;
035    import org.apache.tapestry.form.ValidatableFieldSupport;
036    import org.apache.tapestry.json.IJSONWriter;
037    import org.apache.tapestry.json.JSONObject;
038    import org.apache.tapestry.services.DataSqueezer;
039    import org.apache.tapestry.valid.ValidatorException;
040    
041    /**
042     * An html field similar to a <code>select</code> input field that 
043     * is wrapped by a dojo ComboBox widget.
044     * 
045     * This component uses the {@link IAutocompleteModel} to retrieve and match against
046     * selected values.
047     * 
048     * @author jkuhnert
049     */
050    public abstract class Autocompleter extends AbstractFormWidget 
051        implements ValidatableField, IJSONRender, IDirect
052    {
053        // mode, can be remote or local (local being from html rendered option elements)
054        private static final String MODE_REMOTE = "remote";
055        
056        /**
057         * 
058         * {@inheritDoc}
059         */
060        protected void renderFormWidget(IMarkupWriter writer, IRequestCycle cycle)
061        {
062            renderDelegatePrefix(writer, cycle);
063            
064            writer.begin("select");
065            writer.attribute("name", getName());
066            
067            if (isDisabled())
068                writer.attribute("disabled", "disabled");
069            
070            renderIdAttribute(writer, cycle);
071            
072            renderDelegateAttributes(writer, cycle);
073            
074            getValidatableFieldSupport().renderContributions(this, writer, cycle);
075            
076            // Apply informal attributes.
077            renderInformalParameters(writer, cycle);
078            
079            writer.end();
080            renderDelegateSuffix(writer, cycle);
081            
082            ILink link = getDirectService().getLink(true, new DirectServiceParameter(this));
083            
084            Map parms = new HashMap();
085            parms.put("id", getClientId());
086            
087            JSONObject json = new JSONObject();
088            json.put("dataUrl", link.getURL() + "&filter=%{searchString}");
089            json.put("mode", MODE_REMOTE);
090            json.put("widgetId", getName());
091            json.put("name", getName());
092            json.put("searchDelay", getSearchDelay());
093            json.put("fadeTime", getFadeTime());
094            
095            IAutocompleteModel model = getModel();
096            if (model == null)
097                throw Tapestry.createRequiredParameterException(this, "model");
098            
099            Object value = getValue();
100            Object key = value != null ? model.getPrimaryKey(value) : null;
101            
102            if (value != null && key != null) {
103                
104                json.put("value", getDataSqueezer().squeeze(key));
105                json.put("label", model.getLabelFor(value));
106            }
107            
108            parms.put("props", json.toString());
109            parms.put("form", getForm().getName());
110            
111            PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, this);
112            getScript().execute(this, cycle, prs, parms);
113        }
114        
115        /**
116         * {@inheritDoc}
117         */
118        public void renderComponent(IJSONWriter writer, IRequestCycle cycle)
119        {
120            IAutocompleteModel model = getModel();
121            
122            if (model == null)
123                throw Tapestry.createRequiredParameterException(this, "model");
124            
125            Map filteredValues = model.filterValues(getFilter());
126            
127            if (filteredValues == null)
128                return;
129            
130            Iterator it = filteredValues.keySet().iterator();
131            Object key = null;
132            
133            JSONObject json = writer.object();
134            
135            while (it.hasNext()) {
136                
137                key = it.next();
138                
139                json.put(getDataSqueezer().squeeze(key), filteredValues.get(key));
140            }
141            
142        }
143        
144        /**
145         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
146         */
147        protected void rewindFormWidget(IMarkupWriter writer, IRequestCycle cycle)
148        {
149            String value = cycle.getParameter(getName());
150            
151            Object object = null;
152            
153            try
154            {
155                if (value != null && value.length() > 0)
156                    object = getModel().getValue(getDataSqueezer().unsqueeze(value));
157                
158                getValidatableFieldSupport().validate(this, writer, cycle, object);
159                
160                setValue(object);
161            }
162            catch (ValidatorException e)
163            {
164                getForm().getDelegate().record(e);
165            }
166        }
167        
168        /** 
169         * {@inheritDoc}
170         */
171        public boolean isStateful()
172        {
173            return true;
174        }
175        
176        /**
177         * Triggerd by using filterOnChange logic.
178         * 
179         * {@inheritDoc}
180         */
181        public void trigger(IRequestCycle cycle)
182        {
183            setFilter(cycle.getParameter("filter"));
184        }
185        
186        public abstract IAutocompleteModel getModel();
187        
188        /** @since 4.1 */
189        public abstract boolean isFilterOnChange();
190        
191        /** whether or not to autocomplete the input text. */
192        public abstract boolean isAutocomplete();
193        
194        /** How long to wait(in ms) before searching after input is received. */
195        public abstract int getSearchDelay();
196        
197        /** The duration(in ms) of the fade effect of list going away. */
198        public abstract int getFadeTime();
199        
200        /** The maximum number of items displayed in select list before the scrollbar is activated. */
201        public abstract int getMaxListLength();
202        
203        /** @since 2.2 * */
204        public abstract Object getValue();
205    
206        /** @since 2.2 * */
207        public abstract void setValue(Object value);
208        
209        /** @since 4.1 */
210        public abstract void setFilter(String value);
211        
212        /** @since 4.1 */
213        public abstract String getFilter();
214        
215        /** Injected. */
216        public abstract DataSqueezer getDataSqueezer();
217        
218        /**
219         * Injected.
220         */
221        public abstract ValidatableFieldSupport getValidatableFieldSupport();
222    
223        /**
224         * Injected.
225         * @return
226         */
227        public abstract IEngineService getDirectService();
228        
229        /**
230         * Injected.
231         * @return
232         */
233        public abstract IScript getScript();
234        
235        /**
236         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
237         */
238        public boolean isRequired()
239        {
240            return getValidatableFieldSupport().isRequired(this);
241        }
242    
243        /** 
244         * {@inheritDoc}
245         */
246        public Collection getUpdateComponents()
247        {
248            List comps = new ArrayList();
249            comps.add(getId());
250            
251            return comps;
252        }
253        
254        /** 
255         * {@inheritDoc}
256         */
257        public boolean isAsync()
258        {
259            return true;
260        }
261        
262        /** 
263         * {@inheritDoc}
264         */
265        public boolean isJson()
266        {
267            return true;
268        }
269    }