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 org.apache.tapestry.*;
017 import org.apache.tapestry.engine.DirectServiceParameter;
018 import org.apache.tapestry.engine.IEngineService;
019 import org.apache.tapestry.engine.ILink;
020 import org.apache.tapestry.form.ValidatableField;
021 import org.apache.tapestry.form.ValidatableFieldSupport;
022 import org.apache.tapestry.json.IJSONWriter;
023 import org.apache.tapestry.json.JSONObject;
024 import org.apache.tapestry.services.DataSqueezer;
025 import org.apache.tapestry.valid.ValidatorException;
026
027 import java.util.ArrayList;
028 import java.util.HashMap;
029 import java.util.List;
030 import java.util.Map;
031
032 /**
033 * An html field similar to a <code>select</code> input field that
034 * is wrapped by a dojo ComboBox widget.
035 *
036 * This component uses the {@link IAutocompleteModel} to retrieve and match against
037 * selected values.
038 *
039 * @author jkuhnert
040 */
041 public abstract class Autocompleter extends AbstractFormWidget implements ValidatableField, IJSONRender, IDirect
042 {
043 // mode, can be remote or local (local being from html rendered option elements)
044 private static final String MODE_REMOTE = "remote";
045 private static final String MODE_LOCAL = "local";
046
047 /**
048 *
049 * {@inheritDoc}
050 */
051 protected void renderFormWidget(IMarkupWriter writer, IRequestCycle cycle)
052 {
053 IAutocompleteModel model = getModel();
054 if (model == null)
055 throw Tapestry.createRequiredParameterException(this, "model");
056
057 Object value = getValue();
058 Object key = value != null && !"".equals(value.toString()) ? model.getPrimaryKey(value) : null;
059
060 renderDelegatePrefix(writer, cycle);
061
062 writer.begin("select");
063 writer.attribute("name", getName());
064 writer.attribute("autocomplete", "off"); // turn off native html autocomplete
065
066 if (isDisabled())
067 writer.attribute("disabled", "disabled");
068
069 renderIdAttribute(writer, cycle);
070
071 renderDelegateAttributes(writer, cycle);
072
073 getValidatableFieldSupport().renderContributions(this, writer, cycle);
074
075 // Apply informal attributes.
076 renderInformalParameters(writer, cycle);
077
078 writer.print(" ");
079
080 if (isLocal())
081 {
082 List list = model.getValues("");
083 for (int i=0; i<list.size(); i++)
084 {
085 Object optionKey = model.getPrimaryKey(list.get(i));
086
087 writer.begin("option");
088 writer.attribute("value", getDataSqueezer().squeeze(optionKey));
089
090 if (optionKey!=null && optionKey.equals(key))
091 writer.attribute("selected", "selected");
092
093 writer.print(model.getLabelFor(list.get(i)));
094 writer.end();
095 }
096 }
097
098 writer.end();
099 renderDelegateSuffix(writer, cycle);
100
101 Map parms = new HashMap();
102 parms.put("id", getClientId());
103
104 JSONObject json = new JSONObject();
105 if (!isLocal())
106 {
107 ILink link = getDirectService().getLink(true, new DirectServiceParameter(this));
108 json.put("dataUrl", link.getURL() + "&filter=%{searchString}");
109 }
110
111 json.put("mode", isLocal() ? MODE_LOCAL : MODE_REMOTE);
112 json.put("widgetId", getName());
113 json.put("name", getName());
114 json.put("searchDelay", getSearchDelay());
115 json.put("fadeTime", getFadeTime());
116 json.put("maxListLength", getMaxListLength());
117 json.put("forceValidOption", isForceValidOption());
118 json.put("disabled", isDisabled());
119 json.put("autoComplete", getAutoCompleteField());
120
121 json.put("value", key != null ? getDataSqueezer().squeeze(key) : "");
122 json.put("label", value != null ? model.getLabelFor(value) : "");
123
124 parms.put("props", json.toString());
125 parms.put("form", getForm().getName());
126 parms.put("widget", this);
127
128 PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, this);
129 getScript().execute(this, cycle, prs, parms);
130 }
131
132 /**
133 * {@inheritDoc}
134 */
135 public void renderComponent(IJSONWriter writer, IRequestCycle cycle)
136 {
137 IAutocompleteModel model = getModel();
138
139 if (model == null)
140 throw Tapestry.createRequiredParameterException(this, "model");
141
142 List filteredValues = model.getValues(getFilter());
143
144 if (filteredValues == null)
145 return;
146
147 Object key = null;
148 String label = null;
149
150 JSONObject json = writer.object();
151
152 for (int i=0; i < filteredValues.size(); i++) {
153 Object value = filteredValues.get(i);
154
155 key = model.getPrimaryKey(value);
156 label = model.getLabelFor(value);
157
158 json.put(getDataSqueezer().squeeze(key), label );
159 }
160
161 }
162
163 /**
164 * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle)
165 */
166 protected void rewindFormWidget(IMarkupWriter writer, IRequestCycle cycle)
167 {
168 String value = cycle.getParameter(getName());
169
170 Object object = null;
171
172 try
173 {
174 if (value != null && value.length() > 0)
175 object = getModel().getValue(getDataSqueezer().unsqueeze(value));
176
177 getValidatableFieldSupport().validate(this, writer, cycle, object);
178
179 setValue(object);
180 }
181 catch (ValidatorException e)
182 {
183 getForm().getDelegate().record(e);
184 }
185 }
186
187 /**
188 * {@inheritDoc}
189 */
190 public boolean isStateful()
191 {
192 return true;
193 }
194
195 /**
196 * Triggerd by using filterOnChange logic.
197 *
198 * {@inheritDoc}
199 */
200 public void trigger(IRequestCycle cycle)
201 {
202 setFilter(cycle.getParameter("filter"));
203 }
204
205 public abstract IAutocompleteModel getModel();
206
207 /** How long to wait(in ms) before searching after input is received. */
208 public abstract int getSearchDelay();
209
210 /** The duration(in ms) of the fade effect of list going away. */
211 public abstract int getFadeTime();
212
213 /** The maximum number of items displayed in select list before the scrollbar is activated. */
214 public abstract int getMaxListLength();
215
216 /** Forces select to only allow valid option strings. */
217 public abstract boolean isForceValidOption();
218
219 /** Forces select to work in local mode (no xhr). */
220 public abstract boolean isLocal();
221
222 /** @since 2.2 * */
223 public abstract Object getValue();
224
225 /** @since 2.2 * */
226 public abstract void setValue(Object value);
227
228 /** @since 4.1 */
229 public abstract void setFilter(String value);
230
231 /** @since 4.1 */
232 public abstract String getFilter();
233
234 /** @since 4.1.4 */
235 public abstract boolean getAutoCompleteField();
236
237 /** Injected. */
238 public abstract DataSqueezer getDataSqueezer();
239
240 /**
241 * Injected.
242 */
243 public abstract ValidatableFieldSupport getValidatableFieldSupport();
244
245 /**
246 * Injected.
247 */
248 public abstract IEngineService getDirectService();
249
250 /**
251 * Injected.
252 */
253 public abstract IScript getScript();
254
255 /**
256 * @see org.apache.tapestry.form.AbstractFormComponent#isRequired()
257 */
258 public boolean isRequired()
259 {
260 return getValidatableFieldSupport().isRequired(this);
261 }
262
263 /**
264 * {@inheritDoc}
265 */
266 public List getUpdateComponents()
267 {
268 List comps = new ArrayList();
269 comps.add(getClientId());
270
271 return comps;
272 }
273
274 /**
275 * {@inheritDoc}
276 */
277 public boolean isAsync()
278 {
279 return true;
280 }
281
282 /**
283 * {@inheritDoc}
284 */
285 public boolean isJson()
286 {
287 return true;
288 }
289 }