extremely rough sketch of querytable using slickgrid
authorThierry Parmentelat <thierry.parmentelat@inria.fr>
Fri, 29 Nov 2013 17:13:57 +0000 (18:13 +0100)
committerThierry Parmentelat <thierry.parmentelat@inria.fr>
Fri, 29 Nov 2013 17:13:57 +0000 (18:13 +0100)
plugins/querytable/__init__.py
plugins/querytable/static/css/querytable.css
plugins/querytable/static/js/querytable.js
plugins/querytable/templates/querytable.html

index b7b92f4..cafba03 100644 (file)
@@ -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',]
index edbc683..0ac0055 100644 (file)
@@ -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;
-}
index 9d7bced..45d12aa 100644 (file)
@@ -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 */
             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);
 
         /* 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("<span class='glyphicon glyphicon-ok' style='float:right;display:inline-block;'></span>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
             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('<a href="../resource/'+record['urn']+'"><span class="glyphicon glyphicon-search"></span></a> '+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);
                 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;
 
 
         /*************************** 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;
             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);
             }
         },
 
-        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:
 
         /* 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:
             }
         },
 
-        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 
         /** 
          * @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 ? */
             return ret;
         },
 
-        _querytable_draw_callback: function()
-        {
+        _querytable_draw_callback: function() {
             /* 
              * Handle clicks on checkboxes: reassociate checkbox click every time
              * the table is redrawn 
             }
         },
 
-        _check_click: function(e) 
-        {
+        _check_click: function(e) {
             e.stopPropagation();
 
             var self = e.data;
             
         },
 
-        _selectAll: function() 
-        {
+        _selectAll: function() {
             // requires jQuery id
             var uuid=this.id.split("-");
             var oTable=$("#querytable-"+uuid[1]).dataTable();
 
     $.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);
 
index 902c55c..7b885c8 100644 (file)
@@ -1,20 +1,3 @@
-<div id='main-{{ domid }}' class='querytable-spacer'>
-  <table class='table table-striped table-bordered dataTable' id='{{domid}}__table'>
-    <thead>
-      <tr>
-        {% for column in columns %} <th>{{ column }}</th> {% endfor %} 
-        {% for column in hidden_columns %} <th>{{ column }}</th> {% endfor %} 
-        {% if checkboxes %} <th class="checkbox">+/-</th> {% endif %}
-      </tr>
-    </thead> 
-    <tbody>
-    </tbody>
-    <tfoot>
-      <tr>
-        {% for column in columns %} <th>{{ column }}</th> {% endfor %} 
-        {% for column in hidden_columns %} <th>{{ column }}</th> {% endfor %} 
-        {% if checkboxes %} <th>+/-</th> {% endif %}
-      </tr>
-    </tfoot> 
-  </table>
+<div id='spacer-{{ domid }}' class='querytable-spacer'>
+<div class="querytable" id="grid-{{ domid }}"></div>
 </div>