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