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.HashMap;
017    import java.util.Map;
018    
019    import org.apache.tapestry.IDirect;
020    import org.apache.tapestry.IJSONRender;
021    import org.apache.tapestry.IMarkupWriter;
022    import org.apache.tapestry.IRequestCycle;
023    import org.apache.tapestry.IScript;
024    import org.apache.tapestry.PageRenderSupport;
025    import org.apache.tapestry.Tapestry;
026    import org.apache.tapestry.TapestryUtils;
027    import org.apache.tapestry.engine.DirectServiceParameter;
028    import org.apache.tapestry.engine.IEngineService;
029    import org.apache.tapestry.engine.ILink;
030    import org.apache.tapestry.form.IPropertySelectionModel;
031    import org.apache.tapestry.form.ValidatableField;
032    import org.apache.tapestry.form.ValidatableFieldSupport;
033    import org.apache.tapestry.json.IJSONWriter;
034    import org.apache.tapestry.valid.ValidatorException;
035    
036    /**
037     * An html field similar to a <code>select</code> input field that 
038     * is wrapped by a dojo ComboBox widget.
039     * 
040     * @author jkuhnert
041     */
042    public abstract class Autocompleter extends AbstractFormWidget 
043        implements ValidatableField, IJSONRender, IDirect
044    {
045        
046        /**
047         * 
048         * {@inheritDoc}
049         */
050        protected void renderFormWidget(IMarkupWriter writer, IRequestCycle cycle)
051        {
052            renderDelegatePrefix(writer, cycle);
053            
054            writer.begin("select");
055            writer.attribute("name", getName());
056            
057            if (isDisabled())
058                writer.attribute("disabled", "disabled");
059            
060            if (getSubmitOnChange())
061                writer.attribute("onchange", "javascript:   this.form.events.submit();");
062            
063            renderIdAttribute(writer, cycle);
064            
065            renderDelegateAttributes(writer, cycle);
066            
067            getValidatableFieldSupport().renderContributions(this, writer, cycle);
068            
069            // Apply informal attributes.
070            renderInformalParameters(writer, cycle);
071            
072            writer.end();
073            renderDelegateSuffix(writer, cycle);
074            
075            DirectServiceParameter dsp = 
076                new DirectServiceParameter(this, new Object[]{}, 
077                        new String[]{getId()}, true);
078            ILink link = getDirectService().getLink(true, dsp);
079            
080            Map parms = new HashMap();
081            parms.put("id", getClientId());
082            
083            StringBuffer str = new StringBuffer("{");
084            str.append("dataUrl:'").append(link.getURL()).append("&filter=%{searchString}',")
085            .append("mode:'remote',")
086            .append("widgetId:'").append(getName()).append("', ")
087            .append("name:'").append(getName()).append("'");
088            
089            IPropertySelectionModel model = getModel();
090            if (model == null)
091                throw Tapestry.createRequiredParameterException(this, "model");
092            
093            int count = model.getOptionCount();
094            Object value = getValue();
095            
096            for (int i = 0; i < count; i++) {
097                Object option = model.getOption(i);
098                
099                if (isEqual(option, value)) {
100                    str.append(", comboBoxValue:'").append(model.getValue(i)).append("',")
101                    .append("comboBoxSelectionValue:'").append(model.getLabel(i))
102                    .append("'");
103                    break;
104                }
105            }
106            
107            str.append("}");
108            
109            parms.put("props", str.toString());
110            
111            PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, this);
112            getScript().execute(cycle, prs, parms);
113        }
114        
115        /**
116         * {@inheritDoc}
117         */
118        public void renderComponent(IJSONWriter writer, IRequestCycle cycle)
119        {
120            IPropertySelectionModel model = getModel();
121            
122            if (model == null)
123                throw Tapestry.createRequiredParameterException(this, "model");
124            
125            int count = model.getOptionCount();
126            
127            for (int i = 0; i < count; i++)
128            {
129                String value = model.getValue(i);
130                String label = model.getLabel(i);
131                
132                if (getFilter() == null || getFilter().trim().length() <= 0) {
133                    writer.put(value, label);
134                    continue;
135                }
136                
137                // primitive filter, for now
138                // TODO: Create filter interface in IPropertySelectionModel
139                if (getFilter() != null 
140                        && label.toLowerCase().indexOf(getFilter().toLowerCase()) > -1) {
141                    writer.put(value, label);
142                }
143            }
144        }
145        
146        /**
147         * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
148         */
149        protected void rewindFormWidget(IMarkupWriter writer, IRequestCycle cycle)
150        {
151            String value = cycle.getParameter(getName() + "_selected");
152            
153            Object object = getModel().translateValue(value);
154            
155            try
156            {
157                getValidatableFieldSupport().validate(this, writer, cycle, object);
158                
159                setValue(object);
160            }
161            catch (ValidatorException e)
162            {
163                getForm().getDelegate().record(e);
164            }
165        }
166        
167        private boolean isEqual(Object left, Object right)
168        {
169            // Both null, or same object, then are equal
170            
171            if (left == right)
172                return true;
173            
174            // If one is null, the other isn't, then not equal.
175            
176            if (left == null || right == null)
177                return false;
178            
179            // Both non-null; use standard comparison.
180            
181            return left.equals(right);
182        }
183        
184        /** 
185         * {@inheritDoc}
186         */
187        public boolean isStateful()
188        {
189            return false;
190        }
191        
192        /**
193         * Triggerd by using filterOnChange logic.
194         * 
195         * {@inheritDoc}
196         */
197        public void trigger(IRequestCycle cycle)
198        {
199            setFilter(cycle.getParameter("filter"));
200        }
201        
202        public abstract IPropertySelectionModel getModel();
203        
204        /** @since 4.1 */
205        public abstract boolean isFilterOnChange();
206        
207        /** @since 2.2 * */
208        public abstract boolean getSubmitOnChange();
209    
210        /** @since 2.2 * */
211        public abstract Object getValue();
212    
213        /** @since 2.2 * */
214        public abstract void setValue(Object value);
215        
216        /** @since 4.1 */
217        public abstract void setFilter(String value);
218        
219        /** @since 4.1 */
220        public abstract String getFilter();
221        
222        /**
223         * Injected.
224         */
225        public abstract ValidatableFieldSupport getValidatableFieldSupport();
226    
227        /**
228         * Injected.
229         * @return
230         */
231        public abstract IEngineService getDirectService();
232        
233        /**
234         * Injected.
235         * @return
236         */
237        public abstract IScript getScript();
238        
239        /**
240         * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
241         */
242        public boolean isRequired()
243        {
244            return getValidatableFieldSupport().isRequired(this);
245        }
246    }