001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.tags.form;
018
019import java.util.HashMap;
020import java.util.Map;
021
022import javax.servlet.jsp.JspException;
023import javax.servlet.jsp.tagext.DynamicAttributes;
024
025import org.springframework.lang.Nullable;
026import org.springframework.util.CollectionUtils;
027import org.springframework.util.ObjectUtils;
028import org.springframework.util.StringUtils;
029
030/**
031 * Base class for databinding-aware JSP tags that render HTML element. Provides
032 * a set of properties corresponding to the set of HTML attributes that are common
033 * across elements.
034 *
035 * <p>Additionally, this base class allows for rendering non-standard attributes
036 * as part of the tag's output.  These attributes are accessible to subclasses if
037 * needed via the {@link AbstractHtmlElementTag#getDynamicAttributes() dynamicAttributes}
038 * map.
039 *
040 * @author Rob Harrop
041 * @author Jeremy Grelle
042 * @author Rossen Stoyanchev
043 * @since 2.0
044 */
045@SuppressWarnings("serial")
046public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElementTag implements DynamicAttributes {
047
048        public static final String CLASS_ATTRIBUTE = "class";
049
050        public static final String STYLE_ATTRIBUTE = "style";
051
052        public static final String LANG_ATTRIBUTE = "lang";
053
054        public static final String TITLE_ATTRIBUTE = "title";
055
056        public static final String DIR_ATTRIBUTE = "dir";
057
058        public static final String TABINDEX_ATTRIBUTE = "tabindex";
059
060        public static final String ONCLICK_ATTRIBUTE = "onclick";
061
062        public static final String ONDBLCLICK_ATTRIBUTE = "ondblclick";
063
064        public static final String ONMOUSEDOWN_ATTRIBUTE = "onmousedown";
065
066        public static final String ONMOUSEUP_ATTRIBUTE = "onmouseup";
067
068        public static final String ONMOUSEOVER_ATTRIBUTE = "onmouseover";
069
070        public static final String ONMOUSEMOVE_ATTRIBUTE = "onmousemove";
071
072        public static final String ONMOUSEOUT_ATTRIBUTE = "onmouseout";
073
074        public static final String ONKEYPRESS_ATTRIBUTE = "onkeypress";
075
076        public static final String ONKEYUP_ATTRIBUTE = "onkeyup";
077
078        public static final String ONKEYDOWN_ATTRIBUTE = "onkeydown";
079
080
081        @Nullable
082        private String cssClass;
083
084        @Nullable
085        private String cssErrorClass;
086
087        @Nullable
088        private String cssStyle;
089
090        @Nullable
091        private String lang;
092
093        @Nullable
094        private String title;
095
096        @Nullable
097        private String dir;
098
099        @Nullable
100        private String tabindex;
101
102        @Nullable
103        private String onclick;
104
105        @Nullable
106        private String ondblclick;
107
108        @Nullable
109        private String onmousedown;
110
111        @Nullable
112        private String onmouseup;
113
114        @Nullable
115        private String onmouseover;
116
117        @Nullable
118        private String onmousemove;
119
120        @Nullable
121        private String onmouseout;
122
123        @Nullable
124        private String onkeypress;
125
126        @Nullable
127        private String onkeyup;
128
129        @Nullable
130        private String onkeydown;
131
132        @Nullable
133        private Map<String, Object> dynamicAttributes;
134
135
136        /**
137         * Set the value of the '{@code class}' attribute.
138         * May be a runtime expression.
139         */
140        public void setCssClass(String cssClass) {
141                this.cssClass = cssClass;
142        }
143
144        /**
145         * Get the value of the '{@code class}' attribute.
146         * May be a runtime expression.
147         */
148        @Nullable
149        protected String getCssClass() {
150                return this.cssClass;
151        }
152
153        /**
154         * The CSS class to use when the field bound to a particular tag has errors.
155         * May be a runtime expression.
156         */
157        public void setCssErrorClass(String cssErrorClass) {
158                this.cssErrorClass = cssErrorClass;
159        }
160
161        /**
162         * The CSS class to use when the field bound to a particular tag has errors.
163         * May be a runtime expression.
164         */
165        @Nullable
166        protected String getCssErrorClass() {
167                return this.cssErrorClass;
168        }
169
170        /**
171         * Set the value of the '{@code style}' attribute.
172         * May be a runtime expression.
173         */
174        public void setCssStyle(String cssStyle) {
175                this.cssStyle = cssStyle;
176        }
177
178        /**
179         * Get the value of the '{@code style}' attribute.
180         * May be a runtime expression.
181         */
182        @Nullable
183        protected String getCssStyle() {
184                return this.cssStyle;
185        }
186
187        /**
188         * Set the value of the '{@code lang}' attribute.
189         * May be a runtime expression.
190         */
191        public void setLang(String lang) {
192                this.lang = lang;
193        }
194
195        /**
196         * Get the value of the '{@code lang}' attribute.
197         * May be a runtime expression.
198         */
199        @Nullable
200        protected String getLang() {
201                return this.lang;
202        }
203
204        /**
205         * Set the value of the '{@code title}' attribute.
206         * May be a runtime expression.
207         */
208        public void setTitle(String title) {
209                this.title = title;
210        }
211
212        /**
213         * Get the value of the '{@code title}' attribute.
214         * May be a runtime expression.
215         */
216        @Nullable
217        protected String getTitle() {
218                return this.title;
219        }
220
221        /**
222         * Set the value of the '{@code dir}' attribute.
223         * May be a runtime expression.
224         */
225        public void setDir(String dir) {
226                this.dir = dir;
227        }
228
229        /**
230         * Get the value of the '{@code dir}' attribute.
231         * May be a runtime expression.
232         */
233        @Nullable
234        protected String getDir() {
235                return this.dir;
236        }
237
238        /**
239         * Set the value of the '{@code tabindex}' attribute.
240         * May be a runtime expression.
241         */
242        public void setTabindex(String tabindex) {
243                this.tabindex = tabindex;
244        }
245
246        /**
247         * Get the value of the '{@code tabindex}' attribute.
248         * May be a runtime expression.
249         */
250        @Nullable
251        protected String getTabindex() {
252                return this.tabindex;
253        }
254
255        /**
256         * Set the value of the '{@code onclick}' attribute.
257         * May be a runtime expression.
258         */
259        public void setOnclick(String onclick) {
260                this.onclick = onclick;
261        }
262
263        /**
264         * Get the value of the '{@code onclick}' attribute.
265         * May be a runtime expression.
266         */
267        @Nullable
268        protected String getOnclick() {
269                return this.onclick;
270        }
271
272        /**
273         * Set the value of the '{@code ondblclick}' attribute.
274         * May be a runtime expression.
275         */
276        public void setOndblclick(String ondblclick) {
277                this.ondblclick = ondblclick;
278        }
279
280        /**
281         * Get the value of the '{@code ondblclick}' attribute.
282         * May be a runtime expression.
283         */
284        @Nullable
285        protected String getOndblclick() {
286                return this.ondblclick;
287        }
288
289        /**
290         * Set the value of the '{@code onmousedown}' attribute.
291         * May be a runtime expression.
292         */
293        public void setOnmousedown(String onmousedown) {
294                this.onmousedown = onmousedown;
295        }
296
297        /**
298         * Get the value of the '{@code onmousedown}' attribute.
299         * May be a runtime expression.
300         */
301        @Nullable
302        protected String getOnmousedown() {
303                return this.onmousedown;
304        }
305
306        /**
307         * Set the value of the '{@code onmouseup}' attribute.
308         * May be a runtime expression.
309         */
310        public void setOnmouseup(String onmouseup) {
311                this.onmouseup = onmouseup;
312        }
313
314        /**
315         * Get the value of the '{@code onmouseup}' attribute.
316         * May be a runtime expression.
317         */
318        @Nullable
319        protected String getOnmouseup() {
320                return this.onmouseup;
321        }
322
323        /**
324         * Set the value of the '{@code onmouseover}' attribute.
325         * May be a runtime expression.
326         */
327        public void setOnmouseover(String onmouseover) {
328                this.onmouseover = onmouseover;
329        }
330
331        /**
332         * Get the value of the '{@code onmouseover}' attribute.
333         * May be a runtime expression.
334         */
335        @Nullable
336        protected String getOnmouseover() {
337                return this.onmouseover;
338        }
339
340        /**
341         * Set the value of the '{@code onmousemove}' attribute.
342         * May be a runtime expression.
343         */
344        public void setOnmousemove(String onmousemove) {
345                this.onmousemove = onmousemove;
346        }
347
348        /**
349         * Get the value of the '{@code onmousemove}' attribute.
350         * May be a runtime expression.
351         */
352        @Nullable
353        protected String getOnmousemove() {
354                return this.onmousemove;
355        }
356
357        /**
358         * Set the value of the '{@code onmouseout}' attribute.
359         * May be a runtime expression.
360         */
361        public void setOnmouseout(String onmouseout) {
362                this.onmouseout = onmouseout;
363        }
364        /**
365         * Get the value of the '{@code onmouseout}' attribute.
366         * May be a runtime expression.
367         */
368        @Nullable
369        protected String getOnmouseout() {
370                return this.onmouseout;
371        }
372
373        /**
374         * Set the value of the '{@code onkeypress}' attribute.
375         * May be a runtime expression.
376         */
377        public void setOnkeypress(String onkeypress) {
378                this.onkeypress = onkeypress;
379        }
380
381        /**
382         * Get the value of the '{@code onkeypress}' attribute.
383         * May be a runtime expression.
384         */
385        @Nullable
386        protected String getOnkeypress() {
387                return this.onkeypress;
388        }
389
390        /**
391         * Set the value of the '{@code onkeyup}' attribute.
392         * May be a runtime expression.
393         */
394        public void setOnkeyup(String onkeyup) {
395                this.onkeyup = onkeyup;
396        }
397
398        /**
399         * Get the value of the '{@code onkeyup}' attribute.
400         * May be a runtime expression.
401         */
402        @Nullable
403        protected String getOnkeyup() {
404                return this.onkeyup;
405        }
406
407        /**
408         * Set the value of the '{@code onkeydown}' attribute.
409         * May be a runtime expression.
410         */
411        public void setOnkeydown(String onkeydown) {
412                this.onkeydown = onkeydown;
413        }
414
415        /**
416         * Get the value of the '{@code onkeydown}' attribute.
417         * May be a runtime expression.
418         */
419        @Nullable
420        protected String getOnkeydown() {
421                return this.onkeydown;
422        }
423
424        /**
425         * Get the map of dynamic attributes.
426         */
427        @Nullable
428        protected Map<String, Object> getDynamicAttributes() {
429                return this.dynamicAttributes;
430        }
431
432        /**
433         * {@inheritDoc}
434         */
435        @Override
436        public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {
437                if (this.dynamicAttributes == null) {
438                        this.dynamicAttributes = new HashMap<>();
439                }
440                if (!isValidDynamicAttribute(localName, value)) {
441                        throw new IllegalArgumentException(
442                                        "Attribute " + localName + "=\"" + value + "\" is not allowed");
443                }
444                this.dynamicAttributes.put(localName, value);
445        }
446
447        /**
448         * Whether the given name-value pair is a valid dynamic attribute.
449         */
450        protected boolean isValidDynamicAttribute(String localName, Object value) {
451                return true;
452        }
453
454        /**
455         * Writes the default attributes configured via this base class to the supplied {@link TagWriter}.
456         * Subclasses should call this when they want the base attribute set to be written to the output.
457         */
458        @Override
459        protected void writeDefaultAttributes(TagWriter tagWriter) throws JspException {
460                super.writeDefaultAttributes(tagWriter);
461                writeOptionalAttributes(tagWriter);
462        }
463
464        /**
465         * Writes the optional attributes configured via this base class to the supplied {@link TagWriter}.
466         * The set of optional attributes that will be rendered includes any non-standard dynamic attributes.
467         * Called by {@link #writeDefaultAttributes(TagWriter)}.
468         */
469        protected void writeOptionalAttributes(TagWriter tagWriter) throws JspException {
470                tagWriter.writeOptionalAttributeValue(CLASS_ATTRIBUTE, resolveCssClass());
471                tagWriter.writeOptionalAttributeValue(STYLE_ATTRIBUTE,
472                                ObjectUtils.getDisplayString(evaluate("cssStyle", getCssStyle())));
473                writeOptionalAttribute(tagWriter, LANG_ATTRIBUTE, getLang());
474                writeOptionalAttribute(tagWriter, TITLE_ATTRIBUTE, getTitle());
475                writeOptionalAttribute(tagWriter, DIR_ATTRIBUTE, getDir());
476                writeOptionalAttribute(tagWriter, TABINDEX_ATTRIBUTE, getTabindex());
477                writeOptionalAttribute(tagWriter, ONCLICK_ATTRIBUTE, getOnclick());
478                writeOptionalAttribute(tagWriter, ONDBLCLICK_ATTRIBUTE, getOndblclick());
479                writeOptionalAttribute(tagWriter, ONMOUSEDOWN_ATTRIBUTE, getOnmousedown());
480                writeOptionalAttribute(tagWriter, ONMOUSEUP_ATTRIBUTE, getOnmouseup());
481                writeOptionalAttribute(tagWriter, ONMOUSEOVER_ATTRIBUTE, getOnmouseover());
482                writeOptionalAttribute(tagWriter, ONMOUSEMOVE_ATTRIBUTE, getOnmousemove());
483                writeOptionalAttribute(tagWriter, ONMOUSEOUT_ATTRIBUTE, getOnmouseout());
484                writeOptionalAttribute(tagWriter, ONKEYPRESS_ATTRIBUTE, getOnkeypress());
485                writeOptionalAttribute(tagWriter, ONKEYUP_ATTRIBUTE, getOnkeyup());
486                writeOptionalAttribute(tagWriter, ONKEYDOWN_ATTRIBUTE, getOnkeydown());
487
488                if (!CollectionUtils.isEmpty(this.dynamicAttributes)) {
489                        for (Map.Entry<String, Object> entry : this.dynamicAttributes.entrySet()) {
490                                tagWriter.writeOptionalAttributeValue(entry.getKey(), getDisplayString(entry.getValue()));
491                        }
492                }
493        }
494
495        /**
496         * Gets the appropriate CSS class to use based on the state of the current
497         * {@link org.springframework.web.servlet.support.BindStatus} object.
498         */
499        protected String resolveCssClass() throws JspException {
500                if (getBindStatus().isError() && StringUtils.hasText(getCssErrorClass())) {
501                        return ObjectUtils.getDisplayString(evaluate("cssErrorClass", getCssErrorClass()));
502                }
503                else {
504                        return ObjectUtils.getDisplayString(evaluate("cssClass", getCssClass()));
505                }
506        }
507
508}