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 }