From 120979bd0e438477dfeb702ed99d06a3d257b9af Mon Sep 17 00:00:00 2001 From: Thierry Parmentelat Date: Fri, 29 Nov 2013 18:13:57 +0100 Subject: [PATCH] extremely rough sketch of querytable using slickgrid --- plugins/querytable/__init__.py | 44 ++- plugins/querytable/static/css/querytable.css | 61 +--- plugins/querytable/static/js/querytable.js | 305 ++++++------------- plugins/querytable/templates/querytable.html | 21 +- 4 files changed, 125 insertions(+), 306 deletions(-) diff --git a/plugins/querytable/__init__.py b/plugins/querytable/__init__.py index b7b92f44..cafba030 100644 --- a/plugins/querytable/__init__.py +++ b/plugins/querytable/__init__.py @@ -57,6 +57,8 @@ Current implementation makes the following assumptions else: self.columns = [] self.hidden_columns = [] + # needs to be json-serializable, and sets are not + self.columns=list(self.columns) self.id_key=id_key self.datatables_options=datatables_options # if checkboxes were required, we tell datatables about this column's type @@ -84,20 +86,31 @@ Current implementation makes the following assumptions def requirements (self): reqs = { - 'js_files' : [ "js/spin.presets.js", "js/spin.min.js", "js/jquery.spin.js", - "js/dataTables.js", "js/dataTables.bootstrap.js", "js/with-datatables.js", - "js/manifold.js", "js/manifold-query.js", - "js/unfold-helper.js", - # querytable.js needs to be loaded after dataTables.js as it extends - # dataTableExt.afnSortData - "js/querytable.js", - ] , - 'css_files': [ "css/dataTables.bootstrap.css", - # hopefully temporary, when/if datatables supports sPaginationType=bootstrap3 - # for now we use full_numbers, with our own ad hoc css - "css/dataTables.full_numbers.css", - "css/querytable.css" , - ], + 'js_files' : [ + "js/spin.presets.js", "js/spin.min.js", "js/jquery.spin.js", + "http://mleibman.github.io/SlickGrid/lib/jquery.event.drag-2.2.js", + "http://mleibman.github.io/SlickGrid/slick.core.js", + "http://mleibman.github.io/SlickGrid/plugins/slick.cellrangedecorator.js", + "http://mleibman.github.io/SlickGrid/plugins/slick.cellrangeselector.js", + "http://mleibman.github.io/SlickGrid/plugins/slick.cellselectionmodel.js", + "http://mleibman.github.io/SlickGrid/slick.grid.js", +# "js/dataTables.js", "js/dataTables.bootstrap.js", "js/with-datatables.js", + "js/manifold.js", "js/manifold-query.js", + "js/unfold-helper.js", + # querytable.js needs to be loaded after dataTables.js as it extends + # dataTableExt.afnSortData + "js/querytable.js", + ] , + 'css_files': [ +# "css/dataTables.bootstrap.css", + # hopefully temporary, when/if datatables supports sPaginationType=bootstrap3 + # for now we use full_numbers, with our own ad hoc css +# "css/dataTables.full_numbers.css", + "css/querytable.css" , + "http://mleibman.github.io/SlickGrid/slick.grid.css", +# "http://mleibman.github.io/SlickGrid/css/smoothness/jquery-ui-1.8.16.custom.css", +# "http://mleibman.github.io/SlickGrid/examples/examples.css", + ], } return reqs @@ -106,4 +119,5 @@ Current implementation makes the following assumptions return ['plugin_uuid', 'domid', 'query_uuid', 'query_all_uuid', 'checkboxes', 'datatables_options', - 'hidden_columns', 'id_key',] + 'columns','hidden_columns', + 'id_key',] diff --git a/plugins/querytable/static/css/querytable.css b/plugins/querytable/static/css/querytable.css index edbc683e..0ac0055e 100644 --- a/plugins/querytable/static/css/querytable.css +++ b/plugins/querytable/static/css/querytable.css @@ -1,62 +1,13 @@ /* the bottom of the datatable needs more space */ div.querytable-spacer { padding: 8px 4px 15px 4px; } -div.QueryTable table.dataTable th { - font: bold 12px/22px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; - color: #4f6b72; - border-right: 1px solid #C1DAD7; - border-bottom: 1px solid #C1DAD7; - border-top: 1px solid #C1DAD7; - letter-spacing: 1px; - text-transform: uppercase; - text-align: left; - padding: 8px 12px 4px 20px; - vertical-align:middle; -/* background: #CAE8EA url(../img/tablesort-header.jpg) no-repeat; */ -} - -div.QueryTable table.dataTable th.checkbox { - padding-left: 14px; +div.querytable { + width: 100%; + height: 400px; } -div.QueryTable table.dataTable td, div.QueryTable table.dataTable textarea, div.QueryTable table.dataTable input [type="text"] { - font: normal 12px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; - border-right: 1px solid #C1DAD7; - border-bottom: 1px solid #C1DAD7; -} -div.QueryTable table.dataTable td { - padding: 4px 8px 4px 8px; - /* this applies on even rows only, odd ones have a setting in bootstrap of rbg 249.249.249 */ - background-color: #f4f4f4; -} -div.QueryTable table.dataTable td a { - font-weight:normal; -} -/* these come from bootstrap */ -div.QueryTable div.dataTables_info { - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +div.querytable { + font: bold 12px/22px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; + color: #4f6b72; } -/* one could think or using repeat-x here but that's not working because of the arrows - * we might need to make these wider some day - * and/or to add background-color: #caebea - * which would look less conspicuous in case of overflow -*/ - -div.QueryTable table.dataTable thead .sorting { background: url('../img/tablesort-header-sortable.png') no-repeat; } -div.QueryTable table.dataTable thead .sorting_asc { background: url('../img/tablesort-header-up.png') no-repeat; } -div.QueryTable table.dataTable thead .sorting_desc { background: url('../img/tablesort-header-down.png') no-repeat; } -/* this icons set does not have that exact equivalent - using an approximation for now */ -div.QueryTable table.dataTable thead .sorting_asc_disabled { background: url('../img/tablesort-header.png') repeat-x; } -div.QueryTable table.dataTable thead .sorting_desc_disabled { background: url('../img/tablesort-header.png') repeat-x; } - -/* the footers are not active */ -div.QueryTable table.dataTable tfoot { - background: url('../img/tablesort-header.png') repeat-x; - background-color: #caebea; -} -/* and when sorting is turned off it's useful to set this on header too */ -div.QueryTable table.dataTable thead { - background: url('../img/tablesort-header.png') repeat-x; - background-color: #caebea; -} diff --git a/plugins/querytable/static/js/querytable.js b/plugins/querytable/static/js/querytable.js index 9d7bced0..45d12aae 100644 --- a/plugins/querytable/static/js/querytable.js +++ b/plugins/querytable/static/js/querytable.js @@ -4,15 +4,14 @@ * License: GPLv3 */ -(function($){ +(function($) { var debug=false; -// debug=true + debug=true var QueryTable = Plugin.extend({ - init: function(options, element) - { + init: function(options, element) { this._super(options, element); /* Member variables */ @@ -23,13 +22,11 @@ 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 datatables 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); @@ -52,7 +49,7 @@ } if (! this.key) messages.warning("querytable.init could not kind valid key"); - messages.debug("querytable: key="+this.key); + if (debug) messages.debug("querytable: key="+this.key); /* Setup query and record handlers */ this.listen_query(options.query_uuid); @@ -64,105 +61,53 @@ /* 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(); - } - } - }); - */ }, // 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) + for (c in all_columns) { + var column=all_columns[c]; + this.slick_columns.push ( {id:column, name:column, field:column }); } - $.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); - }); + // xxx should be extensible from caller with this.options.slickgrid_options + this.slick_options = { + enableCellNavigation: false, + enableColumnReorder: true, + }; + + this.slick_data=[]; + + var selector="#grid-"+this.options.domid; + if (debug) { + 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); + } + } + this.slick_grid = new Slick.Grid(selector, this.slick_data, this.slick_columns, this.slick_options); + }, // initialize_table - /** - * @brief Determine index of key in the table columns - * @param key - * @param cols - */ + // Determine index of key in the table columns 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) - { + checkbox_html : function (key, value) { // if (debug) messages.debug("checkbox_html, value="+value); var result=""; // Prefix id with plugin_uuid @@ -181,64 +126,19 @@ return result; }, - - new_record: function(record) - { - // this models a line in dataTables, each element in the line describes a cell - line = new Array(); - - // go through table headers to get column names we want - // in order (we have temporarily hack some adjustments in names) - var cols = this.table.fnSettings().aoColumns; - var colnames = cols.map(function(x) {return x.sTitle}) - var nb_col = cols.length; - /* if we've requested checkboxes, then forget about the checkbox column for now */ - if (this.options.checkboxes) nb_col -= 1; - - /* fill in stuff depending on the column name */ - for (var j = 0; j < nb_col; j++) { - if (typeof colnames[j] == 'undefined') { - line.push('...'); - } else if (colnames[j] == 'hostname') { - if (record['type'] == 'resource,link') - //TODO: we need to add source/destination for links - line.push(''); - else - line.push(record['hostname']); - - } else if (colnames[j] == 'hrn' && typeof(record) != 'undefined') { - line.push(' '+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); + new_record: function(record) { + this.slick_data.push(record); }, - clear_table: function() - { - this.table.fnClearTable(); + clear_table: function() { + console.log("clear_table not implemented"); }, - redraw_table: function() - { + redraw_table: function() { this.table.fnDraw(); }, - show_column: function(field) - { + show_column: function(field) { var oSettings = this.table.fnSettings(); var cols = oSettings.aoColumns; var index = this.getColIndex(field,cols); @@ -246,17 +146,13 @@ this.table.fnSetColumnVis(index, true); }, - 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); + hide_column: function(field) { + console.log("hide_column not implemented - field="+field); }, - set_checkbox: function(record, checked) - { + set_checkbox: function(record, checked) { + console.log("set_checkbox not implemented"); + return; /* Default: checked = true */ if (checked === undefined) checked = true; @@ -293,14 +189,12 @@ /*************************** 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; @@ -308,68 +202,56 @@ 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); @@ -380,25 +262,21 @@ } }, - 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(); }, - on_field_state_changed: function(data) - { + on_field_state_changed: function(data) { switch(data.request) { case FIELD_REQUEST_ADD: case FIELD_REQUEST_ADD_RESET: @@ -415,8 +293,7 @@ /* 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: @@ -431,28 +308,26 @@ } }, - 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() { + if (debug) messages.debug("1-shot initializing dataTables content with " + this.slick_data.length + " lines"); + this.slick_grid.setData (this.slick_data, true); + if (debug) + for (k in this.slick_data[0]) messages.debug("slick_data[0]["+k+"]="+this.slick_data[0][k]); + this.slick_grid.render(); var self = this; // if we've already received the slice query, we have not been able to set @@ -474,8 +349,7 @@ /** * @brief QueryTable filtering function */ - _querytable_filter: function(oSettings, aData, iDataIndex) - { + _querytable_filter: function(oSettings, aData, iDataIndex) { var ret = true; $.each (this.filters, function(index, filter) { /* XXX How to manage checkbox ? */ @@ -520,8 +394,7 @@ return ret; }, - _querytable_draw_callback: function() - { + _querytable_draw_callback: function() { /* * Handle clicks on checkboxes: reassociate checkbox click every time * the table is redrawn @@ -550,8 +423,7 @@ } }, - _check_click: function(e) - { + _check_click: function(e) { e.stopPropagation(); var self = e.data; @@ -563,8 +435,7 @@ }, - _selectAll: function() - { + _selectAll: function() { // requires jQuery id var uuid=this.id.split("-"); var oTable=$("#querytable-"+uuid[1]).dataTable(); @@ -587,16 +458,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); diff --git a/plugins/querytable/templates/querytable.html b/plugins/querytable/templates/querytable.html index 902c55cf..7b885c80 100644 --- a/plugins/querytable/templates/querytable.html +++ b/plugins/querytable/templates/querytable.html @@ -1,20 +1,3 @@ -
- - - - {% for column in columns %} {% endfor %} - {% for column in hidden_columns %} {% endfor %} - {% if checkboxes %} {% endif %} - - - - - - - {% for column in columns %} {% endfor %} - {% for column in hidden_columns %} {% endfor %} - {% if checkboxes %} {% endif %} - - -
{{ column }}{{ column }}+/-
{{ column }}{{ column }}+/-
+
+
-- 2.43.0