3 # Copyright (c) 2013 NITLab, University of Thessaly, CERTH, Greece
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:
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
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
24 # This is a MySlice plugin for the NITOS Scheduler
29 // XXX groupid = all slots those that go with a min granularity
33 var scheduler2Instance;
34 //is ctrl keyboard button pressed
35 var schedulerCtrlPressed = false;
37 var schedulerTblId = "scheduler-reservation-table";
38 var SCHEDULER_FIRST_COLWIDTH = 200;
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;
47 var schedulerMaxRows = 12;
50 var SchedulerData = [];
53 var SchedulerSlots = [];
55 var SchedulerDateSelected = new Date();
57 SchedulerDateSelected.setHours(0,0,0,0);
59 /* Filtered resources */
60 var SchedulerDataViewData = [];
62 var SchedulerSlotsViewData = [];
64 var _schedulerCurrentCellPosition = 0;
66 var schedulerDebug = true;
68 var tmpSchedulerLeases = [];
70 var SCHEDULER_COLWIDTH = 50;
73 /******************************************************************************
74 * ANGULAR CONTROLLER *
75 ******************************************************************************/
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.
82 // Define our Controller constructor.
83 function Controller($scope) {
85 // Store the scope so we can reference it in our
89 // Set up the default scope value.
90 this.scope.errorMessage = null;
94 $scope.current_page = 1;
95 this.scope.items_per_page = 10;
96 $scope.from = 0; // JORDAN
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";
104 angular.element(document).ready(function() {
105 //console.log('Hello World');
106 //alert('Hello World');
107 //afterAngularRendered();
112 $scope.range = function() {
113 var range_size = $scope.page_count() > DEFAULT_PAGE_RANGE ? DEFAULT_PAGE_RANGE : $scope.page_count();
117 start = $scope.current_page;
118 if ( start > $scope.page_count()-range_size ) {
119 start = $scope.page_count()-range_size+1;
122 for (var i=start; i<start+range_size; i++) {
128 $scope.prevPage = function() {
129 if ($scope.current_page > 1) {
130 $scope.current_page--;
134 $scope.prevPageDisabled = function() {
135 return $scope.current_page === 1 ? "disabled" : "";
138 $scope.page_count = function()
140 // XXX need visible resources only
141 var query_ext, visible_resources_length;
142 if (!$scope.instance)
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++;
150 return Math.ceil(visible_resources_length/$scope.items_per_page);
153 $scope.nextPage = function() {
154 if ($scope.current_page < $scope.page_count()) {
155 $scope.current_page++;
159 $scope.nextPageDisabled = function() {
160 return $scope.current_page === $scope.page_count() ? "disabled" : "";
163 $scope.setPage = function(n) {
164 $scope.current_page = n;
170 $scope.filter_visible = function(resource)
172 return manifold.query_store.get_record_state($scope.instance.options.query_uuid, resource['urn'], STATE_VISIBLE);
177 $scope._create_new_lease = function(resource_urn, start_time, end_time)
179 var lease_key, new_lease, data;
181 lease_key = manifold.metadata.get_key('lease');
184 resource: resource_urn,
185 start_time: start_time,
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);
199 manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
201 /* Add to local cache also, unless we listen to events from outside */
202 if (!(resource_urn in $scope._leases_by_resource)){
203 $scope._leases_by_resource[resource_urn] = [];
204 /* Add the resource of the selected timeslot to the pending list */
211 manifold.raise_event($scope.instance.options.query_uuid, FIELD_STATE_CHANGED, data_resource);
213 $scope._leases_by_resource[resource_urn].push(new_lease);
217 $scope._remove_lease = function(other)
219 var lease_key, other_key, data;
221 lease_key = manifold.metadata.get_key('lease');
223 // XXX This could be a manifold.record_get_value
225 resource: other.resource,
226 start_time: other.start_time,
227 end_time: other.end_time
229 other_key.hashCode = manifold.record_hashcode(lease_key.sort());
230 other_key.equals = manifold.record_equals(lease_key);
235 op : STATE_SET_REMOVE,
238 manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
239 /* Remove from local cache also, unless we listen to events from outside */
240 $scope._leases_by_resource[other.resource] = $.grep($scope._leases_by_resource[other.resource], function(x) { return x != other; });
241 /* Last lease removed for this resource -> remove the resource from the list */
242 if($scope._leases_by_resource.hasOwnProperty(other.resource) && $scope._leases_by_resource[other.resource].length == 0){
243 /* remove resource from the list of selected resources */
247 op : STATE_SET_REMOVE,
248 value: other.resource
250 manifold.raise_event($scope.instance.options.query_uuid, FIELD_STATE_CHANGED, data_resource);
255 $scope.select = function(index, model_lease, model_resource)
257 var data, resource_granularity;
259 //resource_granularity = model_resource.granularity === undefined ? RESOURCE_DEFAULT_GRANULARITY : model_resource.granularity;
261 console.log("Selected", index, model_lease, model_resource);
263 var day_timestamp = SchedulerDateSelected.getTime() / 1000;
264 var start_time = day_timestamp + index * model_resource.granularity; // XXX resource_granularity
265 var end_time = day_timestamp + (index + 1) * model_resource.granularity; //
266 var start_date = new Date(start_time * 1000);
267 var end_date = new Date(end_time * 1000);
269 var lease_key = manifold.metadata.get_key('lease');
271 // We search for leases in the cache we previously constructed
272 var resource_leases = $scope._leases_by_resource[model_resource.urn];
274 switch (model_lease.status)
278 if (resource_leases) {
279 /* Search for leases before */
280 $.each(resource_leases, function(i, other) {
281 if (other.end_time != start_time)
282 return true; // ~ continue
284 /* The lease 'other' is just before, and there should not exist
285 * any other lease before it */
286 start_time = other.start_time;
289 resource: other.resource,
290 start_time: other.start_time,
291 end_time: other.end_time
293 // This is needed to create a hashable object
294 other_key.hashCode = manifold.record_hashcode(lease_key.sort());
295 other_key.equals = manifold.record_equals(lease_key);
300 op : STATE_SET_REMOVE,
303 manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
304 /* Remove from local cache also, unless we listen to events from outside */
305 $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });
306 return false; // ~ break
309 /* Search for leases after */
310 $.each(resource_leases, function(i, other) {
311 if (other.start_time != end_time)
312 return true; // ~ continue
314 /* The lease 'other' is just after, and there should not exist
315 * any other lease after it */
316 end_time = other.end_time;
318 resource: other.resource,
319 start_time: other.start_time,
320 end_time: other.end_time
322 // This is needed to create a hashable object
323 other_key.hashCode = manifold.record_hashcode(lease_key.sort());
324 other_key.equals = manifold.record_equals(lease_key);
329 op : STATE_SET_REMOVE,
332 manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
333 /* Remove from local cache also, unless we listen to events from outside */
334 $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });
335 return false; // ~ break
339 $scope._create_new_lease(model_resource.urn, start_time, end_time);
340 model_lease.status = (model_lease.status == 'free') ? 'pendingin' : 'selected';
341 // unless the exact same lease already existed (pending_out status for the lease, not the cell !!)
347 // We remove the cell
349 /* We search for leases including this cell. Either 0, 1 or 2.
350 * 0 : NOT POSSIBLE, should be checked.
351 * 1 : either IN or OUT, we have make no change in the session
352 * 2 : both will be pending, since we have made a change in the session
353 * /!\ need to properly remove pending_in leases when removed again
355 if (resource_leases) {
356 $.each(resource_leases, function(i, other) {
357 if ((other.start_time <= start_time) && (other.end_time >= end_time)) {
358 // The cell is part of this lease.
360 // If the cell is not at the beginning of the lease, we recreate a lease with cells before
361 if (start_time > other.start_time) {
362 $scope._create_new_lease(model_resource.urn, other.start_time, start_time);
365 // If the cell is not at the end of the lease, we recreate a lease with cells after
366 if (end_time < other.end_time) {
367 $scope._create_new_lease(model_resource.urn, end_time, other.end_time);
370 // The other lease will be removed
371 $scope._remove_lease(other);
373 // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status).
377 // cf comment in previous switch case
378 model_lease.status = (model_lease.status == 'selected') ? 'pendingout' : 'free';
389 //$scope._dump_leases();
392 $scope._dump_leases = function()
394 // DEBUG: display all leases and their status in the log
395 var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid);
396 console.log("--------------------");
397 $.each(leases, function(i, lease) {
398 var key = manifold.metadata.get_key('lease');
399 var lease_key = manifold.record_get_value(lease, key);
400 var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET);
404 state_str = 'STATE_SET_IN';
407 state_str = 'STATE_SET_OUT';
409 case STATE_SET_IN_PENDING:
410 state_str = 'STATE_SET_IN_PENDING';
412 case STATE_SET_OUT_PENDING:
413 state_str = 'STATE_SET_OUT_PENDING';
415 case STATE_SET_IN_SUCCESS:
416 state_str = 'STATE_SET_IN_SUCCESS';
418 case STATE_SET_OUT_SUCCESS:
419 state_str = 'STATE_SET_OUT_SUCCESS';
421 case STATE_SET_IN_FAILURE:
422 state_str = 'STATE_SET_IN_FAILURE';
424 case STATE_SET_OUT_FAILURE:
425 state_str = 'STATE_SET_OUT_FAILURE';
428 console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str);
432 // Return this object reference.
437 // Define the Controller as the constructor function.
438 app.controller("SchedulerCtrl", Controller);
440 })(angular, ManifoldApp);
442 /******************************************************************************
444 ******************************************************************************/
447 scheduler2 = Plugin.extend({
450 * @brief Plugin constructor
451 * @param options : an associative array of setting values
453 * @return : a jQuery collection of objects on which the plugin is
454 * applied, which allows to maintain chainability of calls
456 init: function(options, element) {
457 // Call the parent constructor, see FAQ when forgotten
458 this._super(options, element);
460 var scope = this._get_scope()
461 scope.instance = this;
464 scheduler2Instance = this;
466 // We need to remember the active filter for datatables filtering
468 this.filters = Array();
471 $(window).delegate('*', 'keypress', function (evt){
475 $(window).keydown(function(evt) {
476 if (evt.which == 17) { // ctrl
477 schedulerCtrlPressed = true;
479 }).keyup(function(evt) {
480 if (evt.which == 17) { // ctrl
481 schedulerCtrlPressed = false;
486 //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);
488 this._resources_received = false;
489 this._leases_received = false;
491 scope._leases_by_resource = {};
493 /* Listening to queries */
494 this.listen_query(options.query_uuid, 'resources');
495 this.listen_query(options.query_lease_uuid, 'leases');
497 this.elmt().on('show', this, this.on_show);
498 this.elmt().on('shown.bs.tab', this, this.on_show);
499 this.elmt().on('resize', this, this.on_resize);
501 /* Generate slots according to the default granularity. Should
502 * be updated when resources arrive. Should be the pgcd in fact XXX */
503 this._granularity = DEFAULT_GRANULARITY;
504 scope.granularity = this._granularity;
505 this.scope_resources_by_key = {};
515 do_resize: function()
517 var scope = this._get_scope();
518 var num_hidden_cells, new_max, lcm;
520 // do_resize has to be called when the window is resized, or one parameter changes
521 // e.g. when new resources have been received
523 this.resource_granularities = [3600, 1800]; //, 2400]; /* s */
525 /* Compute the slot length to accommodate all resources. This
526 * is the GCD of all resource granularities. */
527 this._slot_length = this._gcdn(this.resource_granularities);
529 $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);
530 //self get width might need fix depending on the template
531 var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();
533 /* Number of visible cells...*/
534 this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);
536 /* ...should be a multiple of the lcm of all encountered granularities. */
537 lcm = this._lcmn(this.resource_granularities) / this._slot_length;
538 this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % lcm;
540 // A list of {id, time} dictionaries representing the slots for the given day
541 this._all_slots = this._generate_all_slots();
543 /* scope also needs this value */
544 scope.slots = this._all_slots;
545 scope.slot_length = this._slot_length;
546 scope.num_visible_cells = this._num_visible_cells;
547 scope.lcm_colspan = this._lcmn(this.resource_granularities); // XXX WHY ?
550 this._scope_clear_leases();
551 this._set_all_lease_slots();
554 if ($('#tblSlider').data('slider') != undefined) {
555 num_hidden_cells = this._all_slots.length - this._num_visible_cells;
557 $('#tblSlider').slider('setAttribute', 'max', num_hidden_cells);
558 $('#tblSlider').slider('setValue', scope.from, true);
560 this._get_scope().$apply();
569 self._get_scope().$apply();
572 on_resize: function(e)
576 self._get_scope().$apply();
581 _get_scope : function()
583 return angular.element(document.getElementById('SchedulerCtrl')).scope();
586 _scope_set_resources : function()
589 var scope = this._get_scope();
591 var records = manifold.query_store.get_records(this.options.query_uuid);
593 scope.resources = [];
595 $.each(records, function(i, record) {
596 if (!record.exclusive)
597 return true; // ~ continue
599 // copy not to modify original record
600 var resource = jQuery.extend(true, {}, record);
603 //resource_granularity = ((resource.granularity === undefined) || (typeof(resource.granularity) != "number")) ? RESOURCE_DEFAULT_GRANULARITY : resource.granularity;
604 if (typeof(resource.granularity) != "number")
605 resource.granularity = RESOURCE_DEFAULT_GRANULARITY;
606 resource.leases = []; // a list of occupied timeslots
608 self.scope_resources_by_key[resource['urn']] = resource;
609 scope.resources.push(resource);
613 _scope_clear_leases: function()
617 var scope = this._get_scope();
619 now = new Date().getTime();
621 // Setup leases with a default free status...
622 $.each(this.scope_resources_by_key, function(resource_key, resource) {
623 resource.leases = [];
624 var colspan_lease = resource.granularity / self._slot_length; //eg. 3600 / 1800 => 2 cells
625 time = SchedulerDateSelected.getTime();
626 for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity
627 resource.leases.push({
629 status: (time < now) ? 'disabled': 'free', // 'selected', 'reserved', 'maintenance' XXX pending ??
631 time += resource.granularity * 1000;
637 _scope_set_leases: function()
641 var scope = this._get_scope();
643 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
644 //console.log("SET LEASES", lease.resource, new Date(lease.start_time* 1000), new Date(lease.end_time* 1000));
645 // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work
647 // Populate leases by resource array: this will help us merging leases later
649 // let's only put _our_ leases
650 lease_status = manifold.query_store.get_record_state(self.options.query_lease_uuid, lease_key, STATE_SET);
651 if (lease_status != STATE_SET_IN)
652 return true; // ~continue
653 if (!(lease.resource in scope._leases_by_resource))
654 scope._leases_by_resource[lease.resource] = [];
655 scope._leases_by_resource[lease.resource].push(lease);
659 this._set_all_lease_slots();
662 _set_all_lease_slots: function()
666 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
667 self._set_lease_slots(lease_key, lease);
671 on_resources_query_done: function(data)
673 this._resources_received = true;
674 this._scope_set_resources();
675 this._scope_clear_leases();
676 if (this._leases_received)
677 this._scope_set_leases();
679 this._get_scope().$apply();
682 on_leases_query_done: function(data)
684 this._leases_received = true;
685 if (this._resources_received) {
686 this._scope_set_leases();
687 this._get_scope().$apply();
691 /* Filters on resources */
692 on_resources_filter_added: function(filter) { this._get_scope().$apply(); },
693 on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },
694 on_resources_filter_clear: function() { this._get_scope().$apply(); },
696 /* Filters on leases ? */
697 on_leases_filter_added: function(filter) { this._get_scope().$apply(); },
698 on_leases_filter_removed: function(filter) { this._get_scope().$apply(); },
699 on_leases_filter_clear: function() { this._get_scope().$apply(); },
701 on_field_state_changed: function(data)
704 this._set_lease_slots(lease_key, lease);
710 case STATE_SET_IN_SUCCESS:
711 case STATE_SET_OUT_FAILURE:
712 this.set_checkbox_from_data(data.value, true);
713 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
716 case STATE_SET_OUT_SUCCESS:
717 case STATE_SET_IN_FAILURE:
718 this.set_checkbox_from_data(data.value, false);
719 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
721 case STATE_SET_IN_PENDING:
722 this.set_checkbox_from_data(data.key, true);
723 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_ADDED);
725 case STATE_SET_OUT_PENDING:
726 this.set_checkbox_from_data(data.key, false);
727 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_REMOVED);
733 this.change_status(data.key, data.value);
740 /* INTERNAL FUNCTIONS */
742 _set_lease_slots: function(lease_key, lease)
744 var resource, lease_status, lease_class;
745 var day_timestamp, id_start, id_end, colspan_lease;
747 resource = this.scope_resources_by_key[lease.resource];
748 day_timestamp = SchedulerDateSelected.getTime() / 1000;
749 if(resource === undefined){
750 console.log('resource undefined = '+lease.resource);
753 id_start = Math.floor((lease.start_time - day_timestamp) / resource.granularity);
755 /* Some leases might be in the past */
758 /* Leases in the future: ignore */
759 if (id_start >= this._all_slots.length)
760 return true; // ~ continue
762 id_end = Math.ceil((lease.end_time - day_timestamp) / resource.granularity);
763 colspan_lease = resource.granularity / this._slot_length; //eg. 3600 / 1800 => 2 cells
764 if (id_end >= this._all_slots.length / colspan_lease) {
765 /* Limit the display to the current day */
766 id_end = this._all_slots.length / colspan_lease
768 lease_status = manifold.query_store.get_record_state(this.options.query_lease_uuid, lease_key, STATE_SET);
769 // the same slots might be affected multiple times.
770 // PENDING_IN + PENDING_OUT => IN
772 // RESERVED vs SELECTED !
775 switch(lease_status) {
777 lease_class = 'selected'; // my leases
780 case STATE_SET_IN_SUCCESS:
781 lease_class = 'selected'; // my leases
782 lease_success = 'success';
783 case STATE_SET_OUT_FAILURE:
784 lease_class = 'selected'; // my leases
785 lease_success = 'failure';
788 lease_class = 'reserved'; // other leases
791 case STATE_SET_OUT_SUCCESS:
792 lease_class = 'free'; // other leases
793 lease_success = 'success';
795 case STATE_SET_IN_FAILURE:
796 lease_class = 'free'; // other leases
797 lease_success = 'failure';
799 case STATE_SET_IN_PENDING:
800 lease_class = 'pendingin';
803 case STATE_SET_OUT_PENDING:
804 // pending_in & pending_out == IN == replacement
805 if (resource.leases[i].status == 'pendingin')
806 lease_class = 'pendingin'
808 lease_class = 'pendingout';
814 for (i = id_start; i < id_end; i++) {
815 resource.leases[i].status = lease_class;
816 resource.leases[i].success = lease_success;
820 /* XXX IN TEMPLATE XXX
821 if (SchedulerDataViewData.length == 0) {
822 $("#plugin-scheduler").hide();
823 $("#plugin-scheduler-empty").show();
824 tmpScope.clearStuff();
826 $("#plugin-scheduler-empty").hide();
827 $("#plugin-scheduler").show();
828 // initSchedulerResources
829 tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);
834 * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.
839 var scope = self._get_scope();
841 var num_hidden_cells;
843 $("#DateToRes").datepicker({
844 dateFormat: "D, d M yy",
845 onRender: function(date) {
846 return date.valueOf() < now.valueOf() ? 'disabled' : '';
848 }).on('changeDate', function(ev) {
849 SchedulerDateSelected = new Date(ev.date);
850 SchedulerDateSelected.setHours(0,0,0,0);
851 // Set slider to origin
852 //$('#tblSlider').slider('setValue', 0); // XXX
854 self._scope_clear_leases();
855 self._set_all_lease_slots();
857 self._get_scope().$apply();
858 }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');
861 num_hidden_cells = self._all_slots.length - self._num_visible_cells;
862 init_cell = (new Date().getHours() - 1) * 3600 / self._granularity;
863 if (init_cell > num_hidden_cells)
864 init_cell = num_hidden_cells;
866 $('#tblSlider').slider({
868 max: num_hidden_cells,
870 }).on('slide', function(ev) {
871 var scope = self._get_scope();
872 scope.from = ev.value;
875 scope.from = init_cell;
878 $("#plugin-scheduler-loader").hide();
879 $("#plugin-scheduler").show();
885 * Greatest common divisor
887 _gcd : function(x, y)
889 return (y==0) ? x : this._gcd(y, x % y);
892 _gcdn : function(array)
895 return array.reduce(function(prev, cur, idx, arr) { return self._gcd(prev, cur); });
899 * Least common multiple
901 _lcm : function(x, y)
903 return x * y / this._gcd(x, y);
906 _lcmn : function(array)
909 return array.reduce(function(prev, cur, idx, arr) { return self._lcm(prev, cur); });
912 _pad_str : function(i)
914 return (i < 10) ? "0" + i : "" + i;
918 * Member variables used:
922 * A list of {id, time} dictionaries.
924 _generate_all_slots: function()
927 // Start with a random date (a first of a month), only time will matter
928 var d = new Date(2014, 1, 1, 0, 0, 0, 0);
930 // Loop until we change the day
931 while (d.getDate() == 1) {
932 // Nicely format the time...
933 var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());
934 /// ...and add the slot to the list of results
935 slots.push({ id: i, time: tmpTime });
936 // Increment the date with the granularity
937 d = new Date(d.getTime() + this._slot_length * 1000);
945 /* Plugin registration */
946 $.plugin('Scheduler2', scheduler2);