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 &lt;div&gt;)
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}