major updates to slice reservation page and plugins
[myslice.git] / plugins / scheduler2 / static / js / scheduler2.js
index fe8e3e8..5f35a0f 100755 (executable)
@@ -35,7 +35,7 @@ var scheduler2Instance;
 var schedulerCtrlPressed = false;\r
 //table Id\r
 var schedulerTblId = "scheduler-reservation-table";\r
-var SCHEDULER_FIRST_COLWIDTH = 150;\r
+var SCHEDULER_FIRST_COLWIDTH = 200;\r
 \r
 \r
 /* Number of scheduler slots per hour. Used to define granularity. Should be inferred from resources XXX */\r
@@ -69,6 +69,331 @@ var tmpSchedulerLeases = [];
 \r
 var SCHEDULER_COLWIDTH = 50;\r
 \r
+\r
+/******************************************************************************\r
+ *                             ANGULAR CONTROLLER                             *\r
+ ******************************************************************************/\r
+\r
+// Create a private execution space for our controller. When\r
+// executing this function expression, we're going to pass in\r
+// the Angular reference and our application module.\r
+(function (ng, app) {\r
+\r
+    // Define our Controller constructor.\r
+    function Controller($scope) {\r
+\r
+        // Store the scope so we can reference it in our\r
+        // class methods\r
+        this.scope = $scope;\r
+\r
+        // Set up the default scope value.\r
+        this.scope.errorMessage = null;\r
+        this.scope.name = "";\r
+\r
+        //Pagin\r
+        $scope.current_page = 1;\r
+        this.scope.items_per_page = 10;\r
+        $scope.from = 0; // JORDAN\r
+\r
+        $scope.instance = null;\r
+        $scope.resources = new Array();\r
+        $scope.slots = SchedulerSlotsViewData;\r
+        $scope.granularity = DEFAULT_GRANULARITY; /* Will be setup */\r
+        //$scope.msg = "hello";\r
+\r
+        angular.element(document).ready(function() {\r
+            //console.log('Hello World');\r
+            //alert('Hello World');\r
+            //afterAngularRendered();\r
+        });\r
+\r
+        // Pagination\r
+\r
+        $scope.range = function() {\r
+            var range_size = $scope.page_count() > DEFAULT_PAGE_RANGE ? DEFAULT_PAGE_RANGE : $scope.page_count();\r
+            var ret = [];\r
+            var start;\r
+\r
+            start = $scope.current_page;\r
+            if ( start > $scope.page_count()-range_size ) {\r
+              start = $scope.page_count()-range_size+1;\r
+            }\r
+\r
+            for (var i=start; i<start+range_size; i++) {\r
+              ret.push(i);\r
+            }\r
+            return ret;\r
+        };\r
+\r
+        $scope.prevPage = function() {\r
+          if ($scope.current_page > 1) {\r
+            $scope.current_page--;\r
+          }\r
+        };\r
+\r
+        $scope.prevPageDisabled = function() {\r
+          return $scope.current_page === 1 ? "disabled" : "";\r
+        };\r
+  \r
+        $scope.page_count = function()\r
+        {\r
+            // XXX need visible resources only\r
+            var query_ext, visible_resources_length;\r
+            if (!$scope.instance)\r
+                return 0;\r
+            query_ext = manifold.query_store.find_analyzed_query_ext($scope.instance.options.query_uuid);\r
+            var visible_resources_length = 0;\r
+            query_ext.state.each(function(i, state) {\r
+                if (state[STATE_VISIBLE])\r
+                    visible_resources_length++;\r
+            });\r
+            return Math.ceil(visible_resources_length/$scope.items_per_page);\r
+        };\r
+  \r
+        $scope.nextPage = function() {\r
+          if ($scope.current_page < $scope.page_count()) {\r
+            $scope.current_page++;\r
+          }\r
+        };\r
+  \r
+        $scope.nextPageDisabled = function() {\r
+          return $scope.current_page === $scope.page_count() ? "disabled" : "";\r
+        }; \r
+\r
+        $scope.setPage = function(n) {\r
+            $scope.current_page = n;\r
+        };\r
+        // END pagination\r
+\r
+        // FILTER\r
+\r
+        $scope.filter_visible = function(resource)\r
+        {\r
+            return manifold.query_store.get_record_state($scope.instance.options.query_uuid, resource['urn'], STATE_VISIBLE);\r
+        };\r
+\r
+        // SELECTION\r
+\r
+        $scope._create_new_lease = function(resource_urn, start_time, end_time)\r
+        {\r
+            var lease_key, new_lease;\r
+\r
+            lease_key = manifold.metadata.get_key('lease');\r
+\r
+            new_lease = {\r
+                resource:   resource_urn,\r
+                start_time: start_time,\r
+                end_time:   end_time,\r
+            };\r
+\r
+            // This is needed to create a hashable object\r
+            new_lease.hashCode = manifold.record_hashcode(lease_key.sort());\r
+            new_lease.equals   = manifold.record_equals(lease_key);\r
+\r
+            manifold.raise_event($scope.instance.options.query_lease_uuid, SET_ADD, new_lease);\r
+            /* Add to local cache also, unless we listen to events from outside */\r
+            if (!(resource_urn in $scope._leases_by_resource))\r
+                $scope._leases_by_resource[resource_urn] = [];\r
+            $scope._leases_by_resource[resource_urn].push(new_lease);\r
+        }\r
+\r
+        $scope._remove_lease = function(other)\r
+        {\r
+            var lease_key, other_key;\r
+\r
+            lease_key = manifold.metadata.get_key('lease');\r
+\r
+            // XXX This could be a manifold.record_get_value\r
+            other_key = {\r
+                resource:   other.resource,\r
+                start_time: other.start_time,\r
+                end_time:   other.end_time\r
+            }\r
+            other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
+            other_key.equals   = manifold.record_equals(lease_key);\r
+\r
+            manifold.raise_event($scope.instance.options.query_lease_uuid, SET_REMOVED, other_key);\r
+            /* Remove from local cache also, unless we listen to events from outside */\r
+            $.grep($scope._leases_by_resource[other.resource], function(x) { return x != other; });\r
+\r
+        }\r
+\r
+        $scope.select = function(index, model_lease, model_resource)\r
+        {\r
+            console.log("Selected", index, model_lease, model_resource);\r
+\r
+            var day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
+            var start_time = day_timestamp + index       * model_resource.granularity;\r
+            var end_time   = day_timestamp + (index + 1) * model_resource.granularity;\r
+            var start_date = new Date(start_time * 1000);\r
+            var end_date   = new Date(end_time   * 1000);\r
+\r
+            var lease_key = manifold.metadata.get_key('lease');\r
+\r
+            // We search for leases in the cache we previously constructed\r
+            var resource_leases = $scope._leases_by_resource[model_resource.urn];\r
+\r
+            switch (model_lease.status)\r
+            {\r
+                case 'free': // out\r
+                case 'pendingout':\r
+                    if (resource_leases) {\r
+                        /* Search for leases before */\r
+                        $.each(resource_leases, function(i, other) {\r
+                            if (other.end_time != start_time)\r
+                                return true; // ~ continue\r
+        \r
+                            /* The lease 'other' is just before, and there should not exist\r
+                             * any other lease before it */\r
+                            start_time = other.start_time;\r
+        \r
+                            other_key = {\r
+                                resource:   other.resource,\r
+                                start_time: other.start_time,\r
+                                end_time:   other.end_time\r
+                            }\r
+                            // This is needed to create a hashable object\r
+                            other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
+                            other_key.equals   = manifold.record_equals(lease_key);\r
+        \r
+                            manifold.raise_event($scope.instance.options.query_lease_uuid, SET_REMOVED, other_key);\r
+                            /* Remove from local cache also, unless we listen to events from outside */\r
+                            $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
+                            return false; // ~ break\r
+                        });\r
+        \r
+                        /* Search for leases after */\r
+                        $.each(resource_leases, function(i, other) {\r
+                            if (other.start_time != end_time)\r
+                                return true; // ~ continue\r
+        \r
+                            /* The lease 'other' is just after, and there should not exist\r
+                             * any other lease after it */\r
+                            end_time = other.end_time;\r
+                            // XXX SET_ADD and SET_REMOVE should accept full objects\r
+                            other_key = {\r
+                                resource:   other.resource,\r
+                                start_time: other.start_time,\r
+                                end_time:   other.end_time\r
+                            }\r
+                            // This is needed to create a hashable object\r
+                            other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
+                            other_key.equals   = manifold.record_equals(lease_key);\r
+        \r
+                            manifold.raise_event($scope.instance.options.query_lease_uuid, SET_REMOVED, other_key);\r
+                            /* Remove from local cache also, unless we listen to events from outside */\r
+                            $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
+                            return false; // ~ break\r
+                        });\r
+                    }\r
+        \r
+                    $scope._create_new_lease(model_resource.urn, start_time, end_time);\r
+                    model_lease.status = 'pendingin'; \r
+                    // unless the exact same lease already existed (pending_out status for the lease, not the cell !!)\r
+\r
+                    break;\r
+\r
+                case 'selected':\r
+                case 'pendingin':\r
+                    // We remove the cell\r
+\r
+                    /* We search for leases including this cell. Either 0, 1 or 2.\r
+                     * 0 : NOT POSSIBLE, should be checked.\r
+                     * 1 : either IN or OUT, we have make no change in the session\r
+                     * 2 : both will be pending, since we have made a change in the session\r
+                    * /!\ need to properly remove pending_in leases when removed again\r
+                     */\r
+                    if (resource_leases) {\r
+                        $.each(resource_leases, function(i, other) {\r
+                            if ((other.start_time <= start_time) && (other.end_time >= end_time)) {\r
+                                // The cell is part of this lease.\r
+\r
+                                // If the cell is not at the beginning of the lease, we recreate a lease with cells before\r
+                                if (start_time > other.start_time) {\r
+                                    $scope._create_new_lease(model_resource.urn, other.start_time, start_time);\r
+                                }\r
+\r
+                                // If the cell is not at the end of the lease, we recreate a lease with cells after\r
+                                if (end_time < other.end_time) {\r
+                                    $scope._create_new_lease(model_resource.urn, end_time, other.end_time);\r
+                                }\r
+                                \r
+                                // The other lease will be removed\r
+                                $scope._remove_lease(other);\r
+                            }\r
+                            // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status).\r
+                        });\r
+                    }\r
+                \r
+                    // cf comment in previous switch case\r
+                    model_lease.status = 'pendingout'; \r
+\r
+                    break;\r
+\r
+                case 'reserved':\r
+                case 'maintainance':\r
+                    // Do nothing\r
+                    break;\r
+            }\r
+            \r
+\r
+            //$scope._dump_leases();\r
+        };\r
+  \r
+        $scope._dump_leases = function()\r
+        {\r
+            // DEBUG: display all leases and their status in the log\r
+            var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid);\r
+            console.log("--------------------");\r
+            $.each(leases, function(i, lease) {\r
+                var key = manifold.metadata.get_key('lease');\r
+                var lease_key = manifold.record_get_value(lease, key);\r
+                var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET);\r
+                var state_str;\r
+                switch(state) {\r
+                    case STATE_SET_IN:\r
+                        state_str = 'STATE_SET_IN';\r
+                        break;\r
+                    case STATE_SET_OUT:\r
+                        state_str = 'STATE_SET_OUT';\r
+                        break;\r
+                    case STATE_SET_IN_PENDING:\r
+                        state_str = 'STATE_SET_IN_PENDING';\r
+                        break;\r
+                    case STATE_SET_OUT_PENDING:\r
+                        state_str = 'STATE_SET_OUT_PENDING';\r
+                        break;\r
+                    case STATE_SET_IN_SUCCESS:\r
+                        state_str = 'STATE_SET_IN_SUCCESS';\r
+                        break;\r
+                    case STATE_SET_OUT_SUCCESS:\r
+                        state_str = 'STATE_SET_OUT_SUCCESS';\r
+                        break;\r
+                    case STATE_SET_IN_FAILURE:\r
+                        state_str = 'STATE_SET_IN_FAILURE';\r
+                        break;\r
+                    case STATE_SET_OUT_FAILURE:\r
+                        state_str = 'STATE_SET_OUT_FAILURE';\r
+                        break;\r
+                }\r
+                console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str);\r
+            });\r
+        };\r
+\r
+        // Return this object reference.\r
+        return (this);\r
+\r
+    }\r
+\r
+    // Define the Controller as the constructor function.\r
+    app.controller("SchedulerCtrl", Controller);\r
+\r
+})(angular, ManifoldApp);\r
+\r
+/******************************************************************************\r
+ *                              MANIFOLD PLUGIN                               *\r
+ ******************************************************************************/\r
+\r
 (function($) {\r
         scheduler2 = Plugin.extend({\r
 \r
@@ -80,11 +405,11 @@ var SCHEDULER_COLWIDTH = 50;
          *     applied, which allows to maintain chainability of calls\r
          */\r
             init: function(options, element) {\r
-                this.classname = "scheduler2";\r
                 // Call the parent constructor, see FAQ when forgotten\r
                 this._super(options, element);\r
 \r
                 var scope = this._get_scope()\r
+                scope.instance = this;\r
 \r
                 // XXX not needed\r
                 scheduler2Instance = this;\r
@@ -120,15 +445,35 @@ var SCHEDULER_COLWIDTH = 50;
                 this.listen_query(options.query_uuid, 'resources');\r
                 this.listen_query(options.query_lease_uuid, 'leases');\r
 \r
+                this.elmt().on('show', this, this.on_show);\r
+                this.elmt().on('shown.bs.tab', this, this.on_show);\r
+                this.elmt().on('resize', this, this.on_resize);\r
+\r
                 /* Generate slots according to the default granularity. Should\r
                  * be updated when resources arrive.  Should be the pgcd in fact XXX */\r
                 this._granularity = DEFAULT_GRANULARITY;\r
                 scope.granularity = this._granularity;\r
                 this._all_slots = this._generate_all_slots();\r
 \r
+                // A list of {id, time} dictionaries representing the slots for the given day\r
+                scope.slots = this._all_slots;\r
+                this.scope_resources_by_key = {};\r
+\r
+                this.do_resize();\r
+    \r
+                scope.from = 0;\r
+\r
+                this._initUI();\r
+\r
+            },\r
+\r
+            do_resize: function()\r
+            {\r
+                var scope = this._get_scope();\r
+\r
                 $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);\r
-                //this get width might need fix depending on the template \r
-                var tblwidth = $('#scheduler-tab').parent().outerWidth();\r
+                //self get width might need fix depending on the template \r
+                var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();\r
 \r
                 /* Number of visible cells...*/\r
                 this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);\r
@@ -140,15 +485,27 @@ var SCHEDULER_COLWIDTH = 50;
                 scope.num_visible_cells = this._num_visible_cells;\r
                 scope.lcm_colspan = this._lcm_colspan;\r
 \r
-                scope.options = this.options;\r
-                scope.from = 0;\r
+                // Slider max value\r
 \r
-                // A list of {id, time} dictionaries representing the slots for the given day\r
-                scope.slots = this._all_slots;\r
-                this.scope_resources_by_key = {};\r
+                if ($('#tblSlider').data('slider') != undefined) {\r
+                    var new_max = (this._all_slots.length - this._num_visible_cells) / this._lcm_colspan;\r
+                    $('#tblSlider').slider('setAttribute', 'max', new_max);\r
+                }\r
 \r
-                this._initUI();\r
+            },\r
 \r
+            on_show: function(e)\r
+            {\r
+                var self = e.data;\r
+                self.do_resize();\r
+                self._get_scope().$apply();\r
+            },\r
+\r
+            on_resize: function(e)\r
+            {\r
+                var self = e.data;\r
+                self.do_resize();\r
+                self._get_scope().$apply();\r
             },\r
 \r
             /* Handlers */\r
@@ -299,39 +656,32 @@ var SCHEDULER_COLWIDTH = 50;
                 var self = this;\r
 \r
                 $("#DateToRes").datepicker({\r
-                    dateFormat: "yy-mm-dd",\r
-                    minDate: 0,\r
-                    numberOfMonths: 3\r
-                }).change(function() {\r
-                    // the selected date\r
-                    SchedulerDateSelected = $("#DateToRes").datepicker("getDate");\r
-                    if (SchedulerDateSelected == null || SchedulerDateSelected == '') {\r
-                        alert("Please select a date, so the scheduler can reserve leases.");\r
-                        return;\r
+                    onRender: function(date) {\r
+                        return date.valueOf() < now.valueOf() ? 'disabled' : '';\r
                     }\r
+                }).on('changeDate', function(ev) {\r
+                    SchedulerDateSelected = new Date(ev.date);\r
+                    SchedulerDateSelected.setHours(0,0,0,0);\r
                     // Set slider to origin\r
-                    $('#tblSlider').slider('value', 0);\r
+                    $('#tblSlider').slider('setValue', 0); // XXX\r
                     // Refresh leases\r
                     self._scope_clear_leases();\r
                     self._scope_set_leases();\r
                     // Refresh display\r
                     self._get_scope().$apply();\r
-                }).datepicker('setDate', SchedulerDateSelected);\r
+                }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');\r
 \r
                 //init Slider\r
                 $('#tblSlider').slider({\r
                     min: 0,\r
-                    max: (this._all_slots.length - self._num_visible_cells) / self._lcm_colspan,\r
+                    max: (self._all_slots.length - self._num_visible_cells) / self._lcm_colspan,\r
                     value: 0,\r
-                    slide: function(event, ui) {\r
-                        var scope = self._get_scope();\r
-                        scope.from = ui.value * self._lcm_colspan;\r
-                        scope.$apply();\r
-                   }\r
+                }).on('slide', function(ev) {\r
+                    var scope = self._get_scope();\r
+                    scope.from = ev.value * self._lcm_colspan;\r
+                    scope.$apply();\r
                 });\r
 \r
-                $('#btnSchedulerSubmit').click(this._on_submit);\r
-\r
                 $("#plugin-scheduler-loader").hide();\r
                 $("#plugin-scheduler").show();\r
             },\r