1 /** 2 * jTemplates 0.8.3 (http://jtemplates.tpython.com) 3 * Copyright (c) 2007-2012 Tomasz Gloc (http://www.tpython.com) 4 * 5 * Dual licensed under the MIT (MIT-LICENSE.txt) 6 * and/or GPL (GPL-LICENSE.txt) licenses. 7 * 8 * Id: $Id: jquery-jtemplates_uncompressed.js 197 2012-07-29 08:20:01Z tom $ 9 */ 10 11 /** 12 * @fileOverview Template engine in JavaScript. 13 * @name jTemplates 14 * @author Tomasz Gloc 15 * @date $Date: 2012-07-29 10:20:01 +0200 (N, 29 lip 2012) $ 16 */ 17 18 if (window.jQuery && !window.jQuery.createTemplate) {(function (jQuery) { 19 20 /** 21 * [abstract] 22 * @name BaseNode 23 * @class Abstract node. [abstract] 24 */ 25 26 /** 27 * Process node and get the html string. [abstract] 28 * @name get 29 * @function 30 * @param {object} d data 31 * @param {object} param parameters 32 * @param {Element} element a HTML element 33 * @param {Number} deep 34 * @return {String} 35 * @memberOf BaseNode 36 */ 37 38 /** 39 * [abstract] 40 * @name BaseArray 41 * @augments BaseNode 42 * @class Abstract array/collection. [abstract] 43 */ 44 45 /** 46 * Add node 'e' to array. 47 * @name push 48 * @function 49 * @param {BaseNode} e a node 50 * @memberOf BaseArray 51 */ 52 53 /** 54 * See (http://jquery.com/). 55 * @name jQuery 56 * @class jQuery Library (http://jquery.com/) 57 */ 58 59 /** 60 * See (http://jquery.com/) 61 * @name fn 62 * @class jQuery Library (http://jquery.com/) 63 * @memberOf jQuery 64 */ 65 66 /** 67 * Create new template from string s. 68 * @name Template 69 * @class A template or multitemplate. 70 * @param {string} s A template string (like: "Text: {$T.txt}."). 71 * @param {array} [includes] Array of included templates. 72 * @param {object} [settings] Settings. 73 * @config {boolean} [disallow_functions] Do not allow use function in data (default: true). 74 * @config {boolean} [filter_data] Enable filter data using escapeHTML (default: true). 75 * @config {boolean} [filter_params] Enable filter parameters using escapeHTML (default: false). 76 * @config {boolean} [runnable_functions] Automatically run function (from data) inside {} [default: false]. 77 * @config {boolean} [clone_data] Clone input data [default: true] 78 * @config {boolean} [clone_params] Clone input parameters [default: true] 79 * @config {Function} [f_cloneData] Function used to data cloning 80 * @config {Function} [f_escapeString] Function used to escape strings 81 * @config {Function} [f_parseJSON] Function used to parse JSON 82 * @augments BaseNode 83 */ 84 var Template = function (s, includes, settings) { 85 this._tree = []; 86 this._param = {}; 87 this._includes = null; 88 this._templates = {}; 89 this._templates_code = {}; 90 91 //default parameters 92 this.settings = jQuery.extend({ 93 disallow_functions: false, 94 filter_data: true, 95 filter_params: false, 96 runnable_functions: false, 97 clone_data: true, 98 clone_params: true 99 }, settings); 100 101 //set handlers 102 this.f_cloneData = (this.settings.f_cloneData !== undefined) ? (this.settings.f_cloneData) : (TemplateUtils.cloneData); 103 this.f_escapeString = (this.settings.f_escapeString !== undefined) ? (this.settings.f_escapeString) : (TemplateUtils.escapeHTML); 104 this.f_parseJSON = (this.settings.f_parseJSON !== undefined) ? (this.settings.f_parseJSON) : ((this.settings.disallow_functions) ? (jQuery.parseJSON) : (TemplateUtils.parseJSON)); 105 106 if(s == null) { 107 return; 108 } 109 110 //split multiteplate 111 this.splitTemplates(s, includes); 112 113 if(s) { 114 //set main template 115 this.setTemplate(this._templates_code['MAIN'], includes, this.settings); 116 } 117 118 this._templates_code = null; 119 }; 120 121 /** 122 * jTemplates version 123 * @type string 124 */ 125 Template.version = '0.8.3'; 126 127 /** 128 * Debug mode (all errors are on), default: off 129 * @type Boolean 130 */ 131 Template.DEBUG_MODE = false; 132 133 /** 134 * Foreach loop limit (enable only when DEBUG_MODE = true) 135 * @type integer 136 */ 137 Template.FOREACH_LOOP_LIMIT = 10000; 138 139 /** 140 * Global guid 141 * @type integer 142 */ 143 Template.guid = 0; 144 145 /** 146 * Split multitemplate into multiple templates. 147 * @param {string} s A template string (like: "Text: {$T.txt}."). 148 * @param {array} includes Array of included templates. 149 */ 150 Template.prototype.splitTemplates = function (s, includes) { 151 var reg = /\{#template *(\w+) *(.*?) *\}/g, //split multitemplate into subtemplates 152 iter, tname, se, lastIndex = null, _template_settings = [], i; 153 154 //while find new subtemplate 155 while((iter = reg.exec(s)) !== null) { 156 lastIndex = reg.lastIndex; 157 tname = iter[1]; 158 se = s.indexOf('{#/template ' + tname + '}', lastIndex); 159 if(se === -1) { 160 throw new Error('jTemplates: Template "' + tname + '" is not closed.'); 161 } 162 //save a subtemplate and parse options 163 this._templates_code[tname] = s.substring(lastIndex, se); 164 _template_settings[tname] = TemplateUtils.optionToObject(iter[2]); 165 } 166 //when no subtemplates, use all as main template 167 if(lastIndex === null) { 168 this._templates_code['MAIN'] = s; 169 return; 170 } 171 172 //create a new object for every subtemplates 173 for(i in this._templates_code) { 174 if(i !== 'MAIN') { 175 this._templates[i] = new Template(); 176 } 177 } 178 for(i in this._templates_code) { 179 if(i !== 'MAIN') { 180 this._templates[i].setTemplate(this._templates_code[i], 181 jQuery.extend({}, includes || {}, this._templates || {}), 182 jQuery.extend({}, this.settings, _template_settings[i])); 183 this._templates_code[i] = null; 184 } 185 } 186 }; 187 188 /** 189 * Parse template. (should be template, not multitemplate). 190 * @param {string} s A template string (like: "Text: {$T.txt}."). 191 * @param {array} includes Array of included templates. 192 * @param {object} [settings] Settings. 193 */ 194 Template.prototype.setTemplate = function (s, includes, settings) { 195 if(s == undefined) { 196 this._tree.push(new TextNode('', 1, this)); 197 return; 198 } 199 s = s.replace(/[\n\r]/g, ''); //remove endlines 200 s = s.replace(/\{\*.*?\*\}/g, ''); //remove comments 201 this._includes = jQuery.extend({}, this._templates || {}, includes || {}); 202 this.settings = new Object(settings); 203 var node = this._tree, 204 op = s.match(/\{#.*?\}/g), //find operators 205 ss = 0, se = 0, e, literalMode = 0, i, l; 206 207 //loop operators 208 for(i=0, l=(op)?(op.length):(0); i<l; ++i) { 209 var this_op = op[i]; 210 211 //when literal mode is on, treat operator like a text 212 if(literalMode) { 213 se = s.indexOf('{#/literal}'); 214 if(se === -1) { 215 throw new Error("jTemplates: No end of literal."); 216 } 217 if(se > ss) { 218 node.push(new TextNode(s.substring(ss, se), 1, this)); 219 } 220 ss = se + 11; 221 literalMode = 0; 222 i = jQuery.inArray('{#/literal}', op); 223 continue; 224 } 225 226 se = s.indexOf(this_op, ss); 227 if(se > ss) { 228 node.push(new TextNode(s.substring(ss, se), literalMode, this)); 229 } 230 this_op.match(/\{#([\w\/]+).*?\}/); //find operator name 231 var op_ = RegExp.$1; 232 switch(op_) { 233 case 'elseif': 234 node.addCond(this_op); 235 break; 236 case 'if': 237 e = new opIF(node, this); 238 e.addCond(this_op); 239 node.push(e); 240 node = e; 241 break; 242 case 'else': 243 node.switchToElse(); 244 break; 245 case '/if': 246 case '/for': 247 case '/foreach': 248 node = node.getParent(); 249 break; 250 case 'foreach': 251 e = new opFOREACH(this_op, node, this); 252 node.push(e); 253 node = e; 254 break; 255 case 'for': 256 e = opFORFactory(this_op, node, this); 257 node.push(e); 258 node = e; 259 break; 260 case 'continue': 261 case 'break': 262 node.push(new JTException(op_)); 263 break; 264 case 'include': 265 node.push(new Include(this_op, this._includes, this)); 266 break; 267 case 'param': 268 node.push(new UserParam(this_op, this)); 269 break; 270 case 'var': 271 node.push(new UserVariable(this_op, this)); 272 break; 273 case 'cycle': 274 node.push(new Cycle(this_op)); 275 break; 276 case 'ldelim': 277 node.push(new TextNode('{', 1, this)); 278 break; 279 case 'rdelim': 280 node.push(new TextNode('}', 1, this)); 281 break; 282 case 'literal': 283 literalMode = 1; 284 break; 285 case '/literal': 286 if(Template.DEBUG_MODE) { 287 throw new Error("jTemplates: Missing begin of literal."); 288 } 289 break; 290 default: 291 if(Template.DEBUG_MODE) { 292 throw new Error('jTemplates: unknown tag: ' + op_ + '.'); 293 } 294 } 295 296 ss = se + this_op.length; 297 } 298 299 if(s.length > ss) { 300 node.push(new TextNode(s.substr(ss), literalMode, this)); 301 } 302 }; 303 304 /** 305 * Process template and get the html string. 306 * @param {object} d data 307 * @param {object} param parameters 308 * @param {Element} element a HTML element 309 * @param {Number} deep 310 * @return {String} 311 */ 312 Template.prototype.get = function (d, param, element, deep) { 313 ++deep; 314 315 if (deep == 1 && element != undefined) { 316 jQuery.removeData(element, "jTemplatesRef"); 317 } 318 319 var $T = d, $P, ret = ''; 320 321 //create clone of data 322 if(this.settings.clone_data) { 323 $T = this.f_cloneData(d, {escapeData: (this.settings.filter_data && deep == 1), noFunc: this.settings.disallow_functions}, this.f_escapeString); 324 } 325 326 //create clone of parameters 327 if(!this.settings.clone_params) { 328 $P = jQuery.extend({}, this._param, param); 329 } else { 330 $P = jQuery.extend({}, 331 this.f_cloneData(this._param, {escapeData: (this.settings.filter_params), noFunc: false}, this.f_escapeString), 332 this.f_cloneData(param, {escapeData: (this.settings.filter_params && deep == 1), noFunc: false}, this.f_escapeString)); 333 } 334 335 for(var i=0, l=this._tree.length; i<l; ++i) { 336 ret += this._tree[i].get($T, $P, element, deep); 337 } 338 339 this.EvalObj = null; 340 341 --deep; 342 return ret; 343 }; 344 345 /** 346 * Create and return EvalClass object 347 * @return {EvalClass} 348 */ 349 Template.prototype.getBin = function () { 350 if(this.EvalObj == null) { 351 this.EvalObj = new EvalClass(this); 352 } 353 return this.EvalObj; 354 }; 355 356 /** 357 * Set to parameter 'name' value 'value'. 358 * @param {string} name 359 * @param {object} value 360 */ 361 Template.prototype.setParam = function (name, value) { 362 this._param[name] = value; 363 }; 364 365 366 /** 367 * Template utilities. 368 * @namespace Template utilities. 369 */ 370 TemplateUtils = function () { 371 }; 372 373 /** 374 * Replace chars &, >, <, ", ' with html entities. 375 * To disable function set settings: filter_data=false, filter_params=false 376 * @param {string} string 377 * @return {string} 378 * @static 379 * @memberOf TemplateUtils 380 */ 381 TemplateUtils.escapeHTML = function (txt) { 382 return txt.replace(/&/g,'&').replace(/>/g,'>').replace(/</g,'<').replace(/"/g,'"').replace(/'/g,'''); 383 }; 384 385 /** 386 * Make a copy od data 'd'. It also filters data (depend on 'filter'). 387 * @param {object} d input data 388 * @param {object} filter a filters 389 * @config {boolean} [escapeData] Use escapeHTML on every string. 390 * @config {boolean} [noFunc] Do not allow to use function (throws exception). 391 * @param {Function} f_escapeString function using to filter string (usually: TemplateUtils.escapeHTML) 392 * @return {object} output data (filtered) 393 * @static 394 * @memberOf TemplateUtils 395 */ 396 TemplateUtils.cloneData = function (d, filter, f_escapeString) { 397 if(d == null) { 398 return d; 399 } 400 switch(d.constructor) { 401 case Object: 402 var o = {}; 403 for(var i in d) { 404 o[i] = TemplateUtils.cloneData(d[i], filter, f_escapeString); 405 } 406 if(!filter.noFunc) { 407 if(d.hasOwnProperty("toString")) { 408 o.toString = d.toString; 409 } 410 } 411 return o; 412 case Array: 413 var a = []; 414 for(var i=0,l=d.length; i<l; ++i) { 415 a[i] = TemplateUtils.cloneData(d[i], filter, f_escapeString); 416 } 417 return a; 418 case String: 419 return (filter.escapeData) ? (f_escapeString(d)) : (d); 420 case Function: 421 if(filter.noFunc) { 422 if(Template.DEBUG_MODE) { 423 throw new Error("jTemplates: Functions are not allowed."); 424 } 425 else { 426 return undefined; 427 } 428 } 429 } 430 return d; 431 }; 432 433 /** 434 * Convert text-based option string to Object 435 * @param {string} optionText text-based option string 436 * @return {Object} 437 * @static 438 * @memberOf TemplateUtils 439 */ 440 TemplateUtils.optionToObject = function (optionText) { 441 if(optionText === null || optionText === undefined) { 442 return {}; 443 } 444 445 var o = optionText.split(/[= ]/); 446 if(o[0] === '') { 447 o.shift(); 448 } 449 450 var obj = {}; 451 for(var i=0, l=o.length; i<l; i+=2) { 452 obj[o[i]] = o[i+1]; 453 } 454 455 return obj; 456 }; 457 458 /** 459 * Parse JSON string into object 460 * @param {string} data Text JSON 461 * @return {Object} 462 * @static 463 */ 464 TemplateUtils.parseJSON = function (data) { 465 if ( typeof data !== "string" || !data ) { 466 return null; 467 } 468 try { 469 return (new Function("return " + jQuery.trim(data)))(); 470 } catch(e) { 471 if(Template.DEBUG_MODE) { 472 throw new Error("jTemplates: Invalid JSON"); 473 } 474 return {}; 475 } 476 }; 477 478 /** 479 * Find parents nodes for a reference value and return it 480 * @param {Element} el html element 481 * @param {int} guid template process unique identificator 482 * @param {int} id index 483 * @return {object} 484 * @static 485 */ 486 TemplateUtils.ReturnRefValue = function (el, guid, id) { 487 //search element with stored data 488 while(el != null) { 489 var d = jQuery.data(el, 'jTemplatesRef'); 490 if(d != undefined && d.guid == guid && d.d[id] != undefined) { 491 return d.d[id]; 492 } 493 el = el.parentNode; 494 } 495 return null; 496 }; 497 498 /** 499 * Create a new text node. 500 * @name TextNode 501 * @class All text (block {..}) between control's block "{#..}". 502 * @param {string} val text string 503 * @param {boolean} literalMode When enable (true) template does not process blocks {..}. 504 * @param {Template} Template object 505 * @augments BaseNode 506 */ 507 var TextNode = function (val, literalMode, template) { 508 this._value = val; 509 this._literalMode = literalMode; 510 this._template = template; 511 }; 512 513 /** 514 * Get the html string for a text node. 515 * @param {object} d data 516 * @param {object} param parameters 517 * @param {Element} element a HTML element 518 * @param {Number} deep 519 * @return {String} 520 */ 521 TextNode.prototype.get = function (d, param, element, deep) { 522 if(this._literalMode) { 523 return this._value; 524 } 525 var s = this._value; 526 var result = ""; 527 var i = -1; 528 var nested = 0; 529 var sText = -1; 530 var sExpr = 0; 531 while(true) { 532 var lm = s.indexOf("{", i+1); 533 var rm = s.indexOf("}", i+1); 534 if(lm < 0 && rm < 0) { 535 break; 536 } 537 if((lm != -1 && lm < rm) || (rm == -1)) { 538 i = lm; 539 if(++nested == 1) { 540 sText = lm; 541 result += s.substring(sExpr, i); 542 sExpr = -1; 543 } 544 } else { 545 i = rm; 546 if(--nested === 0) { 547 if(sText >= 0) { 548 result += this._template.getBin().evaluateContent(d, param, element, s.substring(sText, rm+1)); 549 sText = -1; 550 sExpr = i+1; 551 } 552 } else if(nested < 0) { 553 nested = 0; 554 } 555 } 556 } 557 if(sExpr > -1) { 558 result += s.substr(sExpr); 559 } 560 return result; 561 }; 562 563 /** 564 * Virtual context for eval() (internal class) 565 * @name EvalClass 566 * @class Virtual bin for eval() evaluation 567 * @param {Template} t template 568 * @private 569 */ 570 EvalClass = function (t) { 571 this.__templ = t; 572 }; 573 574 /** 575 * Evaluate expression (template content) 576 * @param {object} $T data 577 * @param {object} $P parameters 578 * @param {object} $Q element 579 * @param {String} __value Template content 580 * @return {String} 581 */ 582 EvalClass.prototype.evaluateContent = function ($T, $P, $Q, __value) { 583 try { 584 var result = eval(__value); 585 586 if(jQuery.isFunction(result)) { 587 if(this.__templ.settings.disallow_functions || !this.__templ.settings.runnable_functions) { 588 return ''; 589 } 590 result = result($T, $P, $Q); 591 } 592 return (result === undefined) ? ("") : (String(result)); 593 } catch(e) { 594 if(Template.DEBUG_MODE) { 595 if(e instanceof JTException) { 596 e.type = "subtemplate"; 597 } 598 throw e; 599 } 600 return ""; 601 } 602 }; 603 604 /** 605 * Evaluate expression (simple eval) 606 * @param {object} $T data 607 * @param {object} $P parameters 608 * @param {object} $Q element 609 * @param {String} __value content to evaluate 610 * @return {String} 611 */ 612 EvalClass.prototype.evaluate = function ($T, $P, $Q, __value) { 613 return eval(__value); 614 }; 615 616 /** 617 * Create a new conditional node. 618 * @name opIF 619 * @class A class represent: {#if}. 620 * @param {object} par parent node 621 * @param {Template} templ template 622 * @augments BaseArray 623 */ 624 var opIF = function (par, templ) { 625 this._parent = par; 626 this._templ = templ; 627 this._cond = []; //conditions 628 this._tree = []; //conditions subtree 629 this._curr = null; //current subtree 630 }; 631 632 /** 633 * Add node 'e' to array. 634 * @param {BaseNode} e a node 635 */ 636 opIF.prototype.push = function (e) { 637 this._curr.push(e); 638 }; 639 640 /** 641 * Get a parent node. 642 * @return {BaseNode} 643 */ 644 opIF.prototype.getParent = function () { 645 return this._parent; 646 }; 647 648 /** 649 * Add condition 650 * @param {string} oper content of operator {#..} 651 */ 652 opIF.prototype.addCond = function (oper) { 653 oper.match(/\{#(?:else)*if (.*?)\}/); 654 this._cond.push(RegExp.$1); 655 this._curr = []; 656 this._tree.push(this._curr); 657 }; 658 659 /** 660 * Switch to else 661 */ 662 opIF.prototype.switchToElse = function () { 663 this._cond.push(true); //else is the last condition and its always true 664 this._curr = []; 665 this._tree.push(this._curr); 666 }; 667 668 /** 669 * Process node depend on conditional and get the html string. 670 * @param {object} d data 671 * @param {object} param parameters 672 * @param {Element} element a HTML element 673 * @param {Number} deep 674 * @return {String} 675 */ 676 opIF.prototype.get = function (d, param, element, deep) { 677 var ret = ''; //result 678 679 try { 680 //foreach condition 681 for(var ci=0, cl=this._cond.length; ci<cl; ++ci) { 682 //if condition is true 683 if(this._templ.getBin().evaluate(d, param, element, this._cond[ci])) { 684 //execute and exit 685 var t = this._tree[ci]; 686 for(var i=0, l=t.length; i<l; ++i) { 687 ret += t[i].get(d, param, element, deep); 688 } 689 return ret; 690 } 691 } 692 } catch(e) { 693 if(Template.DEBUG_MODE || (e instanceof JTException)) { 694 throw e; 695 } 696 } 697 return ret; 698 }; 699 700 /** 701 * Handler for a tag 'FOR'. Create new and return relative opFOREACH object. 702 * @name opFORFactory 703 * @class Handler for a tag 'FOR'. Create new and return relative opFOREACH object. 704 * @param {string} oper content of operator {#..} 705 * @param {object} par parent node 706 * @param {Template} template a pointer to Template object 707 * @return {opFOREACH} 708 */ 709 opFORFactory = function (oper, par, template) { 710 //create operator FOREACH with function as iterator 711 if(oper.match(/\{#for (\w+?) *= *(\S+?) +to +(\S+?) *(?:step=(\S+?))*\}/)) { 712 var f = new opFOREACH(null, par, template); 713 f._name = RegExp.$1; 714 f._option = {'begin': (RegExp.$2 || 0), 'end': (RegExp.$3 || -1), 'step': (RegExp.$4 || 1), 'extData': '$T'}; 715 f._runFunc = (function (i){return i;}); 716 return f; 717 } else { 718 throw new Error('jTemplates: Operator failed "find": ' + oper); 719 } 720 }; 721 722 /** 723 * Create a new loop node. 724 * @name opFOREACH 725 * @class A class represent: {#foreach}. 726 * @param {string} oper content of operator {#..} 727 * @param {object} par parent node 728 * @param {Template} template a pointer to Template object 729 * @augments BaseArray 730 */ 731 var opFOREACH = function (oper, par, template) { 732 this._parent = par; 733 this._template = template; 734 if(oper != null) { 735 oper.match(/\{#foreach +(.+?) +as +(\w+?)( .+)*\}/); 736 this._arg = RegExp.$1; 737 this._name = RegExp.$2; 738 this._option = RegExp.$3 || null; 739 this._option = TemplateUtils.optionToObject(this._option); 740 } 741 742 this._onTrue = []; 743 this._onFalse = []; 744 this._currentState = this._onTrue; 745 //this._runFunc = null; 746 }; 747 748 /** 749 * Add node 'e' to array. 750 * @param {BaseNode} e 751 */ 752 opFOREACH.prototype.push = function (e) { 753 this._currentState.push(e); 754 }; 755 756 /** 757 * Get a parent node. 758 * @return {BaseNode} 759 */ 760 opFOREACH.prototype.getParent = function () { 761 return this._parent; 762 }; 763 764 /** 765 * Switch from collection onTrue to onFalse. 766 */ 767 opFOREACH.prototype.switchToElse = function () { 768 this._currentState = this._onFalse; 769 }; 770 771 /** 772 * Process loop and get the html string. 773 * @param {object} d data 774 * @param {object} param parameters 775 * @param {Element} element a HTML element 776 * @param {Number} deep 777 * @return {String} 778 */ 779 opFOREACH.prototype.get = function (d, param, element, deep) { 780 try { 781 //array of elements in foreach (or function) 782 var fcount = (this._runFunc === undefined) ? (this._template.getBin().evaluate(d, param, element, this._arg)) : (this._runFunc); 783 if(fcount === $) { 784 throw new Error("jTemplate: Variable '$' cannot be used as loop-function"); 785 } 786 var key = []; //only for objects 787 var mode = typeof fcount; 788 if(mode == 'object') { 789 //transform object to array 790 var arr = []; 791 jQuery.each(fcount, function (k, v) { 792 key.push(k); 793 arr.push(v); 794 }); 795 fcount = arr; 796 } 797 //setup primary iterator, iterator can get data from options (using by operator FOR) or from data "$T" 798 var extData = (this._option.extData !== undefined) ? (this._template.getBin().evaluate(d, param, element, this._option.extData)) : ((d != null) ? (d) : ({})); 799 if(extData == null) { 800 extData = {}; 801 } 802 //start, end and step 803 var s = Number(this._template.getBin().evaluate(d, param, element, this._option.begin) || 0), e; //start, end 804 var step = Number(this._template.getBin().evaluate(d, param, element, this._option.step) || 1); 805 if(mode != 'function') { 806 e = fcount.length; 807 } else { 808 if(this._option.end === undefined || this._option.end === null) { 809 e = Number.MAX_VALUE; 810 } else { 811 e = Number(this._template.getBin().evaluate(d, param, element, this._option.end)) + ((step>0) ? (1) : (-1)); 812 } 813 } 814 var ret = ''; //result string 815 var i,l; //local iterators 816 817 if(this._option.count) { 818 //limit number of loops 819 var tmp = s + Number(this._template.getBin().evaluate(d, param, element, this._option.count)); 820 e = (tmp > e) ? (e) : (tmp); 821 } 822 823 if((e>s && step>0) || (e<s && step<0)) { 824 var iteration = 0; 825 var _total = (mode != 'function') ? (Math.ceil((e-s)/step)) : undefined; 826 var ckey, cval; //current key, current value 827 var loopCounter = 0; 828 for(; ((step>0) ? (s<e) : (s>e)); s+=step, ++iteration, ++loopCounter) { 829 if(Template.DEBUG_MODE && loopCounter > Template.FOREACH_LOOP_LIMIT) { 830 throw new Error("jTemplate: Foreach loop limit was exceed"); 831 } 832 ckey = key[s]; 833 if(mode != 'function') { 834 cval = fcount[s]; //get value from array 835 } else { 836 cval = fcount(s); //calc function 837 //if no result from function then stop foreach 838 if(cval === undefined || cval === null) { 839 break; 840 } 841 } 842 if((typeof cval == 'function') && (this._template.settings.disallow_functions || !this._template.settings.runnable_functions)) { 843 continue; 844 } 845 if((mode == 'object') && (ckey in Object) && (cval === Object[ckey])) { 846 continue; 847 } 848 //backup on value 849 var prevValue = extData[this._name]; 850 //set iterator properties 851 extData[this._name] = cval; 852 extData[this._name + '$index'] = s; 853 extData[this._name + '$iteration'] = iteration; 854 extData[this._name + '$first'] = (iteration === 0); 855 extData[this._name + '$last'] = (s+step >= e); 856 extData[this._name + '$total'] = _total; 857 extData[this._name + '$key'] = (ckey !== undefined && ckey.constructor == String) ? (this._template.f_escapeString(ckey)) : (ckey); 858 extData[this._name + '$typeof'] = typeof cval; 859 for(i=0, l=this._onTrue.length; i<l; ++i) { 860 try { 861 ret += this._onTrue[i].get(extData, param, element, deep); 862 } catch(ex) { 863 if(ex instanceof JTException) { 864 switch(ex.type) { 865 case 'continue': 866 i = l; //force skip to next node 867 break; 868 case 'break': 869 i = l; //force skip to next node 870 s = e; //force skip outsite foreach 871 break; 872 default: 873 throw ex; 874 } 875 } else { 876 throw ex; 877 } 878 } 879 } 880 //restore values 881 delete extData[this._name + '$index']; 882 delete extData[this._name + '$iteration']; 883 delete extData[this._name + '$first']; 884 delete extData[this._name + '$last']; 885 delete extData[this._name + '$total']; 886 delete extData[this._name + '$key']; 887 delete extData[this._name + '$typeof']; 888 delete extData[this._name]; 889 extData[this._name] = prevValue; 890 } 891 } else { 892 //no items to loop ("foreach->else") 893 for(i=0, l=this._onFalse.length; i<l; ++i) { 894 ret += this._onFalse[i].get(d, param, element, deep); 895 } 896 } 897 return ret; 898 } catch(e) { 899 if(Template.DEBUG_MODE || (e instanceof JTException)) { 900 throw e; 901 } 902 return ""; 903 } 904 }; 905 906 /** 907 * Template-control exceptions 908 * @name JTException 909 * @class A class used internals for a template-control exceptions 910 * @param type {string} Type of exception 911 * @augments Error 912 * @augments BaseNode 913 */ 914 var JTException = function (type) { 915 this.type = type; 916 }; 917 JTException.prototype = Error; 918 919 /** 920 * Throw a template-control exception 921 * @throws It throws itself 922 */ 923 JTException.prototype.get = function (d) { 924 throw this; 925 }; 926 927 /** 928 * Create a new entry for included template. 929 * @name Include 930 * @class A class represent: {#include}. 931 * @param {string} oper content of operator {#..} 932 * @param {array} includes 933 * @param {Template} templ template 934 * @augments BaseNode 935 */ 936 var Include = function (oper, includes, templ) { 937 oper.match(/\{#include (.*?)(?: root=(.*?))?\}/); 938 this._template = includes[RegExp.$1]; 939 if(this._template == undefined) { 940 if(Template.DEBUG_MODE) { 941 throw new Error('jTemplates: Cannot find include: ' + RegExp.$1); 942 } 943 } 944 this._root = RegExp.$2; 945 this._mainTempl = templ; 946 }; 947 948 /** 949 * Run method get on included template. 950 * @param {object} d data 951 * @param {object} param parameters 952 * @param {Element} element a HTML element 953 * @param {Number} deep 954 * @return {String} 955 */ 956 Include.prototype.get = function (d, param, element, deep) { 957 try { 958 //run a subtemplates with a new root node 959 return this._template.get(this._mainTempl.getBin().evaluate(d, param, element, this._root), param, element, deep); 960 } catch(e) { 961 if(Template.DEBUG_MODE || (e instanceof JTException)) { 962 throw e; 963 } 964 } 965 return ''; 966 }; 967 968 /** 969 * Create new node for {#param}. 970 * @name UserParam 971 * @class A class represent: {#param}. 972 * @param {string} oper content of operator {#..} 973 * @param {Template} templ template 974 * @augments BaseNode 975 */ 976 var UserParam = function (oper, templ) { 977 oper.match(/\{#param name=(\w*?) value=(.*?)\}/); 978 this._name = RegExp.$1; 979 this._value = RegExp.$2; 980 this._templ = templ; 981 }; 982 983 /** 984 * Return value of selected parameter. 985 * @param {object} d data 986 * @param {object} param parameters 987 * @param {Element} element a HTML element 988 * @param {Number} deep 989 * @return {String} empty string 990 */ 991 UserParam.prototype.get = function (d, param, element, deep) { 992 try { 993 param[this._name] = this._templ.getBin().evaluate(d, param, element, this._value); 994 } catch(e) { 995 if(Template.DEBUG_MODE || (e instanceof JTException)) { 996 throw e; 997 } 998 param[this._name] = undefined; 999 } 1000 return ''; 1001 }; 1002 1003 /** 1004 * Create new node for {#var}. 1005 * @name UserVariable 1006 * @class A class represent: {#var}. 1007 * @param {string} oper content of operator {#..} 1008 * @param {Template} templ template 1009 * @augments BaseNode 1010 */ 1011 var UserVariable = function (oper, templ) { 1012 oper.match(/\{#var (.*?)\}/); 1013 this._id = RegExp.$1; 1014 this._templ = templ; 1015 }; 1016 1017 /** 1018 * Return value of selected variable. 1019 * @param {object} d data 1020 * @param {object} param parameters 1021 * @param {Element} element a HTML element 1022 * @param {Number} deep 1023 * @return {String} calling of function ReturnRefValue (as text string) 1024 */ 1025 UserVariable.prototype.get = function (d, param, element, deep) { 1026 try { 1027 if(element == undefined) { 1028 return ""; 1029 } 1030 var obj = this._templ.getBin().evaluate(d, param, element, this._id); 1031 var refobj = jQuery.data(element, "jTemplatesRef"); 1032 if(refobj == undefined) { 1033 refobj = {guid:(++Template.guid), d:[]}; 1034 } 1035 var i = refobj.d.push(obj); 1036 jQuery.data(element, "jTemplatesRef", refobj); 1037 return "(TemplateUtils.ReturnRefValue(this," + refobj.guid + "," + (i-1) + "))"; 1038 } catch(e) { 1039 if(Template.DEBUG_MODE || (e instanceof JTException)) { 1040 throw e; 1041 } 1042 return ''; 1043 } 1044 }; 1045 1046 /** 1047 * Create a new cycle node. 1048 * @name Cycle 1049 * @class A class represent: {#cycle}. 1050 * @param {string} oper content of operator {#..} 1051 * @augments BaseNode 1052 */ 1053 var Cycle = function (oper) { 1054 oper.match(/\{#cycle values=(.*?)\}/); 1055 this._values = eval(RegExp.$1); 1056 this._length = this._values.length; 1057 if(this._length <= 0) { 1058 throw new Error('jTemplates: no elements for cycle'); 1059 } 1060 this._index = 0; 1061 this._lastSessionID = -1; 1062 }; 1063 1064 /** 1065 * Do a step on cycle and return value. 1066 * @param {object} d data 1067 * @param {object} param parameters 1068 * @param {Element} element a HTML element 1069 * @param {Number} deep 1070 * @return {String} 1071 */ 1072 Cycle.prototype.get = function (d, param, element, deep) { 1073 var sid = jQuery.data(element, 'jTemplateSID'); 1074 if(sid != this._lastSessionID) { 1075 this._lastSessionID = sid; 1076 this._index = 0; 1077 } 1078 var i = this._index++ % this._length; 1079 return this._values[i]; 1080 }; 1081 1082 1083 /** 1084 * Add a Template to HTML Elements. 1085 * @param {Template/string} s a Template or a template string 1086 * @param {array} [includes] Array of included templates. 1087 * @param {object} [settings] Settings (see Template) 1088 * @return {jQuery} chainable jQuery class 1089 * @memberOf jQuery.fn 1090 */ 1091 jQuery.fn.setTemplate = function (s, includes, settings) { 1092 return jQuery(this).each(function () { 1093 var t = (s && s.constructor == Template) ? s : new Template(s, includes, settings); 1094 jQuery.data(this, 'jTemplate', t); 1095 jQuery.data(this, 'jTemplateSID', 0); 1096 }); 1097 }; 1098 1099 /** 1100 * Add a Template (from URL) to HTML Elements. 1101 * @param {string} url_ URL to template 1102 * @param {array} [includes] Array of included templates. 1103 * @param {object} [settings] Settings (see Template) 1104 * @return {jQuery} chainable jQuery class 1105 * @memberOf jQuery.fn 1106 */ 1107 jQuery.fn.setTemplateURL = function (url_, includes, settings) { 1108 var s = jQuery.ajax({ 1109 url: url_, 1110 dataType: 'text', 1111 async: false, 1112 type: 'GET' 1113 }).responseText; 1114 1115 return jQuery(this).setTemplate(s, includes, settings); 1116 }; 1117 1118 /** 1119 * Create a Template from element's content. 1120 * @param {string} elementName an ID of element 1121 * @param {array} [includes] Array of included templates. 1122 * @param {object} [settings] Settings (see Template) 1123 * @return {jQuery} chainable jQuery class 1124 * @memberOf jQuery.fn 1125 */ 1126 jQuery.fn.setTemplateElement = function (elementName, includes, settings) { 1127 var s = jQuery('#' + elementName).val(); 1128 if(s == null) { 1129 s = jQuery('#' + elementName).html(); 1130 s = s.replace(/</g, "<").replace(/>/g, ">"); 1131 } 1132 1133 s = jQuery.trim(s); 1134 s = s.replace(/^<\!\[CDATA\[([\s\S]*)\]\]>$/im, '$1'); 1135 s = s.replace(/^<\!--([\s\S]*)-->$/im, '$1'); 1136 1137 return jQuery(this).setTemplate(s, includes, settings); 1138 }; 1139 1140 /** 1141 * Check it HTML Elements have a template. Return count of templates. 1142 * @return {number} Number of templates. 1143 * @memberOf jQuery.fn 1144 */ 1145 jQuery.fn.hasTemplate = function () { 1146 var count = 0; 1147 jQuery(this).each(function () { 1148 if(jQuery.getTemplate(this)) { 1149 ++count; 1150 } 1151 }); 1152 return count; 1153 }; 1154 1155 /** 1156 * Remote Template from HTML Element(s) 1157 * @return {jQuery} chainable jQuery class 1158 */ 1159 jQuery.fn.removeTemplate = function () { 1160 jQuery(this).processTemplateStop(); 1161 return jQuery(this).each(function () { 1162 jQuery.removeData(this, 'jTemplate'); 1163 }); 1164 }; 1165 1166 /** 1167 * Set to parameter 'name' value 'value'. 1168 * @param {string} name 1169 * @param {object} value 1170 * @return {jQuery} chainable jQuery class 1171 * @memberOf jQuery.fn 1172 */ 1173 jQuery.fn.setParam = function (name, value) { 1174 return jQuery(this).each(function () { 1175 var t = jQuery.getTemplate(this); 1176 if(t != null) { 1177 t.setParam(name, value); 1178 } else if(Template.DEBUG_MODE) { 1179 throw new Error('jTemplates: Template is not defined.'); 1180 } 1181 }); 1182 }; 1183 1184 /** 1185 * Process template using data 'd' and parameters 'param'. Update HTML code. 1186 * @param {object} d data 1187 * @param {object} [param] parameters 1188 * @option {object} [options] internal use only 1189 * @return {jQuery} chainable jQuery class 1190 * @memberOf jQuery.fn 1191 */ 1192 jQuery.fn.processTemplate = function (d, param, options) { 1193 return jQuery(this).each(function () { 1194 var t = jQuery.getTemplate(this); 1195 if(t != null) { 1196 if(options != undefined && options.StrToJSON) { 1197 d = t.f_parseJSON(d); 1198 } 1199 jQuery.data(this, 'jTemplateSID', jQuery.data(this, 'jTemplateSID') + 1); 1200 jQuery(this).html(t.get(d, param, this, 0)); 1201 } else if(Template.DEBUG_MODE) { 1202 throw new Error('jTemplates: Template is not defined.'); 1203 } 1204 }); 1205 }; 1206 1207 /** 1208 * Process template using data from URL 'url_' (only format JSON) and parameters 'param'. Update HTML code. 1209 * @param {string} url_ URL to data (in JSON) 1210 * @param {object} [param] parameters 1211 * @param {object} options options (over ajaxSettings) and callbacks 1212 * @return {jQuery} chainable jQuery class 1213 * @memberOf jQuery.fn 1214 */ 1215 jQuery.fn.processTemplateURL = function (url_, param, options) { 1216 var that = this; 1217 1218 var o = jQuery.extend({cache: false}, jQuery.ajaxSettings); 1219 o = jQuery.extend(o, options); 1220 1221 jQuery.ajax({ 1222 url: url_, 1223 type: o.type, 1224 data: o.data, 1225 dataFilter: o.dataFilter, 1226 async: o.async, 1227 cache: o.cache, 1228 timeout: o.timeout, 1229 dataType: 'text', 1230 success: function (d) { 1231 var r = jQuery(that).processTemplate(d, param, {StrToJSON:true}); 1232 if(o.on_success) { 1233 o.on_success(r); 1234 } 1235 }, 1236 error: o.on_error, 1237 complete: o.on_complete 1238 }); 1239 return this; 1240 }; 1241 1242 /** 1243 * Create new Updater. 1244 * @name Updater 1245 * @class This class is used for 'Live Refresh!'. 1246 * @param {string} url A destination URL 1247 * @param {object} param Parameters (for template) 1248 * @param {number} interval Time refresh interval 1249 * @param {object} args Additional URL parameters (in URL alter ?) as assoc array. 1250 * @param {array} objs An array of HTMLElement which will be modified by Updater. 1251 * @param {object} options options and callbacks 1252 */ 1253 var Updater = function (url, param, interval, args, objs, options) { 1254 this._url = url; 1255 this._param = param; 1256 this._interval = interval; 1257 this._args = args; 1258 this.objs = objs; 1259 this.timer = null; 1260 this._options = options || {}; 1261 1262 var that = this; 1263 jQuery(objs).each(function () { 1264 jQuery.data(this, 'jTemplateUpdater', that); 1265 }); 1266 this.run(); 1267 }; 1268 1269 /** 1270 * Create new HTTP request to server, get data (as JSON) and send it to templates. Also check does HTMLElements still exists in Document. 1271 */ 1272 Updater.prototype.run = function () { 1273 //remove deleted node 1274 this.objs = jQuery.grep(this.objs, function (elem) { 1275 return (jQuery.contains(document.body, elem.jquery ? elem[0] : elem)); 1276 }); 1277 //if no node then do nothing 1278 if(this.objs.length === 0) { 1279 return; 1280 } 1281 //ajax call 1282 var that = this; 1283 jQuery.ajax({ 1284 url: this._url, 1285 dataType: 'text', 1286 data: this._args, 1287 cache: false, 1288 success: function (d) { 1289 try { 1290 var r = jQuery(that.objs).processTemplate(d, that._param, {StrToJSON:true}); 1291 if(that._options.on_success) { 1292 that._options.on_success(r); //callback 1293 } 1294 } catch(ex) {} 1295 } 1296 }); 1297 //schedule next run 1298 this.timer = setTimeout(function (){that.run();}, this._interval); 1299 }; 1300 1301 /** 1302 * Start 'Live Refresh!'. 1303 * @param {string} url A destination URL 1304 * @param {object} param Parameters (for template) 1305 * @param {number} interval Time refresh interval 1306 * @param {object} args Additional URL parameters (in URL alter ?) as assoc array. 1307 * @param {object} options options and callbacks 1308 * @return {Updater} an Updater object 1309 * @memberOf jQuery.fn 1310 */ 1311 jQuery.fn.processTemplateStart = function (url, param, interval, args, options) { 1312 return new Updater(url, param, interval, args, this, options); 1313 }; 1314 1315 /** 1316 * Stop 'Live Refresh!'. 1317 * @return {jQuery} chainable jQuery class 1318 * @memberOf jQuery.fn 1319 */ 1320 jQuery.fn.processTemplateStop = function () { 1321 return jQuery(this).each(function () { 1322 var updater = jQuery.data(this, 'jTemplateUpdater'); 1323 if(updater == null) { 1324 return; 1325 } 1326 var that = this; 1327 updater.objs = jQuery.grep(updater.objs, function (o) { 1328 return o != that; 1329 }); 1330 jQuery.removeData(this, 'jTemplateUpdater'); 1331 }); 1332 }; 1333 1334 jQuery.extend(/** @scope jQuery.prototype */{ 1335 /** 1336 * Create new Template. 1337 * @param {string} s A template string (like: "Text: {$T.txt}."). 1338 * @param {array} includes Array of included templates. 1339 * @param {object} settings Settings. (see Template) 1340 * @return {Template} 1341 */ 1342 createTemplate: function (s, includes, settings) { 1343 return new Template(s, includes, settings); 1344 }, 1345 1346 /** 1347 * Create new Template from URL. 1348 * @param {string} url_ URL to template 1349 * @param {array} includes Array of included templates. 1350 * @param {object} settings Settings. (see Template) 1351 * @return {Template} 1352 */ 1353 createTemplateURL: function (url_, includes, settings) { 1354 var s = jQuery.ajax({ 1355 url: url_, 1356 dataType: 'text', 1357 async: false, 1358 type: 'GET' 1359 }).responseText; 1360 1361 return new Template(s, includes, settings); 1362 }, 1363 1364 /** 1365 * Get a Template for HTML node 1366 * @param {Element} HTML node 1367 * @return {Template} a Template or "undefined" 1368 */ 1369 getTemplate: function (element) { 1370 return jQuery.data(element, 'jTemplate'); 1371 }, 1372 1373 /** 1374 * Process template and return text content. 1375 * @param {Template} template A Template 1376 * @param {object} data data 1377 * @param {object} param parameters 1378 * @return {string} Content of template 1379 */ 1380 processTemplateToText: function (template, data, parameter) { 1381 return template.get(data, parameter, undefined, 0); 1382 }, 1383 1384 /** 1385 * Set Debug Mode 1386 * @param {Boolean} value 1387 */ 1388 jTemplatesDebugMode: function (value) { 1389 Template.DEBUG_MODE = value; 1390 } 1391 }); 1392 1393 })(jQuery);}; 1394