1 (function (templateLayout) {
  2 
  3     var generator, log;
  4     log = wef.logger("templateLayout.generator");
  5 
  6     log.info("load generator module...");
  7 
  8     function generateRootTemplate(template) {
  9         var rootElement;
 10 
 11         function appendFreeNodes(currentNode, defaultNode) {
 12             var i, className, candidates = [];
 13             for (i = 0; i < currentNode.childNodes.length; i++) {
 14                 className = currentNode.childNodes[i].className;
 15                 if (className || className === "") {
 16                     //HTMLElementNodes
 17                     if (!/templateLayout/.test(className)) {
 18                         console.log(className, currentNode.childNodes[i].tagName);
 19                         candidates.push(currentNode.childNodes[i]);
 20                     }
 21                 } else {
 22                     //TextNodes
 23                     console.log("text", currentNode.childNodes[i].textContent);
 24                     //insert in template.grid.getDefaultNode()
 25                     candidates.push(currentNode.childNodes[i]);
 26                 }
 27             }
 28             while (candidates.length > 0) {
 29                 defaultNode.appendChild(candidates.shift());
 30             }
 31         }
 32 
 33         function generateTemplate(template, parentHtmlNode) {
 34             var templateNode, gridNode, rowNode, slotNode, defaultSlot, defaultNode;
 35             log.info("template:", template.selectorText, "(parent:", parentHtmlNode.localName, ")");
 36             templateNode = generator.fn.appendTemplate(template, parentHtmlNode);
 37             if (!template.isLeaf()) {
 38                 defaultSlot = template.grid.getDefaultSlot();
 39                 gridNode = generator.fn.appendGrid(template.grid, templateNode);
 40                 template.grid.rows.forEach(function (row) {
 41                     log.info("row:", row.rowText);
 42                     rowNode = generator.fn.appendRow(row, gridNode);
 43                     row.slots.forEach(function (slot) {
 44                         log.info("slot:", slot.slotText);
 45                         slotNode = generator.fn.appendSlot(slot, rowNode);
 46                         if (defaultSlot.slotText === slot.slotText) {
 47                             //mark currentNode as default
 48                             defaultNode = slotNode;
 49                         }
 50                         //each slot can have multiple elements or none
 51                         if (template.grid.filledSlots[slot.slotText]) {
 52                             template.grid.filledSlots[slot.slotText].forEach(function (childTemplate) {
 53                                 log.info("slot contains:", childTemplate.selectorText);
 54                                 generateTemplate(childTemplate, slotNode);
 55                             });
 56                         }
 57                     });
 58                 });
 59                 appendFreeNodes(templateNode, defaultNode);
 60             }
 61             log.debug("template generated: ", template);
 62         }
 63 
 64         function resizeTemplateWidth(template, parentHtmlNode) {
 65             var templateNode, gridNode, columnNode, rowNode, slotNode, availableWidth, computedWidths, availableHeight, computedHeights;
 66             log.info("resize template - parentWidth:", parentHtmlNode.clientWidth);
 67             templateNode = template.htmlNode;
 68             availableWidth = templateNode.clientWidth;
 69 
 70             if (!template.isLeaf()) {
 71                 computedWidths = generator.fn.computeColWidths(availableWidth, template);
 72 
 73                 gridNode = generator.fn.getGridNode(templateNode);
 74                 generator.fn.setGridNodeWidth(gridNode, computedWidths);
 75 
 76                 columnNode = generator.fn.getColumnNodes(gridNode, template.grid.colNumber);
 77                 generator.fn.setColNodeWidth(columnNode, computedWidths);
 78 
 79                 template.grid.rows.forEach(function (row, rowIndex) {
 80                     log.info("resize row:", row.rowText);
 81                     rowNode = generator.fn.getRowNode(gridNode, rowIndex, columnNode.length);
 82                     row.slots.forEach(function (slot, colIndex) {
 83                         log.info("resize slot:", slot.slotText);
 84                         if (template.grid.filledSlots[slot.slotText]) {
 85                             slotNode = slot.htmlNode;
 86                             generator.fn.setSlotNodeWidth(slotNode, computedWidths, colIndex);
 87                             template.grid.filledSlots[slot.slotText].forEach(function (childTemplate) {
 88                                 resizeTemplateWidth(childTemplate, slotNode);
 89                             });
 90                         }
 91                     });
 92                 });
 93             } else {
 94                 log.warn("leaf - no grid");
 95             }
 96             log.debug("template resize... [OK]");
 97         }
 98 
 99         function resizeTemplateHeight(template, parentHtmlNode) {
100             var templateNode, gridNode, columnNode, rowNode, slotNode, computedHeights;
101             log.info("resize template - parentHeight:", parentHtmlNode.clientHeight);
102             templateNode = template.htmlNode;
103 
104             if (!template.isLeaf()) {
105 //                computedHeights = generator.fn.computeRowHeights(template);
106                 gridNode = generator.fn.getGridNode(templateNode);
107 
108                 columnNode = generator.fn.getColumnNodes(gridNode, template.grid.colNumber);
109                 template.grid.rows.forEach(function (row, rowIndex) {
110                     log.info("resize row:", row.rowText);
111                     rowNode = generator.fn.getRowNode(gridNode, rowIndex, columnNode.length);
112 
113                     row.slots.forEach(function (slot, colIndex) {
114                         log.info("resize slot:", slot.slotText);
115                         if (template.grid.filledSlots[slot.slotText]) {
116                             slotNode = slot.htmlNode;
117 
118                             template.grid.filledSlots[slot.slotText].forEach(function (childTemplate) {
119                                 resizeTemplateHeight(childTemplate, slotNode);
120 
121                             });
122                             //todo:delete
123                             //generator.fn.setSlotNodeHeight(slotNode, computedHeights, rowIndex);
124 //                                    var zzz = slot.htmlNode.offsetHeight;
125 //                                    if(zzz>computedHeights.rowHeight[rowIndex]) {
126 //                                        computedHeights.rowHeight[rowIndex] = zzz;
127 //                                        generator.fn.setSlotNodeHeight(slotNode, computedHeights, rowIndex);
128 //                                    }
129                         }
130                         computedHeights = generator.fn.computeRowHeights(template);
131                         generator.fn.setSlotNodeHeight(slotNode, computedHeights, rowIndex);
132                     });
133                     computedHeights = generator.fn.computeRowHeights(template);
134                     generator.fn.setRowNodeHeight(rowNode, computedHeights, rowIndex);
135                 });
136                 computedHeights = generator.fn.computeRowHeights(template);
137                 generator.fn.setGridNodeHeight(gridNode, computedHeights);
138             } else {
139                 log.warn("leaf - no grid");
140             }
141             log.debug("template resize... [OK]");
142         }
143 
144         rootElement = document.querySelector(template.selectorText);
145         generateTemplate(template, rootElement.parentNode);
146         resizeTemplateWidth(template, rootElement.parentNode);
147         resizeTemplateHeight(template, rootElement.parentNode);
148     }
149 
150     /**
151      * Creates a template compiler
152      *
153      * @param {r}tom Template Object Model
154      *
155      * @class Generates HTML code that maps generated TOM to tables, then injects
156      * this code into  the page DOM.
157      * This version uses the old "table layout" method.
158      */
159     generator = function (tom) {
160         return new generator.prototype.init(tom);
161     };
162 
163     generator.prototype = {
164         /**
165          * Local copy of given TOM
166          * @type rootTemplate
167          */
168         tom:undefined,
169 
170         /**
171          * @ignore
172          * see constructor
173          */
174         init:function (tom) {
175             this.tom = tom;
176             return this;
177         },
178         /**
179          * Generates the HTML code and injects it into the page DOM.
180          * </p>
181          * Code generation is done in three steps:
182          * <ul>
183          *     <li>Step 1: Plain object generation</li>
184          *     <li>Step 2: Column width resize</li>
185          *     <li>Step 3: Ror height resize</li>
186          * </ul>
187          */
188         patchDOM:function () {
189             log.info("patch DOM...");
190             log.debug("TOM: ", this.tom);
191             generator.fn.resetCSS();
192             this.tom.rows.forEach(generateRootTemplate);
193         },
194         /**
195          * Resets browser default CSS styles.
196          */
197         resetCSS:function () {
198             var head = document.getElementsByTagName('head')[0],
199                 cssString = [
200                     ".templateLayout {" +
201                     "margin: 0;" +
202                     "padding: 0;" +
203                     "border: 0;" +
204                     "font-size: 100%;" +
205                     "font: inherit;" +
206                     "vertical-align: baseline;" +
207                     "line-height: 1;" +
208                     "border-collapse: collapse;" +
209                     "border-spacing: 0;" +
210                     "}"
211                 ],
212                 styleTag = document.createElement('style');
213             styleTag.setAttribute('type', 'text/css');
214             head.appendChild(styleTag);
215             styleTag.innerHTML = cssString;
216         },
217         /**
218          * Set grid width (the TABLE node)
219          *
220          * @param {HTMLElement}gridNode DOM node that maps grid element
221          * @param {Object}computedWidths column widths given by calculateColWidths()
222          * @param {integer}computedWidths.totalWidth table width
223          */
224         setGridNodeWidth:function (gridNode, computedWidths) {
225             gridNode.style.tableLayout = "fixed";
226             gridNode.width = computedWidths.totalWidth;
227         },
228         /**
229          * Set grid height (the TABLE node)
230          *
231          * @param {HTMLElement}gridNode DOM node that maps grid element
232          * @param {Object}computedHeights column widths given by calculateRowHeights()
233          * @param {integer}computedHeights.maxHeight table height
234          */
235         setGridNodeHeight:function (gridNode, computedHeights) {
236             gridNode.style.height = computedHeights.totalHeight + "px";
237             gridNode.style.maxHeight = computedHeights.totalHeight + "px";
238         },
239         /**
240          * Set columns width (the COL nodes)
241          *
242          * @param {HTMLElement[]}colNodes array of DOM nodes that maps column nodes
243          * @param {Object}computedWidths column widths given by calculateColWidths()
244          * @param {integer[]}computedWidths.colWidth array of column widths
245          */
246         setColNodeWidth:function (colNodes, computedWidths) {
247             colNodes.forEach(function (node, index) {
248                 node.width = computedWidths.colWidth[index];
249                 node.style.maxWidth = computedWidths.colWidth[index] + "px";
250             });
251         },
252         /**
253          * Set row height (the TR nodes)
254          *
255          * @param {HTMLElement}rowNode DOM node that maps row element
256          * @param {Object}computedHeights column widths given by calculateRowHeights()
257          * @param {integer[]}computedHeights.rowHeight array of row heights
258          * @param {integer}rowIndex row index
259          */
260         setRowNodeHeight:function (rowNode, computedHeights, rowIndex) {
261             rowNode.style.height = computedHeights.rowHeight[rowIndex] + "px";
262             rowNode.style.maxHeight = computedHeights.rowHeight[rowIndex] + "px";
263         },
264         /**
265          * Set slot width (the TD nodes)
266          *
267          * @param {HTMLElement}slotNode DOM node that maps slot element
268          * @param {Object}computedWidths column widths given by calculateColWidths()
269          * @param {integer[]}computedWidths.colWidth array of column widths
270          * @param {integer}colIndex slot colIndex.  See {@link gridSlot#colIndex}
271          */
272         setSlotNodeWidth:function (slotNode, computedWidths, colIndex) {
273             var i, width = 0;
274             for (i = 0; i < slotNode.colSpan; i++) {
275                 width += computedWidths.colWidth[colIndex + i];
276             }
277             slotNode.style.width = width + "px";
278             slotNode.style.maxWidth = width + "px";
279         },
280         /**
281          * Set slot height (the TD nodes)
282          * @param {HTMLElement}slotNode DOM node that maps slot element
283          * @param {Object}computedHeights column widths given by calculateRowHeights()
284          * @param {integer[]}computedHeights.rowHeight array of row heights
285          * @param {integer}rowIndex slot rowIndex.  See {@link gridSlot#rowIndex}
286          */
287         setSlotNodeHeight:function (slotNode, computedHeights, rowIndex) {
288             var i, height = 0;
289             for (i = 0; i < slotNode.rowSpan; i++) {
290                 height += computedHeights.rowHeight[rowIndex + i];
291             }
292             slotNode.style.overflow = "auto";
293 
294             slotNode.childNodes[0].style.height = height + "px";
295             slotNode.childNodes[0].style.maxHeight = height + "px";
296             slotNode.childNodes[0].style.overflow = "auto";
297         },
298         /**
299          * Get DOM node that maps template grid (TABLE node)
300          * @param {HTMLElement}templateNode template node
301          * @returns {HTMLElement} templateNode.childNodes[0]
302          */
303         getGridNode:function (templateNode) {
304             return templateNode.childNodes[0];
305         },
306 
307         /**
308          * Get DOM nodes that maps template grid "columns" (COL nodes)
309          * @param {HTMLElement}gridNode grid node
310          * @param {integer}maxColumns number of columns in grid
311          * @returns {HTMLElement[]} array of matching gridNode.childNodes[]
312          */
313         getColumnNodes:function (gridNode, maxColumns) {
314             var i, columnNodes = [];
315             for (i = 0; i < maxColumns; i++) {
316                 columnNodes.push(gridNode.childNodes[i]);
317             }
318             return columnNodes;
319         },
320         /**
321          * Get DOM node that maps row (TR nodes)
322          * @param {HTMLElement}gridNode grid node
323          * @param {integer}index row index
324          * @param {integer}maxColumns number of columns in grid
325          * @returns {HTMLElement[]} array of matching gridNode.childNodes[]
326          */
327         getRowNode:function (gridNode, index, maxColumns) {
328             return gridNode.childNodes[maxColumns + index];
329         },
330         /**
331          * Get pixel size. Currently converts "px" and "%"
332          * @param {string}dimension source in "75[px|%]" format
333          * @param {integer}max max size in pixels. Only used relative sizes ("%")
334          * @returns {integer} given size converted to pixels or Error
335          */
336         getPixels:function (dimension, max) {
337             var found = dimension.match(/(\d+)(px|%)/);
338             if (found[2] === "%") {
339                 return parseInt(found[1], 10) * max / 100;
340             }
341             if (found[2] === "px") {
342                 return parseInt(found[1], 10);
343             }
344         },
345         /**
346          * A lightly modified version of "compute heights" algorithm defined in
347          * the draft.
348          * </p>
349          * The good parts, it works (thanks to native table algorithms);
350          * the bad, needs further improvements
351          *
352          * @param {template}template the source template
353          * @returns {ComputedHeight} rows height
354          */
355         computeRowHeights:function (template) {
356             /**
357              *
358              * @namespace Computed template rows heights
359              * @name ComputedHeight
360              */
361             var result =
362             /**
363              * @lends ComputedHeight#
364              */
365             {
366                 /**
367                  * Sum of rows heights
368                  * @type integer
369                  */
370                 totalHeight:undefined,
371                 /**
372                  * Array of row heights
373                  * @type integer[]
374                  */
375                 rowHeight:[]
376             }, tmp, height = 0, fixedHeights = 0, relativeHeights = 0;
377 
378             tmp = template.grid.rows.map(function (row) {
379                 if (/(\d+)(px)/.test(row.height)) {
380                     return generator.fn.getPixels(row.height, 0);
381                 }
382                 return row.height;
383             }, this);
384 
385             template.grid.rows.forEach(function (row, rowIndex) {
386                 if (row.height === "*" || row.height === "auto") {
387                     tmp[rowIndex] = 0;
388                     row.slots.forEach(function (slot) {
389                         if (slot.rowSpan === 1) {
390                             var zzz = slot.htmlNode.offsetHeight;
391                             if (zzz > tmp[rowIndex]) {
392                                 tmp[rowIndex] = zzz;
393                             }
394                         }
395                     }, this);
396                 }
397             }, this);
398 
399             template.grid.rows.forEach(function (row, rowIndex) {
400                 if (row.height === "*") {
401                     if (tmp[rowIndex] > height) {
402                         height = tmp[rowIndex];
403                     }
404                 }
405             }, this);
406             template.grid.rows.forEach(function (row, rowIndex) {
407                 if (row.height === "*") {
408                     tmp[rowIndex] = height;
409                 }
410             }, this);
411 
412             tmp.forEach(function (height) {
413                 if (/(\d+)(%)/.test(height)) {
414                     var found = height.match(/(\d+)(%)/);
415                     relativeHeights += parseInt(found[1], 10);
416                 } else {
417                     fixedHeights += height;
418                 }
419             });
420             result.totalHeight = (fixedHeights * 100) / (100 - relativeHeights);
421             result.rowHeight = tmp;
422             return result;
423         },
424         /**
425          * A lightly modified version of "compute width" algorithm defined in
426          * the draft.
427          * </p>
428          * The good parts, it works (thanks to native table algorithms);
429          * the bad, needs further improvements
430          *
431          * @param {Number}availableWidth parent node max width
432          * @param {template}template the source template
433          * @returns {ComputedWidth} columns width
434          */
435         computeColWidths:function (availableWidth, template) {
436             /**
437              *
438              * @namespace Computed template columns widths
439              * @name ComputedWidth
440              * */
441             var result =
442             /**
443              * @lends ComputedWidth#
444              */
445             {
446                 /**
447                  * Sum of columns widths
448                  * @type integer
449                  */
450                 totalWidth:undefined,
451                 /**
452                  * Array of columns widths
453                  * @type integer[]
454                  */
455                 colWidth:[]
456             }, gridMinWidth, flexibleColCounter = 0, fixedColSum = 0, flexibleWidth = 0, gridFinalWidth = 0;
457 
458 
459             template.grid.widths.forEach(function (colWidth) {
460                 if (colWidth === "*") {
461                     flexibleColCounter++;
462                 } else {
463                     fixedColSum += generator.fn.getPixels(colWidth, availableWidth);
464                 }
465             });
466             flexibleWidth = (flexibleColCounter > 0) ? (availableWidth - fixedColSum) / flexibleColCounter : 0;
467 
468             gridMinWidth = template.grid.minWidths.reduce(function (previous, colMinWidth) {
469                 return previous + generator.fn.getPixels(colMinWidth, availableWidth);
470             }, 0);
471 
472             if (gridMinWidth > availableWidth) {
473                 result.totalWidth = availableWidth;
474                 result.colWidth = template.grid.minWidths.map(function (col) {
475                     return generator.fn.getPixels(col, availableWidth);
476                 });
477             } else {
478                 result.colWidth = template.grid.widths.map(function (width) {
479                     var tmp;
480                     if (width === "*") {
481                         gridFinalWidth += flexibleWidth;
482                         return flexibleWidth;
483                     }
484                     if (/(\d+)(px|%)/.test(width)) {
485                         //minWidth==width==preferredWidth
486                         tmp = generator.fn.getPixels(width, availableWidth);
487                         gridFinalWidth += tmp;
488                         return tmp;
489                     }
490                     //no more use cases
491                 });
492                 result.totalWidth = gridFinalWidth;
493             }
494             return result;
495         },
496         /**
497          * Create the grid node (using TABLE) and inject it into parent node.
498          * Uses appendCol to create column nodes too.
499          * @param {grid} grid template grid
500          * @param {HTMLElement}parentNode DOM parent node
501          * @returns {HTMLElement} current node
502          */
503         appendGrid:function (grid, parentNode) {
504             var gridNode = document.createElement("table");
505             gridNode.className = "templateLayout templateLayoutTable";
506             parentNode.appendChild(gridNode);
507             generator.fn.appendCol(gridNode, grid.colNumber);
508             return gridNode;
509         },
510         /**
511          * Create columns nodes (using COL) and inject it into parent node
512          * @param {HTMLElement}parentNode DOM parent node
513          * @param {integer}colNumber max number of columns
514          * @returns {HTMLElement} current node
515          */
516         appendCol:function (parentNode, colNumber) {
517             var i, colNode;
518             for (i = 0; i < colNumber; i++) {
519                 colNode = document.createElement("col");
520                 colNode.className = "templateLayout templateLayoutCol";
521                 parentNode.appendChild(colNode);
522             }
523         },
524         /**
525          * Create a row node (using TR) and inject it into parent node
526          * @param {gridRow}row template row
527          * @param {HTMLElement}parentNode DOM parent node
528          * @returns {HTMLElement} current node
529          */
530         appendRow:function (row, parentNode) {
531             var rowNode = document.createElement("tr");
532             rowNode.className = "templateLayout templateLayoutRow";
533             parentNode.appendChild(rowNode);
534             return rowNode;
535         },
536         /**
537          * Create a slot node (using TD) and inject it into parent node
538          * @param {gridSlot}slot template slot
539          * @param {HTMLElement}parentNode DOM parent node
540          * @returns {HTMLElement} current node
541          */
542         appendSlot:function (slot, parentNode) {
543             //create container
544             var cellNode, overflowNode;
545             cellNode = document.createElement("td");
546             cellNode.className = "templateLayout templateLayoutSlot";
547             slot.htmlNode = cellNode;
548             parentNode.appendChild(cellNode);
549 
550             if (slot.rowSpan > 1) {
551                 cellNode.rowSpan = slot.rowSpan;
552             }
553             if (slot.colSpan > 1) {
554                 cellNode.colSpan = slot.colSpan;
555             }
556 
557             overflowNode = document.createElement("div");
558             overflowNode.className = "templateLayout templateOverflow";
559             cellNode.appendChild(overflowNode);
560             return overflowNode;
561         },
562         /**
563          * Create a virtual node (using DIV) and inject it into parent node.
564          *
565          * Virtual nodes are valid templates that doesn't match selector queries
566          * because doesn´t have HTML content (used in nested templates chains).
567          * </p>
568          * <pre>
569          *     #parent {display: "aa" "bc" "dd"}
570          *     #header {position:a; display: "123"}
571          *     #logo {position:1}
572          *     ...
573          *     <div id="parent"></div>
574          *     <img id="logo" src="logo.png"/>
575          *     ...
576          * </pre>
577          * #parent maps to DIV element and #logo to image, but #header maps
578          * nothing, its a template used only to simplify layout. #header needs
579          * to create a virtual node.
580          *
581          * @param {HTMLElement}parentNode DOM parent node
582          * @returns {HTMLElement} current node
583          */
584         appendVirtualNode:function (parentNode) {
585             var extraNode = document.createElement("div");
586             extraNode.className = "templateLayout templateLayoutVirtualNode";
587             parentNode.appendChild(extraNode);
588             return extraNode;
589         },
590         /**
591          * Get template node and inject it into parent node.
592          * Template node is get using a CSS selector query OR if query fails calling {@link generator#appendVirtualNode}
593          * @param {template}template the template
594          * @param {HTMLElement}parentNode DOM parent node
595          * @returns {HTMLElement} current node
596          */
597         appendTemplate:function (template, parentNode) {
598             var templateNode = document.querySelector(template.selectorText) || generator.fn.appendVirtualNode(parentNode);
599             template.htmlNode = templateNode;
600             if (templateNode.parentNode !== parentNode) {
601                 parentNode.appendChild(templateNode);
602             }
603             return templateNode;
604         }
605     };
606 
607     generator.fn = generator.prototype;
608     generator.prototype.init.prototype = generator.prototype;
609 
610     templateLayout.fn.generator = generator;
611 
612     log.info("generator module load... [OK]");
613 
614 })(window.templateLayout);