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}