1 // -*- js-indent-tab:2 -*-
3 * Description: display a query result in a slickgrid-powered table
4 * Copyright (c) 2012-2013 UPMC Sorbonne Universite - INRIA
11 * This is very rough for now and not deemed working
16 /* ongoing adaptation to slickgrid
18 . checkboxes really running properly
19 . ability to sort on columns (should be straightforward
20 IIRC this got broken when moving to dataview, see dataview doc
21 . ability to sort on the checkboxes column
22 (e.g. have resources 'in' the slice show up first)
23 not quite clear how to do this
27 . rendering in the sliceview - does not use up all space,
28 this is different from the behaviour with simpleview
38 var QueryGrid = Plugin.extend({
40 init: function(options, element) {
41 this.classname="querygrid";
42 this._super(options, element);
44 /* Member variables */
45 // in general we expect 2 queries here
46 // query_uuid refers to a single object (typically a slice)
47 // query_all_uuid refers to a list (typically resources or users)
48 // these can return in any order so we keep track of which has been received yet
49 this.received_all_query = false;
50 this.received_query = false;
52 // // We need to remember the active filter for filtering
53 // this.filters = Array();
55 // an internal buffer for records that are 'in' and thus need to be checked
56 this.buffered_records_to_check = [];
59 this.elmt().on('show', this, this.on_show);
61 var query = manifold.query_store.find_analyzed_query(this.options.query_uuid);
62 this.object = query.object;
64 //// we need 2 different keys
65 // * canonical_key is the primary key as derived from metadata (typically: urn)
66 // and is used to communicate about a given record with the other plugins
67 // * init_key is a key that both kinds of records
68 // (i.e. records returned by both queries) must have (typically: hrn or hostname)
69 // in general query_all will return well populated records, but query
70 // returns records with only the fields displayed on startup
71 var keys = manifold.metadata.get_key(this.object);
72 this.canonical_key = (keys && keys.length == 1) ? keys[0] : undefined;
74 this.init_key = this.options.init_key;
75 // have init_key default to canonical_key
76 this.init_key = this.init_key || this.canonical_key;
78 if ( ! this.init_key ) messages.warning ("QueryGrid : cannot find init_key");
79 if ( ! this.canonical_key ) messages.warning ("QueryGrid : cannot find canonical_key");
80 if (debug) messages.debug("querygrid: canonical_key="+this.canonical_key+" init_key="+this.init_key);
82 /* Setup query and record handlers */
83 this.listen_query(options.query_uuid);
84 this.listen_query(options.query_all_uuid, 'all');
86 /* GUI setup and event binding */
87 this.initialize_table();
92 on_show: function(e) {
99 /* GUI MANIPULATION */
101 initialize_table: function() {
102 // compute columns based on columns and hidden_columns
103 this.slick_columns = [];
104 var all_columns = this.options.columns; // .concat(this.options.hidden_columns)
105 // xxx would be helpful to support a column_renamings options arg
106 // for redefining some labels like 'network_hrn' that really are not meaningful
107 for (c in all_columns) {
108 var column=all_columns[c];
109 this.slick_columns.push ( {id:column, name:column, field:column,
110 cssClass: "querygrid-column-"+column,
111 width:100, minWidth:40, });
113 var checkbox_selector = new Slick.CheckboxSelectColumn({
114 cssClass: "slick-checkbox"
116 this.slick_columns.push(checkbox_selector.getColumnDefinition());
118 // xxx should be extensible from caller with this.options.slickgrid_options
119 this.slick_options = {
120 enableCellNavigation: false,
121 enableColumnReorder: true,
123 syncColumnCellResize: true,
126 this.slick_data = [];
127 this.slick_dataview = new Slick.Data.UnfoldDataView();
128 // capturing for debug
129 window.dv=this.slick_dataview;
131 this.slick_dataview.onRowCountChanged.subscribe ( function (e,args) {
132 self.slick_grid.updateRowCount();
133 self.slick_grid.autosizeColumns();
134 self.slick_grid.render();
138 var selector="#grid-"+this.options.domid;
140 messages.debug("slick grid selector is " + selector);
141 for (c in this.slick_columns) {
142 var col=this.slick_columns[c];
144 for (k in col) msg = msg+" col["+k+"]="+col[k];
145 messages.debug("slick_column["+c+"]:"+msg);
149 this.slick_grid = new Slick.Grid(selector, this.slick_dataview, this.slick_columns, this.slick_options);
150 // this.slick_grid.setSelectionModel (new Slick.RowSelectionModel ({selectActiveRow: false}));
151 this.slick_grid.setSelectionModel (new Slick.UnfoldSelectionModel({selectActiveRow: false}));
152 this.slick_grid.registerPlugin (checkbox_selector);
153 // autotooltips: for showing the full column name when ellipsed
154 var auto_tooltips = new Slick.AutoTooltips ({ enableForHeaderCells: true });
155 this.slick_grid.registerPlugin (auto_tooltips);
157 this.columnpicker = new Slick.Controls.ColumnPicker (this.slick_columns, this.slick_grid, this.slick_options)
159 }, // initialize_table
161 new_record: function(record) {
162 this.slick_data.push(record);
165 clear_table: function() {
167 this.slick_dataview.setItems(this.slick_data,this.init_key,this.canonical_key);
170 redraw_table: function() {
171 this.slick_grid.autosizeColumns();
172 this.slick_grid.render();
175 show_column: function(field) {
176 console.log ("querygrid.show_column not yet implemented with slickgrid - field="+field);
179 hide_column: function(field) {
180 console.log("querygrid.hide_column not implemented with slickgrid - field="+field);
183 /*************************** QUERY HANDLER ****************************/
185 on_filter_added: function(filter) {
186 this.filters.push(filter);
190 on_filter_removed: function(filter) {
191 // Remove corresponding filters
192 this.filters = $.grep(this.filters, function(x) {
198 on_filter_clear: function() {
202 on_field_added: function(field) {
203 this.show_column(field);
206 on_field_removed: function(field) {
207 this.hide_column(field);
210 on_field_clear: function() {
211 alert('QueryGrid::clear_fields() not implemented');
214 /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
215 /*************************** ALL QUERY HANDLER ****************************/
217 on_all_filter_added: function(filter) {
222 on_all_filter_removed: function(filter) {
227 on_all_filter_clear: function() {
232 on_all_field_added: function(field) {
233 this.show_column(field);
236 on_all_field_removed: function(field) {
237 this.hide_column(field);
240 on_all_field_clear: function() {
241 alert('QueryGrid::clear_fields() not implemented');
245 /*************************** RECORD HANDLER ***************************/
247 on_new_record: function(record) {
248 if (this.received_all_query) {
249 // if the 'all' query has been dealt with already we may turn on the checkbox
250 this._set_checkbox_from_record(record, true);
252 // otherwise we need to remember that and do it later on
253 if (debug) messages.debug("Remembering record to check, "+this.init_key+'='+ record[this.init_key]);
254 this.buffered_records_to_check.push(record);
258 on_clear_records: function() {
261 // Could be the default in parent
262 on_query_in_progress: function() {
266 on_query_done: function() {
267 this.received_query = true;
268 // unspin once we have received both
269 if (this.received_all_query && this.received_query) {
270 this._init_checkboxes();
275 on_field_state_changed: function(data) {
276 switch(data.request) {
277 case FIELD_REQUEST_ADD:
278 case FIELD_REQUEST_ADD_RESET:
279 this._set_checkbox_from_data(data.value, true);
281 case FIELD_REQUEST_REMOVE:
282 case FIELD_REQUEST_REMOVE_RESET:
283 this._set_checkbox_from_data(data.value, false);
290 /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
292 on_all_field_state_changed: function(data) {
293 switch(data.request) {
294 case FIELD_REQUEST_ADD:
295 case FIELD_REQUEST_ADD_RESET:
296 this._set_checkbox_from_data(data.value, true);
298 case FIELD_REQUEST_REMOVE:
299 case FIELD_REQUEST_REMOVE_RESET:
300 this._set_checkbox_from_data(data.value, false);
307 on_all_new_record: function(record) {
308 this.new_record(record);
311 on_all_clear_records: function() {
316 on_all_query_in_progress: function() {
319 }, // on_all_query_in_progress
321 on_all_query_done: function() {
322 var start=new Date();
323 if (debug) messages.debug("1-shot initializing slickgrid content with " + this.slick_data.length + " lines");
324 // use this.init_key as the key for identifying rows
325 this.slick_dataview.setItems (this.slick_data, this.init_key,this.canonical_key);
326 var duration=new Date()-start;
327 if (debug) messages.debug("setItems " + duration + " ms");
329 // show full contents of first row app
330 for (k in this.slick_data[0]) messages.debug("slick_data[0]["+k+"]="+this.slick_data[0][k]);
334 // if we've already received the slice query, we have not been able to set
335 // checkboxes on the fly at that time (dom not yet created)
336 $.each(this.buffered_records_to_check, function(i, record) {
337 if (debug) messages.debug ("delayed turning on checkbox " + i + " record= " + record);
338 self._set_checkbox_from_record(record, true);
340 this.buffered_records_to_check = [];
342 this.received_all_query = true;
343 // unspin once we have received both
344 if (this.received_all_query && this.received_query) {
345 this._init_checkboxes();
349 }, // on_all_query_done
351 /************************** PRIVATE METHODS ***************************/
353 _set_checkbox_from_record : function(record, checked) {
354 var init_id = record[this.init_key];
355 if (debug) messages.debug("querygrid.set_checkbox_from_record, init_id="+init_id);
356 var index = this.slick_dataview.getIdxById(init_id);
357 this._set_checkbox_from_index (index,checked);
360 _set_checkbox_from_data : function (id, checked) {
361 if (debug) messages.debug("querygrid.set_checkbox_from_data, id="+id);
362 // this is a local addition to mainstream dataview
363 // it's kind if slow in this first implementation (no hashing)
364 // but we should not notice that much
365 var index = this.slick_dataview.getIdxByIdKey(id,this.canonical_key);
366 this._set_checkbox_from_index (index,checked);
369 _set_checkbox_from_index : function (index, checked) {
370 if (index === undefined) { messages.warn("querygrid.set_checkbox - cannot find index"); return;}
371 if (checked === undefined) checked = true;
372 var selectedRows=this.slick_grid.getSelectedRows();
373 if (checked) // add index in current list
374 selectedRows=selectedRows.concat(index);
375 else // remove index from current list
376 selectedRows=selectedRows.filter(function(idx) {return idx!=index;});
378 this.slick_grid.setSelectedRows(selectedRows);
381 // initializing checkboxes
382 // have tried 2 approaches, but none seems to work as we need it
383 // issue summarized in here
384 // http://stackoverflow.com/questions/20425193/slickgrid-selection-changed-callback-how-to-tell-between-manual-and-programmat
385 // arm the click callback on checkboxes
386 _init_checkboxes_manual : function () {
387 // xxx looks like checkboxes can only be the last column??
388 var checkbox_col = this.slick_grid.getColumns().length-1; // -1 +1 =0
389 console.log ("checkbox_col="+checkbox_col);
391 console.log ("HERE 1 with "+this.slick_dataview.getLength()+" sons");
392 for (var index=0; index < this.slick_dataview.getLength(); index++) {
393 // retrieve key (i.e. hrn) for this line
394 var key=this.slick_dataview.getItem(index)[this.key];
395 // locate cell <div> for the checkbox
396 var div=this.slick_grid.getCellNode(index,checkbox_col);
397 if (index <=30) console.log("HERE2 div",div," index="+index+" col="+checkbox_col);
398 // arm callback on single son of <div> that is the <input>
399 $(div).children("input").each(function () {
400 if (index<=30) console.log("HERE 3, index="+index+" key="+key);
401 $(this).click(function() {self._checkbox_clicked(self,this,key);});
406 // onSelectedRowsChanged will fire even when
407 _init_checkboxes : function () {
408 console.log("_init_checkboxes");
409 var grid=this.slick_grid;
410 this.slick_grid.onSelectedRowsChanged.subscribe(function(){
411 row_ids = grid.getSelectedRows();
412 console.log(row_ids);
416 // the callback for when user clicks
417 _checkbox_clicked: function(querygrid,input,key) {
418 // XXX this.value = key of object to be added... what about multiple keys ?
419 if (debug) messages.debug("querygrid click handler checked=" + input.checked + " key=" + key);
420 manifold.raise_event(querygrid.options.query_uuid, input.checked?SET_ADD:SET_REMOVED, key);
421 //return false; // prevent checkbox to be checked, waiting response from manifold plugin api
425 // xxx from this and down, probably needs further tweaks for slickgrid
427 _querygrid_filter: function(oSettings, aData, iDataIndex) {
429 $.each (this.filters, function(index, filter) {
430 /* XXX How to manage checkbox ? */
433 var value = filter[2];
435 /* Determine index of key in the table columns */
436 var col = $.map(oSettings.aoColumns, function(x, i) {if (x.sTitle == key) return i;})[0];
438 /* Unknown key: no filtering */
439 if (typeof(col) == 'undefined')
442 col_value=unfold.get_value(aData[col]);
443 /* Test whether current filter is compatible with the column */
444 if (op == '=' || op == '==') {
445 if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a")
447 }else if (op == '!=') {
448 if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a")
451 if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a")
454 if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a")
456 } else if(op=='<=' || op=='≤') {
457 if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a")
459 } else if(op=='>=' || op=='≥') {
460 if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a")
463 // How to break out of a loop ?
464 alert("filter not supported");
472 _selectAll: function() {
473 // requires jQuery id
474 var uuid=this.id.split("-");
475 var oTable=$("#querygrid-"+uuid[1]).dataTable();
476 // Function available in QueryGrid 1.9.x
477 // Filter : displayed data only
478 var filterData = oTable._('tr', {"filter":"applied"});
479 /* TODO: WARNING if too many nodes selected, use filters to reduce nuber of nodes */
480 if(filterData.length<=100){
481 $.each(filterData, function(index, obj) {
482 var last=$(obj).last();
483 var key_value=unfold.get_value(last[0]);
484 if(typeof($(last[0]).attr('checked'))=="undefined"){
485 $.publish('selected', 'add/'+key_value);
493 $.plugin('QueryGrid', QueryGrid);