send_email: backup recipient email added [support@myslice.info]
[myslice.git] / plugins / scheduler / static / js / scheduler.js
index 0ffed88..c9b2096 100644 (file)
@@ -45,10 +45,11 @@ var txt_otherslice = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-s
 
         init: function(options, element) 
         {
+           this.classname="scheduler";
             this._super(options, element);
 
             /* Member variables */
-            this.canvas_id = this.id('leases_area');
+            this._canvas_id = this.id('leases_area');
 
             this.query_uuid = options.query_uuid;
             this.rows = null;
@@ -56,22 +57,30 @@ var txt_otherslice = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-s
             //this.current_resources = Array();
             //this.current_leases = Array();
 
-            this.myLeases = Array();
-            this.allLeases = Array();
+            /* Managing asynchronous reception of resources and leases */
+            this._resources          = Array();
+            this._leases             = Array();
+            this._received_resources = false;
+            this._received_leases    = false;
 
-            this.listLeases = Array();
+            this._axisx              = Array();
+            this._nodelabels         = Array();
+            this._lease_elements     = Array();
+            //this.myLeases = Array();
+            //this.allLeases = Array();
+
+            /* The time axis is an array of tuple (time, printable time) representing timeslots */
 
-            this.axisx = Array();
-            this.axisy = Array();
             this.data = Array();
             this.default_granularity = 1800; /* 30 min */
-            this.initial_timestamp = null;
+            this._initial_timestamp = null;
 
             /* This should be updated to be the ppcm of all granularities */
             this.min_granularity = this.default_granularity;
 
             // the data contains slice names, and lease_id, we need this to find our own leases (mine)
-            this.paper=null;
+            this._paper=null;
+
 
             /* XXX Events */
 
@@ -79,7 +88,8 @@ var txt_otherslice = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-s
 
             /* Listening to queries */
             this.listen_query(options.query_uuid);
-            this.listen_query(options.query_lease_uuid);
+            this.listen_query(options.query_all_resources_uuid, 'all_resources');
+            this.listen_query(options.query_lease_uuid, 'lease');
 
             /* XXX GUI setup and event binding */
             jQuery("#datepicker").datetimepicker({
@@ -99,55 +109,16 @@ var txt_otherslice = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-s
                     // pass the result to init_axisx - done
                     //console.log(currentDate);
                     s.clear();
-                    s.init_axisx(currentDate);
-                    s.draw();
+                    s._init_axisx(currentDate);
+                    s._draw();
                     // Do we need to populate the timeslots with existing leases? 
                     // Look how to populate with initial_leases [we have to show the leases]
                     
                 } 
             });
-            //console.log(s.nodelabels[1]);
-            // Do we need to populate the timeslots with existing leases? 
-            // Look how to populate with initial_leases
-            
-            // TODO -- DONE  Note: autocomplete is not search box
-            // Implement a filtering functionality, on the name of the node
-            // http://jqueryui.com/autocomplete/
-            // pass s.nodelabels to autocomplete function
-            //jQuery("#search").autocomplete('s.nodelabel');
-            //console.log(s.nodelabel);
-            
-            /*
-             * TODO
-             * During init, there are no resources
-             * So the list of nodes is empty
-             * The function triggered by the subscription to the resources query
-             * var RESULTS_RESOURCES = '/results/' + options.resource_query_uuid + '/changed';
-             * $.subscribe(RESULTS_RESOURCES, function(e, resources) { s.set_resources(resources);   });
-             * Will have to update the list of nodes available through autocomplete (availableTags)
-             * 
-             * Be inspired by QueryEditor plugin
-             * 
-             * Resources informations in s.axisy ???
-             * 
-             * Filter what has been selected in other plugins:
-             * QueryEditor
-             * QuickFilter
-             * AdvancedFilter
-             * 
-             * Implement an action of filtering while typing
-             * filter what correspond to the user choice
-             * 
-            $(function() {
-                var availableTags = ['omf','nitos','ple', s.nodelabel];
-            $( "#search" ).autocomplete({
-              source: availableTags
-                });
-            });
-             */
 
-            this.init_axisx('');
-            this.draw();
+            this._init_axisx('');
+            this._draw();
 
         }, /* init */
 
@@ -167,74 +138,134 @@ var txt_otherslice = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-s
             radius:              6,
             anim_delay:          350,
             checkboxes:          false,
-            resource_query_uuid: null,  /* resources */
-            lease_query_uuid:    null,  /* leases */
         },
 
         /* PLUGIN EVENTS */
 
         /* GUI EVENTS */
 
-        /* GUI MANIPULATION */
+        /************************** GUI MANIPULATION **************************/
 
-        /* TEMPLATES */
+        /* NOTE: All Raphael-dependent code should go here. Performance issues
+         * are suspected, it might be improved by a move to d3.js. The more
+         * general problem to solve is how to manipulate lots of svg objects.
+         */
 
-        /* QUERY HANDLERS */
+        /*************************** RECORD HANDLER ***************************/
 
