googlemap and querytable now use 2 keys
authorThierry Parmentelat <thierry.parmentelat@inria.fr>
Mon, 9 Dec 2013 15:42:59 +0000 (16:42 +0100)
committerThierry Parmentelat <thierry.parmentelat@inria.fr>
Mon, 9 Dec 2013 15:42:59 +0000 (16:42 +0100)
* init_key, expected to be provided by caller, specifies a key that can be used at init-time (shoud be present in both queries)
* canonical_key is rderived from metadata

plugins/googlemap/static/js/googlemap.js
plugins/querygrid/static/js/querygrid.js
plugins/querytable/__init__.py
plugins/querytable/static/js/querytable.js
portal/sliceview.py
trash/simplegridview.py
trash/simpletableview.py

index 082a0c5..7346f73 100644 (file)
             this.in_set_backlog = [];
 
             // we keep a couple of global hashes
-               // lat_lon --> { marker, <ul> }
-               // id --> { <li>, <input> }
-               this.by_lat_lon = {};
-               this.by_id = {};
+           // lat_lon --> { marker, <ul> }
+           // id --> { <li>, <input> }
+           this.by_lat_lon = {};
+           // locating checkboxes by DOM selectors might be abstruse, as we cannot safely assume 
+           // all the items will belong under the toplevel <div>
+           this.by_id = {};
+           this.by_init_id = {};
 
             /* XXX Events */
             this.elmt().on('show', this, this.on_show);
             var query = manifold.query_store.find_analyzed_query(this.options.query_uuid);
             this.object = query.object;
 
-        //    var keys = manifold.metadata.get_key(this.object);
+           // see querytable.js for an explanation
+           var keys = manifold.metadata.get_key(this.object);
+           this.canonical_key = (keys && keys.length == 1) ? keys[0] : undefined;
            // 
-        //    this.key = (keys && keys.length == 1) ? keys[0] : null;
-
-           // xxx temporary hack
-           // as of nov. 28 2013 we have here this.key='urn', but in any place where
-           // the code tries to access record[this.key] the records only have
-           // keys=type,hrn,network_hrn,hostname
-           // so for now we force using hrn instead
-           // as soon as record have their primary key set this line can be removed
-           // see also same hack in querytable
-           //this.key= (this.key == 'urn') ? 'hrn' : this.key;
-        this.key = (this.options.id_key);
-        if (typeof(this.key)=='undefined' || (this.key).startsWith("unknown")) {
-            // if not specified by caller, decide from metadata
-            var keys = manifold.metadata.get_key(this.object);
-            this.key = (keys && keys.length == 1) ? keys[0] : null;
-        }
+           this.init_key = this.options.init_key;
+           // have init_key default to canonical_key
+           this.init_key = this.init_key || this.canonical_key;
+           // sanity check
+           if ( ! this.init_key ) messages.warning ("QueryTable : cannot find init_key");
+           if ( ! this.canonical_key ) messages.warning ("QueryTable : cannot find canonical_key");
+           if (debug) messages.debug("googlemap: canonical_key="+this.canonical_key+" init_key="+this.init_key);
 
             //// Setup query and record handlers 
            // this query is the one about the slice itself 
             this.infowindow = new google.maps.InfoWindow();
         }, // initialize_map
 
-        // The function accepts both records and their id
-       // record.key points to the name of the primary key for this record
-       // typically this is 'urn'
-       record_id : function (input) {
-            var id;
-            switch (manifold.get_type(input)) {
-            case TYPE_VALUE:
-               id = input;
-                break;
-            case TYPE_RECORD:
-               if ( ! this.key in input ) return;
-                id = input[this.key];
-                break;
-            default:
-                throw "googlemap.record_id: not implemented";
-                break;
-            }
-           return id;
-       },
-
        // return { marker: gmap_marker, ul : <ul DOM> }
        create_marker_struct: function (object,lat,lon) {
            // the DOM fragment
        // returns the created <input> element for further checkbox manipulation
        create_record_checkbox: function (record,ul,checked) {
            var checkbox = $("<input>", {type:'checkbox', checked:checked, class:'geo'});
-           var id=this.record_id(record);
-           // use hrn as far as possible for displaying
-           // var label= ('hrn' in record) ? record.hrn : id;
-           var label= (this.key in record) ? record[this.key] : id;
+           var id=record[this.canonical_key];
+           var init_id=record[this.init_key];
+           // xxx use init_key to find out label - or should we explicitly accept an incoming label_key ?
+           var label=init_id;
            ul.append($("<li>").addClass("geo").append(checkbox).
                      append($("<span>").addClass("geo").append(label)));
-           var googlemap=this;
+           // hash by id and by init_id 
+           this.by_id[id]=checkbox;
+            this.by_init_id[init_id] = checkbox;
+           //
            // the callback for when a user clicks
            // NOTE: this will *not* be called for changes done by program
+           var self=this;
            checkbox.change( function (e) {
-               manifold.raise_event (googlemap.options.query_uuid, this.checked ? SET_ADD : SET_REMOVED, id);
+               manifold.raise_event (self.options.query_uuid, this.checked ? SET_ADD : SET_REMOVED, id);
            });
            return checkbox;
        },
        },
            
        // retrieve DOM checkbox and make sure it is checked/unchecked
