home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 June / PersonalComputerWorld-June2009-CoverdiscCD.iso / Software / Freeware / Firebug 1.3.3 / firebug-1.3.3-fx.xpi / content / firebug / domplate.js < prev    next >
Encoding:
JavaScript  |  2009-02-19  |  27.0 KB  |  1,026 lines

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