various fixes to slice page plugins
[unfold.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             $.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;\r
236 \r
237             console.log("Selected", index, model_lease, model_resource);\r
238 \r
239             var day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
240             var start_time = day_timestamp + index       * model_resource.granularity;\r
241             var end_time   = day_timestamp + (index + 1) * model_resource.granularity;\r
242             var start_date = new Date(start_time * 1000);\r
243             var end_date   = new Date(end_time   * 1000);\r
244 \r
245             var lease_key = manifold.metadata.get_key('lease');\r
246 \r
247             // We search for leases in the cache we previously constructed\r
248             var resource_leases = $scope._leases_by_resource[model_resource.urn];\r
249 \r
250             switch (model_lease.status)\r
251             {\r
252                 case 'free': // out\r
253                 case 'pendingout':\r
254                     if (resource_leases) {\r
255                         /* Search for leases before */\r
256                         $.each(resource_leases, function(i, other) {\r
257                             if (other.end_time != start_time)\r
258                                 return true; // ~ continue\r
259         \r
260                             /* The lease 'other' is just before, and there should not exist\r
261                              * any other lease before it */\r
262                             start_time = other.start_time;\r
263         \r
264                             other_key = {\r
265                                 resource:   other.resource,\r
266                                 start_time: other.start_time,\r
267                                 end_time:   other.end_time\r
268                             }\r
269                             // This is needed to create a hashable object\r
270                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
271                             other_key.equals   = manifold.record_equals(lease_key);\r
272         \r
273                             data = {\r
274                                 state: STATE_SET,\r
275                                 key  : null,\r
276                                 op   : STATE_SET_REMOVE,\r
277                                 value: other_key\r
278                             }\r
279                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
280                             /* Remove from local cache also, unless we listen to events from outside */\r
281                             $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
282                             return false; // ~ break\r
283                         });\r
284         \r
285                         /* Search for leases after */\r
286                         $.each(resource_leases, function(i, other) {\r
287                             if (other.start_time != end_time)\r
288                                 return true; // ~ continue\r
289         \r
290                             /* The lease 'other' is just after, and there should not exist\r
291                              * any other lease after it */\r
292                             end_time = other.end_time;\r
293                             other_key = {\r
294                                 resource:   other.resource,\r
295                                 start_time: other.start_time,\r
296                                 end_time:   other.end_time\r
297                             }\r
298                             // This is needed to create a hashable object\r
299                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
300                             other_key.equals   = manifold.record_equals(lease_key);\r
301         \r
302                             data = {\r
303                                 state: STATE_SET,\r
304                                 key  : null,\r
305                                 op   : STATE_SET_REMOVE,\r
306                                 value: other_key\r
307                             }\r
308                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, other_key);\r
309                             /* Remove from local cache also, unless we listen to events from outside */\r
310                             $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
311                             return false; // ~ break\r
312                         });\r
313                     }\r
314         \r
315                     $scope._create_new_lease(model_resource.urn, start_time, end_time);\r
316                     model_lease.status = 'pendingin'; \r
317                     // unless the exact same lease already existed (pending_out status for the lease, not the cell !!)\r
318 \r
319                     break;\r
320 \r
321                 case 'selected':\r
322                 case 'pendingin':\r
323                     // We remove the cell\r
324 \r
325                     /* We search for leases including this cell. Either 0, 1 or 2.\r
326                      * 0 : NOT POSSIBLE, should be checked.\r
327                      * 1 : either IN or OUT, we have make no change in the session\r
328                      * 2 : both will be pending, since we have made a change in the session\r
329                     * /!\ need to properly remove pending_in leases when removed again\r
330                      */\r
331                     if (resource_leases) {\r
332                         $.each(resource_leases, function(i, other) {\r
333                             if ((other.start_time <= start_time) && (other.end_time >= end_time)) {\r
334                                 // The cell is part of this lease.\r
335 \r
336                                 // If the cell is not at the beginning of the lease, we recreate a lease with cells before\r
337                                 if (start_time > other.start_time) {\r
338                                     $scope._create_new_lease(model_resource.urn, other.start_time, start_time);\r
339                                 }\r
340 \r
341                                 // If the cell is not at the end of the lease, we recreate a lease with cells after\r
342                                 if (end_time < other.end_time) {\r
343                                     $scope._create_new_lease(model_resource.urn, end_time, other.end_time);\r
344                                 }\r
345                                 \r
346                                 // The other lease will be removed\r
347                                 $scope._remove_lease(other);\r
348                             }\r
349                             // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status).\r
350                         });\r
351                     }\r
352                 \r
353                     // cf comment in previous switch case\r
354                     model_lease.status = 'pendingout'; \r
355 \r
356                     break;\r
357 \r
358                 case 'reserved':\r
359                 case 'maintainance':\r
360                     // Do nothing\r
361                     break;\r
362             }\r
363             \r
364 \r
365             $scope._dump_leases();\r
366         };\r
367   \r
368         $scope._dump_leases = function()\r
369         {\r
370             // DEBUG: display all leases and their status in the log\r
371             var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid);\r
372             console.log("--------------------");\r
373             $.each(leases, function(i, lease) {\r
374                 var key = manifold.metadata.get_key('lease');\r
375                 var lease_key = manifold.record_get_value(lease, key);\r
376                 var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET);\r
377                 var state_str;\r
378                 switch(state) {\r
379                     case STATE_SET_IN:\r
380                         state_str = 'STATE_SET_IN';\r
381                         break;\r
382                     case STATE_SET_OUT:\r
383                         state_str = 'STATE_SET_OUT';\r
384                         break;\r
385                     case STATE_SET_IN_PENDING:\r
386                         state_str = 'STATE_SET_IN_PENDING';\r
387                         break;\r
388                     case STATE_SET_OUT_PENDING:\r
389                         state_str = 'STATE_SET_OUT_PENDING';\r
390                         break;\r
391                     case STATE_SET_IN_SUCCESS:\r
392                         state_str = 'STATE_SET_IN_SUCCESS';\r
393                         break;\r
394                     case STATE_SET_OUT_SUCCESS:\r
395                         state_str = 'STATE_SET_OUT_SUCCESS';\r
396                         break;\r
397                     case STATE_SET_IN_FAILURE:\r
398                         state_str = 'STATE_SET_IN_FAILURE';\r
399                         break;\r
400                     case STATE_SET_OUT_FAILURE:\r
401                         state_str = 'STATE_SET_OUT_FAILURE';\r
402                         break;\r
403                 }\r
404                 console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str);\r
405             });\r
406         };\r
407 \r
408         // Return this object reference.\r
409         return (this);\r
410 \r
411     }\r
412 \r
413     // Define the Controller as the constructor function.\r
414     app.controller("SchedulerCtrl", Controller);\r
415 \r
416 })(angular, ManifoldApp);\r
417 \r
418 /******************************************************************************\r
419  *                              MANIFOLD PLUGIN                               *\r
420  ******************************************************************************/\r
421 \r
422 (function($) {\r
423         scheduler2 = Plugin.extend({\r
424 \r
425             /** XXX to check\r
426          * @brief Plugin constructor\r
427          * @param options : an associative array of setting values\r
428          * @param element : \r
429          * @return : a jQuery collection of objects on which the plugin is\r
430          *     applied, which allows to maintain chainability of calls\r
431          */\r
432             init: function(options, element) {\r
433                 // Call the parent constructor, see FAQ when forgotten\r
434                 this._super(options, element);\r
435 \r
436                 var scope = this._get_scope()\r
437                 scope.instance = this;\r
438 \r
439                 // XXX not needed\r
440                 scheduler2Instance = this;\r
441 \r
442                 // We need to remember the active filter for datatables filtering\r
443                 // XXX not needed\r
444                 this.filters = Array();\r
445 \r
446                 // XXX BETTER !!!!\r
447                 $(window).delegate('*', 'keypress', function (evt){\r
448                         alert("erm");\r
449                       });\r
450 \r
451                 $(window).keydown(function(evt) {\r
452                     if (evt.which == 17) { // ctrl\r
453                         schedulerCtrlPressed = true;\r
454                     }\r
455                 }).keyup(function(evt) {\r
456                     if (evt.which == 17) { // ctrl\r
457                         schedulerCtrlPressed = false;\r
458                     }\r
459                 });\r
460 \r
461                 // XXX naming\r
462                 //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
463 \r
464                 this._resources_received = false;\r
465                 this._leases_received = false;\r
466                 \r
467                 scope._leases_by_resource = {};\r
468 \r
469                 /* Listening to queries */\r
470                 this.listen_query(options.query_uuid, 'resources');\r
471                 this.listen_query(options.query_lease_uuid, 'leases');\r
472 \r
473                 this.elmt().on('show', this, this.on_show);\r
474                 this.elmt().on('shown.bs.tab', this, this.on_show);\r
475                 this.elmt().on('resize', this, this.on_resize);\r
476 \r
477                 /* Generate slots according to the default granularity. Should\r
478                  * be updated when resources arrive.  Should be the pgcd in fact XXX */\r
479                 this._granularity = DEFAULT_GRANULARITY;\r
480                 scope.granularity = this._granularity;\r
481                 this._all_slots = this._generate_all_slots();\r
482 \r
483                 // A list of {id, time} dictionaries representing the slots for the given day\r
484                 scope.slots = this._all_slots;\r
485                 this.scope_resources_by_key = {};\r
486 \r
487                 this.do_resize();\r
488     \r
489                 scope.from = 0;\r
490 \r
491                 this._initUI();\r
492 \r
493             },\r
494 \r
495             do_resize: function()\r
496             {\r
497                 var scope = this._get_scope();\r
498 \r
499                 $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);\r
500                 //self get width might need fix depending on the template \r
501                 var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();\r
502 \r
503                 /* Number of visible cells...*/\r
504                 this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);\r
505                 /* ...should be a multiple of the lcm of all encountered granularities. */\r
506                 // XXX Should be updated everytime a new resource is added\r
507                 this._lcm_colspan = this._lcm(this._granularity, RESOURCE_DEFAULT_GRANULARITY) / this._granularity;\r
508                 this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % this._lcm_colspan;\r
509                 /* scope also needs this value */\r
510                 scope.num_visible_cells = this._num_visible_cells;\r
511                 scope.lcm_colspan = this._lcm_colspan;\r
512 \r
513                 // Slider max value\r
514 \r
515                 if ($('#tblSlider').data('slider') != undefined) {\r
516                     var new_max = (this._all_slots.length - this._num_visible_cells) / this._lcm_colspan;\r
517                     $('#tblSlider').slider('setAttribute', 'max', new_max);\r
518                 }\r
519 \r
520             },\r
521 \r
522             on_show: function(e)\r
523             {\r
524                 var self = e.data;\r
525                 self.do_resize();\r
526                 self._get_scope().$apply();\r
527             },\r
528 \r
529             on_resize: function(e)\r
530             {\r
531                 var self = e.data;\r
532                 self.do_resize();\r
533                 self._get_scope().$apply();\r
534             },\r
535 \r
536             /* Handlers */\r
537 \r
538             _get_scope : function()\r
539             {\r
540                 return angular.element(document.getElementById('SchedulerCtrl')).scope();\r
541             },\r
542             \r
543             _scope_set_resources : function()\r
544             {\r
545                 var self = this;\r
546                 var scope = this._get_scope();\r
547 \r
548                 var records = manifold.query_store.get_records(this.options.query_uuid);\r
549 \r
550                 scope.resources = [];\r
551 \r
552                 $.each(records, function(i, record) {\r
553                     if (!record.exclusive)\r
554                         return true; // ~ continue\r
555 \r
556                     // copy not to modify original record\r
557                     var resource = jQuery.extend(true, {}, record);\r
558 \r
559                     // Fix granularity\r
560                     resource.granularity = typeof(resource.granularity) == "number" ? resource.granularity : RESOURCE_DEFAULT_GRANULARITY;\r
561                     resource.leases = []; // a list of occupied timeslots\r
562 \r
563                     self.scope_resources_by_key[resource['urn']] = resource;\r
564                     scope.resources.push(resource);\r
565                 });\r
566             },\r
567 \r
568             _scope_clear_leases: function()\r
569             {\r
570                 var self = this;\r
571                 var scope = this._get_scope();\r
572 \r
573                 // Setup leases with a default free status...\r
574                 $.each(this.scope_resources_by_key, function(resource_key, resource) {\r
575                     resource.leases = [];\r
576                     var colspan_lease = resource.granularity / self._granularity; //eg. 3600 / 1800 => 2 cells\r
577                     for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity\r
578                         resource.leases.push({\r
579                             id:     'coucou',\r
580                             status: 'free', // 'selected', 'reserved', 'maintenance' XXX pending ??\r
581                         });\r
582                     }\r
583                 });\r
584 \r
585             },\r
586 \r
587             _scope_set_leases: function()\r
588             {\r
589                 var self = this;\r
590                 var scope = this._get_scope();\r
591             \r
592                 var leases = manifold.query_store.get_records(this.options.query_lease_uuid);\r
593                 $.each(leases, function(i, lease) {\r
594 \r
595                     console.log("SET LEASES", new Date(lease.start_time* 1000));\r
596                     console.log("          ", new Date(lease.end_time* 1000));\r
597                     // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work\r
598 \r
599                     // Populate leases by resource array: this will help us merging leases later\r
600                     if (!(lease.resource in scope._leases_by_resource))\r
601                         scope._leases_by_resource[lease.resource] = [];\r
602                     scope._leases_by_resource[lease.resource].push(lease);\r
603 \r
604                     var resource = self.scope_resources_by_key[lease.resource];\r
605                     var day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
606 \r
607                     var id_start = (lease.start_time - day_timestamp) / resource.granularity;\r
608                     if (id_start < 0) {\r
609                         /* Some leases might be in the past */\r
610                         id_start = 0;\r
611                     }\r
612     \r
613                     var id_end   = (lease.end_time   - day_timestamp) / resource.granularity - 1;\r
614                     var colspan_lease = resource.granularity / self._granularity; //eg. 3600 / 1800 => 2 cells\r
615                     if (id_end >= self._all_slots.length / colspan_lease) {\r
616                         /* Limit the display to the current day */\r
617                         id_end = self._all_slots.length / colspan_lease\r
618                     }\r
619 \r
620                     for (i = id_start; i <= id_end; i++)\r
621                         // the same slots might be affected multiple times.\r
622                         // PENDING_IN + PENDING_OUT => IN \r
623                         //\r
624                         // RESERVED vs SELECTED !\r
625                         //\r
626                         // PENDING !!\r
627                         resource.leases[i].status = 'selected'; \r
628                 });\r
629             },\r
630 \r
631             on_resources_query_done: function(data)\r
632             {\r
633                 this._resources_received = true;\r
634                 this._scope_set_resources();\r
635                 this._scope_clear_leases();\r
636                 if (this._leases_received)\r
637                     this._scope_set_leases();\r
638                     \r
639                 this._get_scope().$apply();\r
640             },\r
641 \r
642             on_leases_query_done: function(data)\r
643             {\r
644                 this._leases_received = true;\r
645                 if (this._resources_received) {\r
646                     this._scope_set_leases();\r
647                     this._get_scope().$apply();\r
648                 }\r
649             },\r
650 \r
651             /* Filters on resources */\r
652             on_resources_filter_added:   function(filter) { this._get_scope().$apply(); },\r
653             on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },\r
654             on_resources_filter_clear:   function()       { this._get_scope().$apply(); },\r
655 \r
656             /* Filters on leases ? */\r
657             on_leases_filter_added:      function(filter) { this._get_scope().$apply(); },\r
658             on_leases_filter_removed:    function(filter) { this._get_scope().$apply(); },\r
659             on_leases_filter_clear:      function()       { this._get_scope().$apply(); },\r
660 \r
661             /* INTERNAL FUNCTIONS */\r
662 \r
663 /* XXX IN TEMPLATE XXX\r
664                 if (SchedulerDataViewData.length == 0) {\r
665                     $("#plugin-scheduler").hide();\r
666                     $("#plugin-scheduler-empty").show();\r
667                     tmpScope.clearStuff();\r
668                 } else {\r
669                     $("#plugin-scheduler-empty").hide();\r
670                     $("#plugin-scheduler").show();\r
671                     // initSchedulerResources\r
672                     tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);\r
673                 }\r
674 */\r
675 \r
676             /**\r
677              * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.\r
678              */\r
679             _initUI: function() \r
680             {\r
681                 var self = this;\r
682 \r
683                 $("#DateToRes").datepicker({\r
684                     onRender: function(date) {\r
685                         return date.valueOf() < now.valueOf() ? 'disabled' : '';\r
686                     }\r
687                 }).on('changeDate', function(ev) {\r
688                     SchedulerDateSelected = new Date(ev.date);\r
689                     SchedulerDateSelected.setHours(0,0,0,0);\r
690                     // Set slider to origin\r
691                     $('#tblSlider').slider('setValue', 0); // XXX\r
692                     // Refresh leases\r
693                     self._scope_clear_leases();\r
694                     self._scope_set_leases();\r
695                     // Refresh display\r
696                     self._get_scope().$apply();\r
697                 }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');\r
698 \r
699                 //init Slider\r
700                 $('#tblSlider').slider({\r
701                     min: 0,\r
702                     max: (self._all_slots.length - self._num_visible_cells) / self._lcm_colspan,\r
703                     value: 0,\r
704                 }).on('slide', function(ev) {\r
705                     var scope = self._get_scope();\r
706                     scope.from = ev.value * self._lcm_colspan;\r
707                     scope.$apply();\r
708                 });\r
709 \r
710                 $("#plugin-scheduler-loader").hide();\r
711                 $("#plugin-scheduler").show();\r
712             },\r
713 \r
714         // PRIVATE METHODS\r
715 \r
716         /**\r
717          * Greatest common divisor\r
718          */\r
719         _gcd : function(x, y)\r
720         {\r
721             return (y==0) ? x : this._gcd(y, x % y);\r
722         },\r
723 \r
724         /**\r
725          * Least common multiple\r
726          */\r
727         _lcm : function(x, y)\r
728         {\r
729             return x * y / this._gcd(x, y);\r
730         },\r
731     \r
732         _pad_str : function(i)\r
733         {\r
734             return (i < 10) ? "0" + i : "" + i;\r
735         },\r
736 \r
737         /**\r
738          * Member variables used:\r
739          *   _granularity\r
740          * \r
741          * Returns:\r
742          *   A list of {id, time} dictionaries.\r
743          */\r
744         _generate_all_slots: function()\r
745         {\r
746             var slots = [];\r
747             // Start with a random date (a first of a month), only time will matter\r
748             var d = new Date(2014, 1, 1, 0, 0, 0, 0);\r
749             var i = 0;\r
750             // Loop until we change the day\r
751             while (d.getDate() == 1) {\r
752                 // Nicely format the time...\r
753                 var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());\r
754                 /// ...and add the slot to the list of results\r
755                 slots.push({ id: i, time: tmpTime });\r
756                 // Increment the date with the granularity\r
757                 d = new Date(d.getTime() + this._granularity * 1000);\r
758                 i++;\r
759             }\r
760             return slots;\r
761 \r
762         },\r
763     });\r
764 \r
765     /* Plugin registration */\r
766     $.plugin('Scheduler2', scheduler2);\r
767 \r
768 })(jQuery);\r
769 \r
770 \r
771 \r