-        set_resources: function(resources) 
-        {
-            //console.log(resources);
-            var scheduler = this;
-            jQuery.each(resources, function(i, resource) {
-                // ... add reservable ones to the x axis
-                if ((typeof resource.exclusive != 'undefined') && (resource.exclusive)) {
-                    scheduler.axisy.push(Array(resource.urn, resource.resource_hrn, resource.type));
-                }
-                // ... if we do not have information about slivers (first update), update it
-                if (typeof resource.sliver != 'undefined') {
-                    // XXX
-                }
-            });
+        /* ------------------------------------------------------------------
+         * Resources
+         * ------------------------------------------------------------------ */
+
+        on_query_in_progress: function() {
+            this.spin();
+        },
 
-            this.draw(this.canvas_id);
+        on_query_done: function() {
+            /* We have received all leases */
+            if (this._received_resources) {
+                this._draw(this._canvas_id);
+                this.unspin();
+            }
+            this._received_leases = true;
         },
 
-        set_leases: function(leases) 
+        /* ------------------------------------------------------------------
+         * All resources
+         * ------------------------------------------------------------------ */
+
+        on_all_resources_query_in_progress: function() {
+            console.log("all resources query in progress");
+        },
+
+        on_all_resources_new_record: function(record)
         {
-            this.initial_leases=leases;
-            this.draw(this.canvas_id);
+            if ((typeof record.exclusive != 'undefined') && (record.exclusive)) {
+                this._resources.push(Array(record.urn, record.hrn, record.type));
+            }
+            // ... if we do not have information about slivers (first update), update it
+            if (typeof record.sliver != 'undefined') {
+                // XXX
+            }
         },
 
-        update_resources: function(resources) 
+        /* ------------------------------------------------------------------
+         * Leases
+         * ------------------------------------------------------------------ */
+        
+        on_lease_new_record: function(record)
         {
-            //
+            this._leases.push(record);
+            // this.initial_leases=leases;
         },
 
-        update_leases: function(leases)
+        on_lease_field_state_changed: function(data)
         {
-            //
+            var lease = data.value;
+            var urn = lease[0];
+            var start_time = lease[1];
+
+            var lease_element = this._lease_element_find(urn, start_time);
+            if (!lease_element) {
+                console.log("Alert: lease element not found");
+                return;
+            }
+
+            switch(data.request) {
+                case FIELD_REQUEST_ADD:
+                case FIELD_REQUEST_ADD_RESET:
+                    this._leases.push(data.value);
+                    this._lease_init_mine(lease_element);
+                    break;
+                case FIELD_REQUEST_REMOVE:
+                case FIELD_REQUEST_REMOVE_RESET:
+                    // We remove data.value (aka keep those leases different from data.value
+                    this._leases = $.grep(this._leases, function(x) { return x != data.value; });
+                    this._lease_init_free(lease_element);
+                    break;
+                default:
+                    break;
+
+            }
         },
 
-        /* RECORD HANDLERS */
+        on_lease_query_done: function(record)
+        {
+            /* We have received all resources */
+            if (this._received_leases) {
+                this._draw(this._canvas_id);
+                this.unspin();
+            }
+            this._received_resources = true;
+        },
 
-        /* INTERNAL FUNCTIONS */
+        /************************** PRIVATE METHODS ***************************/
 
         /**
          * @brief Return the number of time slots
          */
         nb_grains: function () 
         {
-            return this.axisx.length; 
+            return this._axisx.length; 
         },
 
         /**
          * @brief Returns whether there is a pending lease at this timestamp
          */
-        find_lease: function(urn, timestamp)
+        _lease_find: function(urn, timestamp)
         {
             var scheduler = this;
             var result = null;
 
+            $.each(scheduler._leases, function(i, lease) {
+                    if ((lease[0] == urn) &&
+                        ((timestamp >= lease[1]) && (timestamp < (lease[1] + lease[2] * 1800)))) {
+                            result = lease;
+                            return false; // stop each
+                    }
+            });
+
+/*
             $.each(Array(scheduler.myLeases, scheduler.allLeases), function(i, array) {
                 $.each(array, function(i, lease) {
                     if (lease[0] == urn) {
@@ -248,133 +279,152 @@ var txt_otherslice = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-s
                 if (result)
                     return false;
             });
+*/
             return result;
         },
 
+        /* Iterative search through raphael.js objects, no forEach, no getById in the current version */
+        _lease_element_find: function(urn, start_time)
+        {
+            var date = new Date(start_time*1000);
+            var pos = this._paper.top; 
+            while (pos) { 
+                 if (pos.key == urn + "-" + date)
+                    return pos;
+                 pos = pos.prev; 
+            } 
+            return null;
+        },
+
         /**
          * @brief Draw
          */
-        draw: function() 
+        _draw: function() 
         { 
-            var canvas_id = this.canvas_id;
+            var canvas_id = this._canvas_id;
             var o = this.options;
+
             var total_width = o.x_nodelabel + this.nb_grains() * this.options.leases_w;
             var total_height = 2 * o.y_header /* the timelabels */
                              + 2 * o.y_sep    /* extra space */
-                             + o.y_node          /* all-nodes & timebuttons row */ 
-                             + (this.axisy.length)*(o.y_node+o.y_sep);  /* the regular nodes and preceding space */
+                             + o.y_node          /* all-nodes & timebuttons row */ 
+                             + (this._resources.length) * (o.y_node + o.y_sep);  /* the regular nodes and preceding space */
 
             /* reuse for paper if exists with same size, or (re-)create otherwise */
             var paper;
-            if (this.paper == null) {
-                paper = Raphael (canvas_id, total_width+o.x_sep, total_height);
-            } else if (this.paper.width==total_width && this.paper.height==total_height) {
-                paper=this.paper;
+            if (this._paper == null) {
+                paper = Raphael (canvas_id, total_width + o.x_sep, total_height);
+            } else if (this._paper.width==total_width && this._paper.height==total_height) {
+                paper=this._paper;
                 paper.clear();
             } else {
-                $$("#"+canvas_id)[0].innerHTML="";
-                paper = Raphael (canvas_id, total_width+o.x_sep, total_height);
+                $("#"+canvas_id)[0].innerHTML="";
+                //this.elmt().html();
+                paper = Raphael (canvas_id, total_width + o.x_sep, total_height);
             }
-            this.paper=paper;
+            this._paper = paper;
 
             /* the path for the triangle-shaped buttons */
-            this.timebutton_path = "M1,0L"+(this.options.leases_w-1)+",0L"+(this.options.leases_w/2)+","+o.y_header+"L1,0";
+            var timebutton_path = "M1,0L"+(this.options.leases_w-1)+",0L"+(this.options.leases_w/2)+","+o.y_header+"L1,0";
 
-            var axisx = this.axisx;
-            var axisy = this.axisy;
+            var axisx = this._axisx;
+            var axisy = this._resources;
 
             /* maintain the list of nodelabels for the 'all nodes' button */
-            this.nodelabels=[];
+            this._nodelabels = [];
             
 
             /* create the time slots legend */
             var top = 0;
             var left = o.x_nodelabel;
 
-            var daymarker_height= 2*o.y_header + 2*o.y_sep + (axisy.length+1)*(o.y_node+o.y_sep);
-            var daymarker_path="M0,0L0," + daymarker_height;
+            var daymarker_height = 2*o.y_header + 2*o.y_sep + (axisy.length + 1) * (o.y_node + o.y_sep);
+            var daymarker_path = "M0,0L0," + daymarker_height;
 
             var half_daymarker_off= 2*o.y_header + o.y_sep;
             var half_daymarker_path="M0," + half_daymarker_off + "L0," + daymarker_height;
 
             var col=0;
-            for (var i=0, len=axisx.length; i < len; ++i) {
+            for (var i=0, len = axisx.length; i < len; ++i) {
                 /* pick the printable part */
-                var timelabel=axisx[i][1];
+                var timelabel = axisx[i][1];
                 var y = top + o.y_header;
-                if (col%2 == 0) y += o.y_header;
+                if (col % 2 == 0) 
+                    y += o.y_header;
                 col +=1;
                 /* display time label */
-                var timelabel=paper.text(left,y,timelabel).attr(txt_timelabel).attr({"text-anchor":"middle"});
+                var timelabel = paper.text(left, y, timelabel).attr(txt_timelabel).attr({"text-anchor": "middle"});
                 /* draw vertical line */
-                var path_spec="M"+left+" "+(y+o.y_header/2)+"L"+left+" "+this.total_height;
-                var rule=paper.path(path_spec).attr(attr_rules);
+                var path_spec = "M" + left + " " + (y+o.y_header / 2) + "L" + left + " " + this.total_height;
+                var rule = paper.path(path_spec).attr(attr_rules);
                 /* show a day marker when relevant */
-                var timestamp=parseInt(axisx[i][0]);
-                if ( (timestamp%(24*3600))==0) {
-                    paper.path(daymarker_path).attr({'translation':left+','+top}).attr(attr_daymarker);
+                var timestamp = parseInt(axisx[i][0]);
+                if ((timestamp % (24 * 3600)) == 0) {
+                    paper.path(daymarker_path).attr({'translation': left + ',' + top}).attr(attr_daymarker);
                 } else if ( (timestamp%(12*3600))==0) {
-                    paper.path(half_daymarker_path).attr({'translation':left+','+top}).attr(attr_daymarker);
+                    paper.path(half_daymarker_path).attr({'translation': left + ',' + top}).attr(attr_daymarker);
                 }
-                left += this.options.leases_w;
+                left += o.leases_w;
             }
 
             ////////// the row with the timeslot buttons (the one labeled 'All nodes')
-            this.granularity= this.min_granularity; // XXX axisx[1][0]-axisx[0][0];
+            this.granularity = this.min_granularity; // XXX axisx[1][0]-axisx[0][0];
 
             // move two lines down
-            top += 2*o.y_header + 2*o.y_sep;
-            left=o.x_nodelabel;
+            top += 2 * o.y_header + 2 * o.y_sep;
+            left = o.x_nodelabel;
             // all nodes buttons
-            var allnodes = paper.text (o.x_nodelabel-o.x_sep,top+o.y_node/2,"All nodes").attr(txt_allnodes)
-                .attr ({"font-size":o.y_node, "text-anchor":"end","baseline":"bottom"});
-            allnodes.scheduler=this;
-            allnodes.click(this.allnodes_methods.click);
+            var allnodes = paper.text(o.x_nodelabel - o.x_sep, top + o.y_node / 2, "All nodes").attr(txt_allnodes)
+                .attr({"font-size": o.y_node, "text-anchor": "end", "baseline": "bottom"});
+            //allnodes.scheduler = this;
+            allnodes.click(this._allnodes_click); // XXX click
             
             // timeslot buttons [it's the triangles above the slots]
-            for (var i=0, len=axisx.length; i < len; ++i) {
-                var timebutton=paper.path(this.timebutton_path).attr({'translation':left+','+top}).attr(attr_timebutton);
-                timebutton.from_time=axisx[i][0];
-                timebutton.scheduler=this;
-                timebutton.click(this.timebutton_methods.click);
-                left+=(this.options.leases_w);
+            for (var i = 0, len = axisx.length; i < len; ++i) {
+                var timebutton = paper.path(timebutton_path).attr({'translation':left + ',' + top}).attr(attr_timebutton);
+                timebutton.from_time = axisx[i][0];
+                timebutton.scheduler = this;
+                timebutton.click(this._timebutton_click);
+                left += (o.leases_w);
             }
             
             //////// the body of the scheduler : loop on nodes
             top += o.y_node + o.y_sep;
-            var data_index=0;
-            this.leases=[];
-            for (var i=0, len=axisy.length; i<len; ++i) {
-                var urn=axisy[i][0];
-                var nodename=axisy[i][1];
-                var type=axisy[i][2];
-                left=0;            
+
+            var data_index = 0;
+            this.leases = []; /* XXX ??? XXX */
+
+            for (var i = 0, len = axisy.length; i < len; ++i) {
+                var urn = axisy[i][0];
+                var nodename = axisy[i][1];
+                var type = axisy[i][2];
+                left = 0;
                 /*
                  *  MODIFIED font-size
                 var nodelabel = paper.text(o.x_nodelabel-x_sep,top+y_node/2,nodename).attr(txt_nodelabel)
                 .attr ({"font-size":y_node, "text-anchor":"end","baseline":"bottom"});
                 */
-                var nodelabel = paper.text(o.x_nodelabel-o.x_sep,top+o.y_node/3,nodename).attr(txt_nodelabel)
-                .attr ({"font-size":"12px", "text-anchor":"end","baseline":"bottom"});    
-                //console.log(nodelabel);
-                this.nodelabel_methods.selected(nodelabel,1);
-                //nodelabel_methods.selected( this, ! this.selected );
-                nodelabel.click(this.nodelabel_methods.click); //click action works here
-                //timebutton.click(timebutton_methods.click);
-                
+                var nodelabel = paper.text(o.x_nodelabel - o.x_sep, top + o.y_node / 3, nodename).attr(txt_nodelabel)
+                    .attr({"font-size": "12px", "text-anchor": "end","baseline": "bottom"});    
+
+                this._nodelabel_select(nodelabel, 1); // By default we select all nodes
+                nodelabel.scheduler = this;
+                nodelabel.click(this._nodelabel_click); //click action works here
+                this._nodelabels.push(nodelabel);
+
                // lease_methods.init_free(nodelabel.click, lease_methods.click_mine);
                //lease_methods.init_free(nodelabel.lease);
-                this.nodelabels.push(nodelabel);
                 
                 left += o.x_nodelabel;
-                var grain=0;
+
                 // data index contains the full array of leases
                 // not the same amount of grains per node
 
                 // NOTE: remembering the previous lease might help for long leases.
+                var grain=0;
                 while (grain < this.nb_grains()) {
 
-                    if (l = this.find_lease(urn, this.initial_timestamp + grain * 1800)) {
+                    if (l = this._lease_find(urn, this._initial_timestamp + grain * 1800)) {
                         slicename = l.slice_id;
                     } else {
                         slicename = "";
@@ -385,77 +435,82 @@ var txt_otherslice = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-s
                     /* Duration should not be the lease duration, but the grain */
                     var duration = 1; // this.data[data_index][2];
 
-                    var lease=paper.rect (left,top,this.options.leases_w*duration,o.y_node,o.radius);
-                    // record scheduler in lease - early as we need this in init_other
-                    lease.scheduler=this;
+                    var lease = paper.rect(left, top, o.leases_w * duration, o.y_node, o.radius);
+                    lease.scheduler = this; // we record a pointer to the scheduler in the lease element
                     //lease.lease_id=lease_id;
-                    lease.nodename=nodename;
-                    lease.urn=urn;
-                    lease.nodelabel=nodelabel;
+                    lease.nodename  = nodename;
+                    lease.urn       = urn;
+                    lease.nodelabel = nodelabel;
+
                     if (slicename == "") {
-                        lease.initial="free";
-                        lease_methods.init_free(lease);
-                    } else if (slicename == this.options.slicename) {
-                        lease.initial="mine";
-                        lease_methods.init_mine(lease);
+                        lease.initial = "free";
+                        this._lease_init_free(lease);
+                    } else if (slicename == o.slicename) {
+                        lease.initial = "mine";
+                        this._lease_init_mine(lease);
                     } else {
-                        lease.initial="other";
-                        lease_methods.init_other(lease,slicename);
+                        lease.initial = "other";
+                        this._lease_init_other(lease, slicename);
                     }
-                    lease.from_time = axisx[grain%this.nb_grains()][0];
+
+                    lease.from_time = axisx[grain % this.nb_grains()][0];
                     grain += duration;
-                    lease.until_time = axisx[grain%this.nb_grains()][0];
+                    lease.until_time = axisx[grain % this.nb_grains()][0];
+
+                    /* We set a key to the lease element to find it later in the paper thanks to this._lease_element_find() */
+                    lease.key        = urn + '-' + lease.from_time;
+
                     // and vice versa
-                    this.leases.push(lease);
+                    this._lease_elements.push(lease);
                     // move on with the loop
-                    left += this.options.leases_w*duration;
-                    data_index +=1;
+                    left += o.leases_w * duration;
+                    data_index += 1;
                 }
                 top += o.y_node + o.y_sep;
             };
-        }, /* draw */
+        }, /* _draw */
 
-        init_axisx: function(currentDate) 
+        _init_axisx: function(currentDate) 
         {
-            this.axisx = Array();
+            this._axisx = Array();
     
-            if(currentDate=="") {
+            if(currentDate == "") {
                 // creating timestamp of the current time
                 currentDate = new Date().getTime() / 1000;
             }
-            this.initial_timestamp = currentDate;
+            this._initial_timestamp = currentDate;
             //console.log(currentDate);
     
             // round it by granularity (becomes an Int)
-            var rounded = Math.round(currentDate/this.min_granularity)*this.min_granularity;
+            var rounded = Math.round(currentDate / this.min_granularity) * this.min_granularity;
             // Convert Int to Date
-            rounded = new Date(rounded*1000);
+            rounded = new Date(rounded * 1000);
             // get hours and minutes in a 24h format 00:00
-            var roundedHours=(rounded.getHours()<10?'0':'') + rounded.getHours();
-            var roundedMinutes=(rounded.getMinutes()<10?'0':'') + rounded.getMinutes();
+            var roundedHours   = (rounded.getHours()   < 10 ? '0' : '') + rounded.getHours();
+            var roundedMinutes = (rounded.getMinutes() < 10 ? '0' : '') + rounded.getMinutes();
     
-            //timeFrame.push(rounded);
-            this.axisx.push(Array(rounded, roundedHours+":"+roundedMinutes));
+            this._axisx.push(Array(rounded, roundedHours + ":" + roundedMinutes));
     
-            // Generate as man slots as we need
-            for(i=0; i<this.options.leases_slots;i++) {
-                rounded = this.min_granularity+(rounded.getTime()/1000);
-                rounded = new Date(rounded*1000);
-                roundedHours=(rounded.getHours()<10?'0':'') + rounded.getHours();
-                roundedMinutes=(rounded.getMinutes()<10?'0':'') + rounded.getMinutes();
+            // Generate as many slots as we need
+            for(i = 0; i < this.options.leases_slots; i++) {
+                rounded = this.min_granularity + (rounded.getTime() / 1000);
+                rounded = new Date(rounded * 1000);
+                roundedHours   = (rounded.getHours()   < 10 ? '0' : '') + rounded.getHours();
+                roundedMinutes = (rounded.getMinutes() < 10 ? '0' : '') + rounded.getMinutes();
     
-                //timeFrame.push(rounded);
-                this.axisx.push(Array(rounded, roundedHours+":"+roundedMinutes));
+                this._axisx.push(Array(rounded, roundedHours + ":" + roundedMinutes));
             }
         },
 
         clear: function ()
         {
-            for (var i=0, len=this.leases.length; i<len; ++i) {
-                var lease=this.leases[i];
+            for (var i=0, len = this.leases.length; i<len; ++i) {
+                var lease = this.leases[i];
                 if (lease.current != lease.initial) {
-                if (lease.initial == 'free') lease_methods.init_free(lease,lease_methods.click_mine);
-                else                        lease_methods.init_mine(lease,lease_methods.click_free);
+                    if (lease.initial == 'free')
+                        this._lease_init_free(lease, this._lease_click_mine);
+                    else
+                        this._lease_init_mine(lease, this._lease_methods.click_free);
                 }
             }
         },
@@ -465,182 +520,213 @@ var txt_otherslice = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-s
         /* ---------------------------------------------------------------------
          * The 'all nodes' button
          */
-        allnodes_methods: {
-            click: function (event) {
-                var scheduler=this.scheduler;
-                /* decide what to do */
-                var unselected=0;
-                for (var i=0, len=scheduler.nodelabels.length; i<len; ++i) 
-                    if (! scheduler.nodelabels[i].selected) unselected++;
-                /* if at least one is not selected : select all */
-                var new_state = (unselected >0) ? 1 : 0;
-                for (var i=0, len=scheduler.nodelabels.length; i<len; ++i) 
-                    this.nodelabel_methods.selected(scheduler.nodelabels[i],new_state);
-            }
+        _allnodes_click: function (event) {
+            var scheduler=this.scheduler;
+
+            /* decide what to do */
+            var unselected = 0;
+            for (var i = 0, len = this._nodelabels.length; i < len; ++i) 
+                if (!this._nodelabels[i].selected) 
+                    unselected++;
+
+            /* if at least one is not selected : select all */
+            var new_state = (unselected >0) ? 1 : 0;
+            for (var i=0, len=this._nodelabels.length; i<len; ++i) 
+                this._nodelabel_select(this._nodelabels[i], new_state);
         },
 
         /* ---------------------------------------------------------------------
          * The buttons for managing the whole timeslot
          */
-        timebutton_methods: {
-
-            /* clicking */
-            click: function (event) {
-                var scheduler = this.scheduler;
-                var from_time = this.from_time;
-                var until_time = from_time + scheduler.granularity;
-                /* scan leases on selected nodes, store in two arrays */
-                var relevant_free=[], relevant_mine=[];
-                for (var i=0,len=scheduler.leases.length; i<len; ++i) {
-                    var scan=scheduler.leases[i];
-                    if ( ! scan.nodelabel.selected) continue;
-                    // overlap ?
-                    if (scan.from_time<=from_time && scan.until_time>=until_time) {
-                    if (scan.current == "free")       relevant_free.push(scan);
-                    else if (scan.current == "mine")  relevant_mine.push(scan);
-                    }
+
+        /* clicking */
+        _timebutton_click: function (event) 
+        {
+            var scheduler  = this.scheduler;
+            var from_time  = this.from_time;
+            var until_time = new Date(from_time.getTime() + scheduler.granularity);
+            /* scan leases on selected nodes, store in two arrays */
+            var relevant_free = [], relevant_mine = [];
+            for (var i = 0, len = scheduler._lease_elements.length; i < len; ++i) {
+                var scan = scheduler._lease_elements[i];
+                if (!scan.nodelabel.selected)
+                    continue;
+                // overlap ?
+                if (scan.from_time <= from_time && scan.until_time >= until_time) {
+                    if (scan.current == "free")
+                        relevant_free.push(scan);
+                    else if (scan.current == "mine")
+                        relevant_mine.push(scan);
                 }
-                // window.console.log("Found " + relevant_free.length + " free and " + relevant_mine.length + " mine");
-                /* decide what to do, whether book or release */
-                if (relevant_mine.length==0 && relevant_free.length==0) {
-                    alert ("Nothing to do in this timeslot on the selected nodes");
-                    return;
+            }
+            // window.console.log("Found " + relevant_free.length + " free and " + relevant_mine.length + " mine");
+            /* decide what to do, whether book or release */
+            if (relevant_mine.length == 0 && relevant_free.length == 0) {
+                alert ("Nothing to do in this timeslot on the selected nodes");
+                return;
+            }
+            // if at least one is free, let's book
+            if (relevant_free.length > 0) {
+                for (var i = 0, len = relevant_free.length; i < len; ++i) {
+                    var lease = relevant_free[i];
+                    scheduler._lease_init_mine(lease, scheduler._lease_click_free);
                 }
-                // if at least one is free, let's book
-                if (relevant_free.length > 0) {
-                    for (var i=0, len=relevant_free.length; i<len; ++i) {
-                        var lease=relevant_free[i];
-                        lease_methods.init_mine(lease,lease_methods.click_free);
-                    }
-                // otherwise we unselect
-                } else {
-                    for (var i=0, len=relevant_mine.length; i<len; ++i) {
-                        var lease=relevant_mine[i];
-                        lease_methods.init_free(lease,lease_methods.click_mine);
-                    }
+            // otherwise we unselect
+            } else {
+                for (var i = 0, len = relevant_mine.length; i < len; ++i) {
+                    var lease = relevant_mine[i];
+                    scheduler._lease_init_free(lease, scheduler._lease_click_mine);
                 }
-            } /* click */
-        }, 
+            }
+        }, /* _timebutton_click */
 
         /* ---------------------------------------------------------------------
          * The nodelabel buttons
          */
-        nodelabel_methods: {
             
-            // set selected mode and render visually
-            selected: function (nodelabel, flag) {
-                nodelabel.selected=flag;
-                nodelabel.attr({'font-weight': (flag ? 'bold' : 'normal')});
-                // TODO
-                // 
-                // loop on axis x and select each timebutton
-                // for (var i=0, len=axisx.length; i < len; ++i)
-                // figure out how to use timebutton_methods.click();
-                    
-            },
+        // set selected mode and render visually
+        _nodelabel_select: function (nodelabel, flag)
+        {
+            nodelabel.selected = flag;
+            nodelabel.attr({'font-weight': (flag ? 'bold' : 'normal')});
+            // TODO
+            // 
+            // loop on axis x and select each timebutton
+            // for (var i=0, len=axisx.length; i < len; ++i)
+            // figure out how to use timebutton_methods.click();
+                
+        },
 
-            // toggle selected
-            click: function (event) {
-                nodelabel_methods.selected( this, ! this.selected );
-            }
+        // toggle selected
+        _nodelabel_click: function (event) 
+        {
+            this.scheduler._nodelabel_select( this, ! this.selected );
         },
 
 
         /* ---------------------------------------------------------------------
          * The lease buttons
          */
-        lease_methods: {
-        
-            init_free: function (lease, unclick) {
-                var o = lease.scheduler.options;
-                lease.current="free";
-                // set color
-                lease.animate((lease.initial=="free") ? attr_lease_free_free : attr_lease_mine_free, o.anim_delay);
-                // keep track of the current status
-                // record action
-                lease.click (lease_methods.click_free);
-                if (unclick) lease.unclick(unclick);
-            },
-                     
-            // find out all the currently free leases that overlap this one
-            click_free: function (event) {
-                var scheduler = this.scheduler;
-                lease_methods.init_mine(this,lease_methods.click_free);        
-                //publish
-                //this.from_time
-                //this.urn
-                //this.until_time
-                var urn = this.urn
-                var start_time=new Date(this.from_time).getTime() / 1000;        
-                var end_time=new Date(this.until_time).getTime() / 1000;
-                var duration=(end_time-start_time)/1800; // XXX HARDCODED LEASE GRAIN
-
-                /* Add a new lease : XXX should be replaced by a dictionary */
-                // Do we have a lease with the same urn  just before or just after ?
-                var removeIdBefore = null;
-                var removeIdAfter = null;
-                jQuery.each(scheduler.listLeases, function(i, lease) {
-                    if (lease[0] == urn) {
-                        if (lease[1] + lease[2] * 1800 == start_time) { // XXX HARDCODED LEASE GRAIN
-                            // Merge with previous lease
-                            removeIdBefore = i;
-                            start_time = lease[1];
-                            duration += lease[2];
-                        }
-                        if (lease[1] == end_time) {
-                            // Merge with following lease
-                            removeIdAfter = i;
-                            duration += lease[2];
-                        }
+        _lease_init_free: function (lease, unclick) 
+        {
+            var o = lease.scheduler.options;
+            lease.current = "free";
+            // set color
+            lease.animate((lease.initial == "free") ? attr_lease_free_free : attr_lease_mine_free, o.anim_delay);
+            // keep track of the current status
+            // record action
+            lease.click(this._lease_click_free);
+            if (unclick) lease.unclick(unclick);
+        },
+                 
+        // find out all the currently free leases that overlap this one
+        _lease_click_free: function (event) 
+        {
+            var scheduler = this.scheduler;
+
+            scheduler._lease_init_mine(this, scheduler._lease_click_free);        
+            //publish
+            //this.from_time
+            //this.urn
+            //this.until_time
+            var urn        = this.urn
+            var start_time = this.from_time.getTime() / 1000;        
+            var end_time   = this.until_time.getTime() / 1000;
+            var duration   = (end_time - start_time) / 1800; // XXX HARDCODED LEASE GRAIN
+
+            /* Add a new lease : XXX should be replaced by a dictionary */
+            /*
+            // Do we have a lease with the same urn  just before or just after ?
+            //var removeIdBefore = null;
+            //var removeIdAfter  = null;
+            var remove_lease_before = null;
+            var remove_lease_after  = null;
+            // It is important to group leases,  while this is technically
+            // equivalent, some testbeds such as IotLab limit the number of
+            // leases a user can have.
+            // XXX we might have several leases before or after if they have
+            // XXX not been grouped like this tool does
+            $.each(scheduler._leases, function(i, lease) {
+                if (lease[0] == urn) {
+                    if (lease[1] + lease[2] * 1800 == start_time) { // XXX HARDCODED LEASE GRAIN
+                        // Merge with previous lease
+                        // removeIdBefore = i;
+                        remove_lease_before = lease;
+                        start_time          = lease[1];
+                        duration           += lease[2];
+                    }
+                    if (lease[1] == end_time) {
+                        // Merge with following lease
+                        // removeIdAfter = i;
+                        remove_lease_after  = lease;
+                        duration           += lease[2];
                     }
-                });
-                if (removeIdBefore != null) {
-                    scheduler.listLeases.splice(removeIdBefore , 1);
-                    if (removeIdAfter != null)
-                        removeIdAfter -= 1;
-                }
-                if (removeIdAfter != null) {
-                    scheduler.listLeases.splice(removeIdAfter , 1);
                 }
+            });
+            //if (removeIdBefore != null) {
+            //    scheduler._leases.splice(removeIdBefore , 1);
+            //    if (removeIdAfter != null)
+            //        removeIdAfter -= 1;
+            //}
+            //if (removeIdAfter != null) {
+            //    scheduler._leases.splice(removeIdAfter , 1);
+            //}
+
+            // We add the new lease, no need to push
+            var new_lease = [this.urn, start_time, duration];
+
+            // We send events, manifold will inform us about the change and we will react accordingly
+            /*
+            if (remove_lease_before != null)
+                manifold.raise_event(scheduler.options.query_lease_uuid, SET_REMOVED, remove_lease_before);
+            if (remove_lease_after != null)
+                manifold.raise_event(scheduler.options.query_lease_uuid, SET_REMOVED, remove_lease_after);
+            */
+            manifold.raise_event(scheduler.options.query_lease_uuid, SET_ADD,     new_lease);
+            //scheduler._leases.push([this.urn, start_time, duration]);
+
+            //console.log(scheduler._leases);
+            //jQuery.publish('/update-set/' + scheduler.options.query_uuid, [scheduler._leases]);
+
+            /* We need to inform manifold about the whole diff, in addition to maintaining our own structure */
+            event.preventDefault();
+        },
 
-                scheduler.listLeases.push([this.urn, start_time, duration]);
+        _lease_init_mine: function (lease, unclick) 
+        {
+            var o = lease.scheduler.options;
+            lease.current = "mine";
+            lease.animate((lease.initial == "mine") ? attr_lease_mine_mine : attr_lease_free_mine, o.anim_delay);
+            lease.click(this._lease_click_mine);
+            if (unclick)
+                lease.unclick(unclick);
+        },
+        
+        /* TODO: remove selected lease from array _leases and publish change */
+        _lease_click_mine: function (event) 
+        {
+            var scheduler = this.scheduler;
+            // this lease was originally free but is now marked for booking
+            // we free just this lease
+            //console.log('this is mine');
+            scheduler._lease_init_free(this, scheduler._lease_click_mine);
+            event.preventDefault();
+        },
 
-                console.log(scheduler.listLeases);
-                //jQuery.publish('/update-set/' + scheduler.options.query_uuid, [scheduler.listLeases]);
-                jQuery.publish('/update-set/' + scheduler.options.lease_query_uuid, [scheduler.listLeases]);
-            },
 
-            init_mine: function (lease, unclick) {
-                var o = lease.scheduler.options;
-                lease.current="mine";
-                lease.animate((lease.initial=="mine") ? attr_lease_mine_mine : attr_lease_free_mine,o.anim_delay);
-                lease.click (lease_methods.click_mine);
-                if (unclick) lease.unclick(unclick);
-            },
-            
-            /* TODO: remove selected lease from array listLeases and publish change */
-            click_mine: function (event) {
-                var scheduler = this.scheduler;
-                // this lease was originally free but is now marked for booking
-                // we free just this lease
-                //console.log('this is mine');
-                lease_methods.init_free(this, lease_methods.click_mine);
-            },
-
-
-            init_other: function (lease, slicename) {
-                lease.animate (attr_lease_other,anim_delay);
-                /* a text obj to display the name of the slice that owns that lease */
-                var otherslicelabel = lease.scheduler.paper.text (lease.attr("x")+lease.attr("width")/2,
-                                          // xxx
-                                          lease.attr("y")+lease.attr("height")/2,slicename).attr(txt_otherslice);
-                /* hide it right away */
-                otherslicelabel.hide();
-                /* record it */
-                lease.label=otherslicelabel;
-                lease.hover ( function (e) {this.label.toFront();this.label.show();},
-                          function (e) {this.label.hide();} ); 
-            }
+        lease_init_other: function (lease, slicename) 
+        {
+            lease.animate (attr_lease_other,anim_delay);
+            /* a text obj to display the name of the slice that owns that lease */
+            var otherslicelabel = lease.scheduler.paper.text (lease.attr("x")+lease.attr("width")/2,
+                                      // xxx
+                                      lease.attr("y")+lease.attr("height")/2,slicename).attr(txt_otherslice);
+            /* hide it right away */
+            otherslicelabel.hide();
+            /* record it */
+            lease.label=otherslicelabel;
+            lease.hover ( function (e) {this.label.toFront();this.label.show();},
+                      function (e) {this.label.hide();} ); 
         }
 
     });