1 /*
2  * Copyright (C) 2012 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 
7 #include "Wt/WApplication.h"
8 #include "Wt/WContainerWidget.h"
9 #include "Wt/WEnvironment.h"
10 #include "Wt/WGridLayout.h"
11 #include "Wt/WLogger.h"
12 
13 #include "StdGridLayoutImpl2.h"
14 #include "SizeHandle.h"
15 #include "DomElement.h"
16 #include "WebUtils.h"
17 
18 #ifndef WT_DEBUG_JS
19 #include "js/StdGridLayoutImpl2.min.js"
20 #include "js/WtResize.min.js"
21 #endif
22 
23 #ifdef WT_WIN32
24 #define snprintf _snprintf
25 #endif
26 
27 namespace Wt {
28 
29 LOGGER("WGridLayout2");
30 
StdGridLayoutImpl2(WLayout * layout,Impl::Grid & grid)31 StdGridLayoutImpl2::StdGridLayoutImpl2(WLayout *layout, Impl::Grid& grid)
32   : StdLayoutImpl(layout),
33     grid_(grid),
34     needAdjust_(false),
35     needRemeasure_(false),
36     needConfigUpdate_(false)
37 {
38   const char *THIS_JS = "js/StdGridLayoutImpl2.js";
39 
40   WApplication *app = WApplication::instance();
41 
42   if (!app->javaScriptLoaded(THIS_JS)) {
43     app->styleSheet().addRule("table.Wt-hcenter", "margin: 0px auto;"
44 			      "position: relative");
45 
46     LOAD_JAVASCRIPT(app, THIS_JS, "StdLayout2", wtjs1);
47     LOAD_JAVASCRIPT(app, THIS_JS, "layouts2", appjs1);
48 
49     app->doJavaScript(app->javaScriptClass() + ".layouts2.scheduleAdjust();");
50     app->doJavaScript("(function(){"
51 	              "var f=function(){"
52 		        + app->javaScriptClass() + ".layouts2.scheduleAdjust();"
53 		      "};"
54 	              "if($().jquery.indexOf('1.') === 0)"
55 		        "$(window).load(f);"
56 		      "else "
57 		        "$(window).on('load',f);"
58 		      "})();");
59 
60     WApplication::instance()->addAutoJavaScript
61       ("if(" + app->javaScriptClass() + ".layouts2) "
62        + app->javaScriptClass() + ".layouts2.adjustNow();");
63   }
64 }
65 
itemResized(WLayoutItem * item)66 bool StdGridLayoutImpl2::itemResized(WLayoutItem *item)
67 {
68   const unsigned colCount = grid_.columns_.size();
69   const unsigned rowCount = grid_.rows_.size();
70 
71   for (unsigned row = 0; row < rowCount; ++row)
72     for (unsigned col = 0; col < colCount; ++col)
73       if (grid_.items_[row][col].item_.get() == item &&
74 	  !grid_.items_[row][col].update_) {
75 	grid_.items_[row][col].update_ = true;
76 	needAdjust_ = true;
77 	return true;
78       }
79 
80   return false;
81 }
82 
parentResized()83 bool StdGridLayoutImpl2::parentResized()
84 {
85   if (!needRemeasure_) {
86     needRemeasure_ = true;
87     return true;
88   } else
89     return false;
90 }
91 
nextRowWithItem(int row,int c)92 int StdGridLayoutImpl2::nextRowWithItem(int row, int c) const
93 {
94   for (row += grid_.items_[row][c].rowSpan_; row < (int)grid_.rows_.size();
95        ++row) {
96     for (unsigned col = 0; col < grid_.columns_.size();
97 	 col += grid_.items_[row][col].colSpan_) {
98       if (hasItem(row, col))
99 	return row;
100     }
101   }
102 
103   return grid_.rows_.size();
104 }
105 
nextColumnWithItem(int row,int col)106 int StdGridLayoutImpl2::nextColumnWithItem(int row, int col) const
107 {
108   for (;;) {
109     col = col + grid_.items_[row][col].colSpan_;
110 
111     if (col < (int)grid_.columns_.size()) {
112       for (unsigned i = 0; i < grid_.rows_.size(); ++i)
113 	if (hasItem(i, col))
114 	  return col;
115     } else
116       return grid_.columns_.size();
117   }
118 }
119 
hasItem(int row,int col)120 bool StdGridLayoutImpl2::hasItem(int row, int col) const
121 {
122   WLayoutItem *item = grid_.items_[row][col].item_.get();
123 
124   if (item) {
125     WWidget *w = item->widget();
126     return !w || !w->isHidden();
127   } else
128     return false;
129 }
130 
createElement(WLayoutItem * item,WApplication * app)131 DomElement *StdGridLayoutImpl2::createElement(WLayoutItem *item,
132 					      WApplication *app)
133 {
134   DomElement *c = getImpl(item)->createDomElement(nullptr, true, true, app);
135 
136   c->setProperty(Property::StyleVisibility, "hidden");
137 
138   return c;
139 }
140 
updateDom(DomElement & parent)141 void StdGridLayoutImpl2::updateDom(DomElement& parent)
142 {
143   WApplication *app = WApplication::instance();
144 
145   if (needConfigUpdate_) {
146     needConfigUpdate_ = false;
147 
148     DomElement *div = DomElement::getForUpdate(this, DomElementType::DIV);
149 
150     for (unsigned i = 0; i < addedItems_.size(); ++i) {
151       WLayoutItem *item = addedItems_[i];
152       DomElement *c = createElement(item, app);
153       div->addChild(c);
154     }
155 
156     addedItems_.clear();
157 
158     for (unsigned i = 0; i < removedItems_.size(); ++i)
159       parent.callJavaScript(WT_CLASS ".remove('" + removedItems_[i] + "');",
160 			    true);
161 
162     removedItems_.clear();
163 
164     parent.addChild(div);
165 
166     WStringStream js;
167     js << app->javaScriptClass() << ".layouts2.updateConfig('"
168        << id() << "',";
169     streamConfig(js, app);
170     js << ");";
171 
172     app->doJavaScript(js.str());
173 
174     needRemeasure_ = false;
175     needAdjust_ = false;
176   }
177 
178   if (needRemeasure_) {
179     needRemeasure_ = false;
180     WStringStream js;
181     js << app->javaScriptClass() << ".layouts2.setDirty('" << id() << "');";
182     app->doJavaScript(js.str());
183   }
184 
185   if (needAdjust_) {
186     needAdjust_ = false;
187 
188     WStringStream js;
189     js << app->javaScriptClass() << ".layouts2.adjust('" << id() << "', [";
190 
191     bool first = true;
192 
193     const unsigned colCount = grid_.columns_.size();
194     const unsigned rowCount = grid_.rows_.size();
195 
196     for (unsigned row = 0; row < rowCount; ++row)
197       for (unsigned col = 0; col < colCount; ++col)
198 	if (grid_.items_[row][col].update_) {
199 	  grid_.items_[row][col].update_ = false;
200 	  if (!first)
201 	    js << ",";
202 	  first = false;
203 	  js << "[" << (int)row << "," << (int)col << "]";
204 	}
205 
206     js << "]);";
207 
208     app->doJavaScript(js.str());
209   }
210 
211   const unsigned colCount = grid_.columns_.size();
212   const unsigned rowCount = grid_.rows_.size();
213 
214   for (unsigned i = 0; i < rowCount; ++i) {
215     for (unsigned j = 0; j < colCount; ++j) {
216       WLayoutItem *item = grid_.items_[i][j].item_.get();
217       if (item) {
218 	WLayout *nested = item->layout();
219 	if (nested)
220 	  (dynamic_cast<StdLayoutImpl *>(nested->impl()))->updateDom(parent);
221       }
222     }
223   }
224 }
225 
~StdGridLayoutImpl2()226 StdGridLayoutImpl2::~StdGridLayoutImpl2()
227 {
228   WApplication *app = WApplication::instance();
229 
230   /*
231    * If it is a top-level layout (as opposed to a nested layout),
232    * configure overflow of the container.
233    */
234   if (parentLayoutImpl() == nullptr) {
235     if (container() == app->root()) {
236       app->setBodyClass("");
237       app->setHtmlClass("");
238     }
239 
240     if (app->environment().agentIsIElt(9) && container())
241       container()->setOverflow(Overflow::Visible);
242   }
243 }
244 
minimumHeightForRow(int row)245 int StdGridLayoutImpl2::minimumHeightForRow(int row) const
246 {
247   int minHeight = 0;
248 
249   const unsigned colCount = grid_.columns_.size();
250   for (unsigned j = 0; j < colCount; ++j) {
251     WLayoutItem *item = grid_.items_[row][j].item_.get();
252     if (item)
253       minHeight = std::max(minHeight, getImpl(item)->minimumHeight());
254   }
255 
256   return minHeight;
257 }
258 
minimumWidthForColumn(int col)259 int StdGridLayoutImpl2::minimumWidthForColumn(int col) const
260 {
261   int minWidth = 0;
262 
263   const unsigned rowCount = grid_.rows_.size();
264   for (unsigned i = 0; i < rowCount; ++i) {
265     WLayoutItem *item = grid_.items_[i][col].item_.get();
266     if (item)
267       minWidth = std::max(minWidth, getImpl(item)->minimumWidth());
268   }
269 
270   return minWidth;
271 }
272 
minimumWidth()273 int StdGridLayoutImpl2::minimumWidth() const
274 {
275   const unsigned colCount = grid_.columns_.size();
276 
277   int total = 0;
278 
279   for (unsigned i = 0; i < colCount; ++i)
280     total += minimumWidthForColumn(i);
281 
282   return total + (colCount-1) * grid_.horizontalSpacing_;
283 }
284 
minimumHeight()285 int StdGridLayoutImpl2::minimumHeight() const
286 {
287   const unsigned rowCount = grid_.rows_.size();
288 
289   int total = 0;
290 
291   for (unsigned i = 0; i < rowCount; ++i)
292     total += minimumHeightForRow(i);
293 
294   return total + (rowCount-1) * grid_.verticalSpacing_;
295 }
296 
itemAdded(WLayoutItem * item)297 void StdGridLayoutImpl2::itemAdded(WLayoutItem *item)
298 {
299   addedItems_.push_back(item);
300   update();
301 }
302 
itemRemoved(WLayoutItem * item)303 void StdGridLayoutImpl2::itemRemoved(WLayoutItem *item)
304 {
305   Utils::erase(addedItems_, item);
306   removedItems_.push_back(getImpl(item)->id());
307   update();
308 }
309 
update()310 void StdGridLayoutImpl2::update()
311 {
312   WContainerWidget *c = container();
313 
314   if (c)
315     c->layoutChanged(false);
316 
317   needConfigUpdate_ = true;
318 }
319 
320 void StdGridLayoutImpl2
streamConfig(WStringStream & js,const std::vector<Impl::Grid::Section> & sections,bool rows,WApplication * app)321 ::streamConfig(WStringStream& js,
322 	       const std::vector<Impl::Grid::Section>& sections,
323 	       bool rows, WApplication *app)
324 {
325   js << "[";
326 
327   for (unsigned i = 0; i < sections.size(); ++i) {
328     if (i != 0)
329       js << ",";
330 
331     js << "[" << sections[i].stretch_ << ",";
332 
333     if (sections[i].resizable_) {
334       SizeHandle::loadJavaScript(app);
335 
336       js << "[";
337 
338       const WLength& size = sections[i].initialSize_;
339 
340       if (size.isAuto())
341 	js << "-1";
342       else if (size.unit() == LengthUnit::Percentage)
343 	js << size.value() << ",1";
344       else
345 	js << size.toPixels();
346 
347       js << "],";
348     } else
349       js << "0,";
350 
351     if (rows)
352       js << minimumHeightForRow(i);
353     else
354       js << minimumWidthForColumn(i);
355 
356     js << "]";
357 
358   }
359 
360   js << "]";
361 }
362 
streamConfig(WStringStream & js,WApplication * app)363 void StdGridLayoutImpl2::streamConfig(WStringStream& js, WApplication *app)
364 {
365   js << "{ rows:";
366 
367   streamConfig(js, grid_.rows_, true, app);
368 
369   js << ", cols:";
370 
371   streamConfig(js, grid_.columns_, false, app);
372 
373   js << ", items: [";
374 
375   const unsigned colCount = grid_.columns_.size();
376   const unsigned rowCount = grid_.rows_.size();
377 
378   for (unsigned row = 0; row < rowCount; ++row) {
379     for (unsigned col = 0; col < colCount; ++col) {
380       Impl::Grid::Item& item = grid_.items_[row][col];
381 
382       AlignmentFlag hAlign = item.alignment_ & AlignHorizontalMask;
383       AlignmentFlag vAlign = item.alignment_ & AlignVerticalMask;
384 
385       if (row + col != 0)
386 	js << ",";
387 
388       if (item.item_) {
389 	std::string id = getImpl(item.item_.get())->id();
390 
391 	js << "{";
392 
393 	if (item.colSpan_ != 1 || item.rowSpan_ != 1)
394 	  js << "span: [" << item.colSpan_ << "," << item.rowSpan_ << "],";
395 
396 	if (item.alignment_.value()) {
397 	  unsigned align = 0;
398 
399 	  if (hAlign != static_cast<AlignmentFlag>(0))
400 	    switch (hAlign) {
401 	    case AlignmentFlag::Left: align |= 0x1; break;
402 	    case AlignmentFlag::Right: align |= 0x2; break;
403 	    case AlignmentFlag::Center: align |= 0x4; break;
404 	    default: break;
405 	    }
406 
407 	  if (vAlign != static_cast<AlignmentFlag>(0))
408 	    switch (vAlign) {
409 	    case AlignmentFlag::Top: align |= 0x10; break;
410 	    case AlignmentFlag::Bottom: align |= 0x20; break;
411 	    case AlignmentFlag::Middle: align |= 0x40; break;
412 	    default: break;
413 	    }
414 
415 	  js << "align:" << (int)align << ",";
416 	}
417 
418 	js << "dirty:" << (grid_.items_[row][col].update_ ? 2 : 0)
419 	   << ",id:'" << id << "'"
420 	   << "}";
421 
422 	grid_.items_[row][col].update_ = 0;
423       } else
424 	js << "null";
425     }
426   }
427 
428   js << "]}";
429 }
430 
pixelSize(const WLength & size)431 int StdGridLayoutImpl2::pixelSize(const WLength& size)
432 {
433   if (size.unit() == LengthUnit::Percentage)
434     return 0;
435   else
436     return (int)size.toPixels();
437 }
438 
439 /*
440  * fitWidth, fitHeight:
441  *  - from setLayout(AlignmentFlag::Left | AlignmentFlag::Top)
442  *    is being deprecated but still needs to be implemented
443  *  - nested layouts: handles as other layout items
444  */
createDomElement(DomElement * parent,bool fitWidth,bool fitHeight,WApplication * app)445 DomElement *StdGridLayoutImpl2::createDomElement(DomElement *parent,
446 						 bool fitWidth, bool fitHeight,
447 						 WApplication *app)
448 {
449   needAdjust_ = needConfigUpdate_ = needRemeasure_ = false;
450   addedItems_.clear();
451   removedItems_.clear();
452 
453   const unsigned colCount = grid_.columns_.size();
454   const unsigned rowCount = grid_.rows_.size();
455 
456   int margin[] = { 0, 0, 0, 0};
457 
458   int maxWidth = 0, maxHeight = 0;
459 
460   if (layout()->parentLayout() == nullptr) {
461     /*
462      * If it is a top-level layout (as opposed to a nested layout),
463      * configure overflow of the container.
464      */
465     if (container() == app->root()) {
466       /*
467        * Reset body,html default paddings and so on if we are doing layout
468        * in the entire document.
469        */
470       app->setBodyClass(app->bodyClass() + " Wt-layout");
471       app->setHtmlClass(app->htmlClass() + " Wt-layout");
472     }
473 
474 #ifndef WT_TARGET_JAVA
475     layout()->getContentsMargins(margin + 3, margin, margin + 1, margin + 2);
476 #else // WT_TARGET_JAVA
477     margin[3] = layout()->getContentsMargin(Side::Left);
478     margin[0] = layout()->getContentsMargin(Side::Top);
479     margin[1] = layout()->getContentsMargin(Side::Right);
480     margin[2] = layout()->getContentsMargin(Side::Bottom);
481 #endif // WT_TARGET_JAVA
482 
483     maxWidth = pixelSize(container()->maximumWidth());
484     maxHeight = pixelSize(container()->maximumHeight());
485   }
486 
487   WStringStream js;
488 
489   js << app->javaScriptClass()
490      << ".layouts2.add(new " WT_CLASS ".StdLayout2("
491      << app->javaScriptClass() << ",'"
492      << id() << "',";
493 
494   if (layout()->parentLayout() &&
495       dynamic_cast<StdGridLayoutImpl2*>(getImpl(layout()->parentLayout())))
496     js << "'" << getImpl(layout()->parentLayout())->id() << "',";
497   else
498     js << "null,";
499 
500   bool progressive = !app->environment().ajax();
501   js << (fitWidth ? '1' : '0') << "," << (fitHeight ? '1' : '0') << ","
502      << (progressive ? '1' : '0') << ",";
503 
504   js << maxWidth << "," << maxHeight
505      << ",["
506      << grid_.horizontalSpacing_ << "," << margin[3] << "," << margin[1]
507      << "],["
508      << grid_.verticalSpacing_ << "," << margin[0] << "," << margin[2] << "],";
509 
510   streamConfig(js, app);
511 
512   DomElement *div = DomElement::createNew(DomElementType::DIV);
513   div->setId(id());
514   div->setProperty(Property::StylePosition, "relative");
515 
516   DomElement *table = nullptr, *tbody = nullptr, *tr = nullptr;
517   if (progressive) {
518     table = DomElement::createNew(DomElementType::TABLE);
519 
520     WStringStream style;
521     if (maxWidth)
522       style << "max-width: " << maxWidth << "px;";
523     if (maxHeight)
524       style << "max-height: " << maxHeight << "px;";
525     style << "width: 100%;";
526 
527     table->setProperty(Property::Style, style.str());
528 
529     int totalColStretch = 0;
530     for (unsigned col = 0; col < colCount; ++col)
531       totalColStretch += std::max(0, grid_.columns_[col].stretch_);
532 
533     for (unsigned col = 0; col < colCount; ++col) {
534       DomElement *c = DomElement::createNew(DomElementType::COL);
535       int stretch = std::max(0, grid_.columns_[col].stretch_);
536 
537       if (stretch || totalColStretch == 0) {
538 	char buf[30];
539 
540 	double pct = totalColStretch == 0 ? 100.0 / colCount
541 	  : (100.0 * stretch / totalColStretch);
542 
543 	WStringStream ss;
544 	ss << "width:" << Utils::round_css_str(pct, 2, buf) << "%;";
545 	c->setProperty(Property::Style, ss.str());
546       }
547 
548       table->addChild(c);
549     }
550 
551     tbody = DomElement::createNew(DomElementType::TBODY);
552   }
553 
554 #ifndef WT_TARGET_JAVA
555   std::vector<bool> overSpanned(colCount * rowCount, false);
556 #else
557   std::vector<bool> overSpanned;
558   overSpanned.insert(0, colCount * rowCount, false);
559 #endif // WT_TARGET_JAVA
560 
561   int prevRowWithItem = -1;
562 
563   for (unsigned row = 0; row < rowCount; ++row) {
564     if (table)
565       tr = DomElement::createNew(DomElementType::TR);
566 
567     bool rowVisible = false;
568     int prevColumnWithItem = -1;
569 
570     for (unsigned col = 0; col < colCount; ++col) {
571       Impl::Grid::Item& item = grid_.items_[row][col];
572 
573       if (!overSpanned[row * colCount + col]) {
574 	for (int i = 0; i < item.rowSpan_; ++i)
575 	  for (int j = 0; j < item.colSpan_; ++j)
576 	    if (i + j > 0)
577 	      overSpanned[(row + i) * colCount + col + j] = true;
578 
579 	AlignmentFlag hAlign = item.alignment_ & AlignHorizontalMask;
580 	AlignmentFlag vAlign = item.alignment_ & AlignVerticalMask;
581 
582 	DomElement *td = nullptr;
583 
584 	if (table) {
585 	  bool itemVisible = hasItem(row, col);
586 	  rowVisible = rowVisible || itemVisible;
587 
588 	  td = DomElement::createNew(DomElementType::TD);
589 
590 	  if (itemVisible) {
591 	    int padding[] = { 0, 0, 0, 0 };
592 
593 	    int nextRow = nextRowWithItem(row, col);
594 	    int prevRow = prevRowWithItem;
595 
596 	    int nextCol = nextColumnWithItem(row, col);
597 	    int prevCol = prevColumnWithItem;
598 
599 	    if (prevRow == -1)
600 	      padding[0] = margin[0];
601 	    else
602 	      padding[0] = (grid_.verticalSpacing_+1) / 2;
603 
604 	    if (nextRow == (int)rowCount)
605 	      padding[2] = margin[2];
606 	    else
607 	      padding[2] = grid_.verticalSpacing_ / 2;
608 
609 	    if (prevCol == -1)
610 	      padding[3] = margin[3];
611 	    else
612 	      padding[3] = (grid_.horizontalSpacing_ + 1)/2;
613 
614 	    if (nextCol == (int)colCount)
615 	      padding[1] = margin[1];
616 	    else
617 	      padding[1] = (grid_.horizontalSpacing_)/2;
618 
619 	    WStringStream style;
620 
621 	    if (app->layoutDirection() == LayoutDirection::RightToLeft)
622 	      std::swap(padding[1], padding[3]);
623 
624 	    if (padding[0] == padding[1] && padding[0] == padding[2]
625 		&& padding[0] == padding[3]) {
626 	      if (padding[0] != 0)
627 		style << "padding:" << padding[0] << "px;";
628 	    } else
629 	      style << "padding:"
630 		    << padding[0] << "px " << padding[1] << "px "
631 		    << padding[2] << "px " << padding[3] << "px;";
632 
633 	    if (static_cast<unsigned int>(vAlign) != 0)
634 	      switch (vAlign) {
635 	      case AlignmentFlag::Top:
636 		style << "vertical-align:top;";
637 		break;
638 	      case AlignmentFlag::Middle:
639 		style << "vertical-align:middle;";
640 		break;
641 	      case AlignmentFlag::Bottom:
642 		style << "vertical-align:bottom;";
643 	      default:
644 		break;
645 	      }
646 
647 	    td->setProperty(Property::Style, style.str());
648 
649 	    if (item.rowSpan_ != 1)
650 	      td->setProperty(Property::RowSpan,
651 			      std::to_string(item.rowSpan_));
652 	    if (item.colSpan_ != 1)
653 	      td->setProperty(Property::ColSpan,
654 			      std::to_string(item.colSpan_));
655 
656 	    prevColumnWithItem = col;
657 	  }
658 	}
659 
660 	DomElement *c = nullptr;
661 
662 	if (!table) {
663 	  if (item.item_) {
664 	    c = createElement(item.item_.get(), app);
665 	    div->addChild(c);
666 	  }
667 	} else
668 	  if (item.item_)
669 	    c = getImpl(item.item_.get())->createDomElement(nullptr, true,
670 							    true, app);
671 
672 	if (table) {
673 	  if (c) {
674 	    if (!app->environment().agentIsIElt(9))
675 	      c->setProperty(Property::StyleBoxSizing, "border-box");
676 
677 	    if (static_cast<unsigned int>(hAlign) == 0)
678 	      hAlign = AlignmentFlag::Justify;
679 
680 	    switch (hAlign) {
681 	    case AlignmentFlag::Center: {
682 	      DomElement *itable = DomElement::createNew(DomElementType::TABLE);
683 	      itable->setProperty(Property::Class, "Wt-hcenter");
684 	      if (static_cast<unsigned int>(vAlign) == 0)
685 		itable->setProperty(Property::Style, "height:100%;");
686 	      DomElement *irow = DomElement::createNew(DomElementType::TR);
687 	      DomElement *itd = DomElement::createNew(DomElementType::TD);
688 	      if (static_cast<unsigned int>(vAlign) == 0)
689 		itd->setProperty(Property::Style, "height:100%;");
690 
691 	      bool haveMinWidth
692 		= !c->getProperty(Property::StyleMinWidth).empty();
693 
694 	      itd->addChild(c);
695 
696 	      if (app->environment().agentIsIElt(9)) {
697 		// IE7 and IE8 do support min-width but do not enforce it
698 		// properly when in a table.
699 		//  see http://stackoverflow.com/questions/2356525
700 		//            /css-min-width-in-ie6-7-and-8
701 		if (haveMinWidth) {
702 		  DomElement *spacer = DomElement::createNew(DomElementType::DIV);
703 		  spacer->setProperty(Property::StyleWidth,
704 				      c->getProperty(Property::StyleMinWidth));
705 		  spacer->setProperty(Property::StyleHeight, "1px");
706 		  itd->addChild(spacer);
707 		}
708 	      }
709 
710 	      irow->addChild(itd);
711 	      itable->addChild(irow);
712 	      c = itable;
713 	      break;
714 	    }
715 	    case AlignmentFlag::Right:
716 	      if (!c->isDefaultInline())
717 		c->setProperty(Property::StyleFloat, "right");
718 	      else
719 		td->setProperty(Property::StyleTextAlign, "right");
720 	      break;
721 	    case AlignmentFlag::Left:
722 	      if (!c->isDefaultInline())
723 		c->setProperty(Property::StyleFloat, "left");
724 	      else
725 		td->setProperty(Property::StyleTextAlign, "left");
726 	      break;
727 	    default:
728 	      break;
729 	    }
730 
731 	    bool haveMinWidth
732 		= !c->getProperty(Property::StyleMinWidth).empty();
733 
734 	    td->addChild(c);
735 
736 	    if (app->environment().agentIsIElt(9)) {
737 	      // IE7 and IE8 do support min-width but do not enforce it properly
738 	      // when in a table.
739 	      //  see http://stackoverflow.com/questions/2356525
740 	      //            /css-min-width-in-ie6-7-and-8
741 	      if (haveMinWidth) {
742 		DomElement *spacer = DomElement::createNew(DomElementType::DIV);
743 		spacer->setProperty(Property::StyleWidth,
744 				    c->getProperty(Property::StyleMinWidth));
745 		spacer->setProperty(Property::StyleHeight, "1px");
746 		td->addChild(spacer);
747 	      }
748 	    }
749 	  }
750 
751 	  tr->addChild(td);
752 	}
753       }
754     }
755 
756     if (tr) {
757       if (!rowVisible)
758 	tr->setProperty(Property::StyleDisplay, "hidden");
759       else
760 	prevRowWithItem = row;
761       tbody->addChild(tr);
762     }
763   }
764 
765   js << "));";
766 
767   if (table) {
768     table->addChild(tbody);
769     div->addChild(table);
770   }
771 
772   div->callJavaScript(js.str());
773 
774   if (layout()->parentLayout() == nullptr) {
775     WContainerWidget *c = container();
776 
777     /*
778      * Take the hint: if the container is relative, then we can use an absolute
779      * layout for its contents, under the assumption that a .wtResize or
780      * auto-javascript sets the width too (like in WTreeView, WTableView)
781      */
782     if (c->positionScheme() == PositionScheme::Relative ||
783 	c->positionScheme() == PositionScheme::Absolute) {
784       div->setProperty(Property::StylePosition, "absolute");
785       div->setProperty(Property::StyleLeft, "0");
786       div->setProperty(Property::StyleRight, "0");
787     } else if (app->environment().agentIsIE()) {
788       /*
789        * position: relative element needs to be in a position: relative
790        * parent otherwise scrolling is broken
791        */
792       if (app->environment().agentIsIE()
793 	  && c->parent()->positionScheme() != PositionScheme::Static)
794 	parent->setProperty(Property::StylePosition, "relative");
795     }
796 
797     AlignmentFlag hAlign = c->contentAlignment() & AlignHorizontalMask;
798     switch (hAlign) {
799     case AlignmentFlag::Center: {
800       DomElement *itable = DomElement::createNew(DomElementType::TABLE);
801       itable->setProperty(Property::Class, "Wt-hcenter");
802       if (fitHeight)
803 	itable->setProperty(Property::Style, "height:100%;");
804       DomElement *irow = DomElement::createNew(DomElementType::TR);
805       DomElement *itd = DomElement::createNew(DomElementType::TD);
806       if (fitHeight)
807 	itd->setProperty(Property::Style, "height:100%;");
808       itd->addChild(div);
809       irow->addChild(itd);
810       itable->addChild(irow);
811       itable->setId(id() + "l");
812       div = itable;
813 
814       break;
815     }
816     case AlignmentFlag::Left:
817       break;
818     case AlignmentFlag::Right:
819       div->setProperty(Property::StyleFloat, "right");
820       break;
821     default:
822       break;
823     }
824   }
825 
826   return div;
827 }
828 
829 }
830