]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/reports/oils_rpt_builder.js
more refactoring of the template builder so where and having clauses
[Evergreen.git] / Open-ILS / web / reports / oils_rpt_builder.js
1 /** initializes reports, some basid display settings, 
2   * grabs and builds the IDL tree
3   */
4 function oilsInitReportBuilder() {
5         if(!oilsInitReports()) return false;
6         oilsReportBuilderReset();
7         DOM.oils_rpt_table.onclick = 
8                 function(){hideMe(DOM.oils_rpt_column_editor)};
9         oilsDrawRptTree(
10                 function() { 
11                         hideMe(DOM.oils_rpt_tree_loading); 
12                         unHideMe(DOM.oils_rpt_table); 
13                 }
14         );
15
16         DOM.oils_rpt_builder_save_template.onclick = oilsReportBuilderSave;
17 }
18
19 function oilsReportBuilderReset() {
20         var n = (oilsRpt) ? oilsRpt.name : "";
21         oilsRpt = new oilsReport();
22         oilsRpt.name = n;
23         oilsRptDisplaySelector  = DOM.oils_rpt_display_selector;
24         oilsRptFilterSelector   = DOM.oils_rpt_filter_selector;
25         oilsRptHavingSelector= DOM.oils_rpt_agg_filter_selector;
26         removeChildren(oilsRptDisplaySelector);
27         removeChildren(oilsRptFilterSelector);
28         removeChildren(oilsRptHavingSelector);
29         oilsRptResetParams();
30 }
31
32 function oilsReportBuilderSave() {
33
34         var tmpl = new rt();
35         tmpl.name(DOM.oils_rpt_builder_new_name.value);
36         tmpl.description(DOM.oils_rpt_builder_new_desc.value);
37         tmpl.owner(USER.id());
38         tmpl.folder(new CGI().param('folder'));
39         tmpl.data(js2JSON(oilsRpt.def));
40
41         if(!confirm('Name : '+tmpl.name() + '\nDescription: ' + tmpl.description()+'\nSave Template?'))
42                 return;
43
44         debugFMObject(tmpl);
45         //return; /* XXX */
46
47
48         var req = new Request(OILS_RPT_CREATE_TEMPLATE, SESSION, tmpl);
49         req.callback(
50                 function(r) {
51                         var res = r.getResultObject();
52                         if( res != 0 ) {
53                                 oilsRptAlertSuccess();
54                                 _l('oils_rpt.xhtml');
55                         }
56                 }
57         );
58         
59         req.send();
60 }
61
62
63
64 /* adds an item to the display window */
65 function oilsAddRptDisplayItem(path, name, tform, params) {
66         if( ! oilsAddSelectorItem(oilsRptDisplaySelector, path, name) ) 
67                 return;
68
69         /* add the selected columns to the report output */
70         name = (name) ? name : oilsRptPathCol(path);
71         if( !tform ) tform = 'Bare';
72
73         var aggregate = oilsRptGetIsAgg(tform);
74
75         /* add this item to the select blob */
76         var sel = {
77                 relation: hex_md5(oilsRptPathRel(path)), 
78                 path : path,
79                 alias:    name,
80                 column:   { transform: tform, colname: oilsRptPathCol(path) }
81         };
82
83         if( params ) sel.column.params = params;
84
85         if(!oilsRptGetIsAgg(tform)) {
86                 var select = [];
87                 var added = false;
88                 for( var i = 0; i < oilsRpt.def.select.length; i++ ) {
89                         var item = oilsRpt.def.select[i];
90                         if( !added && oilsRptGetIsAgg( item.column.transform ) ) {
91                                 select.push(sel);
92                                 added = true;
93                         }
94                         select.push(item);
95                 }
96                 if(!added) select.push(sel);
97                 oilsRpt.def.select = select;
98         } else {
99                 oilsRpt.def.select.push(sel);
100         }
101
102
103         mergeObjects( oilsRpt.def.from, oilsRptBuildFromClause(path));
104         oilsRptDebug();
105 }
106
107 function oilsRptGetIsAgg(tform) {
108         return OILS_RPT_TRANSFORMS[tform].aggregate;
109         
110         /* DEPRECATED */
111         var sel = $n(DOM.oils_rpt_tform_table,'selector');
112         for( var i = 0; i < sel.options.length; i++ ) {
113                 var opt = sel.options[i];
114                 if( opt.getAttribute('value') == tform )
115                         return opt.getAttribute('aggregate');
116         }
117 }
118
119 /* takes a column path and builds a from-clause object for the path */
120 function oilsRptBuildFromClause(path) {
121
122         /* the path is the full path (relation) from the source 
123                 object to the column in question (e.g. au-home_ou-aou-name)*/
124         var parts = path.split(/-/);
125
126         /* the final from clause */
127         var obj = {}; 
128
129         /* reference to the current position in the from clause */
130         var tobj = obj; 
131
132         var newpath = "";
133
134         /* walk the path, fleshing the from clause as we go */
135         for( var i = 0; i < parts.length; i += 2 ) {
136
137                 var cls = parts[i]; /* class name (id) */
138                 var col = parts[i+1]; /* column name */
139
140                 /* a "node" is a class description from the IDL, it 
141                         contains relevant info, plus a list of "fields",
142                         or column objects */
143                 var node = oilsIDL[cls];
144                 var pkey = oilsRptFindField(node, node.pkey);
145
146                 /* a "field" is a parsed version of a column from the IDL,
147                         contains datatype, column name, etc. */
148                 var field = oilsRptFindField(node, col);
149
150                 /* re-construct the path as we go so 
151                         we know what all we've seen thus far */
152                 newpath = (newpath) ? newpath + '-'+ cls : cls;
153
154                 /* extract relevant info */
155                 tobj.table = node.table;
156                 tobj.path = newpath;
157                 tobj.alias = hex_md5(newpath);
158
159                 _debug('field type is ' + field.type);
160                 if( i == (parts.length - 2) ) break;
161
162                 /* we still have columns left in the path, keep adding join's */
163                 var path_col = col;
164                 if(field.reltype != 'has_a')
165                         col = pkey.name + '-' + col;
166
167                 tobj.join = {};
168                 tobj = tobj.join;
169
170                 tobj[col] = {};
171                 tobj = tobj[col];
172                 if( field.type == 'link' )
173                         tobj.key = field.key;
174
175                 newpath = newpath + '-'+ path_col;
176         }
177
178         _debug("built 'from' clause: path="+path+"\n"+formatJSON(js2JSON(obj)));
179         return obj;
180 }
181
182
183 /* removes a specific item from the display window */
184 function oilsDelDisplayItem(val) {
185         oilsDelSelectorItem(oilsRptDisplaySelector, val);
186 }
187
188 /* removes selected items from the display window */
189 function oilsDelSelectedDisplayItems() {
190         var list = oilsDelSelectedItems(oilsRptDisplaySelector);
191
192         _debug('deleting list: ' + list);
193
194         /* remove the de-selected columns from the report output */
195         oilsRpt.def.select = grep( oilsRpt.def.select, 
196                 function(i) {
197                         for( var j = 0; j < list.length; j++ ) {
198                                 var d = list[j]; /* path */
199                                 var col = i.column;
200
201                                 _debug('in delete, looking at list = '+d+' : col = ' + 
202                                         col.colname + ' : relation = ' + i.relation + ' : encoded = ' + hex_md5(oilsRptPathRel(d)) );
203
204                                 if( hex_md5(oilsRptPathRel(d)) == i.relation && oilsRptPathCol(d) == col.colname ) {
205                                         return false;
206                                 }
207                         }
208                         return true;
209                 }
210         );
211
212         if(!oilsRpt.def.select) oilsRpt.def.select = [];
213
214         oilsRptPruneFromList(list);
215
216         /*
217         for( var j = 0; j < list.length; j++ ) {
218                 debug('seeing if we can prune from clause with relation = ' + hex_md5(oilsRptPathRel(list[j])));
219                 if(     !grep(oilsRpt.def.select,
220                                 function(i){ return (i.relation == hex_md5(oilsRptPathRel(list[j]))); })
221                         && !grep(oilsRpt.def.where,
222                                 function(i){ return (i.relation == hex_md5(oilsRptPathRel(list[j]))); })
223                         && !grep(oilsRpt.def.having,
224                                 function(i){ return (i.relation == hex_md5(oilsRptPathRel(list[j]))); })
225                 ) {
226                         _debug('pruning from clause');
227                         oilsRptPruneFromClause(oilsRptPathRel(list[j]));
228                 }
229         }
230         */
231
232         oilsRptDebug();
233 }
234
235 function oilsRptPruneFromList(pathlist) {
236
237         for( var j = 0; j < pathlist.length; j++ ) {
238                 /* if there are no items left in the "select", "where", or "having" clauses 
239                         for the given relation, trim this relation from the "from" clause */
240                 var path = pathlist[j];
241                 var encrel = hex_md5(oilsRptPathRel(path));
242
243                 debug('seeing if we can prune from clause with relation = ' + encrel +' : path = ' + path);
244
245                 var func = function(i){ return (i.relation == hex_md5(oilsRptPathRel(path))); };
246
247                 if(     !grep(oilsRpt.def.select, func) && 
248                                 !grep(oilsRpt.def.where, func) && 
249                                 !grep(oilsRpt.def.having, func) ) {
250
251                         oilsRptPruneFromClause(oilsRptPathRel(pathlist[j]));
252                 }
253         }
254 }
255
256
257 /* for each item in the path list, remove the associated data
258         from the "from" clause */
259
260 function oilsRptPruneFromClause(relation, node) {
261         _debug("removing relation from 'from' clause: " + relation);
262         if(!node) node = oilsRpt.def.from.join;
263         if(!node) return false;
264
265         for( var i in node ) {
266                 _debug("from prune looking at node "+node[i].path);
267                 // first, descend into the tree, and prune leaves first
268                 if( node[i].join ) {
269                         oilsRptPruneFromClause(relation, node[i].join); 
270                         if(oilsRptObjectKeys(node[i].join).length == 0) 
271                                 delete node[i].join;
272                 }
273         }
274
275         if(!node.join) {
276
277                 var key = oilsRptObjectKeys(node)[0];
278                 var from_alias = node[key].alias;
279                 var func = function(n){ return (n.relation == from_alias)};
280
281                 _debug("pruning from clause with alias "+ from_alias);
282
283                 if(     !grep(oilsRpt.def.select, func) &&
284                                 !grep(oilsRpt.def.where, func) &&
285                                 !grep(oilsRpt.def.having, func) ) {
286
287                         // if we're at an unused empty leaf, remove it
288                         delete node[i];
289                         return true;
290                 }
291         }
292
293         return false;
294 }
295
296 function oilsRptMkFilterTags(path, tform, filter) {
297         var name = oilsRptMakeLabel(path);
298         if(tform) name += ' ('+tform+')';
299         name += ' "' + filter + '"';
300         var epath = path + ':'+filter+':';
301         if(tform) epath += tform;
302
303         return [ name, epath ];
304 }
305
306
307 function oilsAddRptFilterItem(path, tform, filter) {
308         _debug("Adding filter item for "+path+" tform="+tform+" filter="+filter);
309
310         var name = oilsRptMkFilterTags(path, tform, filter);
311         var epath = name[1];
312         name = name[0];
313
314         if( ! oilsAddSelectorItem(oilsRptFilterSelector, epath, name) )
315                 return;
316
317         var where = {
318                 relation: hex_md5(oilsRptPathRel(path)), 
319                 path : path,
320                 column:   { transform: tform, colname: oilsRptPathCol(path) },
321                 condition : {}
322         };
323         if( filter == 'is' || filter == 'is not' )
324                 where.condition[filter] = null;
325         else where.condition[filter] = oilsRptNextParam();
326
327         switch(tform) {
328                 case 'substring' : where.column.params = oilsRptNextParam();
329         }
330
331         oilsRpt.def.where.push(where);
332         mergeObjects( oilsRpt.def.from, oilsRptBuildFromClause(path));
333         oilsRptDebug();
334 }
335
336
337 function oilsAddRptHavingItem(path, tform, filter) {
338         _debug("Adding filter item for "+path+" tform="+tform+" filter="+filter);
339
340         var name = oilsRptMkFilterTags(path, tform, filter);
341         var epath = name[1];
342         name = name[0];
343
344         if( ! oilsAddSelectorItem(oilsRptHavingSelector, epath, name) )
345                 return;
346
347         var having = {
348                 relation: hex_md5(oilsRptPathRel(path)), 
349                 path : path,
350                 column:   { transform: tform, colname: oilsRptPathCol(path) },
351                 condition : {}
352         };
353         if( filter == 'is' || filter == 'is not' )
354                 having.condition[filter] = null;
355         else having.condition[filter] = oilsRptNextParam();
356
357         switch(tform) {
358                 case 'substring' : having.column.params = oilsRptNextParam();
359         }
360
361         oilsRpt.def.having.push(having);
362         mergeObjects( oilsRpt.def.from, oilsRptBuildFromClause(path));
363         oilsRptDebug();
364 }
365
366
367
368 function oilsDelSelectedFilterItems() {
369         _oilsDelSelectedFilterItems('where');
370 }
371 function oilsDelSelectedAggFilterItems() {
372         _oilsDelSelectedFilterItems('having');
373 }
374
375 function _oilsDelSelectedFilterItems(type) {
376
377         /* the values in this list are formed:  <path>:<operation>:<transform> */
378         var list = oilsDelSelectedItems(oilsRptFilterSelector);
379
380         for( var i = 0; i < list.length; i++ ) {
381                 var enc_path = list[i];
382                 var data = oilsRptParseFilterEncPath(enc_path);
383                 oilsRpt.def[type] = grep( 
384                         oilsRpt.def[type],
385                         function(f) { 
386                                 return oilsRptFilterDataMatches( 
387                                         f, data.path, data.operation, data.tform );
388                         }
389                 );
390         }
391
392         if(!oilsRpt.def[type]) oilsRpt.def[type] = [];
393         oilsRptPruneFromList(list);
394         oilsRptDebug();
395 }
396
397 function oilsRptParseFilterEncPath(item) {
398         return {
399                 path:           item.replace(/:.*/,''),
400                 operation:      item.replace(/.*:(.*):.*/,'$1'),
401                 tform:  item.replace(/.*?:.*?:(.*)/,'$1')
402         };
403 }
404
405
406 function oilsRptFilterDataMatches(filter, path, operation, tform) {
407         var rel = hex_md5(oilsRptPathRel(path));
408         var col = oilsRptPathCol(path);
409
410         if(     col == filter.column.colname &&
411                         rel == filter.relation &&       
412                         tform == filter.column.transform &&
413                         operation == oilsRptObjectKeys(filter)[0] ) return true;
414
415         return false;
416 }
417
418 /*
419 function oilsRptFilterGrep(flist, filter) {
420
421         for( var j = 0; j < flist.length; j++ ) {
422
423                 var fil = flist[j];
424                 var col = filter.column;
425                 var frel = hex_md5(oilsRptPathRel(fil.path));
426                 var fcol = oilsRptPathCol(fil.path);
427
428                 var op = oilsRptObjectKeys(filter.condition)[0];
429
430                 if(     frel == filter.relation && 
431                                 fcol == col.colname && 
432                                 fil.operation == op &&
433                                 fil.tform == col.transform ) {
434                                 return false;
435                 }
436         }
437         return true;
438 }
439 */
440
441 /* adds an item to the display window */
442 function oilsAddRptAggFilterItem(val) {
443         oilsAddSelectorItem(oilsRptHavingFilterSelector, val);
444 }
445
446 /* removes a specific item from the display window */
447 function oilsDelAggFilterItem(val) {
448         oilsDelSelectorItem(oilsRptHavingFilterSelector, val);
449 }
450
451
452
453 /*
454 function ___oilsDelSelectedAggFilterItems() {
455         var list = oilsDelSelectedItems(oilsRptHavingFilterSelector);
456         oilsRpt.def.having = grep( oilsRpt.def.having, 
457                 function(i) {
458                         for( var j = 0; j < list.length; j++ ) {
459                                 var d = list[j];
460                                 var col = i.column;
461
462                                 if( typeof col != 'string' ) 
463                                         for( var c in col ) col = col[c];
464
465                                 if( typeof col != 'string' ) col = col[0];
466
467                                 if( oilsRptPathRel(d) == i.relation && oilsRptPathCol(d) == col ) {
468                                 */
469                                 //      var param = (i.alias) ? i.alias.match(/::P\d*/) : null;
470                                         /*
471                                         if( param ) delete oilsRpt.params[param];
472                                         return false;
473                                 }
474                         }
475                         return true;
476                 }
477         );
478
479         if(!oilsRpt.def.having) oilsRpt.def.having = [];
480         oilsRptPruneFromList(list);
481         oilsRptDebug();
482 }
483 */
484
485
486 /* adds an item to the display window */
487 function oilsAddSelectorItem(sel, val, name) {
488         name = (name) ? name : oilsRptMakeLabel(val);
489         for( var i = 0; i < sel.options.length; i++ ) {
490                 var opt = sel.options[i];
491                 if( opt.value == val ) return false;
492         }
493         insertSelectorVal( sel, -1, name, val );
494         return true;
495 }
496
497
498 /* removes a specific item from the display window */
499 function oilsDelSelectorItem(sel, val) {
500         var opts = sel.options;
501         for( var i = 0; i < opts.length; i++ ) {
502                 var opt = opts[i];
503                 if( opt.value == val )  {
504                         if( i == opts.length - 1 ) 
505                                 opts[i] = null;
506                         else opts[i] = opts[i+1];
507                         return;
508                 }
509         }
510 }
511
512 /* removes selected items from the display window */
513 function oilsDelSelectedItems(sel) {
514         var list = getSelectedList(sel);
515         for( var i = 0; i < list.length; i++ ) 
516                 oilsDelSelectorItem(sel, list[i]);
517         return list;
518 }
519
520
521 /* hides the different field editor tabs */
522 function oilsRptHideEditorDivs() {
523         hideMe(DOM.oils_rpt_tform_div);
524         hideMe(DOM.oils_rpt_filter_div);
525         hideMe(DOM.oils_rpt_agg_filter_div);
526 }
527
528
529 /**
530   This draws the 3-tabbed window containing the transform,
531   filter, and aggregate filter picker window
532   */
533 function oilsRptDrawDataWindow(path) {
534         var col = oilsRptPathCol(path);
535         var cls = oilsRptPathClass(path);
536         var field = oilsRptFindField(oilsIDL[cls], col);
537
538         appendClear(DOM.oils_rpt_editor_window_label, text(oilsRptMakeLabel(path)));
539         appendClear(DOM.oils_rpt_editor_window_datatype, text(field.datatype));
540
541         _debug("setting update data window for column "+col+' on class '+cls);
542
543         var div = DOM.oils_rpt_column_editor;
544         /* set a preliminary top position so the page won't bounce around */
545         div.setAttribute('style','top:'+oilsMouseX+'px');
546
547         /* unhide the div so we can determine the dimensions */
548         unHideMe(div);
549
550         /* don't let them see the floating div until the position is fully determined */
551         div.style.visibility='hidden'; 
552
553         oilsRptDrawTransformWindow(path, col, cls, field);
554         oilsRptDrawFilterWindow(path, col, cls, field);
555         oilsRptDrawHavingWindow(path, col, cls, field);
556
557         buildFloatingDiv(div, 600);
558
559         /* now let them see it */
560         div.style.visibility='visible';
561         oilsRptSetDataWindowActions(div);
562 }
563
564
565 function oilsRptSetDataWindowActions(div) {
566         /* give the tab links behavior */
567         DOM.oils_rpt_tform_tab.onclick = 
568                 function(){oilsRptHideEditorDivs();unHideMe(DOM.oils_rpt_tform_div)};
569         DOM.oils_rpt_filter_tab.onclick = 
570                 function(){oilsRptHideEditorDivs();unHideMe(DOM.oils_rpt_filter_div)};
571         DOM.oils_rpt_agg_filter_tab.onclick = 
572                 function(){oilsRptHideEditorDivs();unHideMe(DOM.oils_rpt_agg_filter_div)};
573
574         DOM.oils_rpt_tform_tab.onclick();
575         DOM.oils_rpt_column_editor_close_button.onclick = function(){hideMe(div);};
576 }
577
578
579 function oilsRptDrawFilterWindow(path, col, cls, field) {
580
581         var tformPicker = new oilsRptTformPicker( {     
582                         node : DOM.oils_rpt_filter_tform_table,
583                         datatype : field.datatype,
584                         non_aggregate : true
585                 }
586         );
587
588         var filterPicker = new oilsRptFilterPicker({
589                         node : DOM.oils_rpt_filter_op_table,
590                         datatype : field.datatype
591                 }
592         );
593
594         DOM.oils_rpt_filter_submit.onclick = function() {
595                 oilsAddRptFilterItem(
596                         path, tformPicker.getSelected(), filterPicker.getSelected());
597         }
598 }
599
600
601 function oilsRptDrawHavingWindow(path, col, cls, field) {
602         var tformPicker = new oilsRptTformPicker( {     
603                         node : DOM.oils_rpt_agg_filter_tform_table,
604                         datatype : field.datatype,
605                         aggregate : true
606                 }
607         );
608
609         var filterPicker = new oilsRptFilterPicker({
610                         node : DOM.oils_rpt_agg_filter_op_table,
611                         datatype : field.datatype
612                 }
613         );
614
615         DOM.oils_rpt_agg_filter_submit.onclick = function() {
616                 oilsAddRptHavingItem(
617                         path, tformPicker.getSelected(), filterPicker.getSelected());
618         }
619 }
620
621 /* draws the transform window */
622 function oilsRptDrawTransformWindow(path, col, cls, field) {
623         DOM.oils_rpt_tform_label_input.value = oilsRptMakeLabel(path);
624         var dtype = field.datatype;
625
626         var tformPicker = new oilsRptTformPicker( {     
627                         node : DOM.oils_rpt_tform_table,
628                         datatype : field.datatype,
629                         non_aggregate : true,
630                         aggregate : true
631                 }
632         );
633
634         DOM.oils_rpt_tform_submit.onclick = 
635                 function(){ 
636                         oilsAddRptDisplayItem(path, 
637                                 DOM.oils_rpt_tform_label_input.value, tformPicker.getSelected() );
638                 };
639
640
641         DOM.oils_rpt_tform_label_input.focus();
642         DOM.oils_rpt_tform_label_input.select();
643
644         _debug("Building transform window for datatype "+dtype);
645 }