-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathbabajs.js
1107 lines (1045 loc) · 38.9 KB
/
babajs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @fileOverview This is the implementation of BabaJS - javascript template engine
* @author <a href="mailto:[email protected]">Amir Harel</a>
* @version 1.1.0
* @description check out full documentation at https://github.com/mrharel/babajs/
* BabaJS is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
var BabaJS = {
_stack : {},
_cacheTemplates : false,
_cache : {},
/**
* @description generates an HTML from a given template. this method could be used in synch (return value) or asynch (use callback) way.
* @param {Object|String} obj could be a template string or a template object. in case the obj is a string then the method will return the generated HTML.
* in case the obj is an object the following attributes are expected:
* templateName {String} the template name to be used
* fetcher {Function} a callback function to be used in case we need to fetch a template. the method will get two params, the template name and the callback to be called when the template is available. the callback will get the template text.
* context {Object} the context of the callbacks (fetcher and URLConvertor). if not provided then we use the context which was set in the setConfig and if there is no context there we use the window as the default context.
* URLConvertor {Function} callback function to convert a template name into a URL. please provide this method if you don't provide the fetcher. the template manager will try to fetch templates if needed, but it needs to know how to map a template name into a template URL. this callback will get the template name and return the template URL.
* ready {Function} callback to be used when the template is ready.
* requires {Object} this is used to tell the template manager which template/js/css files this template needs. the template manager will try to fetch these resources if they are not stored locally already. the requires could be either an array of template names or an object with 3 arrays: js,css,templates.
* @param {Object} data the data to be passed into the template.
* @returns {String|undefined} if ready is provided then the function return undefined, else it return the template only if the template manager has it locally.
*/
generateHTML: function(obj,data){
this._stack = {};
if( typeof obj === "string" ){ //we got the template as the first parameter.
var compiledTemplate = this._compile(obj); //compile it
return this._replaceCompiledTags(compiledTemplate.chtml,compiledTemplate.ctags,data); //evaluate it
}
if( !obj.ready ){ //this means the caller is expecting the html in the return value.
var template = this._templates[obj.templateName];
if( template ){
if( !template.compiled ){
template.compiled = this._compile(template.raw);
delete template.raw;
}
return this._replaceCompiledTags(template.compiled.chtml,template.compiled.ctags,data);
}
else{
throw new Error(obj.templateName + " was not found in template manager");
}
}
this._asynchGenerateHTML(obj,data);
},
/**
* @private
* @description config params for the template manager.
* @property {Function} fetcher callback function to be used in order to fetch templates when they are not stored locally.
* @property {Function} URLConvertor callback to be used in order to convert a template name into a URL
* @property {Object} context the context of the above callback functions. the default is this.
*/
_cfg: {fetcher:null,URLConvertor:null,context: this},
/**
* @description set global config for the template manager. The template manager will try to use these settings in case they are not provided in the generateHTL method.
* @param {Object} cfg can contains the followings:
* fetcher {Function}
* URLConvertor {Function}
* cacheTemplates {Boolean}
* context {Object}
*/
setConfig: function(cfg){
if( cfg.URLConvertor ) this._cfg.URLConvertor = cfg.URLConvertor;
if( cfg.fetcher ) this._cfg.fetcher = cfg.fetcher;
if( cfg.context ) this._cfg.context = cfg.context;
if( cfg.dep ) this._dep = cfg.dep;
this._cacheTemplates = cfg.cacheTemplates;
if( cfg.flagCb ) this._cfg.flagCb = cfg.flagCb;
},
/**
* @private
* @description dependencies of template. the structure is key=template name and value=object of templates, js, and css where all of them ar arryay of files of dependencies.
*/
_dep: {},
/**
* @description include template within a template. use this method to include a sub template from a template. when you include a template make sure you have the template locally since this method works in a snych way.
* @param {String} name the template name
* @param {Object} the data object to be used in the template.
* @returns {String} the evaluated template.
*/
includeTemplate: function(name,data){
var template = this._templates[name];
if( template ){
if( !template.compiled ){
template.compiled = this._compile(template.raw);
delete template.raw;
}
return this._replaceCompiledTags(template.compiled.chtml,template.compiled.ctags,data);
}
throw new Error( name + " was not found in template manager");
},
/**
* @description removes a template from the template manager
* @param {String} templateName the template to be removed.
*/
removeTemplate: function(templateName){
delete this._templates[templateName];
},
/**
* @private
* @description this method checks from the provided array which template we don't have locally.
* @param {Array} arr array of templates
* @returns {Array} array of templates that we don't have locally.
*/
_getMissingTemplates: function(arr){
var res = [];
for( var i=0; i<arr.length; i++){
if( !this._templates[arr[i]] ){
res.push(arr[i]);
}
}
return res;
},
/**
* @private
* @description fetch a template from server and store it locally.
*/
_fetchTemplate: function(obj,name,url,cb,context){
var fetcher = obj.fetcher ? obj.fetcher : this._cfg.fetcher;
var cbContext = obj.context ? obj.context : this._cfg.context;
if( fetcher ){
var that = this;
fetcher.call(cbContext,name,function(text){
that._cache[name] = true;
that._templates[name] = {compiled: that._compile(text)};
cb.call(context,true);
});
}
else{
this._ajax(url, function(text){
if( text !== false ){
this._cache[name] = true;
this._templates[name] = {compiled: this._compile(text)};
cb.call(context,true);
}
else{
cb.call(context,false);
}
},this);
}
},
/**
* @private
* @description fetch a url from the server
*/
_ajax: function(url,cb,context){
var xhr;
if( typeof ActiveXObject !== "undefined" ){
xhr = new ActiveXObject( "Microsoft.XMLHTTP" );
}
else if(typeof XMLHttpRequest !== "undefined" ){
xhr = new XMLHttpRequest();
}
xhr.open("GET",url);
xhr.onreadystatechange = function(){
if( xhr.readyState == 4 ){//&& xhr.status == 200 ){
if( xhr.status == 200 || xhr.status == 0 ){
cb.call(context,xhr.responseText);
}
else{
cb.call(context,false);
}
}
};
xhr.send();
},
/**
* @description make sure that required files are stored locally.
* @param {Array} tempArr array of all the template names
* @param {Array} jsArr array of all the javascript files
* @param {Array} cssArr array of all the css files.
* @param {Function} cb callback to be called when all the resources were loaded successfuly.
*/
ensureLocal: function(tempArr,jsArr,cssArr,cb){
this._ensureLocal(
{URLConvertor:this._cfg.URLConvertor,fetcher:this._cfg.fetcher,context:this._cfg.context},
tempArr,
jsArr,
cssArr,
function(success){
cb.call(this._cfg.context,success);
},this);
},
_getNonCachedTemplates: function(arr){
var nonCached = [];
for( var i=0; i<arr.length; i++ ){
if( !this._cache[arr[i]] ){
nonCached.push(arr[i]);
}
}
return nonCached;
},
/**
* @private
* @description fetches the templates/js/css files.
*/
_ensureLocal: function( obj,tempArr,jsArr,cssArr,cb,context ){
jsArr = jsArr ? jsArr : [];
cssArr = cssArr ? cssArr : [];
var counter = 0;
tempArr = this._getNonCachedTemplates(tempArr);
var convertor = obj.URLConvertor ? obj.URLConvertor : this._cfg.URLConvertor;
var cbContext = obj.context ? obj.context : this._cfg.context;
var totalCount = tempArr.length + cssArr.length + jsArr.length;
if( totalCount == 0 ){
cb.call(context,true);
}
for( var i=0; i<tempArr.length; i++ ){
var url = convertor ? convertor.call(cbContext,tempArr[i]) : "";
this._fetchTemplate(obj,tempArr[i],url,function(success){
if( success === false ){
throw new Error("Failed to fetch template" + url );
return;
}
counter++;
if( counter == totalCount ){
cb.call(context,true);
}
},this);
}
for( var i=0; i<jsArr.length; i++){
this._fetchJS(jsArr[i],function(success){
counter++;
if( counter == totalCount ){
cb.call(context,true);
}
},this);
}
for( var i=0; i<cssArr.length; i++){
this._fetchCSS(cssArr[i],function(success){
counter++;
if( counter == totalCount ){
cb.call(context,true);
}
},this);
}
},
/**
* @private
* @description fetch the javascript file and place it in the head tag
*/
_fetchJS: function(js,cb,context){
if( this._js[js] ){
cb.call(context,true);
}
else{
this._ajax(js,function(text){
if( text === false ){
cb.call(context,false);
return;
}
var script = document.createElement("script");
script.type = "text/javascript";
if( this._isIE() ){
//IE has its own thing: http://www.phpied.com/dynamic-script-and-style-elements-in-ie/
script.text = text;
}else{
script.innerHTML = text;
}
document.getElementsByTagName("head")[0].appendChild(script);
this._js[js] = true;
cb.call(context,true);
},this);
}
},
/**
* @private
*/
_ie: null,
/**
* @private
* @description returns true if the browser is IE
*/
_isIE: function(){
if( this._ie === null ){
this._ie = navigator.userAgent.indexOf("MSIE") != -1 ? true : false;
}
return this._ie;
},
/**
* @private
* @description fetch a css file and place it in the head tag
*/
_fetchCSS: function(css,cb,context){
if( this._css[css] ){
cb.call(context,true);
}
else{
this._ajax(css,function(text){
if( text === false ){
cb.call(context,false);
return;
}
var style = document.createElement("style");
style.type = "text/css";
if( this._isIE() ){
//IE has its own thing: http://www.phpied.com/dynamic-script-and-style-elements-in-ie/
style.styleSheet.cssText = text;
}
else{
style.innerHTML = text;
}
document.getElementsByTagName("head")[0].appendChild(style);
this._css[css] = true;
cb.call(context,true);
},this);
}
},
/**
* @private
* @description cheks all the dependencies of a template in a recursive way.
*/
_getDependencies: function(templateName,res,visited){
res = res? res : {templates:[],js:[],css:[]};
visited = visited? visited : {};
if( visited[templateName] ) return res; //preventing endless loops
if( !this._dep[templateName] ) return res;
visited[templateName] = true;
if( this._dep[templateName].templates ){
res.templates = this._uniqueAdd(res.templates,this._dep[templateName].templates);
for( var i=0; i<this._dep[templateName].templates.length; i++){
this._getDependencies(this._dep[templateName].templates[i],res,visited);
}
}
if( this._dep[templateName].js ){
res.js = this._uniqueAdd(res.js,this._dep[templateName].js);
}
if( this._dep[templateName].css ){
res.css = this._uniqueAdd(res.css,this._dep[templateName].css);
}
return res;
},
/**
* @private
* @description concat two arrays and making sure for unique values.
* @returns {Array} new array
*/
_uniqueAdd: function(arr1,arr2){
var res = [];
//if( !(arr2 instanceof Array) ) arr2 = [arr2];
var checker = {};
for( var i=0; i<arr1.length; i++ ){
res.push(arr1[i]);
checker[arr1[i]] = true;
}
for( var i=0; i<arr2.length; i++ ){
if( !checker[arr2[i]] ) res.push(arr2[i]);
}
return res;
},
/**
* @private
* @description generate an HTML in a asynch mode.
*/
_asynchGenerateHTML: function(obj,data){
var cbContext = obj.context ? obj.context : this._cfg.context;
var templatesArr = [obj.templateName];
if( obj.requires && obj.requires instanceof Array ) templatesArr = templatesArr.concat(obj.requires);
if( obj.requires && obj.requires.templates ) templatesArr = templatesArr.concat(obj.requires.templates);
var templatesToFetch = this._getMissingTemplates( templatesArr );
var jsRequires = [];
if( obj.requires && obj.requires.js ) jsRequires = obj.requires.js;
var cssRequires = [];
if( obj.requires && obj.requires.css ) cssRequires = obj.requires.css;
//getting the dependencies we have for the template
var dep = this._getDependencies(obj.templateName);
var visited = {};
visited[obj.templateName] = true;
//gathering all the dependencies for all the templates that are required for this template.
for( var i=0; i<templatesArr.length; i++ ){
this._getDependencies(templatesArr[i],dep,visited);
}
templatesToFetch = this._uniqueAdd(templatesToFetch,dep.templates);
jsRequires = this._uniqueAdd(jsRequires,dep.js);
cssRequires = this._uniqueAdd(cssRequires,dep.css);
this._ensureLocal( obj,templatesToFetch , jsRequires, cssRequires, function(success){
var template = this._templates[obj.templateName];
if( !template.compiled ){
template.compiled = this._compile(template.raw);
delete template.raw;
}
var res = this._replaceCompiledTags(template.compiled.chtml,template.compiled.ctags,data);
if( obj.ready ) obj.ready.call(cbContext,res,obj.templateName);
}, this);
},
_templates:{},
_css:{},
_js:{},
/**
* @description Add templates to the template manager to be stored locally.
* @param {Object} templates associative array with key=template name and value= template text.
*/
addTemplates: function(templates){
for( var name in templates ){
this.addTemplate(name, templates[name]);
}
},
/**
* @description Add one template to the template manager
* @param {String} templateName
* @param {String} text the template string
*/
addTemplate: function(templateName,text){
if( this._templates[templateName] ) return;
var compiled = this._compile(text);
this._templates[templateName] = {compiled:compiled};
if( this._cacheTemplates ){
this._cache[templateName] = true;
}
},
/**
* get a template object to be used without the need of BabaJS.
* This is used to support RequireJS and the BabaJS requirejs plugin where
* you can set a template as a dependency and get the template object.
* @param templateName
* @returns {Object}
* <pre>
* toString {Function} if the template just needed to be used as simple HTML snippet
* compile {Function} get one paraneter which is the data to be passed to the template and return
* the compiled HTML string.
* </pre>
*/
getTemplateObj: function(templateName){
if( !this._templates[templateName] ) return null;
return {
_tplObj : this._templates[templateName],
_babajs : this,
_tplName : templateName,
toString: function(){
return this.compile();
},
compile: function(data){
return this._babajs.includeTemplate(this._tplName,data);
}
};
},
/**
* @private
* @description replace the compiled tags ( _{x}_ ) with its evaluation ctag.
* @param {String} chtml the compiled HTML where template tags where replaced with _{x}_ where x=ctag id
* @param {Object} ctags associative array where key=ctag id and value=ctag object.
* @param {Object} data the data to be passed to the template
* @returns {String} the result html.
*/
_replaceCompiledTags: function(chtml,ctags,data){
var regex = /_{([\d]+)}_/g;
var regReplace = /_{[\d]+}_/;
var match;
regex.lastIndex = 0;
while((match = regex.exec(chtml))){
chtml = chtml.replace( regReplace , this._evalCtag(ctags,match[1],data));
regex.lastIndex = 0;
}
return chtml;
},
/**
* @private
* @description return the html representation of a ctag when given the data to evaluate it with.
* @param {Object} ctags associative array where key=ctag id and value=ctag object.
* @param {Number} ctagid the ctag id to be eval
* @param {Object} data the data to be passed to the template
* @returns {String} the result HTML.
*/
_evalCtag: function(ctags,ctagid,data){
var ctag = ctags[ctagid];
switch( ctag.type ){
case "code" :return this._evalCodeTag(data,ctag);
case "assign" :return this._evalCodeTag(data,ctag,true);
case "if" :return this._evalConditionTag(data,ctags,ctag);
case "loop" :return this._evalLoopTag(data,ctags,ctag);
default:
return "Unknown tag";
}
},
/**
* @private
* @description evaluate a loop ctag
*/
_evalLoopTag: function(data,ctags,ctag){
var vars = "";
var res = "";
var fnBody = "";
for( var i in this._stack ){
vars += "var " + i + "=" + this._stringify(this._stack[i]) + ";\r\n";
}
var stack = "";
for( var i in this._stack ){
stack += "this._stack['"+i+"'] = " + i + ";\r\n";
}
for( var i=0; i<ctag.vars.length;i++){
stack += "this._stack['"+ctag.vars[i]+"'] = " + ctag.vars[i] + ";\r\n";
}
var code = "var _retVal = '';\r\n"+ctag.code + "{\r\n\t "+stack+"\r\n _retVal += this._replaceCompiledTags('"+ctag.chtml1.replace(/[\r\n]/g,"")+"',ctags,data);} return _retVal;";
fnBody = 'try{'+
vars +
code +
'}catch(_err){return _err;}finally{'+
stack +
'}' ;
try{
var fn = new Function("data","ctags", fnBody);
var res = fn.call(this,data,ctags);
if( typeof res == "string" ){
res = res.replace(/[\$]/g,"$");
}
}
catch(err){
this._log("ERROR: ", err," Fn=_evalLoopTag ctag=",ctag," data=",data);
throw err;
}
return res;
},
_log: function(){
if( typeof console !== "undefined" ){
console.log.apply(console,arguments);
}
},
/**
* @private
* @description eval a condition tag
*/
_evalConditionTag: function(data,ctags,ctag){
var vars = "";
var res = "";
for( var i in this._stack ){
vars += "var " + i + "=" + this._stringify(this._stack[i]) + ";\r\n";
}
var stack = "";
for( var i in this._stack ){
stack += "this._stack['"+i+"'] = " + i + ";\r\n";
}
for( var i=0; i<ctag.vars.length;i++){
stack += "this._stack['"+ctag.vars[i]+"'] = " + ctag.vars[i] + ";\r\n";
}
var code = "return (" + ctag.code + ");";
var fnBody = 'try{'+
vars +
code +
'}catch(_err){return _err;}finally{'+
stack +
'}' ;
try{
var fn = new Function("data", fnBody);
var res = fn.call(this,data);
if( res ){
return this._replaceCompiledTags(ctag.chtml1,ctags,data);
}
else{
return this._replaceCompiledTags(ctag.chtml2,ctags,data);
}
}
catch(err){
this._log("ERROR: ",err," Fn=_evalConditionTag ctag=",ctag," data=",data);
throw err;
}
return res;
},
/**
* @private
* @description eval a code tag
*/
_evalCodeTag: function(data,ctag,assign){
var vars = "";
var res = "";
for( var i in this._stack ){
vars += "var " + i + "=" + this._stringify(this._stack[i]) + ";\r\n";
}
var stack = "";
for( var i in this._stack ){
stack += "this._stack['"+i+"'] = " + i + ";\r\n";
//vars += "var " + i + "=" + this._stack[i] + ";\r\n";
}
for( var i=0; i<ctag.vars.length;i++){
stack += "this._stack['"+ctag.vars[i]+"'] = " + ctag.vars[i] + ";\r\n";
}
var code = ((assign===true)? "return " : "" ) + ctag.code + ((assign===true)? ";" : "" );
var fnBody = 'try{'+
vars +
code +
'}catch(_err){return _err;}finally{'+
stack +
' '+
'}' ;
try{
var fn = new Function("data", fnBody);
var res = fn.call(this,data);
if( typeof res === 'undefined' ){
res = "";
}
if( res instanceof Error ){
this._log("BabaJS Error: ",res.message,res.href,res.lineNo,res.source);
return res.message;
}
if( typeof res == "string" ){
res = res.replace(/[\$]/g,"$"); //$ sign needs to be replaced with the special char to fix a replace bug
}
}
catch(err){
this._log("ERROR: ",err," Fn=_evalCodeTag ctag=",ctag," data=",data);
throw err;
}
//checking to see if we have flags for this compiled tag
if( ctag.flags.length ){
if( this._cfg.flagCb ){ //checking to see if there is a callback hook
var cbRes = this._cfg.flagCb.call(this._cfg.context,res, ctag.flags);
if( typeof cbRes == "string" ){ //anything which is not a string is ignored.
res = cbRes;
}
else{
res = this._processPredefinedFlags(ctag.flags,res);
}
}
else{
res = this._processPredefinedFlags(ctag.flags,res);
}
}
return res;
},
/**
* @private
* Predefined flags of BabaJS
*
* @param flags {Array}
* @param res {string} the string to convert
*/
_processPredefinedFlags: function(flags,res){
if( flags[0] == 's' ) return this._secureText(res,"low");
if( flags[0] == 'S' ) return this._secureText(res,"high");
return res;
},
/**
* Escape string according to the flag level
*
* @param text {String} string to escape
* @param flag {String} 'low' or 'high' for different type of ecaping levels.
* low level will escape <,>,",and '
* hight level will escape <,>,&,",',`,,!,@,$,%,(,),[,],{,},= and+
* @return {String} escaped string
*/
secureText: function(text,flag){
flag = (typeof flag == "undefined") ? 'low' : flag;
return this._secureText(text,flag);
},
/**
* @private
* Escape string according to the flag level. i wad inspired by Ryan Grove's post:'There's more to HTML escaping than &, <, >, and "' (http://wonko.com/post/html-escaping)
*
* @param text {String} string to escape
* @param flag {String} 'low' or 'high' for different type of ecaping levels.
* low level will escape <,>,",and '
* hight level will escape <,>,&,",',`,,!,@,$,%,(,),[,],{,},= and +
* @return {String} escaped string
*
*
*/
_secureText: function(text,security){
if( !text ) return text;
var rx = {
"low" : /[\<\>\"\']/g ,
"high" : /[\<\>\&\"\'\`\,\!\@\$\%\(\)\[\]\{\}\=\+]/g
};
var that = this;
var replacer = function(str){
return that._escaped[str];
};
return text.replace( rx[security] , replacer );
},
/**
* escape special characters object. the key is the char and the value is the ecaped value.
* for more reference on Special Characters in HTML - http://www.degraeve.com/reference/specialcharacters.php
*/
_escaped: {
"\'": "‘" ,
"<" : "<",
">" : ">" ,
"&" : "&" ,
"\"": """ ,
"`" : "`",
"," : "," ,
"!" : "!" ,
"@" : "@" ,
"$" : "$" ,
"(" : "(" ,
")" : ")" ,
"[" : "[" ,
"]" : "]" ,
"{" : "{" ,
"}" : "}",
"=" : "=" ,
"+" : "+",
"%" : "%"
},
/**
* @description convert an object into a string. we try to use the browser implementation as a default. if there is no JSON
* object we throw an error only if the obj is either an Object or an Array.
*/
_stringify: function(obj){
if( typeof JSON !== "undefined" ) return JSON.stringify(obj);
if( typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean" ) return obj.toString();
throw new Error("Can't stringify an Object or an Array without JSON Support. please include a JSON object to the window object. Try https://github.com/douglascrockford/JSON-js");
},
/**
* @private
* @description compile a template into a ctags and chtml.
* @param {String} t template text
* @returns {Object} with chtml and ctags.
*/
_compile: function(t){
var q = []; //queue of the current ctag that we are working on. this is only the ctag id.
var count = 0; //keeping track of the ctag ids.
//the return value
var res = {
chtml: "",
ctags: {}
};
var c; //current char
var currentFlag = "";
var currentFlagType = null;
var current = "chtml"; //controller that tells us if we are working on a ctag or on the chtml from the res object.
var currentTag = null; //the current ctag type
var state = "html"; //if the current is the ctag id, we need to know on which part of the ctag we are working on right now.
//starting to loop over all the chars in the template.
for( var i=0,len=t.length; i<len; i++){
c = t.charAt(i); //the current char
if( (c == "<") && ( i<len-1 && t.charAt(i+1) == "%" ) ){ //checking to see if we found an open ctag
var ctagType = this._getCtagType(t,i+2); //getting the ctag type
currentTag = ctagType; //saving the current ctag type
if( ctagType == "code" || ctagType == "assign" || ctagType == "if" || ctagType == "loop" ){
//first we need to save the state for the current ctag
if( q.length > 0 ){
res.ctags[q[q.length-1]]._state = state;
}
var ctag = this._getNewCtag(ctagType); //creating a new ctag
q.push(count); //add it to the top of the queue
res.ctags[count] = ctag; //add it to the result object
this._addToCurrent(current,state,res, "_{" + count + "}_"); //add the ctag reference to the current working text.
current = count; //save the current ctag id
count++; //increase the number of ctags
}
state = "tag"; //we are now looping over the ctag name
}
else if( state == "flag" ){
if( c != "," && c != " " && c != "\"" && c != "\'" && c != ")" ){
currentFlag += c;
if( c == "." && currentFlagType == "number" ){
currentFlagType = "float";
}
else if( c >= "0" && c <= "9" && currentFlagType !== "string" && currentFlagType !== "float"){
currentFlagType = "number";
}
else{
currentFlagType == "string"
}
}
else if( c == "," || c == " " ){
if( currentFlag.length ){
var value = currentFlag;
if( currentFlagType == "number" ) value = parseInt(currentFlag);
if( currentFlagType == "float" ) value = parseFloat(currentFlag);
ctag.flags.push(value);
}
currentFlag = "";
currentFlagType = null;
}
else if( c== ")" ){
if( currentFlag.length ){
var value = currentFlag;
if( currentFlagType == "number" ) value = parseInt(currentFlag);
if( currentFlagType == "float" ) value = parseFloat(currentFlag);
ctag.flags.push(value);
}
currentFlag = "";
currentFlagType = null;
if( t.charAt(i+1) == " " || t.charAt(i+1) == "="){
state = "code";
if( t.charAt(i+1) == "=" ){
ctag.type = "assign";
ctagType = "assign";
}
i++;
}
}
}
else if( state == "tag" ){
if( (ctagType == "code" || ctagType == "assign") && c == "%" && t.charAt(i+1) == 'f' ){
state = "flag";
i+= 2;
currentFlag = "";
currentFlagType = null;
}
else if( ctagType == "code" && c == "%" && t.charAt(i+1).toLowerCase() != 's' && t.charAt(i+1) != 'f'){
state = "code"
}
else if( ctagType == "assign" && c == "=" ){
state = "code";
}
else if( c == " " ) state = "code";
else if( c == "%" && i<len-1 && t.charAt(i+1) == ">"){ //its either endloop or endif or else
if(currentTag == "else" ){
state = "chtml2";
}
else{
q.pop(); //we are no longer working on this ctag
if( q.length == 0 ){ //if there is no more ctags in the queue, we set the working env to the up most html
currentTag = null;
current = "chtml";
state = "html";
}
else{ //we set the previous ctag to be the current one.
var lastCtag = res.ctags[q[q.length-1]];
currentTag = lastCtag.type;
current = q[q.length-1];
state = lastCtag._state;
}
}
i++;
}
}
else if( c == "%" && i<len-1 && t.charAt(i+1) == ">" ){ //found a closing ctag
if( currentTag == "code" || currentTag == "assign" ){//|| currentTag == "endif" || currentTag == "endloop"){
//closing the current ctag
q.pop(); //we are no longer working on this ctag
if( q.length == 0 ){ //if there is no more ctags in the queue, we set the working env to the up most html
currentTag = null;
current = "chtml";
state = "html";
}
else{ //we set the previous ctag to be the current one.
var lastCtag = res.ctags[q[q.length-1]];
currentTag = lastCtag.type;
current = q[q.length-1];
state = lastCtag._state;
}
}
else if( currentTag == "if" || currentTag == "loop"){ //if the closing ctag is if or loop it means that we are now looping the chtm1
state = "chtml1";
}
i++; //ignoreing the closing sign i.e ">"
}
else if( state != "tag" ){ //we don't keep the ctag name
this._addToCurrent(current,state,res,c);
}
}
this._findVars(res); //find all the var declerations in the code sections.
//this._removeNewLinesFromCode(res);
return res;
},
/**
* @private
* @description finds all var in the code and put it in the vars array
*/
_findVars: function(res){
for( var id in res.ctags ){
res.ctags[id].vars = this._findVar(res.ctags[id].code);
}
},
/**
* check if a char is a white space
* @param c {String} one char
* @return {Boolean} true is this is a whitespace char
*@private
*/
_isWhiteSpace: function(c){
return /[\s]/.test(c);
},
/**
* finds all variable declerations in the code segment.
* @param code{String}
* @return {Array} array with all the variables defined in this code segment.
* @private
*/
_findVar: function(code){
var res = []; //return array with all variable found in the code.
var inDec = false; //are we inside of a decleration of variables
var newVar = ""; //new var
var pc = 0; // perenthasis count
var bc = 0; // bracket count
var abc = 0; //assign bc
var asc = 0;
var sc = 0; //square bracket count;
var inFn = false; //are we inside of a function - we ignore code if we do
var inStr = false; //are we inside of a string - we ignore the code if we do
var dqc = 0; //double qoute counter
var sqc = 0; //single quote counter
var inAssign = false; //are we in an assignment code for a variable
var inLoop = false; //are we inside of a loop. we need to know since you can define vars like 'for( var a in b )'
for( var i=0, len = code.length; i< len ; i++ ){ //main loop on the code segment.
var ch = code.charAt(i); //current char
if( ch == "("){
pc++;
//testing to see if we have "function("
if( !inFn && i>=8 && code.charAt(i-1) == "n" && code.charAt(i-2) == "o" && code.charAt(i-3) == "i" && code.charAt(i-4) == "t" && code.charAt(i-5) == "c" && code.charAt(i-6) == "n" && code.charAt(i-7) == "u" && code.charAt(i-8) == "f" ){
inFn = true;
}
//testing to see if we have "for(" or "while("
if( !inLoop && !inFn && !inAssign &&
((i>=3 && code.charAt(i-1) == 'r' && code.charAt(i-2) == 'o' && code.charAt(i-3) == 'f' )||
(i>=5 && code.charAt(i-1) == 'e' && code.charAt(i-2) == 'l' && code.charAt(i-3) == 'i' && code.charAt(i-4) == 'h' && code.charAt(i-5) == 'w'))){
inLoop = true;
}
}
else if( ch == ")"){
pc--;
if( pc == 0 && !inFn && !inStr && inLoop ) inLoop = false; //checking to see if the loop was closed.
if( inFn && bc == 0 && pc == 0 ) inFn = false; //checking to see if a function was closed.
}
else if( ch == "{"){
bc++;
if( inAssign ) abc++;
}
else if( ch == "["){
sc++;
if( inAssign ) asc++;
}
else if( ch == "]"){
sc--;
if( inAssign ) asc--;
}
else if( ch == "}"){
bc--;
if( inAssign ) abc--;
if( inFn && bc == 0 && pc == 0 ) inFn = false; //checking to see if a function was closed.
}
else if( ch == "\"" ){
dqc = dqc ? 0 : 1;
}
else if( ch == "\'" ){
sqc = sqc ? 0 : 1;
}
else if( ch == "=" && !inFn && inDec && !inStr ){ //checking to see if we are in an assignment for a variable
inAssign = true;
if( newVar ){
res.push( newVar );
newVar = "";
}
continue;
}
if( sqc || dqc ){ //checking to see if we are inside of a string
inStr = true;
}
else{
inStr = false;
}
if( inFn || inStr ) continue; //ignoring function and strings
if( !inLoop && pc) continue; // ignoring function parameters: someFunc(a,b);