progress bar on startup, and make sure stuff is loaded before we display it
[plstackapi.git] / planetstack / core / xoslib / static / js / xoslib / xos-backbone.js
1 if (! window.XOSLIB_LOADED ) {
2     window.XOSLIB_LOADED=true;
3
4     SLIVER_API = "/plstackapi/slivers/";
5     SLICE_API = "/plstackapi/slices/";
6     SLICEROLE_API = "/plstackapi/slice_roles/";
7     NODE_API = "/plstackapi/nodes/";
8     SITE_API = "/plstackapi/sites/";
9     USER_API = "/plstackapi/users/";
10     USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
11     DEPLOYMENT_API = "/plstackapi/deployments/";
12     IMAGE_API = "/plstackapi/images/";
13     NETWORKTEMPLATE_API = "/plstackapi/networktemplates/";
14     NETWORK_API = "/plstackapi/networks/";
15     NETWORKSLIVER_API = "/plstackapi/networkslivers/";
16     SERVICE_API = "/plstackapi/services/";
17     SLICEPRIVILEGE_API = "/plstackapi/slice_privileges/";
18     NETWORKDEPLOYMENT_API = "/plstackapi/networkdeployments/";
19
20     /* changed as a side effect of the big rename
21     SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
22     USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
23     */
24
25     SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
26     USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
27
28     SLICEPLUS_API = "/xoslib/slicesplus/";
29
30     XOSModel = Backbone.Model.extend({
31         /* from backbone-tastypie.js */
32         //idAttribute: 'resource_uri',
33
34         /* from backbone-tastypie.js */
35         url: function() {
36                     var url = this.attributes.resource_uri;
37
38                     if (!url) {
39                         if (this.id) {
40                             url = this.urlRoot + this.id;
41                         } else {
42                             // this happens when creating a new model.
43                             url = this.urlRoot;
44                         }
45                     }
46
47                     if (!url) {
48                         // XXX I'm not sure this does anything useful
49                         url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
50                         url = url || this.urlRoot;
51                     }
52
53                     // remove any existing query parameters
54                     url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
55
56                     url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
57
58                     url && ( url += "?no_hyperlinks=1" );
59
60                     return url;
61             },
62
63             listMethods: function() {
64                 var res = [];\r
65                 for(var m in this) {\r
66                     if(typeof this[m] == "function") {\r
67                         res.push(m)\r
68                     }\r
69                 }\r
70                 return res;\r
71             }
72     });
73
74     XOSCollection = Backbone.Collection.extend({
75         objects: function() {
76                     return this.models.map(function(element) { return element.attributes; });
77                  },
78
79         initialize: function(){
80           this.isLoaded = false;
81           this.failedLoad = false;
82           this.startedLoad = false;
83           this.sortVar = 'name';\r
84           this.sortOrder = 'asc';\r
85           this.on( "sort", this.sorted );\r
86         },\r
87 \r
88         relatedCollections: [],\r
89         foreignCollections: [],\r
90 \r
91         sorted: function() {\r
92             //console.log("sorted " + this.modelName);\r
93         },\r
94 \r
95         simpleComparator: function( model ){\r
96           parts=this.sortVar.split(".");\r
97           result = model.get(parts[0]);\r
98           for (index=1; index<parts.length; ++index) {\r
99               result=result[parts[index]];\r
100           }\r
101           return result;\r
102         },\r
103 \r
104         comparator: function (left, right) {\r
105             var l = this.simpleComparator(left);\r
106             var r = this.simpleComparator(right);\r
107 \r
108             if (l === void 0) return -1;\r
109             if (r === void 0) return 1;\r
110 \r
111             if (this.sortOrder=="desc") {\r
112                 return l < r ? 1 : l > r ? -1 : 0;\r
113             } else {\r
114                 return l < r ? -1 : l > r ? 1 : 0;\r
115             }\r
116         },\r
117 \r
118         fetchSuccess: function(collection, response, options) {\r
119             this.failedLoad = false;\r
120             if (!this.isLoaded) {\r
121                 this.isLoaded = true;\r
122                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
123             }\r
124             if (options["orig_success"]) {\r
125                 options["orig_success"](collection, response, options);\r
126             }\r
127         },\r
128 \r
129         fetchFailure: function(collection, response, options) {\r
130             if ((!this.isLoaded) && (!this.failedLoad)) {\r
131                 this.failedLoad=true;\r
132                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
133             }\r
134             if (options["orig_failure"]) {\r
135                 options["orig_failure"](collection, response, options);\r
136             }\r
137         },\r
138 \r
139         fetch: function(options) {\r
140             var self=this;\r
141             if (!this.startedLoad) {\r
142                 this.startedLoad=true;\r
143                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
144             }\r
145             if (options == undefined) {\r
146                 options = {};\r
147             }\r
148             options["orig_success"] = options["success"];\r
149             options["orig_failure"] = options["failure"];\r
150             options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };\r
151             options["failure"] = this.fetchFailure;\r
152             Backbone.Collection.prototype.fetch.call(this, options);\r
153         },\r
154 \r
155         startPolling: function() {\r
156             if (!this._polling) {\r
157                 var collection=this;
158                 setInterval(function() { collection.fetch(); }, 10000);
159                 this._polling=true;
160                 this.fetch();
161             }
162         },
163
164         maybeFetch: function(options){
165                 // Helper function to fetch only if this collection has not been fetched before.
166             if(this._fetched){
167                     // If this has already been fetched, call the success, if it exists
168                 options.success && options.success();
169                 console.log("alreadyFetched");
170                 return;
171             }
172
173                 // when the original success function completes mark this collection as fetched
174             var self = this,
175             successWrapper = function(success){
176                 return function(){
177                     self._fetched = true;
178                     success && success.apply(this, arguments);
179                 };
180             };
181             options.success = successWrapper(options.success);
182             console.log("call fetch");
183             this.fetch(options);
184         },
185
186         getOrFetch: function(id, options){
187                 // Helper function to use this collection as a cache for models on the server
188             var model = this.get(id);
189
190             if(model){
191                 options.success && options.success(model);
192                 return;
193             }
194
195             model = new this.model({
196                 resource_uri: id
197             });
198
199             model.fetch(options);
200         },
201
202         filterBy: function(fieldName, value) {
203              filtered = this.filter(function(obj) {
204                  return obj.get(fieldName) == value;
205                  });
206              return new this.constructor(filtered);
207         },
208
209         /* from backbone-tastypie.js */
210         url: function( models ) {
211                     var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
212                     url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
213
214                     // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
215                     // (set to 'resource_uri') contains the model's id.
216                     if ( models && models.length ) {
217                             var ids = _.map( models, function( model ) {
218                                             var parts = _.compact( model.id.split('/') );
219                                             return parts[ parts.length - 1 ];
220                                     });
221                             url += 'set/' + ids.join(';') + '/';
222                     }
223
224                     url && ( url += "?no_hyperlinks=1" );
225
226                     return url;
227             },
228
229         listMethods: function() {
230                 var res = [];\r
231                 for(var m in this) {\r
232                     if(typeof this[m] == "function") {\r
233                         res.push(m)\r
234                     }\r
235                 }\r
236                 return res;\r
237             },
238     });
239
240     function define_model(lib, attrs) {
241         modelName = attrs.modelName;
242         modelClassName = modelName;
243         collectionClassName = modelName + "Collection";
244         collectionName = modelName + "s";
245
246         modelAttrs = {}
247         collectionAttrs = {}
248
249         for (key in attrs) {
250             value = attrs[key];
251             if ($.inArray(key, ["urlRoot", "modelName"])>=0) {
252                 modelAttrs[key] = value;
253             }
254             if ($.inArray(key, ["urlRoot", "modelName", "relatedCollections", "foreignCollections"])>=0) {
255                 collectionAttrs[key] = value;
256             }
257         }
258
259         if (xosdefaults && xosdefaults[modelName]) {
260             modelAttrs["defaults"] = xosdefaults[modelName];
261         }
262
263         lib[modelName] = XOSModel.extend(modelAttrs);
264
265         collectionAttrs["model"] = lib[modelName];
266
267         lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
268         lib[collectionName] = new lib[collectionClassName]();
269
270         lib.allCollectionNames.push(collectionName);
271         lib.allCollections.push(lib[collectionName]);
272     };
273
274     function xoslib() {
275         this.allCollectionNames = [];
276         this.allCollections = [];
277
278         define_model(this, {urlRoot: SLIVER_API,
279                             relatedCollections: {"networkSlivers": "sliver"},
280                             foreignCollections: ["slices", "deployments", "images", "nodes", "users"],
281                             modelName: "sliver"});
282
283         define_model(this, {urlRoot: SLICE_API,
284                            relatedCollections: {"slivers": "slice", "sliceDeployments": "slice", "slicePrivileges": "slice", "networks": "owner"},
285                            foreignCollections: ["services", "sites"],
286                            modelName: "slice"});
287
288         define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
289                            foreignCollections: ["slices", "deployments"],
290                            modelName: "sliceDeployment"});
291
292         define_model(this, {urlRoot: SLICEPRIVILEGE_API,
293                             foreignCollections: ["slices", "users", "sliceRoles"],
294                             modelName: "slicePrivilege"});
295
296         define_model(this, {urlRoot: SLICEROLE_API,
297                             modelName: "sliceRole"});
298
299         define_model(this, {urlRoot: NODE_API,
300                             foreignCollections: ["sites", "deployments"],
301                             modelName: "node"});
302
303         define_model(this, {urlRoot: SITE_API,
304                             relatedCollections: {"users": "site", "slices": "site", "nodes": "site"},
305                             modelName: "site"});
306
307         define_model(this, {urlRoot: USER_API,
308                             relatedCollections: {"slicePrivileges": "user", "slices": "owner", "userDeployments": "user"},
309                             foreignCollections: ["sites"],
310                             modelName: "user"});
311
312         define_model(this, {urlRoot: USERDEPLOYMENT_API,
313                             foreignCollections: ["users","deployments"],
314                             modelName: "userDeployment"});
315
316         define_model(this, { urlRoot: DEPLOYMENT_API,
317                              relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork", "networkDeployments": "deployment", "userDeployments": "deployment"},
318                              modelName: "deployment"});
319
320         define_model(this, {urlRoot: IMAGE_API,
321                             model: this.image,
322                             modelName: "image"});
323
324         define_model(this, {urlRoot: NETWORKTEMPLATE_API,
325                             modelName: "networkTemplate"});
326
327         define_model(this, {urlRoot: NETWORK_API,
328                             relatedCollections: {"networkDeployments": "network", "networkSlivers": "network"},
329                             foreignCollections: ["slices", "networkTemplates"],
330                             modelName: "network"});
331
332         define_model(this, {urlRoot: NETWORKSLIVER_API,
333                             modelName: "networkSliver"});
334
335         define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
336                             modelName: "networkDeployment"});
337
338         define_model(this, {urlRoot: SERVICE_API,
339                             modelName: "service"});
340
341         // enhanced REST
342         define_model(this, {urlRoot: SLICEPLUS_API,
343                             relatedCollections: {'slivers': "slice"},
344                             modelName: "slicePlus"});
345
346         this.listObjects = function() { return this.allCollectionNames; };
347
348         this.getCollectionStatus = function() {
349             stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
350             for (index in this.allCollections) {
351                 collection = this.allCollections[index];
352                 if (collection.isLoaded) {
353                     stats["isLoaded"] = stats["isLoaded"] + 1;
354                 }
355                 if (collection.failedLoad) {
356                     stats["failedLoad"] = stats["failedLoad"] + 1;
357                 }
358                 if (collection.startedLoad) {
359                     stats["startedLoad"] = stats["startedLoad"] + 1;
360                 }
361             }
362             stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
363             return stats;
364         };
365     };
366
367     xos = new xoslib();
368
369     function getCookie(name) {
370         var cookieValue = null;\r
371         if (document.cookie && document.cookie != '') {\r
372             var cookies = document.cookie.split(';');\r
373             for (var i = 0; i < cookies.length; i++) {\r
374                 var cookie = jQuery.trim(cookies[i]);\r
375                 // Does this cookie string begin with the name we want?\r
376                 if (cookie.substring(0, name.length + 1) == (name + '=')) {\r
377                     cookieValue = decodeURIComponent(cookie.substring(name.length + 1));\r
378                     break;\r
379                 }\r
380             }\r
381         }\r
382         return cookieValue;\r
383     }
384
385     (function() {
386       var _sync = Backbone.sync;\r
387       Backbone.sync = function(method, model, options){\r
388         options.beforeSend = function(xhr){\r
389           var token = getCookie("csrftoken");\r
390           xhr.setRequestHeader('X-CSRFToken', token);\r
391         };\r
392         return _sync(method, model, options);\r
393       };\r
394     })();
395 }