/**
 * @fileoverview Objekty pro implemetnaci zakladniho sablonovaciho systemu v JS
 * 
 * <p>Jednoduchy zakladni sablonovaci system pro Javascript.</p>
 * 
 * <ul> 
 *  <li>object JsPlate</li>
 *  <li>object templateObject</li> 
 * </ul>   
 * 
 * @author Laki <info@laki.cz> & JsDoc Toolkit team <http://code.google.com/p/jsdoc-toolkit/>
 * @version 1.4 <14-12-2009>
 */  

//jazykove verze sablon?

/**
 * Objekt pro implementaci zakladniho sablonovaciho systemu v JS.
 *
 * <p>
 *   Kod vychazi z JSDOC.JsPlate z projektu JsDoc Toolkit (<a href="http://code.google.com/p/jsdoc-toolkit/">http://code.google.com/p/jsdoc-toolkit/</a>). Provedeny byly jen drobne upravy pro online provoz a debugovani.
 *   <br /><br /> 
 *   Pouziti kodu zustava stejne - priklady pouziti na originalni dokumentacni strance <a href="http://code.google.com/p/jsdoc-toolkit/wiki/JsPlate">http://code.google.com/p/jsdoc-toolkit/wiki/JsPlate</a>.
 *   <br /><br />
 *   Licence: MIT Licence
 * </p>
 *   
 * <p>Pozn. <i>Oproti originalu byl zmemen objektovy namespace jen na JsPlate.</i></p>
 * 
 * <p>Implementované sablonovaci tagy:</p> 
 * <dl>
 *  <dt>&lt;for&gt;..&lt;/for&gt;</dt>
 *    <dd>Cyklus prochazejici objekty a pole, umi i cislovani a detekci posledniho zaznamu</dd> 
 *  <dt>&lt;if&gt;..&lt;/if&gt;</dt>
 *    <dd>podminky</dd>  
 * </dl>   
 * <p>Implementované sablonovaci konstrukce:</p> 
 * <dl>
 *  <dt>{+foo+}</dt>
 *    <dd>vlozeni obsahu promenne</dd> 
 *  <dt>{!doSomething();!}</dt>
 *    <dd>provedeni js kodu</dd>  
 *  <dt>{# komentar #}</dt>
 *    <dd>komentar v sablone</dd> 
 * </dl> 
 * 
 * @example Typicke volani, do sablony prida obsah promenne 'jmeno'
 *  var tmpl = "&lt;h2&gt;Ahoj {+jmeno+}&lt;/h2&gt;";
 *  var jmeno = "Honzo"; 
 *  var t = new JsPlate(tmpl);
 *  var o = t.process();
 *  if (t.error)
 *  {
 *    document.getElementById("error").innerText = t.errorMsg;  
 *  }
 *  else
 *  {
 *    document.getElementById("insert").innerHTML = o;
 *  }
 *  //vypise zpravu 'Ahoj Honzo'  
 *  
 * @example Typicke volani s datovym objektem, do sablony prida obsah promenne 'jmeno'
 *  var tmpl = "&lt;h2&gt;Ahoj {+data.jmeno+}&lt;/h2&gt;";
 *  var o = new Object;
 *  o.jmeno = "Honzo"; 
 *  var t = new JsPlate(tmpl);
 *  var o = t.process(o);
 *  if (t.error)
 *  {
 *    document.getElementById("error").innerText = t.errorMsg;  
 *  }
 *  else
 *  {
 *    document.getElementById("insert").innerHTML = o;
 *  }
 *  //vypise zpravu 'Ahoj Honzo'    
 * 
 * @example Vlozi vysledek funkce (lze pouzit jakykoliv js kod)
 *  function fce()
 *  {
 *    return "Honzo"; 
 *  }    
 *  var tmpl = "&lt;h2&gt;Ahoj {!fce();!}&lt;/h2&gt;";
 *  //vypise zpravu 'Ahoj Honzo'
 *  
 * @example Podminka
 *  var b = null;   
 *  var tmpl = '&lt;if test="b==null"&gt;Neexistuje&lt;else /&gt;Existuje&lt;/if&gt;';
 *  // Neexistuje 
 *  
 * @example For each objekt
 *  var pole = {Hruska: "zelena", Banan: "zluty", Pomeranc: "oranzovy"};
 *  var tmpl = '&lt;for each="barva" in="pole"&gt;{+$barva_key+": "+barva+"\n"+}&lt;/for&gt;';
 *  // Hruska: zelena\nBanan: zluty\nPomeranc: oranzovy\n
 *  
 * @example For each pole
 *  var pole = ["jedna", "dva", "tri"];
 *  var tmpl = '&lt;for each="item" in="pole"&gt;index: {+$item_key+} hodnota: {+item+} citac foreach: {+$item_i+}\n&lt;/for&gt;';
 *  // index: 0 hodnota: "jedna" citac foreach: 0\nindex: 1 hodnota: "dva" citac foreach: 1\nindex: 2 hodnota: "tri" citac foreach: 2  
 *  
 * @example For each s cislovanim
 *  var pole = {Hruska: "zelena", Banan: "zluty", Pomeranc: "oranzovy"};
 *  var tmpl = '&lt;for each="barva" in="pole"&gt;{+$barva_i+": "+barva+"\n"+}&lt;/for&gt;';
 *  // 0: zelena\n1: zluty\n2: oranzovy\n
 *  
 * @example For each s detekci posledniho zaznamu
 *  var pole = {Hruska: "zelena", Banan: "zluty", Pomeranc: "oranzovy"};
 *  var tmpl = '&lt;for each="barva" in="pole"&gt;{+barva+}&lt;if test="!$barva_last"&gt;\n&lt;/if&gt;&lt;/for&gt;';
 *  // zelena\nzluty\noranzovy     
 *  
 * @class JsPlate - implementace sablonovaciho systemu 
 * @extends Object
 * @version 1.0
 * @constructor
 * @param {string}   template  kod sablony
 */ 