-    set_checkbox: function(record, checked) {
-           var id=this.record_id (record);
-           if (! id) { 
-               this.warning (record, "googlemap.set_checkbox: record has no id");
-               return; 
-           }
+       set_checkbox_from_record: function(record, checked) {
+           var init_id=record[this.init_key];
+           var checkbox = this.by_id [ init_id ];
+           checkbox.prop('checked',checked);
+       }, 
+
+       set_checkbox_from_data: function(id, checked) {
+           var id=record[this.canonical_key];
            var checkbox = this.by_id [ id ];
-           if (! checkbox ) { 
-               this.warning (record, "googlemap.set_checkbox: checkbox not found");
-               return; 
-           }
            checkbox.prop('checked',checked);
-     }, // set_checkbox
+       }, 
 
        // this record is *in* the slice
         new_record: function(record) {
            // this is where the checkbox will be appended
            var ul=marker_s.ul;
            var checkbox = this.create_record_checkbox (record, ul, false);
-           var id=this.record_id(record);
-           // used to keep a dict here, but only checkbox is required
-            this.by_id[id] = checkbox;
         }, // new_record
 
         arm_marker: function(marker, map) {
            if (debug_deep) messages.debug("on_new_record");
             if (this.received_all)
                 // update checkbox for record
-                this.set_checkbox(record, true);
+                this.set_checkbox_from_record(record, true);
             else
                 // store for later update of checkboxes
                 this.in_set_backlog.push(record);
             switch(data.request) {
             case FIELD_REQUEST_ADD:
             case FIELD_REQUEST_ADD_RESET:
-                this.set_checkbox(data.value, true);
+                this.set_checkbox_from_data(data.value, true);
                 break;
             case FIELD_REQUEST_REMOVE:
             case FIELD_REQUEST_REMOVE_RESET:
-                this.set_checkbox(data.value, false);
+                this.set_checkbox_from_data(data.value, false);
                 break;
             default:
                 break;
             if (this.received_set) {
                 /* ... and check the ones specified in the resource list */
                 $.each(this.in_set_backlog, function(i, record) {
-                    googlemap.set_checkbox(record, true);
+                    googlemap.set_checkbox_from_record(record, true);
                 });
                // reset 
                googlemap.in_set_backlog = [];
index 6f9694f..b4c7b29 100644 (file)
@@ -4,6 +4,17 @@
  * License: GPLv3
  */
 
+/* 
+ * WARNINGS
+ *
+ * This is very rough for now and not deemed working
+ * 
+ * Also it still requires adaptation for the init_key / init_id / canonical_key / id business 
+ * if the basic logic was to become usable
+ * 
+ * WARNINGS
+ */
+
 /* ongoing adaptation to slickgrid 
    still missing are
 . checkboxes really running properly
index b7b92f4..b435b38 100644 (file)
@@ -20,13 +20,13 @@ Current implementation makes the following assumptions
   with checkboxes is desired
 * optionally pass columns as the initial set of columns
   if None then this is taken from the query's fields
-* id_key is the name of a column used internally in the plugin
-  for checkboxes management. Caller should specify a column that is present 
-  in the fields returned by 'query' and that has unique values.
+* init_key is the name of a column that should appear in both queries
+  and used internally in the plugin for checkboxes initialization. 
   If not specified, metadata will be used to find out a primary key.
   However in the case of nodes & slice for example, the default key
-  as returned by the metadata would be 'urn', but it is not necessarily 
-  a good idea to show urn's initially - if at all.
+  as returned by the metadata would be 'urn', but 'urn' could only 
+  be used for this purpose if it gets displayed initially, which is
+  not necessarily a good idea.
   This is why a slice view would use 'hrn' here instead.
 * datatables_options are passed to dataTables as-is; 
   however please refrain from passing an 'aoColumns' 
@@ -35,7 +35,7 @@ Current implementation makes the following assumptions
 
     def __init__ (self, query=None, query_all=None, 
                   checkboxes=False, columns=None, 
-                  id_key=None,
+                  init_key=None,
                   datatables_options={}, **settings):
         Plugin.__init__ (self, **settings)
         self.query          = query
@@ -57,7 +57,7 @@ Current implementation makes the following assumptions
         else:
             self.columns = []
             self.hidden_columns = []
-        self.id_key=id_key
+        self.init_key=init_key
         self.datatables_options=datatables_options
         # if checkboxes were required, we tell datatables about this column's type
         # so that sorting can take place on a selected-first basis (or -last of course)
@@ -106,4 +106,4 @@ Current implementation makes the following assumptions
         return ['plugin_uuid', 'domid', 
                 'query_uuid', 'query_all_uuid', 
                 'checkboxes', 'datatables_options', 
-                'hidden_columns', 'id_key',]
+                'hidden_columns', 'init_key',]
index 39e5049..ebfb833 100644 (file)
@@ -7,7 +7,7 @@
 (function($){
 
     var debug=false;
-//    debug=true
+    debug=true
 
     var QueryTable = Plugin.extend({
 
             var query = manifold.query_store.find_analyzed_query(this.options.query_uuid);
             this.object = query.object;
 
-           // 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 (typeof(this.key)=='undefined' || (this.key).startsWith("unknown")) {
-                   // if not specified by caller, decide from metadata
-                   var keys = manifold.metadata.get_key(this.object);
-                   this.key = (keys && keys.length == 1) ? keys[0] : null;
-           }
-           if (! this.key) messages.warning("querytable.init could not kind valid key");
-           messages.debug("querytable: key="+this.key);
+           //// we need 2 different keys
+           // * canonical_key is the primary key as derived from metadata (typically: urn)
+           //   and is used to communicate about a given record with the other plugins
+           // * init_key is a key that both kinds of records 
+           //   (i.e. records returned by both queries) must have (typically: hrn or hostname)
+           //   in general query_all will return well populated records, but query
+           //   returns records with only the fields displayed on startup
+           var keys = manifold.metadata.get_key(this.object);
+           this.canonical_key = (keys && keys.length == 1) ? keys[0] : undefined;
+           // 
+           this.init_key = this.options.init_key;
+           // have init_key default to canonical_key
+           this.init_key = this.init_key || this.canonical_key;
+           // sanity check
+           if ( ! this.init_key ) messages.warning ("QueryTable : cannot find init_key");
+           if ( ! this.canonical_key ) messages.warning ("QueryTable : cannot find canonical_key");
+           if (debug) messages.debug("querytable: canonical_key="+this.canonical_key+" init_key="+this.init_key);
 
             /* Setup query and record handlers */
             this.listen_query(options.query_uuid);
             return (tabIndex.length > 0) ? tabIndex[0] : -1;
         }, // getColIndex
 
-        checkbox_html : function (key, value)
-        {
-//         if (debug) messages.debug("checkbox_html, value="+value);
+       // create a checkbox <input> tag
+       // computes 'id' attribute from canonical_key (using flat_id)
+       // computes 'common_id' from init_key for initialization phase
+        checkbox_html : function (record) {
             var result="";
             // Prefix id with plugin_uuid
             result += "<input";
             result += " class='querytable-checkbox'";
-            result += " id='" + this.flat_id(this.id('checkbox', value)) + "'";
-            result += " name='" + key + "'";
+        // compute id from canonical_key
+           var id = record[this.canonical_key]
+           // normalize using flat_id - see plugin.js
+           id = this.flat_id(id)
+//         if (debug) messages.debug("checkbox_html, id="+id);
+            result += " id='" + id + "'";
+        // compute common_id too
+           var common_id=record[this.init_key];
+           common_id=this.flat_id(common_id);
+        // wrap up
+           result += "common_id='" + common_id + "'";
+           // set value for posting events upon user's clicks
+           result += " value='"+record[this.canonical_key]+"'";
             result += " type='checkbox'";
             result += " autocomplete='off'";
-           if (value === undefined) {
-               messages.warning("querytable.checkbox_html - undefined value");
-           } else {
-               result += " value='" + value + "'";
-           }
             result += "></input>";
             return result;
         }, 
             // 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]));
+                line.push(this.checkbox_html(record));
                }
     
            // adding an array in one call is *much* more efficient
                 this.table.fnSetColumnVis(index, false);
         },
 
-        set_checkbox: function(record, checked)
-        {
-            /* Default: checked = true */
+       // this is used at init-time, at which point only init_key can make sense
+       // (because the argument record, if it comes from query, might not have canonical_key set
+       set_checkbox_from_record: function (record, checked) {
             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;
-               }
-            // PB TO CHECK THE RIGHT CHECKBOXES IS HERE... flat_id using \ in the key
-            // need to use escape_id when creating the id of the checkboxes
-            var checkbox_id = this.flat_id(this.id('checkbox', id));
-            // function escape_id(myid) is defined in portal/static/js/common.functions.js
-            checkbox_id = escape_id(checkbox_id);
-            // As we are using [id="x"] syntax, we need to remove the # in the checkbox_id
-            checkbox_id = checkbox_id.replace("#","");
-            // using dataTables's $ to search also in nodes that are not currently displayed
-            var element = this.table.$('[id="' + checkbox_id + '"]');
-            if (debug) 
-                messages.debug("set_checkbox checked=" + checked
-                               + " id=" + checkbox_id + " matches=" + element.length);
-            element.attr('checked', checked);
-        },
+           var common_id = this.flat_id(record[this.init_key]);
+           if (debug) messages.debug("set_checkbox_from_record, common_id="+common_id);
+           var element = this.table.$('[common_id="'+common_id+'"]');
+           element.attr('checked',checked);
+       },
+
+       // id relates to canonical_key
+       set_checkbox_from_data: function (id, checked) {
+            if (checked === undefined) checked = true;
+           var id=this.flat_id(id);
+           if (debug) messages.debug("set_checkbox_from_data, id="+id);
+           var element = this.table.$('#'+id);
+           element.attr('checked',checked);
+       },
 
         /*************************** QUERY HANDLER ****************************/
 
         {
             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_from_record(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]);
