Merge branch 'onelab' of ssh://git.onelab.eu/git/myslice into onelab
authorJordan Augé <jordan.auge@lip6.fr>
Sun, 6 Jul 2014 17:56:52 +0000 (19:56 +0200)
committerJordan Augé <jordan.auge@lip6.fr>
Sun, 6 Jul 2014 17:56:52 +0000 (19:56 +0200)
Conflicts:
portal/sliceresourceview.py

51 files changed:
README.manifold-tables [new file with mode: 0644]
manifoldapi/static/js/hashtable.js [new file with mode: 0644]
manifoldapi/static/js/manifold-query.js
manifoldapi/static/js/manifold.js
manifoldapi/static/js/plugin.js
plugins/apply/__init__.py [new file with mode: 0644]
plugins/apply/static/css/apply.css [new file with mode: 0644]
plugins/apply/static/js/apply.js [new file with mode: 0644]
plugins/apply/templates/apply.html [new file with mode: 0644]
plugins/filter_status/__init__.py [new file with mode: 0644]
plugins/filter_status/static/js/filter_status.js [new file with mode: 0644]
plugins/filter_status/templates/filter_status.html [new file with mode: 0644]
plugins/googlemap/__init__.py
plugins/maddash/static/css/maddash.css
plugins/querytable/__init__.py
plugins/querytable/static/css/querytable.css
plugins/querytable/static/js/querytable.js
plugins/querytable/templates/querytable.html
plugins/queryupdater/__init__.py
plugins/queryupdater/static/js/queryupdater.js
plugins/scheduler2/__init__.py
plugins/scheduler2/asdf.txt [deleted file]
plugins/scheduler2/static/css/scheduler2.css
plugins/scheduler2/static/js/scheduler-SchedulerCtrl.js [deleted file]
plugins/scheduler2/static/js/scheduler-helpers.js
plugins/scheduler2/static/js/scheduler-table-selector.js [deleted file]
plugins/scheduler2/static/js/scheduler2.js
plugins/scheduler2/static/js/scheduler2_old.js [deleted file]
plugins/scheduler2/static/js/selectRangeWorker.js [deleted file]
plugins/scheduler2/templates/scheduler.html
plugins/testbeds/__init__.py
plugins/testbeds/static/js/testbeds.js
plugins/testbeds/templates/testbeds.html
portal/sliceresourceview.py
portal/sliceviewold.py
portal/static/js/myslice-ui.js
portal/static/js/onelab_slice-resource-view.js
portal/templates/base.html
portal/templates/fed4fire/fed4fire_base.html
portal/templates/onelab/onelab_base.html
portal/templates/slice-resource-view.html
portal/views/__init__.py [moved from portal/views.py with 100% similarity]
third-party/bootstrap-datepicker [new symlink]
third-party/bootstrap-datepicker-1/bootstrap-datepicker.js [new file with mode: 0644]
third-party/bootstrap-datepicker-1/datepicker.css [new file with mode: 0644]
third-party/bootstrap-slider [new symlink]
third-party/bootstrap-slider-1/bootstrap-slider.js [new file with mode: 0644]
third-party/bootstrap-slider-1/slider.css [new file with mode: 0644]
to-be-integrated/plugins/pres_view/static/css/pres_view.css
ui/templates/messages-transient-header.html
unfold/templates/page-queries.js

diff --git a/README.manifold-tables b/README.manifold-tables
new file mode 100644 (file)
index 0000000..6852dee
--- /dev/null
@@ -0,0 +1 @@
+manifold-tables -A -o authority -f \* -a R -j CACHE
diff --git a/manifoldapi/static/js/hashtable.js b/manifoldapi/static/js/hashtable.js
new file mode 100644 (file)
index 0000000..3922671
--- /dev/null
@@ -0,0 +1,402 @@
+/**\r
+ * @license jahashtable, a JavaScript implementation of a hash table. It creates a single constructor function called\r
+ * Hashtable in the global scope.\r
+ *\r
+ * http://www.timdown.co.uk/jshashtable/\r
+ * Copyright %%build:year%% Tim Down.\r
+ * Version: %%build:version%%\r
+ * Build date: %%build:date%%\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+var Hashtable = (function(UNDEFINED) {\r
+    var FUNCTION = "function", STRING = "string", UNDEF = "undefined";\r
+\r
+    // Require Array.prototype.splice, Object.prototype.hasOwnProperty and encodeURIComponent. In environments not\r
+    // having these (e.g. IE <= 5), we bail out now and leave Hashtable null.\r
+    if (typeof encodeURIComponent == UNDEF ||\r
+            Array.prototype.splice === UNDEFINED ||\r
+            Object.prototype.hasOwnProperty === UNDEFINED) {\r
+        return null;\r
+    }\r
+\r
+    function toStr(obj) {\r
+        return (typeof obj == STRING) ? obj : "" + obj;\r
+    }\r
+\r
+    function hashObject(obj) {\r
+        var hashCode;\r
+        if (typeof obj == STRING) {\r
+            return obj;\r
+        } else if (typeof obj.hashCode == FUNCTION) {\r
+            // Check the hashCode method really has returned a string\r
+            hashCode = obj.hashCode();\r
+            return (typeof hashCode == STRING) ? hashCode : hashObject(hashCode);\r
+        } else {\r
+            return toStr(obj);\r
+        }\r
+    }\r
+    \r
+    function merge(o1, o2) {\r
+        for (var i in o2) {\r
+            if (o2.hasOwnProperty(i)) {\r
+                o1[i] = o2[i];\r
+            }\r
+        }\r
+    }\r
+\r
+    function equals_fixedValueHasEquals(fixedValue, variableValue) {\r
+        return fixedValue.equals(variableValue);\r
+    }\r
+\r
+    function equals_fixedValueNoEquals(fixedValue, variableValue) {\r
+        return (typeof variableValue.equals == FUNCTION) ?\r
+            variableValue.equals(fixedValue) : (fixedValue === variableValue);\r
+    }\r
+\r
+    function createKeyValCheck(kvStr) {\r
+        return function(kv) {\r
+            if (kv === null) {\r
+                throw new Error("null is not a valid " + kvStr);\r
+            } else if (kv === UNDEFINED) {\r
+                throw new Error(kvStr + " must not be undefined");\r
+            }\r
+        };\r
+    }\r
+\r
+    var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value");\r
+\r
+    /*----------------------------------------------------------------------------------------------------------------*/\r
+\r
+    function Bucket(hash, firstKey, firstValue, equalityFunction) {\r
+        this[0] = hash;\r
+        this.entries = [];\r
+        this.addEntry(firstKey, firstValue);\r
+\r
+        if (equalityFunction !== null) {\r
+            this.getEqualityFunction = function() {\r
+                return equalityFunction;\r
+            };\r
+        }\r
+    }\r
+\r
+    var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2;\r
+\r
+    function createBucketSearcher(mode) {\r
+        return function(key) {\r
+            var i = this.entries.length, entry, equals = this.getEqualityFunction(key);\r
+            while (i--) {\r
+                entry = this.entries[i];\r
+                if ( equals(key, entry[0]) ) {\r
+                    switch (mode) {\r
+                        case EXISTENCE:\r
+                            return true;\r
+                        case ENTRY:\r
+                            return entry;\r
+                        case ENTRY_INDEX_AND_VALUE:\r
+                            return [ i, entry[1] ];\r
+                    }\r
+                }\r
+            }\r
+            return false;\r
+        };\r
+    }\r
+\r
+    function createBucketLister(entryProperty) {\r
+        return function(aggregatedArr) {\r
+            var startIndex = aggregatedArr.length;\r
+            for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) {\r
+                aggregatedArr[startIndex + i] = entries[i][entryProperty];\r
+            }\r
+        };\r
+    }\r
+\r
+    Bucket.prototype = {\r
+        getEqualityFunction: function(searchValue) {\r
+            return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals;\r
+        },\r
+\r
+        getEntryForKey: createBucketSearcher(ENTRY),\r
+\r
+        getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE),\r
+\r
+        removeEntryForKey: function(key) {\r
+            var result = this.getEntryAndIndexForKey(key);\r
+            if (result) {\r
+                this.entries.splice(result[0], 1);\r
+                return result[1];\r
+            }\r
+            return null;\r
+        },\r
+\r
+        addEntry: function(key, value) {\r
+            this.entries.push( [key, value] );\r
+        },\r
+\r
+        keys: createBucketLister(0),\r
+\r
+        values: createBucketLister(1),\r
+\r
+        getEntries: function(destEntries) {\r
+            var startIndex = destEntries.length;\r
+            for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) {\r
+                // Clone the entry stored in the bucket before adding to array\r
+                destEntries[startIndex + i] = entries[i].slice(0);\r
+            }\r
+        },\r
+\r
+        containsKey: createBucketSearcher(EXISTENCE),\r
+\r
+        containsValue: function(value) {\r
+            var entries = this.entries, i = entries.length;\r
+            while (i--) {\r
+                if ( value === entries[i][1] ) {\r
+                    return true;\r
+                }\r
+            }\r
+            return false;\r
+        }\r
+    };\r
+\r
+    /*----------------------------------------------------------------------------------------------------------------*/\r
+\r
+    // Supporting functions for searching hashtable buckets\r
+\r
+    function searchBuckets(buckets, hash) {\r
+        var i = buckets.length, bucket;\r
+        while (i--) {\r
+            bucket = buckets[i];\r
+            if (hash === bucket[0]) {\r
+                return i;\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    function getBucketForHash(bucketsByHash, hash) {\r
+        var bucket = bucketsByHash[hash];\r
+\r
+        // Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype\r
+        return ( bucket && (bucket instanceof Bucket) ) ? bucket : null;\r
+    }\r
+\r
+    /*----------------------------------------------------------------------------------------------------------------*/\r
+\r
+    function Hashtable() {\r
+        var buckets = [];\r
+        var bucketsByHash = {};\r
+        var properties = {\r
+            replaceDuplicateKey: true,\r
+            hashCode: hashObject,\r
+            equals: null\r
+        };\r
+\r
+        var arg0 = arguments[0], arg1 = arguments[1];\r
+        if (arg1 !== UNDEFINED) {\r
+            properties.hashCode = arg0;\r
+            properties.equals = arg1;\r
+        } else if (arg0 !== UNDEFINED) {\r
+            merge(properties, arg0);\r
+        }\r
+\r
+        var hashCode = properties.hashCode, equals = properties.equals;\r
+\r
+        this.properties = properties;\r
+\r
+        this.put = function(key, value) {\r
+            checkKey(key);\r
+            checkValue(value);\r
+            var hash = hashCode(key), bucket, bucketEntry, oldValue = null;\r
+\r
+            // Check if a bucket exists for the bucket key\r
+            bucket = getBucketForHash(bucketsByHash, hash);\r
+            if (bucket) {\r
+                // Check this bucket to see if it already contains this key\r
+                bucketEntry = bucket.getEntryForKey(key);\r
+                if (bucketEntry) {\r
+                    // This bucket entry is the current mapping of key to value, so replace the old value.\r
+                    // Also, we optionally replace the key so that the latest key is stored.\r
+                    if (properties.replaceDuplicateKey) {\r
+                        bucketEntry[0] = key;\r
+                    }\r
+                    oldValue = bucketEntry[1];\r
+                    bucketEntry[1] = value;\r
+                } else {\r
+                    // The bucket does not contain an entry for this key, so add one\r
+                    bucket.addEntry(key, value);\r
+                }\r
+            } else {\r
+                // No bucket exists for the key, so create one and put our key/value mapping in\r
+                bucket = new Bucket(hash, key, value, equals);\r
+                buckets.push(bucket);\r
+                bucketsByHash[hash] = bucket;\r
+            }\r
+            return oldValue;\r
+        };\r
+\r
+        this.get = function(key) {\r
+            checkKey(key);\r
+\r
+            var hash = hashCode(key);\r
+\r
+            // Check if a bucket exists for the bucket key\r
+            var bucket = getBucketForHash(bucketsByHash, hash);\r
+            if (bucket) {\r
+                // Check this bucket to see if it contains this key\r
+                var bucketEntry = bucket.getEntryForKey(key);\r
+                if (bucketEntry) {\r
+                    // This bucket entry is the current mapping of key to value, so return the value.\r
+                    return bucketEntry[1];\r
+                }\r
+            }\r
+            return null;\r
+        };\r
+\r
+        this.containsKey = function(key) {\r
+            checkKey(key);\r
+            var bucketKey = hashCode(key);\r
+\r
+            // Check if a bucket exists for the bucket key\r
+            var bucket = getBucketForHash(bucketsByHash, bucketKey);\r
+\r
+            return bucket ? bucket.containsKey(key) : false;\r
+        };\r
+\r
+        this.containsValue = function(value) {\r
+            checkValue(value);\r
+            var i = buckets.length;\r
+            while (i--) {\r
+                if (buckets[i].containsValue(value)) {\r
+                    return true;\r
+                }\r
+            }\r
+            return false;\r
+        };\r
+\r
+        this.clear = function() {\r
+            buckets.length = 0;\r
+            bucketsByHash = {};\r
+        };\r
+\r
+        this.isEmpty = function() {\r
+            return !buckets.length;\r
+        };\r
+\r
+        var createBucketAggregator = function(bucketFuncName) {\r
+            return function() {\r
+                var aggregated = [], i = buckets.length;\r
+                while (i--) {\r
+                    buckets[i][bucketFuncName](aggregated);\r
+                }\r
+                return aggregated;\r
+            };\r
+        };\r
+\r
+        this.keys = createBucketAggregator("keys");\r
+        this.values = createBucketAggregator("values");\r
+        this.entries = createBucketAggregator("getEntries");\r
+\r
+        this.remove = function(key) {\r
+            checkKey(key);\r
+\r
+            var hash = hashCode(key), bucketIndex, oldValue = null;\r
+\r
+            // Check if a bucket exists for the bucket key\r
+            var bucket = getBucketForHash(bucketsByHash, hash);\r
+\r
+            if (bucket) {\r
+                // Remove entry from this bucket for this key\r
+                oldValue = bucket.removeEntryForKey(key);\r
+                if (oldValue !== null) {\r
+                    // Entry was removed, so check if bucket is empty\r
+                    if (bucket.entries.length == 0) {\r
+                        // Bucket is empty, so remove it from the bucket collections\r
+                        bucketIndex = searchBuckets(buckets, hash);\r
+                        buckets.splice(bucketIndex, 1);\r
+                        delete bucketsByHash[hash];\r
+                    }\r
+                }\r
+            }\r
+            return oldValue;\r
+        };\r
+\r
+        this.size = function() {\r
+            var total = 0, i = buckets.length;\r
+            while (i--) {\r
+                total += buckets[i].entries.length;\r
+            }\r
+            return total;\r
+        };\r
+    }\r
+\r
+    Hashtable.prototype = {\r
+        each: function(callback) {\r
+            var entries = this.entries(), i = entries.length, entry;\r
+            while (i--) {\r
+                entry = entries[i];\r
+                callback(entry[0], entry[1]);\r
+            }\r
+        },\r
+\r
+        equals: function(hashtable) {\r
+            var keys, key, val, count = this.size();\r
+            if (count == hashtable.size()) {\r
+                keys = this.keys();\r
+                while (count--) {\r
+                    key = keys[count];\r
+                    val = hashtable.get(key);\r
+                    if (val === null || val !== this.get(key)) {\r
+                        return false;\r
+                    }\r
+                }\r
+                return true;\r
+            }\r
+            return false;\r
+        },\r
+\r
+        putAll: function(hashtable, conflictCallback) {\r
+            var entries = hashtable.entries();\r
+            var entry, key, value, thisValue, i = entries.length;\r
+            var hasConflictCallback = (typeof conflictCallback == FUNCTION);\r
+            while (i--) {\r
+                entry = entries[i];\r
+                key = entry[0];\r
+                value = entry[1];\r
+\r
+                // Check for a conflict. The default behaviour is to overwrite the value for an existing key\r
+                if ( hasConflictCallback && (thisValue = this.get(key)) ) {\r
+                    value = conflictCallback(key, thisValue, value);\r
+                }\r
+                this.put(key, value);\r
+            }\r
+        },\r
+\r
+        clone: function() {\r
+            var clone = new Hashtable(this.properties);\r
+            clone.putAll(this);\r
+            return clone;\r
+        }\r
+    };\r
+\r
+    Hashtable.prototype.toQueryString = function() {\r
+        var entries = this.entries(), i = entries.length, entry;\r
+        var parts = [];\r
+        while (i--) {\r
+            entry = entries[i];\r
+            parts[i] = encodeURIComponent( toStr(entry[0]) ) + "=" + encodeURIComponent( toStr(entry[1]) );\r
+        }\r
+        return parts.join("&");\r
+    };\r
+\r
+    return Hashtable;\r
+})();\r
index 3116f84..64eb3a7 100644 (file)
@@ -1,3 +1,15 @@
+var guid = (function() {
+  function s4() {
+    return Math.floor((1 + Math.random()) * 0x10000)
+               .toString(16)
+               .substring(1);
+  }
+  return function() {
+    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+           s4() + '-' + s4() + s4() + s4();
+  };
+})();
+
 function ManifoldQuery(action, object, timestamp, filters, params, fields, unique, query_uuid, aq, sq) {  
     // get, update, delete, create
     var action;
@@ -131,7 +143,7 @@ INSERT INTO object VALUES(field=value)
             });
         };
 
-        if (this.analyzed_query !== undefined)
+        if (!!this.analyzed_query)
             query = this.analyzed_query;
         else
             query = this;
@@ -257,7 +269,10 @@ INSERT INTO object VALUES(field=value)
     else
         this.unique = unique;
 
-    this.query_uuid = query_uuid;
+    if (typeof unique == "undefined")
+        this.query_uuid = guid();
+    else
+        this.query_uuid = query_uuid;
 
     if (typeof aq == "undefined")
         this.analyzed_query = null;
index fa5415a..b6d2d21 100644 (file)
@@ -38,27 +38,71 @@ var FIELD_REMOVED  = 5;
 var CLEAR_FIELDS   = 6;
 var NEW_RECORD     = 7;
 var CLEAR_RECORDS  = 8;
+
+/**
+ * event: FIELD_STATE_CHANGED
+ *
+ * Parameters:
+ *   dict :
+ *      .request    : ???? used to be FIELD_REQUEST_ADD / FIELD_REQUEST_REMOVE
+ *      .key        : ??? the key fields of the record
+ *      .value      : the key of the record who has received an update
+ *      .status     : the new state of the record
+ *        TODO rename to state, and use values from STATE_SET            
+ */
 var FIELD_STATE_CHANGED = 9;
 
 var IN_PROGRESS    = 101;
 var DONE           = 102;
 
 /* Update requests related to subqueries */
+
+/**
+ * event: SET_ADD
+ *
+ * Parameters:
+ *    string : The key of the element being added
+ */
 var SET_ADD        = 201;
+
+/**
+ * event: SET_REMOVED
+ *
+ * Parameters:
+ *    string : The key of the element being removed
+ */
 var SET_REMOVED    = 202;
 
+
 // request
 var FIELD_REQUEST_CHANGE  = 301;
 var FIELD_REQUEST_ADD     = 302;
 var FIELD_REQUEST_REMOVE  = 303;
 var FIELD_REQUEST_ADD_RESET = 304;
 var FIELD_REQUEST_REMOVE_RESET = 305;
-// status
+// status (XXX Should be deprecated)
 var FIELD_REQUEST_PENDING = 401;
 var FIELD_REQUEST_SUCCESS = 402;
 var FIELD_REQUEST_FAILURE = 403;
+var STATUS_OKAY           = 404;
+var STATUS_SET_WARNING    = 405;
+var STATUS_ADD_WARNING    = 406;
+var STATUS_REMOVE_WARNING = 407;
+var STATUS_RESET          = 408;
+
+/* Requests for query cycle */
+var RUN_UPDATE     = 601;
+
+/* MANIFOLD types */
+var TYPE_VALUE  = 1;
+var TYPE_RECORD = 2;
+var TYPE_LIST_OF_VALUES = 3;
+var TYPE_LIST_OF_RECORDS = 4;
+
+/******************************************************************************
+ * QUERY STATUS (for manifold events)
+ ******************************************************************************/
 
-/* Query status */
 var STATUS_NONE               = 500; // Query has not been started yet
 var STATUS_GET_IN_PROGRESS    = 501; // Query has been sent, no result has been received
 var STATUS_GET_RECEIVED       = 502; // Success
@@ -68,19 +112,47 @@ var STATUS_UPDATE_IN_PROGRESS = 505;
 var STATUS_UPDATE_RECEIVED    = 506;
 var STATUS_UPDATE_ERROR       = 507;
 
-/* Requests for query cycle */
-var RUN_UPDATE     = 601;
+/******************************************************************************
+ * QUERY STATE (for query_store)
+ ******************************************************************************/
 
-/* MANIFOLD types */
-var TYPE_VALUE  = 1;
-var TYPE_RECORD = 2;
-var TYPE_LIST_OF_VALUES = 3;
-var TYPE_LIST_OF_RECORDS = 4;
+// XXX Rendundant with query status ?
+
+var QUERY_STATE_INIT        = 0;
+var QUERY_STATE_INPROGRESS  = 1;
+var QUERY_STATE_DONE        = 2;
+
+/******************************************************************************
+ * RECORD STATES (for query_store)
+ ******************************************************************************/
+
+var STATE_SET       = 0;
+var STATE_WARNINGS  = 1;
+var STATE_VISIBLE   = 2;
+
+// STATE_SET : enum
+var STATE_SET_IN            = 0;
+var STATE_SET_OUT           = 1;
+var STATE_SET_IN_PENDING    = 2;
+var STATE_SET_OUT_PENDING   = 3;
+var STATE_SET_IN_SUCCESS    = 4;
+var STATE_SET_OUT_SUCCESS   = 5;
+var STATE_SET_IN_FAILURE    = 6;
+var STATE_SET_OUT_FAILURE   = 7;
+
+// STATE_WARNINGS : dict
+
+// STATE_VISIBLE : boolean
+
+/******************************************************************************
+ * CONSTRAINTS
+ ******************************************************************************/
 
+var CONSTRAINT_RESERVABLE_LEASE     = 0;
 
 // A structure for storing queries
 
-function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, disabled) {
+function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, disabled, domain_query_ext) {
 
     /* Constructor */
     if (typeof query == "undefined")
@@ -90,10 +162,36 @@ function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, dis
     this.main_query_ext        = (typeof main_query_ext        == "undefined") ? null  : main_query_ext;
     this.update_query_ext      = (typeof update_query_ext      == "undefined") ? null  : update_query_ext;
     this.update_query_orig_ext = (typeof update_query_orig_ext == "undefined") ? null  : update_query_orig_ext;
-    this.disabled              = (typeof update_query_ext      == "undefined") ? false : disabled;
+    this.disabled              = (typeof disabled              == "undefined") ? false : disabled;
+
+    // A domain query is a query that is issued to retrieve all possible values for a set
+    // eg. all resources that can be attached to a slice
+    // It is null unless we are a subquery for which a domain query has been issued
+    this.domain_query_ext      = (typeof domain_query_ext      == "undefined") ? null  : domain_query_ext;
+
+    // Set members to buffer until the domain query is completed
+    // A list of keys
+    this.set_members = [];
+
+    // The set query is the query for which the domain query has been issued.
+    // It is null unless the query is a domain query
+    this.set_query_ext         = (typeof set_query_ext         == "undefined") ? null  : domain_query_ext;
     
-    this.status       = null;
-    this.results      = null;
+    this.query_state = QUERY_STATE_INIT;
+
+    // Results from a query consists in a dict that maps keys to records
+    this.records = new Hashtable();
+
+    // Status is a dict that maps keys to record status
+    this.state = new Hashtable();
+
+    // Filters that impact visibility in the local interface
+    this.filters = [];
+
+    // XXX Until we find a better solution
+    this.num_pending = 0;
+    this.num_unconfigured = 0;
+
     // update_query null unless we are a main_query (aka parent_query == null); only main_query_fields can be updated...
 }
 