JsPlate = function(template) 
{
  /**
   * Kod sablony.
   * @type string   
   * @private   
   */     
	this.template = template;

	/**
	 * Parsovany obsah sablony do js kodu.
	 * @type string
	 * @private   	 
	 */   	
	this.code = "";
	
	/**
	 * Priznak zda nastala chyba pri procesu sablony.
	 * @type boolean
	 * @public   	 
	 */
  this.error = false;   	

  /**
   * Chybova zprava procesovani prelozene sablony.
   * @type string
   * @public      
   */
  this.errorMsg = "";     
	
	this.parse();
}

/**
 * Metoda parsujici sablonu na js kod.
 * @private 
 */  
JsPlate.prototype.parse = function()
{
	this.template = this.template.replace(/\{#[\s\S]+?#\}/gi, "");
	this.code = "var output=\u001e"+this.template;

	this.code = this.code.replace(
		/<for +each="(.+?)" +in="(.+?)" *>/gi, 
		function (match, eachName, inName)
    {
			return "\u001e;\rvar $"+eachName+"_keys = keys("+inName+");\rfor(var $"+eachName+"_i = 0; $"+eachName+"_i < $"+eachName+"_keys.length; $"+eachName+"_i++) {\rvar $"+eachName+"_last = ($"+eachName+"_i == $"+eachName+"_keys.length-1);\rvar $"+eachName+"_key = $"+eachName+"_keys[$"+eachName+"_i];\rvar "+eachName+" = "+inName+"[$"+eachName+"_key];\routput+=\u001e";
		}
	);	
	this.code = this.code.replace(/<if test="(.+?)">/g, "\u001e;\rif ($1) { output+=\u001e");
	this.code = this.code.replace(/<elseif test="(.+?)"\s*\/>/g, "\u001e;}\relse if ($1) { output+=\u001e");
	this.code = this.code.replace(/<else\s*\/>/g, "\u001e;}\relse { output+=\u001e");
	this.code = this.code.replace(/<\/(if|for)>/g, "\u001e;\r};\routput+=\u001e");
	this.code = this.code.replace(
		/\{\+\s*([\s\S]+?)\s*\+\}/gi,
		function (match, code)
    {
			code = code.replace(/"/g, "\u001e"); // prevent qoute-escaping of inline code
			code = code.replace(/(\r?\n)/g, " ");
			return "\u001e+ ("+code+") +\u001e";
		}
	);
	this.code = this.code.replace(
		/\{!\s*([\s\S]+?)\s*!\}/gi,
		function (match, code)
    {
			code = code.replace(/"/g, "\u001e"); // prevent qoute-escaping of inline code
			code = code.replace(/(\n)/g, " ");
			return "\u001e; "+code+";\routput+=\u001e";
		}
	);
	this.code = this.code+"\u001e;";

	this.code = this.code.replace(/(\r?\n)/g, "\\n");
	this.code = this.code.replace(/"/g, "\\\"");
	this.code = this.code.replace(/\u001e/g, "\"");
}

/**
 * Vraci parsovany kod sablony na proveditelny js kod.  
 * @public
 * @return {string} Parsovany JS kod sablony
 * @type string  
 */ 
JsPlate.prototype.toCode = function()
{
	return this.code;
}

/**
 * Vraci klice (vlastnosti) objektu.
 * @private
 * @param {object} obj Objekt k ziskani klicu 
 * @return {array} pole klicovych hodnot objektu
 * @type array  
 */ 
JsPlate.keys = function(obj)
{
	var keys = [];
	if (obj.constructor.toString().indexOf("Array") > -1)
  {
		for (var i = 0; i < obj.length; i++)
    {
			keys.push(i);
		}
	}
	else
  {
		for (var i in obj)
    {
			keys.push(i);
		}
	}
	return keys;
};

/**
 * Vraci hodnoty vlastnosti objektu.
 * @private
 * @param {object} obj objekt k ziskani hodnot 
 * @return {array} pole hodnot objektu
 * @type array
 */
JsPlate.values = function(obj)
{
	var values = [];
	if (obj.constructor.toString().indexOf("Array") > -1)
  {
		for (var i = 0; i < obj.length; i++)
    {
			values.push(obj[i]);
		}
	}
	else
  {
		for (var i in obj)
    {
			values.push(obj[i]);
		}
	}
	return values;
};

/**
 * Vygeneruje HTML kod z prelozene sablony.
 * 
 * <p> 
 * Pokud je se zpracovavaji obsahy sablon samostatne, odkazuje se v sablone na datovy objekt v prvnim paramertu, ktere se pak musi teto metode predat.
 * </p> 
 * 
 * <p> 
 * Pokud vrati hodnotu NULL, doslo k chybe pro parsovani. Text chyby je k nalezeni ve vlastnosti {@link JsPlate#errorMsg}.
 * <br /> 
 * Chybu lze take overit dotazem na metodu {@link JsPlate#error()}.
 * </p>  
 *   
 * @public
 * @param {string}  data    datovy objekt s hodnotami pro sablonu, volitelne
 * @param {boolean} compact pokud nabyva hodnoty true, vraci vygenerovany HTML kod v kompaktni forme, volitelne
 * @return {string} vygenerovany HTML kod z prelozene sablony
 * @type string  
 */
JsPlate.prototype.process = function(data, compact)
{
	var keys = JsPlate.keys;
	var values = JsPlate.values;
	
	try
  {
		eval(this.code);
	}
	catch (e)
  {
		this.errorMsg = ">> There was an error evaluating the compiled code from template: \n";
		this.errorMsg += "   The error was on line "+e.lineNumber+" "+e.name+": "+e.message+"\n";
		var lines = this.code.split("\r");
		if (e.lineNumber-2 >= 0) this.errorMsg += "line "+(e.lineNumber-1)+": "+lines[e.lineNumber-2]+"\n";
		this.errorMsg += "line "+e.lineNumber+": "+lines[e.lineNumber-1]+"\n";
		
		this.error = true;  //nastala chyba
		return null;
	}
	
	if (compact)
  { // patch by mcbain.asm
 		// Remove lines that contain only space-characters, usually left by lines in the template
 		// which originally only contained JSPlate tags or code. This makes it easier to write
 		// non-tricky templates which still put out nice code (not bloated with extra lines).
 		// Lines purposely left blank (just a line ending) are left alone.
 		output = output.replace(/\s+?(\r?)\n/g, "$1\n");
 	}
 	
	return output;
}


/*****************************************************************************
 *  Objekt TEMPLATEOBJECT
 ******************************************************************************/ 


/**
 * Objekt pro praci se sablonama.
 *
 * <p>Pri inicializaci provede asynchronni nacteni externich sablon a dale zabezpecuje praci se sablonama.</p>
 * <p>Objekt je automaticky inicializovan pri nacteni v promenne "template" (lze zmenit redefinici zaznamu autoinit objektu).</p> 
 * 
 * <p>
 *  Externi sablony se definuji v HTML hlavicce jako souvisejici obsah (LINK) a atributem REL=jstemplate:
 *  <pre>
 *   &lt;link href="http://127.0.0.1/template.tmpl.xml" rel="jstemplate" charset="utf-8" media="screen" /&gt;
 *  </pre>  
 * </p>
 * 
 * <p>
 *  Interni sablony se definuji blokem XML kodu (zakomentovany kod -> parser XHTML valid):
 *  <pre>
 *   &lt;script language="xml" id="data" type="text/xml" defer="defer"&gt;
 *   &lt;!--
 *    &lt;templates&gt; ... &lt;/templates&gt;
 *   --&gt;
 *   &lt;/script&gt;
 *  </pre>
 * </p>
 * 
 * <p>
 *  V obou pripadech ma definice sablon stejny format (XML zapis, sablonovaci kod JsPlate):
 *  <pre>
 *    &lt;templates&gt;
 *      &lt;template id="test"&gt;
 *       &lt;div&gt;{+data.foo+}&lt;/div&gt;
 *      &lt;/template&gt;
 *      &lt;template id="seznam"&gt;
 *        &lt;ul&gt;&lt;li&gt;red&lt;/li&gt;&lt;li&gt;green&lt;/li&gt;&lt;li&gt;blue&lt;/li&gt;&lt;/ul&gt; 
 *      &lt;/template&gt;  
 *    &lt;/templates&gt;
 *  </pre> 
 *  Celou definici obaluje kontejner TEMPLATES, ktery obsahuje definice jednotlivych sablon (kontejnery TEMPLATE), ktere maji definovan svuj jednoznacny identifikator jako atributu ID. Obsah techto kontejnetu TEMPLATE je jiz samotna JsPlate sablona.
 * </p>    
 * 
 * <p>Struktura vstupniho objektu:</p> 
 * <pre>
 *    loadExtern - boolean - volitelne
 *               - priznak zda se maji nacitat pri inicializaci externi styly
 *               - ve vychozim nastaveni ma hodnotu true 
 *               
 *    loadDoneFnct - function - volitelne
 *                 - uzivatelska funkce, ktera se zavola po nacteni vsech externich stylu 
 * </pre>    
 * 
 * @example Zakladni priklad - vyuziti autoinit objektu, kod pridat jeste do hlavicky pred vlozenim tohoto skriptu 
 *   function myFnct()
 *   {
 *     alert("Load done");   
 *   }
 *   autoinit.module["templateObject"] = {variable: template, param: "{loadDoneFnct: myFnct}" };
 *  
 * @class templateObject - sprava sablon
 * @version 1.3
 * @constructor
 * @requires ajaxObject AJAX komunikace  
 * @param {object} p Objekt vstupnich parametru
 */
templateObject = function(p)
{  
  /**
   * Fronta nacitanych externich stylu. Pole instanci loadObject - jeden zaznam pro kazdy nacitany externi styl.
   * Pokud ma pole aspon jeden zaznam, probiha nacitani externich stylu.
   *       
   * @type array of Object   
   * @private
   */     
  this.loadingExternalTemplates = new Array();
  
  /**
   * Uzivatelska funkce, ktera se vola po nacteni vsech externich stylu.
   * <i>Lze nastavit jine hodnoty pri inicializaci objektu vlastnosti loadDoneFnct vstupniho objektu.</i></br>   
   * @type function
   * @private   
   */     
  this.loadDoneFnct = null;
  
  if ( (p != null) && (p.loadDoneFnct != null) && (typeof(p.loadDoneFnct) == "function") )
  {
    this.loadDoneFnct = p.loadDoneFnct;
  }

  /**
   * Zasobnik (pole) nactenych sablon.
   * Obsahuje objekty s informacemi o sablonach - seznam vlastnosti:
   *   id    - string  
   *         - identifikator sablony (jedinecny! Jinak vrati zvdy prvni sablonu se stejny id)
   *   src   - string
   *         - zdroj sablony v XML
   *   parse - JSPlate object
   *         - parsovana sablona (provadi se az pri prvnim pozadavku)
   *         - null, pokud sablona jeste nebyla pozadovana a tudiz nebyl duvod ji parsovat
   * @type array of object
   * @private         
   */        
  this.templates = new Array();
  

  /**
   * Objekt loadObject pro nacitani externiho stylu. Instance pro kazdy pokus o nacteni stulu.
   * 
   * @type object
   * @param {string} url  URL externi sablony, ktera se ma nacist
   * @param {object} that Ukazatel materskeho objektu templateObject
   * @inner
   * @memberOf templateObject   
   * @constructor   
   * @private   
   */     
  this.loadObject = function(url, that)
  {
    this.that = that;   //ulozit si odkaz na objekt, ktery vyvolal tuto instanci
    
    var ajax = new ajaxObject(url);
    ajax.callback = events.getAJAXcallback(this.load, this);
    ajax.update();
  }

  /**
   * Metoda zpracujici vysledek nacteni externi sablony.
   * @param {string}     responseText   Textova prezentace nacteneho obsahu
   * @param {int}        responseStatus HTTP stavovy kod odpovedi
   * @param {DOM object} responseXML    DOM objekt nacteneho obsahu
   * @inner
   * @memberOf templateObject#loadObject
   * @private               
   */  
  this.loadObject.prototype.load = function(responseText, responseStatus, responseXML)
  {   
    if (responseStatus == 200)  //vse OK
    {
      var root = responseXML.getElementsByTagName("templates");   //je to dokument se sablonama?
      
      if (root.length > 0)
      {
        var items = root[0].getElementsByTagName("template");  //vyhledej vsechny sablony v dokumentu
    
        for (var i=0; i<items.length; i++)
        {
          this.that.addTemplateDOM(items[i]);    //pridej nalezenou sablonu
        }
      
      } 
    }
    
    // najit sam sebe a odstranit prenos z fronty
    for (i=0; i<this.that.loadingExternalTemplates.length; i++)
    {
      if (this == this.that.loadingExternalTemplates[i])
      { // nasel svoji instanci a odstrani se z pole nacitanych objektu sablon
        this.that.loadingExternalTemplates.splice(i, 1);
      }
    }
    
    // jsou-li nacteny vsechny sablony, volej uzivatelskou funkci
    if ((this.that.loadingExternalTemplates.length == 0) && (this.that.loadDoneFnct != null) && (typeof this.that.loadDoneFnct == "function") )
    {
      this.that.loadDoneFnct.call();    //zavolej uzivatelskou funkci po nacteni vsech externich stylu
    }
  }

  //pokud je zakazano nacitani externich sablon, skonci
  if ( (p != null) && (p.loadExtern != null) && (typeof(p.loadExtern) == "boolean") && (p.loadExtern == false) ) return;

  // projit v hlavicce vsechny tagy LINK a pokud jsou nalezeny navazane externi sablony, provest jejich nacteni
  var head = document.getElementsByTagName("head")[0];
  var links = head.getElementsByTagName("link");
  for (i=0; i<links.length; i++)
  {
    if (links[i].getAttribute("rel").toLowerCase() == "jstemplate")
    {
      // udelat instanci objektu loadObject a pridat zaznam do pole nacitanych sablon
      this.loadingExternalTemplates.push(new this.loadObject(links[i].getAttribute("href"), this));
    }
  }
}


/**
 * Metoda nacte definici sablon z bloku &lt;script&gt;
 * @param {string} id Identifikator bloku SCRIPT
 * @public 
 */  
templateObject.prototype.add = function(id)
{
  var data = document.getElementById(id);
  if (data == null) return;
  
  if (data.tagName.toUpperCase() != "SCRIPT") return;
  
  data = trim(data.innerHTML.replace(/<!--/, "").replace(/-->/, "")); //celej obsah je zakomentovany kvuli XHTML parserum
  
  // preved obsah na DOM object
  var dom = new DOMParser();
  
  var tmpls = dom.parseFromString(data, "text/xml");
    
  var items = tmpls.getElementsByTagName("template");  //vyhledej vsechny sablony v dokumentu  
  for (var i=0; i<items.length; i++)
  {
    this.addTemplateDOM(items[i]);    //pridej nalezenou sablonu
  }
}

/**
 * Metoda prida sablonu ve formatu DOM objektu do zasobniku sablon.
 * 
 * Obalem sablony je tag TEMPLATE s atributem ID nesoucim jednoznacny identifikator sablony.
 *    
 * @param {DOM object} template Sablona
 * @public  
 */ 
templateObject.prototype.addTemplateDOM = function(template)
{
  var t = new Object();
  
  t.id = template.getAttribute("id");
  
  t.src = "";
  for (i=0; i<template.childNodes.length; i++)
  {
    t.src += XMLserialize(template.childNodes[i]);
  }
  
  t.parse = null;
  
  this.templates.push(t);
}

/**
 * Metoda prida sablonu ve formatu textove prezentace do zasobniku sablon.
 * @param {string} template Obsah sablony
 * @param {string} id       Jednoznacny identifikator sablony
 * @public  
 */ 
templateObject.prototype.addTemplate = function(template, id)
{
  var t = new Object();
  
  t.id = id;
  t.src = template;
  t.parse = null;
  
  this.templates.push(t);
}

/**
 * Metoda vyhleda a vrati pozadovanou jiz parsovanou sablonu ze zasobniku sablon.
 * 
 * Sablona se hleda podle identifikatoru sablony. POkud neni zadna sablona nalezena, vraci prazdny retezec (bezpecne pro nasledne zpracovani sablony).
 * Parsovani sabony se deje jen pri prvnim pozadavku na pozadovanou sablonu. 
 * @param {string} id Identifikator pozadovane sablony
 * @return {object JsPlate} Parsovana sablona
 * @type object JsPlate
 * @public  
 */  
templateObject.prototype.get = function(id)
{  
  var index = null;   //index hledane sablony v zasobniku sablon
  
  //projdi zasobnik sablon
  for (i=0; i<this.templates.length; i++)
  {
    if (this.templates[i].id.toUpperCase() == id.toUpperCase())
    { //nalezena sablona
      index = i;
      
      if (this.templates[i].parse == null)
      { //popkud jeste nebyla sablona parsovana, zparsuj ji
        this.templates[i].parse = new JsPlate(this.templates[i].src);
      }
      
      return this.templates[i].parse;
    }
  }
  
  return "";
}

/**
 * Metoda odstrani pozadovanou sablonu ze zasobniku sablon.
 * 
 * Sablona se hleda podle identifikatoru sablony. Pokud neni zadna sablona nalezena, vraci prazdny false, jinak true. 
 * @param {string} id Identifikator pozadovane sablony
 * @return {boolean} uspesnost akce
 * @type boolean
 * @public  
 */  
templateObject.prototype.remove = function(id)
{    
  //projdi zasobnik sablon
  for (i=0; i<this.templates.length; i++)
  {
    if (this.templates[i].id.toUpperCase() == id.toUpperCase())
    { //nalezena sablona
      this.templates.splice(i, 1);  //odstraneni polozky pole
      
      return true;
    }
  }
  
  return false;
}


// Autoinit - idealni zapis (funguje i pokud jeste neni definovan) 
if (typeof autoinit == "undefined") var autoinit = {module:[]};
if (typeof autoinit.module["templateObject"] == "undefined") autoinit.module["templateObject"] = {variable: "templates"};
if (typeof autoinit.load == "function") autoinit.load("templateObject");
