1 /*
2  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 #include <cstdio>
7 #include <sstream>
8 
9 #include "Wt/WObject.h"
10 #include "Wt/WApplication.h"
11 #include "Wt/WContainerWidget.h"
12 #include "Wt/WEnvironment.h"
13 #include "Wt/WException.h"
14 #include "Wt/WStringStream.h"
15 #include "Wt/WTheme.h"
16 
17 #include "DomElement.h"
18 #include "WebUtils.h"
19 #include "StringUtils.h"
20 
21 namespace {
22 
23 std::string elementNames_[] =
24   { "a", "br", "button", "col",
25     "colgroup",
26     "div", "fieldset", "form",
27     "h1", "h2", "h3", "h4",
28 
29     "h5", "h6", "iframe", "img",
30     "input", "label", "legend", "li",
31     "ol",
32 
33     "option", "ul", "script", "select",
34     "span", "table", "tbody", "thead",
35     "tfoot", "th", "td", "textarea",
36     "optgroup",
37 
38     "tr", "p", "canvas",
39     "map", "area", "style",
40 
41     "object", "param",
42 
43     "audio", "video", "source",
44 
45     "b", "strong", "em", "i", "hr"
46   };
47 
48 bool defaultInline_[] =
49   { true, false, true, false,
50     false,
51     false, false, false,
52     false, false, false, false,
53 
54     false, false, true, true,
55     true, true, true, false,
56     false,
57 
58     true, false, false, true,
59     true, false, false, false,
60     false, false, false, true,
61     true,
62 
63     false, false, true,
64     false, true, true,
65 
66     false, false,
67 
68     false, false, false,
69 
70     true, true, true, true, false
71   };
72 
73 std::string cssNames_[] =
74   { "position",
75     "z-index", "float", "clear",
76     "width", "height", "line-height",
77     "min-width", "min-height",
78     "max-width", "max-height",
79     "left", "right", "top", "bottom",
80     "vertical-align", "text-align",
81     "padding",
82     "padding-top", "padding-right",
83     "padding-bottom", "padding-left",
84     "margin",
85     "margin-top", "margin-right",
86     "margin-bottom", "margin-left", "cursor",
87     "border-top", "border-right",
88     "border-bottom", "border-left",
89     "border-color-top", "border-color-right",
90     "border-color-bottom", "border-color-left",
91     "border-width-top", "border-width-right",
92     "border-width-bottom", "border-width-left",
93     "color", "overflow-x", "overflow-y",
94     "opacity",
95     "font-family", "font-style", "font-variant",
96     "font-weight", "font-size",
97     "background-color", "background-image", "background-repeat",
98     "background-attachment", "background-position",
99     "text-decoration", "white-space",
100     "table-layout", "border-spacing",
101     "border-collapse",
102     "page-break-before", "page-break-after",
103     "zoom", "visibility", "display",
104     "box-sizing", "flex", "flex-flow", "align-self", "justify-content"};
105 
106 std::string cssCamelNames_[] =
107   { "cssText", "width", "position",
108     "zIndex", "cssFloat", "clear",
109     "width", "height", "lineHeight",
110     "minWidth", "minHeight",
111     "maxWidth", "maxHeight",
112     "left", "right", "top", "bottom",
113     "verticalAlign", "textAlign",
114     "padding",
115     "paddingTop", "paddingRight",
116     "paddingBottom", "paddingLeft",
117     "margin",
118     "marginTop", "marginRight",
119     "marginBottom", "marginLeft",
120     "cursor",
121     "borderTop", "borderRight",
122     "borderBottom", "borderLeft",
123     "borderColorTop", "borderColorRight",
124     "borderColorBottom", "borderColorLeft",
125     "borderWidthTop", "borderWidthRight",
126     "borderWidthBottom", "borderWidthLeft",
127     "color", "overflowX", "overflowY",
128     "opacity",
129     "fontFamily", "fontStyle", "fontVariant",
130     "fontWeight", "fontSize",
131     "backgroundColor", "backgroundImage", "backgroundRepeat",
132     "backgroundAttachment", "backgroundPosition",
133     "textDecoration", "whiteSpace",
134     "tableLayout", "borderSpacing",
135     "border-collapse",
136     "pageBreakBefore", "pageBreakAfter",
137     "zoom", "visibility", "display",
138     "boxSizing", "flex", "flexFlow", "alignSelf", "justifyContent"
139   };
140 
141 const std::string unsafeChars_ = " $&+,:;=?@'\"<>#%{}|\\^~[]`/";
142 
hexLookup(int n)143 inline char hexLookup(int n) {
144   return "0123456789abcdef"[(n & 0xF)];
145 }
146 
147 #ifndef WT_TARGET_JAVA
148 static_assert(sizeof(elementNames_) / sizeof(elementNames_[0]) == static_cast<unsigned int>(Wt::DomElementType::UNKNOWN), "There should be as many element names as there are dom elements (excluding unknown and other)");
149 static_assert(sizeof(defaultInline_) / sizeof(defaultInline_[0]) == static_cast<unsigned int>(Wt::DomElementType::UNKNOWN), "defaultInline_ should be the same size as the number of dom elements (excluding unknown and other)");
150 #endif // WT_TARGET_JAVA
151 
152 }
153 
154 namespace Wt {
155 
156 #if defined(WT_THREADED) || defined(WT_TARGET_JAVA)
157   std::atomic<unsigned> DomElement::nextId_(0);
158 #else
159   unsigned DomElement::nextId_ = 0;
160 #endif
161 
createNew(DomElementType type)162 DomElement *DomElement::createNew(DomElementType type)
163 {
164   DomElement *e = new DomElement(Mode::Create, type);
165   return e;
166 }
167 
getForUpdate(const std::string & id,DomElementType type)168 DomElement *DomElement::getForUpdate(const std::string& id,
169 				     DomElementType type)
170 {
171   if (id.empty())
172     throw WException("Cannot update widget without id");
173 
174   DomElement *e = new DomElement(Mode::Update, type);
175   e->id_ = id;
176 
177   return e;
178 }
179 
updateGiven(const std::string & var,DomElementType type)180 DomElement *DomElement::updateGiven(const std::string& var,
181 				    DomElementType type)
182 {
183   DomElement *e = new DomElement(Mode::Update, type);
184   e->var_ = var;
185 
186   return e;
187 }
188 
getForUpdate(const WObject * object,DomElementType type)189 DomElement *DomElement::getForUpdate(const WObject *object,
190 				     DomElementType type)
191 {
192   return getForUpdate(object->id(), type);
193 }
194 
DomElement(Mode mode,DomElementType type)195 DomElement::DomElement(Mode mode, DomElementType type)
196   : mode_(mode),
197     wasEmpty_(mode_ == Mode::Create),
198     removeAllChildren_(-1),
199     minMaxSizeProperties_(false),
200     unstubbed_(false),
201     unwrapped_(false),
202     replaced_(nullptr),
203     insertBefore_(nullptr),
204     type_(type),
205     numManipulations_(0),
206     timeOut_(-1),
207     timeOutJSRepeat_(-1),
208     globalUnfocused_(false)
209 { }
210 
~DomElement()211 DomElement::~DomElement()
212 {
213   for (unsigned i = 0; i < childrenToAdd_.size(); ++i)
214     delete childrenToAdd_[i].child;
215 
216   for (unsigned i = 0; i < updatedChildren_.size(); ++i)
217     delete updatedChildren_[i];
218 
219   delete replaced_;
220   delete insertBefore_;
221 }
222 
setDomElementTagName(const std::string & name)223 void DomElement::setDomElementTagName(const std::string& name) {
224   this->elementTagName_ = name;
225 }
226 
227 #ifndef WT_TARGET_JAVA
228 #define toChar(b) char(b)
229 #else
toChar(int b)230 unsigned char toChar(int b) {
231   return (unsigned char)b;
232 }
233 #endif
234 
urlEncodeS(const std::string & url,const std::string & allowed)235 std::string DomElement::urlEncodeS(const std::string& url,
236                                    const std::string &allowed)
237 {
238   WStringStream result;
239 
240 #ifdef WT_TARGET_JAVA
241   std::vector<unsigned char> bytes;
242   try {
243     bytes = url.getBytes("UTF-8");
244   } catch (UnsupportedEncodingException& e) {
245     // eat silly UnsupportedEncodingException
246   }
247 #else
248   const std::string& bytes = url;
249 #endif
250 
251   for (unsigned i = 0; i < bytes.size(); ++i) {
252     unsigned char c = toChar(bytes[i]);
253     if (c <= 31 || c >= 127 || unsafeChars_.find(c) != std::string::npos) {
254       if (allowed.find(c) != std::string::npos) {
255         result << (char)c;
256       } else {
257         result << '%';
258 	result << hexLookup(c >> 4);
259         result << hexLookup(c);
260       }
261     } else
262       result << (char)c;
263   }
264 
265   return result.str();
266 }
267 
urlEncodeS(const std::string & url)268 std::string DomElement::urlEncodeS(const std::string& url)
269 {
270   return urlEncodeS(url, std::string());
271 }
272 
setType(DomElementType type)273 void DomElement::setType(DomElementType type)
274 {
275   type_ = type;
276 }
277 
setWasEmpty(bool how)278 void DomElement::setWasEmpty(bool how)
279 {
280   wasEmpty_ = how;
281 }
282 
updateInnerHtmlOnly()283 void DomElement::updateInnerHtmlOnly()
284 {
285   mode_ = Mode::Update;
286 
287   assert(replaced_ == nullptr);
288   assert(insertBefore_ == nullptr);
289 
290   attributes_.clear();
291   removedAttributes_.clear();
292   eventHandlers_.clear();
293 
294   for (PropertyMap::iterator i = properties_.begin(); i != properties_.end();) {
295     if (   i->first == Property::InnerHTML
296 	|| i->first == Property::Target)
297       ++i;
298     else
299       Utils::eraseAndNext(properties_, i);
300   }
301 }
302 
addChild(DomElement * child)303 void DomElement::addChild(DomElement *child)
304 {
305   if (child->mode() == Mode::Create) {
306     numManipulations_ += 2; // cannot be short-cutted
307 
308     if (wasEmpty_ && canWriteInnerHTML(WApplication::instance())) {
309       child->asHTML(childrenHtml_, javaScript_, timeouts_);
310       delete child;
311     } else {
312       childrenToAdd_.push_back(ChildInsertion(-1, child));
313     }
314   } else
315     updatedChildren_.push_back(child);
316 }
317 
saveChild(const std::string & id)318 void DomElement::saveChild(const std::string& id)
319 {
320   childrenToSave_.push_back(id);
321 }
322 
setAttribute(const std::string & attribute,const std::string & value)323 void DomElement::setAttribute(const std::string& attribute,
324 			      const std::string& value)
325 {
326   ++numManipulations_;
327   attributes_[attribute] = value;
328   removedAttributes_.erase(attribute);
329 }
330 
getAttribute(const std::string & attribute)331 std::string DomElement::getAttribute(const std::string& attribute) const
332 {
333   AttributeMap::const_iterator i = attributes_.find(attribute);
334   if (i != attributes_.end())
335     return i->second;
336   else
337     return std::string();
338 }
339 
removeAttribute(const std::string & attribute)340 void DomElement::removeAttribute(const std::string& attribute)
341 {
342   ++numManipulations_;
343   attributes_.erase(attribute);
344   removedAttributes_.insert(attribute);
345 }
346 
setEventSignal(const char * eventName,const EventSignalBase & signal)347 void DomElement::setEventSignal(const char *eventName,
348 				const EventSignalBase& signal)
349 {
350   setEvent(eventName, signal.javaScript(),
351 	   signal.encodeCmd(), signal.isExposedSignal());
352 }
353 
setEvent(const char * eventName,const std::string & jsCode,const std::string & signalName,bool isExposed)354 void DomElement::setEvent(const char *eventName,
355 			  const std::string& jsCode,
356 			  const std::string& signalName,
357 			  bool isExposed)
358 {
359   WApplication *app = WApplication::instance();
360 
361   bool anchorClick = type() == DomElementType::A
362     && eventName == WInteractWidget::CLICK_SIGNAL;
363 
364   WStringStream js;
365   if (isExposed || anchorClick || !jsCode.empty()) {
366     js << "var e=event||window.event,";
367     js << "o=this;";
368 
369     if (anchorClick)
370       js << "if(e.ctrlKey||e.metaKey||(" WT_CLASS ".button(e) > 1))"
371 	"return true;else{";
372 
373     /*
374      * This order, first JavaScript and then event propagation is important
375      * for WCheckBox where the tristate state is cleared before propagating
376      * its value
377      */
378     js << jsCode;
379 
380     if (isExposed)
381       js << app->javaScriptClass() << "._p_.update(o,'"
382 	 << signalName << "',e,true);";
383 
384     if (anchorClick)
385       js << "}";
386   }
387 
388   ++numManipulations_;
389   eventHandlers_[eventName] = EventHandler(js.str(), signalName);
390 }
391 
setEvent(const char * eventName,const std::string & jsCode)392 void DomElement::setEvent(const char *eventName, const std::string& jsCode)
393 {
394   eventHandlers_[eventName] = EventHandler(jsCode, std::string());
395 }
396 
addEvent(const char * eventName,const std::string & jsCode)397 void DomElement::addEvent(const char *eventName, const std::string& jsCode)
398 {
399   eventHandlers_[eventName].jsCode += jsCode;
400 }
401 
EventAction(const std::string & aJsCondition,const std::string & aJsCode,const std::string & anUpdateCmd,bool anExposed)402 DomElement::EventAction::EventAction(const std::string& aJsCondition,
403 				     const std::string& aJsCode,
404 				     const std::string& anUpdateCmd,
405 				     bool anExposed)
406   : jsCondition(aJsCondition),
407     jsCode(aJsCode),
408     updateCmd(anUpdateCmd),
409     exposed(anExposed)
410 { }
411 
setEvent(const char * eventName,const std::vector<EventAction> & actions)412 void DomElement::setEvent(const char *eventName,
413 			  const std::vector<EventAction>& actions)
414 {
415   WStringStream code;
416 
417   for (unsigned i = 0; i < actions.size(); ++i) {
418     if (!actions[i].jsCondition.empty())
419       code << "if(" << actions[i].jsCondition << "){";
420 
421     /*
422      * This order, first JavaScript and then event propagation is important
423      * for WCheckBox where the tristate state is cleared before propagating
424      * its value
425      */
426     code << actions[i].jsCode;
427 
428     if (actions[i].exposed)
429       code << WApplication::instance()->javaScriptClass()
430 	   << "._p_.update(o,'" << actions[i].updateCmd << "',e,true);";
431 
432     if (!actions[i].jsCondition.empty())
433       code << "}";
434   }
435 
436   setEvent(eventName, code.str(), "");
437 }
438 
processProperties(WApplication * app)439 void DomElement::processProperties(WApplication *app) const
440 {
441   if (minMaxSizeProperties_
442       && app->environment().agent() == UserAgent::IE6) {
443     DomElement *self = const_cast<DomElement *>(this);
444 
445     PropertyMap::iterator w = self->properties_.find(Property::StyleWidth);
446     PropertyMap::iterator minw = self->properties_.find(Property::StyleMinWidth);
447     PropertyMap::iterator maxw = self->properties_.find(Property::StyleMaxWidth);
448 
449     if (minw != self->properties_.end() || maxw != self->properties_.end()) {
450       if (w == self->properties_.end()) {
451 	WStringStream expr;
452 	expr << WT_CLASS ".IEwidth(this,";
453 	if (minw != self->properties_.end()) {
454 	  expr << '\'' << minw->second << '\'';
455           self->properties_.erase(Property::StyleMinWidth); // C++: could be minw
456 	} else
457 	  expr << "'0px'";
458 	expr << ',';
459 	if (maxw != self->properties_.end()) {
460 	  expr << '\''<< maxw->second << '\'';
461 	  self->properties_.erase(Property::StyleMaxWidth); // C++: could be maxw
462 	} else
463 	  expr << "'100000px'";
464 	expr << ")";
465 
466 	self->properties_.erase(Property::StyleWidth);
467 	self->properties_[Property::StyleWidthExpression] = expr.str();
468       }
469     }
470 
471     PropertyMap::iterator i = self->properties_.find(Property::StyleMinHeight);
472 
473     if (i != self->properties_.end()) {
474       self->properties_[Property::StyleHeight] = i->second;
475     }
476   }
477 }
478 
processEvents(WApplication * app)479 void DomElement::processEvents(WApplication *app) const
480 {
481   DomElement *self = const_cast<DomElement *>(this);
482 
483   const char *S_keypress = WInteractWidget::KEYPRESS_SIGNAL;
484 
485   EventHandlerMap::const_iterator keypress = eventHandlers_.find(S_keypress);
486   if (keypress != eventHandlers_.end() && !keypress->second.jsCode.empty())
487     Utils::access(self->eventHandlers_, S_keypress).jsCode
488       = "if (" WT_CLASS ".isKeyPress(event)){"
489       + Utils::access(self->eventHandlers_, S_keypress).jsCode
490       + '}';
491 }
492 
setTimeout(int msec,bool jsRepeat)493 void DomElement::setTimeout(int msec, bool jsRepeat)
494 {
495   ++numManipulations_;
496   timeOut_ = msec;
497   timeOutJSRepeat_ = jsRepeat ? msec : -1;
498 }
499 
setTimeout(int delay,int interval)500 void DomElement::setTimeout(int delay, int interval)
501 {
502   ++numManipulations_;
503   timeOut_ = delay;
504   timeOutJSRepeat_ = interval;
505 }
506 
callJavaScript(const std::string & jsCode,bool evenWhenDeleted)507 void DomElement::callJavaScript(const std::string& jsCode,
508 				bool evenWhenDeleted)
509 {
510   ++numManipulations_;
511   if (!evenWhenDeleted)
512     javaScript_ << jsCode << '\n';
513   else
514     javaScriptEvenWhenDeleted_ += jsCode;
515 }
516 
setProperties(const PropertyMap & properties)517 void DomElement::setProperties(const PropertyMap& properties)
518 {
519   for (PropertyMap::const_iterator i = properties.begin();
520        i != properties.end(); ++i)
521     setProperty(i->first, i->second);
522 }
523 
clearProperties()524 void DomElement::clearProperties()
525 {
526   numManipulations_ -= properties_.size();
527   properties_.clear();
528 }
529 
setProperty(Property property,const std::string & value)530 void DomElement::setProperty(Property property, const std::string& value)
531 {
532   ++numManipulations_;
533   properties_[property] = value;
534 
535   if (property >= Property::StyleMinWidth && property <= Property::StyleMaxHeight)
536     minMaxSizeProperties_ = true;
537 }
538 
addPropertyWord(Property property,const std::string & value)539 void DomElement::addPropertyWord(Property property, const std::string& value)
540 {
541   PropertyMap::const_iterator i = properties_.find(property);
542 
543   if (i != properties_.end()) {
544     Utils::SplitSet words;
545     Utils::split(words, i->second, " ", true);
546     if (words.find(value) != words.end())
547       return;
548   }
549 
550   setProperty(property, Utils::addWord(getProperty(property), value));
551 }
552 
getProperty(Property property)553 std::string DomElement::getProperty(Property property) const
554 {
555   PropertyMap::const_iterator i = properties_.find(property);
556 
557   if (i != properties_.end())
558     return i->second;
559   else
560     return std::string();
561 }
562 
removeProperty(Property property)563 void DomElement::removeProperty(Property property)
564 {
565   properties_.erase(property);
566 }
567 
setId(const std::string & id)568 void DomElement::setId(const std::string& id)
569 {
570   ++numManipulations_;
571   id_ = id;
572 }
573 
setName(const std::string & name)574 void DomElement::setName(const std::string& name)
575 {
576   ++numManipulations_;
577   id_ = name;
578   setAttribute("name", name);
579 }
580 
insertChildAt(DomElement * child,int pos)581 void DomElement::insertChildAt(DomElement *child, int pos)
582 {
583   ++numManipulations_;
584 
585   childrenToAdd_.push_back(ChildInsertion(pos, child));
586 }
587 
insertBefore(DomElement * sibling)588 void DomElement::insertBefore(DomElement *sibling)
589 {
590   ++numManipulations_;
591   insertBefore_ = sibling;
592 }
593 
removeFromParent()594 void DomElement::removeFromParent()
595 {
596   callJavaScript(WT_CLASS ".remove('" + id() + "');", true);
597 }
598 
removeAllChildren(int firstChild)599 void DomElement::removeAllChildren(int firstChild)
600 {
601   ++numManipulations_;
602   removeAllChildren_ = firstChild;
603   wasEmpty_ = firstChild == 0;
604 }
605 
replaceWith(DomElement * newElement)606 void DomElement::replaceWith(DomElement *newElement)
607 {
608   ++numManipulations_;
609   replaced_ = newElement;
610 }
611 
unstubWith(DomElement * newElement,bool hideWithDisplay)612 void DomElement::unstubWith(DomElement *newElement, bool hideWithDisplay)
613 {
614   replaceWith(newElement);
615   unstubbed_ = true;
616   hideWithDisplay_ = hideWithDisplay;
617 }
618 
unwrap()619 void DomElement::unwrap()
620 {
621   ++numManipulations_;
622   unwrapped_ = true;
623 }
624 
callMethod(const std::string & method)625 void DomElement::callMethod(const std::string& method)
626 {
627   ++numManipulations_;
628 
629   if (var_.empty())
630     javaScript_ << WT_CLASS << ".$('" << id_ << "').";
631   else
632     javaScript_ << var_ << '.';
633 
634   javaScript_ << method << ";\n";
635 }
636 
jsStringLiteral(WStringStream & out,const std::string & s,char delimiter)637 void DomElement::jsStringLiteral(WStringStream& out, const std::string& s,
638 				 char delimiter)
639 {
640   EscapeOStream sout(out);
641   jsStringLiteral(sout, s, delimiter);
642 }
643 
htmlAttributeValue(WStringStream & out,const std::string & s)644 void DomElement::htmlAttributeValue(WStringStream& out, const std::string& s)
645 {
646   EscapeOStream sout(out);
647   sout.pushEscape(EscapeOStream::HtmlAttribute);
648   sout << s;
649 }
650 
fastJsStringLiteral(EscapeOStream & outRaw,const EscapeOStream & outEscaped,const std::string & s)651 void DomElement::fastJsStringLiteral(EscapeOStream& outRaw,
652 				     const EscapeOStream& outEscaped,
653 				     const std::string& s)
654 {
655   outRaw << '\'';
656   outRaw.append(s, outEscaped);
657   outRaw << '\'';
658 }
659 
jsStringLiteral(EscapeOStream & out,const std::string & s,char delimiter)660 void DomElement::jsStringLiteral(EscapeOStream& out, const std::string& s,
661 				 char delimiter)
662 {
663   out << delimiter;
664 
665   out.pushEscape(delimiter == '\'' ?
666 		 EscapeOStream::JsStringLiteralSQuote :
667 		 EscapeOStream::JsStringLiteralDQuote);
668   out << s;
669   out.popEscape();
670 
671   out << delimiter;
672 }
673 
fastHtmlAttributeValue(EscapeOStream & outRaw,const EscapeOStream & outEscaped,const std::string & s)674 void DomElement::fastHtmlAttributeValue(EscapeOStream& outRaw,
675 					const EscapeOStream& outEscaped,
676 					const std::string& s)
677 {
678   outRaw << '"';
679   outRaw.append(s, outEscaped);
680   outRaw << '"';
681 }
682 
cssStyle()683 std::string DomElement::cssStyle() const
684 {
685   if (properties_.empty())
686     return std::string();
687 
688   EscapeOStream style;
689   const std::string *styleProperty = nullptr;
690 
691   for (PropertyMap::const_iterator j = properties_.begin();
692        j != properties_.end(); ++j) {
693     unsigned p = static_cast<unsigned int>(j->first);
694 
695     if (j->first == Property::Style)
696       styleProperty = &j->second;
697     else if ((p >= static_cast<unsigned int>(Property::StylePosition)) &&
698 	     (p < static_cast<unsigned int>(Property::LastPlusOne))) {
699       if ((j->first == Property::StyleCursor) && (j->second == "pointer")) {
700 	style << "cursor:pointer;cursor:hand;";
701       } else {
702 	if (!j->second.empty()) {
703 	  style << cssNames_[p -
704 			     static_cast<unsigned int>(Property::StylePosition)]
705 		<< ':' << j->second << ';';
706 	  if (p >= static_cast<unsigned int>(Property::StyleBoxSizing)) {
707 	    WApplication *app = WApplication::instance();
708 
709 	    if (app) {
710 	      if (app->environment().agentIsGecko())
711 		style << "-moz-";
712 	      else if (app->environment().agentIsWebKit())
713 		style << "-webkit-";
714 	    }
715 
716 	    style << cssNames_[p -
717 			       static_cast<unsigned int>(Property::StylePosition)]
718 		  << ':' << j->second << ';';
719 	  }
720 	}
721       }
722     } else if (j->first == Property::StyleWidthExpression) {
723       style << "width:expression(" << j->second << ");";
724     }
725   }
726 
727   if (styleProperty)
728     style << *styleProperty;
729 
730   return style.c_str();
731 }
732 
setJavaScriptEvent(EscapeOStream & out,const char * eventName,const EventHandler & handler,WApplication * app)733 void DomElement::setJavaScriptEvent(EscapeOStream& out,
734 				    const char *eventName,
735 				    const EventHandler& handler,
736 				    WApplication *app) const
737 {
738   // events on the dom root container are events received by the whole
739   // document when no element has focus
740 
741   unsigned fid = nextId_++;
742 
743   out << "function f" << fid << "(event) { ";
744 
745   out << handler.jsCode;
746 
747   out << "}\n";
748 
749   if (globalUnfocused_) {
750     out << app->javaScriptClass()
751       <<  "._p_.bindGlobal('" << std::string(eventName) <<"', '" << id_ << "', f" << fid
752       << ")\n";
753     return;
754   } else {
755     declare(out);
756     out << var_;
757   }
758 
759   if (eventName == WInteractWidget::WHEEL_SIGNAL &&
760       app->environment().agentIsIE() &&
761       static_cast<unsigned int>(app->environment().agent()) >=
762       static_cast<unsigned int>(UserAgent::IE9))
763     out << ".addEventListener('wheel', f" << fid << ", false);\n";
764   else
765     out << ".on" << const_cast<char *>(eventName) << "=f" << fid << ";\n";
766 }
767 
asHTML(EscapeOStream & out,EscapeOStream & javaScript,std::vector<TimeoutEvent> & timeouts,bool openingTagOnly)768 void DomElement::asHTML(EscapeOStream& out,
769 			EscapeOStream& javaScript,
770 			std::vector<TimeoutEvent>& timeouts,
771 			bool openingTagOnly) const
772 {
773   if (mode_ != Mode::Create)
774     throw WException("DomElement::asHTML() called with ModeUpdate");
775 
776   WApplication *app = WApplication::instance();
777   processEvents(app);
778   processProperties(app);
779 
780   EventHandlerMap::const_iterator clickEvent
781     = eventHandlers_.find(WInteractWidget::CLICK_SIGNAL);
782 
783   bool needButtonWrap
784     = (!app->environment().ajax()
785        && (clickEvent != eventHandlers_.end())
786        && (!clickEvent->second.signalName.empty())
787        && (!app->environment().agentIsSpiderBot()));
788 
789   bool isSubmit = needButtonWrap;
790   DomElementType renderedType = type_;
791 
792   if (needButtonWrap) {
793     if (type_ == DomElementType::BUTTON) {
794       /*
795        * We don't need to wrap a button: we can just modify the attributes
796        * type name and value. This avoid layout problems.
797        *
798        * Note that IE posts the button text instead of the value. We fix
799        * this by encoding the value into the name.
800        *
801        * IE6 hell: IE will post all submit buttons, not just the one clicked.
802        * We should therefore really be using input
803        */
804       DomElement *self = const_cast<DomElement *>(this);
805       self->setAttribute("type", "submit");
806       self->setAttribute("name", "signal=" + clickEvent->second.signalName);
807 
808       needButtonWrap = false;
809     } else if (type_ == DomElementType::IMG) {
810       /*
811        * We don't need to wrap an image: we can substitute it for an input
812        * type image. This avoid layout problems.
813        */
814       renderedType = DomElementType::INPUT;
815 
816       DomElement *self = const_cast<DomElement *>(this);
817       self->setAttribute("type", "image");
818       self->setAttribute("name", "signal=" + clickEvent->second.signalName);
819       needButtonWrap = false;
820     }
821   }
822 
823   /*
824    * We also should not wrap anchors, map area elements and form elements.
825    */
826   if (needButtonWrap) {
827     if (   type_ == DomElementType::AREA
828 	|| type_ == DomElementType::INPUT
829 	|| type_ == DomElementType::SELECT)
830       needButtonWrap = false;
831 
832     if (type_ == DomElementType::A) {
833       std::string href = getAttribute("href");
834 
835       /*
836        * If we're IE7/8 or there is a real URL, then we don't wrap
837        */
838       if (app->environment().agent() == UserAgent::IE7 ||
839 	  app->environment().agent() == UserAgent::IE8 ||
840 	  href.length() > 1)
841 	needButtonWrap = false;
842       else if (app->theme()->canStyleAnchorAsButton()) {
843 	DomElement *self = const_cast<DomElement *>(this);
844 	self->setAttribute("href", app->url(app->internalPath())
845 			   + "&signal=" + clickEvent->second.signalName);
846 	needButtonWrap = false;
847       }
848     } else if (type_ == DomElementType::AREA) {
849       DomElement *self = const_cast<DomElement *>(this);
850       self->setAttribute("href", app->url(app->internalPath())
851 			 + "&signal=" + clickEvent->second.signalName);
852     }
853   }
854 
855   const bool supportButton = true;
856 
857   bool needAnchorWrap = false;
858 
859   if (!supportButton && type_ == DomElementType::BUTTON) {
860     renderedType = DomElementType::INPUT;
861 
862     DomElement *self = const_cast<DomElement *>(this);
863     if (!isSubmit)
864       self->setAttribute("type", "button");
865     self->setAttribute("value",
866 		       properties_.find(Property::InnerHTML)->second);
867     self->setProperty(Property::InnerHTML, "");
868   }
869 
870 #ifndef WT_TARGET_JAVA
871   EscapeOStream attributeValues(out);
872 #else // WT_TARGET_JAVA
873   EscapeOStream attributeValues = out.push();
874 #endif // WT_TARGET_JAVA
875   attributeValues.pushEscape(EscapeOStream::HtmlAttribute);
876 
877   std::string style;
878 
879   if (needButtonWrap) {
880     if (supportButton) {
881       out << "<button type=\"submit\" name=\"signal=";
882       out.append(clickEvent->second.signalName, attributeValues);
883       out << "\" class=\"Wt-wrap ";
884 
885       PropertyMap::const_iterator l = properties_.find(Property::Class);
886       if (l != properties_.end()) {
887 	out << l->second;
888 	PropertyMap& map = const_cast<PropertyMap&>(properties_);
889 	map.erase(Property::Class);
890       }
891 
892       out << '"';
893 
894       std::string wrapStyle = cssStyle();
895       if (!isDefaultInline()) {
896 	// Put display: block; first, because it might
897 	// still be overridden if a widget is set to be inlined,
898 	// but isn't inline by default.
899 	wrapStyle = "display: block;" + wrapStyle;
900       }
901 
902       if (!wrapStyle.empty()) {
903 	out << " style=";
904 	fastHtmlAttributeValue(out, attributeValues, wrapStyle);
905       }
906 
907       PropertyMap::const_iterator i = properties_.find(Property::Disabled);
908       if ((i != properties_.end()) && (i->second=="true"))
909 	out << " disabled=\"disabled\"";
910 
911       for (AttributeMap::const_iterator j = attributes_.begin();
912 	   j != attributes_.end(); ++j)
913 	if (j->first == "title") {
914 	  out << ' ' << j->first << '=';
915 	  fastHtmlAttributeValue(out, attributeValues, j->second);
916 	}
917 
918       if (app->environment().agent() != UserAgent::Konqueror
919 	  && !app->environment().agentIsWebKit()
920 	  && !app->environment().agentIsIE())
921 	style = "margin: 0px -3px -2px -3px;";
922 
923       out << "><" << elementNames_[static_cast<unsigned int>(renderedType)];
924     } else {
925       if (type_ == DomElementType::IMG)
926 	out << "<input type=\"image\"";
927       else
928 	out << "<input type=\"submit\"";
929 
930       out << " name=";
931       fastHtmlAttributeValue(out, attributeValues,
932 			     "signal=" + clickEvent->second.signalName);
933       out << " value=";
934 
935       PropertyMap::const_iterator i = properties_.find(Property::InnerHTML);
936       if (i != properties_.end())
937 	fastHtmlAttributeValue(out, attributeValues, i->second);
938       else
939 	out << "\"\"";
940     }
941   } else if (needAnchorWrap) {
942     out << "<a href=\"#\" class=\"Wt-wrap\" onclick=";
943     fastHtmlAttributeValue(out, attributeValues, clickEvent->second.jsCode);
944     out << "><" << elementNames_[static_cast<unsigned int>(renderedType)];
945   } else if (renderedType == DomElementType::OTHER)  // Custom tag name
946 	out << '<' << elementTagName_;
947   else
948     out << '<' << elementNames_[static_cast<unsigned int>(renderedType)];
949 
950   if (!id_.empty()) {
951     out << " id=";
952     fastHtmlAttributeValue(out, attributeValues, id_);
953   }
954 
955   for (AttributeMap::const_iterator i = attributes_.begin();
956        i != attributes_.end(); ++i)
957     if (!app->environment().agentIsSpiderBot() || i->first != "name") {
958       out << ' ' << i->first << '=';
959       fastHtmlAttributeValue(out, attributeValues, i->second);
960     }
961 
962   if (app->environment().ajax()) {
963     for (EventHandlerMap::const_iterator i = eventHandlers_.begin();
964 	 i != eventHandlers_.end(); ++i) {
965       if (!i->second.jsCode.empty()) {
966         if (globalUnfocused_
967 	    || (i->first == WInteractWidget::WHEEL_SIGNAL &&
968 		app->environment().agentIsIE() &&
969 		static_cast<unsigned int>(app->environment().agent()) >=
970 		static_cast<unsigned int>(UserAgent::IE9)))
971 	  setJavaScriptEvent(javaScript, i->first, i->second, app);
972 	else {
973 	  out << " on" << const_cast<char *>(i->first) << '=';
974 	  fastHtmlAttributeValue(out, attributeValues, i->second.jsCode);
975 	}
976       }
977     }
978   }
979 
980   std::string innerHTML = "";
981 
982   for (PropertyMap::const_iterator i = properties_.begin();
983        i != properties_.end(); ++i) {
984     switch (i->first) {
985     case Property::InnerHTML:
986       innerHTML += i->second; break;
987     case Property::Disabled:
988       if (i->second == "true")
989 	out << " disabled=\"disabled\"";
990       break;
991     case Property::ReadOnly:
992       if (i->second == "true")
993 	out << " readonly=\"readonly\"";
994       break;
995     case Property::TabIndex:
996       out << " tabindex=\"" << i->second << '"';
997       break;
998     case Property::Checked:
999       if (i->second == "true")
1000 	out << " checked=\"checked\"";
1001       break;
1002     case Property::Selected:
1003       if (i->second == "true")
1004 	out << " selected=\"selected\"";
1005       break;
1006     case Property::SelectedIndex:
1007       if (i->second == "-1") {
1008 	DomElement *self = const_cast<DomElement *>(this);
1009 	self->callMethod("selectedIndex=-1");
1010       }
1011       break;
1012     case Property::Multiple:
1013       if (i->second == "true")
1014 	out << " multiple=\"multiple\"";
1015       break;
1016     case Property::Target:
1017       out << " target=\"" << i->second << "\"";
1018       break;
1019 	case Property::Download:
1020 	  out << " download=\"" << i->second << "\"";
1021 	  break;
1022     case Property::Indeterminate:
1023       if (i->second == "true") {
1024 	DomElement *self = const_cast<DomElement *>(this);
1025 	self->callMethod("indeterminate=" + i->second);
1026       }
1027       break;
1028     case Property::Value:
1029       if (type_ != DomElementType::TEXTAREA) {
1030 	out << " value=";
1031 	fastHtmlAttributeValue(out, attributeValues, i->second);
1032       } else {
1033 	std::string v = i->second;
1034   innerHTML += WWebWidget::escapeText(v, false);
1035       }
1036       break;
1037     case Property::Src:
1038       out << " src=";
1039       fastHtmlAttributeValue(out, attributeValues, i->second);
1040       break;
1041     case Property::ColSpan:
1042       out << " colspan=";
1043       fastHtmlAttributeValue(out, attributeValues, i->second);
1044       break;
1045     case Property::RowSpan:
1046       out << " rowspan=";
1047       fastHtmlAttributeValue(out, attributeValues, i->second);
1048       break;
1049     case Property::Class:
1050       out << " class=";
1051       fastHtmlAttributeValue(out, attributeValues, i->second);
1052       break;
1053     case Property::Label:
1054       out << " label=";
1055       fastHtmlAttributeValue(out, attributeValues, i->second);
1056       break;
1057     case Property::Placeholder:
1058       out << " placeholder=";
1059       fastHtmlAttributeValue(out, attributeValues, i->second);
1060       break;
1061     default:
1062       break;
1063     }
1064   }
1065 
1066   if (!needButtonWrap)
1067     style += cssStyle();
1068 
1069   if (!style.empty()) {
1070     out << " style=";
1071     fastHtmlAttributeValue(out, attributeValues, style);
1072   }
1073 
1074   if (needButtonWrap && !supportButton)
1075     out << " />";
1076   else {
1077     if (openingTagOnly) {
1078       out << '>';
1079       return;
1080     }
1081 
1082     /*
1083      * http://www.w3.org/TR/html/#guidelines
1084      * XHTML recommendation, back-wards compatibility with HTML: C.2, C.3:
1085      * do not use minimized forms when content is empty like <p />, and use
1086      * minimized forms for certain elements like <br />
1087      */
1088     if (!isSelfClosingTag(renderedType)) {
1089       out << '>';
1090       for (unsigned i = 0; i < childrenToAdd_.size(); ++i)
1091 	childrenToAdd_[i].child->asHTML(out, javaScript, timeouts);
1092 
1093       out << innerHTML; // for WPushButton must be after childrenToAdd_
1094 
1095       out << childrenHtml_.str();
1096 
1097       // IE6 will incorrectly set the height of empty divs
1098       if (renderedType == DomElementType::DIV
1099 	  && app->environment().agent() == UserAgent::IE6
1100 	  && innerHTML.empty()
1101 	  && childrenToAdd_.empty()
1102 	  && childrenHtml_.empty())
1103 	out << "&nbsp;";
1104 	  if (renderedType  == DomElementType::OTHER) // Custom tag name
1105 	    out << "</" << elementTagName_ << ">";
1106 	  else
1107 	    out << "</" << elementNames_[static_cast<unsigned int>(renderedType)]
1108 		<< ">";
1109     } else
1110       out << " />";
1111 
1112     if (needButtonWrap && supportButton)
1113       out << "</button>";
1114     else if (needAnchorWrap)
1115       out << "</a>";
1116   }
1117 
1118   javaScript << javaScriptEvenWhenDeleted_ << javaScript_;
1119 
1120   if (timeOut_ != -1)
1121     timeouts.push_back(TimeoutEvent(timeOut_, id_, timeOutJSRepeat_));
1122 
1123   Utils::insert(timeouts, timeouts_);
1124 }
1125 
createVar()1126 std::string DomElement::createVar() const
1127 {
1128 #ifndef WT_TARGET_JAVA
1129   char buf[20];
1130   std::sprintf(buf, "j%u", nextId_++);
1131   var_ = buf;
1132 #else // !WT_TARGET_JAVA
1133   var_ = "j" + std::to_string(nextId_++);
1134 #endif // !WT_TARGET_JAVA
1135 
1136   return var_;
1137 }
1138 
declare(EscapeOStream & out)1139 void DomElement::declare(EscapeOStream& out) const
1140 {
1141   if (var_.empty())
1142     out << "var " << createVar() << "=" WT_CLASS ".$('" << id_ << "');\n";
1143 }
1144 
canWriteInnerHTML(WApplication * app)1145 bool DomElement::canWriteInnerHTML(WApplication *app) const
1146 {
1147   /*
1148    * http://lists.apple.com/archives/web-dev/2004/Apr/msg00122.html
1149    * "The problem is not that innerHTML doesn't work (it works fine),
1150    *  but that Safari can't handle writing the innerHTML of a <tbody> tag.
1151    *  If you write the entire table (including <table> and <tbody>) in the
1152    *  innerHTML string it works fine.
1153    */
1154   /* http://msdn.microsoft.com/workshop/author/tables/buildtables.asp
1155    * Note When using Dynamic HTML (DHTML) to create a document, you can
1156    * create objects and set the innerText or innerHTML property of the object.
1157    * However, because of the specific structure required by tables,
1158    * the innerText and innerHTML properties of the table and tr objects are
1159    * read-only.
1160    */
1161   /* http://support.microsoft.com/kb/276228
1162    * BUG: Internet Explorer Fails to Set the innerHTML Property of the
1163    * SelectionFlag::Select Object. Seems to affect at least up to IE6.0
1164    */
1165   if ((app->environment().agentIsIE()
1166        || app->environment().agent() == UserAgent::Konqueror)
1167       && (   type_ == DomElementType::TBODY
1168 	  || type_ == DomElementType::THEAD
1169 	  || type_ == DomElementType::TABLE
1170 	  || type_ == DomElementType::COLGROUP
1171 	  || type_ == DomElementType::TR
1172 	  || type_ == DomElementType::SELECT
1173 	  || type_ == DomElementType::TD
1174 	  || type_ == DomElementType::OPTGROUP))
1175     return false;
1176 
1177   return true;
1178 }
1179 
1180 #if 0
1181 bool DomElement::containsElement(DomElementType type) const
1182 {
1183   for (unsigned i = 0; i < childrenToAdd_.size(); ++i) {
1184     if (childrenToAdd_[i].child->type_ == type)
1185       return true;
1186     if (childrenToAdd_[i].child->containsElement(type))
1187       return true;
1188   }
1189 
1190   return false;
1191 }
1192 #endif
1193 
asJavaScript(WStringStream & out)1194 void DomElement::asJavaScript(WStringStream& out)
1195 {
1196   mode_ = Mode::Update;
1197 
1198   EscapeOStream eout(out);
1199 
1200   declare(eout);
1201   eout << var_ << ".setAttribute('id', '" << id_ << "');\n";
1202 
1203   mode_ = Mode::Create;
1204 
1205   setJavaScriptProperties(eout, WApplication::instance());
1206   setJavaScriptAttributes(eout);
1207   asJavaScript(eout, Priority::Update);
1208 }
1209 
createTimeoutJs(WStringStream & out,const TimeoutList & timeouts,WApplication * app)1210 void DomElement::createTimeoutJs(WStringStream& out,
1211 				 const TimeoutList& timeouts, WApplication *app)
1212 {
1213   for (unsigned i = 0; i < timeouts.size(); ++i)
1214     out << app->javaScriptClass()
1215 	<< "._p_.addTimerEvent('" << timeouts[i].event << "', "
1216 	<< timeouts[i].msec << ","
1217 	<< timeouts[i].repeat << ");\n";
1218 }
1219 
createElement(WStringStream & out,WApplication * app,const std::string & domInsertJS)1220 void DomElement::createElement(WStringStream& out, WApplication *app,
1221 			       const std::string& domInsertJS)
1222 {
1223   EscapeOStream sout(out);
1224   createElement(sout, app, domInsertJS);
1225 }
1226 
createElement(EscapeOStream & out,WApplication * app,const std::string & domInsertJS)1227 void DomElement::createElement(EscapeOStream& out, WApplication *app,
1228 			       const std::string& domInsertJS)
1229 {
1230   if (var_.empty())
1231     createVar();
1232 
1233   out << "var " << var_ << "=";
1234 
1235   if (app->environment().agentIsIE()
1236       && app->environment().agent() <= UserAgent::IE8
1237       && type_ != DomElementType::TEXTAREA) {
1238     /*
1239      * IE pre 9 can create the entire opening tag at once.
1240      * This rocks because it results in fewer JavaScript statements.
1241      * It also avoids problems with changing certain attributes not
1242      * working in IE.
1243      *
1244      * However, we cannot do it for TEXTAREA since there are inconsistencies
1245      * with setting its value
1246      */
1247     out << "document.createElement('";
1248     out.pushEscape(EscapeOStream::JsStringLiteralSQuote);
1249     TimeoutList timeouts;
1250     EscapeOStream dummy;
1251     asHTML(out, dummy, timeouts, true);
1252     out.popEscape();
1253     out << "');";
1254     out << domInsertJS;
1255     renderInnerHtmlJS(out, app);
1256     renderDeferredJavaScript(out);
1257   } else {
1258     out << "document.createElement('"
1259 	<< elementNames_[static_cast<unsigned int>(type_)] << "');";
1260     out << domInsertJS;
1261     asJavaScript(out, Priority::Create);
1262     asJavaScript(out, Priority::Update);
1263   }
1264 }
1265 
addToParent(WStringStream & out,const std::string & parentVar,int pos,WApplication * app)1266 std::string DomElement::addToParent(WStringStream& out,
1267 				    const std::string& parentVar,
1268 				    int pos, WApplication *app)
1269 {
1270   EscapeOStream sout(out);
1271   return addToParent(sout, parentVar, pos, app);
1272 }
1273 
addToParent(EscapeOStream & out,const std::string & parentVar,int pos,WApplication * app)1274 std::string DomElement::addToParent(EscapeOStream& out,
1275 				    const std::string& parentVar,
1276 				    int pos, WApplication *app)
1277 {
1278   createVar();
1279 
1280   if (type_ == DomElementType::TD || type_ == DomElementType::TR) {
1281     out << "var " << var_ << "=";
1282 
1283     if (type_ == DomElementType::TD)
1284       out << parentVar << ".insertCell(" << pos << ");\n";
1285     else
1286       out << parentVar << ".insertRow(" << pos << ");\n";
1287 
1288     asJavaScript(out, Priority::Create);
1289     asJavaScript(out, Priority::Update);
1290   } else {
1291     WStringStream insertJS;
1292     if (pos != -1)
1293       insertJS << WT_CLASS ".insertAt(" << parentVar << "," << var_
1294 	       << "," << pos << ");";
1295     else
1296       insertJS << parentVar << ".appendChild(" << var_ << ");\n";
1297 
1298     createElement(out, app, insertJS.str());
1299   }
1300 
1301   return var_;
1302 }
1303 
asJavaScript(EscapeOStream & out,Priority priority)1304 std::string DomElement::asJavaScript(EscapeOStream& out,
1305 				     Priority priority) const
1306 {
1307   switch(priority) {
1308   case Priority::Delete:
1309     if (!javaScriptEvenWhenDeleted_.empty() || (removeAllChildren_ >= 0)) {
1310       out << javaScriptEvenWhenDeleted_;
1311       if (removeAllChildren_ >= 0) {
1312 	declare(out);
1313 	if (removeAllChildren_ == 0)
1314 	  out << WT_CLASS << ".setHtml(" << var_ << ", '');\n";
1315 	else {
1316 	  out << "$(" << var_ << ").children(':gt(" << (removeAllChildren_ - 1)
1317 	      << ")').remove();";
1318 	}
1319       }
1320     }
1321 
1322     return var_;
1323   case Priority::Create:
1324     if (mode_ == Mode::Create) {
1325       if (!id_.empty())
1326 	out << var_ << ".setAttribute('id', '" << id_ << "');\n";
1327 
1328       setJavaScriptAttributes(out);
1329       setJavaScriptProperties(out, WApplication::instance());
1330     }
1331 
1332     return var_;
1333   case Priority::Update:
1334   {
1335     WApplication *app = WApplication::instance();
1336 
1337     bool childrenUpdated = false;
1338 
1339     /*
1340      * short-cut for frequent short manipulations
1341      */
1342     if (mode_ == Mode::Update && numManipulations_ == 1) {
1343       for (unsigned i = 0; i < updatedChildren_.size(); ++i) {
1344 	DomElement *child = updatedChildren_[i];
1345 	child->asJavaScript(out, Priority::Update);
1346       }
1347 
1348       childrenUpdated = true;
1349 
1350       if (properties_.find(Property::StyleDisplay) != properties_.end()) {
1351 	std::string style = properties_.find(Property::StyleDisplay)->second;
1352 	if (style == "none") {
1353 	  out << WT_CLASS ".hide('" << id_ << "');\n";
1354 	  return var_;
1355 	} else if (style == "inline") {
1356 	  out << WT_CLASS ".inline('" + id_ + "');\n";
1357 	  return var_;
1358 	} else if (style == "block") {
1359 	  out << WT_CLASS ".block('" + id_ + "');\n";
1360 	  return var_;
1361 	} else {
1362 	  out << WT_CLASS ".show('" << id_ << "', '" << style << "');\n";
1363 	  return var_;
1364 	}
1365       } else if (!javaScript_.empty()) {
1366 	out << javaScript_;
1367 	return var_;
1368       }
1369     }
1370 
1371     if (unwrapped_)
1372       out << WT_CLASS ".unwrap('" << id_ << "');\n";
1373 
1374     processEvents(app);
1375     processProperties(app);
1376 
1377     if (replaced_) {
1378       declare(out);
1379 
1380       std::string varr = replaced_->createVar();
1381       WStringStream insertJs;
1382       insertJs << var_ << ".parentNode.replaceChild("
1383 	       << varr << ',' << var_ << ");\n";
1384       replaced_->createElement(out, app, insertJs.str());
1385       if (unstubbed_)
1386 	out << WT_CLASS ".unstub(" << var_ << ',' << varr << ','
1387 	    << (hideWithDisplay_ ? 1 : 0) << ");\n";
1388 
1389       return var_;
1390     } else if (insertBefore_) {
1391       declare(out);
1392 
1393       std::string varr = insertBefore_->createVar();
1394       WStringStream insertJs;
1395       insertJs << var_ << ".parentNode.insertBefore(" << varr << ","
1396 	       << var_ + ");\n";
1397       insertBefore_->createElement(out, app, insertJs.str());
1398 
1399       return var_;
1400     }
1401 
1402     // FIXME optimize with subselect
1403 
1404     if (!childrenToSave_.empty()) {
1405       declare(out);
1406       out << WT_CLASS << ".saveReparented(" << var_ << ");";
1407     }
1408 
1409     for (unsigned i = 0; i < childrenToSave_.size(); ++i) {
1410       out << "var c" << var_ << (int)i << '='
1411 	  << "$('#" << childrenToSave_[i] << "')";
1412       // In IE, contents is deleted by setting innerHTML
1413       if (app->environment().agentIsIE())
1414 	out << ".detach()";
1415       out << ";";
1416     }
1417 
1418     if (mode_ != Mode::Create) {
1419       setJavaScriptProperties(out, app);
1420       setJavaScriptAttributes(out);
1421     }
1422 
1423     for (EventHandlerMap::const_iterator i = eventHandlers_.begin();
1424 	 i != eventHandlers_.end(); ++i)
1425       if ((mode_ == Mode::Update) || !i->second.jsCode.empty())
1426 	setJavaScriptEvent(out, i->first, i->second, app);
1427 
1428     renderInnerHtmlJS(out, app);
1429 
1430     for (unsigned i = 0; i < childrenToSave_.size(); ++i)
1431       out << WT_CLASS ".replaceWith('" << childrenToSave_[i] << "',c"
1432 	  << var_ << (int)i << ");";
1433 
1434     // Fix for http://redmine.emweb.be/issues/1847: custom JS
1435     // won't find objects that still have to be moved in place
1436     renderDeferredJavaScript(out);
1437 
1438     if (!childrenUpdated)
1439       for (unsigned i = 0; i < updatedChildren_.size(); ++i) {
1440 	DomElement *child = updatedChildren_[i];
1441 	child->asJavaScript(out, Priority::Update);
1442       }
1443 
1444     return var_;
1445   }
1446   }
1447 
1448   return var_;
1449 }
1450 
willRenderInnerHtmlJS(WApplication * app)1451 bool DomElement::willRenderInnerHtmlJS(WApplication *app) const
1452 {
1453   /*
1454    * Returns whether we will (or at least can) write the
1455    * innerHTML with setHtml(), combining children and literal innerHTML
1456    */
1457   return !childrenHtml_.empty() || (wasEmpty_ && canWriteInnerHTML(app));
1458 }
1459 
renderInnerHtmlJS(EscapeOStream & out,WApplication * app)1460 void DomElement::renderInnerHtmlJS(EscapeOStream& out, WApplication *app) const
1461 {
1462   if (willRenderInnerHtmlJS(app)) {
1463     std::string innerHTML;
1464 
1465     if (!properties_.empty()) {
1466       PropertyMap::const_iterator i = properties_.find(Property::InnerHTML);
1467       if (i != properties_.end()) {
1468 	innerHTML += i->second;
1469       }
1470       i = properties_.find(Property::AddedInnerHTML);
1471       if (i != properties_.end()) {
1472 	innerHTML += i->second;
1473       }
1474     }
1475 
1476     /*
1477      * Do we actually have anything to render ?
1478      *   first condition: for IE6: write &nbsp; inside a empty <div></div>
1479      */
1480     if ((type_ == DomElementType::DIV
1481 	 && app->environment().agent() == UserAgent::IE6)
1482 	|| !childrenToAdd_.empty() || !childrenHtml_.empty()
1483 	|| !innerHTML.empty()) {
1484       declare(out);
1485 
1486       out << WT_CLASS ".setHtml(" << var_ << ",'";
1487 
1488       out.pushEscape(EscapeOStream::JsStringLiteralSQuote);
1489       TimeoutList timeouts;
1490       EscapeOStream js;
1491 
1492       for (unsigned i = 0; i < childrenToAdd_.size(); ++i)
1493 	childrenToAdd_[i].child->asHTML(out, js, timeouts);
1494 
1495       out << innerHTML;
1496 
1497       out << childrenHtml_.str();
1498 
1499       if (type_ == DomElementType::DIV
1500 	  && app->environment().agent() == UserAgent::IE6
1501 	  && childrenToAdd_.empty()
1502 	  && innerHTML.empty()
1503 	  && childrenHtml_.empty())
1504 	out << "&nbsp;";
1505 
1506       out.popEscape();
1507 
1508       out << "');\n";
1509 
1510       Utils::insert(timeouts, timeouts_);
1511 
1512       for (unsigned i = 0; i < timeouts.size(); ++i) {
1513 	out << app->javaScriptClass()
1514 	    << "._p_.addTimerEvent('" << timeouts[i].event << "', "
1515 	    << timeouts[i].msec << ','
1516 	    << timeouts[i].repeat << ");\n";
1517       }
1518 
1519       out << js;
1520     }
1521   } else {
1522     for (unsigned i = 0; i < childrenToAdd_.size(); ++i) {
1523       declare(out);
1524       DomElement *child = childrenToAdd_[i].child;
1525       child->addToParent(out, var_, childrenToAdd_[i].pos, app);
1526     }
1527   }
1528 
1529   if (timeOut_ != -1) {
1530     out << app->javaScriptClass() << "._p_.addTimerEvent('"
1531 	<< id_ << "', " << timeOut_ << ','
1532 	<< timeOutJSRepeat_ << ");\n";
1533   }
1534 }
1535 
renderDeferredJavaScript(EscapeOStream & out)1536 void DomElement::renderDeferredJavaScript(EscapeOStream& out) const
1537 {
1538   if (!javaScript_.empty()) {
1539     declare(out);
1540     out << javaScript_ << '\n';
1541   }
1542 }
1543 
setJavaScriptProperties(EscapeOStream & out,WApplication * app)1544 void DomElement::setJavaScriptProperties(EscapeOStream& out,
1545 					 WApplication *app) const
1546 {
1547 #ifndef WT_TARGET_JAVA
1548   EscapeOStream escaped(out);
1549 #else
1550   EscapeOStream escaped = out.push();
1551 #endif // WT_TARGET_JAVA
1552 
1553   bool pushed = false;
1554 
1555   for (PropertyMap::const_iterator i = properties_.begin();
1556        i != properties_.end(); ++i) {
1557     declare(out);
1558 
1559     switch(i->first) {
1560     case Property::InnerHTML:
1561     case Property::AddedInnerHTML:
1562       /*
1563        * In all cases, setJavaScriptProperties() is followed by
1564        * renderInnerHtmlJS() which also considers children.
1565        *
1566        * When there's 'AddedInnerHTML' then willRenderInnerHtmlJS() should
1567        * return false, and that's necessary since then we need to pass 'true'
1568        * as last argument to setHtml()
1569        */
1570       if (willRenderInnerHtmlJS(app))
1571 	break;
1572 
1573       out << WT_CLASS ".setHtml(" << var_ << ',';
1574       if (!pushed) {
1575 	escaped.pushEscape(EscapeOStream::JsStringLiteralSQuote);
1576 	pushed = true;
1577       }
1578       fastJsStringLiteral(out, escaped, i->second);
1579       if (i->first == Property::InnerHTML)
1580 	out << ",false";
1581       else
1582 	out << ",true";
1583 
1584       out << ");";
1585 
1586       break;
1587     case Property::Value:
1588       out << var_ << ".value=";
1589       if (!pushed) {
1590 	escaped.pushEscape(EscapeOStream::JsStringLiteralSQuote);
1591 	pushed = true;
1592       }
1593       fastJsStringLiteral(out, escaped, i->second);
1594       out << ';';
1595       break;
1596     case Property::Target:
1597       out << var_ << ".target='" << i->second << "';";
1598       break;
1599     case Property::Indeterminate:
1600       out << var_ << ".indeterminate=" << i->second << ";";
1601       break;
1602     case Property::Disabled:
1603       if (type_ == DomElementType::A) {
1604 	if (i->second == "true")
1605 	  out << var_ << ".setAttribute('disabled', 'disabled');";
1606 	else
1607 	  out << var_ << ".removeAttribute('disabled', 'disabled');";
1608       } else
1609 	out << var_ << ".disabled=" << i->second << ';';
1610       break;
1611     case Property::ReadOnly:
1612       out << var_ << ".readOnly=" << i->second << ';';
1613       break;
1614     case Property::TabIndex:
1615       out << var_ << ".tabIndex=" << i->second << ';';
1616       break;
1617     case Property::Checked:
1618       out << var_ << ".checked=" << i->second << ';';
1619       break;
1620     case Property::Selected:
1621       out << var_ << ".selected=" << i->second << ';';
1622       break;
1623     case Property::SelectedIndex:
1624       out << "setTimeout(function() { "
1625 	  << var_ << ".selectedIndex=" << i->second << ";}, 0);";
1626       break;
1627     case Property::Multiple:
1628       out << var_ << ".multiple=" << i->second << ';';
1629       break;
1630     case Property::Src:
1631       out << var_ << ".src='" << i->second << "\';";
1632       break;
1633     case Property::ColSpan:
1634       out << var_ << ".colSpan=" << i->second << ";";
1635       break;
1636     case Property::RowSpan:
1637       out << var_ << ".rowSpan=" << i->second << ";";
1638       break;
1639     case Property::Label:
1640       out << var_ << ".label=";
1641       if (!pushed) {
1642 	escaped.pushEscape(EscapeOStream::JsStringLiteralSQuote);
1643 	pushed = true;
1644       }
1645       fastJsStringLiteral(out, escaped, i->second);
1646       out << ';';
1647       break;
1648     case Property::Placeholder:
1649       out << var_ << ".placeholder=";
1650       if (!pushed) {
1651 	escaped.pushEscape(EscapeOStream::JsStringLiteralSQuote);
1652 	pushed = true;
1653       }
1654       fastJsStringLiteral(out, escaped, i->second);
1655       out << ';';
1656       break;
1657     case Property::Class:
1658       out << var_ << ".className=";
1659       if (!pushed) {
1660 	escaped.pushEscape(EscapeOStream::JsStringLiteralSQuote);
1661 	pushed = true;
1662       }
1663       fastJsStringLiteral(out, escaped, i->second);
1664       out << ';';
1665       break;
1666     case Property::StyleFloat:
1667       out << var_ << ".style.";
1668       if (app->environment().agentIsIE())
1669 	out << "styleFloat";
1670       else
1671 	out << "cssFloat";
1672       out << "=\'" << i->second << "\';";
1673       break;
1674     case Wt::Property::StyleWidthExpression:
1675       out << var_ << ".style.setExpression('width',";
1676       if (!pushed) {
1677 	escaped.pushEscape(EscapeOStream::JsStringLiteralSQuote);
1678 	pushed = true;
1679       }
1680       fastJsStringLiteral(out, escaped, i->second);
1681       out << ");";
1682       break;
1683     default: {
1684       unsigned int p = static_cast<unsigned int>(i->first);
1685       if (p >= static_cast<unsigned int>(Property::Style) &&
1686 	  p < static_cast<unsigned int>(Property::LastPlusOne)) {
1687 	if (app->environment().agent() == UserAgent::IE6) {
1688 	  /*
1689 	   * Unsupported properties, like min-height, would otherwise be
1690 	   * ignored, but we want this information client-side. (Still, really ?)
1691 	   */
1692 	  out << var_ << ".style['"
1693 	      << cssNames_[p - static_cast<unsigned int>(Property::StylePosition)]
1694 	      << "']='" << i->second << "';";
1695 	} else {
1696 	  out << var_ << ".style."
1697 	      << cssCamelNames_[p - static_cast<unsigned int>(Property::Style)]
1698 	      << "='" << i->second << "';";
1699 	}
1700       }
1701     }
1702     }
1703 
1704     out << '\n';
1705   }
1706 }
1707 
setJavaScriptAttributes(EscapeOStream & out)1708 void DomElement::setJavaScriptAttributes(EscapeOStream& out) const
1709 {
1710   for (AttributeMap::const_iterator i = attributes_.begin();
1711        i != attributes_.end(); ++i) {
1712     declare(out);
1713 
1714     if (i->first == "style") {
1715       out << var_ << ".style.cssText = ";
1716       jsStringLiteral(out, i->second, '\'');
1717       out << ';' << '\n';
1718     } else {
1719       out << var_ << ".setAttribute('" << i->first << "',";
1720       jsStringLiteral(out, i->second, '\'');
1721       out << ");\n";
1722     }
1723   }
1724 
1725   for (AttributeSet::const_iterator i = removedAttributes_.begin();
1726       i != removedAttributes_.end(); ++i) {
1727     declare(out);
1728 
1729     out << var_ << ".removeAttribute('" << *i << "');\n";
1730   }
1731 }
1732 
isDefaultInline()1733 bool DomElement::isDefaultInline() const
1734 {
1735   return isDefaultInline(type_);
1736 }
1737 
isDefaultInline(DomElementType type)1738 bool DomElement::isDefaultInline(DomElementType type)
1739 {
1740   assert(static_cast<unsigned int>(type) < static_cast<unsigned int>(DomElementType::UNKNOWN));
1741   return defaultInline_[static_cast<unsigned int>(type)];
1742 }
1743 
isSelfClosingTag(const std::string & tag)1744 bool DomElement::isSelfClosingTag(const std::string& tag)
1745 {
1746   return (   (tag == "br")
1747 	  || (tag == "hr")
1748 	  || (tag == "img")
1749 	  || (tag == "area")
1750 	  || (tag == "col")
1751 	  || (tag == "input"));
1752 }
1753 
isSelfClosingTag(DomElementType element)1754 bool DomElement::isSelfClosingTag(DomElementType element)
1755 {
1756   return ((   element == DomElementType::BR)
1757        /* || (element == DomElementType::HR) */
1758 	  || (element == DomElementType::IMG)
1759 	  || (element == DomElementType::AREA)
1760 	  || (element == DomElementType::COL)
1761 	  || (element == DomElementType::INPUT));
1762 }
1763 
parseTagName(const std::string & tag)1764 DomElementType DomElement::parseTagName(const std::string& tag)
1765 {
1766   for (unsigned i = 0; i < static_cast<unsigned int>(DomElementType::UNKNOWN); ++i)
1767     if (tag == elementNames_[i])
1768       return (DomElementType)i;
1769 
1770   return DomElementType::UNKNOWN;
1771 }
1772 
tagName(DomElementType type)1773 std::string DomElement::tagName(DomElementType type)
1774 {
1775   assert(static_cast<unsigned int>(type) < static_cast<unsigned int>(DomElementType::UNKNOWN));
1776   return elementNames_[static_cast<unsigned int>(type)];
1777 }
1778 
cssName(Property property)1779 const std::string& DomElement::cssName(Property property)
1780 {
1781   return cssNames_[static_cast<unsigned int>(property) -
1782 		   static_cast<unsigned int>(Property::StylePosition)];
1783 }
1784 
setGlobalUnfocused(bool b)1785 void DomElement::setGlobalUnfocused(bool b)
1786 {
1787   globalUnfocused_ = b;
1788 }
1789 
1790 
1791 
1792 }
1793