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 }