a13856a98a41f7c220dfbba1c502918b5c52ff9c
[unfold.git] / plugins / scheduler2 / static / js / scheduler2.js
1 /*
2 #
3 # Copyright (c) 2013 NITLab, University of Thessaly, CERTH, Greece
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
22 #
23 #
24 # This is a MySlice plugin for the NITOS Scheduler
25 # Nitos Scheduler v1
26 #
27 */
28
29 // XXX groupid = all slots those that go with a min granularity
30
31 /* some params */
32 var scheduler2;
33 var scheduler2Instance;
34 //is ctrl keyboard button pressed
35 var schedulerCtrlPressed = false;
36 //table Id
37 var schedulerTblId = "scheduler-reservation-table";
38 var SCHEDULER_FIRST_COLWIDTH = 200;
39
40
41 /* Number of scheduler slots per hour. Used to define granularity. Should be inferred from resources XXX */
42 var schedulerSlotsPerHour = 6;
43 var RESOURCE_DEFAULT_GRANULARITY    = 1800 /* s */; // should be computed automatically from resource information
44 var DEFAULT_GRANULARITY             = 1800 /* s */; // should be computed automatically from resource information. Test with 600
45 var DEFAULT_PAGE_RANGE = 5;
46
47 var schedulerMaxRows = 12;
48
49 /* All resources */
50 var SchedulerData = [];
51
52 /* ??? */
53 var SchedulerSlots = [];
54
55 var SchedulerDateSelected = new Date();
56 // Round to midnight
57 SchedulerDateSelected.setHours(0,0,0,0);
58
59 /* Filtered resources */
60 var SchedulerDataViewData = [];
61
62 var SchedulerSlotsViewData = [];
63 //Help Variables
64 var _schedulerCurrentCellPosition = 0;
65 //Enable Debug
66 var schedulerDebug = true;
67 //tmp to delete
68 var tmpSchedulerLeases = [];
69
70 var SCHEDULER_COLWIDTH = 50;
71
72
73 /******************************************************************************
74  *                             ANGULAR CONTROLLER                             *
75  ******************************************************************************/
76
77 // Create a private execution space for our controller. When
78 // executing this function expression, we're going to pass in
79 // the Angular reference and our application module.
80 (function (ng, app) {
81
82     // Define our Controller constructor.
83     function Controller($scope) {
84
85         // Store the scope so we can reference it in our
86         // class methods
87         this.scope = $scope;
88
89         // Set up the default scope value.
90         this.scope.errorMessage = null;
91         this.scope.name = "";
92
93         //Pagin
94         $scope.current_page = 1;
95         this.scope.items_per_page = 10;
96         $scope.from = 0; // JORDAN
97
98         $scope.instance = null;
99         $scope.resources = new Array();
100         $scope.slots = SchedulerSlotsViewData;
101         $scope.granularity = DEFAULT_GRANULARITY; /* Will be setup */
102         //$scope.msg = "hello";
103
104         angular.element(document).ready(function() {
105             //console.log('Hello World');
106             //alert('Hello World');
107             //afterAngularRendered();
108         });
109
110         // Pagination
111
112         $scope.range = function() {
113             var range_size = $scope.page_count() > DEFAULT_PAGE_RANGE ? DEFAULT_PAGE_RANGE : $scope.page_count();
114             var ret = [];
115             var start;
116
117             start = $scope.current_page;
118             if ( start > $scope.page_count()-range_size ) {
119               start = $scope.page_count()-range_size+1;
120             }
121
122             for (var i=start; i<start+range_size; i++) {
123               ret.push(i);
124             }
125             return ret;
126         };
127
128         $scope.prevPage = function() {
129           if ($scope.current_page > 1) {
130             $scope.current_page--;
131           }
132         };
133
134         $scope.prevPageDisabled = function() {
135           return $scope.current_page === 1 ? "disabled" : "";
136         };
137   
138         $scope.page_count = function()
139         {
140             // XXX need visible resources only
141             var query_ext, visible_resources_length;
142             if (!$scope.instance)
143                 return 0;
144             query_ext = manifold.query_store.find_analyzed_query_ext($scope.instance.options.query_uuid);
145             var visible_resources_length = 0;
146             query_ext.state.each(function(i, state) {
147                 if (state[STATE_VISIBLE])
148                     visible_resources_length++;
149             });
150             return Math.ceil(visible_resources_length/$scope.items_per_page);
151         };
152   
153         $scope.nextPage = function() {
154           if ($scope.current_page < $scope.page_count()) {
155             $scope.current_page++;
156           }
157         };
158   
159         $scope.nextPageDisabled = function() {
160           return $scope.current_page === $scope.page_count() ? "disabled" : "";
161         }; 
162
163         $scope.setPage = function(n) {
164             $scope.current_page = n;
165         };
166         // END pagination
167
168         // FILTER
169
170         $scope.filter_visible = function(resource)
171         {
172             return manifold.query_store.get_record_state($scope.instance.options.query_uuid, resource['urn'], STATE_VISIBLE);
173         };
174
175         // SELECTION
176
177         $scope._create_new_lease = function(resource_urn, start_time, end_time)
178         {
179             var lease_key, new_lease, data;
180
181             lease_key = manifold.metadata.get_key('lease');
182
183             new_lease = {
184                 resource:   resource_urn,
185                 start_time: start_time,
186                 end_time:   end_time,
187             };
188
189             // This is needed to create a hashable object
190             new_lease.hashCode = manifold.record_hashcode(lease_key.sort());
191             new_lease.equals   = manifold.record_equals(lease_key);
192
193             data = {
194                 state: STATE_SET,
195                 key  : null,
196                 op   : STATE_SET_ADD,
197                 value: new_lease
198             }
199             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
200             /* Add to local cache also, unless we listen to events from outside */
201             if (!(resource_urn in $scope._leases_by_resource))
202                 $scope._leases_by_resource[resource_urn] = [];
203             $scope._leases_by_resource[resource_urn].push(new_lease);
204         }
205
206         $scope._remove_lease = function(other)
207         {
208             var lease_key, other_key, data;
209
210             lease_key = manifold.metadata.get_key('lease');
211
212             // XXX This could be a manifold.record_get_value
213             other_key = {
214                 resource:   other.resource,
215                 start_time: other.start_time,
216                 end_time:   other.end_time
217             }
218             other_key.hashCode = manifold.record_hashcode(lease_key.sort());
219             other_key.equals   = manifold.record_equals(lease_key);
220
221             data = {
222                 state: STATE_SET,
223                 key  : null,
224                 op   : STATE_SET_REMOVE,
225                 value: other_key
226             }
227             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
228             /* Remove from local cache also, unless we listen to events from outside */
229             $scope._leases_by_resource[other.resource] = $.grep($scope._leases_by_resource[other.resource], function(x) { return x != other; });
230
231         }
232
233         $scope.select = function(index, model_lease, model_resource)
234         {
235             var data, resource_granularity;
236
237             //resource_granularity = model_resource.granularity === undefined ? RESOURCE_DEFAULT_GRANULARITY : model_resource.granularity;
238
239             console.log("Selected", index, model_lease, model_resource);
240
241             var day_timestamp = SchedulerDateSelected.getTime() / 1000;
242             var start_time = day_timestamp + index       * model_resource.granularity; // XXX resource_granularity
243             var end_time   = day_timestamp + (index + 1) * model_resource.granularity; //
244             var start_date = new Date(start_time * 1000);
245             var end_date   = new Date(end_time   * 1000);
246
247             var lease_key = manifold.metadata.get_key('lease');
248
249             // We search for leases in the cache we previously constructed
250             var resource_leases = $scope._leases_by_resource[model_resource.urn];
251
252             switch (model_lease.status)
253             {
254                 case 'free': // out
255                 case 'pendingout':
256                     if (resource_leases) {
257                         /* Search for leases before */
258                         $.each(resource_leases, function(i, other) {
259                             if (other.end_time != start_time)
260                                 return true; // ~ continue
261         
262                             /* The lease 'other' is just before, and there should not exist
263                              * any other lease before it */
264                             start_time = other.start_time;
265         
266                             other_key = {
267                                 resource:   other.resource,
268                                 start_time: other.start_time,
269                                 end_time:   other.end_time
270                             }
271                             // This is needed to create a hashable object
272                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());
273                             other_key.equals   = manifold.record_equals(lease_key);
274         
275                             data = {
276                                 state: STATE_SET,
277                                 key  : null,
278                                 op   : STATE_SET_REMOVE,
279                                 value: other_key
280                             }
281                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
282                             /* Remove from local cache also, unless we listen to events from outside */
283                             $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });
284                             return false; // ~ break
285                         });
286         
287                         /* Search for leases after */
288                         $.each(resource_leases, function(i, other) {
289                             if (other.start_time != end_time)
290                                 return true; // ~ continue
291         
292                             /* The lease 'other' is just after, and there should not exist
293                              * any other lease after it */
294                             end_time = other.end_time;
295                             other_key = {
296                                 resource:   other.resource,
297                                 start_time: other.start_time,
298                                 end_time:   other.end_time
299                             }
300                             // This is needed to create a hashable object
301                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());
302                             other_key.equals   = manifold.record_equals(lease_key);
303         
304                             data = {
305                                 state: STATE_SET,
306                                 key  : null,
307                                 op   : STATE_SET_REMOVE,
308                                 value: other_key
309                             }
310                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
311                             /* Remove from local cache also, unless we listen to events from outside */
312                             $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });
313                             return false; // ~ break
314                         });
315                     }
316         
317                     $scope._create_new_lease(model_resource.urn, start_time, end_time);
318                     model_lease.status = (model_lease.status == 'free') ? 'pendingin' : 'selected';
319                     // unless the exact same lease already existed (pending_out status for the lease, not the cell !!)
320
321                     break;
322
323                 case 'selected':
324                 case 'pendingin':
325                     // We remove the cell
326
327                     /* We search for leases including this cell. Either 0, 1 or 2.
328                      * 0 : NOT POSSIBLE, should be checked.
329                      * 1 : either IN or OUT, we have make no change in the session
330                      * 2 : both will be pending, since we have made a change in the session
331                     * /!\ need to properly remove pending_in leases when removed again
332                      */
333                     if (resource_leases) {
334                         $.each(resource_leases, function(i, other) {
335                             if ((other.start_time <= start_time) && (other.end_time >= end_time)) {
336                                 // The cell is part of this lease.
337
338                                 // If the cell is not at the beginning of the lease, we recreate a lease with cells before
339                                 if (start_time > other.start_time) {
340                                     $scope._create_new_lease(model_resource.urn, other.start_time, start_time);
341                                 }
342
343                                 // If the cell is not at the end of the lease, we recreate a lease with cells after
344                                 if (end_time < other.end_time) {
345                                     $scope._create_new_lease(model_resource.urn, end_time, other.end_time);
346                                 }
347                                 
348                                 // The other lease will be removed
349                                 $scope._remove_lease(other);
350                             }
351                             // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status).
352                         });
353                     }
354                 
355                     // cf comment in previous switch case
356                     model_lease.status = (model_lease.status == 'selected') ? 'pendingout' : 'free';
357
358                     break;
359
360                 case 'reserved':
361                 case 'maintainance':
362                     // Do nothing
363                     break;
364             }
365             
366
367             //$scope._dump_leases();
368         };
369   
370         $scope._dump_leases = function()
371         {
372             // DEBUG: display all leases and their status in the log
373             var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid);
374             console.log("--------------------");
375             $.each(leases, function(i, lease) {
376                 var key = manifold.metadata.get_key('lease');
377                 var lease_key = manifold.record_get_value(lease, key);
378                 var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET);
379                 var state_str;
380                 switch(state) {
381                     case STATE_SET_IN:
382                         state_str = 'STATE_SET_IN';
383                         break;
384                     case STATE_SET_OUT:
385                         state_str = 'STATE_SET_OUT';
386                         break;
387                     case STATE_SET_IN_PENDING:
388                         state_str = 'STATE_SET_IN_PENDING';
389                         break;
390                     case STATE_SET_OUT_PENDING:
391                         state_str = 'STATE_SET_OUT_PENDING';
392                         break;
393                     case STATE_SET_IN_SUCCESS:
394                         state_str = 'STATE_SET_IN_SUCCESS';
395                         break;
396                     case STATE_SET_OUT_SUCCESS:
397                         state_str = 'STATE_SET_OUT_SUCCESS';
398                         break;
399                     case STATE_SET_IN_FAILURE:
400                         state_str = 'STATE_SET_IN_FAILURE';
401                         break;
402                     case STATE_SET_OUT_FAILURE:
403                         state_str = 'STATE_SET_OUT_FAILURE';
404                         break;
405                 }
406                 console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str);
407             });
408         };
409
410         // Return this object reference.
411         return (this);
412
413     }
414
415     // Define the Controller as the constructor function.
416     app.controller("SchedulerCtrl", Controller);
417
418 })(angular, ManifoldApp);
419
420 /******************************************************************************
421  *                              MANIFOLD PLUGIN                               *
422  ******************************************************************************/
423
424 (function($) {
425         scheduler2 = Plugin.extend({
426
427             /** XXX to check
428          * @brief Plugin constructor
429          * @param options : an associative array of setting values
430          * @param element : 
431          * @return : a jQuery collection of objects on which the plugin is
432          *     applied, which allows to maintain chainability of calls
433          */
434             init: function(options, element) {
435                 // Call the parent constructor, see FAQ when forgotten
436                 this._super(options, element);
437
438                 var scope = this._get_scope()
439                 scope.instance = this;
440
441                 // XXX not needed
442                 scheduler2Instance = this;
443
444                 // We need to remember the active filter for datatables filtering
445                 // XXX not needed
446                 this.filters = Array();
447
448                 // XXX BETTER !!!!
449                 $(window).delegate('*', 'keypress', function (evt){
450                         alert("erm");
451                       });
452
453                 $(window).keydown(function(evt) {
454                     if (evt.which == 17) { // ctrl
455                         schedulerCtrlPressed = true;
456                     }
457                 }).keyup(function(evt) {
458                     if (evt.which == 17) { // ctrl
459                         schedulerCtrlPressed = false;
460                     }
461                 });
462
463                 // XXX naming
464                 //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);
465
466                 this._resources_received = false;
467                 this._leases_received = false;
468                 
469                 scope._leases_by_resource = {};
470
471                 /* Listening to queries */
472                 this.listen_query(options.query_uuid, 'resources');
473                 this.listen_query(options.query_lease_uuid, 'leases');
474
475                 this.elmt().on('show', this, this.on_show);
476                 this.elmt().on('shown.bs.tab', this, this.on_show);
477                 this.elmt().on('resize', this, this.on_resize);
478
479                 /* Generate slots according to the default granularity. Should
480                  * be updated when resources arrive.  Should be the pgcd in fact XXX */
481                 this._granularity = DEFAULT_GRANULARITY;
482                 scope.granularity = this._granularity;
483                 this.scope_resources_by_key = {};
484
485                 this.do_resize();
486     
487                 scope.from = 0;
488
489                 this._initUI();
490
491             },
492
493             do_resize: function()
494             {
495                 var scope = this._get_scope();
496                 var num_hidden_cells, new_max, lcm;
497
498                 // do_resize has to be called when the window is resized, or one parameter changes
499                 // e.g. when new resources have been received
500                 //
501                 this.resource_granularities = [3600, 1800]; //, 2400]; /* s */
502
503                 /* Compute the slot length to accommodate all resources. This
504                  * is the GCD of all resource granularities. */
505                 this._slot_length = this._gcdn(this.resource_granularities);
506
507                 $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);
508                 //self get width might need fix depending on the template 
509                 var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();
510
511                 /* Number of visible cells...*/
512                 this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);
513
514                 /* ...should be a multiple of the lcm of all encountered granularities. */
515                 lcm = this._lcmn(this.resource_granularities) / this._slot_length;
516                 this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % lcm;
517
518                 // A list of {id, time} dictionaries representing the slots for the given day
519                 this._all_slots = this._generate_all_slots();
520
521                 /* scope also needs this value */
522                 scope.slots = this._all_slots;
523                 scope.slot_length = this._slot_length;
524                 scope.num_visible_cells = this._num_visible_cells;
525                 scope.lcm_colspan = this._lcmn(this.resource_granularities); // XXX WHY ?
526
527                 /* Redraw... */
528                 this._scope_clear_leases();
529                 this._set_all_lease_slots();
530
531                 // Slider max value
532                 if ($('#tblSlider').data('slider') != undefined) {
533                     num_hidden_cells = this._all_slots.length - this._num_visible_cells;
534
535                     $('#tblSlider').slider('setAttribute', 'max', num_hidden_cells);
536                     $('#tblSlider').slider('setValue', scope.from, true);
537                 }
538                 this._get_scope().$apply();
539
540
541             },
542
543             on_show: function(e)
544             {
545                 var self = e.data;
546                 self.do_resize();
547                 self._get_scope().$apply();
548             },
549
550             on_resize: function(e)
551             {
552                 var self = e.data;
553                 self.do_resize();
554                 self._get_scope().$apply();
555             },
556
557             /* Handlers */
558
559             _get_scope : function()
560             {
561                 return angular.element(document.getElementById('SchedulerCtrl')).scope();
562             },
563             
564             _scope_set_resources : function()
565             {
566                 var self = this;
567                 var scope = this._get_scope();
568
569                 var records = manifold.query_store.get_records(this.options.query_uuid);
570
571                 scope.resources = [];
572
573                 $.each(records, function(i, record) {
574                     if (!record.exclusive)
575                         return true; // ~ continue
576
577                     // copy not to modify original record
578                     var resource = jQuery.extend(true, {}, record);
579
580                     // Fix granularity
581                     //resource_granularity = ((resource.granularity === undefined) || (typeof(resource.granularity) != "number")) ? RESOURCE_DEFAULT_GRANULARITY : resource.granularity;
582                     if (typeof(resource.granularity) != "number")
583                         resource.granularity = RESOURCE_DEFAULT_GRANULARITY;
584                     resource.leases = []; // a list of occupied timeslots
585
586                     self.scope_resources_by_key[resource['urn']] = resource;
587                     scope.resources.push(resource);
588                 });
589             },
590
591             _scope_clear_leases: function()
592             {
593                 var time, now;
594                 var self = this;
595                 var scope = this._get_scope();
596
597                 now = new Date().getTime();
598
599                 // Setup leases with a default free status...
600                 $.each(this.scope_resources_by_key, function(resource_key, resource) {
601                     resource.leases = [];
602                     var colspan_lease = resource.granularity / self._slot_length; //eg. 3600 / 1800 => 2 cells
603                     time = SchedulerDateSelected.getTime();
604                     for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity
605                         resource.leases.push({
606                             id:     'coucou',
607                             status: (time < now) ? 'disabled':  'free', // 'selected', 'reserved', 'maintenance' XXX pending ??
608                         });
609                         time += resource.granularity * 1000;
610                     }
611                 });
612
613             },
614
615             _scope_set_leases: function()
616             {
617                     var status;
618                 var self = this;
619                 var scope = this._get_scope();
620             
621                 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
622                     //console.log("SET LEASES", lease.resource, new Date(lease.start_time* 1000), new Date(lease.end_time* 1000));
623                     // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work
624
625                     // Populate leases by resource array: this will help us merging leases later
626
627                     // let's only put _our_ leases
628                     lease_status = manifold.query_store.get_record_state(self.options.query_lease_uuid, lease_key, STATE_SET);
629                     if (lease_status != STATE_SET_IN)
630                         return true; // ~continue
631                     if (!(lease.resource in scope._leases_by_resource))
632                         scope._leases_by_resource[lease.resource] = [];
633                     scope._leases_by_resource[lease.resource].push(lease);
634
635                 });
636
637                 this._set_all_lease_slots();
638             },
639
640             _set_all_lease_slots: function()
641             {
642                 var self = this;
643             
644                 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
645                     self._set_lease_slots(lease_key, lease);
646                 });
647             },
648
649             on_resources_query_done: function(data)
650             {
651                 this._resources_received = true;
652                 this._scope_set_resources();
653                 this._scope_clear_leases();
654                 if (this._leases_received)
655                     this._scope_set_leases();
656                     
657                 this._get_scope().$apply();
658             },
659
660             on_leases_query_done: function(data)
661             {
662                 this._leases_received = true;
663                 if (this._resources_received) {
664                     this._scope_set_leases();
665                     this._get_scope().$apply();
666                 }
667             },
668
669             /* Filters on resources */
670             on_resources_filter_added:   function(filter) { this._get_scope().$apply(); },
671             on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },
672             on_resources_filter_clear:   function()       { this._get_scope().$apply(); },
673
674             /* Filters on leases ? */
675             on_leases_filter_added:      function(filter) { this._get_scope().$apply(); },
676             on_leases_filter_removed:    function(filter) { this._get_scope().$apply(); },
677             on_leases_filter_clear:      function()       { this._get_scope().$apply(); },
678
679             on_field_state_changed: function(data)
680             {
681                 /*
682                 this._set_lease_slots(lease_key, lease);
683
684                 switch(data.state) {
685                     case STATE_SET:
686                         switch(data.op) {
687                             case STATE_SET_IN:
688                             case STATE_SET_IN_SUCCESS:
689                             case STATE_SET_OUT_FAILURE:
690                                 this.set_checkbox_from_data(data.value, true);
691                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
692                                 break;  
693                             case STATE_SET_OUT:
694                             case STATE_SET_OUT_SUCCESS:
695                             case STATE_SET_IN_FAILURE:
696                                 this.set_checkbox_from_data(data.value, false);
697                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
698                                 break;
699                             case STATE_SET_IN_PENDING:
700                                 this.set_checkbox_from_data(data.key, true);
701                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_ADDED);
702                                 break;  
703                             case STATE_SET_OUT_PENDING:
704                                 this.set_checkbox_from_data(data.key, false);
705                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_REMOVED);
706                                 break;
707                         }
708                         break;
709
710                     case STATE_WARNINGS:
711                         this.change_status(data.key, data.value);
712                         break;
713                 }
714                 */
715             },
716
717
718             /* INTERNAL FUNCTIONS */
719
720             _set_lease_slots: function(lease_key, lease)
721             {
722                 var resource, lease_status, lease_class;
723                 var day_timestamp, id_start, id_end, colspan_lease;
724
725                 resource = this.scope_resources_by_key[lease.resource];
726                 day_timestamp = SchedulerDateSelected.getTime() / 1000;
727                 if(resource === undefined){
728                     console.log('resource undefined = '+lease.resource);
729                     return;
730                 }
731                 id_start = Math.floor((lease.start_time - day_timestamp) / resource.granularity);
732
733                 /* Some leases might be in the past */
734                 if (id_start < 0)
735                     id_start = 0;
736                 /* Leases in the future: ignore */
737                 if (id_start >= this._all_slots.length)
738                     return true; // ~ continue
739
740                 id_end   = Math.ceil((lease.end_time   - day_timestamp) / resource.granularity);
741                 colspan_lease = resource.granularity / this._slot_length; //eg. 3600 / 1800 => 2 cells
742                 if (id_end >= this._all_slots.length / colspan_lease) {
743                     /* Limit the display to the current day */
744                     id_end = this._all_slots.length / colspan_lease
745                 }
746                 lease_status = manifold.query_store.get_record_state(this.options.query_lease_uuid, lease_key, STATE_SET);
747                 // the same slots might be affected multiple times.
748                 // PENDING_IN + PENDING_OUT => IN 
749                 //
750                 // RESERVED vs SELECTED !
751                 //
752                 // PENDING !!
753                 switch(lease_status) {
754                     case STATE_SET_IN:
755                         lease_class = 'selected'; // my leases
756                         lease_success = '';
757                         break;
758                     case STATE_SET_IN_SUCCESS:
759                         lease_class = 'selected'; // my leases
760                         lease_success = 'success';
761                     case STATE_SET_OUT_FAILURE:
762                         lease_class = 'selected'; // my leases
763                         lease_success = 'failure';
764                         break;
765                     case STATE_SET_OUT:
766                         lease_class = 'reserved'; // other leases
767                         lease_success = '';
768                         break;
769                     case STATE_SET_OUT_SUCCESS:
770                         lease_class = 'free'; // other leases
771                         lease_success = 'success';
772                         break;
773                     case STATE_SET_IN_FAILURE:
774                         lease_class = 'free'; // other leases
775                         lease_success = 'failure';
776                         break;
777                     case STATE_SET_IN_PENDING:
778                         lease_class = 'pendingin';
779                         lease_success = '';
780                         break;
781                     case STATE_SET_OUT_PENDING:
782                         // pending_in & pending_out == IN == replacement
783                         if (resource.leases[i].status == 'pendingin')
784                             lease_class = 'pendingin'
785                         else
786                             lease_class = 'pendingout';
787                         lease_success = '';
788                         break;
789                 
790                 }
791
792                 for (i = id_start; i < id_end; i++) {
793                     resource.leases[i].status = lease_class;
794                     resource.leases[i].success = lease_success;
795                 }
796             },
797
798 /* XXX IN TEMPLATE XXX
799                 if (SchedulerDataViewData.length == 0) {
800                     $("#plugin-scheduler").hide();
801                     $("#plugin-scheduler-empty").show();
802                     tmpScope.clearStuff();
803                 } else {
804                     $("#plugin-scheduler-empty").hide();
805                     $("#plugin-scheduler").show();
806                     // initSchedulerResources
807                     tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);
808                 }
809 */
810
811             /**
812              * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.
813              */
814             _initUI: function() 
815             {
816                 var self = this;
817                 var scope = self._get_scope();
818
819                 var num_hidden_cells;
820
821                 $("#DateToRes").datepicker({
822                         dateFormat: "D, d M yy",
823                     onRender: function(date) {
824                         return date.valueOf() < now.valueOf() ? 'disabled' : '';
825                     }
826                 }).on('changeDate', function(ev) {
827                     SchedulerDateSelected = new Date(ev.date);
828                     SchedulerDateSelected.setHours(0,0,0,0);
829                     // Set slider to origin
830                     //$('#tblSlider').slider('setValue', 0); // XXX
831                     // Refresh leases
832                     self._scope_clear_leases();
833                     self._set_all_lease_slots();
834                     // Refresh display
835                     self._get_scope().$apply();
836                 }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');
837
838                 //init Slider
839                 num_hidden_cells = self._all_slots.length - self._num_visible_cells;
840                 init_cell = (new Date().getHours() - 1) * 3600 / self._granularity;
841                 if (init_cell > num_hidden_cells)
842                     init_cell = num_hidden_cells;
843
844                 $('#tblSlider').slider({
845                     min: 0,
846                     max: num_hidden_cells,
847                     value: init_cell,
848                 }).on('slide', function(ev) {
849                     var scope = self._get_scope();
850                     scope.from = ev.value;
851                     scope.$apply();
852                 });
853                 scope.from = init_cell;
854                 scope.$apply();
855
856                 $("#plugin-scheduler-loader").hide();
857                 $("#plugin-scheduler").show();
858             },
859
860         // PRIVATE METHODS
861
862         /**
863          * Greatest common divisor
864          */
865         _gcd : function(x, y)
866         {
867             return (y==0) ? x : this._gcd(y, x % y);
868         },
869
870         _gcdn : function(array)
871         {
872             var self = this;
873             return array.reduce(function(prev, cur, idx, arr) { return self._gcd(prev, cur); });
874         },
875
876         /**
877          * Least common multiple
878          */
879         _lcm : function(x, y)
880         {
881             return x * y / this._gcd(x, y);
882         },
883
884         _lcmn : function(array)
885         {
886             var self = this;
887             return array.reduce(function(prev, cur, idx, arr) { return self._lcm(prev, cur); });
888         },
889     
890         _pad_str : function(i)
891         {
892             return (i < 10) ? "0" + i : "" + i;
893         },
894
895         /**
896          * Member variables used:
897          *   _granularity
898          * 
899          * Returns:
900          *   A list of {id, time} dictionaries.
901          */
902         _generate_all_slots: function()
903         {
904             var slots = [];
905             // Start with a random date (a first of a month), only time will matter
906             var d = new Date(2014, 1, 1, 0, 0, 0, 0);
907             var i = 0;
908             // Loop until we change the day
909             while (d.getDate() == 1) {
910                 // Nicely format the time...
911                 var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());
912                 /// ...and add the slot to the list of results
913                 slots.push({ id: i, time: tmpTime });
914                 // Increment the date with the granularity
915                 d = new Date(d.getTime() + this._slot_length * 1000);
916                 i++;
917             }
918             return slots;
919
920         },
921     });
922
923     /* Plugin registration */
924     $.plugin('Scheduler2', scheduler2);
925
926 })(jQuery);
927
928
929