]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/reports/oils_rpt_builder.js
has_many/might_have support in the from clause
[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         oilsInitReports();
6         oilsReportBuilderReset();
7         DOM.oils_rpt_table.onclick = 
8                 function(){hideMe(DOM.oils_rpt_column_editor)};
9         //oilsRptBuildCalendars();
10         oilsDrawRptTree(
11                 function() { 
12                         hideMe(DOM.oils_rpt_tree_loading); 
13                         unHideMe(DOM.oils_rpt_table); 
14                 }
15         );
16 }
17
18 function oilsReportBuilderReset() {
19         var n = (oilsRpt) ? oilsRpt.name : "";
20         oilsRpt = new oilsReport();
21         oilsRpt.name = n;
22         oilsRptDisplaySelector  = DOM.oils_rpt_display_selector;
23         oilsRptFilterSelector   = DOM.oils_rpt_filter_selector;
24         removeChildren(oilsRptDisplaySelector);
25         removeChildren(oilsRptFilterSelector);
26         oilsRptDebug();
27         oilsRptResetParams();
28 }
29
30
31 /*
32 function oilsRptBuildCalendars() {
33         Calendar.setup({
34                 inputField  : "oils_rpt_filter_tform_timestamp_input", // id of the input field
35                 ifFormat    : "%Y-%m-%d", // format of the input field
36                 button      : "oils_rpt_filter_tform_timestamp_cal",  // trigger for the calendar (button ID)
37                 align       : "Tl", // alignment (defaults to "Bl")
38                 singleClick : true
39         });
40         Calendar.setup({
41                 inputField  : "oils_rpt_filter_tform_timestamp_input_2", // id of the input field
42                 ifFormat    : "%Y-%m-%d", // format of the input field
43                 button      : "oils_rpt_filter_tform_timestamp_cal2",  // trigger for the calendar (button ID)
44                 align       : "Tl", // alignment (defaults to "Bl")
45                 singleClick : true
46         });
47 }
48 */
49
50
51 /* creates a label "path" based on the column path */
52 function oilsRptMakeLabel(path) {
53         var parts = path.split(/-/);
54         var str = '';
55         for( var i = 0; i < parts.length; i++ ) {
56                 if(i%2 == 0) {
57                         if( i == 0 )
58                                 str += oilsIDL[parts[i]].label;
59                 } else {
60                         var f = oilsRptFindField(oilsIDL[parts[i-1]], parts[i]);
61                         str += ":"+f.label;
62                 }
63         }
64         return str;
65 }
66
67
68 /* adds an item to the display window */
69 function oilsAddRptDisplayItem(path, name, tform, params) {
70         if( ! oilsAddSelectorItem(oilsRptDisplaySelector, path, name) ) 
71                 return;
72
73         /* add the selected columns to the report output */
74         name = (name) ? name : oilsRptPathCol(path);
75         var param = oilsRptNextParam();
76
77         /* add this item to the select blob */
78         var sel = {
79                 relation:oilsRptPathRel(path), 
80                 alias:param
81         };
82
83         if( tform ) {
84                 sel.column = {};
85                 if( params ) {
86                         params.unshift(oilsRptPathCol(path));
87                         sel.column[tform] = params;
88                 } else {
89                         sel.column[tform] = oilsRptPathCol(path); 
90                 }
91         } else { sel.column = oilsRptPathCol(path); }
92
93         oilsRpt.def.select.push(sel);
94
95         mergeObjects( oilsRpt.def.from, oilsRptBuildFromClause(path));
96         oilsRpt.params[param] = name;
97         oilsRptDebug();
98 }
99
100 /* takes a column path and builds a from-clause object for the path */
101 function oilsRptBuildFromClause(path) {
102
103         /* the path is the full path (relation) from the source 
104                 object to the column in question (e.g. au-home_ou-aou-name)*/
105         var parts = path.split(/-/);
106
107         /* the final from clause */
108         var obj = {}; 
109
110         /* reference to the current position in the from clause */
111         var tobj = obj; 
112
113         var newpath = "";
114
115         /* walk the path, fleshing the from clause as we go */
116         for( var i = 0; i < parts.length; i += 2 ) {
117
118                 var cls = parts[i]; /* class name (id) */
119                 var col = parts[i+1]; /* column name */
120
121                 /* a "node" is a class description from the IDL, it 
122                         contains relevant info, plus a list of "fields",
123                         or column objects */
124                 var node = oilsIDL[cls];
125                 var pkey = oilsRptFindField(node.pkey);
126
127                 /* a "field" is a parsed version of a column from the IDL,
128                         contains datatype, column name, etc. */
129                 var field = oilsRptFindField(node, col);
130
131                 /* re-construct the path as we go so 
132                         we know what all we've seen thus far */
133                 newpath = (newpath) ? newpath + '-'+ cls : cls;
134
135                 /* extract relevant info */
136                 tobj.table = node.table;
137                 tobj.alias = newpath;
138                 _debug('field type is ' + field.type);
139                 if( i == (parts.length - 2) ) break;
140
141                 /* we still have columns left in the path, keep adding join's */
142                 var path_col = col;
143                 if(field.reltype != 'has_a')
144                         col = pkey.name + '-' + col;
145
146                 tobj.join = {};
147                 tobj = tobj.join;
148
149                 tobj[col] = {};
150                 tobj = tobj[col];
151                 if( field.type == 'link' )
152                         tobj.key = field.key;
153
154                 newpath = newpath + '-'+ path_col;
155         }
156
157         _debug("built 'from' clause: path="+path+"\n"+formatJSON(js2JSON(obj)));
158         return obj;
159 }
160
161
162 /* removes a specific item from the display window */
163 function oilsDelDisplayItem(val) {
164         oilsDelSelectorItem(oilsRptDisplaySelector, val);
165 }
166
167 /* removes selected items from the display window */
168 function oilsDelSelectedDisplayItems() {
169         var list = oilsDelSelectedItems(oilsRptDisplaySelector);
170
171         /* remove the de-selected columns from the report output */
172         oilsRpt.def.select = grep( oilsRpt.def.select, 
173                 function(i) {
174                         for( var j = 0; j < list.length; j++ ) {
175                                 var d = list[j];
176                                 var col = i.column;
177
178                                 /* if this columsn has a transform, 
179                                         it will be an object { tform => column } */
180                                 if( typeof col != 'string' ) 
181                                         for( var c in col ) col = col[c];
182
183                                 /* if this transform requires params, the column 
184                                         will be the first item in the param set array */
185                                 if( typeof col != 'string' ) col = col[0];
186
187                                 if( oilsRptPathRel(d) == i.relation && oilsRptPathCol(d) == col ) {
188                                         var param = (i.alias) ? i.alias.match(/::PARAM\d*/) : null;
189                                         if( param ) delete oilsRpt.params[param];
190                                         return false;
191                                 }
192                         }
193                         return true;
194                 }
195         );
196
197         if(!oilsRpt.def.select) {
198                 oilsRpt.def.select = [];
199                 oilsReportBuilderReset();
200
201         } else {
202                 for( var j = 0; j < list.length; j++ ) 
203                         /* if there are no items left in the "select" clause for the given 
204                                 relation, trim this relation from the "from" clause */
205                         if(!grep(oilsRpt.def.select,
206                                         function(i){ return (i.relation == oilsRptPathRel(list[j])); })) 
207                                 oilsRptPruneFromClause(oilsRptPathRel(list[j]));
208         }
209
210         oilsRptDebug();
211 }
212
213 /* for each item in the path list, remove the associated data
214         from the "from" clause */
215
216 function oilsRptPruneFromClause(relation, node) {
217         _debug("removing relation from 'from' clause " + relation);
218         if(!node) node = oilsRpt.def.from.join;
219         for( var i in node ) {
220                 if( node[i].alias == relation ) {
221                         if( node[i].join ) {
222                                 /* if we have subtrees, don't delete our tree node */
223                                 return false;
224                         } else {
225                                 delete node[i];
226                                 return true;
227                         } 
228                 } else {
229                         if( node[i].join ) {
230                                 if( oilsRptPruneFromClause(relation, node[i].join ) ) {
231                                         if(oilsRptObjectKeys(node[i].join).length == 0) {
232                                                 delete node[i].join;
233                                                 /* if there are no items in the select clause with a relation matching
234                                                         this nodes alias, we can safely remove this node from the tree */
235                                                 if(!grep(oilsRpt.def.select,function(r){return (r.relation==node[i].alias)}))
236                                                         delete node[i];
237                                                 return true;
238                                         }
239                                 }
240                         }
241                 }
242         }
243         return false;
244 }
245
246 /* adds an item to the display window */
247 function oilsAddRptFilterItem(val) {
248         oilsAddSelectorItem(oilsRptFilterSelector, val);
249 }
250
251 /* removes a specific item from the display window */
252 function oilsDelFilterItem(val) {
253         oilsDelSelectorItem(oilsRptFilterSelector, val);
254 }
255
256 /* removes selected items from the display window */
257 function oilsDelSelectedFilterItems() {
258         oilsDelSelectedItems(oilsRptFilterSelector);
259 }
260
261
262 /* adds an item to the display window */
263 function oilsAddSelectorItem(sel, val, name) {
264         name = (name) ? name : oilsRptMakeLabel(val);
265         _debug("adding selector item "+name+' = ' +val);
266         for( var i = 0; i < sel.options.length; i++ ) {
267                 var opt = sel.options[i];
268                 if( opt.value == val ) return false;
269         }
270         insertSelectorVal( sel, -1, name, val );
271         return true;
272 }
273
274
275 /* removes a specific item from the display window */
276 function oilsDelSelectorItem(sel, val) {
277         _debug("deleting selector item "+val);
278         var opts = sel.options;
279         for( var i = 0; i < opts.length; i++ ) {
280                 var opt = opts[i];
281                 if( opt.value == val )  {
282                         if( i == opts.length - 1 ) 
283                                 opts[i] = null;
284                         else opts[i] = opts[i+1];
285                         return;
286                 }
287         }
288 }
289
290 /* removes selected items from the display window */
291 function oilsDelSelectedItems(sel) {
292         var list = getSelectedList(sel);
293         for( var i = 0; i < list.length; i++ ) 
294                 oilsDelSelectorItem(sel, list[i]);
295         return list;
296 }
297
298
299 /* hides the different field editor tabs */
300 function oilsRptHideEditorDivs() {
301         hideMe(DOM.oils_rpt_tform_div);
302         hideMe(DOM.oils_rpt_filter_div);
303         hideMe(DOM.oils_rpt_agg_filter_div);
304 }
305
306
307 /**
308   This draws the 3-tabbed window containing the transform,
309   filter, and aggregate filter picker window
310   */
311 function oilsRptDrawDataWindow(path) {
312         var col = oilsRptPathCol(path);
313         var cls = oilsRptPathClass(path);
314         var field = oilsRptFindField(oilsIDL[cls], col);
315
316         appendClear(DOM.oils_rpt_editor_window_label, text(oilsRptMakeLabel(path)));
317         appendClear(DOM.oils_rpt_editor_window_datatype, text(field.datatype));
318
319         _debug("setting update data window for column "+col+' on class '+cls);
320
321         var div = DOM.oils_rpt_column_editor;
322         /* set a preliminary top position so the page won't bounce around */
323         div.setAttribute('style','top:'+oilsMouseX+'px');
324
325         /* unhide the div so we can determine the dimensions */
326         unHideMe(div);
327
328         /* don't let them see the floating div until the position is fully determined */
329         div.style.visibility='hidden'; 
330
331         oilsRptDrawTransformWindow(path, col, cls, field);
332         oilsRptDrawFilterWindow(path, col, cls, field);
333
334         //oilsRptSetFilters(field.datatype);
335
336         //oilsRptDoFilterWidgets();
337
338         //DOM.oils_rpt_filter_tform_selector.onchange = oilsRptDoFilterWidgets;
339
340         buildFloatingDiv(div, 600);
341
342         /* now let them see it */
343         div.style.visibility='visible';
344
345         oilsRptSetDataWindowActions(div);
346 }
347
348
349 function oilsRptSetDataWindowActions(div) {
350         /* give the tab links behavior */
351         DOM.oils_rpt_tform_tab.onclick = 
352                 function(){oilsRptHideEditorDivs();unHideMe(DOM.oils_rpt_tform_div)};
353         DOM.oils_rpt_filter_tab.onclick = 
354                 function(){oilsRptHideEditorDivs();unHideMe(DOM.oils_rpt_filter_div)};
355         DOM.oils_rpt_agg_filter_tab.onclick = 
356                 function(){oilsRptHideEditorDivs();unHideMe(DOM.oils_rpt_agg_filter_div)};
357
358         DOM.oils_rpt_tform_tab.onclick();
359         DOM.oils_rpt_column_editor_close_button.onclick = function(){hideMe(div);};
360 }
361
362
363 function oilsRptDrawFilterWindow(path, col, cls, field) {
364         oilsRptCurrentFilterTform = new oilsRptTFormManager(DOM.oils_rpt_filter_tform_table);
365         oilsRptCurrentFilterTform.build(field.datatype, false, true);
366         oilsRptCurrentFilterOpManager = new oilsRptOpManager(DOM.oils_rpt_filter_op_table);
367 }
368
369
370 /* draws the transform window */
371 function oilsRptDrawTransformWindow(path, col, cls, field) {
372         DOM.oils_rpt_tform_label_input.value = oilsRptMakeLabel(path);
373         var dtype = field.datatype;
374
375         DOM.oils_rpt_tform_submit.onclick = 
376                 function(){ 
377                         /*
378                         var tform = oilsRptGetTform(dtype);
379                         _debug('found tform: ' + js2JSON(tform));
380                         var params = getRptTformParams(dtype, tform);
381                         _debug('found tform params: ' + js2JSON(params));
382                         tform = (tform == 'raw') ? null : tform;
383                         */
384
385                         var tform = oilsRptCurrentTform.getCurrentTForm();
386                         oilsAddRptDisplayItem(path, DOM.oils_rpt_tform_label_input.value, tform.value, tform.params ) 
387                 };
388
389
390         DOM.oils_rpt_tform_label_input.focus();
391         DOM.oils_rpt_tform_label_input.select();
392
393         oilsRptCurrentTform = new oilsRptTFormManager(DOM.oils_rpt_tform_table);
394         oilsRptCurrentTform.build(dtype, true, true);
395
396         /*
397         oilsRptHideTformFields();
398         oilsRptUnHideTformFields(dtype);
399         */
400
401         _debug("Building transiform window for datatype "+dtype);
402
403         /*
404         unHideMe($('oils_rpt_tform_'+dtype+'_div'));
405         $('oils_rpt_tform_all_raw').checked = true;
406         */
407 }
408
409 /*
410 function oilsRptHideTformFields() {
411         var rows = DOM.oils_rpt_tform_tbody.childNodes;
412         for( var i = 0; i < rows.length; i++ )
413                 if( rows[i] && rows[i].nodeType == 1 )
414                         hideMe(rows[i]);
415 }
416
417 function oilsRptUnHideTformFields(dtype) {
418         var rows = DOM.oils_rpt_tform_tbody.childNodes;
419         for( var i = 0; i < rows.length; i++ ) {
420                 var row = rows[i]
421                 if( row && row.nodeType == 1 && 
422                         (row.getAttribute('datatype')=='all' 
423                                 || row.getAttribute('datatype') == dtype)) {
424                         unHideMe(row);
425                 }
426         }
427 }
428
429
430 function oilsRptGetTform(datatype) {
431         for( var i in oilsRptTransforms[datatype] ) 
432                 if( $('oils_rpt_tform_'+datatype+'_'+oilsRptTransforms[datatype][i]).checked )
433                         return oilsRptTransforms[datatype][i];
434         for( var i in oilsRptTransforms.all ) 
435                 if( $('oils_rpt_tform_all_'+oilsRptTransforms.all[i]).checked )
436                         return oilsRptTransforms.all[i];
437         return null;
438 }
439 */
440
441
442 /*
443 function getRptTformParams(type, tform) {
444         switch(type) {
445                 case 'string' :
446                         switch(tform) {
447                                 case 'substring' :
448                                         return [
449                                                 DOM.oils_rpt_tform_string_substring_offset.value, 
450                                                 DOM.oils_rpt_tform_string_substring_length.value];
451                         }
452         }
453 }
454 */
455
456
457 /* given a transform selector, this displays the appropriate 
458         transforms for the given datatype.
459         if aggregate is true, is displays the aggregate transforms */
460 /*
461 function oilsRptSetTransforms(sel, dtype, show_agg, show_noagg) {
462         for( var i = 0; i < sel.options.length; i++ ) {
463                 var opt = sel.options[i];
464                 var t = opt.getAttribute('datatype');
465                 if( t && t != dtype ){
466                         hideMe(opt);
467                 } else {
468                         var ag = opt.getAttribute('aggregate');
469                         if( ag && show_agg )
470                                 unHideMe(opt);
471                         else if( ag && ! show_agg )
472                                 hideMe(opt)
473                         else if( !ag && show_noagg )
474                                 unHideMe(opt);
475                         else
476                                 hideMe(opt);
477                 }
478         }
479 }
480 */
481
482
483 /* displays the correct filter-transforms for the given datatype */
484 /*
485 function oilsRptSetFilters(dtype) {
486
487         DOM.oils_rpt_filter_submit.onclick = function() {
488                 var data = oilsRptDoFilterWidgets();
489                 alert(js2JSON(data));
490         }
491
492         var sel = DOM.oils_rpt_filter_tform_selector;
493         for( var i = 0; i < sel.options.length; i++ ) {
494                 var opt = sel.options[i];
495                 _debug(opt.getAttribute('op'));
496                 var t = opt.getAttribute('datatype');
497                 if( t && t != dtype ) hideMe(opt);
498                 else unHideMe(opt);
499         }
500 }
501 */
502
503 /* hides all of the filter widgets */
504 function oilsRptHideFilterWidgets(node) {
505         if(!node)
506                 node = DOM.oils_rpt_filter_tform_widget_td;
507         if( node.nodeType != 1 ) return;
508         if( node.getAttribute('widget') ) {
509                 hideMe(node);
510         } else {
511                 var cs = node.childNodes;
512                 for( var i = 0; cs && i < cs.length; i++ )
513                         oilsRptHideFilterWidgets(cs[i]);
514         }
515 }
516
517 /* what does this need to do? */
518 function oilsRptSetFilterOpActions() {
519 }
520
521
522
523 /* hides/unhides the appropriate widgets and returns the parameter
524         array appropriate for the selected widget */
525 function oilsRptDoFilterWidgets() {
526         filter = getSelectorVal(DOM.oils_rpt_filter_tform_selector);
527         oilsRptHideFilterWidgets();
528         var op = null;
529         var tform = null;
530         var params = null;
531
532         switch(filter) {
533                 
534                 /* generic transforms */
535                 case 'equals':
536                         if(!op) op = 'equals';
537                 case 'like':
538                         if(!op) op = 'like';
539                 case 'ilike':
540                         if(!op) op = 'ilike';
541                 case 'gt':
542                         if(!op) op = '>';
543                 case 'gte':
544                         if(!op) op = '>=';
545                 case 'lt':
546                         if(!op) op = '<';
547                 case 'lte':
548                         if(!op) op = '<=';
549                 case 'in':
550                         if(!op) op = 'in';
551                 case 'not_in':
552                         if(!op) op = 'not in';
553                 case 'between':
554                         if(!op) op = 'between';
555                 case 'not_between':
556                         if(!op) op = 'not between';
557                         unHideMe(DOM.oils_rpt_filter_tform_input);      
558                         params = [DOM.oils_rpt_filter_tform_input.value];
559                         break;
560
561                 /* timestamp transforms */
562                 case 'date_between':
563                         if(!op) op = 'between';
564                 case 'date_not_between':
565                         if(!op) op = 'not between';
566                         tform = 'date';
567                         var d = new Date();
568                         unHideMe(DOM.oils_rpt_filter_tform_date_1);
569                         unHideMe(DOM.oils_rpt_filter_tform_date_2);
570                         unHideMe(DOM.oils_rpt_filter_tform_date_hint);
571                         DOM.oils_rpt_filter_tform_date_1.value = mkYearMonDay();
572                         DOM.oils_rpt_filter_tform_date_2.value = mkYearMonDay();
573                         params = [
574                                 DOM.oils_rpt_filter_tform_date_1.value,
575                                 DOM.oils_rpt_filter_tform_date_2.value
576                         ];
577                         break;
578
579                 case 'dow_between':
580                         op = 'between';
581                         if(!tform) tform = 'dow';
582                 case 'dow_not_between':
583                         if(!op) op = 'not between';
584                         if(!tform) tform = 'dow';
585                         break;
586
587                 case 'dom_between':
588                         op = 'between';
589                         if(!tform) tform = 'dom';
590                 case 'dom_not_between':
591                         if(!op) op = 'not between';
592                         if(!tform) tform = 'dom';
593                         break;
594
595                 case 'month_between':
596                         op = 'between';
597                         if(!tform) tform = 'moy';
598                 case 'month_not_between':
599                         if(!op) op = 'not between';
600                         if(!tform) tform = 'moy';
601                         break;
602
603                 case 'quarter_between':
604                         op = 'between';
605                         if(!tform) tform = 'qoy';
606                 case 'quarter_not_between':
607                         if(!op) op = 'not between';
608                         if(!tform) tform = 'qoy';
609                         break;
610
611                 case 'year_between':
612                         if(!op) op = 'between';
613                         if(!tform) tform = 'year_trunc';
614                 case 'year_not_between':
615                         if(!op) op = 'not between';
616                         if(!tform) tform = 'year_trunc';
617                         break;
618
619                 case 'age_between':
620                         if(!op) op = 'between';
621                         if(!tform) tform = 'age';
622                 case 'age_not_between':
623                         if(!op) op = 'not between';
624                         if(!tform) tform = 'age';
625                         break;
626
627                 /* string transforms */
628                 case 'substring':
629                         if(!tform) tform = 'substring';
630                         break;
631
632                 case 'lower':
633                         if(!op) op = '';
634                         if(!tform) tform = 'dow';
635
636                 case 'upper':
637                         if(!op) op = '';
638                         if(!tform) tform = 'dow';
639
640                 /* numeric transforms */
641                 case 'round':
642                         if(!op) op = '';
643                         if(!tform) tform = 'dow';
644
645                 case 'int':
646                         if(!op) op = '';
647                         if(!tform) tform = 'dow';
648         }
649
650         return { op : op, params : params, tform : tform };
651 }
652
653
654
655