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 120 json.put("value", key != null ? getDataSqueezer().squeeze(key) : ""); 121 json.put("label", value != null ? model.getLabelFor(value) : ""); 122 123 parms.put("props", json.toString()); 124 parms.put("form", getForm().getName()); 125 parms.put("widget", this); 126 127 PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, this); 128 getScript().execute(this, cycle, prs, parms); 129 } 130 131 /** 132 * {@inheritDoc} 133 */ 134 public void renderComponent(IJSONWriter writer, IRequestCycle cycle) 135 { 136 IAutocompleteModel model = getModel(); 137 138 if (model == null) 139 throw Tapestry.createRequiredParameterException(this, "model"); 140 141 List filteredValues = model.getValues(getFilter()); 142 143 if (filteredValues == null) 144 return; 145 146 Object key = null; 147 String label = null; 148 149 JSONObject json = writer.object(); 150 151 for (int i=0; i < filteredValues.size(); i++) { 152 Object value = filteredValues.get(i); 153 154 key = model.getPrimaryKey(value); 155 label = model.getLabelFor(value); 156 157 json.put(getDataSqueezer().squeeze(key), label ); 158 } 159 160 } 161 162 /** 163 * @see org.apache.tapestry.form.AbstractFormComponent#rewindFormComponent(org.apache.tapestry.IMarkupWriter, org.apache.tapestry.IRequestCycle) 164 */ 165 protected void rewindFormWidget(IMarkupWriter writer, IRequestCycle cycle) 166 { 167 String value = cycle.getParameter(getName()); 168 169 Object object = null; 170 171 try 172 { 173 if (value != null && value.length() > 0) 174 object = getModel().getValue(getDataSqueezer().unsqueeze(value)); 175 176 getValidatableFieldSupport().validate(this, writer, cycle, object); 177 178 setValue(object); 179 } 180 catch (ValidatorException e) 181 { 182 getForm().getDelegate().record(e); 183 } 184 } 185 186 /** 187 * {@inheritDoc} 188 */ 189 public boolean isStateful() 190 { 191 return true; 192 } 193 194 /** 195 * Triggerd by using filterOnChange logic. 196 * 197 * {@inheritDoc} 198 */ 199 public void trigger(IRequestCycle cycle) 200 { 201 setFilter(cycle.getParameter("filter")); 202 } 203 204 public abstract IAutocompleteModel getModel(); 205 206 /** How long to wait(in ms) before searching after input is received. */ 207 public abstract int getSearchDelay(); 208 209 /** The duration(in ms) of the fade effect of list going away. */ 210 public abstract int getFadeTime(); 211 212 /** The maximum number of items displayed in select list before the scrollbar is activated. */ 213 public abstract int getMaxListLength(); 214 215 /** Forces select to only allow valid option strings. */ 216 public abstract boolean isForceValidOption(); 217 218 /** Forces select to work in local mode (no xhr). */ 219 public abstract boolean isLocal(); 220 221 /** @since 2.2 * */ 222 public abstract Object getValue(); 223 224 /** @since 2.2 * */ 225 public abstract void setValue(Object value); 226 227 /** @since 4.1 */ 228 public abstract void setFilter(String value); 229 230 /** @since 4.1 */ 231 public abstract String getFilter(); 232 233 /** Injected. */ 234 public abstract DataSqueezer getDataSqueezer(); 235 236 /** 237 * Injected. 238 */ 239 public abstract ValidatableFieldSupport getValidatableFieldSupport(); 240 241 /** 242 * Injected. 243 */ 244 public abstract IEngineService getDirectService(); 245 246 /** 247 * Injected. 248 */ 249 public abstract IScript getScript(); 250 251 /** 252 * @see org.apache.tapestry.form.AbstractFormComponent#isRequired() 253 */ 254 public boolean isRequired() 255 { 256 return getValidatableFieldSupport().isRequired(this); 257 } 258 259 /** 260 * {@inheritDoc} 261 */ 262 public List getUpdateComponents() 263 { 264 List comps = new ArrayList(); 265 comps.add(getClientId()); 266 267 return comps; 268 } 269 270 /** 271 * {@inheritDoc} 272 */ 273 public boolean isAsync() 274 { 275 return true; 276 } 277 278 /** 279 * {@inheritDoc} 280 */ 281 public boolean isJson() 282 { 283 return true; 284 } 285 }