+               if (debug) messages.debug("Remembering record to check " + record[this.init_key]);
                 this.buffered_records_to_check.push(record);
             }
         },
             switch(data.request) {
                 case FIELD_REQUEST_ADD:
                 case FIELD_REQUEST_ADD_RESET:
-                    this.set_checkbox(data.value, true);
+                    this.set_checkbox_from_data(data.value, true);
                     break;
                 case FIELD_REQUEST_REMOVE:
                 case FIELD_REQUEST_REMOVE_RESET:
-                    this.set_checkbox(data.value, false);
+                    this.set_checkbox_from_data(data.value, false);
                     break;
                 default:
                     break;
             switch(data.request) {
                 case FIELD_REQUEST_ADD:
                 case FIELD_REQUEST_ADD_RESET:
-                    this.set_checkbox(data.value, true);
+                    this.set_checkboxfrom_data(data.value, true);
                     break;
                 case FIELD_REQUEST_REMOVE:
                 case FIELD_REQUEST_REMOVE_RESET:
-                    this.set_checkbox(data.value, false);
+                    this.set_checkbox_from_data(data.value, false);
                     break;
                 default:
                     break;
            // 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_from_record(record, true);
             });
            this.buffered_records_to_check = [];
 
             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 + " "+this.key+"=" + this.value);
