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