2 * Description: display a query result in a datatables-powered <table>
3 * Copyright (c) 2012-2013 UPMC Sorbonne Universite - INRIA
14 var QueryTable = Plugin.extend({
16 init: function(options, element) {
17 this._super(options, element);
19 /* Member variables */
20 // in general we expect 2 queries here
21 // query_uuid refers to a single object (typically a slice)
22 // query_all_uuid refers to a list (typically resources or users)
23 // these can return in any order so we keep track of which has been received yet
24 this.received_all_query = false;
25 this.received_query = false;
27 // // We need to remember the active filter for datatables filtering
28 // this.filters = Array();
30 // an internal buffer for records that are 'in' and thus need to be checked
31 this.buffered_records_to_check = [];
34 // this.$element.on('show.Datatables', this.on_show);
35 this.elmt().on('show', this, this.on_show);
36 // Unbind all events using namespacing
38 // $(window).unbind('QueryTable');
40 var query = manifold.query_store.find_analyzed_query(this.options.query_uuid);
41 this.method = query.object;
43 // xxx beware that this.key needs to contain a key that all records will have
44 // in general query_all will return well populated records, but query
45 // returns records with only the fields displayed on startup.
46 this.key = (this.options.id_key);
48 // if not specified by caller, decide from metadata
49 var keys = manifold.metadata.get_key(this.method);
50 this.key = (keys && keys.length == 1) ? keys[0] : null;
52 if (! this.key) messages.warning("querytable.init could not kind valid key");
54 if (debug) messages.debug("querytable: key="+this.key);
56 /* Setup query and record handlers */
57 this.listen_query(options.query_uuid);
58 this.listen_query(options.query_all_uuid, 'all');
60 /* GUI setup and event binding */
61 this.initialize_table();
66 on_show: function(e) {
68 self.table.fnAdjustColumnSizing()
73 /* GUI MANIPULATION */
75 initialize_table: function() {
76 // compute columns based on columns and hidden_columns
77 this.slick_columns = [];
78 var all_columns = this.options.columns; // .concat(this.options.hidden_columns)
79 // xxx would be helpful to support a column_renamings options arg
80 // for redefining some labels like 'network_hrn' that really are not meaningful
81 for (c in all_columns) {
82 var column=all_columns[c];
83 this.slick_columns.push ( {id:column, name:column, field:column,
84 cssClass: "querytable-column-"+column,
85 width:100, minWidth:40, });
88 // xxx should be extensible from caller with this.options.slickgrid_options
89 this.slick_options = {
90 enableCellNavigation: false,
91 enableColumnReorder: true,
93 syncColumnCellResize: true,
97 this.slick_dataview = new Slick.Data.DataView();
99 this.slick_dataview.onRowCountChanged.subscribe ( function (e,args) {
100 self.slick_grid.updateRowCount();
101 self.slick_grid.autosizeColumns();
102 self.slick_grid.render();
105 var selector="#grid-"+this.options.domid;
107 messages.debug("slick grid selector is " + selector);
108 for (c in this.slick_columns) {
109 var col=this.slick_columns[c];
111 for (k in col) msg = msg+" col["+k+"]="+col[k];
112 messages.debug("slick_column["+c+"]:"+msg);
115 // add a checkbox column
116 var checkbox_selector = new Slick.CheckboxSelectColumn({
117 cssClass: "slick-cell-checkboxsel"
119 this.slick_columns.push(checkbox_selector.getColumnDefinition());
120 this.slick_grid = new Slick.Grid(selector, this.slick_dataview, this.slick_columns, this.slick_options);
121 this.slick_grid.setSelectionModel (new Slick.RowSelectionModel ({selectActiveRow: false}));
122 this.slick_grid.registerPlugin (checkbox_selector);
123 // autotooltips: for showing the full column name when ellipsed
124 var auto_tooltips = new Slick.AutoTooltips ({ enableForHeaderCells: true });
125 this.slick_grid.registerPlugin (auto_tooltips);
127 this.columnpicker = new Slick.Controls.ColumnPicker (this.slick_columns, this.slick_grid, this.slick_options)
130 }, // initialize_table
132 new_record: function(record) {
133 this.slick_data.push(record);
136 clear_table: function() {
138 this.slick_dataview.setItems(this.slick_data,this.key);
141 redraw_table: function() {
142 this.slick_grid.autosizeColumns();
143 this.slick_grid.render();
146 show_column: function(field) {
147 console.log ("querytable.show_column not yet implemented with slickgrid - field="+field);
150 hide_column: function(field) {
151 console.log("querytable.hide_column not implemented with slickgrid - field="+field);
154 set_checkbox: function(record, checked) {
155 /* Default: checked = true */
156 if (checked === undefined) checked = true;
159 /* The function accepts both records and their key */
160 switch (manifold.get_type(record)) {
165 /* XXX Test the key before ? */
166 id = record[this.key];
169 throw "Not implemented";
174 if (id === undefined) {
175 messages.warning("querytable.set_checkbox record has no id to figure which line to tick");
178 var index = this.slick_dataview.getIdxById(id);
179 var selectedRows=this.slick_grid.getSelectedRows();
180 if (checked) // add index in current list
181 selectedRows=selectedRows.concat(index);
183 selectedRows=selectedRows.filter(function(idx) {return idx!=index;});
184 this.slick_grid.setSelectedRows(selectedRows);
187 /*************************** QUERY HANDLER ****************************/
189 on_filter_added: function(filter) {
190 this.filters.push(filter);
194 on_filter_removed: function(filter) {
195 // Remove corresponding filters
196 this.filters = $.grep(this.filters, function(x) {
202 on_filter_clear: function() {
206 on_field_added: function(field) {
207 this.show_column(field);
210 on_field_removed: function(field) {
211 this.hide_column(field);
214 on_field_clear: function() {
215 alert('QueryTable::clear_fields() not implemented');
218 /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
219 /*************************** ALL QUERY HANDLER ****************************/
221 on_all_filter_added: function(filter) {
226 on_all_filter_removed: function(filter) {
231 on_all_filter_clear: function() {
236 on_all_field_added: function(field) {
237 this.show_column(field);
240 on_all_field_removed: function(field) {
241 this.hide_column(field);
244 on_all_field_clear: function() {
245 alert('QueryTable::clear_fields() not implemented');
249 /*************************** RECORD HANDLER ***************************/
251 on_new_record: function(record) {
252 if (this.received_all_query) {
253 // if the 'all' query has been dealt with already we may turn on the checkbox
254 this.set_checkbox(record, true);
256 // otherwise we need to remember that and do it later on
257 if (debug) messages.debug("Remembering record to check " + record[this.key]);
258 this.buffered_records_to_check.push(record);
262 on_clear_records: function() {
265 // Could be the default in parent
266 on_query_in_progress: function() {
270 on_query_done: function() {
271 this.received_query = true;
272 // unspin once we have received both
273 if (this.received_all_query && this.received_query) this.unspin();
276 on_field_state_changed: function(data) {
277 switch(data.request) {
278 case FIELD_REQUEST_ADD:
279 case FIELD_REQUEST_ADD_RESET:
280 this.set_checkbox(data.value, true);
282 case FIELD_REQUEST_REMOVE:
283 case FIELD_REQUEST_REMOVE_RESET:
284 this.set_checkbox(data.value, false);
291 /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
293 on_all_field_state_changed: function(data) {
294 switch(data.request) {
295 case FIELD_REQUEST_ADD:
296 case FIELD_REQUEST_ADD_RESET:
297 this.set_checkbox(data.value, true);
299 case FIELD_REQUEST_REMOVE:
300 case FIELD_REQUEST_REMOVE_RESET:
301 this.set_checkbox(data.value, false);
308 on_all_new_record: function(record) {
309 this.new_record(record);
312 on_all_clear_records: function() {
317 on_all_query_in_progress: function() {
320 }, // on_all_query_in_progress
322 on_all_query_done: function() {
323 if (debug) messages.debug("1-shot initializing dataTables content with " + this.slick_data.length + " lines");
324 var start=new Date();
325 // use this.key as the key for identifying rows
326 this.slick_dataview.setItems (this.slick_data, this.key);
327 var duration=new Date()-start;
328 if (debug) messages.debug("setItems " + duration + " ms");
330 // show full contents of first row app
331 for (k in this.slick_data[0]) messages.debug("slick_data[0]["+k+"]="+this.slick_data[0][k]);
335 // if we've already received the slice query, we have not been able to set
336 // checkboxes on the fly at that time (dom not yet created)
337 $.each(this.buffered_records_to_check, function(i, record) {
338 if (debug) messages.debug ("delayed turning on checkbox " + i + " record= " + record);
339 self.set_checkbox(record, true);
341 this.buffered_records_to_check = [];
343 this.received_all_query = true;
344 // unspin once we have received both
345 if (this.received_all_query && this.received_query) this.unspin();
347 }, // on_all_query_done
349 /************************** PRIVATE METHODS ***************************/
352 * @brief QueryTable filtering function
354 _querytable_filter: function(oSettings, aData, iDataIndex) {
356 $.each (this.filters, function(index, filter) {
357 /* XXX How to manage checkbox ? */
360 var value = filter[2];
362 /* Determine index of key in the table columns */
363 var col = $.map(oSettings.aoColumns, function(x, i) {if (x.sTitle == key) return i;})[0];
365 /* Unknown key: no filtering */
366 if (typeof(col) == 'undefined')
369 col_value=unfold.get_value(aData[col]);
370 /* Test whether current filter is compatible with the column */
371 if (op == '=' || op == '==') {
372 if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a")
374 }else if (op == '!=') {
375 if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a")
378 if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a")
381 if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a")
383 } else if(op=='<=' || op=='≤') {
384 if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a")
386 } else if(op=='>=' || op=='≥') {
387 if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a")
390 // How to break out of a loop ?
391 alert("filter not supported");
399 _querytable_draw_callback: function() {
401 * Handle clicks on checkboxes: reassociate checkbox click every time
402 * the table is redrawn
404 this.elts('querytable-checkbox').unbind('click').click(this, this._check_click);
409 /* Remove pagination if we show only a few results */
410 var wrapper = this.table; //.parent().parent().parent();
411 var rowsPerPage = this.table.fnSettings()._iDisplayLength;
412 var rowsToShow = this.table.fnSettings().fnRecordsDisplay();
413 var minRowsPerPage = this.table.fnSettings().aLengthMenu[0];
415 if ( rowsToShow <= rowsPerPage || rowsPerPage == -1 ) {
416 $('.querytable_paginate', wrapper).css('visibility', 'hidden');
418 $('.querytable_paginate', wrapper).css('visibility', 'visible');
421 if ( rowsToShow <= minRowsPerPage ) {
422 $('.querytable_length', wrapper).css('visibility', 'hidden');
424 $('.querytable_length', wrapper).css('visibility', 'visible');
428 _check_click: function(e) {
433 // XXX this.value = key of object to be added... what about multiple keys ?
434 if (debug) messages.debug("querytable click handler checked=" + this.checked + " hrn=" + this.value);
435 manifold.raise_event(self.options.query_uuid, this.checked?SET_ADD:SET_REMOVED, this.value);
436 //return false; // prevent checkbox to be checked, waiting response from manifold plugin api
440 _selectAll: function() {
441 // requires jQuery id
442 var uuid=this.id.split("-");
443 var oTable=$("#querytable-"+uuid[1]).dataTable();
444 // Function available in QueryTable 1.9.x
445 // Filter : displayed data only
446 var filterData = oTable._('tr', {"filter":"applied"});
447 /* TODO: WARNING if too many nodes selected, use filters to reduce nuber of nodes */
448 if(filterData.length<=100){
449 $.each(filterData, function(index, obj) {
450 var last=$(obj).last();
451 var key_value=unfold.get_value(last[0]);
452 if(typeof($(last[0]).attr('checked'))=="undefined"){
453 $.publish('selected', 'add/'+key_value);
461 $.plugin('QueryTable', QueryTable);
463 // /* define the 'dom-checkbox' type for sorting in datatables
464 // http://datatables.net/examples/plug-ins/dom_sort.html
465 // using trial and error I found that the actual column number
466 // was in fact given as a third argument, and not second
467 // as the various online resources had it - go figure */
468 // $.fn.dataTableExt.afnSortData['dom-checkbox'] = function ( oSettings, _, iColumn ) {
469 // return $.map( oSettings.oApi._fnGetTrNodes(oSettings), function (tr, i) {
470 // return result=$('td:eq('+iColumn+') input', tr).prop('checked') ? '1' : '0';