src/JsRender/Roo.vala
[app.Builder.js] / src / JsRender / Roo.vala
1 /*
2  * Renderer for Javascript output (roo library based)
3  * 
4  * - translation support
5  * -  doubleStringProps contains elements that are 'translable'
6  *    ** in the old method this our compression tool could extract them
7  *  - the  new idea is to make a list at the top of the javascript file
8  *    and output a map...
9  *    
10  * 
11  * 
12  * 
13  * 
14  */
15 namespace JsRender {
16
17     static int rid = 0; 
18    
19     class Roo : JsRender 
20     {
21         string region;
22         bool disabled;
23
24
25         
26         public Roo(Project.Project project, string path) {
27             base( project, path);
28             this.xtype = "Roo";
29              this.language = "js";
30             
31             
32             //this.items = false;
33             //if (cfg.json) {
34             //    var jstr =  JSON.parse(cfg.json);
35             //    this.items = [ jstr ];
36             //    //console.log(cfg.items.length);
37             //    delete cfg.json; // not needed!
38             // }
39             this.modOrder = "001"; /// sequence id that this uses.
40             this.region = "center";
41             this.disabled = false;
42             
43             // super?!?!
44             this.id = "file-roo-%d".printf(rid++);
45             //console.dump(this);
46             // various loader methods..
47
48             string[]  dsp = { "title",
49                 "legend",
50                 "loadingText",
51                 "emptyText",
52                 "qtip",
53                 "value",
54                 "text",
55                 "emptyMsg",
56                 "displayMsg",
57                 "html",
58                 };
59             for (var i=0;i<dsp.length;i++) {
60                 this.doubleStringProps.add(dsp[i]);
61             }
62
63             
64         }
65     
66     /*    
67         setNSID : function(id)
68         {
69             
70             this.items[0]['|module'] = id;
71        
72             
73         },
74         
75         
76         getType: function() {
77             return 'Roo';
78         },
79
80     */
81                 
82         public   override void   removeFiles() {
83                 var html = GLib.Path.get_dirname(this.path) +"/templates/" + name + ".html";
84                 if (FileUtils.test(html, FileTest.EXISTS)) {
85                         GLib.FileUtils.remove(html);
86                 }
87                 var js = GLib.Path.get_dirname(this.path) +"/" + name + ".html";
88                 if (FileUtils.test(js, FileTest.EXISTS)) {
89                         GLib.FileUtils.remove(js);
90                 }
91         }
92                 
93         public  override void  loadItems() throws GLib.Error // : function(cb, sync) == original was async.
94         {
95             
96              
97                 print("load Items!");
98                 if (this.tree != null) {
99                         return;
100                 }
101                 print("load " + this.path);
102
103                 var pa = new Json.Parser();
104                 pa.load_from_file(this.path);
105                 var node = pa.get_root();
106
107                 if (node.get_node_type () != Json.NodeType.OBJECT) {
108                         throw new Error.INVALID_FORMAT ("Unexpected element type %s", node.type_name ());
109                 }
110                 var obj = node.get_object ();
111         
112         
113                 this.modOrder = this.jsonHasOrEmpty(obj, "modOrder");
114                 this.name = this.jsonHasOrEmpty(obj, "name");
115                 this.parent = this.jsonHasOrEmpty(obj, "parent");
116                 this.permname = this.jsonHasOrEmpty(obj, "permname");
117                 this.title = this.jsonHasOrEmpty(obj, "title");
118                 this.modOrder = this.jsonHasOrEmpty(obj, "modOrder");
119
120                 var bjs_version_str = this.jsonHasOrEmpty(obj, "bjs-version");
121                 bjs_version_str = bjs_version_str == "" ? "1" : bjs_version_str;
122
123                 
124                 // load items[0] ??? into tree...
125                 if (obj.has_member("items") 
126                         && 
127                         obj.get_member("items").get_node_type() == Json.NodeType.ARRAY
128                         &&
129                         obj.get_array_member("items").get_length() > 0
130                 ) {
131                         this.tree = new Node(); 
132                         var ar = obj.get_array_member("items");
133                         var tree_base = ar.get_object_element(0);
134                         this.tree.loadFromJson(tree_base, int.parse(bjs_version_str));
135                 }
136
137
138             
139         }
140         /**
141          * old code had broken xtypes and used arrays differently,
142          * this code should try and clean it up..
143          * 
144          * 
145          * /
146         fixItems : function(node, fixthis)
147         {
148             if (fixthis) {
149                 // fix xtype.
150                 var fn = this.guessName(node);
151                 //print("guessname got " + fn);
152                 if (fn) {
153                     var bits = fn.split('.');
154                     node.xtype = bits.pop();
155                     node['|xns'] = bits.join('.');
156                     
157                 }
158                 // fix array???
159                  
160                 
161             }
162             if (!node.items || !node.items.length) {
163                 return;
164             }
165             var _this = this;
166             var aitems = [];
167             var nitems = [];
168             node.items.forEach(function(i) {
169                 
170                 
171                 
172                 _this.fixItems(i, true);
173                 if (i.xtype == 'Array') {
174                     aitems.push(i);
175                     return;
176                 }    
177                 nitems.push(i);
178             });
179             node.items = nitems; 
180             
181             if (!aitems.length) {
182                 return;
183             }
184             
185             aitems.forEach(function(i) {
186                 
187                 if (!i.items || !i.items.length) {
188                     return;
189                 }
190                 var prop = i['*prop'] + '[]';
191                 // colModel to cm?
192                 i.items.forEach(function(c) {
193                     c['*prop']  = prop;
194                     node.items.push(c);
195                     
196                 });
197                 
198                 
199             });
200             
201             
202             // array handling.. 
203             
204             
205             
206             
207             
208         },
209     */
210         
211         public  override  void save()
212         {
213             
214                 print("--- JsRender.Roo.save");
215                 
216                 this.transStrings = new Gee.HashMap<string,string>();
217                 this.findTransStrings(this.tree);
218                 
219                 this.saveBJS();
220
221                 // no tree..
222                 if (this.tree == null) {
223                         return;
224                 }
225                 // now write the js file..
226                 string js;
227                 try {
228                         Regex regex = new Regex("\\.(bjs|js)$");
229
230                         js = regex.replace(this.path,this.path.length , 0 , ".js");
231                 } catch (RegexError e) {
232                         this.name = "???";
233                         print("count not make filename from path");
234                         return;
235                 }
236
237
238                 //var d = new Date();
239                 var js_src = this.toSource();            
240                 //print("TO SOURCE in " + ((new Date()) - d) + "ms");
241                 try {
242                         this.writeFile(js, js_src);            
243                 } catch (FileError e ) {
244                         print("Save failed\n");
245                 }
246                 // for bootstrap - we can write the HTML to the templates directory..
247                  
248             //var top = this.guessName(this.items[0]);
249             //print ("TOP = " + top)
250              
251             
252             
253             
254         }
255
256          
257
258          
259         public override void saveHTML ( string html )
260         {
261                  
262                 var top = this.tree.fqn();
263                 print ("TOP = " + top + "\n" );
264                 if (top.index_of("Roo.bootstrap.") < 0 &&
265                     top.index_of("Roo.mailer.") < 0
266                         ) {
267                         return;
268                 }
269                 
270                 
271                 //now write the js file..
272                 string fn;
273                 try {
274                         Regex regex = new Regex("\\.(bjs|js)$");
275
276                         fn = regex.replace(this.path,this.path.length , 0 , ".html");
277                 } catch (RegexError e) {
278                         this.name = "???";
279                         print("count not make filename from path");
280                         return;
281                 }
282                 var bn = GLib.Path.get_basename(fn);
283                 var dn = GLib.Path.get_dirname(fn);
284
285                 var targetdir = dn + (
286                         top.index_of("Roo.mailer.") < 0 ? "/templates" : "" );
287                               
288                 
289                 if (!FileUtils.test(targetdir, FileTest.IS_DIR)) {
290                         print("Skip save - templates folder does not exist : %s\n", targetdir);
291                         return;
292                 }
293                 print("SAVE HTML -- %s\n%s\n",targetdir + "/" +  bn, html);
294                 try {
295                         this.writeFile(targetdir + "/" +  bn , html);            
296                 } catch (FileError e ) {
297                         print("SaveHtml failed\n");
298                 }
299             
300             
301             
302         }
303
304                 public Gee.ArrayList<string> findxincludes(Node node,   Gee.ArrayList<string> ret)
305                 {
306                         
307                         if (node.props.has_key("* xinclude")) {
308                                 ret.add(node.props.get("* xinclude"));
309                         }
310                         for (var i =0; i < node.items.size; i++) {
311                                 this.findxincludes(node.items.get(i), ret);
312                         }
313                         return ret;
314                                 
315                 }
316                  
317
318                  
319                 public void  findTransStrings(Node node )
320                 {
321                         // iterate properties...
322                         // use doubleStringProps
323                         
324                         // flagging a translatable string..
325                         // the code would use string _astring to indicate a translatable string
326                         // the to use it it would do String.format(this._message, somedata);
327                         
328                         // loop through and find string starting with '_' 
329                         if (node == null) {
330                                 return;
331                         }               
332                         
333                         var iter = node.props.map_iterator();
334                         while (iter.next()) {
335                                 // key formats : XXXX
336                                 // XXX - plain
337                                 // string XXX - with type
338                                 // $ XXX - with flag (no type)
339                                 // $ string XXX - with flag
340                                 string kname;
341                                 string ktype;
342                                 string kflag;
343                                 node.normalize_key(iter.get_key(), out kname, out kflag, out ktype);
344                                 if (kflag == "$") {
345                                         continue;
346                                 }
347                                 var str = iter.get_value();
348                                 if (this.doubleStringProps.index_of(kname) > -1) {
349                                         this.transStrings.set(str,  
350                                                 GLib.Checksum.compute_for_string (ChecksumType.MD5, str)
351                                         );
352                                         continue;
353                                 }
354                                 print("flag=%s type=%s name=%s\n", kflag,ktype,kname);
355                                 if (ktype.ascii_casecmp("string") == 0 && kname[0] == '_') {
356                                         this.transStrings.set(str,  
357                                                 GLib.Checksum.compute_for_string (ChecksumType.MD5, str)
358                                         );
359                                         continue;
360                                 }
361                                 
362                         }
363                          
364
365                         
366                         // iterate children..
367                         for (var i =0; i < node.items.size; i++) {
368                                 this.findTransStrings(node.items.get(i) );
369                         }
370                 
371                                 
372                 }  
373                 
374                 public string  transStringsToJs()
375                 {
376                         if (this.transStrings.size < 1) {
377                                 return "";
378                         }
379                         string ret = " strings : {\n";
380                         string[] kvs = {};
381                         var iter = this.transStrings.map_iterator();
382                         while (iter.next()) {
383                                 kvs +=  ("  '" + iter.get_value() + "' :" + 
384                                         this.tree.quoteString(iter.get_key())
385                                         );
386                         }
387                         return " strings : {\n" + string.joinv(",\n", kvs) + "\n" + 
388                                 " },";
389                                 
390                 
391               
392                         
393                 }
394                   
395         /**
396          * javascript used in Webkit preview 
397          */
398         
399         public override string  toSourcePreview()
400         {
401                         print("to source preview\n");
402                         if (this.tree == null) {
403                                 return "";
404                         }
405                         var top = this.tree.fqn();
406                         var xinc = new Gee.ArrayList<string>(); 
407
408                         this.findxincludes(this.tree, xinc);
409                         print("got %d xincludes\n", xinc.size);
410                         var prefix_data = "";
411                         if (xinc.size > 0 ) {
412                                 for(var i = 0; i < xinc.size; i++) {
413                                         print("check xinclude:  %s\n", xinc.get(i));
414                                         var sf = this.project.getByName(xinc.get(i));
415                                         if (sf == null) {
416                                                 print("Failed to find file by name?\n");
417                                                 continue;
418                                         }
419
420                                         sf.loadItems();
421                                         var xinc_str = sf.toSource();
422                                         
423                                         //string xinc_str;
424                                         //FileUtils.get_contents(js, out xinc_str);
425                                         prefix_data += "\n" + xinc_str + "\n";
426                                         
427                                 }
428
429                         }
430
431                         
432                         
433                         //print(JSON.stringify(this.items, null,4));
434                                    
435                         if (top == null) {
436                                 print ("guessname returned false");
437                                 return "";
438                         }
439
440
441                         if (top.contains("Dialog")) {
442                                 return prefix_data + this.toSourceDialog(true);
443                         }
444
445                         if (top.contains("Modal")) {
446                                 return prefix_data + this.toSourceModal(true);
447                         }
448
449                         return prefix_data + this.toSourceLayout(true);
450                                 
451                                 
452             
453         }
454         
455         /**
456          * This needs to use some options on the project
457          * to determine how the file is output..
458          * 
459          * At present we are hard coding it..
460          * 
461          * 
462          */
463         public override string toSource()
464         {
465             // dump the file tree back out to a string.
466             
467             // we have 2 types = dialogs and components
468             // 
469             
470             
471                         if (this.tree == null) {
472                                 return "";
473                         }
474             var top = this.tree.fqn();
475             if (top == null) {
476                 return "";
477             }
478             // get the translatable strings..
479             
480             
481             
482             if (top.contains("Dialog")) {
483                 return this.toSourceDialog(false);
484             }
485             
486             if (top.contains("Modal")) {
487                 return this.toSourceModal(false);
488             }
489             return this.toSourceLayout(false);
490             
491             /*
492             eventually support 'classes??'
493              return this.toSourceStdClass();
494             */
495               
496         }
497        
498         public string outputHeader()
499         {
500                 string[] s = {
501                         "//<script type=\"text/javascript\">",
502                         "",
503                         "// Auto generated file - created by app.Builder.js- do not edit directly (at present!)",
504                         ""
505                    
506                 };  
507                 var ret=  string.joinv("\n",s);
508                 var bits = this.name.split(".");
509                 if (bits.length > 1) {
510                         ret += "\nRoo.namespace(\'" + 
511                                 this.name.substring(0, this.name.length - (bits[bits.length-1].length + 1)) +
512                                 "');\n";
513                                 
514                 }
515                 /// genericlly used..
516                   
517                 return ret;
518             
519        
520         }
521         // a standard dialog module.
522         // fixme - this could be alot neater..
523         public string toSourceDialog(bool isPreview) 
524         {
525             
526             //var items = JSON.parse(JSON.stringify(this.items[0]));
527             
528             
529             var o = this.mungeToString("    ");   
530
531  
532             string[] adda = { " = {",
533                 "",
534                 this.transStringsToJs() ,
535                 "",
536                 " dialog : false,",
537                 " callback:  false,",
538                 "",   
539                 " show : function(data, cb)",
540                 " {",
541                 "  if (!this.dialog) {",
542                 "   this.create();",
543                 "  }",
544                 "",
545                 "  this.callback = cb;",
546                 "  this.data = data;",
547                 "  this.dialog.show(this.data._el);",
548                 "  if (this.form) {",
549                 "   this.form.reset();",
550                 "   this.form.setValues(data);",
551                 "   this.form.fireEvent('actioncomplete', this.form,  { type: 'setdata', data: data });",
552                 "  }",
553                 "",   
554                 " },",
555                 "",
556                 " create : function()",
557                 " {",
558                 "   var _this = this;",
559                 "   this.dialog = Roo.factory(" 
560             };
561             string[] addb = {  
562                    ");",
563                 " }",
564                 "};",
565                 ""
566             };
567             return  this.outputHeader() + "\n" +
568                 this.name + string.joinv("\n", adda) + o + string.joinv("\n", addb);
569             
570              
571              
572              
573         }
574         
575         public string toSourceModal(bool isPreview) 
576         {
577             
578             
579             //var items = JSON.parse(JSON.stringify(this.items[0]));
580             var o = this.mungeToString("    ");   
581             
582             string[] adda = { " = {",
583                 "",
584                 this.transStringsToJs() ,
585                 "",
586                 " dialog : false,",
587                 " callback:  false,",
588                 "",   
589                 " show : function(data, cb)",
590                 " {",
591                 "  if (!this.dialog) {",
592                 "   this.create();",
593                 "  }",
594                 "",
595                 "  this.callback = cb;",
596                 "  this.data = data;",
597                 "  this.dialog.show(this.data._el);",
598                 "  if (this.form) {",
599                 "   this.form.reset();",
600                 "   this.form.setValues(data);",
601                 "   this.form.fireEvent('actioncomplete', this.form,  { type: 'setdata', data: data });",
602                 "  }",
603                 "",   
604                 " },",
605                 "",
606                 " create : function()",
607                 " {",
608                 "  var _this = this;",
609                 "  this.dialog = Roo.factory("
610             };
611             string[] addb =  {
612                 "  );",
613                 " }",
614                 "};",
615                 ""
616             };
617             return this.outputHeader() + "\n" + 
618                 this.name + string.joinv("\n", adda) + o + string.joinv("\n", addb);
619              
620              
621              
622         }
623         
624         
625         public string   pathToPart()
626         {
627             var dir = Path.get_basename(Path.get_dirname(this.path));
628             var ar = dir.split(".");
629             var modname = ar[ar.length-1];
630             
631             // now we have the 'module name'..
632             var fbits = Path.get_basename(this.path).split(".");
633             
634              
635             var npart = fbits[fbits.length - 2]; // this should be 'AdminProjectManager' for example...
636             if (modname.length < npart.length && npart.substring(0, modname.length) == modname) {
637                 npart = npart.substring(modname.length);
638             }
639             return "[" + this.tree.quoteString(modname) + ", " + this.tree.quoteString(npart) + " ]";
640             //return ret;
641             
642             
643             
644             
645         }
646         
647         // a layout compoent 
648         public string toSourceLayout(bool isPreview) 
649         {
650           
651             
652                 if (isPreview) {
653                         //       topItem.region = 'center';
654                         //    topItem.background = false;
655                 }
656             
657                 var o = this.mungeToString("   ");   
658                 var reg = new Regex("[^A-Za-z.]+");
659             
660                 string modkey = this.modOrder + "-" + reg.replace(this.name, this.name.length, 0 , "-");
661             
662                 string  parent =   (this.parent.length > 0 ?  "'" + this.parent + "'" :  "false");
663
664                 
665                 
666                 if (isPreview) {
667                         // set to false to ensure this is the top level..
668                         parent = "false";
669                         var topnode = this.tree.fqn();
670                         print("topnode = %s\n", topnode);
671                         if (GLib.Regex.match_simple("^Roo\\.bootstrap\\.",topnode) &&
672                             topnode != "Roo.bootstrap.Body"
673                         ) {
674                                 parent = "\"#bootstrap-body\"";
675                         }
676                           
677                 }
678             
679           
680                 return 
681                         this.outputHeader() + "\n" +
682                         
683                         this.name  +  " = new Roo.XComponent({\n" +
684                         "\n" + 
685                         this.transStringsToJs()  +    "\n" +
686                 "\n" +
687                         "  part     :  "+ this.pathToPart() + ",\n" +
688                                 /// critical used by builder to associate modules/parts/persm
689                         "  order    : '" +modkey+"',\n" +
690                         "  region   : '" + this.region   +"',\n" +
691                         "  parent   : "+ parent + ",\n" +
692                         "  name     : " + this.tree.quoteString(this.title.length > 0 ? this.title : "unnamed module") + ",\n" +
693                         "  disabled : " + (this.disabled ? "true" : "false") +", \n" +
694                         "  permname : '" + (this.permname.length > 0 ? this.permname : "") +"', \n" +
695                             
696                        // "    tree : function() { return this._tree(); },\n" +   //BC
697                         "  _tree : function()\n" +
698                         "  {\n" +
699                         "   var _this = this;\n" + // bc
700                         "   var MODULE = this;\n" + /// this looks like a better name.
701                         "   return " + o + ";" +
702                         "  }\n" +
703                         "});\n";
704                          
705               
706         }
707             
708         public new string? guessName (Node? ar) // turns the object into full name.
709         {
710              // eg. xns: Roo, xtype: XXX -> Roo.xxx
711             if (ar == null) {
712                 return null;
713             }
714             
715             string[] ret = {} ;
716             ret += (ar.get("|xns").length < 1 ? "Roo": ar.get("|xns"));
717              
718             
719             if ( ar.get("xtype").length < 1) {
720                 return null;
721             }
722                     
723             var xtype = ar.get("xtype");
724
725             if (xtype[0] == '*') { // prefixes????
726                 xtype  = xtype.substring(1);
727             }
728             if (! Regex.match_simple("^Roo", xtype)) {
729                 
730                 // already starts with roo...
731                 ret = {};
732             }
733             ret += xtype;
734             var str =  string.joinv(".", ret);
735             
736             return str;
737            // 
738             //Palete.Palete.factory("Roo").guessName(str);
739             
740                             
741                                  
742         }
743         
744         string getHelpUrl(string cls)
745         {
746             return "http://www.roojs.com/roojs1/docs/symbols/" + cls + ".html";
747         }
748                  
749      
750     }
751 }