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