001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.extensions.ajax.markup.html; 018 019import java.util.List; 020 021import org.apache.wicket.Component; 022import org.apache.wicket.MarkupContainer; 023import org.apache.wicket.ajax.attributes.AjaxCallListener; 024import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 025import org.apache.wicket.markup.ComponentTag; 026import org.apache.wicket.markup.MarkupStream; 027import org.apache.wicket.markup.html.WebComponent; 028import org.apache.wicket.markup.html.basic.Label; 029import org.apache.wicket.markup.html.form.DropDownChoice; 030import org.apache.wicket.markup.html.form.FormComponent; 031import org.apache.wicket.markup.html.form.IChoiceRenderer; 032import org.apache.wicket.markup.html.form.TextField; 033import org.apache.wicket.model.IModel; 034import org.apache.wicket.model.Model; 035import org.apache.wicket.util.convert.IConverter; 036import org.apache.wicket.util.string.Strings; 037 038 039/** 040 * An inplace editor much like {@link AjaxEditableLabel}, but instead of a {@link TextField} a 041 * {@link DropDownChoice} is displayed. 042 * 043 * @param <T> 044 * @author Eelco Hillenius 045 */ 046public class AjaxEditableChoiceLabel<T> extends AjaxEditableLabel<T> 047{ 048 private static final long serialVersionUID = 1L; 049 050 /** The list of objects. */ 051 private IModel<? extends List<? extends T>> choices; 052 053 /** The renderer used to generate display/id values for the objects. */ 054 private IChoiceRenderer<? super T> renderer; 055 056 /** 057 * Construct. 058 * 059 * @param id 060 * The component id 061 */ 062 public AjaxEditableChoiceLabel(final String id) 063 { 064 super(id); 065 } 066 067 /** 068 * Construct. 069 * 070 * @param id 071 * The component id 072 * @param model 073 * The model 074 */ 075 public AjaxEditableChoiceLabel(final String id, final IModel<T> model) 076 { 077 super(id, model); 078 } 079 080 /** 081 * Construct. 082 * 083 * @param id 084 * The component id 085 * @param choices 086 * The collection of choices in the dropdown 087 */ 088 public AjaxEditableChoiceLabel(final String id, final List<? extends T> choices) 089 { 090 this(id, null, choices); 091 } 092 093 /** 094 * Construct. 095 * 096 * @param id 097 * The component id 098 * @param model 099 * The model 100 * @param choices 101 * The collection of choices in the dropdown 102 */ 103 public AjaxEditableChoiceLabel(final String id, final IModel<T> model, 104 final IModel<? extends List<? extends T>> choices) 105 { 106 super(id, model); 107 this.choices = choices; 108 } 109 110 /** 111 * Construct. 112 * 113 * @param id 114 * The component id 115 * @param model 116 * The model 117 * @param choices 118 * The collection of choices in the dropdown 119 * @param renderer 120 * The rendering engine 121 */ 122 public AjaxEditableChoiceLabel(final String id, final IModel<T> model, 123 final IModel<? extends List<? extends T>> choices, final IChoiceRenderer<? super T> renderer) 124 { 125 super(id, model); 126 this.choices = choices; 127 this.renderer = renderer; 128 } 129 130 /** 131 * Construct. 132 * 133 * @param id 134 * The component id 135 * @param model 136 * The model 137 * @param choices 138 * The collection of choices in the dropdown 139 */ 140 public AjaxEditableChoiceLabel(final String id, final IModel<T> model, 141 final List<? extends T> choices) 142 { 143 this(id, model, Model.ofList(choices)); 144 } 145 146 /** 147 * Construct. 148 * 149 * @param id 150 * The component id 151 * @param model 152 * The model 153 * @param choices 154 * The collection of choices in the dropdown 155 * @param renderer 156 * The rendering engine 157 */ 158 public AjaxEditableChoiceLabel(final String id, final IModel<T> model, 159 final List<? extends T> choices, final IChoiceRenderer<? super T> renderer) 160 { 161 this(id, model, Model.ofList(choices), renderer); 162 } 163 164 /** 165 * {@inheritDoc} 166 */ 167 @Override 168 protected FormComponent<T> newEditor(final MarkupContainer parent, final String componentId, 169 final IModel<T> model) 170 { 171 IModel<List<? extends T>> choiceModel = new IModel<List<? extends T>>() 172 { 173 private static final long serialVersionUID = 1L; 174 175 @Override 176 public List<? extends T> getObject() 177 { 178 return choices.getObject(); 179 } 180 }; 181 182 DropDownChoice<T> editor = new DropDownChoice<T>(componentId, model, choiceModel, renderer) 183 { 184 private static final long serialVersionUID = 1L; 185 186 @Override 187 protected void onModelChanged() 188 { 189 AjaxEditableChoiceLabel.this.onModelChanged(); 190 } 191 192 @Override 193 protected void onModelChanging() 194 { 195 AjaxEditableChoiceLabel.this.onModelChanging(); 196 } 197 198 @Override 199 protected void onConfigure() 200 { 201 super.onConfigure(); 202 setEscapeModelStrings(AjaxEditableChoiceLabel.this.getEscapeModelStrings()); 203 } 204 205 }; 206 207 editor.setOutputMarkupId(true); 208 editor.setVisible(false); 209 editor.add(new EditorAjaxBehavior() 210 { 211 private static final long serialVersionUID = 1L; 212 213 @Override 214 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 215 { 216 super.updateAjaxAttributes(attributes); 217 attributes.setEventNames("change", "blur", "keyup"); 218 219 CharSequence dynamicExtraParameters = "var result = [], " 220 + "kc=Wicket.Event.keyCode(attrs.event)," 221 + "evtType=attrs.event.type;" 222 + "if (evtType === 'blur' || (evtType === 'keyup' && kc===27)) {" 223 + " result.push( { name: 'save', value: false } );" 224 + "}" 225 + "else {" 226 + " result = Wicket.Form.serializeElement(attrs.c);" 227 + " result.push( { name: 'save', value: true } );" 228 + "}" 229 + "return result;"; 230 attributes.getDynamicExtraParameters().add(dynamicExtraParameters); 231 232 CharSequence precondition = "var kc=Wicket.Event.keyCode(attrs.event)," 233 + "evtType=attrs.event.type," 234 + "ret=false;" 235 + "if(evtType==='blur' || evtType==='change' || (evtType==='keyup' && kc===27)) ret = true;" 236 + "return ret;"; 237 AjaxCallListener ajaxCallListener = new AjaxCallListener(); 238 ajaxCallListener.onPrecondition(precondition); 239 attributes.getAjaxCallListeners().add(ajaxCallListener); 240 } 241 }); 242 return editor; 243 } 244 245 /** 246 * {@inheritDoc} 247 */ 248 @Override 249 protected Component newLabel(final MarkupContainer parent, final String componentId, 250 final IModel<T> model) 251 { 252 Label label = new Label(componentId, model) 253 { 254 private static final long serialVersionUID = 1L; 255 256 /** 257 * {@inheritDoc} 258 */ 259 @Override 260 public <C> IConverter<C> getConverter(final Class<C> type) 261 { 262 return AjaxEditableChoiceLabel.this.getConverter(type); 263 } 264 265 @Override 266 protected void onConfigure() 267 { 268 super.onConfigure(); 269 setEscapeModelStrings(AjaxEditableChoiceLabel.this.getEscapeModelStrings()); 270 } 271 272 /** 273 * {@inheritDoc} 274 */ 275 @SuppressWarnings("unchecked") 276 @Override 277 public void onComponentTagBody(final MarkupStream markupStream, 278 final ComponentTag openTag) 279 { 280 String displayValue = getDefaultModelObjectAsString(); 281 if (renderer != null) 282 { 283 Object displayObject = renderer.getDisplayValue(getModelObject()); 284 Class<?> objectClass = (displayObject == null ? null : displayObject.getClass()); 285 286 String rendered = null; 287 if ((objectClass != null) && (objectClass != String.class)) 288 { 289 @SuppressWarnings("rawtypes") 290 final IConverter converter = getConverter(objectClass); 291 rendered = converter.convertToString(displayObject, getLocale()); 292 } 293 else if (displayObject != null) 294 { 295 rendered = displayObject.toString(); 296 } 297 298 if (rendered != null) 299 { 300 displayValue = getEscapeModelStrings() 301 ? Strings.escapeMarkup(rendered).toString() : rendered; 302 } 303 } 304 305 if (Strings.isEmpty(displayValue)) 306 { 307 String nullLabel = defaultNullLabel(); 308 replaceComponentTagBody(markupStream, openTag, 309 getEscapeModelStrings() ? Strings.escapeMarkup(nullLabel) : nullLabel); 310 } 311 else 312 { 313 replaceComponentTagBody(markupStream, openTag, displayValue); 314 } 315 } 316 }; 317 label.setOutputMarkupId(true); 318 label.add(new LabelAjaxBehavior(getLabelAjaxEvent())); 319 return label; 320 } 321 322 /** 323 * {@inheritDoc} 324 */ 325 @Override 326 protected void onModelChanged() 327 { 328 super.onModelChanged(); 329 } 330 331 /** 332 * {@inheritDoc} 333 */ 334 @Override 335 protected void onModelChanging() 336 { 337 super.onModelChanging(); 338 } 339 340 /** 341 * {@inheritDoc} 342 */ 343 @Override 344 protected void onDetach() 345 { 346 if (choices != null) 347 { 348 choices.detach(); 349 } 350 351 if (renderer != null) 352 { 353 renderer.detach(); 354 } 355 356 super.onDetach(); 357 } 358}