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 org.apache.wicket.AttributeModifier; 020import org.apache.wicket.MarkupContainer; 021import org.apache.wicket.ajax.attributes.AjaxCallListener; 022import org.apache.wicket.ajax.attributes.AjaxRequestAttributes; 023import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.Method; 024import org.apache.wicket.markup.ComponentTag; 025import org.apache.wicket.markup.MarkupStream; 026import org.apache.wicket.markup.html.basic.MultiLineLabel; 027import org.apache.wicket.markup.html.form.FormComponent; 028import org.apache.wicket.markup.html.form.TextArea; 029import org.apache.wicket.model.IModel; 030import org.apache.wicket.util.convert.IConverter; 031 032/** 033 * An inplace editor much like {@link AjaxEditableLabel}, but now with support for multi line 034 * content and a {@link TextArea text area} as its editor. 035 * <p> 036 * <strong>Note</strong>: attach this component to a block HTML element (like <div>) 037 * because its label uses block elements to show the content. 038 * </p> 039 * 040 * @author eelcohillenius 041 * 042 * @param <T> 043 * Model object type 044 */ 045public class AjaxEditableMultiLineLabel<T> extends AjaxEditableLabel<T> 046{ 047 private static final long serialVersionUID = 1L; 048 049 /** text area's number of rows. */ 050 private int rows = 10; 051 052 /** text area's number of columns. */ 053 private int cols = 40; 054 055 /** 056 * Construct. 057 * 058 * @param id 059 * The component id 060 */ 061 public AjaxEditableMultiLineLabel(final String id) 062 { 063 super(id); 064 } 065 066 /** 067 * Construct. 068 * 069 * @param id 070 * The component id 071 * @param model 072 * The model 073 */ 074 public AjaxEditableMultiLineLabel(final String id, final IModel<T> model) 075 { 076 super(id, model); 077 } 078 079 /** 080 * {@inheritDoc} 081 */ 082 @Override 083 protected MultiLineLabel newLabel(final MarkupContainer parent, final String componentId, 084 final IModel<T> model) 085 { 086 MultiLineLabel label = new MultiLineLabel(componentId, model) 087 { 088 private static final long serialVersionUID = 1L; 089 090 @Override 091 public <C> IConverter<C> getConverter(final Class<C> type) 092 { 093 return AjaxEditableMultiLineLabel.this.getConverter(type); 094 } 095 096 @Override 097 public void onComponentTagBody(final MarkupStream markupStream, 098 final ComponentTag openTag) 099 { 100 Object modelObject = getDefaultModelObject(); 101 if ((modelObject == null) || (modelObject instanceof String && ((String) modelObject).isEmpty())) 102 { 103 replaceComponentTagBody(markupStream, openTag, defaultNullLabel()); 104 } 105 else 106 { 107 super.onComponentTagBody(markupStream, openTag); 108 } 109 } 110 }; 111 label.setOutputMarkupId(true); 112 label.add(new LabelAjaxBehavior(getLabelAjaxEvent())); 113 return label; 114 } 115 116 /** 117 * By default this returns "click", users can overwrite this on which event the label behavior 118 * should be triggered 119 * 120 * @return The event name 121 */ 122 @Override 123 protected String getLabelAjaxEvent() 124 { 125 return "click"; 126 } 127 128 @Override 129 protected FormComponent<T> newEditor(final MarkupContainer parent, final String componentId, 130 final IModel<T> model) 131 { 132 TextArea<T> editor = new TextArea<T>(componentId, model) 133 { 134 private static final long serialVersionUID = 1L; 135 136 /** 137 * {@inheritDoc} 138 */ 139 @Override 140 public <C> IConverter<C> getConverter(final Class<C> type) 141 { 142 return AjaxEditableMultiLineLabel.this.getConverter(type); 143 } 144 145 @Override 146 protected boolean shouldTrimInput() 147 { 148 return AjaxEditableMultiLineLabel.this.shouldTrimInput(); 149 } 150 151 @Override 152 protected void onModelChanged() 153 { 154 AjaxEditableMultiLineLabel.this.onModelChanged(); 155 } 156 157 @Override 158 protected void onModelChanging() 159 { 160 AjaxEditableMultiLineLabel.this.onModelChanging(); 161 } 162 }; 163 editor.add(new AttributeModifier("rows", (IModel<Integer>) () -> rows)); 164 editor.add(new AttributeModifier("cols", (IModel<Integer>) () -> cols)); 165 editor.setOutputMarkupId(true); 166 editor.setVisible(false); 167 editor.add(new EditorAjaxBehavior() 168 { 169 private static final long serialVersionUID = 1L; 170 171 @Override 172 protected void updateAjaxAttributes(AjaxRequestAttributes attributes) 173 { 174 super.updateAjaxAttributes(attributes); 175 attributes.setMethod(Method.POST); 176 attributes.setEventNames("blur", "keyup"); 177 CharSequence dynamicExtraParameters = 178 "var result = [], " + 179 "kc=Wicket.Event.keyCode(attrs.event)," + 180 "evtType=attrs.event.type;" + 181 "if (evtType === 'keyup') {" + 182 // ESCAPE key 183 "if (kc===27) { result.push( { name: 'save', value: false } ); }" + 184 "}" + 185 "else if (evtType==='blur') { result = Wicket.Form.serializeElement(attrs.c); result.push( { name: 'save', value: true } ); }" + 186 "return result;"; 187 attributes.getDynamicExtraParameters().add(dynamicExtraParameters); 188 189 CharSequence precondition = 190 "var kc=Wicket.Event.keyCode(attrs.event),"+ 191 "evtType=attrs.event.type,"+ 192 "ret=false;"+ 193 "if(evtType==='blur' || (evtType==='keyup' && (kc===27))) ret = true;"+ 194 "return ret;"; 195 AjaxCallListener ajaxCallListener = new AjaxCallListener(); 196 ajaxCallListener.onPrecondition(precondition); 197 attributes.getAjaxCallListeners().add(ajaxCallListener); 198 } 199 }); 200 return editor; 201 } 202 203 /** 204 * Gets text area's number of columns. 205 * 206 * @return text area's number of columns 207 */ 208 public final int getCols() 209 { 210 return cols; 211 } 212 213 /** 214 * Sets text area's number of columns. 215 * 216 * @param cols 217 * text area's number of columns 218 */ 219 public final void setCols(final int cols) 220 { 221 this.cols = cols; 222 } 223 224 /** 225 * Gets text area's number of rows. 226 * 227 * @return text area's number of rows 228 */ 229 public final int getRows() 230 { 231 return rows; 232 } 233 234 /** 235 * Sets text area's number of rows. 236 * 237 * @param rows 238 * text area's number of rows 239 */ 240 public final void setRows(final int rows) 241 { 242 this.rows = rows; 243 } 244 245 /** 246 * Override this to display a different value when the model object is null. Default is 247 * <code>...</code> 248 * 249 * @return The string which should be displayed when the model object is null. 250 */ 251 @Override 252 protected String defaultNullLabel() 253 { 254 return "..."; 255 } 256 257 /** 258 * {@inheritDoc} 259 */ 260 @Override 261 protected void onModelChanged() 262 { 263 super.onModelChanged(); 264 } 265 266 /** 267 * {@inheritDoc} 268 */ 269 @Override 270 protected void onModelChanging() 271 { 272 super.onModelChanging(); 273 } 274}