+           if (debug) messages.debug("querytable click handler checked=" + this.checked + " "+this.canonical_key+"=" + 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
             
index 09e4433..2d77bdf 100644 (file)
@@ -69,7 +69,8 @@ class SliceView (LoginRequiredAutoLogoutView):
         main_query = Query.get('slice').filter_by('slice_hrn', '=', slicename)
         main_query.select(
                 'slice_hrn',
-                'resource.hrn', 'resource.urn', 'resource.hostname', 'resource.type', 
+                #'resource.hrn', 'resource.urn', 
+                'resource.hostname', 'resource.type', 
                 'resource.network_hrn',
                 'lease.urn',
                 'user.user_hrn',
@@ -77,7 +78,7 @@ class SliceView (LoginRequiredAutoLogoutView):
         )
         # for internal use in the querytable plugin;
         # needs to be a unique column present for each returned record
-        #main_query_key = 'hrn'
+        main_query_init_key = 'hostname'
     
         query_resource_all = Query.get('resource').select(resource_fields)
         if do_query_users:
@@ -173,7 +174,10 @@ class SliceView (LoginRequiredAutoLogoutView):
             togglable  = False,
             query      = sq_resource,
             query_all  = query_resource_all,
+            # this key is the one issued by google
             googlemap_api_key = Config().googlemap_api_key(),
+            # the key to use at init-time
+            init_key   = main_query_init_key,
             checkboxes = True,
             # center on Paris
             latitude   = 49.,
@@ -196,8 +200,8 @@ class SliceView (LoginRequiredAutoLogoutView):
             # this is the query at the core of the slice list
             query      = sq_resource,
             query_all  = query_resource_all,
-            # safer to use 'hrn' as the internal unique key for this plugin
-            #id_key     = main_query_key,
+            # use 'hrn' as the internal unique key for this plugin
+            init_key     = main_query_init_key,
             checkboxes = True,
             datatables_options = { 
                 'iDisplayLength': 25,
@@ -214,8 +218,9 @@ class SliceView (LoginRequiredAutoLogoutView):
                 # this is the query at the core of the slice list
                 query      = sq_resource,
                 query_all  = query_resource_all,
-                # safer to use 'hrn' as the internal unique key for this plugin
-                id_key     = main_query_key,
+                # use 'hrn' as the internal unique key for this plugin
+                # xxx todo on querygrid as well
+                # init_key     = main_query_init_key,
                 checkboxes = True,
                 )
 
@@ -245,8 +250,6 @@ class SliceView (LoginRequiredAutoLogoutView):
         if insert_grid:
             resources_sons.append(resources_as_grid)
 
-        print 40*'+-',"resources_sons has",len(resources_sons),"son"
-
         resources_area = Tabs ( page=page, 
                                 domid="resources",
                                 togglable=True,
index f0f2391..014e143 100644 (file)
@@ -24,7 +24,8 @@ class SimpleGridView (TemplateView):
         main_query = Query.get('slice').filter_by('slice_hrn', '=', slicename)
         main_query.select(
                 'slice_hrn',
-                'resource.hrn', 'resource.hostname', 'resource.type', 
+                #'resource.hrn', 
+                'resource.hostname', 'resource.type', 
                 'resource.network_hrn',
                 'lease.urn',
                 'user.user_hrn',
@@ -32,7 +33,7 @@ class SimpleGridView (TemplateView):
         )
         # for internal use in the querygrid plugin;
         # needs to be a unique column present for each returned record
-        main_query_key = 'hrn'
+        main_query_init_key = 'hostname'
     
         query_resource_all = Query.get('resource').select(resource_fields)
         
@@ -50,7 +51,7 @@ class SimpleGridView (TemplateView):
             query      = sq_resource,
             query_all  = query_resource_all,
             # safer to use 'hrn' as the internal unique key for this plugin
-            id_key     = main_query_key,
+            id_key     = main_query_init_key,
             checkboxes = True,
             datatables_options = { 
                 'iDisplayLength': 25,
index 19eb6be..e0fd770 100644 (file)
@@ -24,7 +24,7 @@ class SimpleTableView (TemplateView):
         main_query = Query.get('slice').filter_by('slice_hrn', '=', slicename)
         main_query.select(
                 'slice_hrn',
-                'resource.hrn', 'resource.hostname', 'resource.type', 
+                'resource.hostname', 'resource.type', 
                 'resource.network_hrn',
                 'lease.urn',
                 'user.user_hrn',
@@ -32,7 +32,7 @@ class SimpleTableView (TemplateView):
         )
         # for internal use in the querytable plugin;
         # needs to be a unique column present for each returned record
-        main_query_key = 'hrn'
+        main_query_init_key = 'hostname'
     
         query_resource_all = Query.get('resource').select(resource_fields)
         
@@ -50,7 +50,7 @@ class SimpleTableView (TemplateView):
             query      = sq_resource,
             query_all  = query_resource_all,
             # safer to use 'hrn' as the internal unique key for this plugin
-            common_key     = main_query_key,
+            init_key     = main_query_init_key,
             checkboxes = True,
             datatables_options = { 
                 'iDisplayLength': 25,