Plugin UnivBristol by Frederic Francois
[myslice.git] / plugins / univbrisfoam / static / js / univbrisfoam.js
1 /**
2  * Description: display a query result in a datatables-powered <table>
3  * Copyright (c) 2012-2013 UPMC Sorbonne Universite - INRIA
4  * License: GPLv3
5  */
6
7 (function($){
8
9     var debug=false;
10     window.query_itr2=0;
11     table_links=[];
12     //debug=true
13
14     regex_filter="((packet)|(compute))";
15
16     var UnivbrisFoam = Plugin.extend({
17
18         init: function(options, element) {
19             //alert("foam init called");
20             this.classname="univbrisfoam";
21             this._super(options, element);
22                 
23             //alert(this.options.hidden_columns);
24             /* Member variables */
25             // in general we expect 2 queries here
26             // query_uuid refers to a single object (typically a slice)
27             // query_all_uuid refers to a list (typically resources or users)
28             // these can return in any order so we keep track of which has been received yet
29             this.received_all_query = false;
30             this.received_query = false;
31
32             // We need to remember the active filter for datatables filtering
33             this.filters = Array(); 
34
35             // an internal buffer for records that are 'in' and thus need to be checked 
36             this.buffered_records_to_check = [];
37             // an internal buffer for keeping lines and display them in one call to fnAddData
38             this.buffered_lines = [];
39
40             /* Events */
41             // xx somehow non of these triggers at all for now
42             this.elmt().on('show', this, this.on_show);
43             this.elmt().on('shown.bs.tab', this, this.on_show);
44             this.elmt().on('resize', this, this.on_resize);
45
46             var query = manifold.query_store.find_analyzed_query(this.options.query_uuid);
47             this.object = query.object;
48
49             //// we need 2 different keys
50             // * canonical_key is the primary key as derived from metadata (typically: urn)
51             //   and is used to communicate about a given record with the other plugins
52             // * init_key is a key that both kinds of records 
53             //   (i.e. records returned by both queries) must have (typically: hrn or hostname)
54             //   in general query_all will return well populated records, but query
55             //   returns records with only the fields displayed on startup
56             var keys = manifold.metadata.get_key(this.object);
57             this.canonical_key = (keys && keys.length == 1) ? keys[0] : undefined;
58             // 
59             this.init_key = this.options.init_key;
60             // have init_key default to canonical_key
61             this.init_key = this.init_key || this.canonical_key;
62             // sanity check
63             if ( ! this.init_key ) messages.warning ("UnivbrisFoam : cannot find init_key");
64             if ( ! this.canonical_key ) messages.warning ("UnivbrisFoam : cannot find canonical_key");
65             if (debug) messages.debug("UnivbrisFoam: canonical_key="+this.canonical_key+" init_key="+this.init_key);
66             /* Setup query and record handlers */
67             this.listen_query(options.query_uuid);
68             this.listen_query(options.query_all_uuid, 'all');
69             this.listen_query(options.sync_query_uuid,'sync');
70
71             /* GUI setup and event binding */
72             this.initialize_table();
73
74             jQuery( "#univbris_foam_ports_selection" ).hide();
75             
76         },
77
78         /* PLUGIN EVENTS */
79
80         on_show: function(e) {
81             if (debug) messages.debug("univbrisfoam.on_show");
82             var self = e.data;
83             self.table.fnAdjustColumnSizing();
84         },        
85
86         on_resize: function(e) {
87             if (debug) messages.debug("univbrisfoam.on_resize");
88             var self = e.data;
89             self.table.fnAdjustColumnSizing();
90         },        
91
92         /* GUI EVENTS */
93
94         /* GUI MANIPULATION */
95
96         initialize_table: function() 
97         {
98             /* Transforms the table into DataTable, and keep a pointer to it */
99                 //alert("init");
100             var self = this;
101             var actual_options = {
102                 // Customize the position of Datatables elements (length,filter,button,...)
103                 // we use a fluid row on top and another on the bottom, making sure we take 12 grid elt's each time
104                 //sDom: "<'row'<'col-xs-5'l><'col-xs-1'r><'col-xs-6'f>>t<'row'<'col-xs-5'i><'col-xs-7'p>>",
105                 sDom: "<'row'<'col-xs-5'l><'col-xs-1'r><'col-xs-6'f>>t<'row'<'col-xs-5'i><'col-xs-5'p>>",
106                 // XXX as of sept. 2013, I cannot locate a bootstrap3-friendly mode for now
107                 // hopefully this would come with dataTables v1.10 ?
108                 // in any case, search for 'sPaginationType' all over the code for more comments
109                 sPaginationType: 'bootstrap',
110                 // Handle the null values & the error : Datatables warning Requested unknown parameter
111                 // http://datatables.net/forums/discussion/5331/datatables-warning-...-requested-unknown-parameter/p2
112                 aoColumnDefs: [{sDefaultContent: '',aTargets: [ '_all' ]}],
113                 // WARNING: this one causes tables in a 'tabs' that are not exposed at the time this is run to show up empty
114                 // sScrollX: '100%',       /* Horizontal scrolling */
115                 bProcessing: true,      /* Loading */
116                 fnDrawCallback: function() { self._querytable_draw_callback.call(self);}
117                 //fnFooterCallback: function() {self._univbrisfoam_footer_callback.call(self,nFoot, aData, iStart, iEnd, aiDisplay)};}
118                 // XXX use $.proxy here !
119             };
120             // the intention here is that options.datatables_options as coming from the python object take precedence
121             // xxx DISABLED by jordan: was causing errors in datatables.js
122             // xxx turned back on by Thierry - this is the code that takes python-provided options into account
123             // check your datatables_options tag instead 
124             // however, we have to accumulate in aoColumnDefs from here (above) 
125             // and from the python wrapper (checkboxes management, plus any user-provided aoColumnDefs)
126             if ( 'aoColumnDefs' in this.options.datatables_options) {
127                 actual_options['aoColumnDefs']=this.options.datatables_options['aoColumnDefs'].concat(actual_options['aoColumnDefs']);
128                 delete this.options.datatables_options['aoColumnDefs'];
129             }
130             $.extend(actual_options, this.options.datatables_options );
131             this.table = $("#univbris_foam_ports_selection__table").dataTable(actual_options);
132         
133             //this.table = this.elmt("table").dataTable(actual_options);
134             //alert(this.table);
135
136             /* Setup the SelectAll button in the dataTable header */
137             /* xxx not sure this is still working */
138             var oSelectAll = $('#datatableSelectAll-'+ this.options.plugin_uuid);
139             oSelectAll.html("<span class='glyphicon glyphicon-ok' style='float:right;display:inline-block;'></span>Select All");
140             oSelectAll.button();
141             oSelectAll.css('font-size','11px');
142             oSelectAll.css('float','right');
143             oSelectAll.css('margin-right','15px');
144             oSelectAll.css('margin-bottom','5px');
145             oSelectAll.unbind('click');
146             oSelectAll.click(this._selectAll);
147
148             /* Add a filtering function to the current table 
149              * Note: we use closure to get access to the 'options'
150              */
151             $.fn.dataTableExt.afnFiltering.push(function( oSettings, aData, iDataIndex ) { 
152                 /* No filtering if the table does not match */
153                 if (oSettings.nTable.id != self.options.plugin_uuid + '__table')
154                     return true;
155                 return self._querytable_filter.call(self, oSettings, aData, iDataIndex);
156             });
157
158            //alert(this.options.hidden_columns);
159
160             /* Processing hidden_columns */
161             $.each(this.options.hidden_columns, function(i, field) {
162                 //manifold.raise_event(self.options.query_all_uuid, FIELD_REMOVED, field);
163                 //alert (field);
164                 self.hide_column(field);
165                 //self.hide_column(field);
166             });
167
168             //$('<button id="next" type="button" style="height: 50px; width: 300px" onclick="fnButnext()">select flowspace</button>').appendTo('div.next');
169
170             //type="submit"
171
172               //$('<a href="http://localhost:8000/login/" id="next_link">next link</a>').appendTo('div.next');
173             //$("#univbris_flowspace_selection").style.display="none";
174             
175
176         }, // initialize_table
177
178
179         fnButnext:function(e){
180                 e.stopPropagation();
181                 //var sData = this.table.$('input').serialize();
182                 //var sData = $(this).closest('checkboxes__table').find('input').serialize();
183                 //var sData = $("form").find('input').serialize();
184                 //var element=this.table.$("id="'NEC A/80<->NEC B/1080'"");
185                 //var element=this.table.$("[id='"+"NEC A/80<->NEC B/1080"+"']");
186                 //var x = element.checked;
187                 //var uuid=this.id.split("-");
188                 //var oTable=$('checkboxes__table').dataTable();
189                 //var oTable=$.fn.dataTable();
190                 //this.elts("table").unbind('click').click(this, this.fnButnext);
191                 //var sData = $('input', oTable.fnGetNodes()).serialize();
192                 //$.fn.dataTable()
193                 //sData="test";
194                 ///var self = e.data;
195                 //self=self.options.query_uuid;
196                 //var oTable=$("#querytable-"+self).dataTable();
197                 //var sData = $('input', oTable.fnGetNodes()).serialize();
198                 //e.stopPropagation();
199                 //var oTable=$('#myTable').dataTable();
200                 //var sData = $('input', $("#uob_form")).serialize();
201                 //$("#univbris_flowspace_selection").style.visibility='visible';
202                 var sData=$("#uob_form").find("input").serialize();
203                 //alert("clicked: "+sData);
204                 jQuery( "#univbris_flowspace_selection" ).show();
205                 //$("#multi_flowpspace_ports_selected").append('<label><input type="checkbox" name="option[]" value="1" />chocolate</label>');
206
207                 //$("form#uob_form :input[type=checkbox]").each(function(){
208                 //      var input = $(this); // This is the jquery object of the input, do what you will
209                 //      //alert("id: "+ input.attr('id') + " checked: "+ input.is(':checked'));
210                 //      if(input.is(':checked')==true){
211                 //              //alert("got true");
212                 //              $("#multi_flowpspace_ports_selected").append('<label><input type="checkbox" name="'+input.attr('id')+'" />'+input.attr('id')+'</label>');
213                 //      }
214                 //});
215
216                 jQuery( "#univbris_foam_ports_selection" ).hide();
217                 //jQuery( "#univbris_flowspace_selection" ).hide();
218
219                 //var url = "http://localhost:8000/login/";    
220                 //$(location).attr('href',url);
221                 //window.location.href = "http://localhost:8000/login/"
222                 //window.location.replace("http://localhost:8000/login/");
223                 //UnivbrisFv(options,elements);
224
225                         //$("#next_link").click();
226                         
227                 },
228
229         
230
231         /**
232          * @brief Determine index of key in the table columns 
233          * @param key
234          * @param cols
235          */
236         getColIndex: function(key, cols) {
237             var tabIndex = $.map(cols, function(x, i) { if (x.sTitle == key) return i; });
238             return (tabIndex.length > 0) ? tabIndex[0] : -1;
239         }, // getColIndex
240
241         // create a checkbox <input> tag
242         // computes 'id' attribute from canonical_key
243         // computes 'init_id' from init_key for initialization phase
244         // no need to used convoluted ids with plugin-uuid or others, since
245         // we search using table.$ which looks only in this table
246         checkbox_html : function (record) {
247             var result="";
248             // Prefix id with plugin_uuid
249             result += "<input";
250             result += " class='univbrisfoam-checkbox'";
251          // compute id from canonical_key
252             var id = record[this.canonical_key]
253          // compute init_id form init_key
254             var init_id=record[this.init_key];
255          // set id - for retrieving from an id, or for posting events upon user's clicks
256             result += " id='"+record[this.canonical_key]+"'";
257          // set init_id
258             result += "init_id='" + init_id + "'";
259          // wrap up
260             result += " type='checkbox'";
261             result += " autocomplete='off'";
262             result += "></input>";
263             return result;
264         }, 
265
266         fnLinkClick:function(link){
267                 //console.log("link has been clicked: ");
268                 //console.log(link.target.id);
269
270                 var svg_links = svg.selectAll(".link");
271                 for(var i=0;i<svg_links[0].length;i++){
272                         //console.log(svg_links[0][i].__data__.value);
273                         if(svg_links[0][i].__data__.value==link.target.id){
274                                 //console.log("got clicked link in svg");
275                                 //console.log(link);
276                                 if(link.target.checked==true){
277                                         svg_links[0][i].style.stroke= 'black';
278                                         svg_links[0][i].style.strokeWidth= '5px';
279                                 }
280                                 else{
281                                         svg_links[0][i].style.stroke= '#ccc';
282                                         svg_links[0][i].style.strokeWidth= '4px';
283                                 }
284                                 //svg.transition();
285                                 break;
286                         }
287         
288                 }
289                 //console.log(svg_links);
290         },
291
292          fake_checkbox_html : function (record) {
293             //alert("fake fun called");
294             var result="";
295             // Prefix id with plugin_uuid
296             result += "<input";
297             //result += " class='univbrisfoam-checkbox'";
298          // set id - for retrieving from an id, or for posting events upon user's clicks
299             result += " id='"+ record +"'";
300             result += " name='"+ record +"'";
301             result += " class='checkboxlink'";
302             //result += " onclick=fnLinkClick(this)";
303          // set init_id
304             result += " init_id='" + record + "'";
305          // wrap up
306             result += " type='checkbox'";
307             result += " autocomplete='off'";
308             result += "></input>";
309             ///alert(result);
310             return result;
311         }, 
312
313
314         new_record: function(record)
315         {
316
317             var urn = record['urn'];
318             var pos = urn.search('link');
319             if (pos!=-1){
320                     var link = urn.substring(pos+5);
321                     pos = link.search("_");
322                     var head_node=link.substring(0,pos);
323                     link=link.substring(pos+1);
324                     pos = link.search("_");
325                     var head_port=link.substring(0,pos);
326                     link=link.substring(pos+1);
327                     pos = link.search("_");
328                     var tail_node=link.substring(0,pos);
329                     link=link.substring(pos+1);
330                     var tail_port=link;
331                 
332                    
333                     var link_type="packet";
334
335                     if (urn.search('opticalpacket')!=-1){
336                         link_type='opticalpacket';
337                     }
338                     else if (urn.search('optical')!=-1){
339                         link_type='optical';
340                     }
341                     else if (urn.search('compute')!=-1){
342                         link_type='compute';
343                     }
344                     else if (urn.search('federation')!=-1){
345                         link_type='federation';
346                     }
347                     
348                     //get island name
349                     pos = urn.search('ofam');
350                     var testbed=urn.substring(pos+5);
351                     testbed=testbed.substring(0, testbed.search('link')-1);
352             
353                     var found=false;
354                     for(i=0;i<table_links.length;i++){
355                                 if((table_links[i].head_node==head_node && table_links[i].tail_node==tail_node && table_links[i].head_port==head_port && table_links[i].tail_port==tail_port) || 
356                                    (table_links[i].head_node==tail_node && table_links[i].tail_node==head_node && table_links[i].head_port==tail_port && table_links[i].tail_port==head_port)){
357                                         found=true;
358                                         break;
359                                 }
360                     }
361
362                     if (found == false){
363                                 var tmp_link={};
364                                 tmp_link['head_node']=head_node;
365                                 tmp_link['tail_node']=tail_node;
366                                 tmp_link['head_port']=head_port;
367                                 tmp_link['tail_port']=tail_port;
368                                 tmp_link['testbed']=testbed;
369                                 tmp_link['link_type']=link_type;
370                                 tmp_link['urn']=urn;
371                                 table_links.push(tmp_link);
372                     }
373                            
374                 }
375
376         },
377
378
379         process_records: function(link){
380
381          // this models a line in dataTables, each element in the line describes a cell
382             line = new Array();
383      
384             // go through table headers to get column names we want
385             // in order (we have temporarily hack some adjustments in names)
386             var cols = this.table.fnSettings().aoColumns;
387             var colnames = cols.map(function(x) {return x.sTitle})
388             var nb_col = cols.length;
389             /* if we've requested checkboxes, then forget about the checkbox column for now */
390             if (this.options.checkboxes) nb_col -= 1;
391             
392             for (var j = 0; j < nb_col; j++) {
393                         if (typeof colnames[j] == 'undefined') {
394                             line.push('...');
395                         } else if (colnames[j] == 'testbed') {
396                            line.push(link.testbed);
397                         } else if (colnames[j] == 'head node id/port') {
398                             line.push(link.head_node+"/"+link.head_port);
399                         }
400                           else if (colnames[j] == 'tail node id/port'){
401                             line.push(link.tail_node+"/"+link.tail_port);
402                         }
403                           else if (colnames[j] == 'link type'){
404                             line.push(link.link_type);
405                         }
406                           else if (colnames[j] == 'selected'){
407                             
408                             line.push(this.fake_checkbox_html(link.urn));
409                         }
410
411              }
412
413                // catch up with the last column if checkboxes were requested 
414             if (this.options.checkboxes) {
415                         // Use a key instead of hostname (hard coded...)
416                         line.push(this.checkbox_html(record));
417             }
418             
419                     // adding an array in one call is *much* more efficient
420                         // this.table.fnAddData(line);
421             this.buffered_lines.push(line);
422         },
423
424         clear_table: function()
425         {
426             this.table.fnClearTable();
427         },
428
429         redraw_table: function()
430         {
431             this.table.fnDraw();
432         },
433
434         show_column: function(field)
435         {
436             var oSettings = this.table.fnSettings();
437             var cols = oSettings.aoColumns;
438             var index = this.getColIndex(field,cols);
439             if (index != -1)
440                 this.table.fnSetColumnVis(index, true);
441         },
442
443         hide_column: function(field)
444         {
445             var oSettings = this.table.fnSettings();
446             var cols = oSettings.aoColumns;
447             var index = this.getColIndex(field,cols);
448             //index=-1;
449             //alert(field + ": index: " + index );
450             if (index != -1)
451                 //alert(field + ": hidden with index: " + index );
452                 this.table.fnSetColumnVis(index, false);
453         },
454
455         // this is used at init-time, at which point only init_key can make sense
456         // (because the argument record, if it comes from query, might not have canonical_key set
457         set_checkbox_from_record: function (record, checked) {
458             if (checked === undefined) checked = true;
459             var init_id = record[this.init_key];
460             if (debug) messages.debug("univbrisfoam.set_checkbox_from_record, init_id="+init_id);
461             // using table.$ to search inside elements that are not visible
462             var element = this.table.$('[init_id="'+init_id+'"]');
463             element.attr('checked',checked);
464         },
465
466         // id relates to canonical_key
467         set_checkbox_from_data: function (id, checked) {
468             if (checked === undefined) checked = true;
469             if (debug) messages.debug("univbrisfoam.set_checkbox_from_data, id="+id);
470             // using table.$ to search inside elements that are not visible
471             var element = this.table.$("[id='"+id+"']");
472             element.attr('checked',checked);
473         },
474
475         /*************************** QUERY HANDLER ****************************/
476
477         on_filter_added: function(filter)
478         {
479             this.filters.push(filter);
480             this.redraw_table();
481         },
482
483         on_filter_removed: function(filter)
484         {
485             // Remove corresponding filters
486             this.filters = $.grep(this.filters, function(x) {
487                 return x != filter;
488             });
489             this.redraw_table();
490         },
491         
492         on_filter_clear: function()
493         {
494             // XXX
495             this.redraw_table();
496         },
497
498         on_field_added: function(field)
499         {
500             this.show_column(field);
501         },
502
503         on_field_removed: function(field)
504         {
505             this.hide_column(field);
506         },
507
508         on_field_clear: function()
509         {
510             alert('UnivbrisFoam::clear_fields() not implemented');
511         },
512
513
514         /* sync query handler*/
515
516         on_sync_filter_added:function(filter)
517         {
518                 //alert("sync query filter called");
519                 this.filter=[];
520                 this.on_filter_added(filter);
521         },
522
523          on_sync_filter_clear: function()
524         {
525             // XXX
526             this.filters=Array();
527             this.redraw_table();
528         },
529
530
531
532
533         /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
534         /*************************** ALL QUERY HANDLER ****************************/
535
536         on_all_filter_added: function(filter)
537         {
538             // XXX
539             this.redraw_table();
540         },
541
542         on_all_filter_removed: function(filter)
543         {
544             // XXX
545             this.redraw_table();
546         },
547         
548         on_all_filter_clear: function()
549         {
550             // XXX
551             this.redraw_table();
552         },
553
554         on_all_field_added: function(field)
555         {
556             this.show_column(field);
557         },
558
559         on_all_field_removed: function(field)
560         {
561             this.hide_column(field);
562         },
563
564         on_all_field_clear: function()
565         {
566             alert('UnivbrisFoam::clear_fields() not implemented');
567         },
568
569
570         /*************************** RECORD HANDLER ***************************/
571
572         on_new_record: function(record)
573         {
574             if (this.received_all_query) {
575                 // if the 'all' query has been dealt with already we may turn on the checkbox
576                 this.set_checkbox_from_record(record, true);
577             } else {
578                 this.buffered_records_to_check.push(record);
579             }
580         },
581
582         on_clear_records: function()
583         {
584         },
585
586         // Could be the default in parent
587         on_query_in_progress: function()
588         {
589             this.spin();
590         },
591
592         on_query_done: function()
593         {
594             this.received_query = true;
595             // unspin once we have received both
596             if (this.received_all_query && this.received_query) this.unspin();
597         },
598         
599         on_field_state_changed: function(data)
600         {
601             switch(data.request) {
602                 case FIELD_REQUEST_ADD:
603                 case FIELD_REQUEST_ADD_RESET:
604                     this.set_checkbox_from_data(data.value, true);
605                     break;
606                 case FIELD_REQUEST_REMOVE:
607                 case FIELD_REQUEST_REMOVE_RESET:
608                     this.set_checkbox_from_data(data.value, false);
609                     break;
610                 default:
611                     break;
612             }
613         },
614
615         /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
616         // all
617         on_all_field_state_changed: function(data)
618         {
619             switch(data.request) {
620                 case FIELD_REQUEST_ADD:
621                 case FIELD_REQUEST_ADD_RESET:
622                     this.set_checkboxfrom_data(data.value, true);
623                     break;
624                 case FIELD_REQUEST_REMOVE:
625                 case FIELD_REQUEST_REMOVE_RESET:
626                     this.set_checkbox_from_data(data.value, false);
627                     break;
628                 default:
629                     break;
630             }
631         },
632
633         on_all_new_record: function(record)
634         {
635             this.new_record(record);
636         },
637
638         on_all_clear_records: function()
639         {
640             this.clear_table();
641
642         },
643
644         on_all_query_in_progress: function()
645         {
646             // XXX parent
647             this.spin();
648         }, // on_all_query_in_progress
649
650         on_all_query_done: function()
651         { 
652             if (debug) messages.debug("1-shot initializing dataTables content with " + this.buffered_lines.length + " lines");
653             this.buffered_lines=[];     
654             for(var i=0;i<table_links.length;i++){
655                 this.process_records(table_links[i]);
656             }
657             this.table.fnAddData (this.buffered_lines);
658             this.buffered_lines=[];
659             //alert(regex_filter)
660             /*try{
661                 //this.table.column(3).search("packet",True,True).draw();
662                 this.table.fnFilter(regex_filter,3,true);
663             }
664             catch (err){
665                 alert(err);
666             }*/
667             
668             var self = this;
669             // if we've already received the slice query, we have not been able to set 
670             // checkboxes on the fly at that time (dom not yet created)
671             $.each(this.buffered_records_to_check, function(i, record) {
672                 if (debug) messages.debug ("querytable delayed turning on checkbox " + i + " record= " + record);
673                 self.set_checkbox_from_record(record, true);
674             });
675             this.buffered_records_to_check = [];
676
677             this.received_all_query = true;
678             // unspin once we have received both
679             if (this.received_all_query && this.received_query) this.unspin();
680
681         }, // on_all_query_done
682
683         /************************** PRIVATE METHODS ***************************/
684
685         /** 
686          * @brief QueryTable filtering function
687          */
688         _querytable_filter: function(oSettings, aData, iDataIndex)
689         {
690             var ret = true;
691             $.each (this.filters, function(index, filter) { 
692                 /* XXX How to manage checkbox ? */
693                 var key = filter[0]; 
694                 var op = filter[1];
695                 var value = filter[2];
696
697                 /* Determine index of key in the table columns */
698                 var col = $.map(oSettings.aoColumns, function(x, i) {if (x.sTitle == key) return i;})[0];
699
700                 /* Unknown key: no filtering */
701                 if (typeof(col) == 'undefined')
702                     return;
703
704                 col_value=unfold.get_value(aData[col]);
705                 /* Test whether current filter is compatible with the column */
706                 if (op == '=' || op == '==') {
707                     if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a")
708                         ret = false;
709                 }else if (op == '!=') {
710                     if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a")
711                         ret = false;
712                 } else if(op=='<') {
713                     if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a")
714                         ret = false;
715                 } else if(op=='>') {
716                     if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a")
717                         ret = false;
718                 } else if(op=='<=' || op=='≤') {
719                     if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a")
720                         ret = false;
721                 } else if(op=='>=' || op=='≥') {
722                     if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a")
723                         ret = false;
724                 }else{
725                     // How to break out of a loop ?
726                     alert("filter not supported");
727                     return false;
728                 }
729
730             });
731             return ret;
732         },
733
734         _querytable_draw_callback: function()
735         {
736             /* 
737              * Handle clicks on checkboxes: reassociate checkbox click every time
738              * the table is redrawn 
739              */
740             //alert(options);
741             this.elts('querytable-checkbox').unbind('click').click(this, this._check_click);
742            // this.elts("next").unbind('click').click(this, this.fnButnext);
743             $(".checkboxlink").unbind('click').click(this,this.fnLinkClick);
744
745             if (!this.table)
746                 return;
747
748             /* Remove pagination if we show only a few results */
749             var wrapper = this.table; //.parent().parent().parent();
750             var rowsPerPage = this.table.fnSettings()._iDisplayLength;
751             var rowsToShow = this.table.fnSettings().fnRecordsDisplay();
752             var minRowsPerPage = this.table.fnSettings().aLengthMenu[0];
753
754             if ( rowsToShow <= rowsPerPage || rowsPerPage == -1 ) {
755                 $('.querytable_paginate', wrapper).css('visibility', 'hidden');
756             } else {
757                 $('.querytable_paginate', wrapper).css('visibility', 'visible');
758             }
759
760             if ( rowsToShow <= minRowsPerPage ) {
761                 $('.querytable_length', wrapper).css('visibility', 'hidden');
762             } else {
763                 $('.querytable_length', wrapper).css('visibility', 'visible');
764             }
765
766         },
767
768
769
770         _check_click: function(e) 
771         {
772             e.stopPropagation();
773
774             var self = e.data;
775             var id=this.id;
776
777             // this.id = key of object to be added... what about multiple keys ?
778             if (debug) messages.debug("querytable._check_click key="+this.canonical_key+"->"+id+" checked="+this.checked);
779             manifold.raise_event(self.options.query_uuid, this.checked?SET_ADD:SET_REMOVED, id);
780             //return false; // prevent checkbox to be checked, waiting response from manifold plugin api
781             
782         },
783
784         _selectAll: function() 
785         {
786             // requires jQuery id
787             var uuid=this.id.split("-");
788             var oTable=$("#querytable-"+uuid[1]).dataTable();
789             // Function available in QueryTable 1.9.x
790             // Filter : displayed data only
791             var filterData = oTable._('tr', {"filter":"applied"});   
792             /* TODO: WARNING if too many nodes selected, use filters to reduce nuber of nodes */        
793             if(filterData.length<=100){
794                 $.each(filterData, function(index, obj) {
795                     var last=$(obj).last();
796                     var key_value=unfold.get_value(last[0]);
797                     if(typeof($(last[0]).attr('checked'))=="undefined"){
798                         $.publish('selected', 'add/'+key_value);
799                     }
800                 });
801             }
802         },
803
804     });
805
806     $.plugin('UnivbrisFoam', UnivbrisFoam);
807
808   /* define the 'dom-checkbox' type for sorting in datatables 
809      http://datatables.net/examples/plug-ins/dom_sort.html
810      using trial and error I found that the actual column number
811      was in fact given as a third argument, and not second 
812      as the various online resources had it - go figure */
813     $.fn.dataTableExt.afnSortData['dom-checkbox'] = function  ( oSettings, _, iColumn ) {
814         return $.map( oSettings.oApi._fnGetTrNodes(oSettings), function (tr, i) {
815             return result=$('td:eq('+iColumn+') input', tr).prop('checked') ? '1' : '0';
816         } );
817     }
818
819      /*function fnLinkClick(link){
820                 console.log("link has been clicked: ");
821                 console.log(link);
822         }*/
823
824 })(jQuery);
825