]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/reports/oils_rpt_builder.js
added move up/down buttons to display items for basic sorting
[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         //removeChildren(oilsRptOrderBySelector);
30         oilsRptResetParams();
31 }
32
33 function oilsReportBuilderSave() {
34
35         var tmpl = new rt();
36         tmpl.name(DOM.oils_rpt_builder_new_name.value);
37         tmpl.description(DOM.oils_rpt_builder_new_desc.value);
38         tmpl.owner(USER.id());
39         tmpl.folder(new CGI().param('folder'));
40         tmpl.data(js2JSON(oilsRpt.def));
41
42         if(!confirm('Name : '+tmpl.name() + '\nDescription: ' + tmpl.description()+'\nSave Template?'))
43                 return;
44
45         debugFMObject(tmpl);
46         //return; /* XXX */
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 && 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 function oilsMoveUpDisplayItems() {
183         var sel = oilsRptDisplaySelector;
184         var idx = sel.selectedIndex;
185         if( idx == 0 ) return;
186         var opt = sel.options[idx];
187         sel.options[idx] = null;
188         idx--;
189         var val = opt.getAttribute('value');
190         insertSelectorVal(sel, idx, opt.innerHTML, val);
191         sel.options[idx].selected = true;
192
193         var arr = oilsRpt.def.select;
194         for( var i = 0; i < arr.length; i++ ) {
195                 if( arr[i].path == val ) {
196                         var other = arr[i-1];
197                         arr[i-1] = arr[i];
198                         arr[i] = other;
199                         break;
200                 }
201         }
202         oilsRptDebug();
203 }
204
205 function oilsMoveDownDisplayItems() {
206         var sel = oilsRptDisplaySelector;
207         var idx = sel.selectedIndex;
208         if( idx == sel.options.length - 1 ) return;
209         var opt = sel.options[idx];
210         sel.options[idx] = null;
211         idx++;
212         var val = opt.getAttribute('value');
213         insertSelectorVal(sel, idx, opt.innerHTML, val);
214         sel.options[idx].selected = true;
215
216         var arr = oilsRpt.def.select;
217         for( var i = 0; i < arr.length; i++ ) {
218                 if( arr[i].path == val ) {
219                         var other = arr[i+1];
220                         arr[i+1] = arr[i];
221                         arr[i] = other;
222                         break;
223                 }
224         }
225         oilsRptDebug();
226 }
227
228
229 /* removes a specific item from the display window */
230 /*
231 function oilsDelDisplayItem(val) {
232         oilsDelSelectorItem(oilsRptDisplaySelector, val);
233 }
234 */
235
236 /* removes selected items from the display window */
237 function oilsDelSelectedDisplayItems() {
238         var list = oilsDelSelectedItems(oilsRptDisplaySelector);
239
240         _debug('deleting list: ' + list);
241
242         /* remove the de-selected columns from the report output */
243         oilsRpt.def.select = grep( oilsRpt.def.select, 
244                 function(i) {
245                         for( var j = 0; j < list.length; j++ ) {
246                                 var d = list[j]; /* path */
247                                 var col = i.column;
248
249                                 _debug('in delete, looking at list = '+d+' : col = ' + 
250                                         col.colname + ' : relation = ' + i.relation + ' : encoded = ' + hex_md5(oilsRptPathRel(d)) );
251
252                                 if( hex_md5(oilsRptPathRel(d)) == i.relation && oilsRptPathCol(d) == col.colname ) {
253                                         return false;
254                                 }
255                         }
256                         return true;
257                 }
258         );
259
260         if(!oilsRpt.def.select) oilsRpt.def.select = [];
261
262         oilsRptPruneFromList(list);
263
264         /*
265         for( var j = 0; j < list.length; j++ ) {
266                 debug('seeing if we can prune from clause with relation = ' + hex_md5(oilsRptPathRel(list[j])));
267                 if(     !grep(oilsRpt.def.select,
268                                 function(i){ return (i.relation == hex_md5(oilsRptPathRel(list[j]))); })
269                         && !grep(oilsRpt.def.where,
270                                 function(i){ return (i.relation == hex_md5(oilsRptPathRel(list[j]))); })
271                         && !grep(oilsRpt.def.having,
272                                 function(i){ return (i.relation == hex_md5(oilsRptPathRel(list[j]))); })
273                 ) {
274                         _debug('pruning from clause');
275                         oilsRptPruneFromClause(oilsRptPathRel(list[j]));
276                 }
277         }
278         */
279
280         oilsRptDebug();
281 }
282
283 function oilsRptPruneFromList(pathlist) {
284
285         for( var j = 0; j < pathlist.length; j++ ) {
286                 /* if there are no items left in the "select", "where", or "having" clauses 
287                         for the given relation, trim this relation from the "from" clause */
288                 var path = pathlist[j];
289                 var encrel = hex_md5(oilsRptPathRel(path));
290
291                 debug('seeing if we can prune from clause with relation = ' + encrel +' : path = ' + path);
292
293                 var func = function(i){ return (i.relation == hex_md5(oilsRptPathRel(path))); };
294
295                 if(     !grep(oilsRpt.def.select, func) && 
296                                 !grep(oilsRpt.def.where, func) && 
297                                 !grep(oilsRpt.def.having, func) ) {
298
299                         oilsRptPruneFromClause(oilsRptPathRel(pathlist[j]));
300                 }
301         }
302 }
303
304
305 /* for each item in the path list, remove the associated data
306         from the "from" clause */
307
308 function oilsRptPruneFromClause(relation, node) {
309         _debug("removing relation from 'from' clause: " + relation);
310         if(!node) node = oilsRpt.def.from.join;
311         if(!node) return false;
312
313         for( var i in node ) {
314                 _debug("from prune looking at node "+node[i].path);
315                 // first, descend into the tree, and prune leaves first
316                 if( node[i].join ) {
317                         oilsRptPruneFromClause(relation, node[i].join); 
318                         if(oilsRptObjectKeys(node[i].join).length == 0) 
319                                 delete node[i].join;
320                 }
321         }
322
323         if(!node.join) {
324
325                 var key = oilsRptObjectKeys(node)[0];
326                 var from_alias = node[key].alias;
327                 var func = function(n){ return (n.relation == from_alias)};
328
329                 _debug("pruning from clause with alias "+ from_alias);
330
331                 if(     !grep(oilsRpt.def.select, func) &&
332                                 !grep(oilsRpt.def.where, func) &&
333                                 !grep(oilsRpt.def.having, func) ) {
334
335                         // if we're at an unused empty leaf, remove it
336                         delete node[i];
337                         return true;
338                 }
339         }
340
341         return false;
342 }
343
344 function oilsRptMkFilterTags(path, tform, filter) {
345         var name = oilsRptMakeLabel(path);
346         if(tform) name += ' ('+tform+')';
347         name += ' "' + filter + '"';
348         var epath = path + ':'+filter+':';
349         if(tform) epath += tform;
350
351         return [ name, epath ];
352 }
353
354
355 function oilsAddRptFilterItem(path, tform, filter) {
356         _debug("Adding filter item for "+path+" tform="+tform+" filter="+filter);
357
358         var name = oilsRptMkFilterTags(path, tform, filter);
359         var epath = name[1];
360         name = name[0];
361
362         if( ! oilsAddSelectorItem(oilsRptFilterSelector, epath, name) )
363                 return;
364
365         var where = {
366                 relation: hex_md5(oilsRptPathRel(path)), 
367                 path : path,
368                 column:   { transform: tform, colname: oilsRptPathCol(path) },
369                 condition : {}
370         };
371         if( filter == 'is' || filter == 'is not' )
372                 where.condition[filter] = null;
373         else where.condition[filter] = oilsRptNextParam();
374
375         switch(tform) {
376                 case 'substring' : where.column.params = oilsRptNextParam();
377         }
378
379         oilsRpt.def.where.push(where);
380         mergeObjects( oilsRpt.def.from, oilsRptBuildFromClause(path));
381         oilsRptDebug();
382 }
383
384
385 function oilsAddRptHavingItem(path, tform, filter) {
386         _debug("Adding filter item for "+path+" tform="+tform+" filter="+filter);
387
388         var name = oilsRptMkFilterTags(path, tform, filter);
389         var epath = name[1];
390         name = name[0];
391
392         if( ! oilsAddSelectorItem(oilsRptHavingSelector, epath, name) )
393                 return;
394
395         var having = {
396                 relation: hex_md5(oilsRptPathRel(path)), 
397                 path : path,
398                 column:   { transform: tform, colname: oilsRptPathCol(path) },
399                 condition : {}
400         };
401         if( filter == 'is' || filter == 'is not' )
402                 having.condition[filter] = null;
403         else having.condition[filter] = oilsRptNextParam();
404
405         switch(tform) {
406                 case 'substring' : having.column.params = oilsRptNextParam();
407         }
408
409         oilsRpt.def.having.push(having);
410         mergeObjects( oilsRpt.def.from, oilsRptBuildFromClause(path));
411         oilsRptDebug();
412 }
413
414
415
416 function oilsDelSelectedFilterItems() {
417         _oilsDelSelectedFilterItems('where');
418 }
419 function oilsDelSelectedAggFilterItems() {
420         _oilsDelSelectedFilterItems('having');
421 }
422
423 function _oilsDelSelectedFilterItems(type) {
424
425         /* the values in this list are formed:  <path>:<operation>:<transform> */
426         var list = oilsDelSelectedItems(oilsRptFilterSelector);
427
428         for( var i = 0; i < list.length; i++ ) {
429                 var enc_path = list[i];
430                 var data = oilsRptParseFilterEncPath(enc_path);
431                 oilsRpt.def[type] = grep( 
432                         oilsRpt.def[type],
433                         function(f) { 
434                                 return oilsRptFilterDataMatches( 
435                                         f, data.path, data.operation, data.tform );
436                         }
437                 );
438         }
439
440         if(!oilsRpt.def[type]) oilsRpt.def[type] = [];
441         oilsRptPruneFromList(list);
442         oilsRptDebug();
443 }
444
445 function oilsRptParseFilterEncPath(item) {
446         return {
447                 path:           item.replace(/:.*/,''),
448                 operation:      item.replace(/.*:(.*):.*/,'$1'),
449                 tform:  item.replace(/.*?:.*?:(.*)/,'$1')
450         };
451 }
452
453
454 function oilsRptFilterDataMatches(filter, path, operation, tform) {
455         var rel = hex_md5(oilsRptPathRel(path));
456         var col = oilsRptPathCol(path);
457
458         if(     col == filter.column.colname &&
459                         rel == filter.relation &&       
460                         tform == filter.column.transform &&
461                         operation == oilsRptObjectKeys(filter)[0] ) return true;
462
463         return false;
464 }
465
466 /*
467 function oilsRptFilterGrep(flist, filter) {
468
469         for( var j = 0; j < flist.length; j++ ) {
470
471                 var fil = flist[j];
472                 var col = filter.column;
473                 var frel = hex_md5(oilsRptPathRel(fil.path));
474                 var fcol = oilsRptPathCol(fil.path);
475
476                 var op = oilsRptObjectKeys(filter.condition)[0];
477
478                 if(     frel == filter.relation && 
479                                 fcol == col.colname && 
480                                 fil.operation == op &&
481                                 fil.tform == col.transform ) {
482                                 return false;
483                 }
484         }
485         return true;
486 }
487 */
488
489 /* adds an item to the display window */
490 function oilsAddRptAggFilterItem(val) {
491         oilsAddSelectorItem(oilsRptHavingFilterSelector, val);
492 }
493
494 /* removes a specific item from the display window */
495 function oilsDelAggFilterItem(val) {
496         oilsDelSelectorItem(oilsRptHavingFilterSelector, val);
497 }
498
499
500
501 /*
502 function ___oilsDelSelectedAggFilterItems() {
503         var list = oilsDelSelectedItems(oilsRptHavingFilterSelector);
504         oilsRpt.def.having = grep( oilsRpt.def.having, 
505                 function(i) {
506                         for( var j = 0; j < list.length; j++ ) {
507                                 var d = list[j];
508                                 var col = i.column;
509
510                                 if( typeof col != 'string' ) 
511                                         for( var c in col ) col = col[c];
512
513                                 if( typeof col != 'string' ) col = col[0];
514
515                                 if( oilsRptPathRel(d) == i.relation && oilsRptPathCol(d) == col ) {
516                                 */
517                                 //      var param = (i.alias) ? i.alias.match(/::P\d*/) : null;
518                                         /*
519                                         if( param ) delete oilsRpt.params[param];
520                                         return false;
521                                 }
522                         }
523                         return true;
524                 }
525         );
526
527         if(!oilsRpt.def.having) oilsRpt.def.having = [];
528         oilsRptPruneFromList(list);
529         oilsRptDebug();
530 }
531 */
532
533
534 /* adds an item to the display window */
535 function oilsAddSelectorItem(sel, val, name) {
536         name = (name) ? name : oilsRptMakeLabel(val);
537         for( var i = 0; i < sel.options.length; i++ ) {
538                 var opt = sel.options[i];
539                 if( opt.value == val ) return false;
540         }
541         insertSelectorVal( sel, -1, name, val );
542         return true;
543 }
544
545
546 /* removes a specific item from the display window */
547 function oilsDelSelectorItem(sel, val) {
548         var opts = sel.options;
549         for( var i = 0; i < opts.length; i++ ) {
550                 var opt = opts[i];
551                 if( opt.value == val )  {
552                         if( i == opts.length - 1 ) 
553                                 opts[i] = null;
554                         else opts[i] = opts[i+1];
555                         return;
556                 }
557         }
558 }
559
560 /* removes selected items from the display window */
561 function oilsDelSelectedItems(sel) {
562         var list = getSelectedList(sel);
563         for( var i = 0; i < list.length; i++ ) 
564                 oilsDelSelectorItem(sel, list[i]);
565         return list;
566 }
567
568
569 /* hides the different field editor tabs */
570 function oilsRptHideEditorDivs() {
571         hideMe(DOM.oils_rpt_tform_div);
572         hideMe(DOM.oils_rpt_filter_div);
573         hideMe(DOM.oils_rpt_agg_filter_div);
574         hideMe(DOM.oils_rpt_order_by_div);
575 }
576
577
578 /**
579   This draws the 3-tabbed window containing the transform,
580   filter, and aggregate filter picker window
581   */
582 function oilsRptDrawDataWindow(path) {
583         var col = oilsRptPathCol(path);
584         var cls = oilsRptPathClass(path);
585         var field = oilsRptFindField(oilsIDL[cls], col);
586
587         appendClear(DOM.oils_rpt_editor_window_label, text(oilsRptMakeLabel(path)));
588         appendClear(DOM.oils_rpt_editor_window_datatype, text(field.datatype));
589
590         _debug("setting update data window for column "+col+' on class '+cls);
591
592         var div = DOM.oils_rpt_column_editor;
593         /* set a preliminary top position so the page won't bounce around */
594         div.setAttribute('style','top:'+oilsMouseX+'px');
595
596         /* unhide the div so we can determine the dimensions */
597         unHideMe(div);
598
599         /* don't let them see the floating div until the position is fully determined */
600         div.style.visibility='hidden'; 
601
602         oilsRptDrawTransformWindow(path, col, cls, field);
603         oilsRptDrawFilterWindow(path, col, cls, field);
604         oilsRptDrawHavingWindow(path, col, cls, field);
605         oilsRptDrawOrderByWindow(path, col, cls, field);
606
607         buildFloatingDiv(div, 600);
608
609         /* now let them see it */
610         div.style.visibility='visible';
611         oilsRptSetDataWindowActions(div);
612 }
613
614
615 function oilsRptSetDataWindowActions(div) {
616         /* give the tab links behavior */
617         DOM.oils_rpt_tform_tab.onclick = 
618                 function(){oilsRptHideEditorDivs();unHideMe(DOM.oils_rpt_tform_div)};
619         DOM.oils_rpt_filter_tab.onclick = 
620                 function(){oilsRptHideEditorDivs();unHideMe(DOM.oils_rpt_filter_div)};
621         DOM.oils_rpt_agg_filter_tab.onclick = 
622                 function(){oilsRptHideEditorDivs();unHideMe(DOM.oils_rpt_agg_filter_div)};
623
624         /*
625         DOM.oils_rpt_order_by_tab.onclick = 
626                 function(){
627                         oilsRptHideEditorDivs();
628                         oilsRptDrawOrderByWindow();
629                         unHideMe(DOM.oils_rpt_order_by_div);
630                         };
631                         */
632
633         DOM.oils_rpt_tform_tab.onclick();
634         DOM.oils_rpt_column_editor_close_button.onclick = function(){hideMe(div);};
635 }
636
637
638 function oilsRptDrawFilterWindow(path, col, cls, field) {
639
640         var tformPicker = new oilsRptTformPicker( {     
641                         node : DOM.oils_rpt_filter_tform_table,
642                         datatype : field.datatype,
643                         non_aggregate : true
644                 }
645         );
646
647         var filterPicker = new oilsRptFilterPicker({
648                         node : DOM.oils_rpt_filter_op_table,
649                         datatype : field.datatype
650                 }
651         );
652
653         DOM.oils_rpt_filter_submit.onclick = function() {
654                 oilsAddRptFilterItem(
655                         path, tformPicker.getSelected(), filterPicker.getSelected());
656         }
657 }
658
659
660 function oilsRptDrawHavingWindow(path, col, cls, field) {
661         var tformPicker = new oilsRptTformPicker( {     
662                         node : DOM.oils_rpt_agg_filter_tform_table,
663                         datatype : field.datatype,
664                         aggregate : true
665                 }
666         );
667
668         var filterPicker = new oilsRptFilterPicker({
669                         node : DOM.oils_rpt_agg_filter_op_table,
670                         datatype : field.datatype
671                 }
672         );
673
674         DOM.oils_rpt_agg_filter_submit.onclick = function() {
675                 oilsAddRptHavingItem(
676                         path, tformPicker.getSelected(), filterPicker.getSelected());
677         }
678 }
679
680 /* draws the transform window */
681 function oilsRptDrawTransformWindow(path, col, cls, field) {
682         DOM.oils_rpt_tform_label_input.value = oilsRptMakeLabel(path);
683         var dtype = field.datatype;
684
685         var tformPicker = new oilsRptTformPicker( {     
686                         node : DOM.oils_rpt_tform_table,
687                         datatype : field.datatype,
688                         non_aggregate : true,
689                         aggregate : true
690                 }
691         );
692
693         DOM.oils_rpt_tform_submit.onclick = 
694                 function(){ 
695                         oilsAddRptDisplayItem(path, 
696                                 DOM.oils_rpt_tform_label_input.value, tformPicker.getSelected() );
697                 };
698
699         DOM.oils_rpt_tform_label_input.focus();
700         DOM.oils_rpt_tform_label_input.select();
701
702         _debug("Building transform window for datatype "+dtype);
703 }
704
705
706 //function oilsRptDrawOrderByWindow(path, col, cls, field) {
707 function oilsRptDrawOrderByWindow() {
708         var sel = DOM.oils_rpt_order_by_selector;
709         removeChildren(sel);
710         DOM.oils_rpt_order_by_submit.onclick = function() {
711                 oilsRptAddOrderBy(getSelectorVal(sel));
712         }
713
714         var cols = oilsRpt.def.select;
715         for( var i = 0; i < cols.length; i++ ) {
716                 var obj = cols[i];
717                 insertSelectorVal(sel, -1, obj.alias, obj.path);
718         }
719 }
720
721 function oilsRptAddOrderBy(path) {
722         var rel = hex_md5(oilsRptPathRel(path));
723         var order_by = oilsRpt.def.order_by;
724
725         /* if this item is already in the order by remove it and overwrite it */
726         order_by = grep(oilsRpt.def.order_by, 
727                 function(i) {return (i.path != path)});
728         
729         if(!order_by) order_by = [];
730
731         /* find the column definition in the select blob */
732         var obj = grep(oilsRpt.def.select,
733                 function(i) {return (i.path == path)});
734         
735         if(!obj) return;
736         obj = obj[0];
737         
738         order_by.push({ 
739                 relation : obj.relation, 
740                 column : obj.column,
741                 direction : getSelectorVal(DOM.oils_rpt_order_by_dir)
742         });
743
744         oilsRpt.def.order_by = order_by;
745         oilsRptDebug();
746 }
747
748
749