@@ -142,13 +240,37 @@ function QueryStore() {
         // We also need to insert all queries and subqueries from the analyzed_query
         // XXX We need the root of all subqueries
         query.iter_subqueries(function(sq, data, parent_query) {
-            if (parent_query)
+            var parent_query_ext;
+            if (parent_query) {
                 parent_query_ext = manifold.query_store.find_analyzed_query_ext(parent_query.query_uuid);
-            else
+            } else {
                 parent_query_ext = null;
+            }
             // XXX parent_query_ext == false
             // XXX main.subqueries = {} # Normal, we need analyzed_query
             sq_ext = new QueryExt(sq, parent_query_ext, query_ext)
+
+            if (parent_query) {
+                /* Let's issue a query for the subquery domain. This query will not need any update etc.
+                   eg. for resources in a slice, we also query all resources */
+                var all_fields = manifold.metadata.get_field_names(sq.object);
+                var domain_query = new ManifoldQuery('get', sq.object, 'now', [], {}, all_fields); 
+                //var domain_query = new ManifoldQuery('get', sq.object); 
+
+                console.log("Created domain query", domain_query);
+                var domain_query_ext = new QueryExt(domain_query);
+
+                domain_query_ext.set_query_ext = sq_ext;
+                sq_ext.domain_query_ext = domain_query_ext;
+
+                // One of these two is useless ?
+                manifold.query_store.main_queries[domain_query.query_uuid] = domain_query_ext;
+                manifold.query_store.analyzed_queries[domain_query.query_uuid] = domain_query_ext;
+
+                // XXX This query is run before the plugins are initialized and listening
+                manifold.run_query(domain_query);
+            }
+
             manifold.query_store.analyzed_queries[sq.query_uuid] = sq_ext;
         });
 
@@ -157,21 +279,343 @@ function QueryStore() {
 
     /* Searching */
 
-    this.find_query_ext = function(query_uuid) {
+    this.find_query_ext = function(query_uuid)
+    {
         return this.main_queries[query_uuid];
     }
 
-    this.find_query = function(query_uuid) {
+    this.find_query = function(query_uuid) 
+    {
         return this.find_query_ext(query_uuid).query;
     }
 
-    this.find_analyzed_query_ext = function(query_uuid) {
+    this.find_analyzed_query_ext = function(query_uuid)
+    {
         return this.analyzed_queries[query_uuid];
     }
 
-    this.find_analyzed_query = function(query_uuid) {
+    this.find_analyzed_query = function(query_uuid) 
+    {
         return this.find_analyzed_query_ext(query_uuid).query;
     }
+
+    this.state_dict_create = function(default_set)
+    {
+        default_set = (default_set === undefined) ? STATE_SET_OUT : default_set;
+        var state_dict = {};
+        // We cannot use constants in literal definition, so...
+        state_dict[STATE_WARNINGS] = {};
+        state_dict[STATE_SET] = default_set;
+        state_dict[STATE_VISIBLE] = true;
+        return state_dict;
+    }
+
+    // RECORDS
+
+    this.set_records = function(query_uuid, records, default_set)
+    {
+        default_set = (default_set === undefined) ? STATE_SET_OUT : default_set;
+
+        var self = this;
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        var record_key = manifold.metadata.get_key(query_ext.query.object);
+        $.each(records, function(i, record) {
+            var key = manifold.metadata.get_key(query_ext.query.object);
+            // ["start_time", "resource", "end_time"]
+            // ["urn"]
+            
+            var record_key_value = manifold.record_get_value(record, record_key);
+            query_ext.records.put(record_key_value, record);
+
+            if (!(query_ext.state.get(record_key_value)))
+                query_ext.state.put(record_key_value, self.state_dict_create(default_set));
+        });
+    }
+
+    this.get_records = function(query_uuid)
+    {
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        return query_ext.records.values();
+    }
+
+    this.get_record = function(query_uuid, record_key)
+    {
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        return query_ext.records.get(record_key);
+    }
+
+    this.add_record = function(query_uuid, record, new_state)
+    {
+        var query_ext, key, record_key;
+        query_ext = this.find_analyzed_query_ext(query_uuid);
+        
+        if (typeof(record) == 'object') {
+            key = manifold.metadata.get_key(query_ext.query.object);
+            record_key = manifold.record_get_value(record, key);
+        } else {
+            record_key = record;
+        }
+
+        var record_entry = query_ext.records.get(record_key);
+        if (!record_entry)
+            query_ext.records.put(record_key, record);
+
+        manifold.query_store.set_record_state(query_uuid, record_key, STATE_SET, new_state);
+    }
+
+    this.remove_record = function(query_uuid, record, new_state)
+    {
+        var query_ext, key, record_key;
+        query_ext = this.find_analyzed_query_ext(query_uuid);
+        
+        if (typeof(record) == 'object') {
+            key = manifold.metadata.get_key(query_ext.query.object);
+            record_key = manifold.record_get_value(record, key);
+        } else {
+            record_key = record;
+        }
+
+        manifold.query_store.set_record_state(query_uuid, record_key, STATE_SET, new_state);
+    }
+
+    this.iter_records = function(query_uuid, callback)
+    {
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        query_ext.records.each(callback);
+        //callback = function(record_key, record)
+    }
+
+    this.iter_visible_records = function(query_uuid, callback)
+    {
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        query_ext.records.each(function(record_key, record) {
+            if (query_ext.state.get(record_key)[STATE_VISIBLE]) // .STATE_VISIBLE would be for the string key
+                callback(record_key, record);
+        });
+        //callback = function(record_key, record)
+
+    }
+
+    // STATE
+
+    this.set_record_state = function(query_uuid, result_key, state, value)
+    {
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        var state_dict = query_ext.state.get(result_key);
+        if (!state_dict)
+            state_dict = this.state_dict_create();
+
+        state_dict[state] = value;
+
+        query_ext.state.put(result_key, state_dict);
+    }
+
+    this.get_record_state = function(query_uuid, result_key, state)
+    {
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        var state_dict = query_ext.state.get(result_key);
+        if (!state_dict)
+            return null;
+        return state_dict[state];
+    }
+
+    // FILTERS
+
+    this.add_filter = function(query_uuid, filter)
+    {
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        // XXX When we update a filter
+        query_ext.filters.push(filter);
+
+        this.apply_filters(query_uuid);
+
+    }
+
+    this.update_filter = function(query_uuid, filter)
+    {
+        // XXX
+
+        this.apply_filters(query_uuid);
+    }
+
+    this.remove_filter = function(query_uuid, filter)
+    {
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        query_ext.filters = $.grep(query_ext.filters, function(x) {
+            return x == filter;
+        });
+
+        this.apply_filters(query_uuid);
+    }
+
+    this.get_filters = function(query_uuid)
+    {
+        var query_ext = this.find_analyzed_query_ext(query_uuid);
+        return query_ext.filters;
+    }
+
+    this.recount = function(query_uuid)
+    {
+        var query_ext;
+        var is_reserved, is_pending, in_set,  is_unconfigured;
+
+        query_ext = manifold.query_store.find_analyzed_query_ext(query_uuid);
+        query_ext.num_pending = 0;
+        query_ext.num_unconfigured = 0;
+
+        this.iter_records(query_uuid, function(record_key, record) {
+            var record_state = manifold.query_store.get_record_state(query_uuid, record_key, STATE_SET);
+            var record_warnings = manifold.query_store.get_record_state(query_uuid, record_key, STATE_WARNINGS);
+
+            is_reserved = (record_state == STATE_SET_IN) 
+                       || (record_state == STATE_SET_OUT_PENDING)
+                       || (record_state == STATE_SET_IN_SUCCESS)
+                       || (record_state == STATE_SET_OUT_FAILURE);
+
+            is_pending = (record_state == STATE_SET_IN_PENDING) 
+                      || (record_state == STATE_SET_OUT_PENDING);
+
+            in_set = (record_state == STATE_SET_IN) // should not have warnings
+                  || (record_state == STATE_SET_IN_PENDING)
+                  || (record_state == STATE_SET_IN_SUCCESS)
+                  || (record_state == STATE_SET_OUT_FAILURE); // should not have warnings
+
+            is_unconfigured = (in_set && !$.isEmptyObject(record_warnings));
+
+            /* Let's update num_pending and num_unconfigured at this stage */
+            if (is_pending)
+                query_ext.num_pending++;
+            if (is_unconfigured)
+                query_ext.num_unconfigured++;
+        });
+
+    }
+
+    this.apply_filters = function(query_uuid)
+    {
+        var start = new Date().getTime();
+
+        // Toggle visibility of records according to the different filters.
+
+        var self = this;
+        var filters = this.get_filters(query_uuid);
+        var col_value;
+        /* Let's update num_pending and num_unconfigured at this stage */
+
+        // Adapted from querytable._querytable_filter()
+
+        this.iter_records(query_uuid, function(record_key, record) {
+            var is_reserved, is_pending, in_set,  is_unconfigured;
+            var visible = true;
+
+            var record_state = manifold.query_store.get_record_state(query_uuid, record_key, STATE_SET);
+            var record_warnings = manifold.query_store.get_record_state(query_uuid, record_key, STATE_WARNINGS);
+
+            is_reserved = (record_state == STATE_SET_IN) 
+                       || (record_state == STATE_SET_OUT_PENDING)
+                       || (record_state == STATE_SET_IN_SUCCESS)
+                       || (record_state == STATE_SET_OUT_FAILURE);
+
+            is_pending = (record_state == STATE_SET_IN_PENDING) 
+                      || (record_state == STATE_SET_OUT_PENDING);
+
+            in_set = (record_state == STATE_SET_IN) // should not have warnings
+                  || (record_state == STATE_SET_IN_PENDING)
+                  || (record_state == STATE_SET_IN_SUCCESS)
+                  || (record_state == STATE_SET_OUT_FAILURE); // should not have warnings
+
+            is_unconfigured = (in_set && !$.isEmptyObject(record_warnings));
+
+            // We go through each filter and decide whether it affects the visibility of the record
+            $.each(filters, function(index, filter) {
+                var key = filter[0];
+                var op = filter[1];
+                var value = filter[2];
+
+
+                /* We do some special handling for the manifold:status filter
+                 * predicates. */
+
+                if (key == 'manifold:status') {
+                    if (op != '=' && op != '==') {
+                        // Unsupported filter, let's ignore it
+                        console.log("Unsupported filter on manifold:status. Should be EQUAL only.");
+                        return true; // ~ continue
+                    }
+
+                    switch (value) {
+                        case 'reserved':
+                            // true  => ~ continue
+                            // false => ~ break
+                            visible = is_reserved;
+                            return visible;
+                        case 'unconfigured':
+                            visible = is_unconfigured;
+                            return visible;
+                        case 'pending':
+                            visible = is_pending;
+                            return visible;
+                    }
+                    return false; // ~ break
+                }
+
+                /* Normal filtering behaviour (according to the record content) follows... */
+                col_value = manifold.record_get_value(record, key);
+
+                // When the filter does not match, we hide the column by default
+                if (col_value === 'undefined') {
+                    visible = false;
+                    return false; // ~ break
+                }
+
+                // XXX This should accept pluggable filtering functions.
+
+
+                /* Test whether current filter is compatible with the column */
+                if (op == '=' || op == '==') {
+                    if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a")
+                        visible = false;
+                }else if (op == 'included') {
+                    $.each(value, function(i,x) {
+                      if(x == col_value){
+                          visible = true;
+                          return false; // ~ break
+                      }else{
+                          visible = false;
+                      }
+                    });
+                }else if (op == '!=') {
+                    if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a")
+                        visible = false;
+                } else if(op=='<') {
+                    if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a")
+                        visible = false;
+                } else if(op=='>') {
+                    if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a")
+                        visible = false;
+                } else if(op=='<=' || op=='≤') {
+                    if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a")
+                        visible = false;
+                } else if(op=='>=' || op=='≥') {
+                    if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a")
+                        visible = false;
+                }else{
+                    // How to break out of a loop ?
+                    alert("filter not supported");
+                    return false; // break
+                }
+
+            });
+
+            // Set the visibility status in the query store
+            self.set_record_state(query_uuid, record_key, STATE_VISIBLE, visible);
+        });
+
+        var end = new Date().getTime();
+        console.log("APPLY FILTERS took", end - start, "ms");
+
+    }
+
 }
 
 /*!
@@ -201,6 +645,108 @@ var manifold = {
         }
     },
 
+    /**
+     *  Args:
+     *      fields: A String instance (field name), or a set of String instances
+     *          (field names) # XXX tuple !!
+     *  Returns:
+     *      If fields is a String,  return the corresponding value.
+     *      If fields is a set, return a tuple of corresponding value.
+     *
+     *  Raises:
+     *      KeyError if at least one of the fields is not found
+     */
+    record_get_value: function(record, fields) 
+    {
+        if (typeof(fields) === 'string') {
+            if (fields.indexOf('.') != -1) {
+                key_subkey = key.split('.', 2);
+                key     = key_subkey[0]; 
+                subkey  = key_subkey[1];
+
+                if (record.indexOf(key) == -1) {
+                    return null;
+                }
+                // Tests if the following is an array (typeof would give object)
+                if (Object.prototype.toString.call(record[key]) === '[object Array]') {
+                    // Records
+                    return $.map(record[key], function(subrecord) { return manifold.record_get_value(subrecord, subkey) });
+                } else if (typeof(record) == 'object') {
+                    // Record
+                    return manifold.record_get_value(record[key], subkey);
+                } else {
+                    console.log('Unknown field');
+                }
+            } else {
+                return record[fields];
+            }
+        } else {
+            // see. get_map_entries
+            if (fields.length == 1)
+                return manifold.record_get_value(record, fields[0])
+
+            // Build a new record
+            var ret = {};
+            $.each(fields, function(i, field) {
+                ret[field] = manifold.record_get_value(record, field);
+            });
+            ret.hashCode = record.hashCode;
+            ret.equals = record.equals;
+            return ret;
+            // this was an array, we want a dictionary
+            //return $.map(fields, function(x) { manifold.record_get_value(record, x) });
+                
+        }
+    },
+
+    record_hashcode: function(key_fields)
+    {
+        return function() {
+            ret = "";
+            for (i=0; i < key_fields.length; i++)
+                ret += "@@" + this[key_fields[i]];
+            return ret;
+        };
+    },
+
+    record_equals: function(key_fields)
+    {
+        var self = this;
+
+        return function(other) {
+            for (i=0; i < key_fields.length; i++) {
+                var this_value  = this[key_fields[i]];
+                var other_value = other[key_fields[i]];
+
+                var this_type = self.get_type(this_value);
+                var other_type = self.get_type(other_value);
+                if (this_type != other_type)
+                    return false;
+
+                switch (this_type) {
+                    case TYPE_VALUE:
+                    case TYPE_LIST_OF_VALUES:
+                        if (this_value != other_value)
+                            return false;
+                        break;
+                    case TYPE_RECORD:
+                        if (!(record_equals(this_value, other_value)))
+                            return false;
+                        break;
+                    case TYPE_LIST_OF_RECORDS:
+                        if (this_value.length != other_value.length)
+                            return false;
+                        for (i = 0; i < this_value.length; i++)
+                            if (!(record_equals(this_value, other_value)))
+                                return false;
+                        break;
+                }
+            }
+            return true;
+        };
+    },
+
+
     /************************************************************************** 
      * Metadata management
      **************************************************************************/ 
@@ -221,6 +767,14 @@ var manifold = {
             return (typeof table.column === 'undefined') ? null : table.column;
         },
 
+        get_field_names: function(method)
+        {
+            var columns = this.get_columns(method);
+            if (!columns)
+                return null;
+            return $.map(columns, function (x) { return x.name });
+        },
+
         get_key: function(method) {
             var table = this.get_table(method);
             if (!table)
@@ -247,6 +801,12 @@ var manifold = {
             if (!table)
                 return null;
 
+            var match = $.grep(table.column, function(x) { return x.name == name });
+            if (match.length == 0) {
+                return undefined;
+            } else {
+                return match[0].type;
+            }
             return (typeof table.type === 'undefined') ? null : table.type;
         }
 
@@ -277,6 +837,11 @@ var manifold = {
         // NEW API
         manifold.query_store.insert(query);
 
+        // Run
+        $(document).ready(function() {
+        manifold.run_query(query);
+        });
+
         // FORMER API
         if (query.analyzed_query == null) {
             query.analyze_subqueries();
@@ -316,21 +881,30 @@ var manifold = {
         }
     },
 
-    run_query: function(query, callback) {
+    run_query: function(query, callback)
+        {
         // default value for callback = null
         if (typeof callback === 'undefined')
             callback = null; 
 
+        var query_ext = manifold.query_store.find_query_ext(query.query_uuid);
+        query_ext.query_state = QUERY_STATE_INPROGRESS;
+
         var query_json = JSON.stringify(query);
 
-        /* Nothing related to pubsub here... for the moment at least. */
-        //query.iter_subqueries(function (sq) {
-        //    manifold.raise_record_event(sq.query_uuid, IN_PROGRESS);
-        //});
+        // Inform plugins about the progress
+        query.iter_subqueries(function (sq) {
+            var sq_query_ext = manifold.query_store.find_analyzed_query_ext(sq.query_uuid);
+            sq_query_ext.query_state = QUERY_STATE_INPROGRESS;
+
+            manifold.raise_record_event(sq.query_uuid, IN_PROGRESS);
+        });
+
 
         $.post(manifold.proxy_url, {'json': query_json} , manifold.success_closure(query, null, callback));
     },
 
+    // XXX DEPRECATED
     // Executes all async. queries - intended for the javascript header to initialize queries
     // input queries are specified as a list of {'query_uuid': <query_uuid> }
     // each plugin is responsible for managing its spinner through on_query_in_progress
@@ -421,6 +995,29 @@ var manifold = {
             messages.debug(".. publish_result (5) END q=" + query.__repr());
     },
 
+    store_records: function(query, records) {
+        // Store records
+        var query_ext = manifold.query_store.find_analyzed_query_ext(query.query_uuid);
+        if (query_ext.set_query_ext) {
+            // We have a domain query
+            // The results are stored in the corresponding set_query
+            manifold.query_store.set_records(query_ext.set_query_ext.query.query_uuid, records)
+            
+        } else if (query_ext.domain_query_ext) {
+            // We have a set query, it is only used to determine which objects are in the set, we should only retrieve the key
+            // Has it a domain query, and has it completed ?
+            $.each(records, function(i, record) {
+                var key = manifold.metadata.get_key(query.object);
+                var record_key = manifold.record_get_value(record, key);
+                manifold.query_store.set_record_state(query.query_uuid, record_key, STATE_SET, STATE_SET_IN);
+            });
+
+        } else {
+            // We have a normal query
+            manifold.query_store.set_records(query.query_uuid, records, STATE_SET_IN);
+        }
+    },
+
     /*!
      * Recursively publish result
      * \fn publish_result_rec(query, result)
@@ -430,24 +1027,43 @@ var manifold = {
      *
      * Note: this function works on the analyzed query
      */
-    publish_result_rec: function(query, result) {
+    publish_result_rec: function(query, records) {
         /* If the result is not unique, only publish the top query;
          * otherwise, publish the main object as well as subqueries
          * XXX how much recursive are we ?
          */
         if (manifold.pubsub_debug)
-            messages.debug (">>>>> publish_result_rec " + query.object);
+             messages.debug (">>>>> publish_result_rec " + query.object);
         if (manifold.query_expects_unique_result(query)) {
             /* Also publish subqueries */
             $.each(query.subqueries, function(object, subquery) {
-                manifold.publish_result_rec(subquery, result[0][object]);
+                manifold.publish_result_rec(subquery, records[0][object]);
                 /* TODO remove object from result */
             });
         }
         if (manifold.pubsub_debug) 
             messages.debug ("===== publish_result_rec " + query.object);
 
-        manifold.publish_result(query, result);
+        var query_ext = manifold.query_store.find_analyzed_query_ext(query.query_uuid);
+        query_ext.query_state = QUERY_STATE_DONE;
+
+        this.store_records(query, records);
+
+        var pub_query;
+
+        if (query_ext.set_query_ext) {
+            if (query_ext.set_query_ext.query_state != QUERY_STATE_DONE)
+                return;
+            pub_query = query_ext.set_query_ext.query;
+        } else if (query_ext.domain_query_ext) {
+            if (query_ext.domain_query_ext.query_state != QUERY_STATE_DONE)
+                return;
+            pub_query = query;
+        } else {
+            pub_query = query;
+        }
+        // We can only publish results if the query (and its related domain query) is complete
+        manifold.publish_result(pub_query, records);
 
         if (manifold.pubsub_debug) 
             messages.debug ("<<<<< publish_result_rec " + query.object);
@@ -462,6 +1078,11 @@ var manifold = {
         var record = records[0];
 
         var update_query_ext = query_ext.update_query_ext;
+
+        console.log("Update case not handled yet!");
+        if (!update_query_ext)
+            return;
+
         var update_query = update_query_ext.query;
         var update_query_ext = query_ext.update_query_ext;
         var update_query_orig = query_ext.update_query_orig_ext.query;
@@ -480,13 +1101,7 @@ var manifold = {
                 if (!subrecords)
                     continue
                 $.each(subrecords, function (i, subrecord) {
-                    if (key.length == 1){
-                        key = key[0];
-                        sq_keys.push(subrecord[key]);
-                    }else{
-                        // more than what's necessary, but should work
-                        sq_keys.push(subrecord);
-                    }
+                    sq_keys.push(manifold.record_get_value(subrecord, key));
                 });
                 update_query.params[method] = sq_keys;
                 update_query_orig.params[method] = sq_keys.slice();
@@ -502,10 +1117,47 @@ var manifold = {
 
     process_get_query_records: function(query, records) {
         this.setup_update_query(query, records);
+        
+        var query_ext = manifold.query_store.find_query_ext(query.query_uuid);
+        query_ext.query_state = QUERY_STATE_DONE;
 
         /* Publish full results */
-        var tmp_query = manifold.find_query(query.query_uuid);
-        manifold.publish_result_rec(tmp_query.analyzed_query, records);
+        var tmp_query = manifold.query_store.find_analyzed_query(query.query_uuid);
+        manifold.publish_result_rec(tmp_query, records);
+    },
+
+    make_records: function(object, records)
+    {
+        $.each(records, function(i, record) {
+            manifold.make_record(object, record);
+        });
+    },
+
+    make_record: function(object, record)
+    {
+        // To make an object a record, we just add the hash function
+        var key = manifold.metadata.get_key(object);
+        record.hashCode = manifold.record_hashcode(key.sort());
+        record.equals   = manifold.record_equals(key);
+
+        // Looking after subrecords
+        for (var field in record) {
+            var result_value = record[field];
+
+            switch (this.get_type(result_value)) {
+                case TYPE_RECORD:
+                    var subobject = manifold.metadata.get_type(object, field);
+                    // if (subobject) XXX Bugs with fields declared string while they are not : network.version is a dict in fact
+                    if (subobject && subobject != 'string')
+                        manifold.make_record(subobject, result_value);
+                    break;
+                case TYPE_LIST_OF_RECORDS:
+                    var subobject = manifold.metadata.get_type(object, field);
+                    if (subobject)
+                        manifold.make_records(subobject, result_value);
+                    break;
+            }
+        }
     },
 
     /**
@@ -523,6 +1175,21 @@ var manifold = {
      * previous 'process_get_query_records' function.
      */
     process_update_query_records: function(query, records) {
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
+        // XXX XXX XXX XXX
         // First issue: we request everything, and not only what we modify, so will will have to ignore some fields
         var query_uuid        = query.query_uuid;
         var query_ext         = manifold.query_store.find_analyzed_query_ext(query_uuid);
@@ -534,7 +1201,8 @@ var manifold = {
 
         // Let's iterate over the object properties
         for (var field in record) {
-            switch (this.get_type(record[field])) {
+            var result_value = record[field];
+            switch (this.get_type(result_value)) {
                 case TYPE_VALUE:
                     // Did we ask for a change ?
                     var update_value = update_query[field];
@@ -544,7 +1212,6 @@ var manifold = {
                         // We assume it won't have changed
                         continue;
 
-                    var result_value = record[field];
                     if (!result_value)
                         throw "Internal error";
 
@@ -563,7 +1230,6 @@ var manifold = {
 
                 case TYPE_LIST_OF_VALUES:
                     // Same as list of records, but we don't have to extract keys
-                    var result_keys  = record[field]
                     
                     // The rest of exactly the same (XXX factorize)
                     var update_keys  = update_query_orig.params[field];
@@ -573,7 +1239,7 @@ var manifold = {
 
 
                     $.each(added_keys, function(i, key) {
-                        if ($.inArray(key, result_keys) == -1) {
+                        if ($.inArray(key, result_value) == -1) {
                             data = {
                                 request: FIELD_REQUEST_ADD,
                                 key   : field,
@@ -626,7 +1292,7 @@ var manifold = {
                     key = key[0];
 
                     /* XXX should be modified for multiple keys */
-                    var result_keys  = $.map(record[field], function(x) { return x[key]; });
+                    var result_keys  = $.map(record[field], function(x) { return manifold.record_get_value(x, key); });
 
                     var update_keys  = update_query_orig.params[field];
                     var query_keys   = update_query.params[field];
@@ -735,6 +1401,7 @@ var manifold = {
         var result=data.value;
         if (result) {
             /* Eventually update the content of related queries (update, etc) */
+            manifold.make_records(query.object, result);
             this.process_query_records(query, result);
 
             /* Publish results: disabled here, done in the previous call */
@@ -789,11 +1456,14 @@ var manifold = {
 
 
     raise_event: function(query_uuid, event_type, value) {
+        var query, query_ext;
+
         // Query uuid has been updated with the key of a new element
         query_ext    = manifold.query_store.find_analyzed_query_ext(query_uuid);
         query = query_ext.query;
 
         switch(event_type) {
+
             case FIELD_STATE_CHANGED:
                 // value is an object (request, key, value, status)
                 // update is only possible is the query is not pending, etc
@@ -855,14 +1525,179 @@ var manifold = {
                 });
                 value.key = value_key;
 
+                manifold.query_store.recount(cur_query.query_uuid);
                 manifold.raise_record_event(cur_query.query_uuid, event_type, value);
 
+
                 // XXX make this DOT a global variable... could be '/'
                 break;
 
             case SET_ADD:
             case SET_REMOVED:
-    
+
+                /* An object has been added to / removed from a set : its
+                 * status become pending or reset to the original state. We
+                 * update the record status in the analyzed queries.
+                 *
+                 * XXX Shall we update something in the main_query ?
+                 */
+                var prev_state, new_state;
+
+                prev_state = manifold.query_store.get_record_state(query_uuid, value, STATE_SET);
+                if (prev_state === null)
+                    prev_state = STATE_SET_OUT;
+
+                if (event_type == SET_ADD) {
+                    switch (prev_state) {
+                        case STATE_SET_OUT:
+                        case STATE_SET_OUT_SUCCESS:
+                        case STATE_SET_IN_FAILURE:
+                            new_state = STATE_SET_IN_PENDING;
+                            break;
+
+                        case STATE_SET_OUT_PENDING:
+                            new_state = STATE_SET_IN;
+                            break;
+
+                        case STATE_SET_IN:
+                        case STATE_SET_IN_PENDING:
+                        case STATE_SET_IN_SUCCESS:
+                        case STATE_SET_OUT_FAILURE:
+                            console.log("Inconsistent state: already in");
+                            return;
+                    }
+                } else { // SET_REMOVE
+                    switch (prev_state) {
+                        case STATE_SET_IN:
+                        case STATE_SET_IN_SUCCESS:
+                        case STATE_SET_OUT_FAILURE:
+                            new_state = STATE_SET_OUT_PENDING;
+                            break;
+
+                        case STATE_SET_IN_PENDING:
+                            new_state = STATE_SET_OUT;
+                            break;  
+
+                        case STATE_SET_OUT:
+                        case STATE_SET_OUT_PENDING:
+                        case STATE_SET_OUT_SUCCESS:
+                        case STATE_SET_IN_FAILURE:
+                            console.log("Inconsistent state: already out");
+                            return;
+                    }
+                }
+
+                
+                var resource_key = value;
+
+                if (event_type == SET_ADD)
+                    manifold.query_store.add_record(query_uuid, resource_key, new_state);
+                else
+                    manifold.query_store.remove_record(query_uuid, resource_key, new_state);
+
+                var record = manifold.query_store.get_record(query_uuid, resource_key);
+
+                /* CONSTRAINTS */
+
+                // XXX When we add a lease we must update the warnings
+
+                switch(query.object) {
+
+                    case 'resource':
+                        // CONSTRAINT_RESERVABLE_LEASE
+                        // 
+                        // +) If a reservable node is added to the slice, then it should have a corresponding lease
+                        // XXX Not always a resource
+                        var is_reservable = (record.exclusive == true);
+                        if (is_reservable) {
+                            var warnings = manifold.query_store.get_record_state(query_uuid, resource_key, STATE_WARNINGS);
+
+                            if (event_type == SET_ADD) {
+                                // We should have a lease_query associated
+                                var lease_query = query_ext.parent_query_ext.query.subqueries['lease']; // in  options
+                                var lease_query_ext = manifold.query_store.find_analyzed_query_ext(lease_query.query_uuid);
+                                // Do we have lease records with this resource
+                                var lease_records = $.grep(lease_query_ext.records, function(lease_key, lease) {
+                                    return lease['resource'] == value;
+                                });
+                                if (lease_records.length == 0) {
+                                    // Sets a warning
+                                    // XXX Need for a better function to manage warnings
+                                    var warn = "No lease defined for this reservable resource.";
+                                    warnings[CONSTRAINT_RESERVABLE_LEASE] = warn;
+                                } else {
+                                    // Lease are defined, delete the warning in case it was set previously
+                                    delete warnings[CONSTRAINT_RESERVABLE_LEASE];
+                                }
+                            } else {
+                                // Remove warnings attached to this resource
+                                delete warnings[CONSTRAINT_RESERVABLE_LEASE];
+                            }
+
+                            manifold.query_store.set_record_state(query_uuid, resource_key, STATE_WARNINGS, warnings);
+                            break;
+                        }
+
+                        // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark)
+                        data = {
+                            request: null,
+                            key   : null,
+                            value : resource_key,
+                            status: STATE_WARNINGS
+                        };
+                        manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data);
+
+                    case 'lease':
+                    /*
+                        var resource_key = record.resource;
+                        var resource_query = query_ext.parent_query_ext.query.subqueries['resource'];
+                        var warnings = manifold.query_store.get_record_state(resource_query.query_uuid, resource_key, STATE_WARNINGS);
+
+                        if (event_type == SET_ADD) {
+                             // A lease is added, it removes the constraint
+                            delete warnings[CONSTRAINT_RESERVABLE_LEASE];
+                        } else {
+                            // A lease is removed, it might trigger the warning
+                            var lease_records = $.grep(query_ext.records, function(lease_key, lease) {
+                                return lease['resource'] == value;
+                            });
+                            if (lease_records.length == 0) { // XXX redundant cases
+                                // Sets a warning
+                                // XXX Need for a better function to manage warnings
+                                var warn = "No lease defined for this reservable resource.";
+                                warnings[CONSTRAINT_RESERVABLE_LEASE] = warn;
+                            } else {
+                                // Lease are defined, delete the warning in case it was set previously
+                                delete warnings[CONSTRAINT_RESERVABLE_LEASE];
+                            }
+                            
+                        }
+
+                        // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark)
+                        data = {
+                            request: null,
+                            key   : null,
+                            value : resource_key,
+                            status: STATE_WARNINGS
+                        };
+                        manifold.raise_record_event(resource_query.query_uuid, FIELD_STATE_CHANGED, data);
+                        break;
+                    */
+                }
+
+
+                // -) When a lease is added, it might remove the warning associated to a reservable node
+
+                // If a NITOS node is reserved, then at least a NITOS channel should be reserved
+                // - When a NITOS channel is added, it might remove a warning associated to all NITOS nodes
+
+                // If a NITOS channel is reserved, then at least a NITOS node should be reserved
+                // - When a NITOS node is added, it might remove a warning associated to all NITOS channels
+
+                // A lease is present while the resource has been removed => Require warnings on nodes not in set !
+
+                /* END CONSTRAINTS */
+
                 // update is only possible is the query is not pending, etc
                 // CHECK status !
 
@@ -888,7 +1723,7 @@ var manifold = {
                     request: (event_type == SET_ADD) ? FIELD_REQUEST_ADD : FIELD_REQUEST_REMOVE,
                     key   : path,
                     value : value,
-                    status: FIELD_REQUEST_PENDING,
+                    status: STATE_SET, // XXX used to be FIELD_REQUEST_PENDING, and not new_state
                 };
                 this.raise_event(main_query.query_uuid, FIELD_STATE_CHANGED, data);
 
@@ -911,12 +1746,26 @@ var manifold = {
                 manifold.run_query(query_ext.main_query_ext.update_query_ext.query);
                 break;
 
+            /* FILTERS */
+
             case FILTER_ADDED: 
+                /* Update internal record state */
+                manifold.query_store.add_filter(query_uuid, value);
+
+                /* Propagate the message to plugins */
                 manifold.raise_query_event(query_uuid, event_type, value);
+
                 break;
+
             case FILTER_REMOVED:
+                /* Update internal record state */
+                manifold.query_store.remove_filter(query_uuid, value);
+
+                /* Propagate the message to plugins */
                 manifold.raise_query_event(query_uuid, event_type, value);
+
                 break;
+
             case FIELD_ADDED:
                 main_query = query_ext.main_query_ext.query;
                 main_update_query = query_ext.main_query_ext.update_query;
index 24d4112..76e1cb5 100644 (file)
@@ -1,3 +1,24 @@
+// Common parts for angularjs plugins
+// only one ng-app is allowed
+
+var ManifoldApp = angular.module('ManifoldApp', []);
+ManifoldApp.config(function ($interpolateProvider) {
+    $interpolateProvider.startSymbol('{[{').endSymbol('}]}');
+});
+
+ManifoldApp.factory('$exceptionHandler', function () {
+    return function (exception, cause) {
+        console.log(exception.message);
+    };
+});
+
+ManifoldApp.filter('offset', function() {
+  return function(input, start) {
+    start = parseInt(start, 10);
+    return input.slice(start);
+  };
+});
+
 // INHERITANCE
 // http://alexsexton.com/blog/2010/02/using-inheritance-patterns-to-organize-large-jquery-applications/
 // We will use John Resig's proposal
@@ -220,7 +241,7 @@ var Plugin = Class.extend({
 
     id_from_key: function(key_field, value) {
         
-        return key_field + manifold.separator + this.escape_id(value).replace(/\\/g, '');
+        return key_field + manifold.separator + this.escape_id(value); //.replace(/\\/g, '');
     },
 
     // NOTE
diff --git a/plugins/apply/__init__.py b/plugins/apply/__init__.py
new file mode 100644 (file)
index 0000000..ceb3c9a
--- /dev/null
@@ -0,0 +1,48 @@
+from unfold.plugin import Plugin
+from plugins.queryupdater import QueryUpdaterPlugin
+
+class ApplyPlugin(Plugin):
+    
+    def __init__ (self, query=None, **settings):
+        Plugin.__init__ (self, **settings)
+        self.query              = query
+
+    def template_file (self):
+        return "apply.html"
+
+    def template_env(self, request):
+        query_updater = QueryUpdaterPlugin(
+            page                = self.page,
+            title               = 'Pending operations',
+            query               = self.query,
+            togglable           = False,
+            # start turned off, it will open up itself when stuff comes in
+            toggled             = True,
+            domid               = 'pending',
+            outline_complete    = True,
+            username            = request.user, # XXX ???
+        )
+
+        env = Plugin.template_env(self, request)
+        env.update({'query_updater': query_updater.render(request)})
+        return env
+
+    def requirements (self):
+        reqs = {
+            'js_files' : [
+                'js/apply.js'
+            ],
+            'css_files' : [
+                'css/apply.css'
+            ],
+        }
+        return reqs
+
+    def json_settings_list (self):
+        # query_uuid will pass self.query results to the javascript
+        # and will be available as "record" in :
+        # on_new_record: function(record)
+        return ['plugin_uuid', 'domid', 'query_uuid']
+
+    def export_json_settings (self):
+        return True
diff --git a/plugins/apply/static/css/apply.css b/plugins/apply/static/css/apply.css
new file mode 100644 (file)
index 0000000..97b2d59
--- /dev/null
@@ -0,0 +1,3 @@
+.modal-dialog-large {
+    width: 800px;
+}
diff --git a/plugins/apply/static/js/apply.js b/plugins/apply/static/js/apply.js
new file mode 100644 (file)
index 0000000..47dc8d8
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+ * TestbedsPlugin: List of testbeds plugin
+ * Version:     0.1
+ * Description: TODO -> generalize to a list of possible filters
+ *              This file is part of the Manifold project 
+ * Requires:    js/plugin.js
+ * URL:         http://www.myslice.info
+ * Author:      Loïc Baron <loic.baron@lip6.fr>
+ * Copyright:   Copyright 2012-2013 UPMC Sorbonne Universités
+ * License:     GPLv3
+ */
+
+(function($){
+
+    var ApplyPlugin = Plugin.extend({
+
+        /** 
+         * @brief Plugin constructor
+         * @param options : an associative array of setting values
+         * @param element : 
+         * @return : a jQuery collection of objects on which the plugin is
+         *     applied, which allows to maintain chainability of calls
+         */
+        init: function(options, element)
+        {
+            // Call the parent constructor, see FAQ when forgotten
+            this._super(options, element);
+        },
+
+
+    })
+
+    /* Plugin registration */
+    $.plugin('ApplyPlugin', ApplyPlugin);
+
+})(jQuery);
diff --git a/plugins/apply/templates/apply.html b/plugins/apply/templates/apply.html
new file mode 100644 (file)
index 0000000..2e5bab0
--- /dev/null
@@ -0,0 +1,24 @@
+<div id={{ domid }}>
+  <!-- Modal - columns selector -->
+  <div class="modal fade" id="{{domid}}__apply" tabindex="-1" role="dialog" aria-labelledby="{{domid}}__apply__label" aria-hidden="true">
+    <div class="modal-dialog modal-dialog-large">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+            <h4 class="modal-title" id="{{domid}}__apply__label">Columns selector</h4>
+        </div>
+        <div class="modal-body">
+          {{query_updater}}
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  
+  <!-- Button toolbar -->
+  <button class="btn btn-primary btn-sm" data-toggle="modal" data-target="#{{domid}}__apply">Apply</button>
+  <button class="btn btn-primary btn-sm" data-toggle="modal" data-target="#{{domid}}__cancel">Cancel</button>
+</div> 
diff --git a/plugins/filter_status/__init__.py b/plugins/filter_status/__init__.py
new file mode 100644 (file)
index 0000000..a0d269b
--- /dev/null
@@ -0,0 +1,30 @@
+from unfold.plugin import Plugin
+
+class FilterStatusPlugin(Plugin):
+    
+    def __init__ (self, query=None, **settings):
+        Plugin.__init__ (self, **settings)
+        self.query              = query
+
+    def template_file (self):
+        return "filter_status.html"
+
+    def requirements (self):
+        reqs = {
+            'js_files' : [
+                'js/filter_status.js'
+            ],
+#            'css_files': [
+#                'css/myplugin.css',
+#            ]
+        }
+        return reqs
+
+    def json_settings_list (self):
+        # query_uuid will pass self.query results to the javascript
+        # and will be available as "record" in :
+        # on_new_record: function(record)
+        return ['plugin_uuid', 'domid', 'query_uuid']
+
+    def export_json_settings (self):
+        return True
diff --git a/plugins/filter_status/static/js/filter_status.js b/plugins/filter_status/static/js/filter_status.js
new file mode 100644 (file)
index 0000000..d5a13be
--- /dev/null
@@ -0,0 +1,137 @@
+/**
+ * TestbedsPlugin: List of testbeds plugin
+ * Version:     0.1
+ * Description: TODO -> generalize to a list of possible filters
+ *              This file is part of the Manifold project 
+ * Requires:    js/plugin.js
+ * URL:         http://www.myslice.info
+ * Author:      Loïc Baron <loic.baron@lip6.fr>
+ * Copyright:   Copyright 2012-2013 UPMC Sorbonne Universités
+ * License:     GPLv3
+ */
+
+(function($){
+
+    var FilterStatusPlugin = Plugin.extend({
+
+        /** 
+         * @brief Plugin constructor
+         * @param options : an associative array of setting values
+         * @param element : 
+         * @return : a jQuery collection of objects on which the plugin is
+         *     applied, which allows to maintain chainability of calls
+         */
+        init: function(options, element)
+        {
+            // Call the parent constructor, see FAQ when forgotten
+            this._super(options, element);
+
+            /* Setup query and record handlers */
+            this.listen_query(options.query_uuid);
+
+            /* Setup click handlers */
+            this.elts('list-group-item').click({'instance': this}, this._on_click);
+
+            this.prev_filter_status = null;
+
+            /* Initialize tooltips */
+            $("[rel='tooltip']").tooltip();
+
+        },
+
+    /**************************************************************************
+     *                            GUI MANAGEMENT                              *
+     **************************************************************************/
+
+        select_tab: function(tab)
+        {
+            this.elts('list-group-item').removeClass('active');
+            this.elmt(tab).addClass('active');
+        },
+
+    /**************************************************************************
+     *                            EVENT HANDLERS                              *
+     **************************************************************************/
+
+    // These functions are here to react on external filters, which we don't
+    // use at the moment
+
+    on_filter_added: function(filter) 
+    {
+        // XXX
+    },
+
+    on_filter_removed: function(filter) 
+    {
+        // XXX
+    },
+
+    on_field_state_changed: function(data) 
+    {
+        var query_ext;
+        
+        switch (data.status) {
+            case STATE_SET:
+                /* Get the number of pending / unconfigured resources */
+                /* Let's store it in query_ext */
+                query_ext = manifold.query_store.find_analyzed_query_ext(this.options.query_uuid);
+
+                $('#badge-pending').text(query_ext.num_pending);
+                if (query_ext.num_pending > 0) {
+                                       $('#badge-pending').show();
+                } else {
+                                       $('#badge-pending').hide();
+                }
+
+                $('#badge-unconfigured').text(query_ext.num_unconfigured);
+                if (query_ext.num_unconfigured > 0) {
+                                       $('#badge-unconfigured').show();
+                } else {
+                                       $('#badge-unconfigured').hide();
+                }
+            default:
+                break;
+        }
+    },
+
+    /**************************************************************************
+     *                            PRIVATE METHODS                             *
+     **************************************************************************/
+
+        /**
+         * @brief : Click event handler
+         */
+        _on_click: function(e)
+        {
+            var filter_status;
+            var filter;
+
+            // A pointer to the plugin instance, since 'this' is overriden here
+            self = e.data.instance;
+
+
+            // Select the tab...
+            filter_status = this.dataset['status'];
+            self.select_tab(filter_status);
+
+            // ... and communicate the appropriate filters to the manager
+            // NOTE: we use the manifold namespace for internal filters 
+            if (self.prev_filter_status)
+                manifold.raise_event(self.options.query_uuid, FILTER_REMOVED, self.prev_filter_status);
+
+            // XXX The datatables will be refreshed twice !
+            if (filter_status != 'all') {
+                // No filter for 'all'
+                var filter = ['manifold:status', '==', filter_status];
+                manifold.raise_event(self.options.query_uuid, FILTER_ADDED, filter);
+            }
+
+            self.prev_filter_status = filter_status;
+        },
+
+    });
+
+    /* Plugin registration */
+    $.plugin('FilterStatusPlugin', FilterStatusPlugin);
+
+})(jQuery);
diff --git a/plugins/filter_status/templates/filter_status.html b/plugins/filter_status/templates/filter_status.html
new file mode 100644 (file)
index 0000000..b6ce25d
--- /dev/null
@@ -0,0 +1,43 @@
+<div id={{ domid }}>
+  <span class="list-group-item-heading">View:</span>
+  
+  <a href="#" 
+     class="list-group-item sl-platform active" 
+     style='display: inline-block !important;' 
+     id="{{ domid }}__all" 
+     data-status="all"
+        title="View resources that are available to be reserved."
+        rel='tooltip'>
+       <p class="list-group-item-heading">Available</p>
+  </a>
+  
+  <a href="#"
+     class="list-group-item sl-platform" 
+     style='display: inline-block !important;' 
+     id="{{ domid }}__reserved" 
+     data-status="reserved"
+     title="View resources that you have previously reserved for the slice."
+        rel='tooltip'>
+       <p class="list-group-item-heading">Reserved</p>
+  </a>
+  
+  <a href="#" class="list-group-item sl-platform"
+     style='display: inline-block !important;' 
+     id="{{ domid }}__unconfigured" 
+     data-status="unconfigured"
+     title="View resources that you have selected to add to your slice, that require configuration before they can be reserved. Hover you mouse over the symbol aside the checkbox for more details."
+     rel='tooltip'>
+       <p class="list-group-item-heading">Unconfigured</p>
+       <span class="badge" id="badge-unconfigured" style="display:none;"></span></a>
+  </a>
+  
+  <a href="#" class="list-group-item sl-platform" 
+     style='display: inline-block !important;' 
+     id="{{ domid }}__pending" 
+     data-status="pending"
+     title="View pending changes to your slice, resources that you have selected to add, and resources that you have selected to remove. Click on the Apply button to apply those changes, and on the Cancel button to cancel them."
+     rel='tooltip'>
+       <p class="list-group-item-heading">Pending</p>
+       <span class="badge" id="badge-pending" style="display:none;"></span></a>
+  </a>
+</div> 
index a769f7f..08f2a7b 100644 (file)
@@ -8,12 +8,10 @@ class GoogleMap (Plugin):
     # googlemap_key :   mandatory googlemap API v3 key
     # latitude,longitude, zoom : the starting point
     # apparently at some point there has been support for a boolean 'checkboxes' input arg but seems dropped
-    def __init__ (self, query, query_all, googlemap_api_key=None, latitude=43., longitude=7., zoom=4, **settings):
+    def __init__ (self, query, googlemap_api_key=None, latitude=43., longitude=7., zoom=4, **settings):
         Plugin.__init__ (self, **settings)
         self.query=query
-        self.query_all = query_all
         self.googlemap_api_key=googlemap_api_key
-        self.query_all_uuid = query_all.query_uuid if query_all else None
         self.latitude=latitude
         self.longitude=longitude
         self.zoom=zoom
@@ -45,7 +43,7 @@ class GoogleMap (Plugin):
 
     # the list of things passed to the js plugin
     def json_settings_list (self): 
-        return [ 'plugin_uuid', 'query_uuid', 'query_all_uuid',
+        return [ 'plugin_uuid', 'query_uuid',
                  'init_key',
                  'latitude', 'longitude', 'zoom', 
                  ]
index 9b8abbc..d591a5b 100644 (file)
@@ -21,7 +21,7 @@
 .gtop{}
 .gsubcell{float:left;}
 rect{fill:none;}
-.tooltip{text-align:left;font-size:12px;}
+.Btooltip{text-align:left;font-size:12px;}
 .tooltip .top-tip{padding-bottom:2px;border-bottom:1px solid #fff}
 .tooltip .bottom-tip{padding-top:2px;}  
 .legends{
index 5a80bb9..2182e70 100644 (file)
@@ -33,6 +33,12 @@ Current implementation makes the following assumptions
   as we use 'aoColumnDefs' instead.
 """
 
+    MAP = {
+        'network_hrn'   :   'Testbed',
+        'hostname'      :   'Resource name',
+        'type'          :   'Type',
+    }
+
     def __init__ (self, query=None, query_all=None, 
                   checkboxes=False, columns=None, 
                   init_key=None,
@@ -43,20 +49,28 @@ Current implementation makes the following assumptions
         self.query_all      = query_all
         self.query_all_uuid = query_all.query_uuid if query_all else None
         self.checkboxes     = checkboxes
+
         # XXX We need to have some hidden columns until we properly handle dynamic queries
         if columns is not None:
-            self.columns=columns
-            self.hidden_columns = []
+            _columns = columns
+            _hidden_columns = []
         elif self.query:
-            self.columns = self.query.fields
+            _columns = [field for field in self.query.fields if not field == 'urn']
             if query_all:
                 # We need a list because sets are not JSON-serializable
-                self.hidden_columns = list(self.query_all.fields - self.query.fields)
+                _hidden_columns = list(self.query_all.fields - self.query.fields)
+                _hidden_columns.append('urn')
             else:
-                self.hidden_columns = []
+                _hidden_columns = []
         else:
-            self.columns = []
-            self.hidden_columns = []
+            _columns = []
+            _hidden_columns = []
+
+        print "_columns=", _columns
+        self.columns = { self.MAP.get(c, c) : c for c in _columns }
+        self.hidden_columns = { self.MAP.get(c, c) : c for c in _hidden_columns }
+        print "self.columns", self.columns
+
         self.init_key=init_key
         self.datatables_options=datatables_options
         # if checkboxes were required, we tell datatables about this column's type
@@ -96,7 +110,7 @@ Current implementation makes the following assumptions
                            # hopefully temporary, when/if datatables supports sPaginationType=bootstrap3
                            # for now we use full_numbers, with our own ad hoc css 
                            #"css/dataTables.full_numbers.css",
-                           #"css/querytable.css" , 
+                           "css/querytable.css" , 
                            ],
             }
         return reqs
index a42ab1f..6879321 100644 (file)
@@ -1,61 +1,61 @@
-/* the bottom of the datatable needs more space */
-div.querytable-spacer { padding: 8px 4px 15px 4px; }
-
-div.QueryTable table.dataTable th {
-    font: bold 12px/22px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
-    color: #4f6b72;
-    border-right: 1px solid #C1DAD7;
-    border-bottom: 1px solid #C1DAD7;
-    border-top: 1px solid #C1DAD7;
-    letter-spacing: 1px;
-    text-transform: uppercase;
-    text-align: left;
-    padding: 8px 12px 4px 20px;
-    vertical-align:middle;
-/*    background: #CAE8EA url(../img/tablesort-header.jpg) no-repeat; */
-}
-
-div.QueryTable table.dataTable th.checkbox {
-}
-
-div.QueryTable table.dataTable td, div.QueryTable table.dataTable textarea, div.QueryTable table.dataTable input [type="text"] {
-    font: normal 12px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
-    border-right: 1px solid #C1DAD7;
-    border-bottom: 1px solid #C1DAD7;
-}
-div.QueryTable table.dataTable td {
-    padding: 4px 8px 4px 8px;
-    /* this applies on even rows only, odd ones have a setting in bootstrap of rbg 249.249.249 */
-    background-color: #f4f4f4;
-}
-div.QueryTable table.dataTable td a {
-    font-weight:normal;
-}
-/* these come from bootstrap */
-div.QueryTable div.dataTables_info {
-    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
-}
-
-/* one could think or using repeat-x here but that's not working because of the arrows
- * we might need to make these wider some day 
- * and/or to add background-color: #caebea
- * which would look less conspicuous in case of overflow
-*/
-
-div.QueryTable table.dataTable thead .sorting { background: url('../img/tablesort-header-sortable.png') no-repeat; }
-div.QueryTable table.dataTable thead .sorting_asc { background: url('../img/tablesort-header-up.png') no-repeat; }
-div.QueryTable table.dataTable thead .sorting_desc { background: url('../img/tablesort-header-down.png') no-repeat; }
-/* this icons set does not have that exact equivalent - using an approximation for now */
-div.QueryTable table.dataTable thead .sorting_asc_disabled { background: url('../img/tablesort-header.png') repeat-x; }
-div.QueryTable table.dataTable thead .sorting_desc_disabled { background: url('../img/tablesort-header.png') repeat-x; }
-
-/* the footers are not active */
-div.QueryTable table.dataTable tfoot { 
-    background: url('../img/tablesort-header.png') repeat-x;
-    background-color: #caebea;
-}
-/* and when sorting is turned off it's useful to set this on header too */
-div.QueryTable table.dataTable thead { 
-    background: url('../img/tablesort-header.png') repeat-x;
-    background-color: #caebea;
+tr.even {
+    background-color: #FAFAFA;
+}
+div .added {
+       background-color: #FFFF99;
+}
+
+div .removed {
+       background-color: #E8E8E8;
+}
+
+.sidebar-nav {
+    padding: 9px 0;
+}
+
+.dropdown-menu .sub-menu {
+    left: 100%;
+    top: 0;
+    position: absolute;
+    visibility: hidden;
+    margin-top: -1px;
+}
+
+.dropdown-menu li:hover .sub-menu {
+    visibility: visible;
+}
+
+.dropdown:hover .dropdown-menu {
+    display: block;
+}
+
+.nav-tabs .dropdown-menu, .nav-pills .dropdown-menu, .navbar .dropdown-menu {
+    margin-top: 0;
+}
+
+.navbar .sub-menu:before {
+    border-bottom: 7px solid transparent;
+    border-left: none;
+    border-right: 7px solid rgba(0, 0, 0, 0.2);
+    border-top: 7px solid transparent;
+    left: -7px;
+    top: 10px;
+}
+.navbar .sub-menu:after {
+    border-top: 6px solid transparent;
+    border-left: none;
+    border-right: 6px solid #fff;
+    border-bottom: 6px solid transparent;
+    left: 10px;
+    top: 11px;
+    left: -6px;
+}
+
+.dropdown-menu-right {
+  left: auto; /* Reset the default from `.dropdown-menu` */
+  right: 0;
+}
+
+.nav > li > a.nopadding {
+  padding: 0 0;
 }
index 97fa4b4..47e0a97 100644 (file)
@@ -4,8 +4,19 @@
  * License: GPLv3
  */
 
+BGCOLOR_RESET   = 0;
+BGCOLOR_ADDED   = 1;
+BGCOLOR_REMOVED = 2;
+
 (function($){
 
+    
+    var QUERYTABLE_MAP = {
+        'Testbed': 'network_hrn',
+        'Resource name': 'hostname',
+        'Type': 'type',
+    };
+
     var debug=false;
 //    debug=true
 
            // query_uuid refers to a single object (typically a slice)
            // query_all_uuid refers to a list (typically resources or users)
            // these can return in any order so we keep track of which has been received yet
-            this.received_all_query = false;
             this.received_query = false;
 
             // We need to remember the active filter for datatables filtering
             this.filters = Array(); 
 
-            // an internal buffer for records that are 'in' and thus need to be checked 
-            this.buffered_records_to_check = [];
-           // an internal buffer for keeping lines and display them in one call to fnAddData
-           this.buffered_lines = [];
-
             /* Events */
            // xx somehow non of these triggers at all for now
             this.elmt().on('show', this, this.on_show);
@@ -60,7 +65,7 @@
 
             /* Setup query and record handlers */
             this.listen_query(options.query_uuid);
-            this.listen_query(options.query_all_uuid, 'all');
+            //this.listen_query(options.query_all_uuid, 'all');
 
             /* GUI setup and event binding */
             this.initialize_table();
                 // WARNING: this one causes tables in a 'tabs' that are not exposed at the time this is run to show up empty
                 // sScrollX: '100%',       /* Horizontal scrolling */
                 bProcessing: true,      /* Loading */
-                fnDrawCallback: function() { self._querytable_draw_callback.call(self); }
+                fnDrawCallback: function() { self._querytable_draw_callback.call(self); },
+                fnRowCallback: function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
+                    // This function is called on fnAddData to set the TR id. What about fnUpdate ?
+
+                    // Get the key from the raw data array aData
+                    var key = self.canonical_key;
+
+                    // Get the index of the key in the columns
+                    var cols = self._get_columns();
+                    var index = self.getColIndex(key, cols);
+                    if (index != -1) {
+                        // The key is found in the table, set the TR id after the data
+                        $(nRow).attr('id', self.id_from_key(key, aData[index]));
+                    }
+
+                    // That's the usual return value
+                    return nRow;
+                }
                 // XXX use $.proxy here !
             };
             // the intention here is that options.datatables_options as coming from the python object take precedence
          * @param cols
          */
         getColIndex: function(key, cols) {
-            var tabIndex = $.map(cols, function(x, i) { if (x.sTitle == key) return i; });
+            var self = this;
+            var tabIndex = $.map(cols, function(x, i) { if (self._get_map(x.sTitle) == key) return i; });
             return (tabIndex.length > 0) ? tabIndex[0] : -1;
         }, // getColIndex
 
 
         new_record: function(record)
         {
+            var self = this;
+
             // this models a line in dataTables, each element in the line describes a cell
             line = new Array();
      
             // go through table headers to get column names we want
             // in order (we have temporarily hack some adjustments in names)
-            var cols = this.table.fnSettings().aoColumns;
-            var colnames = cols.map(function(x) {return x.sTitle})
+            var cols = this._get_columns();
+            var colnames = cols.map(function(x) {return self._get_map(x.sTitle)})
             var nb_col = cols.length;
             /* if we've requested checkboxes, then forget about the checkbox column for now */
             //if (this.options.checkboxes) nb_col -= 1;
                 // Use a key instead of hostname (hard coded...)
                 line.push(this.checkbox_html(record));
                }
+            line.push('<span id="' + this.id_from_key('status', record[this.init_key]) + '"></span>'); // STATUS
                
             /* fill in stuff depending on the column name */
-            for (var j = 1; j < nb_col; j++) {
+            for (var j = 2; j < nb_col - 1; j++) { // nb_col includes status
                 if (typeof colnames[j] == 'undefined') {
                     line.push('...');
                 } else if (colnames[j] == 'hostname') {
                 }
             }
     
-            
-    
            // adding an array in one call is *much* more efficient
                // this.table.fnAddData(line);
-               this.buffered_lines.push(line);
+               return line;
         },
 
         clear_table: function()
        // this is used at init-time, at which point only init_key can make sense
        // (because the argument record, if it comes from query, might not have canonical_key set
        set_checkbox_from_record: function (record, checked) {
-            if (checked === undefined) checked = true;
+        if (checked === undefined) checked = true;
            var init_id = record[this.init_key];
-           if (debug) messages.debug("querytable.set_checkbox_from_record, init_id="+init_id);
+        this.set_checkbox_from_record_key(init_id, checked);
+       },
+
+       set_checkbox_from_record_key: function (record_key, checked) {
+        if (checked === undefined) checked = true;
+           if (debug) messages.debug("querytable.set_checkbox_from_record, record_key="+record_key);
            // using table.$ to search inside elements that are not visible
-           var element = this.table.$('[init_id="'+init_id+'"]');
+           var element = this.table.$('[init_id="'+record_key+'"]');
            element.attr('checked',checked);
        },
 
            element.attr('checked',checked);
        },
 
-        /*************************** QUERY HANDLER ****************************/
-
-        on_filter_added: function(filter)
+        /**
+         * Arguments
+         *
+         * key_value: the key from which we deduce the id
+         * request: STATUS_OKAY, etc.
+         * content: some HTML content
+         */
+        change_status: function(key_value, warnings)
         {
-            this.filters.push(filter);
-            this.redraw_table();
-        },
+            var msg;
+            
+            if ($.isEmptyObject(warnings)) { 
+                var state = manifold.query_store.get_record_state(this.options.query_uuid, key_value, STATE_SET);
+                switch(state) {
+                    case STATE_SET_IN:
+                    case STATE_SET_IN_SUCCESS:
+                    case STATE_SET_OUT_FAILURE:
+                    case STATE_SET_IN_PENDING:
+                        // Checkmark sign if no warning for an object in the set
+                        msg = '&#10003;';
+                        break;
+                    default:
+                        // Nothing is the object is not in the set
+                        msg = '';
+                        break;
+                }
+            } else {
+                msg = '<ul class="nav nav-pills">';
+                msg += '<li class="dropdown">'
+                msg += '<a href="#" data-toggle="dropdown" class="dropdown-toggle nopadding"><b>&#9888</b></a>';
+                msg += '  <ul class="dropdown-menu dropdown-menu-right" id="menu1">';
+                $.each(warnings, function(i,warning) {
+                    msg += '<li><a href="#">' + warning + '</a></li>';
+                });
+                msg += '  </ul>';
+                msg += '</li>';
+                msg += '</ul>';
+            }
 
-        on_filter_removed: function(filter)
-        {
-            // Remove corresponding filters
-            this.filters = $.grep(this.filters, function(x) {
-                return x == filter;
-            });
-            this.redraw_table();
-        },
-        
-        on_filter_clear: function()
-        {
-            // XXX
-            this.redraw_table();
-        },
+            $(document.getElementById(this.id_from_key('status', key_value))).html(msg);
+            $('[data-toggle="tooltip"]').tooltip({'placement': 'bottom'});
 
-        on_field_added: function(field)
-        {
-            this.show_column(field);
         },
 
-        on_field_removed: function(field)
+        set_bgcolor: function(key_value, class_name)
         {
-            this.hide_column(field);
+            var elt = $(document.getElementById(this.id_from_key(this.canonical_key, key_value)))
+            if (class_name == BGCOLOR_RESET)
+                elt.removeClass('added removed');
+            else
+                elt.addClass((class_name == BGCOLOR_ADDED ? 'added' : 'removed'));
         },
 
-        on_field_clear: function()
+        populate_table: function()
         {
-            alert('QueryTable::clear_fields() not implemented');
+            // Let's clear the table and only add lines that are visible
+            var self = this;
+            this.clear_table();
+
+            // XXX Here we have lost checkboxes
+            // set checkbox from record.
+            // only the current plugin known that we have an element in a set
+
+            lines = Array();
+            var record_keys = [];
+            manifold.query_store.iter_records(this.options.query_uuid, function (record_key, record) {
+                lines.push(self.new_record(record));
+                record_keys.push(record_key);
+            });
+               this.table.fnAddData(lines);
+            $.each(record_keys, function(i, record_key) {
+                var state = manifold.query_store.get_record_state(self.options.query_uuid, record_key, STATE_SET);
+                var warnings = manifold.query_store.get_record_state(self.options.query_uuid, record_key, STATE_WARNINGS);
+                switch(state) {
+                    // XXX The row and checkbox still does not exists !!!!
+                    case STATE_SET_IN:
+                    case STATE_SET_IN_SUCCESS:
+                    case STATE_SET_OUT_FAILURE:
+                        self.set_checkbox_from_record_key(record_key, true);
+                        break;
+                    case STATE_SET_OUT:
+                    case STATE_SET_OUT_SUCCESS:
+                    case STATE_SET_IN_FAILURE:
+                        //self.set_checkbox_from_record_key(record_key, false);
+                        break;
+                    case STATE_SET_IN_PENDING:
+                        self.set_checkbox_from_record_key(record_key, true);
+                        self.set_bgcolor(record_key, BGCOLOR_ADDED);
+                        break;
+                    case STATE_SET_OUT_PENDING:
+                        //self.set_checkbox_from_record_key(record_key, false);
+                        self.set_bgcolor(record_key, BGCOLOR_REMOVED);
+                        break;
+                }
+                self.change_status(record_key, warnings); // XXX will retrieve status again
+            });
         },
 
-        /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
-        /*************************** ALL QUERY HANDLER ****************************/
+        /*************************** QUERY HANDLER ****************************/
 
-        on_all_filter_added: function(filter)
+        on_filter_added: function(filter)
         {
-            // XXX
             this.redraw_table();
         },
 
-        on_all_filter_removed: function(filter)
+        on_filter_removed: function(filter)
         {
-            // XXX
             this.redraw_table();
         },
         
-        on_all_filter_clear: function()
+        on_filter_clear: function()
         {
-            // XXX
             this.redraw_table();
         },
 
-        on_all_field_added: function(field)
+        on_field_added: function(field)
         {
             this.show_column(field);
         },
 
-        on_all_field_removed: function(field)
+        on_field_removed: function(field)
         {
             this.hide_column(field);
         },
 
-        on_all_field_clear: function()
+        on_field_clear: function()
         {
             alert('QueryTable::clear_fields() not implemented');
         },
 
-
         /*************************** RECORD HANDLER ***************************/
 
-        on_new_record: function(record)
-        {
-            if (this.received_all_query) {
-               // if the 'all' query has been dealt with already we may turn on the checkbox
-                this.set_checkbox_from_record(record, true);
-            } else {
-                this.buffered_records_to_check.push(record);
-            }
-        },
-
-        on_clear_records: function()
-        {
-        },
-
         // Could be the default in parent
         on_query_in_progress: function()
         {
 
         on_query_done: function()
         {
-            this.received_query = true;
-           // unspin once we have received both
-            if (this.received_all_query && this.received_query) this.unspin();
+            this.populate_table();
+            this.spin(false);
         },
         
         on_field_state_changed: function(data)
         {
-            switch(data.request) {
-                case FIELD_REQUEST_ADD:
-                case FIELD_REQUEST_ADD_RESET:
-                    this.set_checkbox_from_data(data.value, true);
-                    break;
-                case FIELD_REQUEST_REMOVE:
-                case FIELD_REQUEST_REMOVE_RESET:
-                    this.set_checkbox_from_data(data.value, false);
-                    break;
-                default:
+            var state = manifold.query_store.get_record_state(this.options.query_uuid, data.value, data.status);
+            switch(data.status) {
+                case STATE_SET:
+                    switch(state) {
+                        case STATE_SET_IN:
+                        case STATE_SET_IN_SUCCESS:
+                        case STATE_SET_OUT_FAILURE:
+                            this.set_checkbox_from_data(data.value, true);
+                            this.set_bgcolor(data.value, BGCOLOR_RESET);
+                            break;  
+                        case STATE_SET_OUT:
+                        case STATE_SET_OUT_SUCCESS:
+                        case STATE_SET_IN_FAILURE:
+                            this.set_checkbox_from_data(data.value, false);
+                            this.set_bgcolor(data.value, BGCOLOR_RESET);
+                            break;
+                        case STATE_SET_IN_PENDING:
+                            this.set_checkbox_from_data(data.value, true);
+                            this.set_bgcolor(data.value, BGCOLOR_ADDED);
+                            break;  
+                        case STATE_SET_OUT_PENDING:
+                            this.set_checkbox_from_data(data.value, false);
+                            this.set_bgcolor(data.value, BGCOLOR_REMOVED);
+                            break;
+                    }
                     break;
-            }
-        },
 
-        /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */
-        // all
-        on_all_field_state_changed: function(data)
-        {
-            switch(data.request) {
-                case FIELD_REQUEST_ADD:
-                case FIELD_REQUEST_ADD_RESET:
-                    this.set_checkbox_from_data(data.value, true);
-                    break;
-                case FIELD_REQUEST_REMOVE:
-                case FIELD_REQUEST_REMOVE_RESET:
-                    this.set_checkbox_from_data(data.value, false);
-                    break;
-                default:
+                case STATE_WARNINGS:
+                    this.change_status(data.value, state);
                     break;
             }
         },
 
-        on_all_new_record: function(record)
-        {
-            this.new_record(record);
-        },
+        /************************** PRIVATE METHODS ***************************/
 
-        on_all_clear_records: function()
+        _get_columns: function()
         {
-            this.clear_table();
-
+            return this.table.fnSettings().aoColumns;
+            // XXX return $.map(table.fnSettings().aoColumns, function(x, i) { return QUERYTABLE_MAP[x]; });
         },
 
-        on_all_query_in_progress: function()
-        {
-            // XXX parent
-            this.spin();
-        }, // on_all_query_in_progress
-
-        on_all_query_done: function()
+        _get_map: function(column_title) {
+            return (column_title in QUERYTABLE_MAP) ? QUERYTABLE_MAP[column_title] : column_title;
+        },
+        /** 
+         * @brief QueryTable filtering function, called for every line in the datatable.
+         * 
+         * Return value:
+         *   boolean determining whether the column is visible or not.
+         */
+        _querytable_filter: function(oSettings, aData, iDataIndex)
         {
-               if (debug) messages.debug("1-shot initializing dataTables content with " + this.buffered_lines.length + " lines");
-               this.table.fnAddData (this.buffered_lines);
-               this.buffered_lines=[];
-           
             var self = this;
-           // if we've already received the slice query, we have not been able to set 
-           // checkboxes on the fly at that time (dom not yet created)
-            $.each(this.buffered_records_to_check, function(i, record) {
-                               if (debug) messages.debug ("querytable delayed turning on checkbox " + i + " record= " + record);
-                self.set_checkbox_from_record(record, true);
-            });
-               this.buffered_records_to_check = [];
+            var key_col, record_key_value;
 
-            this.received_all_query = true;
-           // unspin once we have received both
-            if (this.received_all_query && this.received_query) this.unspin();
+            /* Determine index of key in the table columns */
+            key_col = $.map(oSettings.aoColumns, function(x, i) {if (self._get_map(x.sTitle) == self.canonical_key) return i;})[0];
 
-        }, // on_all_query_done
-
-        /************************** PRIVATE METHODS ***************************/
+            /* Unknown key: no filtering */
+            if (typeof(key_col) == 'undefined') {
+                console.log("Unknown key");
+                return true;
+            }
 
-        /** 
-         * @brief QueryTable filtering function
-         */
-        _querytable_filter: function(oSettings, aData, iDataIndex)
-        {
-            var ret = true;
-            $.each (this.filters, function(index, filter) { 
-                /* XXX How to manage checkbox ? */
-                var key = filter[0]; 
-                var op = filter[1];
-                var value = filter[2];
-
-                /* Determine index of key in the table columns */
-                var col = $.map(oSettings.aoColumns, function(x, i) {if (x.sTitle == key) return i;})[0];
-
-                /* Unknown key: no filtering */
-                if (typeof(col) == 'undefined')
-                    return;
-
-                col_value=unfold.get_value(aData[col]);
-                /* Test whether current filter is compatible with the column */
-                if (op == '=' || op == '==') {
-                    if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a")
-                        ret = false;
-                }else if (op == 'included') {
-                    $.each(value, function(i,x) {
-                      if(x == col_value){
-                          ret = true;
-                          return false;
-                      }else{
-                          ret = false;
-                      }
-                    });
-                }else if (op == '!=') {
-                    if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a")
-                        ret = false;
-                } else if(op=='<') {
-                    if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a")
-                        ret = false;
-                } else if(op=='>') {
-                    if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a")
-                        ret = false;
-                } else if(op=='<=' || op=='≤') {
-                    if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a")
-                        ret = false;
-                } else if(op=='>=' || op=='≥') {
-                    if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a")
-                        ret = false;
-                }else{
-                    // How to break out of a loop ?
-                    alert("filter not supported");
-                    return false;
-                }
+            record_key_value = unfold.get_value(aData[key_col]);
 
-            });
-            return ret;
+            return manifold.query_store.get_record_state(this.options.query_uuid, record_key_value, STATE_VISIBLE);
         },
 
         _querytable_draw_callback: function()
index d0f18c0..a1792b4 100644 (file)
@@ -2,18 +2,20 @@
   <table class="table dataTable" id="{{domid}}__table" width="100%">
     <thead>
       <tr>
-       {% if checkboxes %}<th class="checkbox">+/-</th>{% endif %}
-        {% for column in columns %} <th>{{ column }}</th> {% endfor %} 
-        {% for column in hidden_columns %} <th>{{ column }}</th> {% endfor %}
+       {% if checkboxes %}<th class="checkbox"><input type="checkbox" disabled/></th>{% endif %}
+        <th></th>
+        {% for column, field in columns.items %} <th>{{ column }}</th> {% endfor %} 
+        {% for column, field in hidden_columns.items %} <th>{{ column }}</th> {% endfor %}
       </tr>
     </thead> 
     <tbody>
     </tbody>
     <tfoot>
       <tr>
-       {% if checkboxes %} <th>+/-</th> {% endif %}
-        {% for column in columns %} <th>{{ column }}</th> {% endfor %} 
-        {% for column in hidden_columns %} <th>{{ column }}</th> {% endfor %} 
+       {% if checkboxes %} <th><input type="checkbox" disabled/></th> {% endif %}
+        <th></th>
+        {% for column, field in columns.items %} <th>{{ column }}</th> {% endfor %} 
+        {% for column, field in hidden_columns.items %} <th>{{ column }}</th> {% endfor %} 
       </tr>
     </tfoot> 
   </table>
index fa09ffb..30dbcee 100644 (file)
@@ -1,6 +1,6 @@
 from unfold.plugin import Plugin
 
-class QueryUpdater(Plugin):
+class QueryUpdaterPlugin(Plugin):
 
     def __init__ (self, query=None, **settings):
         Plugin.__init__ (self, **settings)
index 3057cf5..4bd5189 100644 (file)
@@ -26,7 +26,7 @@
     // Record state through the query cycle
 
 
-    var QueryUpdater = Plugin.extend({
+    var QueryUpdaterPlugin = Plugin.extend({
 
         init: function(options, element) {
                this.classname="queryupdater";
                 // XXX check that the query is not disabled
 
                 self.spin();
-                console.log("do_update");
                 // XXX check that the query is not disabled
                 manifold.raise_event(self.options.query_uuid, RUN_UPDATE);
                 return;
 
         set_state: function(data)
         {
-            console.log("function set_state");
             var action;
             var msg;
             var button = '';
 
         // XXX we don't want to show automaticaly the pending when a checkbox is checked
            //this.toggle_on();
-           
-            switch(data.request) {
-                case FIELD_REQUEST_ADD_RESET:
-                case FIELD_REQUEST_REMOVE_RESET:
+
+            switch (data.status) {
+                case STATE_SET_IN_PENDING:
+                    action = 'ADD';
+                    msg   = 'PENDING';
+                    button = "<span class='glyphicon glyphicon-remove ResourceSelectedClose' id='" + data.key + "'/>";
+                    break;
+                case STATE_SET_OUT_PENDING:
+                    action = 'REMOVE';
+                    msg   = 'PENDING';
+                    button = "<span class='glyphicon glyphicon-remove ResourceSelectedClose' id='" + data.key + "'/>";
+                    break;
+                case STATE_SET_IN:
+                case STATE_SET_OUT:
                     // find line and delete it
+                    // XXX Naming is incorrect for badge-pending !!!!
+                    // XXX What is this badge ?
                     row = this.find_row(data.value);
                     if (row)
                         this.table.fnDeleteRow(row.nTr);
+                        /* indent was wrong !!
                         $("#badge-pending").data('number', $("#badge-pending").data('number') - 1 );
                         $("#badge-pending").text($("#badge-pending").data('number'));
+                        */
                     return;
-                case FIELD_REQUEST_CHANGE:
-                    action = 'UPDATE';
-                    break;
-                case FIELD_REQUEST_ADD:
-                    action = 'ADD';
-                    break;
-                case FIELD_REQUEST_REMOVE:
-                    action = 'REMOVE';
-                    break;
-            }
-
-            switch(data.status) {
-                case FIELD_REQUEST_PENDING:
-                    msg   = 'PENDING';
-                    button = "<span class='glyphicon glyphicon-remove ResourceSelectedClose' id='" + data.key + "'/>";
-                    break;
-                case FIELD_REQUEST_SUCCESS:
+                    break;  
+                case STATE_SET_IN_SUCCESS:
+                case STATE_SET_OUT_SUCCESS:
                     msg   = 'SUCCESS';
                     break;
-                case FIELD_REQUEST_FAILURE:
+                case STATE_SET_IN_FAILURE:
+                case STATE_SET_OUT_FAILURE:
                     msg   = 'FAILURE';
                     break;
+                case STATE_CHANGE:
+                    action = 'UPDATE';
+                    break;
+                
             }
 
             var status = msg + status;
 
-            
-
             // find line
             // if no, create it, else replace it
             // XXX it's not just about adding lines, but sometimes removing some
             // XXX how do we handle status reset ?
-            data.value = JSON.stringify(data.value);
+
+            // Jordan : I don't understand this. I added this test otherwise we have string = ""..."" double quoted twice.
+            if (typeof(data.value) !== "string")
+                data.value = JSON.stringify(data.value);
             data.selected_resources = this.selected_resources;
             row = this.find_row(data.value);
             newline = [
                 // XXX second parameter refresh = false can improve performance. todo in querytable also
                 this.table.fnAddData(newline);
                 row = this.find_row(data.value);
+                /*
                 $("#badge-pending").data('number', $("#badge-pending").data('number') + 1 );
                 $("#badge-pending").text($("#badge-pending").data('number'));
+                */
             } else {
                 // Update row text...
                 this.table.fnUpdate(newline, row.nTr);
 
         on_new_record: function(record)
         {
-            console.log("query_updater on_new_record");
-            console.log(record);
 
             // if (not and update) {
 
 
         on_query_done: function()
         {
-            console.log("on_query_done");
             this.unspin();
         },
 
         // NOTE: record_key could be sufficient 
         on_added_record: function(record)
         {
-            console.log("on_added_record = ",record);
             this.set_record_state(record, RECORD_STATE_ADDED);
             // update pending number
         },
 
         on_removed_record: function(record_key)
         {
-            console.log("on_removed_record = ",record_key);
             this.set_record_state(RECORD_STATE_REMOVED);
         },
 
 
         on_field_state_changed: function(result)
         {
-            console.log("on_field_state_changed");
-            console.log(result);
             if(result.request == FIELD_REQUEST_ADD){
                 this.selected_resources.push(result.value);
             } else if(result.request == FIELD_REQUEST_REMOVE_RESET){
                     this.selected_resources.splice(i,1);
                 }
             }
-            console.log("Resources: " + self.selected_resources);
             messages.debug(result)
-            /* this.set_state(result.request, result.key, result.value, result.status); */
+
             this.set_state(result);
         },
 
 
     });
 
-    $.plugin('QueryUpdater', QueryUpdater);
+    $.plugin('QueryUpdaterPlugin', QueryUpdaterPlugin);
 
 })(jQuery);
index 040b274..abe8eea 100755 (executable)
@@ -5,15 +5,10 @@ from datetime import timedelta
 class Scheduler2 (Plugin):\r
 \r
 \r
-    def __init__ (self, query, query_lease, query_all_resources, query_all_leases, **settings):\r
+    def __init__ (self, query, query_lease, **settings):\r
         Plugin.__init__ (self, **settings)\r
         \r
         self.query=query\r
-        self.query_all_resources = query_all_resources\r
-        self.query_all_resources_uuid = query_all_resources.query_uuid\r
-\r
-        self.query_all_leases = query_all_leases\r
-        self.query_all_leases_uuid = query_all_leases.query_uuid\r
 \r
         self.query_lease = query_lease\r
         self.query_lease_uuid = query_lease.query_uuid\r
@@ -35,12 +30,9 @@ class Scheduler2 (Plugin):
     def requirements (self):\r
         reqs = {\r
             'js_files' : [\r
-                'js/angular/angular.min.js',\r
                 'js/scheduler2.js',\r
-                'js/scheduler-SchedulerCtrl.js',\r
                 #'js/slider/jquery-ui-1.10.3.slider.min.js',\r
                 'js/scheduler-helpers.js',\r
-                'js/scheduler-table-selector.js',\r
             ],\r
             'css_files': [\r
                 'css/scheduler2.css', \r
@@ -54,7 +46,7 @@ class Scheduler2 (Plugin):
         # query_uuid will pass self.query results to the javascript\r
         # and will be available as "record" in :\r
         # on_new_record: function(record)\r
-        return ['plugin_uuid', 'domid', 'query_uuid', 'time_slots', 'nodes', 'query_lease_uuid', 'query_all_resources_uuid', 'query_all_leases_uuid']\r
+        return ['plugin_uuid', 'domid', 'query_uuid', 'time_slots', 'nodes', 'query_lease_uuid']\r
     \r
 \r
     def export_json_settings (self):\r
diff --git a/plugins/scheduler2/asdf.txt b/plugins/scheduler2/asdf.txt
deleted file mode 100755 (executable)
index 1050001..0000000
+++ /dev/null
@@ -1 +0,0 @@
-asd
\ No newline at end of file
index e4de904..cb844a8 100755 (executable)
 \r
 /** tables css **/\r
 \r
+#scheduler-reservation-table > tbody > tr > th {\r
+       font-weight: normal;\r
+}\r
+#scheduler-reservation-table > thead > tr > th {\r
+       font-weight: normal;\r
+}\r
+\r
 /*#ShedulerNodes-scroll-container {\r
     float: left;\r
     overflow-x: scroll;\r
     border: none !important;\r
 }\r
 #scheduler-reservation-table tbody tr td{\r
-    background-color: #A6C9E2 ;\r
-    border: 1px solid #111111;\r
+    background-color: #FFFFFF; /*#A6C9E2 ;*/\r
+/*    border: 1px solid #111111;*/\r
 }\r
 \r
 #scheduler-reservation-table tbody tr.even td{\r
-    background-color: #E0E0E0 ;\r
+    background-color: #FAFAFA; /*E0E0E0 ;*/\r
 }\r
 \r
 #scheduler-reservation-table tbody tr th::selection {color:    #000000;background:transparent;}\r
 }\r
 \r
 #scheduler-reservation-table tbody tr td.reserved {\r
-    background: url("../img/closed-lock-15.png") no-repeat scroll 50% 50% #DD4444;\r
+    background: url("../img/closed-lock-15.png") no-repeat scroll 50% 50%; /* #DD4444;*/\r
     cursor: not-allowed;\r
 }\r
 \r
     background: url("../img/tools-15.png") no-repeat scroll 50% 50% #EDA428;\r
 }\r
 \r
+#scheduler-reservation-table tbody tr td.pendingin {\r
+    background: #FFFF99;\r
+}\r
+\r
+\r
+#scheduler-reservation-table tbody tr td.pendingout {\r
+    background: #E8E8E8;\r
+}\r
+\r
 #scheduler-reservation-table tbody tr td.free:hover ,#scheduler-reservation-table tbody tr td.selected, #scheduler-reservation-table tbody tr td.selected_tmp {\r
     background: #25BA25;\r
 }\r
@@ -266,4 +282,4 @@ td.no-image {
 }\r
 .table-responsive{\r
     overflow: hidden !important;\r
-}
\ No newline at end of file
+}\r
diff --git a/plugins/scheduler2/static/js/scheduler-SchedulerCtrl.js b/plugins/scheduler2/static/js/scheduler-SchedulerCtrl.js
deleted file mode 100755 (executable)
index d89d8c0..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-var myApp = angular.module('myApp', []);\r
-myApp.config(function ($interpolateProvider) {\r
-    $interpolateProvider.startSymbol('{[{').endSymbol('}]}');\r
-});\r
-myApp.factory('$exceptionHandler', function () {\r
-    return function (exception, cause) {\r
-        if (exception.message.contains('leases')) {\r
-            console.log(exception.message);\r
-            \r
-            var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope();\r
-            //tmpScope.initSlots(_schedulerCurrentCellPosition, _schedulerCurrentCellPosition + SchedulerTotalVisibleCells);\r
-        }\r
-            \r
-    };\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
-\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
-        //Pagin\r
-        $scope.totalPages = 4;\r
-        $scope.curPage = 0;\r
-        this.scope.pageSize = 25;\r
-\r
-        $scope.resources = new Array();\r
-        $scope.slots = SchedulerSlotsViewData;\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
-        $scope.clearStuff = function() {\r
-            $scope.resources = new Array();\r
-            $scope.$apply();\r
-        }\r
-\r
-        $scope.initSchedulerResources = function (pageSize) {\r
-            $scope.resources = new Array();\r
-\r
-            for (var k = 0; k < pageSize; k++) {\r
-                $scope.resources.push(jQuery.extend(true, {}, SchedulerDataViewData[k]));\r
-                $scope.resources[k].leases = [];\r
-            }\r
-            $scope.pageSize = pageSize;\r
-            $scope.curPage = 0;\r
-            $scope.totalPages = parseInt(Math.ceil(SchedulerDataViewData.length / $scope.pageSize));\r
-            $scope.initSlots(0, SchedulerTotalVisibleCells);\r
-        };\r
-\r
-        $scope.setPage = function(page) {\r
-            var tmpFrm = $scope.pageSize * page;\r
-            var tmpTo = tmpFrm + $scope.pageSize;\r
-            tmpTo = SchedulerDataViewData.length < tmpTo ? SchedulerDataViewData.length : tmpTo;\r
-            $scope.curPage = page;\r
-            $scope.resources = [];\r
-            var j = 0;\r
-            for (var k = tmpFrm; k < tmpTo; k++) {\r
-                $scope.resources.push(jQuery.extend(true, {}, SchedulerDataViewData[k]));\r
-                $scope.resources[j].leases = [];\r
-                j++;\r
-            }\r
-            //fix slider\r
-            $('#tblSlider').slider('value', 0);\r
-            //init Slots\r
-            $scope.initSlots(0, SchedulerTotalVisibleCells);\r
-        };\r
-\r
-        $scope.initSlots = function (from, to) {\r
-            //init\r
-            $scope.slots = [];\r
-\r
-            var resourceIndex; //gia to paging\r
-            //set\r
-            for (var i = from; i < to; i++) {\r
-                $scope.slots.push(SchedulerSlots[i]);\r
-                resourceIndex = $scope.pageSize * $scope.curPage;\r
-                for (var j = 0; j < $scope.resources.length; j++) {\r
-                    if (i == from) {\r
-                        $scope.resources[j].leases = [];\r
-                    }\r
-                    $scope.resources[j].leases.push(SchedulerDataViewData[resourceIndex].leases[i]);\r
-                    resourceIndex++;\r
-                }\r
-            }\r
-            //apply\r
-            $scope.$apply();\r
-        };\r
-\r
-        $scope.moveFrontSlot = function(from, to) {\r
-            //$scope.slots.shift();\r
-            //$scope.slots.push(SchedulerSlots[to]);\r
-            //for (var j = 0; j < $scope.resources.length; j++) {\r
-            //    $scope.resources[j].leases.shift();\r
-            //    $scope.resources[j].leases.push(SchedulerData[j].leases[to]);\r
-            //}\r
-            //try {\r
-            //    $scope.$digest();\r
-            //    //$scope.$apply();\r
-            //} catch (err) {\r
-            //    $scope.initSlots(from, to);\r
-            //}\r
-            $scope.initSlots(from, to);\r
-        };\r
-\r
-        $scope.moveBackSlot = function(from, to) {\r
-            //$scope.$apply(function() {\r
-                //try {\r
-                //    $scope.slots.pop();\r
-                //    $scope.slots.unshift(SchedulerSlots[from]);\r
-                //    for (var j = 0; j < $scope.resources.length; j++) {\r
-                //        $scope.resources[j].leases.pop();\r
-                //        $scope.resources[j].leases.unshift(SchedulerData[j].leases[from]);\r
-                //    }\r
-                //} catch (err) {\r
-                //    alert("error");\r
-                //}\r
-\r
-            $scope.initSlots(from, to);\r
-            //});\r
-        };\r
-\r
-        $scope.getPageNumbers = function () {\r
-            var totalNumbersShowned = ($scope.totalPages > 10 ? 10 : $scope.totalPages + 1 );\r
-            var tmtNumDiv = totalNumbersShowned / 2;\r
-            //local\r
-            var numFrom = 1;\r
-            var numTo = totalNumbersShowned;\r
-            var rtrnArr = new Array();\r
-\r
-            if (totalNumbersShowned > 1) {\r
-                //set from - to\r
-                if ($scope.totalPages > totalNumbersShowned) {\r
-                    if ($scope.curPage <= tmtNumDiv) {\r
-                        //nothing\r
-                    } else if ($scope.curPage >= $scope.totalPages - tmtNumDiv) {\r
-                        numTo = $scope.totalPages;\r
-                        numFrom = numTo - totalNumbersShowned;\r
-                    } else {\r
-                        numFrom = $scope.curPage - tmtNumDiv;\r
-                        numTo = numFrom + totalNumbersShowned;\r
-                    }\r
-                }\r
-\r
-                for (var i = numFrom; i < numTo; i++)\r
-                    rtrnArr.push(i);\r
-            } else {\r
-                rtrnArr.push(1);\r
-            }\r
-            return rtrnArr;\r
-        };\r
-\r
-        // Return this object reference.\r
-        return (this);\r
-\r
-    }\r
-\r
-\r
-    // Define the Controller as the constructor function.\r
-    app.controller("SchedulerCtrl", Controller);\r
-\r
-\r
-})(angular, myApp);
\ No newline at end of file
index 1daeee0..7275b75 100755 (executable)
@@ -46,20 +46,6 @@ function schedulerCloneArray(originalArray) {
     return clonedArray;\r
 }\r
 \r
-function schedulerGetSlots(slotSpan) {\r
-    if (slotSpan == 0) slotSpan = 10;\r
-    var slots = [];\r
-    var d = new Date(2014, 1, 1, 0, 0, 0, 0);\r
-    var i = 0;\r
-    while (d.getDate() == 1) {\r
-        var tmpTime = schedulerPadStr(d.getHours()) + ':' + schedulerPadStr(d.getMinutes());\r
-        slots.push({ id: i, time: tmpTime });\r
-        d = schedulerAddMinutes(d, slotSpan);\r
-        i++;\r
-    }\r
-    return slots;\r
-}\r
-\r
 function schedulerGetLeases(slotSpan, granularity) {\r
     granularity = granularity / 60;\r
     if (slotSpan == 0) slotSpan = 10;\r
@@ -187,7 +173,16 @@ function schedulerAddMinutes(date, minutes) {
     return new Date(date.getTime() + minutes * 60000);\r
 }\r
 \r
-function schedulerCompareOnDay(dateOne, dateTwo) {\r
+/**\r
+ * Compares two dates\r
+ *\r
+ * Returns:\r
+ *   0 if they are equal\r
+ *   -1 if the first is less than the second\r
+ *   1 if the first is more than the second\r
+ */\r
+function schedulerCompareOnDay(dateOne, dateTwo)\r
+{\r
     if (dateOne.getYear() == dateTwo.getYear() &&\r
         dateOne.getMonth() == dateTwo.getMonth() &&\r
         dateOne.getDate() == dateTwo.getDate()) {\r
diff --git a/plugins/scheduler2/static/js/scheduler-table-selector.js b/plugins/scheduler2/static/js/scheduler-table-selector.js
deleted file mode 100755 (executable)
index d69a2d4..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-////version 3\r
-var scheduler_table_dragStart_td = 0;\r
-var scheduler_table_dragStart_tr = 0;\r
-var scheduler_table_dragEnd_td = 0;\r
-var scheduler_table_dragEnd_tr = 0;\r
-//tmp gia ta loops & check gia to last state\r
-var tmp_scheduler_table_dragStart_td;\r
-var tmp_scheduler_table_dragStart_tr;\r
-var tmp_scheduler_table_dragEnd_td;\r
-var tmp_scheduler_table_dragEnd_tr;\r
-var schedulerTableIsDragging = false;\r
-// try stop \r
-var continueExecuting = false;\r
-var isExecuting = false;\r
-\r
-\r
-\r
-function rangeMouseDown(e) {\r
-    if (SchedulerData) console.time("mouse:rangeMouseDown");\r
-    if (schedulerIsRightClick(e)) {\r
-        return false;\r
-    } else {\r
-        scheduler_table_dragStart_tr = $(this).parent().index();\r
-        scheduler_table_dragStart_td = $(this).index() -1;\r
-        scheduler_table_dragEnd_tr = scheduler_table_dragStart_tr;\r
-        scheduler_table_dragEnd_td = scheduler_table_dragStart_td;\r
-        //alert(scheduler_table_dragStart_tr);\r
-        //var allCells = $("#tblReservation td");\r
-        //dragStart = allCells.index($(this));\r
-\r
-        if ( $(this).hasClass("free")){\r
-            $(this).addClass("selected_tmp");\r
-            $(this).siblings("td[data-groupid='" + $(this).data('groupid') + "']").addClass("selected_tmp");\r
-        }\r
-        schedulerTableIsDragging = true;\r
-        //selectRange();\r
-\r
-        if (typeof e.preventDefault != 'undefined') { e.preventDefault(); }\r
-        document.documentElement.onselectstart = function () { return false; };\r
-    }\r
-    if (SchedulerData) console.timeEnd("mouse:rangeMouseDown");\r
-}\r
-\r
-function rangeMouseUp(e) {\r
-    if (SchedulerData) console.time("mouse:rangeMouseUp");\r
-    if (schedulerIsRightClick(e)) {\r
-        return false;\r
-    } else {\r
-        //var allCells = $("#tblReservation td");\r
-        //dragEnd = allCells.index($(this));\r
-\r
-        scheduler_table_dragEnd_tr = $(this).parent().index();\r
-        scheduler_table_dragEnd_td = $(this).index() -1;\r
-\r
-        schedulerTableIsDragging = false;\r
-        selectRange(false);\r
-\r
-        document.documentElement.onselectstart = function () { return true; };\r
-    }\r
-    if (SchedulerData) console.timeEnd("mouse:rangeMouseUp");\r
-}\r
-\r
-function rangeMouseMove(e) {\r
-    //if (SchedulerData) console.time("mouse:rangeMouseMove");\r
-    if (schedulerTableIsDragging) {\r
-        scheduler_table_dragEnd_tr = $(this).parent().attr('data-trindex');\r
-        scheduler_table_dragEnd_td = $(this).attr('data-tdindex');\r
-\r
-        //if (SchedulerData) this.SchedulerData('foo');\r
-\r
-        if ((scheduler_table_dragEnd_tr != tmp_scheduler_table_dragEnd_tr) || (scheduler_table_dragEnd_td != tmp_scheduler_table_dragEnd_td)) {\r
-            //console.log(scheduler_table_dragEnd_tr + " - " + tmp_scheduler_table_dragEnd_tr);\r
-            //console.log(scheduler_table_dragEnd_td + " - " + tmp_scheduler_table_dragEnd_td);\r
-            //selectRange(true);\r
-        }\r
-    }\r
-    //if (SchedulerData) console.timeEnd("mouse:rangeMouseMove");\r
-}\r
-\r
-function selectRange(isTemp) {\r
-    if (SchedulerData) console.time("mouse:---selectRange");\r
-\r
-    if (!schedulerCtrlPressed)\r
-        $("#" + schedulerTblId + "  td.selected, #" + schedulerTblId + "  td.selected_tmp").each(function() {\r
-            $(this).removeClass('selected selected_tmp').addClass('free');\r
-            $(this).siblings("td[data-groupid='" + $(this).data('groupid') + "']").removeClass('selected selected_tmp').addClass("free");\r
-            schedulerFreeSlot($(this).data('slotid'), $(this).siblings('th').data('rowindex'), $(this).siblings('th').data('resourceindex'));\r
-        });\r
-\r
-    tmp_scheduler_table_dragStart_td = scheduler_table_dragStart_td;\r
-    tmp_scheduler_table_dragStart_tr = scheduler_table_dragStart_tr;\r
-    tmp_scheduler_table_dragEnd_td = scheduler_table_dragEnd_td;\r
-    tmp_scheduler_table_dragEnd_tr = scheduler_table_dragEnd_tr;\r
-\r
-    if (tmp_scheduler_table_dragStart_td > tmp_scheduler_table_dragEnd_td) {\r
-        var tmp = tmp_scheduler_table_dragStart_td;\r
-        tmp_scheduler_table_dragStart_td = tmp_scheduler_table_dragEnd_td;\r
-        tmp_scheduler_table_dragEnd_td = tmp;\r
-    }\r
-\r
-    if (tmp_scheduler_table_dragStart_tr > tmp_scheduler_table_dragEnd_tr) {\r
-        var tmp = tmp_scheduler_table_dragStart_tr;\r
-        tmp_scheduler_table_dragStart_tr = tmp_scheduler_table_dragEnd_tr;\r
-        tmp_scheduler_table_dragEnd_tr = tmp;\r
-    }\r
-    //var angularScope = angular.element(document.getElementById('SchedulerCtrl')).scope();\r
-    //alert("tmp_scheduler_table_dragStart_td:" + tmp_scheduler_table_dragStart_td + "\n tmp_scheduler_table_dragStart_tr:" + tmp_scheduler_table_dragStart_tr + "\n tmp_scheduler_table_dragEnd_td:" + tmp_scheduler_table_dragEnd_td + "\n tmp_scheduler_table_dragEnd_tr:" + tmp_scheduler_table_dragEnd_tr);\r
-\r
-\r
-    for (var i = tmp_scheduler_table_dragStart_tr; i <= tmp_scheduler_table_dragEnd_tr; i++) {\r
-        for (var j = tmp_scheduler_table_dragStart_td; j <= tmp_scheduler_table_dragEnd_td; j++) {\r
-            //alert("i:" + i + "j:" + j);\r
-            var cell = $('#' + schedulerTblId + '  tbody tr:eq(' + i + ') td:eq(' + j + ')');\r
-            //$(cell)\r
-            var curClass = $(cell).attr("class");\r
-            curClass = curClass.replace('ng-scope','').trim();\r
-            //alert(curClass);\r
-            switch (curClass) {\r
-                case "free_tmp":\r
-                    $(cell).removeClass('selected_tmp selected free_tmp free');\r
-                    $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").removeClass("selected_tmp selected free_tmp free");\r
-                    if (isTemp){\r
-                        $(cell).addClass("free_tmp");\r
-                        $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("free");\r
-                    } else {\r
-                        schedulerFreeSlot($(cell).data('slotid'), $(cell).siblings('th').data('rowindex'), $(cell).siblings('th').data('resourceindex'));\r
-                        $(cell).addClass("free");\r
-                        $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("free");\r
-                    }\r
-                    break;\r
-                case "free":\r
-                    $(cell).removeClass('selected_tmp selected free_tmp free');\r
-                    $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").removeClass("selected_tmp selected free_tmp free");\r
-                    if (isTemp){\r
-                        $(cell).addClass("selected_tmp");\r
-                        $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("selected_tmp");\r
-                    }else {\r
-                        schedulerSelectSlot($(cell).data('slotid'), $(cell).siblings('th').data('rowindex'), $(cell).siblings('th').data('resourceindex'));\r
-                        $(cell).addClass("selected");\r
-                        $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("selected");\r
-                    }\r
-                    break;\r
-                case "selected_tmp":\r
-                    $(cell).removeClass('selected_tmp selected free_tmp free');\r
-                    $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").removeClass("selected_tmp selected free_tmp free");\r
-                    if (isTemp){\r
-                        $(cell).addClass("selected_tmp");\r
-                        $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("selected_tmp");\r
-                    } else {\r
-                        schedulerSelectSlot($(cell).data('slotid'), $(cell).siblings('th').data('rowindex'), $(cell).siblings('th').data('resourceindex'));\r
-                        $(cell).addClass("selected");\r
-                        $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("selected");\r
-                    }\r
-                    break;\r
-                case "selected":\r
-                    $(cell).removeClass('selected_tmp selected free_tmp free');\r
-                    $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").removeClass("selected_tmp selected free_tmp free");\r
-                    if (isTemp){\r
-                        $(cell).addClass("free_tmp");\r
-                        $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("free_tmp");\r
-                    } else {\r
-                        schedulerFreeSlot($(cell).data('slotid'), $(cell).siblings('th').data('rowindex'), $(cell).siblings('th').data('resourceindex'));\r
-                        $(cell).addClass("free");\r
-                        $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("free");\r
-                    }\r
-                    break;\r
-                case "closed":\r
-                    //do nothing\r
-                    //alert("not allowed!");\r
-                    break;\r
-            }\r
-        }\r
-    }\r
-\r
-\r
-    /*if (dragEnd + 1 < dragStart) { // reverse select\r
-    //alert(1);\r
-    $("#tblReservation td:not([class='info'])").slice(dragEnd, dragStart + 1).addClass('selected');\r
-    } else {\r
-    alert(dragStart + "-" + dragEnd);\r
-    $("#tblReservation td:not([class='info'])").slice(dragStart, dragEnd).addClass('selected');\r
-    }*/\r
-\r
-    if (SchedulerData) console.timeEnd("mouse:---selectRange");\r
-}\r
-\r
-function ClearTableSelection(){\r
-    $('#' + schedulerTblId + ' .selected').addClass("free").removeClass("selected");\r
-}\r
-\r
-\r
index 10eaa9a..5f35a0f 100755 (executable)
@@ -26,6 +26,8 @@
 #\r
 */\r
 \r
+// XXX groupid = all slots those that go with a min granularity\r
+\r
 /* some params */\r
 var scheduler2;\r
 var scheduler2Instance;\r
@@ -33,26 +35,365 @@ var scheduler2Instance;
 var schedulerCtrlPressed = false;\r
 //table Id\r
 var schedulerTblId = "scheduler-reservation-table";\r
-var schedulerTblFirstColWidth = 150;\r
-//Some Data\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
 var schedulerSlotsPerHour = 6;\r
+var RESOURCE_DEFAULT_GRANULARITY    = 1800 /* s */; // should be computed automatically from resource information\r
+var DEFAULT_GRANULARITY             = 1800 /* s */; // should be computed automatically from resource information. Test with 600\r
+var DEFAULT_PAGE_RANGE = 5;\r
+\r
 var schedulerMaxRows = 12;\r
+\r
+/* All resources */\r
 var SchedulerData = [];\r
+\r
+/* ??? */\r
 var SchedulerSlots = [];\r
+\r
 var SchedulerDateSelected = new Date();\r
+// Round to midnight\r
+SchedulerDateSelected.setHours(0,0,0,0);\r
+\r
+/* Filtered resources */\r
 var SchedulerDataViewData = [];\r
+\r
 var SchedulerSlotsViewData = [];\r
-var SchedulerTotalCells;\r
-var SchedulerTotalVisibleCells;\r
 //Help Variables\r
 var _schedulerCurrentCellPosition = 0;\r
-var _leasesDone = false;\r
-var _resourcesDone = false;\r
 //Enable Debug\r
 var schedulerDebug = true;\r
 //tmp to delete\r
 var tmpSchedulerLeases = [];\r
 \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
@@ -64,16 +405,24 @@ var tmpSchedulerLeases = [];
          *     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
+\r
                 // We need to remember the active filter for datatables filtering\r
+                // XXX not needed\r
                 this.filters = Array();\r
 \r
+                // XXX BETTER !!!!\r
+                $(window).delegate('*', 'keypress', function (evt){\r
+                        alert("erm");\r
+                      });\r
 \r
-                SchedulerSlots = schedulerGetSlots(60 / schedulerSlotsPerHour);\r
-                //selection from table \r
                 $(window).keydown(function(evt) {\r
                     if (evt.which == 17) { // ctrl\r
                         schedulerCtrlPressed = true;\r
@@ -83,154 +432,210 @@ var tmpSchedulerLeases = [];
                         schedulerCtrlPressed = false;\r
                     }\r
                 });\r
-                $("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
 \r
-                // Explain this will allow query events to be handled\r
-                // What happens when we don't define some events ?\r
-                // Some can be less efficient\r
+                // XXX naming\r
+                //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
+\r
+                this._resources_received = false;\r
+                this._leases_received = false;\r
+                \r
+                scope._leases_by_resource = {};\r
 \r
-                if (schedulerDebug) console.time("Listening_to_queries");\r
                 /* Listening to queries */\r
+                this.listen_query(options.query_uuid, 'resources');\r
+                this.listen_query(options.query_lease_uuid, 'leases');\r
 \r
-                this.listen_query(options.query_uuid);\r
-                //this.listen_query(options.query_all_uuid, 'all');\r
-                this.listen_query(options.query_all_resources_uuid, 'all_resources');\r
-                this.listen_query(options.query_lease_uuid, 'lease');\r
-                this.listen_query(options.query_all_leases_uuid, 'all_leases');\r
-                if (schedulerDebug) console.timeEnd("Listening_to_queries");\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
-            },\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
-            /* Handlers */\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
-            /* all_ev QUERY HANDLERS Start */\r
-            on_all_ev_clear_records: function(data) {\r
-                //alert('all_ev clear_records');\r
-            },\r
-            on_all_ev_query_in_progress: function(data) {\r
-                // alert('all_ev query_in_progress');\r
-            },\r
-            on_all_ev_new_record: function(data) {\r
-                //alert('all_ev new_record');\r
-            },\r
-            on_all_ev_query_done: function(data) {\r
-                //alert('all_ev query_done');\r
-            },\r
-            //another plugin has modified something, that requires you to update your display. \r
-            on_all_ev_field_state_changed: function(data) {\r
-                //alert('all_ev query_done');\r
-            },\r
-            /* all_ev QUERY HANDLERS End */\r
-            /* all_resources QUERY HANDLERS Start */\r
-            on_all_resources_clear_records: function(data) {\r
-                //data is empty on load\r
-            },\r
-            on_all_resources_query_in_progress: function(data) {\r
-                //data is empty on load\r
             },\r
-            on_all_resources_new_record: function(data) {\r
-                //alert(data.toSource());\r
-                if (data.exclusive == true) {\r
-                    var tmpGran = schedulerDebug && data.granularity == null ? 1800 : data.granularity;\r
-                    SchedulerData.push({\r
-                        id: data.urn,\r
-                        index: SchedulerData.length,\r
-                        name: data.hrn,\r
-                        granularity: tmpGran,\r
-                        leases: schedulerGetLeases(60 / schedulerSlotsPerHour, tmpGran),\r
-                        type: data.type,\r
-                        org_resource: data\r
-                    });\r
-                    /*if (schedulerDebug && SchedulerData[SchedulerData.length - 1].org_resource.network_hrn == 'omf') {\r
-                        SchedulerData[SchedulerData.length - 1].granularity = 1800;\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
+                //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
+                /* ...should be a multiple of the lcm of all encountered granularities. */\r
+                // XXX Should be updated everytime a new resource is added\r
+                this._lcm_colspan = this._lcm(this._granularity, RESOURCE_DEFAULT_GRANULARITY) / this._granularity;\r
+                this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % this._lcm_colspan;\r
+                /* scope also needs this value */\r
+                scope.num_visible_cells = this._num_visible_cells;\r
+                scope.lcm_colspan = this._lcm_colspan;\r
+\r
+                // Slider max value\r
+\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
-                //alert(data.toSource());\r
 \r
             },\r
-            on_all_resources_query_done: function(data) {\r
-                _resourcesDone = true;\r
-                this._initScheduler();\r
+\r
+            on_show: function(e)\r
+            {\r
+                var self = e.data;\r
+                self.do_resize();\r
+                self._get_scope().$apply();\r
             },\r
-            //another plugin has modified something, that requires you to update your display. \r
-            on_all_resources_field_state_changed: function(data) {\r
-                //alert('all_resources query_done');\r
+\r
+            on_resize: function(e)\r
+            {\r
+                var self = e.data;\r
+                self.do_resize();\r
+                self._get_scope().$apply();\r
             },\r
-            /* all_resources QUERY HANDLERS End */\r
-            /* lease QUERY HANDLERS Start */\r
-            on_lease_clear_records: function(data) { console.log('clear_records'); },\r
-            on_lease_query_in_progress: function(data) { console.log('lease_query_in_progress'); },\r
-            on_all_leases_new_record: function(data) {\r
-                if (data.resource.indexOf("nitos") > -1) {\r
-                    tmpSchedulerLeases.push({\r
-                        id: schedulerGetSlotId(data.start_time, data.duration, data.granularity),\r
-                        end_id: schedulerGetSlotId(data.end_time, data.duration, data.granularity),\r
-                        slice: data.slice,\r
-                        status: 'reserved',\r
-                        resource: data.resource,\r
-                        network: data.network,\r
-                        start_time: new Date(data.start_time * 1000),\r
-                        start_time_unixtimestamp: data.start_time,\r
-                        end_time: new Date(data.end_time * 1000),\r
-                        end_time_unixtimestamp: data.end_time,\r
-                        lease_type: data.lease_type,\r
-                        granularity: data.granularity,\r
-                        duration: data.duration\r
-                    });\r
-                }\r
-                //console.log(data.toSource()); console.log('lease_new_record');\r
+\r
+            /* Handlers */\r
+\r
+            _get_scope : function()\r
+            {\r
+                return angular.element(document.getElementById('SchedulerCtrl')).scope();\r
             },\r
-            on_all_leases_query_done: function(data) {\r
-                _leasesDone = true;\r
-                this._initScheduler();\r
-                // console.log('lease_query_done');\r
+            \r
+            _scope_set_resources : function()\r
+            {\r
+                var self = this;\r
+                var scope = this._get_scope();\r
+\r
+                var records = manifold.query_store.get_records(this.options.query_uuid);\r
+\r
+                scope.resources = [];\r
+\r
+                $.each(records, function(i, record) {\r
+                    if (!record.exclusive)\r
+                        return true; // ~ continue\r
+\r
+                    // copy not to modify original record\r
+                    var resource = jQuery.extend(true, {}, record);\r
+\r
+                    // Fix granularity\r
+                    resource.granularity = typeof(resource.granularity) == "number" ? resource.granularity : RESOURCE_DEFAULT_GRANULARITY;\r
+                    resource.leases = []; // a list of occupied timeslots\r
+\r
+                    self.scope_resources_by_key[resource['urn']] = resource;\r
+                    scope.resources.push(resource);\r
+                });\r
             },\r
-            //another plugin has modified something, that requires you to update your display. \r
-            on_lease_field_state_changed: function(data) { console.log('lease_field_state_changed'); },\r
-            /* lease QUERY HANDLERS End */\r
 \r
+            _scope_clear_leases: function()\r
+            {\r
+                var self = this;\r
+                var scope = this._get_scope();\r
+\r
+                // Setup leases with a default free status...\r
+                $.each(this.scope_resources_by_key, function(resource_key, resource) {\r
+                    resource.leases = [];\r
+                    var colspan_lease = resource.granularity / self._granularity; //eg. 3600 / 1800 => 2 cells\r
+                    for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity\r
+                        resource.leases.push({\r
+                            id:     'coucou',\r
+                            status: 'free', // 'selected', 'reserved', 'maintenance' XXX pending ??\r
+                        });\r
+                    }\r
+                });\r
 \r
-            // no prefix\r
-            on_filter_added: function(filter) {\r
-                this.filters.push(filter);\r
-                this._SetFiletredResources(this.filters);\r
-                //angular and UI\r
-                var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope();\r
-                if (SchedulerDataViewData.length == 0) {\r
-                    $("#plugin-scheduler").hide();\r
-                    $("#plugin-scheduler-empty").show();\r
-                    tmpScope.clearStuff();\r
-                } else {\r
-                    $("#plugin-scheduler-empty").hide();\r
-                    $("#plugin-scheduler").show();\r
-                    tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);\r
-                }\r
             },\r
 \r
-            on_filter_removed: function(filter) {\r
-                // Remove corresponding filters\r
-                this.filters = $.grep(this.filters, function(x) {\r
-                    return x == filter;\r
+            _scope_set_leases: function()\r
+            {\r
+                var self = this;\r
+                var scope = this._get_scope();\r
+            \r
+                var leases = manifold.query_store.get_records(this.options.query_lease_uuid);\r
+                $.each(leases, function(i, lease) {\r
+\r
+                    console.log("SET LEASES", new Date(lease.start_time* 1000));\r
+                    console.log("          ", new Date(lease.end_time* 1000));\r
+                    // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work\r
+\r
+                    // Populate leases by resource array: this will help us merging leases later\r
+                    if (!(lease.resource in scope._leases_by_resource))\r
+                        scope._leases_by_resource[lease.resource] = [];\r
+                    scope._leases_by_resource[lease.resource].push(lease);\r
+\r
+                    var resource = self.scope_resources_by_key[lease.resource];\r
+                    var day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
+\r
+                    var id_start = (lease.start_time - day_timestamp) / resource.granularity;\r
+                    if (id_start < 0) {\r
+                        /* Some leases might be in the past */\r
+                        id_start = 0;\r
+                    }\r
+    \r
+                    var id_end   = (lease.end_time   - day_timestamp) / resource.granularity - 1;\r
+                    var colspan_lease = resource.granularity / self._granularity; //eg. 3600 / 1800 => 2 cells\r
+                    if (id_end >= self._all_slots.length / colspan_lease) {\r
+                        /* Limit the display to the current day */\r
+                        id_end = self._all_slots.length / colspan_lease\r
+                    }\r
+\r
+                    for (i = id_start; i <= id_end; i++)\r
+                        // the same slots might be affected multiple times.\r
+                        // PENDING_IN + PENDING_OUT => IN \r
+                        //\r
+                        // RESERVED vs SELECTED !\r
+                        //\r
+                        // PENDING !!\r
+                        resource.leases[i].status = 'selected'; \r
                 });\r
-                this._SetFiletredResources(this.filters);\r
-                //angular and UI\r
-                var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope();\r
-                if (SchedulerDataViewData.length == 0) {\r
-                    $("#plugin-scheduler").hide();\r
-                    $("#plugin-scheduler-empty").show();\r
-                    tmpScope.clearStuff();\r
-                } else {\r
-                    $("#plugin-scheduler-empty").hide();\r
-                    $("#plugin-scheduler").show();\r
-                    tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);\r
+            },\r
+\r
+            on_resources_query_done: function(data)\r
+            {\r
+                this._resources_received = true;\r
+                this._scope_set_resources();\r
+                this._scope_clear_leases();\r
+                if (this._leases_received)\r
+                    this._scope_set_leases();\r
+                    \r
+                this._get_scope().$apply();\r
+            },\r
+\r
+            on_leases_query_done: function(data)\r
+            {\r
+                this._leases_received = true;\r
+                if (this._resources_received) {\r
+                    this._scope_set_leases();\r
+                    this._get_scope().$apply();\r
                 }\r
             },\r
 \r
-            on_filter_clear: function() {\r
-                this.filters = [];\r
-                this._SetFiletredResources(this.filters);\r
-                //angular and UI\r
-                var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope();\r
+            /* Filters on resources */\r
+            on_resources_filter_added:   function(filter) { this._get_scope().$apply(); },\r
+            on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },\r
+            on_resources_filter_clear:   function()       { this._get_scope().$apply(); },\r
+\r
+            /* Filters on leases ? */\r
+            on_leases_filter_added:      function(filter) { this._get_scope().$apply(); },\r
+            on_leases_filter_removed:    function(filter) { this._get_scope().$apply(); },\r
+            on_leases_filter_clear:      function()       { this._get_scope().$apply(); },\r
+\r
+            /* INTERNAL FUNCTIONS */\r
+\r
+/* XXX IN TEMPLATE XXX\r
                 if (SchedulerDataViewData.length == 0) {\r
                     $("#plugin-scheduler").hide();\r
                     $("#plugin-scheduler-empty").show();\r
@@ -238,296 +643,151 @@ var tmpSchedulerLeases = [];
                 } else {\r
                     $("#plugin-scheduler-empty").hide();\r
                     $("#plugin-scheduler").show();\r
+                    // initSchedulerResources\r
                     tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);\r
                 }\r
-            },\r
-\r
-            on_all_leases_filter_added: function(filter) {\r
-                console.log("Filter on Leases added !");\r
-            },\r
-\r
-            // ... be sure to list all events here\r
-\r
-            /* RECORD HANDLERS */\r
-            on_all_new_record: function(record) {\r
-                //alert('on_all_new_record');\r
-            },\r
-\r
-            debug: function(logTxt) {\r
-                if (typeof window.console != 'undefined') {\r
-                    console.debug(logTxt);\r
-                }\r
-            },\r
+*/\r
 \r
-            /* INTERNAL FUNCTIONS */\r
-            _initScheduler: function() {\r
-                if (_resourcesDone && _leasesDone) {\r
-                    SchedulerDataViewData = SchedulerData;\r
-                    /* GUI setup and event binding */\r
-                    this._FixLeases();\r
-                    this._initUI();\r
-                }\r
-            },\r
+            /**\r
+             * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.\r
+             */\r
+            _initUI: function() \r
+            {\r
+                var self = this;\r
 \r
-            _initUI: function() {\r
-                //alert(1);\r
-                if (schedulerDebug) console.time("_initUI");\r
-                //init DatePicker Start\r
                 $("#DateToRes").datepicker({\r
-                    dateFormat: "yy-mm-dd",\r
-                    minDate: 0,\r
-                    numberOfMonths: 3\r
-                }).change(function() {\r
-                    //Scheduler2.loadWithDate();\r
-                    SchedulerDateSelected = $("#DateToRes").datepicker("getDate");\r
-                    if (SchedulerDateSelected != null && SchedulerDateSelected != '') {\r
-                        for (var i = 0; i < SchedulerData.length; i++) {\r
-                            SchedulerData[i].leases = schedulerGetLeases(60 / schedulerSlotsPerHour, SchedulerData[i].granularity);\r
-                        }\r
-                        scheduler2Instance._FixLeases();\r
-                        $('#tblSlider').slider('value', 0);\r
-                        var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope();\r
-                        tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);\r
-\r
-                        //console.log(SchedulerDateSelected);\r
-                        //console.log(SchedulerDateSelected.getTime()/1000);\r
-                        var tomorrow = new Date(SchedulerDateSelected);\r
-                        tomorrow.setDate(SchedulerDateSelected.getDate()+1);\r
-                        //console.log(tomorrow);\r
-                        //console.log(tomorrow.getTime()/1000);\r
-                        \r
-                        // Remove previous date interval\r
-                        manifold.raise_event(scheduler2Instance.options.query_all_leases_uuid, FILTER_REMOVED, ['start_time', '>']);\r
-                        manifold.raise_event(scheduler2Instance.options.query_all_leases_uuid, FILTER_REMOVED, ['start_time', '<']);\r
-\r
-                        // Add new date interval\r
-                        manifold.raise_event(scheduler2Instance.options.query_all_leases_uuid, FILTER_ADDED, ['start_time', '>', SchedulerDateSelected.getTime()/1000]);\r
-                        manifold.raise_event(scheduler2Instance.options.query_all_leases_uuid, FILTER_ADDED, ['start_time', '<', tomorrow.getTime()/1000]);\r
-                    } else {\r
-                        alert("Please select a date, so the scheduler can reserve leases.");\r
+                    onRender: function(date) {\r
+                        return date.valueOf() < now.valueOf() ? 'disabled' : '';\r
                     }\r
-                }).datepicker('setDate', SchedulerDateSelected);\r
-                /*.click(function () {\r
-                $("#ui-datepicker-div").css("z-index", 5);\r
-            })*/\r
-                //End init DatePicker\r
-\r
-                //init Table\r
-                this._FixTable();\r
-                //End init Table\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('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('setValue', SchedulerDateSelected); //.data('datepicker');\r
 \r
                 //init Slider\r
                 $('#tblSlider').slider({\r
                     min: 0,\r
-                    max: SchedulerTotalCells - SchedulerTotalVisibleCells,\r
+                    max: (self._all_slots.length - self._num_visible_cells) / self._lcm_colspan,\r
                     value: 0,\r
-                    slide: function(event, ui) {\r
-                        //$("#amount").val("$" + ui.values[0] + " - $" + ui.values[1]);\r
-                        //console.log(ui.value);\r
-                        var angScope = angular.element(document.getElementById('SchedulerCtrl')).scope();\r
-                        if (_schedulerCurrentCellPosition > ui.value) {\r
-                            angScope.moveBackSlot(ui.value, ui.value + SchedulerTotalVisibleCells);\r
-                        } else if (_schedulerCurrentCellPosition < ui.value) {\r
-                            angScope.moveFrontSlot(ui.value, ui.value + SchedulerTotalVisibleCells);\r
-                        }\r
-                        _schedulerCurrentCellPosition = ui.value;\r
-                    }\r
-                });\r
-                //End init Slider\r
-\r
-\r
-                //btn Submit leases\r
-                $('#btnSchedulerSubmit').click(function () {\r
-                    console.log("click btnSchedulerSubmit");\r
-                    var leasesForCommit = new Array();\r
-                    var tmpDateTime = SchedulerDateSelected;\r
-                    console.log(SchedulerData);\r
-                    for (var i = 0; i < SchedulerData.length; i++)\r
-                    {\r
-                        var tpmR = SchedulerData[i];\r
-                        //for capturing start and end of the lease\r
-                        var newLeaseStarted = false;\r
-                        for (var j = 0; j < tpmR.leases.length; j++) {\r
-                            var tpmL = tpmR.leases[j];\r
-                            if (newLeaseStarted == false && tpmL.status == 'selected') {\r
-                                //get date of the slot\r
-                                tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime);\r
-                                var unixStartTime = tmpDateTime.getTime() / 1000;\r
-                                //add lease object\r
-                                leasesForCommit.push({\r
-                                    resource: tpmR.id,\r
-                                    //granularity: tpmR.granularity,\r
-                                    //lease_type: null,\r
-                                    //slice: null,\r
-                                    start_time: unixStartTime,\r
-                                    end_time: null,\r
-                                    //duration: null\r
-                                });\r
-                                console.log(tpmR.id);\r
-                                newLeaseStarted = true;\r
-                            } else if (newLeaseStarted == true && tpmL.status != 'selected') {\r
-                                //get date of the slot\r
-                                tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime);\r
-                                var unixEndTime = tmpDateTime.getTime() / 1000;\r
-                                //upate end_time\r
-                                var tmpCL = leasesForCommit[leasesForCommit.length - 1];\r
-                                tmpCL.end_time = unixEndTime;\r
-                                //tmpCL.duration = schedulerFindDuration(tmpCL.start_time, tmpCL.end_time, tmpCL.granularity);\r
-                                newLeaseStarted = false;\r
-                            }\r
-                        }\r
-                    }\r
-                    console.log(leasesForCommit);\r
-                    for (var i = 0; i < leasesForCommit.length; i++) {\r
-                        manifold.raise_event(scheduler2Instance.options.query_lease_uuid, SET_ADD, leasesForCommit[i]);\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
 \r
-\r
-                //End btn Submit leases\r
-\r
-                //other stuff\r
                 $("#plugin-scheduler-loader").hide();\r
                 $("#plugin-scheduler").show();\r
-                //fixOddEvenClasses();\r
-                //$("#" + schedulerTblId + " td:not([class])").addClass("free");\r
-                if (schedulerDebug) console.timeEnd("_initUI");\r
             },\r
 \r
-        _FixLeases  : function () {\r
-            for (var i = 0; i < tmpSchedulerLeases.length; i++) {\r
-                var tmpLea = tmpSchedulerLeases[i];\r
-                if ((schedulerCompareOnDay(tmpLea.start_time, SchedulerDateSelected) == 0) ||\r
-                                (tmpLea.start_time <= SchedulerDateSelected && SchedulerDateSelected <= tmpLea.end_time) || \r
-                                (schedulerCompareOnDay(tmpLea.end_time, SchedulerDateSelected) == 0)) {\r
-                    var tmpRes = schedulerFindResourceById(SchedulerData, tmpLea.resource);\r
-                    if (tmpRes != null) {\r
-                        //Replace Lease with current lease from the manifold\r
-                        var orgLease = tmpRes.leases[tmpLea.id];\r
-                        tmpLea['groupid'] = orgLease.groupid;\r
-                        tmpLea['groupIndex'] = orgLease.groupIndex;\r
-                        if (orgLease.groupIndex != 0) {\r
-                            if (!window.console) {\r
-                                console.warn('there is an error with the leases of the resource :' + tmpRes.name + '\n The lease start in the middle of the granularity!' + '\n The Scheduler plugin might not work!');\r
-                            }\r
-                        }\r
-                        tmpRes.leases[tmpLea.id] = tmpLea;\r
-                        this._ExtractLeaseSlots(tmpRes, tmpRes.leases[tmpLea.id]);\r
+        // GUI EVENTS\r
+\r
+        // TO BE REMOVED\r
+        _on_submit : function()\r
+        {\r
+            var leasesForCommit = new Array();\r
+            var tmpDateTime = SchedulerDateSelected;\r
+            for (var i = 0; i < SchedulerData.length; i++)\r
+            {\r
+                var tpmR = SchedulerData[i];\r
+                //for capturing start and end of the lease\r
+                var newLeaseStarted = false;\r
+                for (var j = 0; j < tpmR.leases.length; j++) {\r
+                    var tpmL = tpmR.leases[j];\r
+                    if (newLeaseStarted == false && tpmL.status == 'selected') {\r
+                        //get date of the slot\r
+                        tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime);\r
+                        var unixStartTime = tmpDateTime.getTime() / 1000;\r
+                        //add lease object\r
+                        leasesForCommit.push({\r
+                            resource: tpmR.id,\r
+                            //granularity: tpmR.granularity,\r
+                            //lease_type: null,\r
+                            //slice: null,\r
+                            start_time: unixStartTime,\r
+                            end_time: null,\r
+                            //duration: null\r
+                        });\r
+                        console.log(tpmR.id);\r
+                        newLeaseStarted = true;\r
+                    } else if (newLeaseStarted == true && tpmL.status != 'selected') {\r
+                        //get date of the slot\r
+                        tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime);\r
+                        var unixEndTime = tmpDateTime.getTime() / 1000;\r
+                        //upate end_time\r
+                        var tmpCL = leasesForCommit[leasesForCommit.length - 1];\r
+                        tmpCL.end_time = unixEndTime;\r
+                        //tmpCL.duration = schedulerFindDuration(tmpCL.start_time, tmpCL.end_time, tmpCL.granularity);\r
+                        newLeaseStarted = false;\r
                     }\r
                 }\r
             }\r
-        },\r
-\r
-        _ExtractLeaseSlots: function (tmpRes, lease) {\r
-            var tmpStartDate = lease.start_time;\r
-            var tmpEndDate = lease.end_time;\r
-            var startLoop; var toLoop;\r
-            if (schedulerCompareOnDay(lease.start_time,lease.end_time) == 0) {\r
-                //in the same date\r
-                startLoop = lease.id;\r
-                toLoop = lease.end_id;\r
-            } else if (lease.start_time < SchedulerDateSelected && SchedulerDateSelected < lease.end_time) {\r
-                //one hole day (more than 3days)\r
-                startLoop = 0;\r
-                toLoop = tmpRes.leases.length;\r
-            } else if (schedulerCompareOnDay(lease.start_time, SchedulerDateSelected) == 0) {\r
-                //the same day and extends\r
-                startLoop = lease.id;\r
-                toLoop = tmpRes.leases.length;\r
-            } else if (schedulerCompareOnDay(lease.end_time, SchedulerDateSelected) == 0) {\r
-                //extends to the last say\r
-                startLoop = 0;\r
-                toLoop = lease.end_id;\r
+            console.log(leasesForCommit);\r
+            for (var i = 0; i < leasesForCommit.length; i++) {\r
+                manifold.raise_event(scheduler2Instance.options.query_lease_uuid, SET_ADD, leasesForCommit[i]);\r
             }\r
-            //var minutGran = tmpRes.granularity * 60;\r
-            for (var li = lease.id; li < toLoop; li++) {\r
-                tmpRes.leases[li].status = 'reserved';\r
-            }\r
-            \r
-            //reserved\r
-            //tmpRes.leases[tmpLea.id\r
         },\r
+        \r
+        // PRIVATE METHODS\r
 \r
-        _FixTable: function () {\r
-            var colWidth = 50;\r
-            SchedulerTotalCells = SchedulerSlots.length;\r
-            $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", schedulerTblFirstColWidth); //.css("display", "block");\r
-            //this get width might need fix depending on the template \r
-            var tblwidth = $('#scheduler-tab').parent().outerWidth();\r
-            SchedulerTotalVisibleCells = parseInt((tblwidth - schedulerTblFirstColWidth) / colWidth);\r
-\r
-            //if (SchedulerData.length == 0) {\r
-            //    //puth some test data\r
-            //    SchedulerData.push({ name: 'xyz+aaa', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'xyz+aaa', type: 'node' });\r
-            //    SchedulerData.push({ name: 'xyz+bbb', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'xyz+bbb', type: 'node' });\r
-            //    SchedulerData.push({ name: 'xyz+ccc', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'xyz+ccc', type: 'node' });\r
-            //    SchedulerData.push({ name: 'nitos1', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'nitos1', type: 'node' });\r
-            //}\r
-            var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope();\r
-            tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);\r
-\r
+        /**\r
+         * Greatest common divisor\r
+         */\r
+        _gcd : function(x, y)\r
+        {\r
+            return (y==0) ? x : this._gcd(y, x % y);\r
         },\r
 \r
-        _SetFiletredResources : function (filters) {\r
-            if (filters.length > 0) {\r
-                SchedulerDataViewData = new Array();\r
-                var tmpAddIt = true;\r
-                for (var i = 0; i < SchedulerData.length; i++) {\r
-                    loopfilters:\r
-                    for (var f = 0; f < filters.length; f++) {\r
-                        tmpAddIt = this._FilterResource(SchedulerData[i], filters[f]);\r
-                        if (tmpAddIt == false) break loopfilters;\r
-                    }\r
-                    if (tmpAddIt) {\r
-                        SchedulerDataViewData.push(SchedulerData[i]);\r
-                    }\r
-                }\r
-            } else {\r
-                SchedulerDataViewData = SchedulerData;\r
-            }\r
+        /**\r
+         * Least common multiple\r
+         */\r
+        _lcm : function(x, y)\r
+        {\r
+            return x * y / this._gcd(x, y);\r
+        },\r
+    \r
+        _pad_str : function(i)\r
+        {\r
+            return (i < 10) ? "0" + i : "" + i;\r
         },\r
 \r
-        _FilterResource: function (resource, filter) {\r
-            var key = filter[0];\r
-            var op = filter[1];\r
-            var value = filter[2];\r
-            var colValue = resource.org_resource[key];\r
-            var ret = true;\r
-            if (schedulerDebug &&  colValue == 'omf') colValue = 'nitos';\r
-\r
-            if (op == '=' || op == '==') {\r
-                if (colValue != value || colValue == null || colValue == "" || colValue == "n/a")\r
-                    ret = false;\r
-            } else if (op == 'included') {\r
-                $.each(value, function (i, x) {\r
-                    if (x == colValue) {\r
-                        ret = true;\r
-                        return false;\r
-                    } else {\r
-                        ret = false;\r
-                    }\r
-                });\r
-            } else if (op == '!=') {\r
-                if (colValue == value || colValue == null || colValue == "" || colValue == "n/a")\r
-                    ret = false;\r
+        /**\r
+         * Member variables used:\r
+         *   _granularity\r
+         * \r
+         * Returns:\r
+         *   A list of {id, time} dictionaries.\r
+         */\r
+        _generate_all_slots: function()\r
+        {\r
+            var slots = [];\r
+            // Start with a random date (a first of a month), only time will matter\r
+            var d = new Date(2014, 1, 1, 0, 0, 0, 0);\r
+            var i = 0;\r
+            // Loop until we change the day\r
+            while (d.getDate() == 1) {\r
+                // Nicely format the time...\r
+                var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());\r
+                /// ...and add the slot to the list of results\r
+                slots.push({ id: i, time: tmpTime });\r
+                // Increment the date with the granularity\r
+                d = new Date(d.getTime() + this._granularity * 1000);\r
+                i++;\r
             }\r
+            return slots;\r
 \r
-            return ret;\r
         },\r
-\r
-        _SetPeriodInPage: function (start, end) {\r
-        }\r
     });\r
 \r
-    //Sched2 = new Scheduler2();\r
-\r
     /* Plugin registration */\r
     $.plugin('Scheduler2', scheduler2);\r
 \r
-    // TODO Here use cases for instanciating plugins in different ways like in the pastie.\r
-\r
-\r
 })(jQuery);\r
 \r
 \r
diff --git a/plugins/scheduler2/static/js/scheduler2_old.js b/plugins/scheduler2/static/js/scheduler2_old.js
deleted file mode 100755 (executable)
index ee833bf..0000000
+++ /dev/null
@@ -1,325 +0,0 @@
-/*\r
-#\r
-# Copyright (c) 2013 NITLab, University of Thessaly, CERTH, Greece\r
-#\r
-# Permission is hereby granted, free of charge, to any person obtaining a copy\r
-# of this software and associated documentation files (the "Software"), to deal\r
-# in the Software without restriction, including without limitation the rights\r
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
-# copies of the Software, and to permit persons to whom the Software is\r
-# furnished to do so, subject to the following conditions:\r
-#\r
-# The above copyright notice and this permission notice shall be included in\r
-# all copies or substantial portions of the Software.\r
-#\r
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE\r
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
-# THE SOFTWARE.\r
-#\r
-#\r
-# This is a MySlice plugin for the NITOS Scheduler\r
-# Nitos Scheduler v1\r
-#\r
-*/\r
-\r
-/* some params */\r
-var init_start_visible_index = 10;\r
-var init_end_visible_index = 21;\r
-var rsvrTblNm = "scheduler-reservation-table";\r
-var SchedulerResources = [];\r
-var schdlr_totalColums = 0;\r
-var SetPerFun = null;\r
-var Sched2 = null;\r
-var Debug = true;\r
-var schdlr_PartsInOneHour = 6;\r
-\r
-(function ($) {\r
-    var Scheduler2 = Plugin.extend({\r
-\r
-        /** XXX to check\r
-         * @brief Plugin constructor\r
-         * @param options : an associative array of setting values\r
-         * @param element : \r
-         * @return : a jQuery collection of objects on which the plugin is\r
-         *     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
-            schdlr_totalColums = $("#scheduler-reservation-table th").length;\r
-\r
-            //selection from table \r
-            $(window).keydown(function (evt) {\r
-                if (evt.which == 17) { // ctrl\r
-                    ctrlPressed = true;\r
-                }\r
-            }).keyup(function (evt) {\r
-                if (evt.which == 17) { // ctrl\r
-                    ctrlPressed = false;\r
-                }\r
-            });\r
-            $("#" + rsvrTblNm).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
-\r
-            // Explain this will allow query events to be handled\r
-            // What happens when we don't define some events ?\r
-            // Some can be less efficient\r
-\r
-            if (Debug) console.time("Listening_to_queries");\r
-            /* Listening to queries */\r
-            this.listen_query(options.query_uuid, 'all_ev');\r
-            this.listen_query(options.query_all_resources_uuid, 'all_resources');\r
-            this.listen_query(options.query_lease_uuid, 'lease');\r
-            //this.listen_query(options.query_lease_uuid, 'lease');\r
-            if (Debug) console.timeEnd("Listening_to_queries");\r
-\r
-            $("#ShedulerNodes tbody").html('<tr><td id="schdlr_frstTD" style="background:transparent; height:45px; border:none;"></td></tr>');\r
-        },\r
-\r
-        /* PLUGIN EVENTS */\r
-        // on_show like in querytable\r
-\r
-\r
-        /* GUI EVENTS */\r
-\r
-        // a function to bind events here: click change\r
-        // how to raise manifold events\r
-\r
-\r
-        /* GUI MANIPULATION */\r
-\r
-        // We advise you to write function to change behaviour of the GUI\r
-        // Will use naming helpers to access content _inside_ the plugin\r
-        // always refer to these functions in the remaining of the code\r
-\r
-        show_hide_button: function () {\r
-            // this.id, this.el, this.cl, this.elts\r
-            // same output as a jquery selector with some guarantees\r
-        },\r
-\r
-        //drawResources: function () {\r
-        drawLeases: function () {\r
-            \r
-            //if (Debug) this.debug('foo');\r
-            if (Debug) console.time("each:SchedulerResources");\r
-\r
-            //scheduler-reservation-table main table columns\r
-            totalColums = $("#scheduler-reservation-table thead tr th").length;\r
-            //var totalCell = [];\r
-            //for (var i = 0; i < totalColums; i++) { totalCell.push("<td></td>"); }\r
-            //var srt_body = [];\r
-            var totalCell = "";\r
-            for (var i = 0; i < totalColums; i++) totalCell +="<td></td>"; \r
-            var srt_body = "";\r
-            /*\r
-            $.each(SchedulerResources, function (i, group) {\r
-                console.log(group.groupName);\r
-                //var groupTR = $("#ShedulerNodes tbody").html('<tr><td class="no-image verticalIndex" rowspan="' + group.resources.length + '"><div class="verticalText">' + group.groupName + '</div></td><td id="schdlr_frstTD" class="info fixed"></td></tr>');\r
-                //var groupTR = $("#ShedulerNodes tbody").html('<tr><td class="no-image verticalIndex" rowspan="' + 30 + '"><div class="verticalText">' + group.groupName + '</div></td><td id="schdlr_frstTD" class="info fixed"></td></tr>');\r
-                var groupTR = $("#ShedulerNodes tbody").html('<tr><td id="schdlr_frstTD" class="info fixed"></td></tr>');\r
-                \r
-                //$.each(group.resources.slice(0,30), function (i, resource) {\r
-                $.each(group.resources, function (i, resource) {\r
-                    if (i == 0) {\r
-                        //$("#ShedulerNodes tbody tr:first").append('<td class="info fixed">' + resource.hostname + '</td>');\r
-                        $(groupTR).find("#schdlr_frstTD").html(resource.urn);\r
-                        //$(srt_body).html("<tr>" + totalCell + "</tr>");\r
-                    } else {\r
-                        $(groupTR).find("tr:last").after('<tr><td class="info fixed">' + resource.urn + '</td></tr>');\r
-                        //$(srt_body).find("tr:last").after("<tr>" + totalCell + "</tr>");\r
-                    }\r
-                    srt_body += "<tr>" + totalCell + "</tr>";\r
-                    //srt_body.push('<tr>'); srt_body = srt_body.concat(totalCell.concat()); srt_body.push('/<tr>');\r
-                });\r
-            });\r
-            */\r
-\r
-            srt_body += "<tr>" + totalCell + "</tr>";\r
-            //$("#scheduler-reservation-table tbody").html(srt_body.join(""));\r
-            //$("#scheduler-reservation-table tbody").append(srt_body);\r
-\r
-            if (Debug) console.timeEnd("each:SchedulerResources");\r
-            \r
-\r
-            $("#" + rsvrTblNm + " tbody tr").each(function (index) { $(this).attr("data-trindex", index); });\r
-\r
-        },\r
-\r
-        /* TEMPLATES */\r
-\r
-        // see in the html template\r
-        // How to load a template, use of mustache\r
-\r
-        /* QUERY HANDLERS */\r
-        loadWithDate: function () {\r
-            // only convention, not strictly enforced at the moment\r
-        },\r
-        // How to make sure the plugin is not desynchronized\r
-        // He should manifest its interest in filters, fields or records\r
-        // functions triggered only if the proper listen is done\r
-\r
-        /* all_ev QUERY HANDLERS Start */\r
-        on_all_ev_clear_records: function (data) {\r
-            //alert('all_ev clear_records');\r
-        },\r
-        on_all_ev_query_in_progress: function (data) {\r
-           // alert('all_ev query_in_progress');\r
-        },\r
-        on_all_ev_new_record: function (data) {\r
-            //alert('all_ev new_record');\r
-        },\r
-        on_all_ev_query_done: function (data) {\r
-            //alert('all_ev query_done');\r
-        },\r
-        //another plugin has modified something, that requires you to update your display. \r
-        on_all_ev_field_state_changed: function (data) {\r
-            //alert('all_ev query_done');\r
-        },\r
-        /* all_ev QUERY HANDLERS End */\r
-        /* all_resources QUERY HANDLERS Start */\r
-        on_all_resources_clear_records: function (data) {\r
-            //data is empty on load\r
-        },\r
-        on_all_resources_query_in_progress: function (data) {\r
-            //data is empty on load\r
-        },\r
-        on_all_resources_new_record: function (data) {\r
-            $("#ShedulerNodes tbody").find("tr:last").after('<tr><td class="info fixed">' + data.urn + '</td></tr>');\r
-            this.drawLeases();\r
-            //console.log(data);\r
-            var tmpGroup = lookup(SchedulerResources, 'groupName', data.type);\r
-            if (tmpGroup == null) {\r
-                tmpGroup = { groupName: data.type, resources: [] };\r
-                SchedulerResources.push(tmpGroup);\r
-                //if (data.type != "node")  alert('not all node');\r
-            }\r
-            tmpGroup.resources.push(data);\r
-            //alert('new_record');\r
-        },\r
-        on_all_resources_query_done: function (data) {\r
-            //this.drawResources();\r
-            //data is empty on load\r
-            /* GUI setup and event binding */\r
-            this._initUI();\r
-            this._SetPeriodInPage(init_start_visible_index, init_end_visible_index);\r
-            this.loadWithDate();\r
-        },\r
-        //another plugin has modified something, that requires you to update your display. \r
-        on_all_resources_field_state_changed: function (data) {\r
-            //alert('all_resources query_done');\r
-        },\r
-        /* all_resources QUERY HANDLERS End */\r
-        /* lease QUERY HANDLERS Start */\r
-        on_lease_clear_records: function (data) { console.log('clear_records'); },\r
-        on_lease_query_in_progress: function (data) { console.log('lease_query_in_progress'); },\r
-        on_lease_new_record: function (data) { console.log('lease_new_record'); },\r
-        on_lease_query_done: function (data) { console.log('lease_query_done'); },\r
-        //another plugin has modified something, that requires you to update your display. \r
-        on_lease_field_state_changed: function (data) { console.log('lease_field_state_changed'); },\r
-        /* lease QUERY HANDLERS End */\r
-\r
-\r
-        // no prefix\r
-\r
-        on_filter_added: function (filter) {\r
-\r
-        },\r
-\r
-        // ... be sure to list all events here\r
-\r
-        /* RECORD HANDLERS */\r
-        on_all_new_record: function (record) {\r
-            //\r
-            alert('on_all_new_record');\r
-        },\r
-\r
-        debug : function (log_txt) {\r
-            if (typeof window.console != 'undefined') {\r
-                console.debug(log_txt);\r
-            }\r
-        },\r
-\r
-        /* INTERNAL FUNCTIONS */\r
-        _initUI: function () {\r
-            if (Debug) console.time("_initUI");\r
-            //fix margins in tables\r
-            mtNodesTbl = $("#" + rsvrTblNm + " tr:first").outerHeight() + 6;\r
-            mtSchrollCon = $("#nodes").outerWidth();\r
-            $("#nodes").css("margin-top", mtNodesTbl);\r
-            $("#reservation-table-scroll-container").css("margin-left", mtSchrollCon);\r
-            SetPerFun = this._SetPeriodInPage;\r
-            //slider\r
-            $("#time-range").slider({\r
-                range: true,\r
-                min: 0,\r
-                max: 24,\r
-                step: 0.5,\r
-                values: [init_start_visible_index, init_end_visible_index],\r
-                slide: function (event, ui) {\r
-                    SetPerFun(ui.values[0], ui.values[1]);\r
-                }\r
-            });\r
-            $("#DateToRes").datepicker({\r
-                dateFormat: "yy-mm-dd",\r
-                minDate: 0,\r
-                numberOfMonths: 3\r
-            }).change(function () {\r
-                //Scheduler2.loadWithDate();\r
-            }).click(function () {\r
-                $("#ui-datepicker-div").css("z-index", 5);\r
-            });\r
-            //other stuff\r
-            fixOddEvenClasses();\r
-            $("#" + rsvrTblNm + " td:not([class])").addClass("free");\r
-            if (Debug) console.timeEnd("_initUI");\r
-        },\r
-        _SetPeriodInPage: function (start, end) {\r
-            if (Debug) console.time("_SetPeriodInPage");\r
-            ClearTableSelection();\r
-            $("#lbltime").html(GetTimeFromInt(start) + " - " + GetTimeFromInt(end));\r
-            \r
-            var start_visible_index = (start * schdlr_PartsInOneHour) + 1;\r
-            var end_visible_index = (end * schdlr_PartsInOneHour);\r
-\r
-            //hide - show\r
-            for (i = 0; i < start_visible_index; i++) {\r
-                $("#" + rsvrTblNm + " td:nth-child(" + i + "), #" + rsvrTblNm + " th:nth-child(" + i + ")").hide();\r
-            }\r
-            for (i = end_visible_index + 1; i <= schdlr_totalColums; i++) {\r
-                $("#" + rsvrTblNm + " td:nth-child(" + i + "), #" + rsvrTblNm + " th:nth-child(" + i + ")").hide();\r
-            }\r
-            /*$("#" + rsvrTblNm + " td:not([class*='info']), #" + rsvrTblNm + " th:not([class*='fixed'])").hide();*/\r
-            for (i = start_visible_index; i <= end_visible_index; i++) {\r
-                $("#" + rsvrTblNm + " td:nth-child(" + i + "), #" + rsvrTblNm + " th:nth-child(" + i + ")").show();\r
-            }\r
-\r
-            if ($("#" + rsvrTblNm + " th:visible:first").width() > 105) {\r
-                $("#" + rsvrTblNm + " th span").css("display", "inline")\r
-            } else {\r
-                $("#" + rsvrTblNm + " th span").css("display", "block");\r
-            }\r
-            mtNodesTbl = $("#" + rsvrTblNm + " tr:first").outerHeight() + 6;\r
-            $("#nodes").css("margin-top", mtNodesTbl);\r
-            //$("#scroll_container").width($("#Search").width() - $("#nodes").width());\r
-            //$("#nodes th").height($("#tblReservation th:visible:first").height() - 2);\r
-            if (Debug) console.timeEnd("_SetPeriodInPage");\r
-        }\r
-    });\r
-\r
-    //Sched2 = new Scheduler2();\r
-\r
-    /* Plugin registration */\r
-    $.plugin('Scheduler2', Scheduler2);\r
-\r
-    // TODO Here use cases for instanciating plugins in different ways like in the pastie.\r
-\r
-\r
-})(jQuery);\r
-\r
-\r
-\r
diff --git a/plugins/scheduler2/static/js/selectRangeWorker.js b/plugins/scheduler2/static/js/selectRangeWorker.js
deleted file mode 100755 (executable)
index 5f28270..0000000
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
index d7cd16e..81efd74 100755 (executable)
@@ -6,78 +6,71 @@
     <img src="../../static/img/no-data.png" alt="no data found" style="width:100px;" />\r
     <h3>no data found...</h3>\r
 </div>\r
-<div id="plugin-{{ domid }}" class="" ng-app="myApp" style="display:none;">\r
+<div id="plugin-{{ domid }}" class="">\r
     <div class="row m-b">\r
         <div class="col-md-1">\r
             <label for="inputEmail3" class="col-sm-2 control-label">Date</label>\r
         </div>\r
         <div class="col-md-9">\r
-            <input id="DateToRes" type="text" class="form-control" placeholder="Reservation Date">\r
+            <input id="DateToRes" type="text" placeholder="Reservation Date">\r
+            <!-- <input id="DateToRes" type="text" class="form-control" placeholder="Reservation Date"> -->\r
             <span class="glyphicon glyphicon-calendar"></span>\r
-        </div>\r
-        <div class="col-md-2 text-center">\r
-            {% comment %}\r
-            <div id="TopologyModal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">\r
-                <div class="modal-dialog">\r
-                    <div class="modal-header">\r
-                        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>\r
-                        <h4 class="modal-title" id="myModalLabel">Topology</h4>\r
-                    </div>\r
-                    <div class="modal-body">\r
-                        <img src="../../static/img/nitos_topo.png" alt="nitos topology" style="width:100%;" />\r
-                    </div>\r
-                </div><!-- /.modal-dialog -->\r
-            </div><!-- /.modal TopologyModal -->\r
-            <button type="button" class="btn btn-primary btn-md" data-toggle="modal" data-target="#TopologyModal">Topology</button>\r
-            {% endcomment  %}\r
-            <button id="btnSchedulerSubmit" type="button" class="btn btn-primary btn-md">Submit Leases</button>\r
-\r
+                       <div class="sliderContainer">\r
+                               <div id="tblSlider"></div>\r
+                       </div>\r
         </div>\r
     </div>\r
-    <!--<div class="row m-b">\r
-        <div class="col-md-1">\r
-            <label for="inputEmail3" class="col-sm-1 control-label">Time</label>\r
-        </div>\r
-        <div class="col-md-9">\r
-            <div id="time-range"></div>\r
-        </div>\r
-        <div class="col-md-2">\r
-            <span id="lbltime" class="label label-primary"></span>\r
-        </div>\r
-    </div>-->\r
     <div id="SchedulerCtrl" ng-controller="SchedulerCtrl" class='query-editor-spacer'>\r
-        <div class="sliderContainer">\r
-            <div id="tblSlider"></div>\r
-        </div>\r
         <div class="table-responsive">\r
+\r
             <table id="scheduler-reservation-table" class="table table-bordered table-condensed">\r
                 <thead>\r
                     <tr>\r
-                        <th>#</th>\r
-                        <th ng-repeat="slot in slots">\r
+                        <th>Resource name</th>\r
+                        <th ng-repeat="slot in slots | offset: from | limitTo: num_visible_cells ">\r
                             {[{ slot.time }]}\r
                         </th>\r
                     </tr>\r
                 </thead>\r
                 <tbody>\r
-                    <tr ng-repeat="resource in resources" ng-class-odd="''" ng-class-even="'even'">\r
-                        <th data-resourceid="{[{ resource.id }]}" data-rowindex="{[{ $index }]}" data-resourceindex="{[{ resource.index }]}" style="word-wrap: break-word; word-break: break-all; ">{[{ resource.name }]}</th>\r
-                        <td ng-repeat="lease in resource.leases" data-slotid="{[{ lease.id }]}" data-groupid="{[{ lease.groupid }]}" ng-class="{{ 'lease.status' }}"></td>\r
+                    <tr ng-repeat="resource in resources | filter: filter_visible | offset: (current_page-1) * items_per_page | limitTo: items_per_page" \r
+                                               ng-class-odd="''" \r
+                                               ng-class-even="'even'">\r
+                        <th data-resourceid="{[{ resource.urn }]}" \r
+                                                       data-rowindex="{[{ $index }]}" \r
+                                                       data-resourceindex="{[{ resource.index }]}" \r
+                                                       style="word-wrap: break-word; word-break: break-all; ">\r
+                                                       {[{ resource.hostname }]}\r
+                                               </th>\r
+                        <td ng-repeat="lease in resource.leases | offset: from / lcm_colspan  | limitTo: num_visible_cells / lcm_colspan"\r
+                                                       data-slotid="{[{ lease.id }]}" \r
+                                                       data-groupid="{[{ lease.groupid }]}" \r
+                                                       ng-class="{{ 'lease.status' }}"\r
+                                                       colspan="{[{resource.granularity / granularity}]}"\r
+                                                       ng-click="select(from+$index, lease, $parent.resource)">\r
+                                               </td>\r
                     </tr>\r
                 </tbody>\r
             </table>\r
+\r
             <div class="row">\r
                 <div class="col-xs-5">\r
                     <div id="resources-list__table_length" class="dataTables_length">\r
-                        <label>total Pages : {[{totalPages}]}</label>\r
+                        <label>Total pages : {[{page_count()}]}</label>\r
                     </div>\r
                 </div>\r
                 <div class="col-xs-7">\r
                     <div class="dataTables_paginate paging_bootstrap">\r
                         <ul class="pagination">\r
-                            <li class="prev disabled"><a href="#">← Previous</a></li>\r
-                            <li ng-cloak ng-repeat="t in getPageNumbers() track by $index" ng-class="{active: t==curPage+1}"><a href="#" ng-click="setPage(t-1)">{[{t}]}</a></li>\r
-                            <li class="next disabled"><a href="#">Next → </a></li>\r
+                                                       <li ng-class="prevPageDisabled()">\r
+                                                         <a href ng-click="prevPage()">« Prev</a>\r
+                                                       </li>\r
+                            <li ng-repeat="n in range()" \r
+                                                           ng-class="{active: n==current_page}" \r
+                                                               ng-click="setPage(n)"><a href="#">{[{n}]}</a></li>\r
+                                                       <li ng-class="nextPageDisabled()">\r
+                                             <a href ng-click="nextPage()">Next »</a>\r
+                                           </li>\r
                         </ul>\r
                     </div>\r
                 </div>\r
index a7dd15e..c72f667 100644 (file)
@@ -2,13 +2,13 @@ from unfold.plugin import Plugin
 
 class TestbedsPlugin(Plugin):
     
-    def __init__ (self, query=None, query_all=None, query_network=None, **settings):
+    def __init__ (self, query=None, query_networks=None, **settings):
         Plugin.__init__ (self, **settings)
 
         # Until we have a proper way to access queries in Python
         self.query              = query
-        self.query_all          = query_all
-        self.query_all_uuid     = query_all.query_uuid if query_all else None
+        self.query_networks          = query_networks
+        self.query_networks_uuid     = query_networks.query_uuid if query_networks else None
 
     def template_file (self):
         return "testbeds.html"
@@ -28,7 +28,7 @@ class TestbedsPlugin(Plugin):
         # query_uuid will pass self.query results to the javascript
         # and will be available as "record" in :
         # on_new_record: function(record)
-        return ['plugin_uuid', 'domid', 'query_uuid', 'query_all_uuid']
+        return ['plugin_uuid', 'domid', 'query_uuid', 'query_networks_uuid']
 
     def export_json_settings (self):
         return True
index 5f990b9..623bc91 100644 (file)
  * License:     GPLv3
  */
 
-(function($){
+// XXX Inherit from an AngularPlugin class ?
+(function (ng, app) {
+
+    // Define our Controller constructor.
+    function Controller($scope) {
+        /* Contructor */
+
+        /* Plugin instance */
+        $scope.instance = null;
+
+        /* Models */
+        $scope.testbeds = Array();
 
+        /* Click event */
+        $scope.select = function(testbed)
+        {
+            var selected, prev_selected, num, num_selected, num_prev_selected, filter;
+
+            prev_selected = $.map($scope.testbeds, function(x, i) {
+                return x.active ? x.network_hrn : null;
+            });
+
+            testbed.active = !testbed.active;
+
+            selected = $.map($scope.testbeds, function(x, i) {
+                return x.active ? x.network_hrn : null;
+            });
+
+            num = $scope.testbeds.length;
+            prev_num_selected = prev_selected.length;
+            num_selected = selected.length;
+
+            
+            if ((prev_num_selected != 0) && (prev_num_selected != num)) {
+                // Remove previous filter
+                filter = ['network_hrn', 'included', prev_selected];
+                manifold.raise_event($scope.instance.options.query_uuid, FILTER_REMOVED, filter);
+            }
+
+            if ((num_selected != 0) && (num_selected != num)) {
+                filter = ['network_hrn', 'included', selected];
+                manifold.raise_event($scope.instance.options.query_uuid, FILTER_ADDED, filter);
+            }
+        };
+
+        /* Return object reference */
+        return (this);
+    }
+
+    // Define the Controller as the constructor function.
+    app.controller("TestbedsCtrl", Controller);
+
+})(angular, ManifoldApp);
+
+(function($){
     var TestbedsPlugin = Plugin.extend({
 
         /** XXX to check
          * @return : a jQuery collection of objects on which the plugin is
          *     applied, which allows to maintain chainability of calls
          */
-        init: function(options, element) {
-               // for debugging tools
-               this.classname="testbedsplugin";
+        init: function(options, element) 
+        {
             // Call the parent constructor, see FAQ when forgotten
             this._super(options, element);
 
             /* Member variables */
             this.filters = Array();
             this.testbeds = Array();
-            /* Plugin events */
 
-            /* Setup query and record handlers */
+            this._get_scope().instance = this;
 
-            // Explain this will allow query events to be handled
-            // What happens when we don't define some events ?
-            // Some can be less efficient
+            /* Handlers */
             this.listen_query(options.query_uuid);
-            this.listen_query(options.query_all_uuid, 'all');
-
-            /* GUI setup and event binding */
-            // call function
-
+            this.listen_query(options.query_networks_uuid, 'networks');
         },
 
-        /* PLUGIN EVENTS */
-        // on_show like in querytable
-
 
-        /* GUI EVENTS */
-
-        // a function to bind events here: click change
-        // how to raise manifold events
-
-
-        /* GUI MANIPULATION */
-
-        // We advise you to write function to change behaviour of the GUI
-        // Will use naming helpers to access content _inside_ the plugin
-        // always refer to these functions in the remaining of the code
-
-        show_hide_button: function() 
-        {
-            // this.id, this.el, this.cl, this.elts
-            // same output as a jquery selector with some guarantees
-        },
-
-        /* TEMPLATES */
-
-        // see in the html template
-        // How to load a template, use of mustache
-
-        /* QUERY HANDLERS */
-
-        // How to make sure the plugin is not desynchronized
-        // He should manifest its interest in filters, fields or records
-        // functions triggered only if the proper listen is done
-
-        // no prefix
+        /* HANDLERS */
 
         /* When a filter is added/removed, update the list of filters local to the plugin */
         on_filter_added: function(filter)
                 }else if(filter[1]=='=' || filter[1]=='=='){
                     $("#testbeds-filter_"+filter[2]).addClass("active");
                 }
+                // XXX NAMING
+                // XXX How to display unsupported filters
+                // XXX Constants for operators
             }
         },
         on_filter_removed: function(filter)
 
         // ... be sure to list all events here
 
-        /* RECORD HANDLERS */
-        on_all_new_record: function(record)
+        on_networks_query_done: function()
         {
-            var self = this;
-            // If the resource has a network_hrn
-            if(record["network_hrn"]!="None" && record["network_hrn"]!="" && record["network_hrn"]!=null){
-                // If this network_hrn is not listed yet
-                if(jQuery.inArray(record["network_hrn"],self.testbeds)==-1){
-                    row  = '<a href="#" class="list-group-item sl-platform" id="testbeds-filter_'+record["network_hrn"]+'" data-platform="'+record["network_hrn"]+'">';
-                    //row += '<span class="list-group-item-heading">'+record["platform"]+'</span>';
-                    //row += '<span class="list-group-item-heading">'+record["network_hrn"]+'</span></a>';
-                    row += '<p class="list-group-item-heading">'+record["network_hrn"]+'</p></a>';
-                    $('#testbeds-filter').append(row);
-                    self.testbeds.push(record["network_hrn"]);
-                }
-            }
+             this.set_networks();
         },
 
-        /* When the query is done, add the click event to the elements  */
-        on_all_query_done: function() {
+/*
             var self = this;
             console.log('query network DONE');
             $("[id^='testbeds-filter_']").on('click',function(e) {
                 return $(this).hasClass('active') ? self._addFilter(key, op, value) : self._removeFilter(key, op, value);
             });
            
-        },
+        },*/
 
         /* INTERNAL FUNCTIONS */
-        _dummy: function() {
-            // only convention, not strictly enforced at the moment
+
+        set_networks: function()
+        {
+            var scope = this._get_scope();
+            var query_ext = manifold.query_store.find_analyzed_query_ext(this.options.query_networks_uuid);
+            scope.testbeds = query_ext.records.values();
+            $.each(scope.testbeds, function(i, testbed) { testbed.active = true });
+            scope.$apply();
         },
+
+        _get_scope : function()
+        {
+            return angular.element('[ng-controller=TestbedsCtrl]').scope()
+        },
+
         _addFilter: function(key, op, value)
         {
-            console.log("add "+value);
-            var self = this;
             values = Array();
             // get the previous list of values for this key, ex: [ple,nitos]
             // remove the previous filter
             if(network_filter.length > 0){
                 $.each(network_filter, function(i,f){
                     values = f[2];
-                    manifold.raise_event(self.options.query_uuid, FILTER_REMOVED, [key, op, values]);
                 });
             }
             // Add the new value to list of values, ex: wilab
index fe8c2ea..289a556 100644 (file)
@@ -1,3 +1,16 @@
-<div id={{ domid }}>
+<div id={{ domid }} ng-controller="TestbedsCtrl">
+
 <div class="list-group-item sl-platform"><span class="list-group-item-heading">Testbeds</span></div>
+
+<div ng-repeat="testbed in testbeds"
+     ng-click="select(testbed)">
+       <a href="#" 
+          class="list-group-item sl-platform"
+       ng-class="{active: testbed.active}"
+          id="testbeds-filter_{[{ testbed.network_hrn }]}"
+          data-platform="{[{ testbed.network_hrn }]}">
+       <span class="list-group-item-heading">{[{ testbed.platform }]}</span>
+       <p class="list-group-item-heading">{[{ testbed.network_hrn }]}</p></a>
+</div>
+
 </div>
index f6d29db..da362d3 100644 (file)
@@ -1,27 +1,30 @@
-from django.template                 import RequestContext
-from django.shortcuts                import render_to_response
+from django.template                    import RequestContext
+from django.shortcuts                   import render_to_response
 
 from manifold.core.query             import Query, AnalyzedQuery
 from manifoldapi.manifoldapi         import execute_query
 import json
 
-from django.views.generic.base      import TemplateView
+from django.views.generic.base          import TemplateView
 
-from unfold.loginrequired           import LoginRequiredView
+from unfold.loginrequired               import LoginRequiredView
 from django.http import HttpResponse
 from django.shortcuts import render
 
-from unfold.page                     import Page
+from unfold.page                        import Page
 
-from myslice.configengine            import ConfigEngine
-from plugins.querytable              import QueryTable
-from plugins.googlemap               import GoogleMap
-from plugins.queryupdater            import QueryUpdater
-from plugins.testbeds                import TestbedsPlugin
-from plugins.scheduler2              import Scheduler2
-from plugins.columns_editor          import ColumnsEditor
-from plugins.sladialog               import SlaDialog
-from plugins.lists.simplelist        import SimpleList
+from myslice.configengine               import ConfigEngine
+
+from plugins.apply                      import ApplyPlugin
+from plugins.querytable                 import QueryTable
+from plugins.googlemap                  import GoogleMap
+#from plugins.queryupdater               import QueryUpdater
+from plugins.filter_status              import FilterStatusPlugin
+from plugins.testbeds                   import TestbedsPlugin
+from plugins.scheduler2                 import Scheduler2
+from plugins.columns_editor             import ColumnsEditor
+from plugins.sladialog                  import SlaDialog
+from plugins.lists.simplelist           import SimpleList
 
 from myslice.theme import ThemeView
 
@@ -49,8 +52,9 @@ class SliceResourceView (LoginRequiredView, ThemeView):
         # Example: select slice_hrn, resource.urn, lease.resource, lease.start_time, lease.end_time from slice where slice_hrn == "ple.upmc.myslicedemo"
         main_query = Query.get('slice').filter_by('slice_hrn', '=', slicename)
         main_query.select(
+                'slice_urn', # XXX We need the key otherwise the storage of records bugs !
                 'slice_hrn',
-                'resource.urn', 
+                'resource.urn',
                 'resource.hostname', 'resource.type',
                 'resource.network_hrn',
                 'lease.resource',
@@ -68,7 +72,7 @@ class SliceResourceView (LoginRequiredView, ThemeView):
         sq_lease       = aq.subquery('lease')
 
         query_resource_all = Query.get('resource').select(resource_fields)
-        page.enqueue_query(query_resource_all)
+        #page.enqueue_query(query_resource_all)
 
         # leases query
         lease_md = metadata.details_by_object('lease')
@@ -154,7 +158,6 @@ class SliceResourceView (LoginRequiredView, ThemeView):
             # tab's sons preferably turn this off
             togglable  = False,
             query      = sq_resource,
-            query_all  = query_resource_all,
             # this key is the one issued by google
             googlemap_api_key = ConfigEngine().googlemap_api_key(),
             # the key to use at init-time
@@ -177,24 +180,22 @@ class SliceResourceView (LoginRequiredView, ThemeView):
             # this is the query at the core of the slice list
             query = sq_resource,
             query_lease = sq_lease,
-            query_all_resources = query_resource_all,
-            query_all_leases = query_lease_all,
         )
 
         # --------------------------------------------------------------------------
         # QueryUpdater (Pending Operations)
  
-        pending_resources = QueryUpdater(
-            page                = page,
-            title               = 'Pending operations',
-            query               = main_query,
-            togglable           = False,
-            # start turned off, it will open up itself when stuff comes in
-            toggled             = False,
-            domid               = 'pending',
-            outline_complete    = True,
-            username            = request.user,
-        )
+#DEPRECATED|        pending_resources = QueryUpdater(
+#DEPRECATED|            page                = page,
+#DEPRECATED|            title               = 'Pending operations',
+#DEPRECATED|            query               = main_query,
+#DEPRECATED|            togglable           = False,
+#DEPRECATED|            # start turned off, it will open up itself when stuff comes in
+#DEPRECATED|            toggled             = False,
+#DEPRECATED|            domid               = 'pending',
+#DEPRECATED|            outline_complete    = True,
+#DEPRECATED|            username            = request.user,
+#DEPRECATED|        )
 
         # --------------------------------------------------------------------------
         # NETWORKS
@@ -203,18 +204,17 @@ class SliceResourceView (LoginRequiredView, ThemeView):
         network_md = metadata.details_by_object('network')
         network_fields = [column['name'] for column in network_md['column']]
 
-        #query_network = Query.get('network').select(network_fields)
-        #page.enqueue_query(query_network)
+        query_networks = Query.get('network').select(network_fields)
+        page.enqueue_query(query_networks)
 
         filter_testbeds = TestbedsPlugin(
-            page          = page,
-            domid         = 'testbeds-filter',
-            title         = 'Filter by testbeds',
-            query         = sq_resource,
-            query_all     = query_resource_all,
-            #query_network = query_network,
-            init_key      = "network_hrn",
-            checkboxes    = True,
+            page            = page,
+            domid           = 'testbeds-filter',
+            title           = 'Filter by testbeds',
+            query           = sq_resource,
+            query_networks  = query_networks,
+            init_key        = "network_hrn",
+            checkboxes      = True,
             datatables_options = {
                 'iDisplayLength': 25,
                 'bLengthChange' : True,
@@ -222,6 +222,18 @@ class SliceResourceView (LoginRequiredView, ThemeView):
                 },
         )
 
+        filter_status = FilterStatusPlugin(
+            page            = page,
+            domid           = "filter-status",
+            query           = sq_resource,
+        )
+        apply = ApplyPlugin(
+            page            = page,
+            domid           = "apply",
+            query           = sq_resource,
+        )
+            
+
         # --------------------------------------------------------------------------
         # SLA View and accept dialog
         
@@ -263,10 +275,13 @@ class SliceResourceView (LoginRequiredView, ThemeView):
         template_env['columns_editor'] = filter_column_editor.render(self.request)
 
         template_env['filter_testbeds'] = filter_testbeds.render(self.request)
+        template_env['filter_status'] = filter_status.render(self.request)
+        template_env['apply'] = apply.render(self.request)
+
         template_env['map_resources'] = map_resources.render(self.request)
         template_env['scheduler'] = resources_as_scheduler2.render(self.request)
-        template_env['pending_resources'] = pending_resources.render(self.request)
-        template_env['sla_dialog'] = sla_dialog.render(self.request)
+#        template_env['pending_resources'] = pending_resources.render(self.request)
+        template_env['sla_dialog'] = '' # sla_dialog.render(self.request)
         template_env["theme"] = self.theme
         template_env["username"] = request.user
         template_env["pi"] = pi
index 7cf80f9..e7f9c35 100644 (file)
@@ -15,7 +15,7 @@ from plugins.stack                   import Stack
 from plugins.tabs                    import Tabs
 from plugins.querytable              import QueryTable 
 from plugins.querygrid               import QueryGrid
-from plugins.queryupdater            import QueryUpdater
+#DEPRECATED|from plugins.queryupdater            import QueryUpdater
 from plugins.googlemap               import GoogleMap
 from plugins.senslabmap              import SensLabMap
 from plugins.scheduler               import Scheduler
@@ -146,18 +146,18 @@ class SliceView (LoginRequiredAutoLogoutView, ThemeView):
         )
     
         # --------------------------------------------------------------------------
-        # QueryUpdater (Pending Operations)
-
-        main_stack.insert(QueryUpdater(
-            page                = page,
-            title               = 'Pending operations',
-            query               = main_query,
-            togglable           = True,
-            # start turned off, it will open up itself when stuff comes in
-            toggled             = False, 
-            domid               = 'pending',
-            outline_complete    = True,
-        ))
+#DEPRECATED|        # QueryUpdater (Pending Operations)
+#DEPRECATED|
+#DEPRECATED|        main_stack.insert(QueryUpdater(
+#DEPRECATED|            page                = page,
+#DEPRECATED|            title               = 'Pending operations',
+#DEPRECATED|            query               = main_query,
+#DEPRECATED|            togglable           = True,
+#DEPRECATED|            # start turned off, it will open up itself when stuff comes in
+#DEPRECATED|            toggled             = False, 
+#DEPRECATED|            domid               = 'pending',
+#DEPRECATED|            outline_complete    = True,
+#DEPRECATED|        ))
 
         # --------------------------------------------------------------------------
         # Filter Resources
index 564e34a..de2ac74 100644 (file)
@@ -43,10 +43,12 @@ $(document).ready(function() {
                                        var el = $('*[data-key="'+myslice.pending[i]+'"]');
                                        el.addClass("active");
                                        el.find('input[type=checkbox]').prop('checked', true);
+                    /*
                                        if (myslice.count() > 0) {
                                                $('#badge-pending').text(myslice.count());
                                                $('#badge-pending').show();
                                        }
+                    */
                                }
                    }
                } );
@@ -60,18 +62,20 @@ $(document).ready(function() {
                                row.removeClass("active");
                                myslice.del(id);
                                cnt = myslice.count();
+                /*
                                $('#badge-pending').text(cnt);
                                if (cnt <= 0) {
                                        $('#badge-pending').hide();
-                               }
+                               }*/
                        } else {
                                row.addClass("active");
                                myslice.add(id);
+                /*
                                cnt = myslice.count();
                                $('#badge-pending').text(cnt);
                                if (cnt > 0) {
                                        $('#badge-pending').show();
-                               }
+                               }*/
                        }
                });
        });
index 1f997d0..7e77184 100644 (file)
@@ -27,6 +27,8 @@ $(document).ready(function() {
        
     });
     
+    // Unused "List of testbeds" tab
+    /*
     $.get("/rest/network", function(data) {
                var list = '<div class="list-group-item sl-platform"><span class="list-group-item-heading">Testbeds</span></div>';
                for(i=0; i<data.length;i++) {
@@ -36,7 +38,8 @@ $(document).ready(function() {
        }).done(function() {
                
        });
-       
+       */
+
        $('button#ApplyPendind').click(function() {
                myslice.apply();
                // $.each(myslice.pending, function(k, p) {
index 940d1c1..b42834b 100644 (file)
 {% block head %} {% endblock head %}
 {# let's add these ones no matter what #}
 {% insert_str prelude "js/jquery.min.js" %}
+{% insert_str prelude "js/angular/angular.min.js" %}
 {% insert_str prelude "js/jquery.html5storage.min.js" %}
 {% insert_str prelude "js/messages-runtime.js" %}
 {% insert_str prelude "js/class.js" %}
 {% insert_str prelude "js/plugin-helper.js" %}
 {% insert_str prelude "js/mustache.js" %}
+{% insert_str prelude "js/hashtable.js" %}
 {% insert_str prelude "js/plugin.js" %}
 {% insert_str prelude "js/manifold.js" %}
 {% insert_str prelude "css/manifold.css" %}
 {% insert_str prelude "css/plugin.css" %}
 {% insert_str prelude "js/bootstrap.js" %}
 {% insert_str prelude "css/bootstrap.css" %}
+{% insert_str prelude "js/bootstrap-datepicker.js" %}
+{% insert_str prelude "css/datepicker.css" %}
+{% insert_str prelude "js/bootstrap-slider.js" %}
+{% insert_str prelude "css/slider.css" %}
 {% insert_str prelude "css/topmenu.css" %}
 {% insert_str prelude "js/logout.js" %}
 <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/{{ theme }}.css">
@@ -76,7 +82,7 @@ $(document).ready(function() {
 });
 </script>
 </head>
-<body>
+<body ng-app="ManifoldApp">
 {% block container %}
        {% block topmenu %}
        {% widget "_widget-topmenu.html" %}
index f411ec2..7eaaf05 100644 (file)
 {% block head %} {% endblock head %}
 {# let's add these ones no matter what #}
 {% insert_str prelude "js/jquery.min.js" %}
+{% insert_str prelude "js/angular/angular.min.js" %}
 {% insert_str prelude "js/jquery.html5storage.min.js" %}
 {% insert_str prelude "js/messages-runtime.js" %}
 {% insert_str prelude "js/class.js" %}
 {% insert_str prelude "js/plugin-helper.js" %}
 {% insert_str prelude "js/mustache.js" %}
+{% insert_str prelude "js/hashtable.js" %}
 {% insert_str prelude "js/plugin.js" %}
 {% insert_str prelude "js/manifold.js" %}
 {% insert_str prelude "css/manifold.css" %}
 {% insert_str prelude "css/plugin.css" %}
 {% insert_str prelude "js/bootstrap.js" %}
 {% insert_str prelude "css/bootstrap.css" %}
+{% insert_str prelude "js/bootstrap-datepicker.js" %}
+{% insert_str prelude "css/datepicker.css" %}
+{% insert_str prelude "js/bootstrap-slider.js" %}
+{% insert_str prelude "css/slider.css" %}
 {% insert_str prelude "css/topmenu.css" %}
 {% insert_str prelude "js/logout.js" %}
 <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/{{ theme }}.css">
 </head>
-<body>
+<body ng-app="ManifoldApp">
 {% block container %}
        {% block topmenu %}
        {% include theme|add:"__widget-topmenu.html" %}
index 8513951..fdab352 100644 (file)
 {% block head %} {% endblock head %}
 {# let's add these ones no matter what #}
 {% insert_str prelude "js/jquery.min.js" %}
+{% insert_str prelude "js/angular/angular.min.js" %}
 {% insert_str prelude "js/jquery.html5storage.min.js" %}
 {% insert_str prelude "js/messages-runtime.js" %}
 {% insert_str prelude "js/class.js" %}
 {% insert_str prelude "js/plugin-helper.js" %}
 {% insert_str prelude "js/mustache.js" %}
+{% insert_str prelude "js/hashtable.js" %}
 {% insert_str prelude "js/plugin.js" %}
 {% insert_str prelude "js/manifold.js" %}
 {% insert_str prelude "css/manifold.css" %}
 {% insert_str prelude "css/plugin.css" %}
 {% insert_str prelude "js/bootstrap.js" %}
 {% insert_str prelude "css/bootstrap.css" %}
+{% insert_str prelude "js/bootstrap-datepicker.js" %}
+{% insert_str prelude "css/datepicker.css" %}
+{% insert_str prelude "js/bootstrap-slider.js" %}
+{% insert_str prelude "css/slider.css" %}
 {% insert_str prelude "css/topmenu.css" %}
 {% insert_str prelude "js/logout.js" %}
 <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/{{ theme }}.css">
 </head>
-<body>
+<body ng-app="ManifoldApp">
 {% block container %}
        {% block topmenu %}
        {% include theme|add:"__widget-topmenu.html" %}
index 062908d..4ecf483 100644 (file)
@@ -6,26 +6,27 @@
 <script src="{{ STATIC_URL }}js/onelab_slice-resource-view.js"></script>
 <script>
        //myslice.slice = "{{ slice }}";
+
+$(document).ready(function() {
+            $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+        // find the plugin object inside the tab content referenced by the current tabs
+        $('.plugin', $($(e.target).attr('href'))).trigger('shown.bs.tab');
+        $('.plugin', $($(e.target).attr('href'))).trigger('show');
+            });
+});
 </script>
 {% endblock %}
 
 {% block content %}
-       <div class="col-md-2">
-               
-               <div class="list-group-item list-resources">
-                       <span class="list-group-item-heading">View</span>
-                       <a class="list-group-item active" data-panel="resources" href="#">All</a>
-                       <a class="list-group-item" data-panel="reserved" href="#">Reserved</a>
-                       <a class="list-group-item" data-panel="pending" href="#">Pending <span class="badge" id="badge-pending" data-number="0"></span></a>
+               <div class="row">
+                       {% widget '_widget-slice-sections.html' %}
                </div>
+       <div class="col-md-2">
                
                <!-- <div id="select-platform" class="list-group"></div> -->
                {{filter_testbeds}}
        </div>
        <div class="col-md-10" style="height:100%;">
-               <div class="row">
-                       {% widget '_widget-slice-sections.html' %}
-               </div>
                <!-- <div class="row slice-pending">
                        <ul class="nav nav-pills">
                                <li><a href="">Unreserved</a></li>
                        <div class="col-md-12"><p class="alert-success">{{ msg }}</p></div>
                        {% endif %}
                </div>
+
+               {{ filter_status }}{{ apply }}
+               <!--
+               <div class="list-group-item list-resources">
+                       <span class="list-group-item-heading" style="padding-left: 0;">Resource status:</span>
+                       <a class="list-group-item active" data-panel="resources" href="#" style='display: inline-block !important;'>All</a>
+                       <a class="list-group-item" data-panel="reserved" href="#" style='display: inline-block !important;'>Reserved</a>
+                       <a class="list-group-item" data-panel="pending" href="#" style='display: inline-block !important;'>Pending <span class="badge" id="badge-pending" data-number="0"></span></a>
+               </div>
+               -->
+
                <div class="row">
-                       <ul class="nav nav-pills nav-resources">
-                         <li class="active"><a data-panel="resources" href="#">Table</a></li>
-                         <li id="GoogleMap"><a data-panel="map" href="#">Map</a></li>
-                         <li id="Scheduler"><a data-panel="scheduler-tab" href="#">Scheduler</a></li>
+                       <ul class="nav nav-tabs">
+                         <li class="active"><a href="#resourcelist" role="tab" data-toggle="tab">Table</a></li>
+                         <li> <a href="#resourcemap" role="tab" data-toggle="tab">Map</a></li>
+                         <li> <a href="#resourcescheduler" role="tab" data-toggle="tab">Scheduler</a></li>
                        </ul>
                </div>
 
                        </div>
                </div>
                
-               <div class="row" style="height:100%;">
-                       <div id="resources" class="panel">
+               <div class="tab-content" style="height:100%;">
+                       <div class="tab-pane active" id="resourcelist">
                                 <!-- Button trigger modal - columns selector -->
-                               <button class="btn btn-primary btn-sm" style="float:right;" data-toggle="modal" data-target="#myModal">Select columns</button>
+                               <button class="btn btn-primary btn-sm" style="float:right;" data-toggle="modal" data-target="#myModal">...</button>
                 {{list_resources}}
                                <!-- <table cellpadding="0" cellspacing="0" border="0" class="table" id="objectList"></table> -->
                        </div>
-                       <div id="reserved" class="panel" style="height:370px;display:none;">
+                       <div class="tab-pane" id="resourcemap">
+                {{map_resources}}
+                       </div>
+                       <div class="tab-pane" id="resourcescheduler">
+                {{scheduler}}
+                       </div>
+
+                       <!--
+                       <div id="reserved" class="tab-pane" style="height:370px;display:none;">
                 <table width="80%">
                     <tr><th width="50%" style="text-align:center;">resources</th><th width="50%" style="text-align:center;">leases</th></tr>
                     <tr>
                     </tr>
                 </table>
                        </div>
-                       <div id="pending" class="panel" style="height:370px;display:none;">
+                       <div id="pending" class="tab-pane" style="height:370px;display:none;">
                 {{pending_resources}}
                        </div>
-                       <div id="sla_dialog" class="panel" style="height:370px;display:none;">
+                       <div id="sla_dialog" class="tab-pane" style="height:370px;display:none;">
                 {{sla_dialog}}
                        </div>
-                       <div id="map" class="panel" style="height:370px;display:none;">
-                {{map_resources}}
-                       </div>
-                       <div id="scheduler-tab" class="panel" style="height:370px;display:none;">
-                {{scheduler}}
-                       </div>
+-->
+
                </div>
        </div>
 {% endblock %}
similarity index 100%
rename from portal/views.py
rename to portal/views/__init__.py
diff --git a/third-party/bootstrap-datepicker b/third-party/bootstrap-datepicker
new file mode 120000 (symlink)
index 0000000..86a0496
--- /dev/null
@@ -0,0 +1 @@
+bootstrap-datepicker-1/
\ No newline at end of file
diff --git a/third-party/bootstrap-datepicker-1/bootstrap-datepicker.js b/third-party/bootstrap-datepicker-1/bootstrap-datepicker.js
new file mode 100644 (file)
index 0000000..4766bba
--- /dev/null
@@ -0,0 +1,962 @@
+/* =========================================================
+ * bootstrap-datepicker.js
+ * http://www.eyecon.ro/bootstrap-datepicker
+ * =========================================================
+ * Copyright 2012 Stefan Petre
+ * Improvements by Andrew Rowls
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+!function( $ ) {
+
+       function UTCDate(){
+               return new Date(Date.UTC.apply(Date, arguments));
+       }
+       function UTCToday(){
+               var today = new Date();
+               return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
+       }
+
+       // Picker object
+
+       var Datepicker = function(element, options) {
+               var that = this;
+
+               this.element = $(element);
+               this.language = options.language||this.element.data('date-language')||"en";
+               this.language = this.language in dates ? this.language : "en";
+               this.isRTL = dates[this.language].rtl||false;
+               this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
+               this.isInline = false;
+               this.isInput = this.element.is('input');
+               this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
+               this.hasInput = this.component && this.element.find('input').length;
+               if(this.component && this.component.length === 0)
+                       this.component = false;
+
+               this._attachEvents();
+
+               this.forceParse = true;
+               if ('forceParse' in options) {
+                       this.forceParse = options.forceParse;
+               } else if ('dateForceParse' in this.element.data()) {
+                       this.forceParse = this.element.data('date-force-parse');
+               }
+                
+
+               this.picker = $(DPGlobal.template)
+                                                       .appendTo(this.isInline ? this.element : 'body')
+                                                       .on({
+                                                               click: $.proxy(this.click, this),
+                                                               mousedown: $.proxy(this.mousedown, this)
+                                                       });
+
+               if(this.isInline) {
+                       this.picker.addClass('datepicker-inline');
+               } else {
+                       this.picker.addClass('datepicker-dropdown dropdown-menu');
+               }
+               if (this.isRTL){
+                       this.picker.addClass('datepicker-rtl');
+                       this.picker.find('.prev i, .next i')
+                                               .toggleClass('icon-arrow-left icon-arrow-right');
+               }
+               $(document).on('mousedown', function (e) {
+                       // Clicked outside the datepicker, hide it
+                       if ($(e.target).closest('.datepicker').length === 0) {
+                               that.hide();
+                       }
+               });
+
+               this.autoclose = false;
+               if ('autoclose' in options) {
+                       this.autoclose = options.autoclose;
+               } else if ('dateAutoclose' in this.element.data()) {
+                       this.autoclose = this.element.data('date-autoclose');
+               }
+
+               this.keyboardNavigation = true;
+               if ('keyboardNavigation' in options) {
+                       this.keyboardNavigation = options.keyboardNavigation;
+               } else if ('dateKeyboardNavigation' in this.element.data()) {
+                       this.keyboardNavigation = this.element.data('date-keyboard-navigation');
+               }
+
+               this.viewMode = this.startViewMode = 0;
+               switch(options.startView || this.element.data('date-start-view')){
+                       case 2:
+                       case 'decade':
+                               this.viewMode = this.startViewMode = 2;
+                               break;
+                       case 1:
+                       case 'year':
+                               this.viewMode = this.startViewMode = 1;
+                               break;
+               }
+
+               this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
+               this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
+
+               this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
+               this.weekEnd = ((this.weekStart + 6) % 7);
+               this.startDate = -Infinity;
+               this.endDate = Infinity;
+               this.daysOfWeekDisabled = [];
+               this.setStartDate(options.startDate||this.element.data('date-startdate'));
+               this.setEndDate(options.endDate||this.element.data('date-enddate'));
+               this.setDaysOfWeekDisabled(options.daysOfWeekDisabled||this.element.data('date-days-of-week-disabled'));
+               this.fillDow();
+               this.fillMonths();
+               this.update();
+               this.showMode();
+
+               if(this.isInline) {
+                       this.show();
+               }
+       };
+
+       Datepicker.prototype = {
+               constructor: Datepicker,
+
+               _events: [],
+               _attachEvents: function(){
+                       this._detachEvents();
+                       if (this.isInput) { // single input
+                               this._events = [
+                                       [this.element, {
+                                               focus: $.proxy(this.show, this),
+                                               keyup: $.proxy(this.update, this),
+                                               keydown: $.proxy(this.keydown, this)
+                                       }]
+                               ];
+                       }
+                       else if (this.component && this.hasInput){ // component: input + button
+                               this._events = [
+                                       // For components that are not readonly, allow keyboard nav
+                                       [this.element.find('input'), {
+                                               focus: $.proxy(this.show, this),
+                                               keyup: $.proxy(this.update, this),
+                                               keydown: $.proxy(this.keydown, this)
+                                       }],
+                                       [this.component, {
+                                               click: $.proxy(this.show, this)
+                                       }]
+                               ];
+                       }
+                                               else if (this.element.is('div')) {  // inline datepicker
+                                                       this.isInline = true;
+                                               }
+                       else {
+                               this._events = [
+                                       [this.element, {
+                                               click: $.proxy(this.show, this)
+                                       }]
+                               ];
+                       }
+                       for (var i=0, el, ev; i<this._events.length; i++){
+                               el = this._events[i][0];
+                               ev = this._events[i][1];
+                               el.on(ev);
+                       }
+               },
+               _detachEvents: function(){
+                       for (var i=0, el, ev; i<this._events.length; i++){
+                               el = this._events[i][0];
+                               ev = this._events[i][1];
+                               el.off(ev);
+                       }
+                       this._events = [];
+               },
+
+               show: function(e) {
+                       this.picker.show();
+                       this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
+                       this.update();
+                       this.place();
+                       $(window).on('resize', $.proxy(this.place, this));
+                       if (e ) {
+                               e.stopPropagation();
+                               e.preventDefault();
+                       }
+                       this.element.trigger({
+                               type: 'show',
+                               date: this.date
+                       });
+               },
+
+               hide: function(e){
+                       if(this.isInline) return;
+                       this.picker.hide();
+                       $(window).off('resize', this.place);
+                       this.viewMode = this.startViewMode;
+                       this.showMode();
+                       if (!this.isInput) {
+                               $(document).off('mousedown', this.hide);
+                       }
+
+                       if (
+                               this.forceParse &&
+                               (
+                                       this.isInput && this.element.val() ||
+                                       this.hasInput && this.element.find('input').val()
+                               )
+                       )
+                               this.setValue();
+                       this.element.trigger({
+                               type: 'hide',
+                               date: this.date
+                       });
+               },
+
+               remove: function() {
+                       this._detachEvents();
+                       this.picker.remove();
+                       delete this.element.data().datepicker;
+               },
+
+               getDate: function() {
+                       var d = this.getUTCDate();
+                       return new Date(d.getTime() + (d.getTimezoneOffset()*60000));
+               },
+
+               getUTCDate: function() {
+                       return this.date;
+               },
+
+               setDate: function(d) {
+                       this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
+               },
+
+               setUTCDate: function(d) {
+                       this.date = d;
+                       this.setValue();
+               },
+
+               setValue: function() {
+                       var formatted = this.getFormattedDate();
+                       if (!this.isInput) {
+                               if (this.component){
+                                       this.element.find('input').val(formatted);
+                               }
+                               this.element.data('date', formatted);
+                       } else {
+                               this.element.val(formatted);
+                       }
+               },
+
+               getFormattedDate: function(format) {
+                       if (format === undefined)
+                               format = this.format;
+                       return DPGlobal.formatDate(this.date, format, this.language);
+               },
+
+               setStartDate: function(startDate){
+                       this.startDate = startDate||-Infinity;
+                       if (this.startDate !== -Infinity) {
+                               this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language);
+                       }
+                       this.update();
+                       this.updateNavArrows();
+               },
+
+               setEndDate: function(endDate){
+                       this.endDate = endDate||Infinity;
+                       if (this.endDate !== Infinity) {
+                               this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language);
+                       }
+                       this.update();
+                       this.updateNavArrows();
+               },
+
+               setDaysOfWeekDisabled: function(daysOfWeekDisabled){
+                       this.daysOfWeekDisabled = daysOfWeekDisabled||[];
+                       if (!$.isArray(this.daysOfWeekDisabled)) {
+                               this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
+                       }
+                       this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) {
+                               return parseInt(d, 10);
+                       });
+                       this.update();
+                       this.updateNavArrows();
+               },
+
+               place: function(){
+                                               if(this.isInline) return;
+                       var zIndex = parseInt(this.element.parents().filter(function() {
+                                                       return $(this).css('z-index') != 'auto';
+                                               }).first().css('z-index'))+10;
+                       var offset = this.component ? this.component.offset() : this.element.offset();
+                       var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true);
+                       this.picker.css({
+                               top: offset.top + height,
+                               left: offset.left,
+                               zIndex: zIndex
+                       });
+               },
+
+               update: function(){
+                       var date, fromArgs = false;
+                       if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
+                               date = arguments[0];
+                               fromArgs = true;
+                       } else {
+                               date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
+                       }
+
+                       this.date = DPGlobal.parseDate(date, this.format, this.language);
+
+                       if(fromArgs) this.setValue();
+
+                       var oldViewDate = this.viewDate;
+                       if (this.date < this.startDate) {
+                               this.viewDate = new Date(this.startDate);
+                       } else if (this.date > this.endDate) {
+                               this.viewDate = new Date(this.endDate);
+                       } else {
+                               this.viewDate = new Date(this.date);
+                       }
+
+                       if (oldViewDate && oldViewDate.getTime() != this.viewDate.getTime()){
+                               this.element.trigger({
+                                       type: 'changeDate',
+                                       date: this.viewDate
+                               });
+                       }
+                       this.fill();
+               },
+
+               fillDow: function(){
+                       var dowCnt = this.weekStart,
+                       html = '<tr>';
+                       while (dowCnt < this.weekStart + 7) {
+                               html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
+                       }
+                       html += '</tr>';
+                       this.picker.find('.datepicker-days thead').append(html);
+               },
+
+               fillMonths: function(){
+                       var html = '',
+                       i = 0;
+                       while (i < 12) {
+                               html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
+                       }
+                       this.picker.find('.datepicker-months td').html(html);
+               },
+
+               fill: function() {
+                       var d = new Date(this.viewDate),
+                               year = d.getUTCFullYear(),
+                               month = d.getUTCMonth(),
+                               startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
+                               startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
+                               endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
+                               endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
+                               currentDate = this.date && this.date.valueOf(),
+                               today = new Date();
+                       this.picker.find('.datepicker-days thead th:eq(1)')
+                                               .text(dates[this.language].months[month]+' '+year);
+                       this.picker.find('tfoot th.today')
+                                               .text(dates[this.language].today)
+                                               .toggle(this.todayBtn !== false);
+                       this.updateNavArrows();
+                       this.fillMonths();
+                       var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
+                               day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
+                       prevMonth.setUTCDate(day);
+                       prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
+                       var nextMonth = new Date(prevMonth);
+                       nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
+                       nextMonth = nextMonth.valueOf();
+                       var html = [];
+                       var clsName;
+                       while(prevMonth.valueOf() < nextMonth) {
+                               if (prevMonth.getUTCDay() == this.weekStart) {
+                                       html.push('<tr>');
+                               }
+                               clsName = '';
+                               if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
+                                       clsName += ' old';
+                               } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
+                                       clsName += ' new';
+                               }
+                               // Compare internal UTC date with local today, not UTC today
+                               if (this.todayHighlight &&
+                                       prevMonth.getUTCFullYear() == today.getFullYear() &&
+                                       prevMonth.getUTCMonth() == today.getMonth() &&
+                                       prevMonth.getUTCDate() == today.getDate()) {
+                                       clsName += ' today';
+                               }
+                               if (currentDate && prevMonth.valueOf() == currentDate) {
+                                       clsName += ' active';
+                               }
+                               if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate ||
+                                       $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1) {
+                                       clsName += ' disabled';
+                               }
+                               html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
+                               if (prevMonth.getUTCDay() == this.weekEnd) {
+                                       html.push('</tr>');
+                               }
+                               prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
+                       }
+                       this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
+                       var currentYear = this.date && this.date.getUTCFullYear();
+
+                       var months = this.picker.find('.datepicker-months')
+                                               .find('th:eq(1)')
+                                                       .text(year)
+                                                       .end()
+                                               .find('span').removeClass('active');
+                       if (currentYear && currentYear == year) {
+                               months.eq(this.date.getUTCMonth()).addClass('active');
+                       }
+                       if (year < startYear || year > endYear) {
+                               months.addClass('disabled');
+                       }
+                       if (year == startYear) {
+                               months.slice(0, startMonth).addClass('disabled');
+                       }
+                       if (year == endYear) {
+                               months.slice(endMonth+1).addClass('disabled');
+                       }
+
+                       html = '';
+                       year = parseInt(year/10, 10) * 10;
+                       var yearCont = this.picker.find('.datepicker-years')
+                                                               .find('th:eq(1)')
+                                                                       .text(year + '-' + (year + 9))
+                                                                       .end()
+                                                               .find('td');
+                       year -= 1;
+                       for (var i = -1; i < 11; i++) {
+                               html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
+                               year += 1;
+                       }
+                       yearCont.html(html);
+               },
+
+               updateNavArrows: function() {
+                       var d = new Date(this.viewDate),
+                               year = d.getUTCFullYear(),
+                               month = d.getUTCMonth();
+                       switch (this.viewMode) {
+                               case 0:
+                                       if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
+                                               this.picker.find('.prev').css({visibility: 'hidden'});
+                                       } else {
+                                               this.picker.find('.prev').css({visibility: 'visible'});
+                                       }
+                                       if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
+                                               this.picker.find('.next').css({visibility: 'hidden'});
+                                       } else {
+                                               this.picker.find('.next').css({visibility: 'visible'});
+                                       }
+                                       break;
+                               case 1:
+                               case 2:
+                                       if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
+                                               this.picker.find('.prev').css({visibility: 'hidden'});
+                                       } else {
+                                               this.picker.find('.prev').css({visibility: 'visible'});
+                                       }
+                                       if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
+                                               this.picker.find('.next').css({visibility: 'hidden'});
+                                       } else {
+                                               this.picker.find('.next').css({visibility: 'visible'});
+                                       }
+                                       break;
+                       }
+               },
+
+               click: function(e) {
+                       e.stopPropagation();
+                       e.preventDefault();
+                       var target = $(e.target).closest('span, td, th');
+                       if (target.length == 1) {
+                               switch(target[0].nodeName.toLowerCase()) {
+                                       case 'th':
+                                               switch(target[0].className) {
+                                                       case 'switch':
+                                                               this.showMode(1);
+                                                               break;
+                                                       case 'prev':
+                                                       case 'next':
+                                                               var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
+                                                               switch(this.viewMode){
+                                                                       case 0:
+                                                                               this.viewDate = this.moveMonth(this.viewDate, dir);
+                                                                               break;
+                                                                       case 1:
+                                                                       case 2:
+                                                                               this.viewDate = this.moveYear(this.viewDate, dir);
+                                                                               break;
+                                                               }
+                                                               this.fill();
+                                                               break;
+                                                       case 'today':
+                                                               var date = new Date();
+                                                               date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
+
+                                                               this.showMode(-2);
+                                                               var which = this.todayBtn == 'linked' ? null : 'view';
+                                                               this._setDate(date, which);
+                                                               break;
+                                               }
+                                               break;
+                                       case 'span':
+                                               if (!target.is('.disabled')) {
+                                                       this.viewDate.setUTCDate(1);
+                                                       if (target.is('.month')) {
+                                                               var month = target.parent().find('span').index(target);
+                                                               this.viewDate.setUTCMonth(month);
+                                                               this.element.trigger({
+                                                                       type: 'changeMonth',
+                                                                       date: this.viewDate
+                                                               });
+                                                       } else {
+                                                               var year = parseInt(target.text(), 10)||0;
+                                                               this.viewDate.setUTCFullYear(year);
+                                                               this.element.trigger({
+                                                                       type: 'changeYear',
+                                                                       date: this.viewDate
+                                                               });
+                                                       }
+                                                       this.showMode(-1);
+                                                       this.fill();
+                                               }
+                                               break;
+                                       case 'td':
+                                               if (target.is('.day') && !target.is('.disabled')){
+                                                       var day = parseInt(target.text(), 10)||1;
+                                                       var year = this.viewDate.getUTCFullYear(),
+                                                               month = this.viewDate.getUTCMonth();
+                                                       if (target.is('.old')) {
+                                                               if (month === 0) {
+                                                                       month = 11;
+                                                                       year -= 1;
+                                                               } else {
+                                                                       month -= 1;
+                                                               }
+                                                       } else if (target.is('.new')) {
+                                                               if (month == 11) {
+                                                                       month = 0;
+                                                                       year += 1;
+                                                               } else {
+                                                                       month += 1;
+                                                               }
+                                                       }
+                                                       this._setDate(UTCDate(year, month, day,0,0,0,0));
+                                               }
+                                               break;
+                               }
+                       }
+               },
+
+               _setDate: function(date, which){
+                       if (!which || which == 'date')
+                               this.date = date;
+                       if (!which || which  == 'view')
+                               this.viewDate = date;
+                       this.fill();
+                       this.setValue();
+                       this.element.trigger({
+                               type: 'changeDate',
+                               date: this.date
+                       });
+                       var element;
+                       if (this.isInput) {
+                               element = this.element;
+                       } else if (this.component){
+                               element = this.element.find('input');
+                       }
+                       if (element) {
+                               element.change();
+                               if (this.autoclose && (!which || which == 'date')) {
+                                       this.hide();
+                               }
+                       }
+               },
+
+               moveMonth: function(date, dir){
+                       if (!dir) return date;
+                       var new_date = new Date(date.valueOf()),
+                               day = new_date.getUTCDate(),
+                               month = new_date.getUTCMonth(),
+                               mag = Math.abs(dir),
+                               new_month, test;
+                       dir = dir > 0 ? 1 : -1;
+                       if (mag == 1){
+                               test = dir == -1
+                                       // If going back one month, make sure month is not current month
+                                       // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
+                                       ? function(){ return new_date.getUTCMonth() == month; }
+                                       // If going forward one month, make sure month is as expected
+                                       // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
+                                       : function(){ return new_date.getUTCMonth() != new_month; };
+                               new_month = month + dir;
+                               new_date.setUTCMonth(new_month);
+                               // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
+                               if (new_month < 0 || new_month > 11)
+                                       new_month = (new_month + 12) % 12;
+                       } else {
+                               // For magnitudes >1, move one month at a time...
+                               for (var i=0; i<mag; i++)
+                                       // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
+                                       new_date = this.moveMonth(new_date, dir);
+                               // ...then reset the day, keeping it in the new month
+                               new_month = new_date.getUTCMonth();
+                               new_date.setUTCDate(day);
+                               test = function(){ return new_month != new_date.getUTCMonth(); };
+                       }
+                       // Common date-resetting loop -- if date is beyond end of month, make it
+                       // end of month
+                       while (test()){
+                               new_date.setUTCDate(--day);
+                               new_date.setUTCMonth(new_month);
+                       }
+                       return new_date;
+               },
+
+               moveYear: function(date, dir){
+                       return this.moveMonth(date, dir*12);
+               },
+
+               dateWithinRange: function(date){
+                       return date >= this.startDate && date <= this.endDate;
+               },
+
+               keydown: function(e){
+                       if (this.picker.is(':not(:visible)')){
+                               if (e.keyCode == 27) // allow escape to hide and re-show picker
+                                       this.show();
+                               return;
+                       }
+                       var dateChanged = false,
+                               dir, day, month,
+                               newDate, newViewDate;
+                       switch(e.keyCode){
+                               case 27: // escape
+                                       this.hide();
+                                       e.preventDefault();
+                                       break;
+                               case 37: // left
+                               case 39: // right
+                                       if (!this.keyboardNavigation) break;
+                                       dir = e.keyCode == 37 ? -1 : 1;
+                                       if (e.ctrlKey){
+                                               newDate = this.moveYear(this.date, dir);
+                                               newViewDate = this.moveYear(this.viewDate, dir);
+                                       } else if (e.shiftKey){
+                                               newDate = this.moveMonth(this.date, dir);
+                                               newViewDate = this.moveMonth(this.viewDate, dir);
+                                       } else {
+                                               newDate = new Date(this.date);
+                                               newDate.setUTCDate(this.date.getUTCDate() + dir);
+                                               newViewDate = new Date(this.viewDate);
+                                               newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
+                                       }
+                                       if (this.dateWithinRange(newDate)){
+                                               this.date = newDate;
+                                               this.viewDate = newViewDate;
+                                               this.setValue();
+                                               this.update();
+                                               e.preventDefault();
+                                               dateChanged = true;
+                                       }
+                                       break;
+                               case 38: // up
+                               case 40: // down
+                                       if (!this.keyboardNavigation) break;
+                                       dir = e.keyCode == 38 ? -1 : 1;
+                                       if (e.ctrlKey){
+                                               newDate = this.moveYear(this.date, dir);
+                                               newViewDate = this.moveYear(this.viewDate, dir);
+                                       } else if (e.shiftKey){
+                                               newDate = this.moveMonth(this.date, dir);
+                                               newViewDate = this.moveMonth(this.viewDate, dir);
+                                       } else {
+                                               newDate = new Date(this.date);
+                                               newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
+                                               newViewDate = new Date(this.viewDate);
+                                               newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
+                                       }
+                                       if (this.dateWithinRange(newDate)){
+                                               this.date = newDate;
+                                               this.viewDate = newViewDate;
+                                               this.setValue();
+                                               this.update();
+                                               e.preventDefault();
+                                               dateChanged = true;
+                                       }
+                                       break;
+                               case 13: // enter
+                                       this.hide();
+                                       e.preventDefault();
+                                       break;
+                               case 9: // tab
+                                       this.hide();
+                                       break;
+                       }
+                       if (dateChanged){
+                               this.element.trigger({
+                                       type: 'changeDate',
+                                       date: this.date
+                               });
+                               var element;
+                               if (this.isInput) {
+                                       element = this.element;
+                               } else if (this.component){
+                                       element = this.element.find('input');
+                               }
+                               if (element) {
+                                       element.change();
+                               }
+                       }
+               },
+
+               showMode: function(dir) {
+                       if (dir) {
+                               this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
+                       }
+                       /*
+                               vitalets: fixing bug of very special conditions:
+                               jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
+                               Method show() does not set display css correctly and datepicker is not shown.
+                               Changed to .css('display', 'block') solve the problem.
+                               See https://github.com/vitalets/x-editable/issues/37
+
+                               In jquery 1.7.2+ everything works fine.
+                       */
+                       //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
+                       this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
+                       this.updateNavArrows();
+               }
+       };
+
+       $.fn.datepicker = function ( option ) {
+               var args = Array.apply(null, arguments);
+               args.shift();
+               return this.each(function () {
+                       var $this = $(this),
+                               data = $this.data('datepicker'),
+                               options = typeof option == 'object' && option;
+                       if (!data) {
+                               $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
+                       }
+                       if (typeof option == 'string' && typeof data[option] == 'function') {
+                               data[option].apply(data, args);
+                       }
+               });
+       };
+
+       $.fn.datepicker.defaults = {
+       };
+       $.fn.datepicker.Constructor = Datepicker;
+       var dates = $.fn.datepicker.dates = {
+               en: {
+                       days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
+                       daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
+                       daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
+                       months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+                       monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+                       today: "Today"
+               }
+       };
+
+       var DPGlobal = {
+               modes: [
+                       {
+                               clsName: 'days',
+                               navFnc: 'Month',
+                               navStep: 1
+                       },
+                       {
+                               clsName: 'months',
+                               navFnc: 'FullYear',
+                               navStep: 1
+                       },
+                       {
+                               clsName: 'years',
+                               navFnc: 'FullYear',
+                               navStep: 10
+               }],
+               isLeapYear: function (year) {
+                       return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
+               },
+               getDaysInMonth: function (year, month) {
+                       return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+               },
+               validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
+               nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
+               parseFormat: function(format){
+                       // IE treats \0 as a string end in inputs (truncating the value),
+                       // so it's a bad format delimiter, anyway
+                       var separators = format.replace(this.validParts, '\0').split('\0'),
+                               parts = format.match(this.validParts);
+                       if (!separators || !separators.length || !parts || parts.length === 0){
+                               throw new Error("Invalid date format.");
+                       }
+                       return {separators: separators, parts: parts};
+               },
+               parseDate: function(date, format, language) {
+                       if (date instanceof Date) return date;
+                       if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
+                               var part_re = /([\-+]\d+)([dmwy])/,
+                                       parts = date.match(/([\-+]\d+)([dmwy])/g),
+                                       part, dir;
+                               date = new Date();
+                               for (var i=0; i<parts.length; i++) {
+                                       part = part_re.exec(parts[i]);
+                                       dir = parseInt(part[1]);
+                                       switch(part[2]){
+                                               case 'd':
+                                                       date.setUTCDate(date.getUTCDate() + dir);
+                                                       break;
+                                               case 'm':
+                                                       date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
+                                                       break;
+                                               case 'w':
+                                                       date.setUTCDate(date.getUTCDate() + dir * 7);
+                                                       break;
+                                               case 'y':
+                                                       date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
+                                                       break;
+                                       }
+                               }
+                               return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
+                       }
+                       var parts = date && date.match(this.nonpunctuation) || [],
+                               date = new Date(),
+                               parsed = {},
+                               setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
+                               setters_map = {
+                                       yyyy: function(d,v){ return d.setUTCFullYear(v); },
+                                       yy: function(d,v){ return d.setUTCFullYear(2000+v); },
+                                       m: function(d,v){
+                                               v -= 1;
+                                               while (v<0) v += 12;
+                                               v %= 12;
+                                               d.setUTCMonth(v);
+                                               while (d.getUTCMonth() != v)
+                                                       d.setUTCDate(d.getUTCDate()-1);
+                                               return d;
+                                       },
+                                       d: function(d,v){ return d.setUTCDate(v); }
+                               },
+                               val, filtered, part;
+                       setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
+                       setters_map['dd'] = setters_map['d'];
+                       date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
+                       var fparts = format.parts.slice();
+                       // Remove noop parts
+                       if (parts.length != fparts.length) {
+                               fparts = $(fparts).filter(function(i,p){
+                                       return $.inArray(p, setters_order) !== -1;
+                               }).toArray();
+                       }
+                       // Process remainder
+                       if (parts.length == fparts.length) {
+                               for (var i=0, cnt = fparts.length; i < cnt; i++) {
+                                       val = parseInt(parts[i], 10);
+                                       part = fparts[i];
+                                       if (isNaN(val)) {
+                                               switch(part) {
+                                                       case 'MM':
+                                                               filtered = $(dates[language].months).filter(function(){
+                                                                       var m = this.slice(0, parts[i].length),
+                                                                               p = parts[i].slice(0, m.length);
+                                                                       return m == p;
+                                                               });
+                                                               val = $.inArray(filtered[0], dates[language].months) + 1;
+                                                               break;
+                                                       case 'M':
+                                                               filtered = $(dates[language].monthsShort).filter(function(){
+                                                                       var m = this.slice(0, parts[i].length),
+                                                                               p = parts[i].slice(0, m.length);
+                                                                       return m == p;
+                                                               });
+                                                               val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
+                                                               break;
+                                               }
+                                       }
+                                       parsed[part] = val;
+                               }
+                               for (var i=0, s; i<setters_order.length; i++){
+                                       s = setters_order[i];
+                                       if (s in parsed && !isNaN(parsed[s]))
+                                               setters_map[s](date, parsed[s]);
+                               }
+                       }
+                       return date;
+               },
+               formatDate: function(date, format, language){
+                       var val = {
+                               d: date.getUTCDate(),
+                               D: dates[language].daysShort[date.getUTCDay()],
+                               DD: dates[language].days[date.getUTCDay()],
+                               m: date.getUTCMonth() + 1,
+                               M: dates[language].monthsShort[date.getUTCMonth()],
+                               MM: dates[language].months[date.getUTCMonth()],
+                               yy: date.getUTCFullYear().toString().substring(2),
+                               yyyy: date.getUTCFullYear()
+                       };
+                       val.dd = (val.d < 10 ? '0' : '') + val.d;
+                       val.mm = (val.m < 10 ? '0' : '') + val.m;
+                       var date = [],
+                               seps = $.extend([], format.separators);
+                       for (var i=0, cnt = format.parts.length; i < cnt; i++) {
+                               if (seps.length)
+                                       date.push(seps.shift());
+                               date.push(val[format.parts[i]]);
+                       }
+                       return date.join('');
+               },
+               headTemplate: '<thead>'+
+                                                       '<tr>'+
+                                                               '<th class="prev"><i class="icon-arrow-left"/></th>'+
+                                                               '<th colspan="5" class="switch"></th>'+
+                                                               '<th class="next"><i class="icon-arrow-right"/></th>'+
+                                                       '</tr>'+
+                                               '</thead>',
+               contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
+               footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
+       };
+       DPGlobal.template = '<div class="datepicker">'+
+                                                       '<div class="datepicker-days">'+
+                                                               '<table class=" table-condensed">'+
+                                                                       DPGlobal.headTemplate+
+                                                                       '<tbody></tbody>'+
+                                                                       DPGlobal.footTemplate+
+                                                               '</table>'+
+                                                       '</div>'+
+                                                       '<div class="datepicker-months">'+
+                                                               '<table class="table-condensed">'+
+                                                                       DPGlobal.headTemplate+
+                                                                       DPGlobal.contTemplate+
+                                                                       DPGlobal.footTemplate+
+                                                               '</table>'+
+                                                       '</div>'+
+                                                       '<div class="datepicker-years">'+
+                                                               '<table class="table-condensed">'+
+                                                                       DPGlobal.headTemplate+
+                                                                       DPGlobal.contTemplate+
+                                                                       DPGlobal.footTemplate+
+                                                               '</table>'+
+                                                       '</div>'+
+                                               '</div>';
+
+       $.fn.datepicker.DPGlobal = DPGlobal;
+
+}( window.jQuery );
diff --git a/third-party/bootstrap-datepicker-1/datepicker.css b/third-party/bootstrap-datepicker-1/datepicker.css
new file mode 100644 (file)
index 0000000..6f061df
--- /dev/null
@@ -0,0 +1,514 @@
+/*!
+ * Datepicker for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Improvements by Andrew Rowls
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+.datepicker {
+  padding: 4px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  direction: ltr;
+  /*.dow {
+               border-top: 1px solid #ddd !important;
+       }*/
+}
+.datepicker-inline {
+  width: 220px;
+}
+.datepicker.datepicker-rtl {
+  direction: rtl;
+}
+.datepicker.datepicker-rtl table tr td span {
+  float: right;
+}
+.datepicker-dropdown {
+  top: 0;
+  left: 0;
+}
+.datepicker-dropdown:before {
+  content: '';
+  display: inline-block;
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+  border-top: 0;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  position: absolute;
+}
+.datepicker-dropdown:after {
+  content: '';
+  display: inline-block;
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid #ffffff;
+  border-top: 0;
+  position: absolute;
+}
+.datepicker-dropdown.datepicker-orient-left:before {
+  left: 6px;
+}
+.datepicker-dropdown.datepicker-orient-left:after {
+  left: 7px;
+}
+.datepicker-dropdown.datepicker-orient-right:before {
+  right: 6px;
+}
+.datepicker-dropdown.datepicker-orient-right:after {
+  right: 7px;
+}
+.datepicker-dropdown.datepicker-orient-top:before {
+  top: -7px;
+}
+.datepicker-dropdown.datepicker-orient-top:after {
+  top: -6px;
+}
+.datepicker-dropdown.datepicker-orient-bottom:before {
+  bottom: -7px;
+  border-bottom: 0;
+  border-top: 7px solid #999;
+}
+.datepicker-dropdown.datepicker-orient-bottom:after {
+  bottom: -6px;
+  border-bottom: 0;
+  border-top: 6px solid #ffffff;
+}
+.datepicker > div {
+  display: none;
+}
+.datepicker.days div.datepicker-days {
+  display: block;
+}
+.datepicker.months div.datepicker-months {
+  display: block;
+}
+.datepicker.years div.datepicker-years {
+  display: block;
+}
+.datepicker table {
+  margin: 0;
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -khtml-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.datepicker td,
+.datepicker th {
+  text-align: center;
+  width: 20px;
+  height: 20px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  border: none;
+}
+.table-striped .datepicker table tr td,
+.table-striped .datepicker table tr th {
+  background-color: transparent;
+}
+.datepicker table tr td.day:hover,
+.datepicker table tr td.day.focused {
+  background: #eeeeee;
+  cursor: pointer;
+}
+.datepicker table tr td.old,
+.datepicker table tr td.new {
+  color: #999999;
+}
+.datepicker table tr td.disabled,
+.datepicker table tr td.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: default;
+}
+.datepicker table tr td.today,
+.datepicker table tr td.today:hover,
+.datepicker table tr td.today.disabled,
+.datepicker table tr td.today.disabled:hover {
+  background-color: #fde19a;
+  background-image: -moz-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: -ms-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fdd49a), to(#fdf59a));
+  background-image: -webkit-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: -o-linear-gradient(top, #fdd49a, #fdf59a);
+  background-image: linear-gradient(top, #fdd49a, #fdf59a);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fdd49a', endColorstr='#fdf59a', GradientType=0);
+  border-color: #fdf59a #fdf59a #fbed50;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #000;
+}
+.datepicker table tr td.today:hover,
+.datepicker table tr td.today:hover:hover,
+.datepicker table tr td.today.disabled:hover,
+.datepicker table tr td.today.disabled:hover:hover,
+.datepicker table tr td.today:active,
+.datepicker table tr td.today:hover:active,
+.datepicker table tr td.today.disabled:active,
+.datepicker table tr td.today.disabled:hover:active,
+.datepicker table tr td.today.active,
+.datepicker table tr td.today:hover.active,
+.datepicker table tr td.today.disabled.active,
+.datepicker table tr td.today.disabled:hover.active,
+.datepicker table tr td.today.disabled,
+.datepicker table tr td.today:hover.disabled,
+.datepicker table tr td.today.disabled.disabled,
+.datepicker table tr td.today.disabled:hover.disabled,
+.datepicker table tr td.today[disabled],
+.datepicker table tr td.today:hover[disabled],
+.datepicker table tr td.today.disabled[disabled],
+.datepicker table tr td.today.disabled:hover[disabled] {
+  background-color: #fdf59a;
+}
+.datepicker table tr td.today:active,
+.datepicker table tr td.today:hover:active,
+.datepicker table tr td.today.disabled:active,
+.datepicker table tr td.today.disabled:hover:active,
+.datepicker table tr td.today.active,
+.datepicker table tr td.today:hover.active,
+.datepicker table tr td.today.disabled.active,
+.datepicker table tr td.today.disabled:hover.active {
+  background-color: #fbf069 \9;
+}
+.datepicker table tr td.today:hover:hover {
+  color: #000;
+}
+.datepicker table tr td.today.active:hover {
+  color: #fff;
+}
+.datepicker table tr td.range,
+.datepicker table tr td.range:hover,
+.datepicker table tr td.range.disabled,
+.datepicker table tr td.range.disabled:hover {
+  background: #eeeeee;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.datepicker table tr td.range.today,
+.datepicker table tr td.range.today:hover,
+.datepicker table tr td.range.today.disabled,
+.datepicker table tr td.range.today.disabled:hover {
+  background-color: #f3d17a;
+  background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
+  background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: -o-linear-gradient(top, #f3c17a, #f3e97a);
+  background-image: linear-gradient(top, #f3c17a, #f3e97a);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
+  border-color: #f3e97a #f3e97a #edde34;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.datepicker table tr td.range.today:hover,
+.datepicker table tr td.range.today:hover:hover,
+.datepicker table tr td.range.today.disabled:hover,
+.datepicker table tr td.range.today.disabled:hover:hover,
+.datepicker table tr td.range.today:active,
+.datepicker table tr td.range.today:hover:active,
+.datepicker table tr td.range.today.disabled:active,
+.datepicker table tr td.range.today.disabled:hover:active,
+.datepicker table tr td.range.today.active,
+.datepicker table tr td.range.today:hover.active,
+.datepicker table tr td.range.today.disabled.active,
+.datepicker table tr td.range.today.disabled:hover.active,
+.datepicker table tr td.range.today.disabled,
+.datepicker table tr td.range.today:hover.disabled,
+.datepicker table tr td.range.today.disabled.disabled,
+.datepicker table tr td.range.today.disabled:hover.disabled,
+.datepicker table tr td.range.today[disabled],
+.datepicker table tr td.range.today:hover[disabled],
+.datepicker table tr td.range.today.disabled[disabled],
+.datepicker table tr td.range.today.disabled:hover[disabled] {
+  background-color: #f3e97a;
+}
+.datepicker table tr td.range.today:active,
+.datepicker table tr td.range.today:hover:active,
+.datepicker table tr td.range.today.disabled:active,
+.datepicker table tr td.range.today.disabled:hover:active,
+.datepicker table tr td.range.today.active,
+.datepicker table tr td.range.today:hover.active,
+.datepicker table tr td.range.today.disabled.active,
+.datepicker table tr td.range.today.disabled:hover.active {
+  background-color: #efe24b \9;
+}
+.datepicker table tr td.selected,
+.datepicker table tr td.selected:hover,
+.datepicker table tr td.selected.disabled,
+.datepicker table tr td.selected.disabled:hover {
+  background-color: #9e9e9e;
+  background-image: -moz-linear-gradient(top, #b3b3b3, #808080);
+  background-image: -ms-linear-gradient(top, #b3b3b3, #808080);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
+  background-image: -webkit-linear-gradient(top, #b3b3b3, #808080);
+  background-image: -o-linear-gradient(top, #b3b3b3, #808080);
+  background-image: linear-gradient(top, #b3b3b3, #808080);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
+  border-color: #808080 #808080 #595959;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td.selected:hover,
+.datepicker table tr td.selected:hover:hover,
+.datepicker table tr td.selected.disabled:hover,
+.datepicker table tr td.selected.disabled:hover:hover,
+.datepicker table tr td.selected:active,
+.datepicker table tr td.selected:hover:active,
+.datepicker table tr td.selected.disabled:active,
+.datepicker table tr td.selected.disabled:hover:active,
+.datepicker table tr td.selected.active,
+.datepicker table tr td.selected:hover.active,
+.datepicker table tr td.selected.disabled.active,
+.datepicker table tr td.selected.disabled:hover.active,
+.datepicker table tr td.selected.disabled,
+.datepicker table tr td.selected:hover.disabled,
+.datepicker table tr td.selected.disabled.disabled,
+.datepicker table tr td.selected.disabled:hover.disabled,
+.datepicker table tr td.selected[disabled],
+.datepicker table tr td.selected:hover[disabled],
+.datepicker table tr td.selected.disabled[disabled],
+.datepicker table tr td.selected.disabled:hover[disabled] {
+  background-color: #808080;
+}
+.datepicker table tr td.selected:active,
+.datepicker table tr td.selected:hover:active,
+.datepicker table tr td.selected.disabled:active,
+.datepicker table tr td.selected.disabled:hover:active,
+.datepicker table tr td.selected.active,
+.datepicker table tr td.selected:hover.active,
+.datepicker table tr td.selected.disabled.active,
+.datepicker table tr td.selected.disabled:hover.active {
+  background-color: #666666 \9;
+}
+.datepicker table tr td.active,
+.datepicker table tr td.active:hover,
+.datepicker table tr td.active.disabled,
+.datepicker table tr td.active.disabled:hover {
+  background-color: #006dcc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+  background-image: linear-gradient(top, #0088cc, #0044cc);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+  border-color: #0044cc #0044cc #002a80;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td.active:hover,
+.datepicker table tr td.active:hover:hover,
+.datepicker table tr td.active.disabled:hover,
+.datepicker table tr td.active.disabled:hover:hover,
+.datepicker table tr td.active:active,
+.datepicker table tr td.active:hover:active,
+.datepicker table tr td.active.disabled:active,
+.datepicker table tr td.active.disabled:hover:active,
+.datepicker table tr td.active.active,
+.datepicker table tr td.active:hover.active,
+.datepicker table tr td.active.disabled.active,
+.datepicker table tr td.active.disabled:hover.active,
+.datepicker table tr td.active.disabled,
+.datepicker table tr td.active:hover.disabled,
+.datepicker table tr td.active.disabled.disabled,
+.datepicker table tr td.active.disabled:hover.disabled,
+.datepicker table tr td.active[disabled],
+.datepicker table tr td.active:hover[disabled],
+.datepicker table tr td.active.disabled[disabled],
+.datepicker table tr td.active.disabled:hover[disabled] {
+  background-color: #0044cc;
+}
+.datepicker table tr td.active:active,
+.datepicker table tr td.active:hover:active,
+.datepicker table tr td.active.disabled:active,
+.datepicker table tr td.active.disabled:hover:active,
+.datepicker table tr td.active.active,
+.datepicker table tr td.active:hover.active,
+.datepicker table tr td.active.disabled.active,
+.datepicker table tr td.active.disabled:hover.active {
+  background-color: #003399 \9;
+}
+.datepicker table tr td span {
+  display: block;
+  width: 23%;
+  height: 54px;
+  line-height: 54px;
+  float: left;
+  margin: 1%;
+  cursor: pointer;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.datepicker table tr td span:hover {
+  background: #eeeeee;
+}
+.datepicker table tr td span.disabled,
+.datepicker table tr td span.disabled:hover {
+  background: none;
+  color: #999999;
+  cursor: default;
+}
+.datepicker table tr td span.active,
+.datepicker table tr td span.active:hover,
+.datepicker table tr td span.active.disabled,
+.datepicker table tr td span.active.disabled:hover {
+  background-color: #006dcc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -ms-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+  background-image: linear-gradient(top, #0088cc, #0044cc);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);
+  border-color: #0044cc #0044cc #002a80;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+  color: #fff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.datepicker table tr td span.active:hover,
+.datepicker table tr td span.active:hover:hover,
+.datepicker table tr td span.active.disabled:hover,
+.datepicker table tr td span.active.disabled:hover:hover,
+.datepicker table tr td span.active:active,
+.datepicker table tr td span.active:hover:active,
+.datepicker table tr td span.active.disabled:active,
+.datepicker table tr td span.active.disabled:hover:active,
+.datepicker table tr td span.active.active,
+.datepicker table tr td span.active:hover.active,
+.datepicker table tr td span.active.disabled.active,
+.datepicker table tr td span.active.disabled:hover.active,
+.datepicker table tr td span.active.disabled,
+.datepicker table tr td span.active:hover.disabled,
+.datepicker table tr td span.active.disabled.disabled,
+.datepicker table tr td span.active.disabled:hover.disabled,
+.datepicker table tr td span.active[disabled],
+.datepicker table tr td span.active:hover[disabled],
+.datepicker table tr td span.active.disabled[disabled],
+.datepicker table tr td span.active.disabled:hover[disabled] {
+  background-color: #0044cc;
+}
+.datepicker table tr td span.active:active,
+.datepicker table tr td span.active:hover:active,
+.datepicker table tr td span.active.disabled:active,
+.datepicker table tr td span.active.disabled:hover:active,
+.datepicker table tr td span.active.active,
+.datepicker table tr td span.active:hover.active,
+.datepicker table tr td span.active.disabled.active,
+.datepicker table tr td span.active.disabled:hover.active {
+  background-color: #003399 \9;
+}
+.datepicker table tr td span.old,
+.datepicker table tr td span.new {
+  color: #999999;
+}
+.datepicker th.datepicker-switch {
+  width: 145px;
+}
+.datepicker thead tr:first-child th,
+.datepicker tfoot tr th {
+  cursor: pointer;
+}
+.datepicker thead tr:first-child th:hover,
+.datepicker tfoot tr th:hover {
+  background: #eeeeee;
+}
+.datepicker .cw {
+  font-size: 10px;
+  width: 12px;
+  padding: 0 2px 0 5px;
+  vertical-align: middle;
+}
+.datepicker thead tr:first-child th.cw {
+  cursor: default;
+  background-color: transparent;
+}
+.input-append.date .add-on i,
+.input-prepend.date .add-on i {
+  cursor: pointer;
+  width: 16px;
+  height: 16px;
+}
+.input-daterange input {
+  text-align: center;
+}
+.input-daterange input:first-child {
+  -webkit-border-radius: 3px 0 0 3px;
+  -moz-border-radius: 3px 0 0 3px;
+  border-radius: 3px 0 0 3px;
+}
+.input-daterange input:last-child {
+  -webkit-border-radius: 0 3px 3px 0;
+  -moz-border-radius: 0 3px 3px 0;
+  border-radius: 0 3px 3px 0;
+}
+.input-daterange .add-on {
+  display: inline-block;
+  width: auto;
+  min-width: 16px;
+  height: 20px;
+  padding: 4px 5px;
+  font-weight: normal;
+  line-height: 20px;
+  text-align: center;
+  text-shadow: 0 1px 0 #ffffff;
+  vertical-align: middle;
+  background-color: #eeeeee;
+  border: 1px solid #ccc;
+  margin-left: -5px;
+  margin-right: -5px;
+}
+.datepicker.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  float: left;
+  display: none;
+  min-width: 160px;
+  list-style: none;
+  background-color: #ffffff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -webkit-background-clip: padding-box;
+  -moz-background-clip: padding;
+  background-clip: padding-box;
+  *border-right-width: 2px;
+  *border-bottom-width: 2px;
+  color: #333333;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 13px;
+  line-height: 20px;
+}
+.datepicker.dropdown-menu th,
+.datepicker.datepicker-inline th,
+.datepicker.dropdown-menu td,
+.datepicker.datepicker-inline td {
+  padding: 4px 5px;
+}
diff --git a/third-party/bootstrap-slider b/third-party/bootstrap-slider
new file mode 120000 (symlink)
index 0000000..cb49b67
--- /dev/null
@@ -0,0 +1 @@
+bootstrap-slider-1
\ No newline at end of file
diff --git a/third-party/bootstrap-slider-1/bootstrap-slider.js b/third-party/bootstrap-slider-1/bootstrap-slider.js
new file mode 100644 (file)
index 0000000..a101dc3
--- /dev/null
@@ -0,0 +1,776 @@
+/* =========================================================
+ * bootstrap-slider.js v3.0.0
+ * =========================================================
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+(function( $ ) {
+
+       var ErrorMsgs = {
+               formatInvalidInputErrorMsg : function(input) {
+                       return "Invalid input value '" + input + "' passed in";
+               },
+               callingContextNotSliderInstance : "Calling context element does not have instance of Slider bound to it. Check your code to make sure the JQuery object returned from the call to the slider() initializer is calling the method"
+       };
+
+       var Slider = function(element, options) {
+               var el = this.element = $(element).hide();
+               var origWidth =  $(element)[0].style.width;
+
+               var updateSlider = false;
+               var parent = this.element.parent();
+
+
+               if (parent.hasClass('slider') === true) {
+                       updateSlider = true;
+                       this.picker = parent;
+               } else {
+                       this.picker = $('<div class="slider">'+
+                                                               '<div class="slider-track">'+
+                                                                       '<div class="slider-selection"></div>'+
+                                                                       '<div class="slider-handle min-slider-handle"></div>'+
+                                                                       '<div class="slider-handle max-slider-handle"></div>'+
+                                                               '</div>'+
+                                                               '<div id="tooltip" class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'+
+                                                               '<div id="tooltip_min" class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'+
+                                                               '<div id="tooltip_max" class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'+
+                                                       '</div>')
+                                                               .insertBefore(this.element)
+                                                               .append(this.element);
+               }
+
+               this.id = this.element.data('slider-id')||options.id;
+               if (this.id) {
+                       this.picker[0].id = this.id;
+               }
+
+               if (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch) {
+                       this.touchCapable = true;
+               }
+
+               var tooltip = this.element.data('slider-tooltip')||options.tooltip;
+
+               this.tooltip = this.picker.find('#tooltip');
+               this.tooltipInner = this.tooltip.find('div.tooltip-inner');
+
+               this.tooltip_min = this.picker.find('#tooltip_min');
+               this.tooltipInner_min = this.tooltip_min.find('div.tooltip-inner');
+
+               this.tooltip_max = this.picker.find('#tooltip_max');
+               this.tooltipInner_max= this.tooltip_max.find('div.tooltip-inner');
+
+               if (updateSlider === true) {
+                       // Reset classes
+                       this.picker.removeClass('slider-horizontal');
+                       this.picker.removeClass('slider-vertical');
+                       this.tooltip.removeClass('hide');
+                       this.tooltip_min.removeClass('hide');
+                       this.tooltip_max.removeClass('hide');
+
+               }
+
+               this.orientation = this.element.data('slider-orientation')||options.orientation;
+               switch(this.orientation) {
+                       case 'vertical':
+                               this.picker.addClass('slider-vertical');
+                               this.stylePos = 'top';
+                               this.mousePos = 'pageY';
+                               this.sizePos = 'offsetHeight';
+                               this.tooltip.addClass('right')[0].style.left = '100%';
+                               this.tooltip_min.addClass('right')[0].style.left = '100%';
+                               this.tooltip_max.addClass('right')[0].style.left = '100%';
+                               break;
+                       default:
+                               this.picker
+                                       .addClass('slider-horizontal')
+                                       .css('width', origWidth);
+                               this.orientation = 'horizontal';
+                               this.stylePos = 'left';
+                               this.mousePos = 'pageX';
+                               this.sizePos = 'offsetWidth';
+                               this.tooltip.addClass('top')[0].style.top = -this.tooltip.outerHeight() - 14 + 'px';
+                               this.tooltip_min.addClass('top')[0].style.top = -this.tooltip_min.outerHeight() - 14 + 'px';
+                               this.tooltip_max.addClass('top')[0].style.top = -this.tooltip_max.outerHeight() - 14 + 'px';
+                               break;
+               }
+
+               var self = this;
+               $.each(['min',
+                               'max',
+                               'step',
+                               'precision',
+                               'value',
+                               'reversed',
+                               'handle'
+                       ], function(i, attr) {
+                               if (typeof el.data('slider-' + attr) !== 'undefined') {
+                                       self[attr] = el.data('slider-' + attr);
+                               } else if (typeof options[attr] !== 'undefined') {
+                                       self[attr] = options[attr];
+                               } else if (typeof el.prop(attr) !== 'undefined') {
+                                       self[attr] = el.prop(attr);
+                               } else {
+                                       self[attr] = 0; // to prevent empty string issues in calculations in IE
+                               }
+               });
+
+               if (this.value instanceof Array) {
+                       if (updateSlider && !this.range) {
+                               this.value = this.value[0];
+                       } else {
+                               this.range = true;
+                       }
+               } else if (this.range) {
+                       // User wants a range, but value is not an array
+                       this.value = [this.value, this.max];
+               }
+
+               this.selection = this.element.data('slider-selection')||options.selection;
+               this.selectionEl = this.picker.find('.slider-selection');
+               if (this.selection === 'none') {
+                       this.selectionEl.addClass('hide');
+               }
+
+               this.selectionElStyle = this.selectionEl[0].style;
+
+               this.handle1 = this.picker.find('.slider-handle:first');
+               this.handle1Stype = this.handle1[0].style;
+
+               this.handle2 = this.picker.find('.slider-handle:last');
+               this.handle2Stype = this.handle2[0].style;
+
+               if (updateSlider === true) {
+                       // Reset classes
+                       this.handle1.removeClass('round triangle');
+                       this.handle2.removeClass('round triangle hide');
+               }
+
+               var availableHandleModifiers = ['round', 'triangle', 'custom'];
+               if (availableHandleModifiers.indexOf(this.handle) !== -1){
+                       this.handle1.addClass(this.handle);
+                       this.handle2.addClass(this.handle);
+               }
+
+               this.offset = this.picker.offset();
+               this.size = this.picker[0][this.sizePos];
+               this.formater = options.formater;
+               
+               this.tooltip_separator = options.tooltip_separator;
+               this.tooltip_split = options.tooltip_split;
+
+               this.setValue(this.value);
+
+               this.handle1.on({
+                       keydown: $.proxy(this.keydown, this, 0)
+               });
+               this.handle2.on({
+                       keydown: $.proxy(this.keydown, this, 1)
+               });
+
+               if (this.touchCapable) {
+                       // Touch: Bind touch events:
+                       this.picker.on({
+                               touchstart: $.proxy(this.mousedown, this)
+                       });
+               }
+               // Bind mouse events:
+               this.picker.on({
+                       mousedown: $.proxy(this.mousedown, this)
+               });
+
+               if(tooltip === 'hide') {
+                       this.tooltip.addClass('hide');
+                       this.tooltip_min.addClass('hide');
+                       this.tooltip_max.addClass('hide');
+               } else if(tooltip === 'always') {
+                       this.showTooltip();
+                       this.alwaysShowTooltip = true;
+               } else {
+                       this.picker.on({
+                               mouseenter: $.proxy(this.showTooltip, this),
+                               mouseleave: $.proxy(this.hideTooltip, this)
+                       });
+                       this.handle1.on({
+                               focus: $.proxy(this.showTooltip, this),
+                               blur: $.proxy(this.hideTooltip, this)
+                       });
+                       this.handle2.on({
+                               focus: $.proxy(this.showTooltip, this),
+                               blur: $.proxy(this.hideTooltip, this)
+                       });
+               }
+
+               this.enabled = options.enabled &&
+                                               (this.element.data('slider-enabled') === undefined || this.element.data('slider-enabled') === true);
+               if(this.enabled) {
+                       this.enable();
+               } else {
+                       this.disable();
+               }
+               this.natural_arrow_keys = this.element.data('slider-natural_arrow_keys') || options.natural_arrow_keys;
+       };
+
+       Slider.prototype = {
+               constructor: Slider,
+
+               over: false,
+               inDrag: false,
+
+               showTooltip: function(){
+            if (this.tooltip_split === false ){
+                this.tooltip.addClass('in');
+            } else {
+                this.tooltip_min.addClass('in');
+                this.tooltip_max.addClass('in');
+            }
+
+                       this.over = true;
+               },
+
+               hideTooltip: function(){
+                       if (this.inDrag === false && this.alwaysShowTooltip !== true) {
+                               this.tooltip.removeClass('in');
+                               this.tooltip_min.removeClass('in');
+                               this.tooltip_max.removeClass('in');
+                       }
+                       this.over = false;
+               },
+
+               layout: function(){
+                       var positionPercentages;
+
+                       if(this.reversed) {
+                               positionPercentages = [ 100 - this.percentage[0], this.percentage[1] ];
+                       } else {
+                               positionPercentages = [ this.percentage[0], this.percentage[1] ];
+                       }
+
+                       this.handle1Stype[this.stylePos] = positionPercentages[0]+'%';
+                       this.handle2Stype[this.stylePos] = positionPercentages[1]+'%';
+
+                       if (this.orientation === 'vertical') {
+                               this.selectionElStyle.top = Math.min(positionPercentages[0], positionPercentages[1]) +'%';
+                               this.selectionElStyle.height = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%';
+                       } else {
+                               this.selectionElStyle.left = Math.min(positionPercentages[0], positionPercentages[1]) +'%';
+                               this.selectionElStyle.width = Math.abs(positionPercentages[0] - positionPercentages[1]) +'%';
+
+                var offset_min = this.tooltip_min[0].getBoundingClientRect();
+                var offset_max = this.tooltip_max[0].getBoundingClientRect();
+
+                if (offset_min.right > offset_max.left) {
+                    this.tooltip_max.removeClass('top');
+                    this.tooltip_max.addClass('bottom')[0].style.top = 18 + 'px';
+                } else {
+                    this.tooltip_max.removeClass('bottom');
+                    this.tooltip_max.addClass('top')[0].style.top = -30 + 'px';
+                }
+                       }
+
+                       if (this.range) {
+                               this.tooltipInner.text(
+                                       this.formater(this.value[0]) + this.tooltip_separator + this.formater(this.value[1])
+                               );
+                               this.tooltip[0].style[this.stylePos] = (positionPercentages[1] + positionPercentages[0])/2 + '%';
+                               if (this.orientation === 'vertical') {
+                                       this.tooltip.css('margin-top', -this.tooltip.outerHeight() / 2 + 'px');
+                               } else {
+                                       this.tooltip.css('margin-left', -this.tooltip.outerWidth() / 2 + 'px');
+                               }
+                               
+                               if (this.orientation === 'vertical') {
+                                       this.tooltip.css('margin-top', -this.tooltip.outerHeight() / 2 + 'px');
+                               } else {
+                                       this.tooltip.css('margin-left', -this.tooltip.outerWidth() / 2 + 'px');
+                               }
+                               this.tooltipInner_min.text(
+                                       this.formater(this.value[0])
+                               );
+                               this.tooltipInner_max.text(
+                                       this.formater(this.value[1])
+                               );
+
+                               this.tooltip_min[0].style[this.stylePos] = positionPercentages[0] + '%';
+                               if (this.orientation === 'vertical') {
+                                       this.tooltip_min.css('margin-top', -this.tooltip_min.outerHeight() / 2 + 'px');
+                               } else {
+                                       this.tooltip_min.css('margin-left', -this.tooltip_min.outerWidth() / 2 + 'px');
+                               }
+                               this.tooltip_max[0].style[this.stylePos] = positionPercentages[1] + '%';
+                               if (this.orientation === 'vertical') {
+                                       this.tooltip_max.css('margin-top', -this.tooltip_max.outerHeight() / 2 + 'px');
+                               } else {
+                                       this.tooltip_max.css('margin-left', -this.tooltip_max.outerWidth() / 2 + 'px');
+                               }
+                       } else {
+                               this.tooltipInner.text(
+                                       this.formater(this.value[0])
+                               );
+                               this.tooltip[0].style[this.stylePos] = positionPercentages[0] + '%';
+                               if (this.orientation === 'vertical') {
+                                       this.tooltip.css('margin-top', -this.tooltip.outerHeight() / 2 + 'px');
+                               } else {
+                                       this.tooltip.css('margin-left', -this.tooltip.outerWidth() / 2 + 'px');
+                               }
+                       }
+               },
+
+               mousedown: function(ev) {
+                       if(!this.isEnabled()) {
+                               return false;
+                       }
+                       // Touch: Get the original event:
+                       if (this.touchCapable && ev.type === 'touchstart') {
+                               ev = ev.originalEvent;
+                       }
+
+                       this.triggerFocusOnHandle();
+
+                       this.offset = this.picker.offset();
+                       this.size = this.picker[0][this.sizePos];
+
+                       var percentage = this.getPercentage(ev);
+
+                       if (this.range) {
+                               var diff1 = Math.abs(this.percentage[0] - percentage);
+                               var diff2 = Math.abs(this.percentage[1] - percentage);
+                               this.dragged = (diff1 < diff2) ? 0 : 1;
+                       } else {
+                               this.dragged = 0;
+                       }
+
+                       this.percentage[this.dragged] = this.reversed ? 100 - percentage : percentage;
+                       this.layout();
+
+                       if (this.touchCapable) {
+                               // Touch: Bind touch events:
+                               $(document).on({
+                                       touchmove: $.proxy(this.mousemove, this),
+                                       touchend: $.proxy(this.mouseup, this)
+                               });
+                       }
+                       // Bind mouse events:
+                       $(document).on({
+                               mousemove: $.proxy(this.mousemove, this),
+                               mouseup: $.proxy(this.mouseup, this)
+                       });
+
+                       this.inDrag = true;
+                       var val = this.calculateValue();
+                       this.element.trigger({
+                                       type: 'slideStart',
+                                       value: val
+                               })
+                               .data('value', val)
+                               .prop('value', val);
+                       this.setValue(val);
+                       return true;
+               },
+
+               triggerFocusOnHandle: function(handleIdx) {
+                       if(handleIdx === 0) {
+                               this.handle1.focus();
+                       }
+                       if(handleIdx === 1) {
+                               this.handle2.focus();
+                       }
+               },
+
+               keydown: function(handleIdx, ev) {
+                       if(!this.isEnabled()) {
+                               return false;
+                       }
+
+                       var dir;
+                       switch (ev.which) {
+                               case 37: // left
+                               case 40: // down
+                                       dir = -1;
+                                       break;
+                               case 39: // right
+                               case 38: // up
+                                       dir = 1;
+                                       break;
+                       }
+                       if (!dir) {
+                               return;
+                       }
+
+                       // use natural arrow keys instead of from min to max
+                       if (this.natural_arrow_keys) {
+                               if ((this.orientation === 'vertical' && !this.reversed) || (this.orientation === 'horizontal' && this.reversed)) {
+                                       dir = dir * -1;
+                               }
+                       }
+
+                       var oneStepValuePercentageChange = dir * this.percentage[2];
+                       var percentage = this.percentage[handleIdx] + oneStepValuePercentageChange;
+
+                       if (percentage > 100) {
+                               percentage = 100;
+                       } else if (percentage < 0) {
+                               percentage = 0;
+                       }
+
+                       this.dragged = handleIdx;
+                       this.adjustPercentageForRangeSliders(percentage);
+                       this.percentage[this.dragged] = percentage;
+                       this.layout();
+
+                       var val = this.calculateValue();
+                       
+                       this.element.trigger({
+                                       type: 'slideStart',
+                                       value: val
+                               })
+                               .data('value', val)
+                               .prop('value', val);
+
+                       this.setValue(val, true);
+
+                       this.element
+                               .trigger({
+                                       type: 'slideStop',
+                                       value: val
+                               })
+                               .data('value', val)
+                               .prop('value', val);
+                       return false;
+               },
+
+               mousemove: function(ev) {
+                       if(!this.isEnabled()) {
+                               return false;
+                       }
+                       // Touch: Get the original event:
+                       if (this.touchCapable && ev.type === 'touchmove') {
+                               ev = ev.originalEvent;
+                       }
+
+                       var percentage = this.getPercentage(ev);
+                       this.adjustPercentageForRangeSliders(percentage);
+                       this.percentage[this.dragged] = this.reversed ? 100 - percentage : percentage;
+                       this.layout();
+
+                       var val = this.calculateValue();
+                       this.setValue(val, true);
+
+                       return false;
+               },
+               adjustPercentageForRangeSliders: function(percentage) {
+                       if (this.range) {
+                               if (this.dragged === 0 && this.percentage[1] < percentage) {
+                                       this.percentage[0] = this.percentage[1];
+                                       this.dragged = 1;
+                               } else if (this.dragged === 1 && this.percentage[0] > percentage) {
+                                       this.percentage[1] = this.percentage[0];
+                                       this.dragged = 0;
+                               }
+                       }
+               },
+
+               mouseup: function() {
+                       if(!this.isEnabled()) {
+                               return false;
+                       }
+                       if (this.touchCapable) {
+                               // Touch: Unbind touch event handlers:
+                               $(document).off({
+                                       touchmove: this.mousemove,
+                                       touchend: this.mouseup
+                               });
+                       }
+                       // Unbind mouse event handlers:
+                       $(document).off({
+                               mousemove: this.mousemove,
+                               mouseup: this.mouseup
+                       });
+
+                       this.inDrag = false;
+                       if (this.over === false) {
+                               this.hideTooltip();
+                       }
+                       var val = this.calculateValue();
+                       this.layout();
+                       this.element
+                               .data('value', val)
+                               .prop('value', val)
+                               .trigger({
+                                       type: 'slideStop',
+                                       value: val
+                               });
+                       return false;
+               },
+
+               calculateValue: function() {
+                       var val;
+                       if (this.range) {
+                               val = [this.min,this.max];
+                if (this.percentage[0] !== 0){
+                    val[0] = (Math.max(this.min, this.min + Math.round((this.diff * this.percentage[0]/100)/this.step)*this.step));
+                    val[0] = this.applyPrecision(val[0]);
+                }
+                if (this.percentage[1] !== 100){
+                    val[1] = (Math.min(this.max, this.min + Math.round((this.diff * this.percentage[1]/100)/this.step)*this.step));
+                    val[1] = this.applyPrecision(val[1]);
+                }
+                               this.value = val;
+                       } else {
+                               val = (this.min + Math.round((this.diff * this.percentage[0]/100)/this.step)*this.step);
+                               if (val < this.min) {
+                                       val = this.min;
+                               }
+                               else if (val > this.max) {
+                                       val = this.max;
+                               }
+                               val = parseFloat(val);
+                               val = this.applyPrecision(val);
+                               this.value = [val, this.value[1]];
+                       }
+                       return val;
+               },
+               applyPrecision: function(val) {
+                       var precision = this.precision || this.getNumDigitsAfterDecimalPlace(this.step);
+                       return this.applyToFixedAndParseFloat(val, precision);
+               },
+               /*
+                       Credits to Mike Samuel for the following method!
+                       Source: http://stackoverflow.com/questions/10454518/javascript-how-to-retrieve-the-number-of-decimals-of-a-string-number
+               */
+               getNumDigitsAfterDecimalPlace: function(num) {
+                       var match = (''+num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
+                       if (!match) { return 0; }
+                       return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? +match[2] : 0));
+               },
+
+               applyToFixedAndParseFloat: function(num, toFixedInput) {
+                       var truncatedNum = num.toFixed(toFixedInput);
+                       return parseFloat(truncatedNum);
+               },
+
+               getPercentage: function(ev) {
+                       if (this.touchCapable && (ev.type === 'touchstart' || ev.type === 'touchmove')) {
+                               ev = ev.touches[0];
+                       }
+                       var percentage = (ev[this.mousePos] - this.offset[this.stylePos])*100/this.size;
+                       percentage = Math.round(percentage/this.percentage[2])*this.percentage[2];
+                       return Math.max(0, Math.min(100, percentage));
+               },
+
+               getValue: function() {
+                       if (this.range) {
+                               return this.value;
+                       }
+                       return this.value[0];
+               },
+
+               setValue: function(val, triggerSlideEvent) {
+                       if (!val) {
+                               val = 0;
+                       }
+                       this.value = this.validateInputValue(val);
+
+                       if (this.range) {
+                               this.value[0] = this.applyPrecision(this.value[0]);
+                               this.value[1] = this.applyPrecision(this.value[1]); 
+
+                               this.value[0] = Math.max(this.min, Math.min(this.max, this.value[0]));
+                               this.value[1] = Math.max(this.min, Math.min(this.max, this.value[1]));
+                       } else {
+                               this.value = this.applyPrecision(this.value);
+                               this.value = [ Math.max(this.min, Math.min(this.max, this.value))];
+                               this.handle2.addClass('hide');
+                               if (this.selection === 'after') {
+                                       this.value[1] = this.max;
+                               } else {
+                                       this.value[1] = this.min;
+                               }
+                       }
+
+                       this.diff = this.max - this.min;
+                       if (this.diff > 0) {
+                               this.percentage = [
+                                       (this.value[0] - this.min) * 100 / this.diff,
+                                       (this.value[1] - this.min) * 100 / this.diff,
+                                       this.step * 100 / this.diff
+                               ];
+                       } else {
+                               this.percentage = [0, 0, 100];
+                       }
+
+                       this.layout();
+
+
+                       if(triggerSlideEvent === true) {
+                               var slideEventValue = this.range ? this.value : this.value[0];
+                               this.element
+                                       .trigger({
+                                               'type': 'slide',
+                                               'value': slideEventValue
+                                       })
+                                       .data('value', slideEventValue)
+                                       .prop('value', slideEventValue);
+                       }
+               },
+
+               validateInputValue : function(val) {
+                       if(typeof val === 'number') {
+                               return val;
+                       } else if(val instanceof Array) {
+                               $.each(val, function(i, input) { if (typeof input !== 'number') { throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(input) ); }});
+                               return val;
+                       } else {
+                               throw new Error( ErrorMsgs.formatInvalidInputErrorMsg(val) );
+                       }
+               },
+
+               destroy: function(){
+                       this.handle1.off();
+                       this.handle2.off();
+                       this.element.off().show().insertBefore(this.picker);
+                       this.picker.off().remove();
+                       $(this.element).removeData('slider');
+               },
+
+               disable: function() {
+                       this.enabled = false;
+                       this.handle1.removeAttr("tabindex");
+                       this.handle2.removeAttr("tabindex");
+                       this.picker.addClass('slider-disabled');
+                       this.element.trigger('slideDisabled');
+               },
+
+               enable: function() {
+                       this.enabled = true;
+                       this.handle1.attr("tabindex", 0);
+                       this.handle2.attr("tabindex", 0);
+                       this.picker.removeClass('slider-disabled');
+                       this.element.trigger('slideEnabled');
+               },
+
+               toggle: function() {
+                       if(this.enabled) {
+                               this.disable();
+                       } else {
+                               this.enable();
+                       }
+               },
+
+               isEnabled: function() {
+                       return this.enabled;
+               },
+
+               setAttribute: function(attribute, value) {
+                       this[attribute] = value;
+               },
+
+               getAttribute: function(attribute) {
+                       return this[attribute];
+               }
+
+       };
+
+       var publicMethods = {
+               getValue : Slider.prototype.getValue,
+               setValue : Slider.prototype.setValue,
+               setAttribute : Slider.prototype.setAttribute,
+               getAttribute : Slider.prototype.getAttribute,
+               destroy : Slider.prototype.destroy,
+               disable : Slider.prototype.disable,
+               enable : Slider.prototype.enable,
+               toggle : Slider.prototype.toggle,
+               isEnabled: Slider.prototype.isEnabled
+       };
+
+       $.fn.slider = function (option) {
+               if (typeof option === 'string' && option !== 'refresh') {
+                       var args = Array.prototype.slice.call(arguments, 1);
+                       return invokePublicMethod.call(this, option, args);
+               } else {
+                       return createNewSliderInstance.call(this, option);
+               }
+       };
+
+       function invokePublicMethod(methodName, args) {
+               if(publicMethods[methodName]) {
+                       var sliderObject = retrieveSliderObjectFromElement(this);
+                       var result = publicMethods[methodName].apply(sliderObject, args);
+
+                       if (typeof result === "undefined") {
+                               return $(this);
+                       } else {
+                               return result;
+                       }
+               } else {
+                       throw new Error("method '" + methodName + "()' does not exist for slider.");
+               }
+       }
+
+       function retrieveSliderObjectFromElement(element) {
+               var sliderObject = $(element).data('slider');
+               if(sliderObject && sliderObject instanceof Slider) {
+                       return sliderObject;
+               } else {
+                       throw new Error(ErrorMsgs.callingContextNotSliderInstance);
+               }
+       }
+
+       function createNewSliderInstance(opts) {
+               var $this = $(this);
+               $this.each(function() {
+                       var $this = $(this),
+                               slider = $this.data('slider'),
+                               options = typeof opts === 'object' && opts;
+
+                       // If slider already exists, use its attributes
+                       // as options so slider refreshes properly
+                       if (slider && !options) {
+                               options = {};
+
+                               $.each($.fn.slider.defaults, function(key) {
+                                       options[key] = slider[key];
+                               });
+                       }
+
+                       $this.data('slider', (new Slider(this, $.extend({}, $.fn.slider.defaults, options))));
+               });
+               return $this;
+       }
+
+       $.fn.slider.defaults = {
+               min: 0,
+               max: 10,
+               step: 1,
+               precision: 0,
+               orientation: 'horizontal',
+               value: 5,
+               range: false,
+               selection: 'before',
+               tooltip: 'show',
+               tooltip_separator: ':',
+               tooltip_split: false,
+               natural_arrow_keys: false,
+               handle: 'round',
+               reversed : false,
+               enabled: true,
+               formater: function(value) {
+                       return value;
+               }
+       };
+
+       $.fn.slider.Constructor = Slider;
+
+})( window.jQuery );
+
+/* vim: set noexpandtab tabstop=4 shiftwidth=4 autoindent: */
diff --git a/third-party/bootstrap-slider-1/slider.css b/third-party/bootstrap-slider-1/slider.css
new file mode 100644 (file)
index 0000000..b527aa8
--- /dev/null
@@ -0,0 +1,138 @@
+/*!
+ * Slider for Bootstrap
+ *
+ * Copyright 2012 Stefan Petre
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+.slider {
+  display: inline-block;
+  vertical-align: middle;
+  position: relative;
+}
+.slider.slider-horizontal {
+  width: 210px;
+  height: 20px;
+}
+.slider.slider-horizontal .slider-track {
+  height: 10px;
+  width: 100%;
+  margin-top: -5px;
+  top: 50%;
+  left: 0;
+}
+.slider.slider-horizontal .slider-selection {
+  height: 100%;
+  top: 0;
+  bottom: 0;
+}
+.slider.slider-horizontal .slider-handle {
+  margin-left: -10px;
+  margin-top: -5px;
+}
+.slider.slider-horizontal .slider-handle.triangle {
+  border-width: 0 10px 10px 10px;
+  width: 0;
+  height: 0;
+  border-bottom-color: #0480be;
+  margin-top: 0;
+}
+.slider.slider-vertical {
+  height: 210px;
+  width: 20px;
+}
+.slider.slider-vertical .slider-track {
+  width: 10px;
+  height: 100%;
+  margin-left: -5px;
+  left: 50%;
+  top: 0;
+}
+.slider.slider-vertical .slider-selection {
+  width: 100%;
+  left: 0;
+  top: 0;
+  bottom: 0;
+}
+.slider.slider-vertical .slider-handle {
+  margin-left: -5px;
+  margin-top: -10px;
+}
+.slider.slider-vertical .slider-handle.triangle {
+  border-width: 10px 0 10px 10px;
+  width: 1px;
+  height: 1px;
+  border-left-color: #0480be;
+  margin-left: 0;
+}
+.slider input {
+  display: none;
+}
+.slider .tooltip-inner {
+  white-space: nowrap;
+}
+.slider-track {
+  position: absolute;
+  cursor: pointer;
+  background-color: #f7f7f7;
+  background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+  background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.slider-selection {
+  position: absolute;
+  background-color: #f7f7f7;
+  background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5));
+  background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5);
+  background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5);
+  background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.slider-handle {
+  position: absolute;
+  width: 20px;
+  height: 20px;
+  background-color: #0e90d2;
+  background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+  background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+  background-image: -o-linear-gradient(top, #149bdf, #0480be);
+  background-image: linear-gradient(to bottom, #149bdf, #0480be);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  opacity: 0.8;
+  border: 0px solid transparent;
+}
+.slider-handle.round {
+  -webkit-border-radius: 20px;
+  -moz-border-radius: 20px;
+  border-radius: 20px;
+}
+.slider-handle.triangle {
+  background: transparent none;
+}
\ No newline at end of file
index 3de6090..e585414 100644 (file)
@@ -86,7 +86,7 @@ img {
 #interval_animation {
        margin: 5px;
 }
-.tooltip {
+.Atooltip {
        display: none;
        position: absolute;
        border: 1px solid #333;
index 151ab1c..2c2495c 100644 (file)
@@ -1,7 +1,7 @@
-<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.js" type="text/javascript"></script>
+<!--<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.js" type="text/javascript"></script>-->
 <!-- <script type="text/javascript">{{ STATIC_URL }}js/ui.widget.js</script> -->
-<script src="{{ STATIC_URL }}js/jquery.notify.js" type="text/javascript"></script>
-<link rel='stylesheet' href='{{ STATIC_URL }}css/ui.notify.css' type='text/css' />
+<!--<script src="{{ STATIC_URL }}js/jquery.notify.js" type="text/javascript"></script>-->
+<!--<link rel='stylesheet' href='{{ STATIC_URL }}css/ui.notify.css' type='text/css' />-->
 
 <script type="text/javascript">
 function create( template, vars, opts ){
@@ -13,7 +13,9 @@ $(function(){
        // the defaults will apply to any notification created within this
        // container, but can be overwritten on notification-by-notification
        // basis.
-       $container = $("#notifications").notify();
+
+       // XXX disabled since jquery ui conflicts with bootstrap!
+       //$container = $("#notifications").notify();
        
        // create two when the pg loads
        //create("default", { title:'Default Notification', text:'Example of a default notification.  I will fade out after 5 seconds'});
index a7c3e22..610b056 100644 (file)
@@ -1,8 +1,8 @@
 {% for json in queries_json %}manifold.insert_query({{ json|safe }});
 {% endfor %}
-$(document).ready(function () {
-var query_exec_tuples = [];
-{% for tuple in query_exec_tuples %} query_exec_tuples.push({'query_uuid':"{{ tuple.query_uuid }}"}); 
-{% endfor %}
-manifold.asynchroneous_exec(query_exec_tuples);
-})
+//$(document).ready(function () {
+//var query_exec_tuples = [];
+//{% for tuple in query_exec_tuples %} query_exec_tuples.push({'query_uuid':"{{ tuple.query_uuid }}"}); 
+//{% endfor %}
+//manifold.asynchroneous_exec(query_exec_tuples);
+//})