1 /*! 2 * templateLayout.compiler 3 * Copyright (c) 2011 Pablo Escalada 4 * MIT Licensed 5 */ 6 (function (templateLayout) { 7 var compiler, log, rootTemplate; 8 log = wef.logger("templateLayout.compiler"); 9 log.info("load compiler module"); 10 11 function parseDisplay(displayValue) { 12 /* 13 * Name: ‘display’ 14 * New value: <display-type>? && [ [ <string> [ / <row-height> ]? ]+ ] <col-width>* 15 * Percentages: N/A 16 * Computed value: specified value 17 * 18 * The <display-type> is one of the following keywords. An omitted keyword is equivalent to ‘block’. 19 * <display-type> = inline | block | list-item | inline-block | table | inline-table 20 * | table-row-group | table-header-group | table-footer-group | table-row 21 * | table-column-group | table-column | table-cell | table-caption | none 22 * 23 * Each <string> consist of one or more at signs (“@”), letters (or digits, see <letter> below), 24 * periods (“.”) and spaces 25 * 26 * Each <row-height> sets the height of the preceding row. The default is ‘auto’. 27 * The values can be as follows: 28 * <length> An explicit height for that row. Negative values make the template illegal. If the length is 29 * expressed in ‘gr’ units, these refer to the inherited grid, not the grid defined by the template itself. 30 * auto The row's height is determined by its contents. 31 * * (asterisk) All rows with an asterisk will be of equal height. 32 * 33 * Each <col-width> can be one of the following: 34 * <length> An explicit width for that column. Negative values make the template illegal. 35 * * (asterisk.) All columns with a ‘*’ have the same width. See the algorithm below. 36 * max-content, min-content, minmax(p,q), fit-content 37 */ 38 log.info("compiling display..."); 39 log.debug("display source: ", displayValue); 40 /** 41 * @namespace Preprocessed template display info 42 * @name DisplayMetadata 43 */ 44 var displayMetadata = 45 /** 46 * @lends DisplayMetadata# 47 */ 48 { 49 /** 50 * Display type. Currently unused 51 * @type string 52 */ 53 displayType:undefined, 54 /** 55 * Array of rows. strings are cleaned 56 * @type string[] 57 */ 58 grid:[], 59 /** 60 * Array of columnns widths 61 * @type string[] 62 */ 63 widths:[] 64 }, 65 allRefExp = /\s*(none|inline)?\s*(:?(\"[A-Za-z0-9\.@ ]+\")\s*(:?\/ ?(\*|\d+(:?px|%)))?)\s*((:?(:?(:?\d+(:?px|%))|\*)\s*)*)/gi, 66 found, 67 displayTypeFound, 68 gridNotFound, 69 completed; 70 //all without displayType (:?(\"[A-Za-z0-9\.@ ]+\")\s*(:?\/(\*|\d+(:?em|px|%)))?\s*)((:?(:?(:?\d+(:?em|px|%))|\*) *)*) 71 // /\s*(inline|block|list-item|inline-block|table|inline-table|table-row-group|table-header-group|table-footer-group|table-row|table-column-group|table-column|table-cell|table-caption|none)?\s*(:?(\"[A-Za-z0-9\.@ ]+\")\s*(:?\/(\*|\d+(:?em|px|%)))?\s*)((:?(:?(:?\d+(:?em|px|%))|\*) *)*)/g 72 if (displayValue !== undefined) { 73 while ((found = allRefExp.exec(displayValue)) !== null) { 74 if (completed) { 75 log.error("Invalid template, invalid width definition"); 76 throw new Error("Invalid template, width definition"); 77 } 78 if (found[1]) { 79 if (displayTypeFound) { 80 log.error("Invalid template, multiple display type"); 81 throw new Error("Invalid template, multiple display type"); 82 } 83 displayMetadata.displayType = found[1]; 84 displayTypeFound = true; 85 } 86 if (found[3]) { 87 if (gridNotFound) { 88 log.error("Invalid template, invalid grid definition"); 89 throw new Error("Invalid template, invalid grid definition"); 90 } 91 displayMetadata.grid.push({rowText:found[3].replace(/"/g, "").replace(/\s*/, ""), height:undefined}); 92 } else { 93 gridNotFound = true; 94 } 95 if (found[5]) { 96 if (!displayMetadata.grid[displayMetadata.grid.length - 1]) { 97 log.error("Invalid template, invalid height definition"); 98 throw new Error("Invalid template, height definition"); 99 } 100 displayMetadata.grid[displayMetadata.grid.length - 1].height = found[5]; 101 } 102 if (found[7]) { 103 displayMetadata.widths = found[7].split(/\s+/); 104 completed = true; 105 } 106 } 107 } 108 log.info("display result: ", displayMetadata); 109 return displayMetadata; 110 } 111 112 /** 113 * @namespace Top level template. Used as "fake page template" 114 */ 115 rootTemplate = function () { 116 117 var that = 118 /** 119 * @lends rootTemplate# 120 */ 121 { 122 /** 123 * Inserts given template into TOM. 124 * If template "isRoot", inserts here, else looks inside TOM and 125 * inserts in place. 126 * 127 * @param {Template}aTemplate the template 128 */ 129 insert:function (aTemplate) { 130 log.info("add template ", aTemplate.selectorText); 131 if (aTemplate.isRoot()) { 132 log.debug("insert as root", aTemplate); 133 that.rows.push(aTemplate); 134 return true; 135 } else { 136 log.debug("search position :", aTemplate.position.position); 137 return that.rows.some(function (element) { 138 return element.insert(aTemplate); 139 }); 140 } 141 }, 142 /** 143 * Templates stored into the root template 144 * @type gridRow[] 145 */ 146 rows:[] 147 }; 148 149 return that; 150 }(); 151 152 function parsePosition(positionValue) { 153 var positionMetadata, matched, positionRegExp; 154 /* 155 * Name: position 156 * New value: <letter> | same 157 * Percentages: N/A 158 * Computed value: ‘<letter>’ or ‘static’; see text 159 * 160 * <letter> must be a single letter or digit, with category Lu, Ll, Lt or Nd in Unicode [UNICODE]), 161 * or a “@” symbol 162 */ 163 log.info("compiling position..."); 164 log.debug("position source: ", positionValue); 165 /** 166 * @namespace Preprocessed template position info 167 * @name PositionMetadata 168 */ 169 positionMetadata = 170 /** 171 * @lends PositionMetadata# 172 */ 173 { 174 /** 175 * Position string 176 * @type string 177 */ 178 position:null 179 }; 180 positionRegExp = /^\s*(same|[a-zA-Z0-9])\s*$/i; 181 if (positionValue !== undefined) { 182 matched = positionValue.match(positionRegExp); 183 if (matched === null) { 184 log.info("Unexpected value at ", positionValue); 185 //throw new Error("Unexpected value at ", positionValue); 186 return positionMetadata; 187 } 188 positionMetadata.position = matched[1]; 189 } 190 log.info("position result: ", positionMetadata); 191 return positionMetadata; 192 } 193 194 function parseProperties(rule) { 195 var preProcessTemplate = {}; 196 log.info("compiling properties..."); 197 log.debug("properties source: ", rule); 198 preProcessTemplate.selectorText = rule.selectorText; 199 preProcessTemplate.display = parseDisplay(rule.declaration[templateLayout.fn.constants.DISPLAY]); 200 preProcessTemplate.position = parsePosition(rule.declaration[templateLayout.fn.constants.POSITION]); 201 log.info("properties result: ", preProcessTemplate); 202 return preProcessTemplate; 203 } 204 205 /** 206 * Creates a compiler 207 * 208 * @class CSS template compiler 209 */ 210 compiler = function () { 211 return new compiler.prototype.init(); 212 }; 213 214 /** 215 * Extension point. 216 * Elements added to compiler.fn extend compiler functionality 217 */ 218 compiler.fn = compiler.prototype; 219 220 compiler.prototype = { 221 constructor:compiler, 222 /** 223 * @ignore 224 */ 225 init:function () { 226 return this; 227 }, 228 /** 229 * Compiles given parser data</p> 230 * 231 * @param {ParserBufferEntry[]}buffer parser generated data 232 * @returns {rootTemplate} Template Object Model 233 */ 234 compile:function (buffer) { 235 var selectorText, preProcessTemplate, inserted, template; 236 log.info("compile..."); 237 log.debug("buffer: ", buffer); 238 239 for (selectorText in buffer) { 240 if (buffer.hasOwnProperty(selectorText)) { 241 log.debug("next buffer element: ", selectorText); 242 log.group(); 243 preProcessTemplate = parseProperties(buffer[selectorText]); 244 if (this.isEmptyDisplay(preProcessTemplate.display) && this.isEmptyPosition(preProcessTemplate.position)) { 245 log.groupEnd(); 246 log.info("preProcess: empty template", preProcessTemplate); 247 } else { 248 log.debug("preProcess:", preProcessTemplate); 249 template = compiler.fn.templateBuilder(preProcessTemplate).createTemplate(); 250 inserted = rootTemplate.insert(template); 251 log.groupEnd(); 252 log.info("element insertion...", inserted ? "[OK]" : "ERROR!"); 253 } 254 } 255 } 256 log.debug("compile... OK"); 257 return rootTemplate; 258 }, 259 /** 260 * Checks if display is empty 261 * @param {DisplayMetadata}display compiled display 262 * @returns {boolean}true if display.grid.length === 0 263 */ 264 isEmptyDisplay:function (display) { 265 return display.grid.length === 0; 266 }, 267 /** 268 * Checks is position is empty 269 * @param {PositionMetadata}position compiled position 270 * @returns {boolean}true if position.position === null 271 */ 272 isEmptyPosition:function (position) { 273 return position.position === null; 274 } 275 }; 276 277 compiler.prototype.init.prototype = compiler.prototype; 278 279 templateLayout.fn.compiler = compiler; 280 281 (function (global) { 282 var gridSlot; 283 log.info("load gridSlot module..."); 284 285 /** 286 * Creates a slot. 287 * 288 * @param {string}slotText slot identifier 289 * @param {integer}rowIndex row index 290 * @param {integer}colIndex column index 291 * @param {Object}[options] optional initialization 292 * @param {integer}options.rowSpan row span number 293 * @param {integer}options.colSpan column span number 294 * @param {boolean}options.allowDisconnected row span number 295 * @param {boolean}options.allowColSpan row span number 296 * @param {boolean}options.allowRowSpan row span number 297 * 298 * @class Template slot. 299 * Features: 300 * <ul> 301 * <li>column and row span</li> 302 * <li>disconnected regions</li> 303 * </ul> 304 */ 305 gridSlot = function (slotText, rowIndex, colIndex, options) { 306 log.debug("slot", slotText + "..."); 307 return new gridSlot.prototype.init(slotText, rowIndex, colIndex, options); 308 }; 309 310 gridSlot.prototype = { 311 constructor:gridSlot, 312 /** 313 * slot identifier 314 * @type string 315 */ 316 slotText:undefined, 317 /** 318 * row index. If row spans, topmost row index 319 * @type integer 320 */ 321 rowIndex:undefined, 322 /** 323 * column index. If column spans, leftmost column index 324 * @type integer 325 */ 326 colIndex:undefined, 327 /** 328 * row span number 329 * @type integer 330 */ 331 rowSpan:1, 332 /** 333 * column span number 334 * @type integer 335 */ 336 colSpan:1, 337 /** 338 * Can exists more than one group of slots with the same identifier? 339 * @type boolean 340 */ 341 allowDisconnected:false, 342 /** 343 * is column span allowed? 344 * @type boolean 345 */ 346 allowColSpan:false, 347 /** 348 * is row span allowed? 349 * @type boolean 350 */ 351 allowRowSpan:false, 352 /** 353 * HTML node that maps the slot 354 * @type HTMLElement 355 */ 356 htmlNode:undefined, 357 /** 358 * Stores the sum of children heights 359 * @type integer 360 */ 361 contentHeight:0, 362 363 /** 364 * @ignore 365 * see gridSlot.constructor 366 */ 367 init:function (slotText, rowIndex, colIndex, options) { 368 this.slotText = slotText; 369 this.rowIndex = rowIndex; 370 this.colIndex = colIndex; 371 this.contentHeight = 0; 372 //options 373 this.rowSpan = 1; 374 this.colSpan = 1; 375 this.height = "auto"; 376 377 this.allowDisconnected = false; 378 this.allowColSpan = true; 379 this.allowRowSpan = true; 380 this.htmlNode = undefined; 381 wef.extend(this, options, ["rowSpan", "colSpan", "allowDisconnected", "allowColSpan", "allowRowSpan"]); 382 } 383 }; 384 385 /** 386 * Extension point 387 */ 388 gridSlot.fn = gridSlot.prototype; 389 gridSlot.prototype.init.prototype = gridSlot.prototype; 390 391 global.gridSlot = gridSlot; 392 log.info("load gridSlot module... [OK]"); 393 })(compiler.fn); 394 395 396 (function (global) { 397 var gridRow; 398 log.info("load gridRow module..."); 399 /** 400 * Creates a row 401 * 402 * @param {string}rowText row slots identifiers 403 * @param {integer}rowIndex row index 404 * @param {gridSlot[]}slots row gridSlot elements 405 * @param {Object}[options] optional initialization 406 * @param {string}options.height row height as string 407 * 408 * @class Template row. Store {@link gridSlot} elements 409 */ 410 gridRow = function (rowText, rowIndex, slots, options) { 411 log.debug("row..."); 412 return new gridRow.prototype.init(rowText, rowIndex, slots, options); 413 }; 414 gridRow.prototype = { 415 constructor:gridRow, 416 /** 417 * Row slots identifiers 418 * @type string 419 */ 420 rowText:undefined, 421 /** 422 * Row index 423 * @type integer 424 */ 425 rowIndex:undefined, 426 /** 427 * Slots row gridSlot elements 428 * @type gridSlot[] 429 */ 430 slots:[], 431 /** 432 * Number of slots in row 433 * @type integer 434 */ 435 length:undefined, 436 /** 437 * Row height as string 438 * @type string 439 */ 440 height:undefined, 441 /** 442 * @ignore 443 * see constructor 444 */ 445 init:function (rowText, rowIndex, slots, options) { 446 this.rowText = rowText; 447 this.rowIndex = rowIndex; 448 this.slots = slots; 449 this.length = this.rowText.length; 450 //options 451 this.height = undefined; 452 wef.extend(this, options, ["height"]); 453 } 454 }; 455 /** 456 * Extension point 457 */ 458 gridRow.fn = gridRow.prototype; 459 gridRow.prototype.init.prototype = gridRow.prototype; 460 461 global.gridRow = gridRow; 462 log.info("load gridRow module... [OK]"); 463 })(compiler.fn); 464 465 466 (function (global) { 467 var grid; 468 log.info("load grid module..."); 469 /** 470 * Creates a template grid 471 * 472 * @param {gridRow[]}rows template rows 473 * @param [options] optional initialization 474 * 475 * @class Template grid, represented as a tabular structure 476 */ 477 grid = function (rows, options) { 478 log.debug("grid..."); 479 return new grid.prototype.init(rows, options); 480 }; 481 482 grid.prototype = { 483 constructor:grid, 484 /** 485 * Template rows 486 * @type gridRow[] 487 */ 488 rows:undefined, 489 /** 490 * Hash table like structure that stores nested Template objects. 491 * <ul> 492 * <li>key = template position</li> 493 * <li>value = array of template objects</li> 494 * <ul> 495 * 496 * @type Object 497 */ 498 filledSlots:undefined, 499 /** 500 * columns widths 501 * @type string[] 502 */ 503 widths:[], 504 /** 505 * minimums columns widths 506 * @type string[] 507 */ 508 minWidths:[], 509 /** 510 * preferred columns widths 511 * @type string[] 512 */ 513 preferredWidths:[], 514 /** 515 * Number of rows 516 * @type integer 517 */ 518 rowNumber:undefined, 519 /** 520 * Number of columns 521 * @type integer 522 */ 523 colNumber:undefined, 524 /** 525 * @ignore 526 */ 527 init:function (rows, options) { 528 this.rows = rows; 529 this.filledSlots = {}; 530 //options 531 this.widths = []; 532 this.minWidths = []; 533 this.preferredWidths = []; 534 wef.extend(this, options, ["widths", "minWidths", "preferredWidths"]); 535 this.colNumber = this.widths.length; 536 this.rowNumber = rows.length; 537 }, 538 /** 539 * Checks if grid contains specified slot identifier 540 * 541 * @param {string}slotIdentifier slot identifier 542 * @returns {boolean} true if rows[i].rowText contains slotIdentifier, 543 * else if not 544 */ 545 hasSlot:function hasSlot(slotIdentifier) { 546 var result; 547 result = this.rows.some(function (row) { 548 var regExp = new RegExp(slotIdentifier); 549 return regExp.exec(row.rowText); 550 }); 551 log.debug("hasSlot " + slotIdentifier + "?", result ? "yes" : "no"); 552 return result; 553 }, 554 /** 555 * Gets the "@" slot OR topmost left slot 556 */ 557 getDefaultSlot:function () { 558 var firstLetterSlot, definedDefaultSlot = false; 559 this.rows.forEach(function (row) { 560 if (definedDefaultSlot) { 561 return; //skip row 562 } 563 Array.prototype.some.call(row.rowText, function (slotText, slotIndex) { 564 if (slotText === "@") { 565 definedDefaultSlot = row.slots[slotIndex]; 566 return true; 567 } 568 if (!firstLetterSlot && slotText !== ".") { 569 firstLetterSlot = row.slots[slotIndex]; 570 return false; //continue searching @ 571 } 572 }); 573 }); 574 return definedDefaultSlot || firstLetterSlot; 575 }, 576 /** 577 * Traverses this grid and its children and insert the given 578 * template in place 579 * @param {template}aTemplate given template 580 * @returns {boolean} true if inserted, false if not 581 */ 582 setTemplate:function (aTemplate) { 583 var row, tmp, result; 584 if (this.hasSlot(aTemplate.position.position)) { 585 //push template 586 tmp = this.filledSlots[aTemplate.position.position] || []; 587 tmp.push(aTemplate); 588 this.filledSlots[aTemplate.position.position] = tmp; 589 log.debug("grid [" + aTemplate.position.position + "] =", aTemplate); 590 return true; 591 } else { 592 result = this.rows.some(function (row) { 593 var result; 594 result = row.slots.some(function (slotId) { 595 var result; 596 result = this.filledSlots[slotId.slotText] && this.filledSlots[slotId.slotText].some(function (currentTemplate) { 597 return !currentTemplate.isLeaf() && currentTemplate.insert(aTemplate); 598 }, this); 599 if (!result) { 600 log.debug("not found, try another slot"); 601 } 602 return result; 603 }, this); 604 if (!result) { 605 log.debug("not found, try another row"); 606 } 607 return result; 608 }, this); 609 if (!result) { 610 log.debug("not found, try another branch"); 611 } 612 return result; 613 } 614 } 615 }; 616 /** 617 * Extension point 618 */ 619 grid.fn = grid.prototype; 620 grid.prototype.init.prototype = grid.prototype; 621 622 global.grid = grid; 623 log.info("load grid module... [OK]"); 624 })(compiler.fn); 625 626 (function (global) { 627 var template; 628 log.info("load template module..."); 629 /** 630 * Creates a template 631 * 632 * @param {string}selectorText CSS selector 633 * @param {PositionMetadata}position raw position information 634 * @param {DisplayMetadata}display raw display information 635 * @param {grid}grid its physical structure 636 * 637 * @class A Template has a grid, the raw information generated by 638 * the preprocessor and a link to the DOM reference node 639 */ 640 template = function (selectorText, position, display, grid) { 641 log.debug("template..."); 642 return new template.prototype.init(selectorText, position, display, grid); 643 }; 644 645 template.prototype = { 646 constructor:template, 647 /** 648 * Link to parent template. Unused 649 */ 650 parentTemplate:undefined, 651 /** 652 * CSS selector 653 * @type string 654 */ 655 selectorText:undefined, 656 /** 657 * Raw display information 658 * @type DisplayMetadata 659 */ 660 display:undefined, 661 /** 662 * Raw position information 663 * @type PositionMetadata 664 */ 665 position:undefined, 666 /** 667 * Its grid, the physical representation 668 * @type grid 669 */ 670 grid:undefined, 671 /** 672 * A link to the DOM reference node 673 * @type HTMLElement 674 */ 675 htmlNode:undefined, 676 /** 677 * @ignore 678 */ 679 init:function (selectorText, position, display, grid) { 680 this.selectorText = selectorText; 681 this.display = display; 682 this.position = position; 683 this.grid = grid; 684 this.htmlNode = undefined; 685 }, 686 /** 687 * Checks if has parent 688 * 689 * @returns {boolean} true if doesn't have a position value, 690 * false if not 691 */ 692 isRoot:function () { 693 return this.position.position === null; 694 }, 695 /** 696 * Checks if has children 697 * 698 * @returns {boolean} true if has a grid value, false if not 699 */ 700 isLeaf:function () { 701 return this.display.grid.length === 0; 702 }, 703 /** 704 * Insert given template in this template or its children. 705 * Calls grid.setTemplate() 706 * 707 * @param aTemplate given template 708 * @returns {boolean} true if inserted, false if not 709 */ 710 insert:function (aTemplate) { 711 log.debug("trying to insert into ", this); 712 return this.grid.setTemplate(aTemplate); 713 } 714 }; 715 /** 716 * Extension point 717 */ 718 template.fn = template.prototype; 719 template.prototype.init.prototype = template.prototype; 720 721 global.template = template; 722 log.info("load template module... [OK]"); 723 })(compiler.fn); 724 725 (function (global) { 726 var templateBuilder; 727 log.info("load templateBuilder module..."); 728 729 /** 730 * Creates a gridBuffer 731 * 732 * @class {@link templateBuilder} temporal data structure. 733 * Only for internal use. Doesn´t check array index 734 * @name GridBuffer 735 */ 736 function GridBuffer() { 737 this._rows = []; 738 } 739 740 GridBuffer.prototype = 741 /** 742 * @lends GridBuffer# 743 */ 744 { 745 constructor:GridBuffer, 746 /** 747 * Array of rows 748 * @type gridRow[] 749 * @public 750 */ 751 _rows:[], 752 /** 753 * Add the slot to _rows 754 * @param {gridSlot}slot the slot 755 */ 756 add:function (slot) { 757 if (!Array.isArray(this._rows[slot.rowIndex])) { 758 this._rows[slot.rowIndex] = []; 759 } 760 this._rows[slot.rowIndex][slot.colIndex] = slot; 761 }, 762 /** 763 * Gets the slot with the specified slot Text 764 * @param {string}slotText slot text 765 * @returns {gridSlot} the specified slot 766 */ 767 getSlot:function (slotText) { 768 var result = []; 769 this._rows.forEach(function (row) { 770 row.forEach(function (slot) { 771 if (slot.slotText === slotText) { 772 result.push(slot); 773 } 774 }, this); 775 }, this); 776 return result; 777 }, 778 /** 779 * Gets rows 780 * @returns {gridRow[]} _rows 781 */ 782 getRows:function () { 783 return this._rows; 784 }, 785 /** 786 * Gets the specified row in _rows 787 * @param index row index 788 * @returns {gridRow} the specified row 789 */ 790 getRow:function (index) { 791 return this._rows[index]; 792 } 793 }; 794 795 /** 796 * @class 797 */ 798 templateBuilder = function (source) { 799 log.debug("templateBuilder..."); 800 return new templateBuilder.prototype.init(source); 801 }; 802 803 templateBuilder.prototype = { 804 constructor:templateBuilder, 805 buffer:undefined, 806 source:undefined, 807 init:function (source) { 808 this.source = source; 809 this.buffer = new GridBuffer(); 810 }, 811 createTemplate:function () { 812 var display, grid, gridRows, gridOptions = {}, heights; 813 display = this.source.display; 814 grid = null; 815 if (display.grid.length > 0) { 816 this._addGrid(this.source.display); 817 818 heights = this._normalizeHeights(this.source.display); 819 gridRows = this.buffer.getRows().map(function (row, index) { 820 return compiler.fn.gridRow(this.source.display.grid[index].rowText, index, row, {height:heights[index]}); 821 }, this); 822 823 gridOptions.widths = this._normalizeWidths(this.source.display.widths); 824 gridOptions.minWidths = gridOptions.widths.map(function (col) { 825 return templateBuilder.fn._getMinWidth(col); 826 }); 827 gridOptions.preferredWidths = gridOptions.widths.map(function (col) { 828 return templateBuilder.fn._getPreferredWidth(col); 829 }); 830 grid = compiler.fn.grid(gridRows, gridOptions); 831 } else { 832 //TODO:fixme 833 } 834 835 return compiler.fn.template(this.source.selectorText, this.source.position, display, grid); 836 }, 837 _addGrid:function (display) { 838 if (display.grid.length > 0) { 839 display.grid.forEach(function (row, rowIndex) { 840 this._addGridRow(row, rowIndex); 841 }, this); 842 } 843 }, 844 checkRowSpan:function (slotText, rowIndex, colIndex) { 845 var previousRow, oldRowSpan, candidateRowSpan, slotGroups; 846 slotGroups = this.buffer.getSlot(slotText); 847 if (!slotGroups[0].allowRowSpan) { 848 log.info("Slot don't allow row span"); 849 return false; 850 } 851 852 if (rowIndex > 0) { 853 return slotGroups.some(function (slot) { 854 previousRow = this.source.display.grid[rowIndex - 1]; 855 if (previousRow.rowText.charAt(colIndex) === slotText) { 856 oldRowSpan = slot.rowSpan; 857 candidateRowSpan = (rowIndex - slot.rowIndex) + 1; 858 if (candidateRowSpan == oldRowSpan) { 859 //do nothing 860 log.debug("Slot row span... [OK]"); 861 return true; 862 } 863 if (candidateRowSpan == oldRowSpan + 1) { 864 slot.rowSpan++; 865 this.buffer.add(slot); 866 log.debug("Slot row span... [OK]"); 867 return true; 868 } else { 869 return false; 870 } 871 } else { 872 return false; 873 } 874 }, this); 875 } else { 876 return false; 877 } 878 }, 879 checkColSpan:function (slotText, row, colIndex) { 880 var slotGroups, oldColSpan, candidateColSpan; 881 slotGroups = this.buffer.getSlot(slotText); 882 if (!slotGroups[0].allowColSpan) { 883 log.info("Slot don't allow col span"); 884 return false; 885 } 886 if (colIndex > 0) { 887 return slotGroups.some(function (slot) { 888 if (row.rowText[colIndex - 1] === slotText) { 889 oldColSpan = slot.colSpan; 890 candidateColSpan = (colIndex - slot.colIndex) + 1; 891 if (candidateColSpan == oldColSpan + 1) { 892 slot.colSpan++; 893 this.buffer.add(slot); 894 log.debug("Slot col span... [OK]"); 895 return true; 896 } else { 897 return false; 898 } 899 } else { 900 return false; 901 } 902 }, this); 903 } else { 904 return false; 905 } 906 }, 907 _addGridRow:function (row, rowIndex) { 908 var identifiers, colSpan, rowSpan, regions; 909 identifiers = Array.prototype.map.call(row.rowText, function (substring) { 910 return substring.charAt(0); 911 }); 912 913 identifiers.forEach(function (slotText, colIndex) { 914 //slot doesn't exist 915 if (this.buffer.getSlot(slotText).length === 0) { 916 this._addGridSlot(slotText, rowIndex, colIndex, 1, 1); 917 return; //no span or region violations 918 } 919 920 if (this.buffer.getSlot(slotText)[0].allowColSpan) { 921 colSpan = this.checkColSpan(slotText, row, colIndex); 922 } else { 923 colSpan = false; 924 } 925 926 if (this.buffer.getSlot(slotText)[0].allowRowSpan) { 927 rowSpan = this.checkRowSpan(slotText, rowIndex, colIndex); 928 } else { 929 rowSpan = false; 930 } 931 932 if (this.buffer.getSlot(slotText)[0].allowDisconnected) { 933 //TODO: region check 934 log.info("Slot allow disconnected regions"); 935 this._addGridSlot(slotText, rowIndex, colIndex, 1, 1); 936 regions = true; 937 } else { 938 regions = false; 939 } 940 941 if (!colSpan && !rowSpan && !regions) { 942 throw new Error("Invalid template definition at \"" + slotText + "\""); 943 } 944 945 }, this); 946 }, 947 _addGridSlot:function (slotText, rowIndex, colIndex, rowSpan, colSpan) { 948 var options, allowDisconnected, allowColSpan, allowRowSpan; 949 950 if (slotText === ".") { 951 allowDisconnected = true; 952 allowColSpan = false; 953 allowRowSpan = false; 954 } else { 955 allowDisconnected = false; 956 allowColSpan = true; 957 allowRowSpan = true; 958 } 959 options = { 960 rowSpan:rowSpan, 961 colSpan:colSpan, 962 allowDisconnected:allowDisconnected, 963 allowColSpan:allowColSpan, 964 allowRowSpan:allowRowSpan 965 }; 966 this.buffer.add(compiler.fn.gridSlot(slotText, rowIndex, colIndex, options)); 967 }, 968 _getMinWidth:function (width) { 969 if (width === "*") { 970 return "0px"; 971 } else { 972 return width; 973 } 974 }, 975 _getPreferredWidth:function (width) { 976 if (width === "*") { 977 return "9999px"; 978 } else { 979 return width; 980 } 981 }, 982 _normalizeWidths:function (widths) { 983 var i, tmp = [], dotColumn; 984 985 function checkDotColumn(row) { 986 return row.rowText.charAt(i) === "."; 987 } 988 989 for (i = 0; i < this._getMaxColNumber(this.source.display.grid); i++) { 990 if (widths[i] === undefined) { 991 tmp[i] = "*"; 992 } else { 993 if (/-/.test(widths[i])) { 994 throw new Error("Invalid argument: negative width not allowed"); 995 } 996 } 997 if (this.source.display.grid.every(checkDotColumn)) { 998 tmp[i] = "0px"; 999 } 1000 if (widths[i] !== undefined) { 1001 tmp[i] = widths[i]; 1002 } 1003 } 1004 return tmp; 1005 }, 1006 _normalizeHeights:function (display) { 1007 var dotLineRegExp = /^\.+$/, negativeHeight = /-/, i, tmp = []; 1008 for (i = 0; i < display.grid.length; i++) { 1009 if (display.grid[i].height === undefined) { 1010 tmp[i] = "auto"; 1011 } else { 1012 if (negativeHeight.test(display.grid[i].rowText)) { 1013 throw new Error("Invalid argument: negative height not allowed"); 1014 } 1015 } 1016 if (dotLineRegExp.test(display.grid[i].rowText)) { 1017 tmp[i] = "0px"; 1018 } 1019 //can overwrite dotLine rule 1020 if (display.grid[i].height !== undefined) { 1021 tmp[i] = display.grid[i].height; 1022 } 1023 } 1024 return tmp; 1025 }, 1026 _getMaxColNumber:function (grid) { 1027 if (grid.length === 0) { 1028 return 0; 1029 } 1030 return grid.reduce(function (last, row) { 1031 return last > row.rowText.length ? last : row.rowText.length; 1032 }, 0); 1033 } 1034 }; 1035 1036 templateBuilder.fn = templateBuilder.prototype; 1037 templateBuilder.prototype.init.prototype = templateBuilder.prototype; 1038 1039 global.templateBuilder = templateBuilder; 1040 log.info("load templateBuilder module... [OK]"); 1041 })(compiler.fn); 1042 1043 log.info("compiler module load... [OK]"); 1044 1045 })(window.templateLayout);