2 * Description: display a query result in a slickgrid-powered <table>
3 * Copyright (c) 2012-2013 UPMC Sorbonne Universite - INRIA
7 /* ongoing adaptation to slickgrid
9 . checkboxes really running properly
10 . ability to sort on columns (should be straightforward
11 IIRC this got broken when moving to dataview, see dataview doc
12 . ability to sort on the checkboxes column
13 (e.g. have resources 'in' the slice show up first)
14 not quite clear how to do this
18 . rendering in the sliceview - does not use up all space,
19 this is different from the behaviour with simpleview
29 var QueryGrid = Plugin.extend({
31 init: function(options, element) {
32 this._super(options, element);
34 /* Member variables */
35 // in general we expect 2 queries here
36 // query_uuid refers to a single object (typically a slice)
37 // query_all_uuid refers to a list (typically resources or users)
38 // these can return in any order so we keep track of which has been received yet
39 this.received_all_query = false;
40 this.received_query = false;
42 // // We need to remember the active filter for filtering
43 // this.filters = Array();
45 // an internal buffer for records that are 'in' and thus need to be checked
46 this.buffered_records_to_check = [];
49 this.elmt().on('show', this, this.on_show);
51 var query = manifold.query_store.find_analyzed_query(this.options.query_uuid);
52 this.method = query.object;
54 // xxx beware that this.key needs to contain a key that all records will have
55 // in general query_all will return well populated records, but query
56 // returns records with only the fields displayed on startup.
57 this.key = (this.options.id_key);
59 // if not specified by caller, decide from metadata
60 var keys = manifold.metadata.get_key(this.method);
61 this.key = (keys && keys.length == 1) ? keys[0] : null;
63 if (! this.key) messages.warning("querygrid.init could not kind valid key");
65 if (debug) messages.debug("querygrid: key="+this.key);
67 /* Setup query and record handlers */
68 this.listen_query(options.query_uuid);
69 this.listen_query(options.query_all_uuid, 'all');
71 /* GUI setup and event binding */
72 this.initialize_table();
77 on_show: function(e) {
84 /* GUI MANIPULATION */
86 initialize_table: function() {
87 // compute columns based on columns and hidden_columns
88 this.slick_columns = [];
89 var all_columns = this.options.columns; // .concat(this.options.hidden_columns)
90 // xxx would be helpful to support a column_renamings options arg
91 // for redefining some labels like 'network_hrn' that really are not meaningful
92 for (c in all_columns) {
93 var column=all_columns[c];
94 this.slick_columns.push ( {id:column, name:column, field:column,
95 cssClass: "querygrid-column-"+column,
96 width:100, minWidth:40, });
98 var checkbox_selector = new Slick.CheckboxSelectColumn({
99 cssClass: "slick-checkbox"
101 this.slick_columns.push(checkbox_selector.getColumnDefinition());
103 // xxx should be extensible from caller with this.options.slickgrid_options
104 this.slick_options = {
105 enableCellNavigation: false,
106 enableColumnReorder: true,
108 syncColumnCellResize: true,
111 this.slick_data = [];
112 this.slick_dataview = new Slick.Data.DataView();
114 this.slick_dataview.onRowCountChanged.subscribe ( function (e,args) {
115 self.slick_grid.updateRowCount();
116 self.slick_grid.autosizeColumns();
117 self.slick_grid.render();
120 var selector="#grid-"+this.options.domid;
122 messages.debug("slick grid selector is " + selector);
123 for (c in this.slick_columns) {
124 var col=this.slick_columns[c];
126 for (k in col) msg = msg+" col["+k+"]="+col[k];
127 messages.debug("slick_column["+c+"]:"+msg);
131 this.slick_grid = new Slick.Grid(selector, this.slick_dataview, this.slick_columns, this.slick_options);
132 this.slick_grid.setSelectionModel (new Slick.RowSelectionModel ({selectActiveRow: false}));
133 this.slick_grid.registerPlugin (checkbox_selector);
134 // autotooltips: for showing the full column name when ellipsed
135 var auto_tooltips = new Slick.AutoTooltips ({ enableForHeaderCells: true });
136 this.slick_grid.registerPlugin (auto_tooltips);
138 this.columnpicker = new Slick.Controls.ColumnPicker (this.slick_columns, this.slick_grid, this.slick_options)
142 }, // initialize_table
144 new_record: function(record) {
145 this.slick_data.push(record);
148 clear_table: function() {
150 this.slick_dataview.setItems(this.slick_data,this.key);
153 redraw_table: function() {
154 this.slick_grid.autosizeColumns();
155 this.slick_grid.render();
158 show_column: function(field) {
159 console.log ("querygrid.show_column not yet implemented with slickgrid - field="+field);
162 hide_column: function(field) {
163 console.log("querygrid.hide_column not implemented with slickgrid - field="+field);
166 /*************************** QUERY HANDLER ****************************/
168 on_filter_added: function(filter) {
169 this.filters.push(filter);
173 on_filter_removed: function(filter) {
174 // Remove corresponding filters
175 this.filters = $.grep(this.filters, function(x) {
181 on_filter_clear: function() {
185 on_field_added: function(field) {
186 this.show_column(field);
189 on_field_removed: function(field) {
190 this.hide_column(field);
193 on_field_clear: function() {
194 alert('QueryGrid::clear_fields() not implemented');
197 /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
198 /*************************** ALL QUERY HANDLER ****************************/
200 on_all_filter_added: function(filter) {
205 on_all_filter_removed: function(filter) {
210 on_all_filter_clear: function() {
215 on_all_field_added: function(field) {
216 this.show_column(field);
219 on_all_field_removed: function(field) {
220 this.hide_column(field);
223 on_all_field_clear: function() {
224 alert('QueryGrid::clear_fields() not implemented');
228 /*************************** RECORD HANDLER ***************************/
230 on_new_record: function(record) {
231 if (this.received_all_query) {
232 // if the 'all' query has been dealt with already we may turn on the checkbox
233 this._set_checkbox(record, true);
235 // otherwise we need to remember that and do it later on
236 if (debug) messages.debug("Remembering record to check " + record[this.key]);
237 this.buffered_records_to_check.push(record);
241 on_clear_records: function() {
244 // Could be the default in parent
245 on_query_in_progress: function() {
249 on_query_done: function() {
250 this.received_query = true;
251 // unspin once we have received both
252 if (this.received_all_query && this.received_query) {
253 this._init_checkboxes();
258 on_field_state_changed: function(data) {
259 switch(data.request) {
260 case FIELD_REQUEST_ADD:
261 case FIELD_REQUEST_ADD_RESET:
262 this._set_checkbox(data.value, true);
264 case FIELD_REQUEST_REMOVE:
265 case FIELD_REQUEST_REMOVE_RESET:
266 this._set_checkbox(data.value, false);
273 /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
275 on_all_field_state_changed: function(data) {
276 switch(data.request) {
277 case FIELD_REQUEST_ADD:
278 case FIELD_REQUEST_ADD_RESET:
279 this._set_checkbox(data.value, true);
281 case FIELD_REQUEST_REMOVE:
282 case FIELD_REQUEST_REMOVE_RESET:
283 this._set_checkbox(data.value, false);
290 on_all_new_record: function(record) {
291 this.new_record(record);
294 on_all_clear_records: function() {
299 on_all_query_in_progress: function() {
302 }, // on_all_query_in_progress
304 on_all_query_done: function() {
305 var start=new Date();
306 if (debug) messages.debug("1-shot initializing slickgrid content with " + this.slick_data.length + " lines");
307 // use this.key as the key for identifying rows
308 this.slick_dataview.setItems (this.slick_data, this.key);
309 var duration=new Date()-start;
310 if (debug) messages.debug("setItems " + duration + " ms");
312 // show full contents of first row app
313 for (k in this.slick_data[0]) messages.debug("slick_data[0]["+k+"]="+this.slick_data[0][k]);
317 // if we've already received the slice query, we have not been able to set
318 // checkboxes on the fly at that time (dom not yet created)
319 $.each(this.buffered_records_to_check, function(i, record) {
320 if (debug) messages.debug ("delayed turning on checkbox " + i + " record= " + record);
321 self._set_checkbox(record, true);
323 this.buffered_records_to_check = [];
325 this.received_all_query = true;
326 // unspin once we have received both
327 if (this.received_all_query && this.received_query) {
328 this._init_checkboxes();
332 }, // on_all_query_done
334 /************************** PRIVATE METHODS ***************************/
336 _set_checkbox: function(record, checked) {
337 /* Default: checked = true */
338 if (checked === undefined) checked = true;
341 /* The function accepts both records and their key */
342 switch (manifold.get_type(record)) {
347 /* XXX Test the key before ? */
348 id = record[this.key];
351 throw "Not implemented";
356 if (id === undefined) {
357 messages.warning("querygrid._set_checkbox record has no id to figure which line to tick");
360 var index = this.slick_dataview.getIdxById(id);
361 var selectedRows=this.slick_grid.getSelectedRows();
362 if (checked) // add index in current list
363 selectedRows=selectedRows.concat(index);
364 else // remove index from current list
365 selectedRows=selectedRows.filter(function(idx) {return idx!=index;});
366 this.slick_grid.setSelectedRows(selectedRows);
369 // initializing checkboxes
370 // have tried 2 approaches, but none seems to work as we need it
371 // issue summarized in here
372 // http://stackoverflow.com/questions/20425193/slickgrid-selection-changed-callback-how-to-tell-between-manual-and-programmat
373 // arm the click callback on checkboxes
374 _init_checkboxes_manual : function () {
375 // xxx looks like checkboxes can only be the last column??
376 var checkbox_col = this.slick_grid.getColumns().length-1; // -1 +1 =0
377 console.log ("checkbox_col="+checkbox_col);
379 console.log ("HERE 1 with "+this.slick_dataview.getLength()+" sons");
380 for (var index=0; index < this.slick_dataview.getLength(); index++) {
381 // retrieve key (i.e. hrn) for this line
382 var key=this.slick_dataview.getItem(index)[this.key];
383 // locate cell <div> for the checkbox
384 var div=this.slick_grid.getCellNode(index,checkbox_col);
385 if (index <=30) console.log("HERE2 div",div," index="+index+" col="+checkbox_col);
386 // arm callback on single son of <div> that is the <input>
387 $(div).children("input").each(function () {
388 if (index<=30) console.log("HERE 3, index="+index+" key="+key);
389 $(this).click(function() {self._checkbox_clicked(self,this,key);});
394 // onSelectedRowsChanged will fire even when
395 _init_checkboxes : function () {
396 console.log("_init_checkboxes");
397 var grid=this.slick_grid;
398 this.slick_grid.onSelectedRowsChanged.subscribe(function(){
399 row_ids = grid.getSelectedRows();
400 console.log(row_ids);
404 // the callback for when user clicks
405 _checkbox_clicked: function(querygrid,input,key) {
406 // XXX this.value = key of object to be added... what about multiple keys ?
407 if (debug) messages.debug("querygrid click handler checked=" + input.checked + " key=" + key);
408 manifold.raise_event(querygrid.options.query_uuid, input.checked?SET_ADD:SET_REMOVED, key);
409 //return false; // prevent checkbox to be checked, waiting response from manifold plugin api
413 // xxx from this and down, probably needs further tweaks for slickgrid
415 _querygrid_filter: function(oSettings, aData, iDataIndex) {
417 $.each (this.filters, function(index, filter) {
418 /* XXX How to manage checkbox ? */
421 var value = filter[2];
423 /* Determine index of key in the table columns */
424 var col = $.map(oSettings.aoColumns, function(x, i) {if (x.sTitle == key) return i;})[0];
426 /* Unknown key: no filtering */
427 if (typeof(col) == 'undefined')
430 col_value=unfold.get_value(aData[col]);
431 /* Test whether current filter is compatible with the column */
432 if (op == '=' || op == '==') {
433 if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a")
435 }else if (op == '!=') {
436 if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a")
439 if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a")
442 if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a")
444 } else if(op=='<=' || op=='≤') {
445 if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a")
447 } else if(op=='>=' || op=='≥') {
448 if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a")
451 // How to break out of a loop ?
452 alert("filter not supported");
460 _selectAll: function() {
461 // requires jQuery id
462 var uuid=this.id.split("-");
463 var oTable=$("#querygrid-"+uuid[1]).dataTable();
464 // Function available in QueryGrid 1.9.x
465 // Filter : displayed data only
466 var filterData = oTable._('tr', {"filter":"applied"});
467 /* TODO: WARNING if too many nodes selected, use filters to reduce nuber of nodes */
468 if(filterData.length<=100){
469 $.each(filterData, function(index, obj) {
470 var last=$(obj).last();
471 var key_value=unfold.get_value(last[0]);
472 if(typeof($(last[0]).attr('checked'))=="undefined"){
473 $.publish('selected', 'add/'+key_value);
481 $.plugin('QueryGrid', QueryGrid);
483 // /* define the 'dom-checkbox' type for sorting in datatables
484 // http://datatables.net/examples/plug-ins/dom_sort.html
485 // using trial and error I found that the actual column number
486 // was in fact given as a third argument, and not second
487 // as the various online resources had it - go figure */
488 // $.fn.dataTableExt.afnSortData['dom-checkbox'] = function ( oSettings, _, iColumn ) {
489 // return $.map( oSettings.oApi._fnGetTrNodes(oSettings), function (tr, i) {
490 // return result=$('td:eq('+iColumn+') input', tr).prop('checked') ? '1' : '0';