X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=plugins%2Fquerytable%2Fstatic%2Fjs%2Fquerytable.js;h=bca90c3551224ea35410d498f680ae2b990ffe3f;hb=8c82aaebbe810904dd985aacf92ebc9eab3fd5f5;hp=e0ac3fd0d6f7be1fdf6cff73035f3c7c50559013;hpb=8be9b358a544e1ea9e53ad40495fa42e9d5edf9a;p=myslice.git diff --git a/plugins/querytable/static/js/querytable.js b/plugins/querytable/static/js/querytable.js index e0ac3fd0..bca90c35 100644 --- a/plugins/querytable/static/js/querytable.js +++ b/plugins/querytable/static/js/querytable.js @@ -1,18 +1,34 @@ /** - * Description: display a query result in a datatables-powered + * Description: display a query result in a slickgrid-powered
* Copyright (c) 2012-2013 UPMC Sorbonne Universite - INRIA * License: GPLv3 */ -(function($){ +/* ongoing adaptation to slickgrid + still missing are +. checkboxes really running properly +. ability to sort on columns (should be straightforward + IIRC this got broken when moving to dataview, see dataview doc +. ability to sort on the checkboxes column + (e.g. have resources 'in' the slice show up first) + not quite clear how to do this +. searching +. filtering +. style improvement +. rendering in the sliceview - does not use up all space, + this is different from the behaviour with simpleview +*/ + +(function($) { var debug=false; -// debug=true + debug=true + var debug_deep=false; +// debug_deep=true; var QueryTable = Plugin.extend({ - init: function(options, element) - { + init: function(options, element) { this._super(options, element); /* Member variables */ @@ -23,26 +39,30 @@ this.received_all_query = false; this.received_query = false; - // We need to remember the active filter for datatables filtering - this.filters = Array(); +// // We need to remember the active filter for filtering +// this.filters = Array(); // an internal buffer for records that are 'in' and thus need to be checked this.buffered_records_to_check = []; - // an internal buffer for keeping lines and display them in one call to fnAddData - this.buffered_lines = []; - /* XXX Events XXX */ - // this.$element.on('show.Datatables', this.on_show); + /* Events */ this.elmt().on('show', this, this.on_show); - // Unbind all events using namespacing - // TODO in destructor - // $(window).unbind('QueryTable'); var query = manifold.query_store.find_analyzed_query(this.options.query_uuid); this.method = query.object; - var keys = manifold.metadata.get_key(this.method); - this.key = (keys && keys.length == 1) ? keys[0] : null; + // xxx beware that this.key needs to contain a key that all records will have + // in general query_all will return well populated records, but query + // returns records with only the fields displayed on startup. + this.key = (this.options.id_key); + if (! this.key) { + // if not specified by caller, decide from metadata + var keys = manifold.metadata.get_key(this.method); + this.key = (keys && keys.length == 1) ? keys[0] : null; + } + if (! this.key) messages.warning("querytable.init could not kind valid key"); + + if (debug) messages.debug("querytable: key="+this.key); /* Setup query and record handlers */ this.listen_query(options.query_uuid); @@ -54,243 +74,103 @@ /* PLUGIN EVENTS */ - on_show: function(e) - { + on_show: function(e) { var self = e.data; - - self.table.fnAdjustColumnSizing() - - /* Refresh dataTabeles if click on the menu to display it : fix dataTables 1.9.x Bug */ - /* temp disabled... useful ? -- jordan - $(this).each(function(i,elt) { - if (jQuery(elt).hasClass('dataTables')) { - var myDiv=jQuery('#querytable-' + this.id).parent(); - if(myDiv.height()==0) { - var oTable=$('#querytable-' + this.id).dataTable(); - oTable.fnDraw(); - } - } - }); - */ + self.redraw_table(); }, // on_show /* GUI EVENTS */ /* GUI MANIPULATION */ - initialize_table: function() - { - /* Transforms the table into DataTable, and keep a pointer to it */ - var self = this; - var actual_options = { - // Customize the position of Datatables elements (length,filter,button,...) - // we use a fluid row on top and another on the bottom, making sure we take 12 grid elt's each time - sDom: "<'row'<'col-xs-5'l><'col-xs-1'r><'col-xs-6'f>>t<'row'<'col-xs-5'i><'col-xs-7'p>>", - // XXX as of sept. 2013, I cannot locate a bootstrap3-friendly mode for now - // hopefully this would come with dataTables v1.10 ? - // in any case, search for 'sPaginationType' all over the code for more comments - sPaginationType: 'bootstrap', - // Handle the null values & the error : Datatables warning Requested unknown parameter - // http://datatables.net/forums/discussion/5331/datatables-warning-...-requested-unknown-parameter/p2 - aoColumnDefs: [{sDefaultContent: '',aTargets: [ '_all' ]}], - // WARNING: this one causes tables in a 'tabs' that are not exposed at the time this is run to show up empty - // sScrollX: '100%', /* Horizontal scrolling */ - bProcessing: true, /* Loading */ - fnDrawCallback: function() { self._querytable_draw_callback.call(self); } - // XXX use $.proxy here ! - }; - // the intention here is that options.datatables_options as coming from the python object take precedence - // xxx DISABLED by jordan: was causing errors in datatables.js - // xxx turned back on by Thierry - this is the code that takes python-provided options into account - // check your datatables_options tag instead - // however, we have to accumulate in aoColumnDefs from here (above) - // and from the python wrapper (checkboxes management, plus any user-provided aoColumnDefs) - if ( 'aoColumnDefs' in this.options.datatables_options) { - actual_options['aoColumnDefs']=this.options.datatables_options['aoColumnDefs'].concat(actual_options['aoColumnDefs']); - delete this.options.datatables_options['aoColumnDefs']; + initialize_table: function() { + // compute columns based on columns and hidden_columns + this.slick_columns = []; + var all_columns = this.options.columns; // .concat(this.options.hidden_columns) + // xxx would be helpful to support a column_renamings options arg + // for redefining some labels like 'network_hrn' that really are not meaningful + for (c in all_columns) { + var column=all_columns[c]; + this.slick_columns.push ( {id:column, name:column, field:column, + cssClass: "querytable-column-"+column, + width:100, minWidth:40, }); + } + var checkbox_selector = new Slick.CheckboxSelectColumn({ + cssClass: "slick-checkbox" + }); + this.slick_columns.push(checkbox_selector.getColumnDefinition()); + + // xxx should be extensible from caller with this.options.slickgrid_options + this.slick_options = { + enableCellNavigation: false, + enableColumnReorder: true, + showHeaderRow: true, + syncColumnCellResize: true, + }; + + this.slick_data = []; + this.slick_dataview = new Slick.Data.DataView(); + var self=this; + this.slick_dataview.onRowCountChanged.subscribe ( function (e,args) { + self.slick_grid.updateRowCount(); + self.slick_grid.autosizeColumns(); + self.slick_grid.render(); + }); + + var selector="#grid-"+this.options.domid; + if (debug_deep) { + messages.debug("slick grid selector is " + selector); + for (c in this.slick_columns) { + var col=this.slick_columns[c]; + var msg=""; + for (k in col) msg = msg+" col["+k+"]="+col[k]; + messages.debug("slick_column["+c+"]:"+msg); + } } - $.extend(actual_options, this.options.datatables_options ); - this.table = this.elmt('table').dataTable(actual_options); - - /* Setup the SelectAll button in the dataTable header */ - /* xxx not sure this is still working */ - var oSelectAll = $('#datatableSelectAll-'+ this.options.plugin_uuid); - oSelectAll.html("Select All"); - oSelectAll.button(); - oSelectAll.css('font-size','11px'); - oSelectAll.css('float','right'); - oSelectAll.css('margin-right','15px'); - oSelectAll.css('margin-bottom','5px'); - oSelectAll.unbind('click'); - oSelectAll.click(this._selectAll); - - /* Add a filtering function to the current table - * Note: we use closure to get access to the 'options' - */ - $.fn.dataTableExt.afnFiltering.push(function( oSettings, aData, iDataIndex ) { - /* No filtering if the table does not match */ - if (oSettings.nTable.id != self.options.plugin_uuid + '__table') - return true; - return self._querytable_filter.call(self, oSettings, aData, iDataIndex); - }); - /* Processing hidden_columns */ - $.each(this.options.hidden_columns, function(i, field) { - //manifold.raise_event(self.options.query_all_uuid, FIELD_REMOVED, field); - self.hide_column(field); - }); - }, // initialize_table + this.slick_grid = new Slick.Grid(selector, this.slick_dataview, this.slick_columns, this.slick_options); + this.slick_grid.setSelectionModel (new Slick.RowSelectionModel ({selectActiveRow: false})); + this.slick_grid.registerPlugin (checkbox_selector); + // autotooltips: for showing the full column name when ellipsed + var auto_tooltips = new Slick.AutoTooltips ({ enableForHeaderCells: true }); + this.slick_grid.registerPlugin (auto_tooltips); + + this.columnpicker = new Slick.Controls.ColumnPicker (this.slick_columns, this.slick_grid, this.slick_options) - /** - * @brief Determine index of key in the table columns - * @param key - * @param cols - */ - getColIndex: function(key, cols) { - var tabIndex = $.map(cols, function(x, i) { if (x.sTitle == key) return i; }); - return (tabIndex.length > 0) ? tabIndex[0] : -1; - }, // getColIndex - - checkbox_html : function (key, value) - { -// if (debug) messages.debug("checkbox_html, value="+value); - var result=""; - // Prefix id with plugin_uuid - result += " '+record['hrn']); - } else { - if (record[colnames[j]]) - line.push(record[colnames[j]]); - else - line.push(''); - } - } - - // catch up with the last column if checkboxes were requested - if (this.options.checkboxes) { - // Use a key instead of hostname (hard coded...) - line.push(this.checkbox_html(this.key, record[this.key])); - } - - // adding an array in one call is *much* more efficient - // this.table.fnAddData(line); - this.buffered_lines.push(line); - }, + g=this.slick_grid; - clear_table: function() - { - this.table.fnClearTable(); - }, + }, // initialize_table - redraw_table: function() - { - this.table.fnDraw(); + new_record: function(record) { + this.slick_data.push(record); }, - show_column: function(field) - { - var oSettings = this.table.fnSettings(); - var cols = oSettings.aoColumns; - var index = this.getColIndex(field,cols); - if (index != -1) - this.table.fnSetColumnVis(index, true); + clear_table: function() { + this.slick_data=[]; + this.slick_dataview.setItems(this.slick_data,this.key); }, - hide_column: function(field) - { - var oSettings = this.table.fnSettings(); - var cols = oSettings.aoColumns; - var index = this.getColIndex(field,cols); - if (index != -1) - this.table.fnSetColumnVis(index, false); + redraw_table: function() { + this.slick_grid.autosizeColumns(); + this.slick_grid.render(); }, - set_checkbox: function(record, checked) - { - /* Default: checked = true */ - if (checked === undefined) checked = true; - - var key_value; - /* The function accepts both records and their key */ - switch (manifold.get_type(record)) { - case TYPE_VALUE: - key_value = record; - break; - case TYPE_RECORD: - /* XXX Test the key before ? */ - key_value = record[this.key]; - break; - default: - throw "Not implemented"; - break; - } - + show_column: function(field) { + console.log ("querytable.show_column not yet implemented with slickgrid - field="+field); + }, - if (key_value === undefined) { - messages.warning("querytable.set_checkbox has no value to figure which line to tick"); - return; - } - var checkbox_id = this.flat_id(this.id('checkbox', key_value)); - // function escape_id(myid) is defined in portal/static/js/common.functions.js - checkbox_id = escape_id(checkbox_id); - // using dataTables's $ to search also in nodes that are not currently displayed - var element = this.table.$(checkbox_id); - if (debug) - messages.debug("set_checkbox checked=" + checked - + " id=" + checkbox_id + " matches=" + element.length); - element.attr('checked', checked); + hide_column: function(field) { + console.log("querytable.hide_column not implemented with slickgrid - field="+field); }, /*************************** QUERY HANDLER ****************************/ - on_filter_added: function(filter) - { + on_filter_added: function(filter) { this.filters.push(filter); this.redraw_table(); }, - on_filter_removed: function(filter) - { + on_filter_removed: function(filter) { // Remove corresponding filters this.filters = $.grep(this.filters, function(x) { return x != filter; @@ -298,71 +178,59 @@ this.redraw_table(); }, - on_filter_clear: function() - { - // XXX + on_filter_clear: function() { this.redraw_table(); }, - on_field_added: function(field) - { + on_field_added: function(field) { this.show_column(field); }, - on_field_removed: function(field) - { + on_field_removed: function(field) { this.hide_column(field); }, - on_field_clear: function() - { + on_field_clear: function() { alert('QueryTable::clear_fields() not implemented'); }, /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */ /*************************** ALL QUERY HANDLER ****************************/ - on_all_filter_added: function(filter) - { + on_all_filter_added: function(filter) { // XXX this.redraw_table(); }, - on_all_filter_removed: function(filter) - { + on_all_filter_removed: function(filter) { // XXX this.redraw_table(); }, - on_all_filter_clear: function() - { + on_all_filter_clear: function() { // XXX this.redraw_table(); }, - on_all_field_added: function(field) - { + on_all_field_added: function(field) { this.show_column(field); }, - on_all_field_removed: function(field) - { + on_all_field_removed: function(field) { this.hide_column(field); }, - on_all_field_clear: function() - { + on_all_field_clear: function() { alert('QueryTable::clear_fields() not implemented'); }, /*************************** RECORD HANDLER ***************************/ - on_new_record: function(record) - { + on_new_record: function(record) { if (this.received_all_query) { // if the 'all' query has been dealt with already we may turn on the checkbox - this.set_checkbox(record, true); + this._set_checkbox(record, true); } else { // otherwise we need to remember that and do it later on if (debug) messages.debug("Remembering record to check " + record[this.key]); @@ -370,33 +238,32 @@ } }, - on_clear_records: function() - { + on_clear_records: function() { }, // Could be the default in parent - on_query_in_progress: function() - { + on_query_in_progress: function() { this.spin(); }, - on_query_done: function() - { + on_query_done: function() { this.received_query = true; // unspin once we have received both - if (this.received_all_query && this.received_query) this.unspin(); + if (this.received_all_query && this.received_query) { + this._init_checkboxes(); + this.unspin(); + } }, - on_field_state_changed: function(data) - { + on_field_state_changed: function(data) { switch(data.request) { case FIELD_REQUEST_ADD: case FIELD_REQUEST_ADD_RESET: - this.set_checkbox(data.value, true); + this._set_checkbox(data.value, true); break; case FIELD_REQUEST_REMOVE: case FIELD_REQUEST_REMOVE_RESET: - this.set_checkbox(data.value, false); + this._set_checkbox(data.value, false); break; default: break; @@ -405,67 +272,147 @@ /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */ // all - on_all_field_state_changed: function(data) - { + on_all_field_state_changed: function(data) { switch(data.request) { case FIELD_REQUEST_ADD: case FIELD_REQUEST_ADD_RESET: - this.set_checkbox(data.value, true); + this._set_checkbox(data.value, true); break; case FIELD_REQUEST_REMOVE: case FIELD_REQUEST_REMOVE_RESET: - this.set_checkbox(data.value, false); + this._set_checkbox(data.value, false); break; default: break; } }, - on_all_new_record: function(record) - { + on_all_new_record: function(record) { this.new_record(record); }, - on_all_clear_records: function() - { + on_all_clear_records: function() { this.clear_table(); }, - on_all_query_in_progress: function() - { + on_all_query_in_progress: function() { // XXX parent this.spin(); }, // on_all_query_in_progress - on_all_query_done: function() - { - if (debug) messages.debug("1-shot initializing dataTables content with " + this.buffered_lines.length + " lines"); - this.table.fnAddData (this.buffered_lines); - this.buffered_lines=[]; + on_all_query_done: function() { + var start=new Date(); + if (debug) messages.debug("1-shot initializing slickgrid content with " + this.slick_data.length + " lines"); + // use this.key as the key for identifying rows + this.slick_dataview.setItems (this.slick_data, this.key); + var duration=new Date()-start; + if (debug) messages.debug("setItems " + duration + " ms"); + if (debug_deep) { + // show full contents of first row app + for (k in this.slick_data[0]) messages.debug("slick_data[0]["+k+"]="+this.slick_data[0][k]); + } var self = this; // if we've already received the slice query, we have not been able to set // checkboxes on the fly at that time (dom not yet created) $.each(this.buffered_records_to_check, function(i, record) { if (debug) messages.debug ("delayed turning on checkbox " + i + " record= " + record); - self.set_checkbox(record, true); + self._set_checkbox(record, true); }); this.buffered_records_to_check = []; this.received_all_query = true; // unspin once we have received both - if (this.received_all_query && this.received_query) this.unspin(); + if (this.received_all_query && this.received_query) { + this._init_checkboxes(); + this.unspin(); + } }, // on_all_query_done /************************** PRIVATE METHODS ***************************/ - /** - * @brief QueryTable filtering function - */ - _querytable_filter: function(oSettings, aData, iDataIndex) - { + _set_checkbox: function(record, checked) { + /* Default: checked = true */ + if (checked === undefined) checked = true; + + var id; + /* The function accepts both records and their key */ + switch (manifold.get_type(record)) { + case TYPE_VALUE: + id = record; + break; + case TYPE_RECORD: + /* XXX Test the key before ? */ + id = record[this.key]; + break; + default: + throw "Not implemented"; + break; + } + + + if (id === undefined) { + messages.warning("querytable._set_checkbox record has no id to figure which line to tick"); + return; + } + var index = this.slick_dataview.getIdxById(id); + var selectedRows=this.slick_grid.getSelectedRows(); + if (checked) // add index in current list + selectedRows=selectedRows.concat(index); + else // remove index from current list + selectedRows=selectedRows.filter(function(idx) {return idx!=index;}); + this.slick_grid.setSelectedRows(selectedRows); + }, + +// initializing checkboxes +// have tried 2 approaches, but none seems to work as we need it +// issue summarized in here +// http://stackoverflow.com/questions/20425193/slickgrid-selection-changed-callback-how-to-tell-between-manual-and-programmat + // arm the click callback on checkboxes + _init_checkboxes_manual : function () { + // xxx looks like checkboxes can only be the last column?? + var checkbox_col = this.slick_grid.getColumns().length-1; // -1 +1 =0 + console.log ("checkbox_col="+checkbox_col); + var self=this; + console.log ("HERE 1 with "+this.slick_dataview.getLength()+" sons"); + for (var index=0; index < this.slick_dataview.getLength(); index++) { + // retrieve key (i.e. hrn) for this line + var key=this.slick_dataview.getItem(index)[this.key]; + // locate cell
for the checkbox + var div=this.slick_grid.getCellNode(index,checkbox_col); + if (index <=30) console.log("HERE2 div",div," index="+index+" col="+checkbox_col); + // arm callback on single son of
that is the + $(div).children("input").each(function () { + if (index<=30) console.log("HERE 3, index="+index+" key="+key); + $(this).click(function() {self._checkbox_clicked(self,this,key);}); + }); + } + }, + + // onSelectedRowsChanged will fire even when + _init_checkboxes : function () { + console.log("_init_checkboxes"); + var grid=this.slick_grid; + this.slick_grid.onSelectedRowsChanged.subscribe(function(){ + row_ids = grid.getSelectedRows(); + console.log(row_ids); + }); + }, + + // the callback for when user clicks + _checkbox_clicked: function(querytable,input,key) { + // XXX this.value = key of object to be added... what about multiple keys ? + if (debug) messages.debug("querytable click handler checked=" + input.checked + " key=" + key); + manifold.raise_event(querytable.options.query_uuid, input.checked?SET_ADD:SET_REMOVED, key); + //return false; // prevent checkbox to be checked, waiting response from manifold plugin api + + }, + + // xxx from this and down, probably needs further tweaks for slickgrid + + _querytable_filter: function(oSettings, aData, iDataIndex) { var ret = true; $.each (this.filters, function(index, filter) { /* XXX How to manage checkbox ? */ @@ -510,51 +457,7 @@ return ret; }, - _querytable_draw_callback: function() - { - /* - * Handle clicks on checkboxes: reassociate checkbox click every time - * the table is redrawn - */ - this.elts('querytable-checkbox').unbind('click').click(this, this._check_click); - - if (!this.table) - return; - - /* Remove pagination if we show only a few results */ - var wrapper = this.table; //.parent().parent().parent(); - var rowsPerPage = this.table.fnSettings()._iDisplayLength; - var rowsToShow = this.table.fnSettings().fnRecordsDisplay(); - var minRowsPerPage = this.table.fnSettings().aLengthMenu[0]; - - if ( rowsToShow <= rowsPerPage || rowsPerPage == -1 ) { - $('.querytable_paginate', wrapper).css('visibility', 'hidden'); - } else { - $('.querytable_paginate', wrapper).css('visibility', 'visible'); - } - - if ( rowsToShow <= minRowsPerPage ) { - $('.querytable_length', wrapper).css('visibility', 'hidden'); - } else { - $('.querytable_length', wrapper).css('visibility', 'visible'); - } - }, - - _check_click: function(e) - { - e.stopPropagation(); - - var self = e.data; - - // XXX this.value = key of object to be added... what about multiple keys ? - if (debug) messages.debug("querytable click handler checked=" + this.checked + " hrn=" + this.value); - manifold.raise_event(self.options.query_uuid, this.checked?SET_ADD:SET_REMOVED, this.value); - //return false; // prevent checkbox to be checked, waiting response from manifold plugin api - - }, - - _selectAll: function() - { + _selectAll: function() { // requires jQuery id var uuid=this.id.split("-"); var oTable=$("#querytable-"+uuid[1]).dataTable(); @@ -577,16 +480,16 @@ $.plugin('QueryTable', QueryTable); - /* define the 'dom-checkbox' type for sorting in datatables - http://datatables.net/examples/plug-ins/dom_sort.html - using trial and error I found that the actual column number - was in fact given as a third argument, and not second - as the various online resources had it - go figure */ - $.fn.dataTableExt.afnSortData['dom-checkbox'] = function ( oSettings, _, iColumn ) { - return $.map( oSettings.oApi._fnGetTrNodes(oSettings), function (tr, i) { - return result=$('td:eq('+iColumn+') input', tr).prop('checked') ? '1' : '0'; - } ); - } +// /* define the 'dom-checkbox' type for sorting in datatables +// http://datatables.net/examples/plug-ins/dom_sort.html +// using trial and error I found that the actual column number +// was in fact given as a third argument, and not second +// as the various online resources had it - go figure */ +// $.fn.dataTableExt.afnSortData['dom-checkbox'] = function ( oSettings, _, iColumn ) { +// return $.map( oSettings.oApi._fnGetTrNodes(oSettings), function (tr, i) { +// return result=$('td:eq('+iColumn+') input', tr).prop('checked') ? '1' : '0'; +// } ); +// } })(jQuery);