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 << " ";
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 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 << " ";
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