2 * Description: display a query result in a datatables-powered <table>
3 * Copyright (c) 2012-2013 UPMC Sorbonne Universite - INRIA
12 var QueryTable = Plugin.extend({
14 init: function(options, element) {
15 this._super(options, element);
17 /* Member variables */
18 // in general we expect 2 queries here
19 // query_uuid refers to a single object (typically a slice)
20 // query_all_uuid refers to a list (typically resources or users)
21 // these can return in any order so we keep track of which has been received yet
22 this.received_all_query = false;
23 this.received_query = false;
25 // // We need to remember the active filter for datatables filtering
26 // this.filters = Array();
28 // an internal buffer for records that are 'in' and thus need to be checked
29 this.buffered_records_to_check = [];
32 // this.$element.on('show.Datatables', this.on_show);
33 this.elmt().on('show', this, this.on_show);
34 // Unbind all events using namespacing
36 // $(window).unbind('QueryTable');
38 var query = manifold.query_store.find_analyzed_query(this.options.query_uuid);
39 this.method = query.object;
41 // xxx beware that this.key needs to contain a key that all records will have
42 // in general query_all will return well populated records, but query
43 // returns records with only the fields displayed on startup.
44 this.key = (this.options.id_key);
46 // if not specified by caller, decide from metadata
47 var keys = manifold.metadata.get_key(this.method);
48 this.key = (keys && keys.length == 1) ? keys[0] : null;
50 if (! this.key) messages.warning("querytable.init could not kind valid key");
52 if (debug) messages.debug("querytable: key="+this.key);
54 /* Setup query and record handlers */
55 this.listen_query(options.query_uuid);
56 this.listen_query(options.query_all_uuid, 'all');
58 /* GUI setup and event binding */
59 this.initialize_table();
64 on_show: function(e) {
66 self.table.fnAdjustColumnSizing()
71 /* GUI MANIPULATION */
73 initialize_table: function() {
74 // compute columns based on columns and hidden_columns
75 this.slick_columns = [];
76 var all_columns = this.options.columns; // .concat(this.options.hidden_columns)
77 for (c in all_columns) {
78 var column=all_columns[c];
79 this.slick_columns.push ( {id:column, name:column, field:column });
82 // xxx should be extensible from caller with this.options.slickgrid_options
83 this.slick_options = {
84 enableCellNavigation: false,
85 enableColumnReorder: true,
90 var selector="#grid-"+this.options.domid;
92 messages.debug("slick grid selector is " + selector);
93 for (c in this.slick_columns) {
94 var col=this.slick_columns[c];
96 for (k in col) msg = msg+" col["+k+"]="+col[k];
97 messages.debug("slick_column["+c+"]:"+msg);
100 // add a checkbox column
101 var checkbox_selector = new Slick.CheckboxSelectColumn({
102 cssClass: "slick-cell-checkboxsel"
104 this.slick_columns.push(checkbox_selector.getColumnDefinition());
105 this.slick_grid = new Slick.Grid(selector, this.slick_data, this.slick_columns, this.slick_options);
106 this.slick_grid.setSelectionModel (new Slick.RowSelectionModel ({selectActiveRow: false}));
107 this.slick_grid.registerPlugin (checkbox_selector);
109 this.columnpicker = new Slick.Controls.ColumnPicker (this.slick_columns, this.slick_grid, this.slick_options)
111 }, // initialize_table
113 // Determine index of key in the table columns
114 getColIndex: function(key, cols) {
115 var tabIndex = $.map(cols, function(x, i) { if (x.sTitle == key) return i; });
116 return (tabIndex.length > 0) ? tabIndex[0] : -1;
119 checkbox_html : function (key, value) {
120 // if (debug) messages.debug("checkbox_html, value="+value);
122 // Prefix id with plugin_uuid
124 result += " class='querytable-checkbox'";
125 result += " id='" + this.flat_id(this.id('checkbox', value)) + "'";
126 result += " name='" + key + "'";
127 result += " type='checkbox'";
128 result += " autocomplete='off'";
129 if (value === undefined) {
130 messages.warning("querytable.checkbox_html - undefined value");
132 result += " value='" + value + "'";
134 result += "></input>";
138 new_record: function(record) {
139 this.slick_data.push(record);
142 clear_table: function() {
143 console.log("clear_table not implemented");
146 redraw_table: function() {
150 show_column: function(field) {
151 var oSettings = this.table.fnSettings();
152 var cols = oSettings.aoColumns;
153 var index = this.getColIndex(field,cols);
155 this.table.fnSetColumnVis(index, true);
158 hide_column: function(field) {
159 console.log("hide_column not implemented - field="+field);
162 set_checkbox: function(record, checked) {
163 console.log("set_checkbox not implemented");
165 /* Default: checked = true */
166 if (checked === undefined) checked = true;
169 /* The function accepts both records and their key */
170 switch (manifold.get_type(record)) {
175 /* XXX Test the key before ? */
176 id = record[this.key];
179 throw "Not implemented";
184 if (id === undefined) {
185 messages.warning("querytable.set_checkbox record has no id to figure which line to tick");
188 var checkbox_id = this.flat_id(this.id('checkbox', id));
189 // function escape_id(myid) is defined in portal/static/js/common.functions.js
190 checkbox_id = escape_id(checkbox_id);
191 // using dataTables's $ to search also in nodes that are not currently displayed
192 var element = this.table.$(checkbox_id);
194 messages.debug("set_checkbox checked=" + checked
195 + " id=" + checkbox_id + " matches=" + element.length);
196 element.attr('checked', checked);
199 /*************************** QUERY HANDLER ****************************/
201 on_filter_added: function(filter) {
202 this.filters.push(filter);
206 on_filter_removed: function(filter) {
207 // Remove corresponding filters
208 this.filters = $.grep(this.filters, function(x) {
214 on_filter_clear: function() {
218 on_field_added: function(field) {
219 this.show_column(field);
222 on_field_removed: function(field) {
223 this.hide_column(field);
226 on_field_clear: function() {
227 alert('QueryTable::clear_fields() not implemented');
230 /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
231 /*************************** ALL QUERY HANDLER ****************************/
233 on_all_filter_added: function(filter) {
238 on_all_filter_removed: function(filter) {
243 on_all_filter_clear: function() {
248 on_all_field_added: function(field) {
249 this.show_column(field);
252 on_all_field_removed: function(field) {
253 this.hide_column(field);
256 on_all_field_clear: function() {
257 alert('QueryTable::clear_fields() not implemented');
261 /*************************** RECORD HANDLER ***************************/
263 on_new_record: function(record) {
264 if (this.received_all_query) {
265 // if the 'all' query has been dealt with already we may turn on the checkbox
266 this.set_checkbox(record, true);
268 // otherwise we need to remember that and do it later on
269 if (debug) messages.debug("Remembering record to check " + record[this.key]);
270 this.buffered_records_to_check.push(record);
274 on_clear_records: function() {
277 // Could be the default in parent
278 on_query_in_progress: function() {
282 on_query_done: function() {
283 this.received_query = true;
284 // unspin once we have received both
285 if (this.received_all_query && this.received_query) this.unspin();
288 on_field_state_changed: function(data) {
289 switch(data.request) {
290 case FIELD_REQUEST_ADD:
291 case FIELD_REQUEST_ADD_RESET:
292 this.set_checkbox(data.value, true);
294 case FIELD_REQUEST_REMOVE:
295 case FIELD_REQUEST_REMOVE_RESET:
296 this.set_checkbox(data.value, false);
303 /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
305 on_all_field_state_changed: function(data) {
306 switch(data.request) {
307 case FIELD_REQUEST_ADD:
308 case FIELD_REQUEST_ADD_RESET:
309 this.set_checkbox(data.value, true);
311 case FIELD_REQUEST_REMOVE:
312 case FIELD_REQUEST_REMOVE_RESET:
313 this.set_checkbox(data.value, false);
320 on_all_new_record: function(record) {
321 this.new_record(record);
324 on_all_clear_records: function() {
329 on_all_query_in_progress: function() {
332 }, // on_all_query_in_progress
334 on_all_query_done: function() {
335 if (debug) messages.debug("1-shot initializing dataTables content with " + this.slick_data.length + " lines");
336 this.slick_grid.setData (this.slick_data, true);
338 for (k in this.slick_data[0]) messages.debug("slick_data[0]["+k+"]="+this.slick_data[0][k]);
339 this.slick_grid.render();
342 // if we've already received the slice query, we have not been able to set
343 // checkboxes on the fly at that time (dom not yet created)
344 $.each(this.buffered_records_to_check, function(i, record) {
345 if (debug) messages.debug ("delayed turning on checkbox " + i + " record= " + record);
346 self.set_checkbox(record, true);
348 this.buffered_records_to_check = [];
350 this.received_all_query = true;
351 // unspin once we have received both
352 if (this.received_all_query && this.received_query) this.unspin();
354 }, // on_all_query_done
356 /************************** PRIVATE METHODS ***************************/
359 * @brief QueryTable filtering function
361 _querytable_filter: function(oSettings, aData, iDataIndex) {
363 $.each (this.filters, function(index, filter) {
364 /* XXX How to manage checkbox ? */
367 var value = filter[2];
369 /* Determine index of key in the table columns */
370 var col = $.map(oSettings.aoColumns, function(x, i) {if (x.sTitle == key) return i;})[0];
372 /* Unknown key: no filtering */
373 if (typeof(col) == 'undefined')
376 col_value=unfold.get_value(aData[col]);
377 /* Test whether current filter is compatible with the column */
378 if (op == '=' || op == '==') {
379 if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a")
381 }else if (op == '!=') {
382 if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a")
385 if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a")
388 if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a")
390 } else if(op=='<=' || op=='≤') {
391 if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a")
393 } else if(op=='>=' || op=='≥') {
394 if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a")
397 // How to break out of a loop ?
398 alert("filter not supported");
406 _querytable_draw_callback: function() {
408 * Handle clicks on checkboxes: reassociate checkbox click every time
409 * the table is redrawn
411 this.elts('querytable-checkbox').unbind('click').click(this, this._check_click);
416 /* Remove pagination if we show only a few results */
417 var wrapper = this.table; //.parent().parent().parent();
418 var rowsPerPage = this.table.fnSettings()._iDisplayLength;
419 var rowsToShow = this.table.fnSettings().fnRecordsDisplay();
420 var minRowsPerPage = this.table.fnSettings().aLengthMenu[0];
422 if ( rowsToShow <= rowsPerPage || rowsPerPage == -1 ) {
423 $('.querytable_paginate', wrapper).css('visibility', 'hidden');
425 $('.querytable_paginate', wrapper).css('visibility', 'visible');
428 if ( rowsToShow <= minRowsPerPage ) {
429 $('.querytable_length', wrapper).css('visibility', 'hidden');
431 $('.querytable_length', wrapper).css('visibility', 'visible');
435 _check_click: function(e) {
440 // XXX this.value = key of object to be added... what about multiple keys ?
441 if (debug) messages.debug("querytable click handler checked=" + this.checked + " hrn=" + this.value);
442 manifold.raise_event(self.options.query_uuid, this.checked?SET_ADD:SET_REMOVED, this.value);
443 //return false; // prevent checkbox to be checked, waiting response from manifold plugin api
447 _selectAll: function() {
448 // requires jQuery id
449 var uuid=this.id.split("-");
450 var oTable=$("#querytable-"+uuid[1]).dataTable();
451 // Function available in QueryTable 1.9.x
452 // Filter : displayed data only
453 var filterData = oTable._('tr', {"filter":"applied"});
454 /* TODO: WARNING if too many nodes selected, use filters to reduce nuber of nodes */
455 if(filterData.length<=100){
456 $.each(filterData, function(index, obj) {
457 var last=$(obj).last();
458 var key_value=unfold.get_value(last[0]);
459 if(typeof($(last[0]).attr('checked'))=="undefined"){
460 $.publish('selected', 'add/'+key_value);
468 $.plugin('QueryTable', QueryTable);
470 // /* define the 'dom-checkbox' type for sorting in datatables
471 // http://datatables.net/examples/plug-ins/dom_sort.html
472 // using trial and error I found that the actual column number
473 // was in fact given as a third argument, and not second
474 // as the various online resources had it - go figure */
475 // $.fn.dataTableExt.afnSortData['dom-checkbox'] = function ( oSettings, _, iColumn ) {
476 // return $.map( oSettings.oApi._fnGetTrNodes(oSettings), function (tr, i) {
477 // return result=$('td:eq('+iColumn+') input', tr).prop('checked') ? '1' : '0';