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