1 /* See license.txt for terms of usage */ 2 3 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 4 5 function DomplateTag(tagName) 6 { 7 this.tagName = tagName; 8 } 9 10 function DomplateEmbed() 11 { 12 } 13 14 function DomplateLoop() 15 { 16 } 17 18 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 19 20 (function() { 21 22 var womb = null; 23 24 top.domplate = function() 25 { 26 var lastSubject; 27 for (var i = 0; i < arguments.length; ++i) 28 lastSubject = lastSubject ? copyObject(lastSubject, arguments[i]) : arguments[i]; 29 30 for (var name in lastSubject) 31 { 32 var val = lastSubject[name]; 33 if (isTag(val)) 34 val.tag.subject = lastSubject; 35 } 36 37 return lastSubject; 38 }; 39 40 domplate.context = function(context, fn) 41 { 42 var lastContext = domplate.lastContext; 43 domplate.topContext = context; 44 fn.apply(context); 45 domplate.topContext = lastContext; 46 }; 47 48 FBL.TAG = function() 49 { 50 var embed = new DomplateEmbed(); 51 return embed.merge(arguments); 52 }; 53 54 FBL.FOR = function() 55 { 56 var loop = new DomplateLoop(); 57 return loop.merge(arguments); 58 }; 59 60 DomplateTag.prototype = 61 { 62 /* 63 * Initializer for DOM templates. Called to create new Functions objects like TR, TD, OBJLINK, etc. See defineTag 64 * @param args keyword argments for the template, the {} brace stuff after the tag name, eg TR({...}, TD(... 65 * @param oldTag a nested tag, eg the TD tag in TR({...}, TD(... 66 */ 67 merge: function(args, oldTag) 68 { 69 if (oldTag) 70 this.tagName = oldTag.tagName; 71 72 this.context = oldTag ? oldTag.context : null; // normally null on construction 73 this.subject = oldTag ? oldTag.subject : null; 74 this.attrs = oldTag ? copyObject(oldTag.attrs) : {}; 75 this.classes = oldTag ? copyObject(oldTag.classes) : {}; 76 this.props = oldTag ? copyObject(oldTag.props) : null; 77 this.listeners = oldTag ? copyArray(oldTag.listeners) : null; 78 this.children = oldTag ? copyArray(oldTag.children) : []; 79 this.vars = oldTag ? copyArray(oldTag.vars) : []; 80 81 var attrs = args.length ? args[0] : null; 82 var hasAttrs = typeof(attrs) == "object" && !isTag(attrs); 83 84 // Do not clear children, they can be copied from the oldTag. 85 //this.children = []; 86 87 if (domplate.topContext) 88 this.context = domplate.topContext; 89 90 if (args.length) 91 parseChildren(args, hasAttrs ? 1 : 0, this.vars, this.children); 92 93 if (hasAttrs) 94 this.parseAttrs(attrs); 95 96 return creator(this, DomplateTag); 97 }, 98 99 parseAttrs: function(args) 100 { 101 for (var name in args) 102 { 103 var val = parseValue(args[name]); 104 readPartNames(val, this.vars); 105 106 if (name.indexOf("on") == 0) 107 { 108 var eventName = name.substr(2); 109 if (!this.listeners) 110 this.listeners = []; 111 this.listeners.push(eventName, val); 112 } 113 else if (name[0] == "_") 114 { 115 var propName = name.substr(1); 116 if (!this.props) 117 this.props = {}; 118 this.props[propName] = val; 119 } 120 else if (name[0] == "$") 121 { 122 var className = name.substr(1); 123 if (!this.classes) 124 this.classes = {}; 125 this.classes[className] = val; 126 } 127 else 128 { 129 if (name == "class" && this.attrs.hasOwnProperty(name) ) 130 this.attrs[name] += " " + val; 131 else 132 this.attrs[name] = val; 133 } 134 } 135 }, 136 137 compile: function() 138 { 139 if (this.renderMarkup) 140 return; 141 142 this.compileMarkup(); 143 this.compileDOM(); 144 }, 145 146 compileMarkup: function() 147 { 148 this.markupArgs = []; 149 var topBlock = [], topOuts = [], blocks = [], info = {args: this.markupArgs, argIndex: 0}; 150 151 this.generateMarkup(topBlock, topOuts, blocks, info); 152 this.addCode(topBlock, topOuts, blocks); 153 154 var fnBlock = ['(function (__code__, __context__, __in__, __out__']; 155 for (var i = 0; i < info.argIndex; ++i) 156 fnBlock.push(', s', i); 157 fnBlock.push(') {\n'); 158 159 if (this.subject) 160 fnBlock.push('with (this) {\n'); 161 if (this.context) 162 fnBlock.push('with (__context__) {\n'); 163 fnBlock.push('with (__in__) {\n'); 164 165 fnBlock.push.apply(fnBlock, blocks); 166 167 if (this.subject) 168 fnBlock.push('}\n'); 169 if (this.context) 170 fnBlock.push('}\n'); 171 172 fnBlock.push('}})\n'); 173 174 function __link__(tag, code, outputs, args) 175 { 176 if (!tag || !tag.tag) 177 return; 178 179 tag.tag.compile(); 180 181 var tagOutputs = []; 182 var markupArgs = [code, tag.tag.context, args, tagOutputs]; 183 markupArgs.push.apply(markupArgs, tag.tag.markupArgs); 184 tag.tag.renderMarkup.apply(tag.tag.subject, markupArgs); 185 186 outputs.push(tag); 187 outputs.push(tagOutputs); 188 } 189 190 function __escape__(value) 191 { 192 function replaceChars(ch) 193 { 194 switch (ch) 195 { 196 case "<": 197 return "<"; 198 case ">": 199 return ">"; 200 case "&": 201 return "&"; 202 case "'": 203 return "'"; 204 case '"': 205 return """; 206 } 207 return "?"; 208 }; 209 return String(value).replace(/[<>&"']/g, replaceChars); 210 } 211 212 function __loop__(iter, outputs, fn) 213 { 214 var iterOuts = []; 215 outputs.push(iterOuts); 216 217 if (iter instanceof Array) 218 iter = new ArrayIterator(iter); 219 220 try 221 { 222 while (1) 223 { 224 var value = iter.next(); 225 var itemOuts = [0,0]; 226 iterOuts.push(itemOuts); 227 fn.apply(this, [value, itemOuts]); 228 } 229 } 230 catch (exc) 231 { 232 if (exc != StopIteration) 233 throw exc; 234 } 235 } 236 237 var js = fnBlock.join(""); 238 this.renderMarkup = eval(js); 239 }, 240 241 getVarNames: function(args) 242 { 243 if (this.vars) 244 args.push.apply(args, this.vars); 245 246 for (var i = 0; i < this.children.length; ++i) 247 { 248 var child = this.children[i]; 249 if (isTag(child)) 250 child.tag.getVarNames(args); 251 else if (child instanceof Parts) 252 { 253 for (var i = 0; i < child.parts.length; ++i) 254 { 255 if (child.parts[i] instanceof Variable) 256 { 257 var name = child.parts[i].name; 258 var names = name.split("."); 259 args.push(names[0]); 260 } 261 } 262 } 263 } 264 }, 265 266 generateMarkup: function(topBlock, topOuts, blocks, info) 267 { 268 topBlock.push(',"<', this.tagName, '"'); 269 270 for (var name in this.attrs) 271 { 272 if (name != "class") 273 { 274 var val = this.attrs[name]; 275 topBlock.push(', " ', name, '=\\""'); 276 addParts(val, ',', topBlock, info, true); 277 topBlock.push(', "\\""'); 278 } 279 } 280 281 if (this.listeners) 282 { 283 for (var i = 0; i < this.listeners.length; i += 2) 284 readPartNames(this.listeners[i+1], topOuts); 285 } 286 287 if (this.props) 288 { 289 for (var name in this.props) 290 readPartNames(this.props[name], topOuts); 291 } 292 293 if ( this.attrs.hasOwnProperty("class") || this.classes) 294 { 295 topBlock.push(', " class=\\""'); 296 if (this.attrs.hasOwnProperty("class")) 297 addParts(this.attrs["class"], ',', topBlock, info, true); 298 topBlock.push(', " "'); 299 for (var name in this.classes) 300 { 301 topBlock.push(', ('); 302 addParts(this.classes[name], '', topBlock, info); 303 topBlock.push(' ? "', name, '" + " " : "")'); 304 } 305 topBlock.push(', "\\""'); 306 } 307 topBlock.push(',">"'); 308 309 this.generateChildMarkup(topBlock, topOuts, blocks, info); 310 topBlock.push(',"</', this.tagName, '>"'); 311 if (FBTrace.DBG_DOMPLATE) 312 FBTrace.sysout("generateMarkup: "+this.tagName, topBlock.join("")); 313 }, 314 315 generateChildMarkup: function(topBlock, topOuts, blocks, info) 316 { 317 for (var i = 0; i < this.children.length; ++i) 318 { 319 var child = this.children[i]; 320 if (isTag(child)) 321 child.tag.generateMarkup(topBlock, topOuts, blocks, info); 322 else 323 addParts(child, ',', topBlock, info, true); 324 } 325 }, 326 327 addCode: function(topBlock, topOuts, blocks) 328 { 329 if (topBlock.length) 330 blocks.push('__code__.push(""', topBlock.join(""), ');\n'); 331 if (topOuts.length) 332 blocks.push('__out__.push(', topOuts.join(","), ');\n'); 333 topBlock.splice(0, topBlock.length); 334 topOuts.splice(0, topOuts.length); 335 }, 336 337 addLocals: function(blocks) 338 { 339 var varNames = []; 340 this.getVarNames(varNames); 341 342 var map = {}; 343 for (var i = 0; i < varNames.length; ++i) 344 { 345 var name = varNames[i]; 346 if ( map.hasOwnProperty(name) ) 347 continue; 348 349 map[name] = 1; 350 var names = name.split("."); 351 blocks.push('var ', names[0] + ' = ' + '__in__.' + names[0] + ';\n'); 352 } 353 }, 354 355 compileDOM: function() 356 { 357 var path = []; 358 var blocks = []; 359 this.domArgs = []; 360 path.embedIndex = 0; 361 path.loopIndex = 0; 362 path.staticIndex = 0; 363 path.renderIndex = 0; 364 var nodeCount = this.generateDOM(path, blocks, this.domArgs); 365 366 var fnBlock = ['(function (root, context, o']; 367 368 for (var i = 0; i < path.staticIndex; ++i) 369 fnBlock.push(', ', 's'+i); 370 371 for (var i = 0; i < path.renderIndex; ++i) 372 fnBlock.push(', ', 'd'+i); 373 374 fnBlock.push(') {\n'); 375 for (var i = 0; i < path.loopIndex; ++i) 376 fnBlock.push('var l', i, ' = 0;\n'); 377 for (var i = 0; i < path.embedIndex; ++i) 378 fnBlock.push('var e', i, ' = 0;\n'); 379 380 if (this.subject) 381 fnBlock.push('with (this) {\n'); 382 if (this.context) 383 fnBlock.push('with (context) {\n'); 384 385 fnBlock.push(blocks.join("")); 386 387 if (this.subject) 388 fnBlock.push('}\n'); 389 if (this.context) 390 fnBlock.push('}\n'); 391 392 fnBlock.push('return ', nodeCount, ';\n'); 393 fnBlock.push('})\n'); 394 395 function __bind__(object, fn) 396 { 397 return function(event) { return fn.apply(object, [event]); } 398 } 399 400 function __link__(node, tag, args) 401 { 402 if (!tag || !tag.tag) 403 return; 404 405 tag.tag.compile(); 406 407 var domArgs = [node, tag.tag.context, 0]; 408 domArgs.push.apply(domArgs, tag.tag.domArgs); 409 domArgs.push.apply(domArgs, args); 410 411 return tag.tag.renderDOM.apply(tag.tag.subject, domArgs); 412 } 413 414 var self = this; 415 function __loop__(iter, fn) 416 { 417 var nodeCount = 0; 418 for (var i = 0; i < iter.length; ++i) 419 { 420 iter[i][0] = i; 421 iter[i][1] = nodeCount; 422 nodeCount += fn.apply(this, iter[i]); 423 } 424 return nodeCount; 425 } 426 427 function __path__(parent, offset) 428 { 429 var root = parent; 430 431 for (var i = 2; i < arguments.length; ++i) 432 { 433 var index = arguments[i]; 434 if (i == 3) 435 index += offset; 436 437 if (index == -1) 438 parent = parent.parentNode; 439 else 440 parent = parent.childNodes[index]; 441 } 442 443 return parent; 444 } 445 var js = fnBlock.join(""); 446 // Exceptions on this line are often in the eval 447 this.renderDOM = eval(js); 448 }, 449 450 generateDOM: function(path, blocks, args) 451 { 452 if (this.listeners || this.props) 453 this.generateNodePath(path, blocks); 454 455 if (this.listeners) 456 { 457 for (var i = 0; i < this.listeners.length; i += 2) 458 { 459 var val = this.listeners[i+1]; 460 var arg = generateArg(val, path, args); 461 blocks.push('node.addEventListener("', this.listeners[i], '", __bind__(this, ', arg, '), false);\n'); 462 } 463 } 464 465 if (this.props) 466 { 467 for (var name in this.props) 468 { 469 var val = this.props[name]; 470 var arg = generateArg(val, path, args); 471 blocks.push('node.', name, ' = ', arg, ';\n'); 472 } 473 } 474 475 this.generateChildDOM(path, blocks, args); 476 return 1; 477 }, 478 479 generateNodePath: function(path, blocks) 480 { 481 blocks.push("var node = __path__(root, o"); 482 for (var i = 0; i < path.length; ++i) 483 blocks.push(",", path[i]); 484 blocks.push(");\n"); 485 }, 486 487 generateChildDOM: function(path, blocks, args) 488 { 489 path.push(0); 490 for (var i = 0; i < this.children.length; ++i) 491 { 492 var child = this.children[i]; 493 if (isTag(child)) 494 path[path.length-1] += '+' + child.tag.generateDOM(path, blocks, args); 495 else 496 path[path.length-1] += '+1'; 497 } 498 path.pop(); 499 }, 500 501 /* 502 * We are just hiding from javascript.options.strict. For some reasons it's ok if we return undefined here. 503 * @return null or undefined or possibly a context. 504 */ 505 getContext: function() 506 { 507 return this.context; 508 } 509 }; 510 511 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 512 513 DomplateEmbed.prototype = copyObject(DomplateTag.prototype, 514 { 515 merge: function(args, oldTag) 516 { 517 this.value = oldTag ? oldTag.value : parseValue(args[0]); 518 this.attrs = oldTag ? oldTag.attrs : {}; 519 this.vars = oldTag ? copyArray(oldTag.vars) : []; 520 521 var attrs = args[1]; 522 for (var name in attrs) 523 { 524 var val = parseValue(attrs[name]); 525 this.attrs[name] = val; 526 readPartNames(val, this.vars); 527 } 528 529 return creator(this, DomplateEmbed); 530 }, 531 532 getVarNames: function(names) 533 { 534 if (this.value instanceof Parts) 535 names.push(this.value.parts[0].name); 536 537 if (this.vars) 538 names.push.apply(names, this.vars); 539 }, 540 541 generateMarkup: function(topBlock, topOuts, blocks, info) 542 { 543 this.addCode(topBlock, topOuts, blocks); 544 545 blocks.push('__link__('); 546 addParts(this.value, '', blocks, info); 547 blocks.push(', __code__, __out__, {\n'); 548 549 var lastName = null; 550 for (var name in this.attrs) 551 { 552 if (lastName) 553 blocks.push(','); 554 lastName = name; 555 556 var val = this.attrs[name]; 557 blocks.push('"', name, '":'); 558 addParts(val, '', blocks, info); 559 } 560 561 blocks.push('});\n'); 562 //this.generateChildMarkup(topBlock, topOuts, blocks, info); 563 }, 564 565 generateDOM: function(path, blocks, args) 566 { 567 var embedName = 'e'+path.embedIndex++; 568 569 this.generateNodePath(path, blocks); 570 571 var valueName = 'd' + path.renderIndex++; 572 var argsName = 'd' + path.renderIndex++; 573 blocks.push(embedName + ' = __link__(node, ', valueName, ', ', argsName, ');\n'); 574 575 return embedName; 576 } 577 }); 578 579 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 580 581 DomplateLoop.prototype = copyObject(DomplateTag.prototype, 582 { 583 merge: function(args, oldTag) 584 { 585 this.isLoop = true; 586 this.varName = oldTag ? oldTag.varName : args[0]; 587 this.iter = oldTag ? oldTag.iter : parseValue(args[1]); 588 this.vars = []; 589 590 this.children = oldTag ? copyArray(oldTag.children) : []; 591 592 var offset = Math.min(args.length, 2); 593 parseChildren(args, offset, this.vars, this.children); 594 595 return creator(this, DomplateLoop); 596 }, 597 598 getVarNames: function(names) 599 { 600 if (this.iter instanceof Parts) 601 names.push(this.iter.parts[0].name); 602 603 DomplateTag.prototype.getVarNames.apply(this, [names]); 604 }, 605 606 generateMarkup: function(topBlock, topOuts, blocks, info) 607 { 608 this.addCode(topBlock, topOuts, blocks); 609 610 var iterName; 611 if (this.iter instanceof Parts) 612 { 613 var part = this.iter.parts[0]; 614 iterName = part.name; 615 616 if (part.format) 617 { 618 for (var i = 0; i < part.format.length; ++i) 619 iterName = part.format[i] + "(" + iterName + ")"; 620 } 621 } 622 else 623 iterName = this.iter; 624 625 blocks.push('__loop__.apply(this, [', iterName, ', __out__, function(', this.varName, ', __out__) {\n'); 626 this.generateChildMarkup(topBlock, topOuts, blocks, info); 627 this.addCode(topBlock, topOuts, blocks); 628 blocks.push('}]);\n'); 629 }, 630 631 generateDOM: function(path, blocks, args) 632 { 633 var iterName = 'd'+path.renderIndex++; 634 var counterName = 'i'+path.loopIndex; 635 var loopName = 'l'+path.loopIndex++; 636 637 if (!path.length) 638 path.push(-1, 0); 639 640 var preIndex = path.renderIndex; 641 path.renderIndex = 0; 642 643 var nodeCount = 0; 644 645 var subBlocks = []; 646 var basePath = path[path.length-1]; 647 for (var i = 0; i < this.children.length; ++i) 648 { 649 path[path.length-1] = basePath+'+'+loopName+'+'+nodeCount; 650 651 var child = this.children[i]; 652 if (isTag(child)) 653 nodeCount += '+' + child.tag.generateDOM(path, subBlocks, args); 654 else 655 nodeCount += '+1'; 656 } 657 658 path[path.length-1] = basePath+'+'+loopName; 659 660 blocks.push(loopName,' = __loop__.apply(this, [', iterName, ', function(', counterName,',',loopName); 661 for (var i = 0; i < path.renderIndex; ++i) 662 blocks.push(',d'+i); 663 blocks.push(') {\n'); 664 blocks.push(subBlocks.join("")); 665 blocks.push('return ', nodeCount, ';\n'); 666 blocks.push('}]);\n'); 667 668 path.renderIndex = preIndex; 669 670 return loopName; 671 } 672 }); 673 674 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 675 676 function Variable(name, format) 677 { 678 this.name = name; 679 this.format = format; 680 } 681 682 function Parts(parts) 683 { 684 this.parts = parts; 685 } 686 687 // ************************************************************************************************ 688 689 function parseParts(str) 690 { 691 var re = /\$([_A-Za-z][_A-Za-z0-9.|]*)/g; 692 var index = 0; 693 var parts = []; 694 695 var m; 696 while (m = re.exec(str)) 697 { 698 var pre = str.substr(index, (re.lastIndex-m[0].length)-index); 699 if (pre) 700 parts.push(pre); 701 702 var expr = m[1].split("|"); 703 parts.push(new Variable(expr[0], expr.slice(1))); 704 index = re.lastIndex; 705 } 706 707 if (!index) 708 return str; 709 710 var post = str.substr(index); 711 if (post) 712 parts.push(post); 713 714 return new Parts(parts); 715 } 716 717 function parseValue(val) 718 { 719 return typeof(val) == 'string' ? parseParts(val) : val; 720 } 721 722 function parseChildren(args, offset, vars, children) 723 { 724 for (var i = offset; i < args.length; ++i) 725 { 726 var val = parseValue(args[i]); 727 children.push(val); 728 readPartNames(val, vars); 729 } 730 } 731 732 function readPartNames(val, vars) 733 { 734 if (val instanceof Parts) 735 { 736 for (var i = 0; i < val.parts.length; ++i) 737 { 738 var part = val.parts[i]; 739 if (part instanceof Variable) 740 vars.push(part.name); 741 } 742 } 743 } 744 745 function generateArg(val, path, args) 746 { 747 if (val instanceof Parts) 748 { 749 var vals = []; 750 for (var i = 0; i < val.parts.length; ++i) 751 { 752 var part = val.parts[i]; 753 if (part instanceof Variable) 754 { 755 var varName = 'd'+path.renderIndex++; 756 if (part.format) 757 { 758 for (var j = 0; j < part.format.length; ++j) 759 varName = part.format[j] + '(' + varName + ')'; 760 } 761 762 vals.push(varName); 763 } 764 else 765 vals.push('"'+part.replace(/"/g, '\\"')+'"'); 766 } 767 768 return vals.join('+'); 769 } 770 else 771 { 772 args.push(val); 773 return 's' + path.staticIndex++; 774 } 775 } 776 777 function addParts(val, delim, block, info, escapeIt) 778 { 779 var vals = []; 780 if (val instanceof Parts) 781 { 782 for (var i = 0; i < val.parts.length; ++i) 783 { 784 var part = val.parts[i]; 785 if (part instanceof Variable) 786 { 787 var partName = part.name; 788 if (part.format) 789 { 790 for (var j = 0; j < part.format.length; ++j) 791 partName = part.format[j] + "(" + partName + ")"; 792 } 793 794 if (escapeIt) 795 vals.push("__escape__(" + partName + ")"); 796 else 797 vals.push(partName); 798 } 799 else 800 vals.push('"'+ part + '"'); 801 } 802 } 803 else if (isTag(val)) 804 { 805 info.args.push(val); 806 vals.push('s'+info.argIndex++); 807 } 808 else 809 vals.push('"'+ val + '"'); 810 811 var parts = vals.join(delim); 812 if (parts) 813 block.push(delim, parts); 814 } 815 816 function isTag(obj) 817 { 818 return (typeof(obj) == "function" || obj instanceof Function) && !!obj.tag; 819 } 820 821 function creator(tag, cons) 822 { 823 var fn = new Function( 824 "var tag = arguments.callee.tag;" + 825 "var cons = arguments.callee.cons;" + 826 "var newTag = new cons();" + 827 "return newTag.merge(arguments, tag);"); 828 829 fn.tag = tag; 830 fn.cons = cons; 831 extend(fn, Renderer); 832 833 return fn; 834 } 835 836 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 837 838 function copyArray(oldArray) 839 { 840 var ary = []; 841 if (oldArray) 842 for (var i = 0; i < oldArray.length; ++i) 843 ary.push(oldArray[i]); 844 return ary; 845 } 846 847 function copyObject(l, r) 848 { 849 var m = {}; 850 extend(m, l); 851 extend(m, r); 852 return m; 853 } 854 855 function extend(l, r) 856 { 857 for (var n in r) 858 l[n] = r[n]; 859 } 860 861 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 862 863 function ArrayIterator(array) 864 { 865 var index = -1; 866 867 this.next = function() 868 { 869 if (++index >= array.length) 870 throw StopIteration; 871 872 return array[index]; 873 }; 874 } 875 876 function StopIteration() {} 877 878 FBL.$break = function() 879 { 880 throw StopIteration; 881 }; 882 883 // ************************************************************************************************ 884 885 var Renderer = 886 { 887 renderHTML: function(args, outputs, self) 888 { 889 var code = []; 890 var markupArgs = [code, this.tag.getContext(), args, outputs]; 891 markupArgs.push.apply(markupArgs, this.tag.markupArgs); 892 this.tag.renderMarkup.apply(self ? self : this.tag.subject, markupArgs); 893 return code.join(""); 894 }, 895 896 insertRows: function(args, before, self) 897 { 898 if (!args) 899 args = {}; 900 901 this.tag.compile(); 902 903 var outputs = []; 904 var html = this.renderHTML(args, outputs, self); 905 906 var doc = before.ownerDocument; 907 var table = doc.createElement("table"); 908 table.innerHTML = html; 909 910 var tbody = table.firstChild; 911 var parent = before.localName.toLowerCase() == "tr" ? before.parentNode : before; 912 var after = before.localName.toLowerCase() == "tr" ? before.nextSibling : null; 913 914 var firstRow = tbody.firstChild, lastRow; 915 while (tbody.firstChild) 916 { 917 lastRow = tbody.firstChild; 918 if (after) 919 parent.insertBefore(lastRow, after); 920 else 921 parent.appendChild(lastRow); 922 } 923 924 // To save the next poor soul: 925 // In order to properly apply properties and event handlers on elements 926 // constructed by a FOR tag, the tag needs to be able to iterate up and 927 // down the tree, meaning if FOR is the root element as is the case with 928 // many insertRows calls, it will need to iterator over portions of the 929 // new parent. 930 // 931 // To achieve this end, __path__ defines the -1 operator which allows 932 // parent traversal. When combined with the offset that we calculate 933 // below we are able to iterate over the elements. 934 // 935 // This fails when applied to a non-loop element as non-loop elements 936 // Do not generate to proper path to bounce up and down the tree. 937 var offset = 0; 938 if (this.tag.isLoop) 939 { 940 var node = firstRow.parentNode.firstChild; 941 for (; node && node != firstRow; node = node.nextSibling) 942 ++offset; 943 } 944 945 // strict warning: this.tag.context undefined 946 var domArgs = [firstRow, this.tag.getContext(), offset]; 947 domArgs.push.apply(domArgs, this.tag.domArgs); 948 domArgs.push.apply(domArgs, outputs); 949 950 this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); 951 return [firstRow, lastRow]; 952 }, 953 954 insertBefore: function(args, before, self) 955 { 956 return this.insertNode( 957 args, before.ownerDocument, 958 function(frag) { 959 before.parentNode.insertBefore(frag, before); 960 }, 961 self); 962 }, 963 964 insertAfter: function(args, after, self) 965 { 966 return this.insertNode( 967 args, after.ownerDocument, 968 function(frag) { 969 after.parentNode.insertBefore(frag, after.nextSibling); 970 }, 971 self); 972 }, 973 974 insertNode: function(args, doc, inserter, self) 975 { 976 if (!args) 977 args = {}; 978 979 this.tag.compile(); 980 981 var outputs = []; 982 var html = this.renderHTML(args, outputs, self); 983 if (FBTrace.DBG_DOMPLATE) 984 FBTrace.sysout("domplate.insertNode html: "+html+"\n"); 985 986 var range = doc.createRange(); 987 range.selectNode(doc.body); 988 var frag = range.createContextualFragment(html); 989 990 var root = frag.firstChild; 991 root = inserter(frag) || root; 992 993 var domArgs = [root, this.tag.context, 0]; 994 domArgs.push.apply(domArgs, this.tag.domArgs); 995 domArgs.push.apply(domArgs, outputs); 996 997 if (FBTrace.DBG_DOMPLATE) 998 FBTrace.sysout("domplate.insertNode domArgs:", domArgs); 999 this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); 1000 1001 return root; 1002 }, 1003 1004 replace: function(args, parent, self) 1005 { 1006 if (!args) 1007 args = {}; 1008 1009 this.tag.compile(); 1010 1011 var outputs = []; 1012 var html = this.renderHTML(args, outputs, self); 1013 1014 var root; 1015 if (parent.nodeType == 1) 1016 { 1017 parent.innerHTML = html; 1018 root = parent.firstChild; 1019 } 1020 else 1021 { 1022 if (!parent || parent.nodeType != 9) 1023 parent = document; 1024 1025 if (!womb || womb.ownerDocument != parent) 1026 womb = parent.createElement("div"); 1027 womb.innerHTML = html; 1028 1029 root = womb.firstChild; 1030 //womb.removeChild(root); 1031 } 1032 1033 var domArgs = [root, this.tag.context, 0]; 1034 domArgs.push.apply(domArgs, this.tag.domArgs); 1035 domArgs.push.apply(domArgs, outputs); 1036 this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); 1037 1038 return root; 1039 }, 1040 1041 append: function(args, parent, self) 1042 { 1043 if (!args) 1044 args = {}; 1045 1046 this.tag.compile(); 1047 1048 var outputs = []; 1049 var html = this.renderHTML(args, outputs, self); 1050 if (FBTrace.DBG_DOMPLATE) 1051 FBTrace.sysout("domplate.append html: "+html+"\n"); 1052 1053 if (!womb || womb.ownerDocument != parent.ownerDocument) 1054 womb = parent.ownerDocument.createElement("div"); 1055 womb.innerHTML = html; 1056 1057 var root = womb.firstChild; 1058 while (womb.firstChild) 1059 parent.appendChild(womb.firstChild); 1060 1061 var domArgs = [root, this.tag.context, 0]; 1062 domArgs.push.apply(domArgs, this.tag.domArgs); 1063 domArgs.push.apply(domArgs, outputs); 1064 1065 if (FBTrace.DBG_DOMPLATE) 1066 FBTrace.sysout("domplate.append domArgs:", domArgs); 1067 1068 this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); 1069 1070 return root; 1071 } 1072 }; 1073 1074 // ************************************************************************************************ 1075 1076 function defineTags() 1077 { 1078 for (var i = 0; i < arguments.length; ++i) 1079 { 1080 var tagName = arguments[i]; 1081 var fn = new Function("var newTag = new DomplateTag('"+tagName+"'); return newTag.merge(arguments);"); 1082 1083 var fnName = tagName.toUpperCase(); 1084 FBL[fnName] = fn; 1085 } 1086 } 1087 1088 defineTags( 1089 "a", "button", "br", "canvas", "col", "colgroup", "div", "fieldset", "form", "h1", "h2", "h3", "hr", 1090 "img", "input", "label", "legend", "li", "ol", "optgroup", "option", "p", "pre", "select", "b", 1091 "span", "strong", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "tr", "tt", "ul", 1092 "iframe", "code" 1093 ); 1094 1095 })(); 1096