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