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