1 // This may look like C code, but it's really -*- C++ -*- 2 /* 3 * Copyright (C) 2008 Emweb bv, Herent, Belgium. 4 * 5 * See the LICENSE file for terms of use. 6 */ 7 #ifndef DOMELEMENT_H_ 8 #define DOMELEMENT_H_ 9 10 #if defined(WT_THREADED) || defined(WT_TARGET_JAVA) 11 #include <atomic> 12 #endif 13 14 #include <map> 15 #include <vector> 16 #include <string> 17 18 #include "Wt/WWebWidget.h" 19 #include "EscapeOStream.h" 20 21 namespace Wt { 22 23 class WApplication; 24 25 typedef EscapeOStream EStream; 26 27 /*! \brief Enumeration for a DOM property. 28 * 29 * This is an internal API, subject to change. 30 */ 31 enum class Property { InnerHTML, AddedInnerHTML, 32 Value, Disabled, 33 Checked, Selected, SelectedIndex, 34 Multiple, Target, Download, Indeterminate, 35 Src, 36 ColSpan, RowSpan, ReadOnly, 37 TabIndex, Label, 38 Class, 39 Placeholder, 40 Style, 41 StyleWidthExpression, 42 StylePosition, 43 StyleZIndex, StyleFloat, StyleClear, 44 StyleWidth, StyleHeight, 45 StyleLineHeight, 46 StyleMinWidth, StyleMinHeight, 47 StyleMaxWidth, StyleMaxHeight, 48 StyleLeft, StyleRight, 49 StyleTop, StyleBottom, 50 StyleVerticalAlign, StyleTextAlign, 51 StylePadding, 52 StylePaddingTop, StylePaddingRight, 53 StylePaddingBottom, StylePaddingLeft, 54 StyleMargin, 55 StyleMarginTop, StyleMarginRight, 56 StyleMarginBottom, StyleMarginLeft, 57 StyleCursor, 58 StyleBorderTop, StyleBorderRight, 59 StyleBorderBottom, StyleBorderLeft, 60 StyleBorderColorTop, StyleBorderColorRight, 61 StyleBorderColorBottom, StyleBorderColorLeft, 62 StyleBorderWidthTop, StyleBorderWidthRight, 63 StyleBorderWidthBottom, StyleBorderWidthLeft, 64 StyleColor, 65 StyleOverflowX, 66 StyleOverflowY, 67 StyleOpacity, 68 StyleFontFamily, 69 StyleFontStyle, 70 StyleFontVariant, 71 StyleFontWeight, 72 StyleFontSize, 73 StyleBackgroundColor, 74 StyleBackgroundImage, 75 StyleBackgroundRepeat, 76 StyleBackgroundAttachment, 77 StyleBackgroundPosition, 78 StyleTextDecoration, StyleWhiteSpace, 79 StyleTableLayout, StyleBorderSpacing, 80 StyleBorderCollapse, 81 StylePageBreakBefore, StylePageBreakAfter, 82 StyleZoom, 83 StyleVisibility, StyleDisplay, 84 85 /* CSS 3 */ 86 StyleBoxSizing, 87 StyleFlex, 88 StyleFlexFlow, 89 StyleAlignSelf, 90 StyleJustifyContent, 91 92 /* Keep as last, e.g. for bitset sizing. Otherwise, unused. */ 93 LastPlusOne }; 94 95 /*! \class DomElement web/DomElement web/DomElement 96 * \brief Class to represent a client-side DOM element (proxy). 97 * 98 * The DOM element proxy object is used as an intermediate layer to 99 * render the creation of new DOM elements or updates to existing DOM 100 * elements. A DOM element can be serialized to HTML or to JavaScript 101 * manipulations, and therefore is the main abstraction layer to avoid 102 * hard-coding JavaScript-based rendering within the library while 103 * still allowing fine-grained Ajax updates or large-scale HTML 104 * changes. 105 * 106 * This is an internal API, subject to change. 107 */ 108 class WT_API DomElement 109 { 110 public: 111 /*! \brief Enumeration for the access mode (creation or update) */ 112 enum class Mode { Create, Update }; 113 114 #ifndef WT_TARGET_JAVA 115 /*! \brief A map for property values */ 116 typedef std::map<Wt::Property, std::string> PropertyMap; 117 #else 118 typedef std::treemap<Wt::Property, std::string> PropertyMap; 119 #endif 120 121 /*! \brief Constructor. 122 * 123 * This constructs a DomElement reference, with a given mode and 124 * element type. Note that even when updating an existing element, 125 * the type is taken into account for information on what kind of 126 * operations are allowed (workarounds for IE deficiencies for 127 * examples) or to infer some basic CSS defaults for it (whether it 128 * is inline or a block element). 129 * 130 * Typically, elements are created using one of the 'named' 131 * constructors: createNew(), getForUpdate() or updateGiven(). 132 */ 133 DomElement(Mode mode, DomElementType type); 134 135 /*! \brief Destructor. 136 */ 137 ~DomElement(); 138 139 /*! \brief set dom element custom tag name 140 */ 141 void setDomElementTagName(const std::string& name); 142 143 /*! \brief Low-level URL encoding function. 144 */ 145 static std::string urlEncodeS(const std::string& url); 146 147 /*! \brief Low-level URL encoding function. 148 * 149 * This variant allows the exclusion of certain characters from URL 150 * encoding. 151 */ 152 static std::string urlEncodeS(const std::string& url, 153 const std::string& allowed); 154 155 /*! \brief Returns the mode. 156 */ mode()157 Mode mode() const { return mode_; } 158 159 /*! \brief Sets the element type. 160 */ 161 void setType(DomElementType type); 162 163 /*! \brief Returns the element type. 164 */ type()165 DomElementType type() const { return type_; } 166 167 /*! \brief Creates a reference to a new element. 168 */ 169 static DomElement *createNew(DomElementType type); 170 171 /*! \brief Creates a reference to an existing element, using its ID. 172 */ 173 static DomElement *getForUpdate(const std::string& id, DomElementType type); 174 175 /*! \brief Creates a reference to an existing element, deriving the ID from 176 * an object. 177 * 178 * This uses object->id() as the id. 179 */ 180 static DomElement *getForUpdate(const WObject *object, DomElementType type); 181 182 /*! \brief Creates a reference to an existing element, using an expression 183 * to access the element. 184 */ 185 static DomElement *updateGiven(const std::string& el, DomElementType type); 186 187 /*! \brief Returns the JavaScript variable name. 188 * 189 * This variable name is only defined when the element is being 190 * rendered using JavaScript, after declare() has been called. 191 */ var()192 std::string var() { return var_; } 193 194 /*! \brief Sets whether the element was initially empty. 195 * 196 * Knowing that an element was empty allows optimization of 197 * addChild() 198 */ 199 void setWasEmpty(bool how); 200 201 /*! \brief Adds a child. 202 * 203 * Ownership of the child is transferred to this element, and the 204 * child should not be manipulated after the call, since it could be 205 * that it gets directly converted into HTML and deleted. 206 */ 207 void addChild(DomElement *child); 208 209 /*! \brief Inserts a child. 210 * 211 * Ownership of the child is transferred to this element, and the child 212 * should not be manipulated after the call. 213 */ 214 void insertChildAt(DomElement *child, int pos); 215 216 /*! \brief Saves an existing child. 217 * 218 * This detaches the child from the parent, allowing the 219 * manipulation of the innerHTML without deleting the child. Stubs 220 * in the the new HTML that reference the same id will be replaced 221 * with the saved child. 222 */ 223 void saveChild(const std::string& id); 224 225 /*! \brief Sets an attribute value. 226 */ 227 void setAttribute(const std::string& attribute, const std::string& value); 228 229 /*! \brief Returns an attribute value set. 230 * 231 * \sa setAttribute() 232 */ 233 std::string getAttribute(const std::string& attribute) const; 234 235 /*! \brief Removes an attribute. 236 */ 237 void removeAttribute(const std::string& attribute); 238 239 /*! \brief Sets a property. 240 */ 241 void setProperty(Wt::Property property, const std::string& value); 242 243 /*! \brief Adds a 'word' to a property. 244 * 245 * This adds a word (delimited by a space) to an existing property value. 246 */ 247 void addPropertyWord(Wt::Property property, const std::string& value); 248 249 /*! \brief Returns a property value set. 250 * 251 * \sa setProperty() 252 */ 253 std::string getProperty(Wt::Property property) const; 254 255 /*! \brief Removes a property. 256 */ 257 void removeProperty(Wt::Property property); 258 259 /*! \brief Sets a whole map of properties. 260 */ 261 void setProperties(const PropertyMap& properties); 262 263 /*! \brief Returns all properties currently set. 264 */ properties()265 const PropertyMap& properties() const { return properties_; } 266 267 /*! \brief Clears all properties. 268 */ 269 void clearProperties(); 270 271 /*! \brief Sets an event handler based on a signal's connections. 272 */ 273 void setEventSignal(const char *eventName, const EventSignalBase& signal); 274 275 /*! \brief Sets an event handler. 276 * 277 * This sets an event handler by a combination of client-side 278 * JavaScript code and a server-side signal to emit. 279 */ 280 void setEvent(const char *eventName, 281 const std::string& jsCode, 282 const std::string& signalName, 283 bool isExposed = false); 284 285 /*! \brief Sets an event handler. 286 * 287 * This sets a JavaScript event handler. 288 */ 289 void setEvent(const char *eventName, const std::string& jsCode); 290 291 /*! \brief This adds more JavaScript to an event handler. 292 */ 293 void addEvent(const char *eventName, const std::string& jsCode); 294 295 /*! \brief A data-structure for an aggregated event handler. */ 296 struct EventAction 297 { 298 std::string jsCondition; 299 std::string jsCode; 300 std::string updateCmd; 301 bool exposed; 302 303 EventAction(const std::string& jsCondition, const std::string& jsCode, 304 const std::string& updateCmd, bool exposed); 305 }; 306 307 /*! \brief Sets an aggregated event handler. */ 308 void setEvent(const char * eventName, 309 const std::vector<EventAction>& actions); 310 311 /*! \brief Sets the DOM element id. 312 */ 313 void setId(const std::string& id); 314 315 /*! \brief Sets a DOM element name. 316 */ 317 void setName(const std::string& name); 318 319 /*! \brief Configures the DOM element as a source for timed events. 320 */ 321 void setTimeout(int msec, bool jsRepeat); 322 323 /*! \brief Configures the DOM element as a source for timed events, 324 * with given initial delay and interval, always repeating. 325 */ 326 void setTimeout(int delay, int interval); 327 328 /*! \brief Calls a JavaScript method on the DOM element. 329 */ 330 void callMethod(const std::string& method); 331 332 /*! \brief Calls JavaScript (related to the DOM element). 333 */ 334 void callJavaScript(const std::string& javascript, 335 bool evenWhenDeleted = false); 336 337 /*! \brief Returns the id. 338 */ id()339 const std::string& id() const { return id_; } 340 341 /*! \brief Removes all children. 342 * 343 * If firstChild != 0, then only children starting from firstChild 344 * are removed. 345 */ 346 void removeAllChildren(int firstChild = 0); 347 348 /*! \brief Removes the element. 349 */ 350 void removeFromParent(); 351 352 /*! \brief Replaces the element by another element. 353 */ 354 void replaceWith(DomElement *newElement); 355 356 /*! \brief Unstubs an element by another element. 357 * 358 * Stubs are used to render hidden elements initially and update 359 * them in the background. This is almost the same as replaceWith() 360 * except that some style properties are copied over (most 361 * importantly its visibility). 362 */ 363 void unstubWith(DomElement *newElement, bool hideWithDisplay); 364 365 /*! \brief Inserts the element in the DOM as a new sibling. 366 */ 367 void insertBefore(DomElement *sibling); 368 369 /*! \brief Unwraps an element to progress to Ajax support. 370 * 371 * In plain HTML mode, some elements are rendered wrapped in or as 372 * another element, to provide more interactivity in the absense of 373 * JavaScript. 374 */ 375 void unwrap(); 376 377 /*! \brief Enumeration for an update rendering phase. 378 */ 379 enum class Priority { Delete, Create, Update }; 380 381 /*! \brief Structure for keeping track of timers attached to this element. 382 */ 383 struct TimeoutEvent { 384 int msec; 385 std::string event; 386 int repeat; 387 TimeoutEventTimeoutEvent388 TimeoutEvent() { } TimeoutEventTimeoutEvent389 TimeoutEvent(int m, const std::string& e, int r) 390 : msec(m), event(e), repeat(r) { } 391 }; 392 393 /*! \brief A list of timeouts. 394 */ 395 typedef std::vector<TimeoutEvent> TimeoutList; 396 397 /*! \brief Renders the element as JavaScript. 398 */ 399 void asJavaScript(WStringStream& out); 400 401 /*! \brief Renders the element as JavaScript, by phase. 402 * 403 * To avoid temporarily having dupliate IDs as elements move around 404 * in the page, rendering is ordered in a number of phases : first 405 * deleting existing elements, then creating new elements, and 406 * finally updates to existing elements. 407 */ 408 std::string asJavaScript(EStream& out, Priority priority) const; 409 410 /*! \brief Renders the element as HTML. 411 * 412 * Anything that cannot be rendered as HTML is rendered as 413 * javaScript as a by-product. 414 */ 415 void asHTML(EStream& out, EStream& javaScript, 416 TimeoutList& timeouts, bool openingTagOnly = false) const; 417 418 /*! \brief Creates the JavaScript statements for timer rendering. 419 */ 420 static void createTimeoutJs(WStringStream& out, const TimeoutList& timeouts, 421 WApplication *app); 422 423 /*! \brief Returns the default display property for this element. 424 * 425 * This returns whether the element is by default an inline or block 426 * element. 427 */ 428 bool isDefaultInline() const; 429 430 /*! \brief Declares the element. 431 * 432 * Only after the element has been declared, var() returns a useful 433 * JavaScript reference. 434 */ 435 void declare(EStream& out) const; 436 437 /*! \brief Renders properties and attributes into CSS. 438 */ 439 std::string cssStyle() const; 440 441 /*! \brief Utility for rapid rendering of JavaScript strings. 442 * 443 * It uses pre-computed mixing rules for escaping of the string. 444 */ 445 static void fastJsStringLiteral(EStream& outRaw, 446 const EStream& outEscaped, 447 const std::string& s); 448 449 /*! \brief Utility that renders a string as JavaScript literal. 450 */ 451 static void jsStringLiteral(EStream& out, const std::string& s, 452 char delimiter); 453 454 /*! \brief Utility that renders a string as JavaScript literal. 455 */ 456 static void jsStringLiteral(WStringStream& out, const std::string& s, 457 char delimiter); 458 459 /*! \brief Utility for rapid rendering of HTML attribute values. 460 * 461 * It uses pre-computed mixing rules for escaping of the attribute 462 * value. 463 */ 464 static void fastHtmlAttributeValue(EStream& outRaw, 465 const EStream& outEscaped, 466 const std::string& s); 467 468 /*! \brief Utility that renders a string as HTML attribute. 469 */ 470 static void htmlAttributeValue(WStringStream& out, const std::string& s); 471 472 /*! \brief Returns whether a tag is self-closing in HTML. 473 */ 474 static bool isSelfClosingTag(const std::string& tag); 475 476 /*! \brief Returns whether a tag is self-closing in HTML. 477 */ 478 static bool isSelfClosingTag(DomElementType element); 479 480 /*! \brief Parses a tag name to a DOMElement type. 481 */ 482 static DomElementType parseTagName(const std::string& tag); 483 484 /*! \brief Returns the tag name for a DOMElement type. 485 */ 486 static std::string tagName(DomElementType type); 487 488 /*! \brief Returns the name for a CSS property, as a string. 489 */ 490 static const std::string& cssName(Property property); 491 492 /*! \brief Returns whether a paritcular element is by default inline. 493 */ 494 static bool isDefaultInline(DomElementType type); 495 496 /*! \brief Returns all custom JavaScript collected in this element. 497 */ javaScript()498 std::string javaScript() const { return javaScript_.str(); } 499 500 /*! \brief Something to do with broken IE Mobile 5 browsers... 501 */ 502 void updateInnerHtmlOnly(); 503 504 /*! \brief Adds an element to a parent, using suitable methods. 505 * 506 * Depending on the type, different DOM methods are needed. In 507 * particular for table cells, some browsers require dedicated API 508 * instead of generic insertAt() or appendChild() functions. 509 */ 510 std::string addToParent(WStringStream& out, const std::string& parentVar, 511 int pos, WApplication *app); 512 513 /*! \brief Renders the element as JavaScript, and inserts it in the DOM. 514 */ 515 void createElement(WStringStream& out, WApplication *app, 516 const std::string& domInsertJS); 517 518 /*! \brief Allocates a JavaScript variable. 519 */ 520 std::string createVar() const; 521 522 void setGlobalUnfocused(bool b); 523 524 private: 525 struct EventHandler { 526 std::string jsCode; 527 std::string signalName; 528 EventHandlerEventHandler529 EventHandler() { } EventHandlerEventHandler530 EventHandler(const std::string& j, const std::string& sn) 531 : jsCode(j), signalName(sn) { } 532 }; 533 534 typedef std::map<std::string, std::string> AttributeMap; 535 typedef std::set<std::string> AttributeSet; 536 typedef std::map<const char *, EventHandler> EventHandlerMap; 537 538 bool willRenderInnerHtmlJS(WApplication *app) const; 539 bool canWriteInnerHTML(WApplication *app) const; 540 bool containsElement(DomElementType type) const; 541 void processEvents(WApplication *app) const; 542 void processProperties(WApplication *app) const; 543 void setJavaScriptProperties(EStream& out, WApplication *app) const; 544 void setJavaScriptAttributes(EStream& out) const; 545 void setJavaScriptEvent(EStream& out, const char *eventName, 546 const EventHandler& handler, WApplication *app) const; 547 void createElement(EStream& out, WApplication *app, 548 const std::string& domInsertJS); 549 std::string addToParent(EStream& out, const std::string& parentVar, 550 int pos, WApplication *app); 551 std::string createAsJavaScript(EStream& out, 552 const std::string& parentVar, int pos, 553 WApplication *app); 554 void renderInnerHtmlJS(EStream& out, WApplication *app) const; 555 void renderDeferredJavaScript(EStream& out) const; 556 557 Mode mode_; 558 bool wasEmpty_; 559 int removeAllChildren_; 560 bool hideWithDisplay_; 561 bool minMaxSizeProperties_; 562 bool unstubbed_; 563 bool unwrapped_; 564 DomElement *replaced_; // when replaceWith() is called 565 DomElement *insertBefore_; 566 DomElementType type_; 567 std::string id_; 568 int numManipulations_; 569 int timeOut_; 570 int timeOutJSRepeat_; 571 EStream javaScript_; 572 std::string javaScriptEvenWhenDeleted_; 573 mutable std::string var_; 574 mutable bool declared_; 575 bool globalUnfocused_; 576 577 AttributeMap attributes_; 578 AttributeSet removedAttributes_; 579 PropertyMap properties_; 580 EventHandlerMap eventHandlers_; 581 582 struct ChildInsertion { 583 int pos; 584 DomElement *child; 585 ChildInsertionChildInsertion586 ChildInsertion() : pos(0), child(nullptr) { } ChildInsertionChildInsertion587 ChildInsertion(int p, DomElement *c) : pos(p), child(c) { } 588 }; 589 590 std::vector<ChildInsertion> childrenToAdd_; 591 std::vector<std::string> childrenToSave_; 592 std::vector<DomElement *> updatedChildren_; 593 EStream childrenHtml_; 594 TimeoutList timeouts_; 595 std::string elementTagName_; 596 597 #if defined(WT_THREADED) || defined(WT_TARGET_JAVA) 598 static std::atomic<unsigned> nextId_; 599 #else 600 static unsigned nextId_; 601 #endif 602 603 friend class WCssDecorationStyle; 604 }; 605 606 } 607 608 #endif // DOMELEMENT_H_ 609