show tabs in test.js
[plstackapi.git] / planetstack / core / xoslib / static / js / xoslib / xosHelper.js
1 function assert(outcome, description) {
2     if (!outcome) {
3         console.log(description);
4     }
5 }
6
7 function templateFromId(id) {
8     return _.template($(id).html());
9 }
10
11 HTMLView = Marionette.ItemView.extend({
12   render: function() {
13       this.$el.append(this.options.html);
14   },
15 });
16
17 XOSApplication = Marionette.Application.extend({
18     detailBoxId: "#detailBox",
19     errorBoxId: "#errorBox",
20     errorCloseButtonId: "#close-error-box",
21     successBoxId: "#successBox",
22     successCloseButtonId: "#close-success-box",
23     errorTemplate: "#xos-error-template",
24     successTemplate: "#xos-success-template",
25     logMessageCount: 0,
26
27     hideError: function() {
28         if (this.logWindowId) {
29         } else {
30             $(this.errorBoxId).hide();
31             $(this.successBoxId).hide();
32         }
33     },
34
35     showSuccess: function(result) {
36          if (this.logTableId) {
37              result["success"] = "success";
38              this.appendLogWindow(result);
39          } else {
40              $(this.successBoxId).show();
41              $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
42              var that=this;
43              $(this.successCloseButtonId).unbind().bind('click', function() {
44                  $(that.successBoxId).hide();
45              });
46          }
47     },
48
49     showError: function(result) {
50          if (this.logTableId) {
51              result["success"] = "failure";
52              this.appendLogWindow(result);
53          } else {
54              $(this.errorBoxId).show();
55              $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
56              var that=this;
57              $(this.errorCloseButtonId).unbind().bind('click', function() {
58                  $(that.errorBoxId).hide();
59              });
60          }
61     },
62
63     showInformational: function(result) {
64          if (this.logTableId) {
65              result["success"] = "information";
66              return this.appendLogWindow(result);
67          } else {
68              return undefined;
69          }
70     },
71
72     appendLogWindow: function(result) {
73         // compute a new logMessageId for this log message
74         logMessageId = "logMessage" + this.logMessageCount;
75         this.logMessageCount = this.logMessageCount + 1;
76         result["logMessageId"] = logMessageId;
77
78         logMessageTemplate=$("#xos-log-template").html();
79         assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
80         newRow = _.template(logMessageTemplate, result);
81         assert(newRow != undefined, "newRow is undefined");
82
83         if (result["infoMsgId"] != undefined) {
84             // We were passed the logMessageId of an informational message,
85             // and the caller wants us to replace that message with our own.
86             // i.e. replace an informational message with a success or an error.
87             $("#"+result["infoMsgId"]).replaceWith(newRow);
88         } else {
89             // Create a brand new log message rather than replacing one.
90             logTableBody = $(this.logTableId + " tbody");
91             logTableBody.prepend(newRow);
92         }
93         return logMessageId;
94     },
95
96     hideLinkedItems: function(result) {
97         var index=0;
98         while (index<4) {\r
99             this["linkedObjs" + (index+1)].empty();\r
100             index = index + 1;\r
101         }\r
102     },\r
103 \r
104     listViewShower: function(listViewName, collection_name, regionName, title) {\r
105         var app=this;\r
106         return function() {\r
107             app[regionName].show(new app[listViewName]);\r
108             app.hideLinkedItems();\r
109             $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));\r
110             $("#detail").show();\r
111             $("#tabs").hide();\r
112         }\r
113     },\r
114 \r
115     detailShower: function(detailName, collection_name, regionName, title) {\r
116         var app=this;\r
117         showModelId = function(model_id) {\r
118             showModel = function(model) {\r
119                 detailViewClass = app[detailName];\r
120                 detailView = new detailViewClass({model: model});\r
121                 app[regionName].show(detailView);\r
122                 detailView.showLinkedItems();\r
123             }\r
124 \r
125             $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));\r
126 \r
127             collection = xos[collection_name];\r
128             model = collection.get(model_id);\r
129             if (model == undefined) {\r
130                 if (!collection.isLoaded) {\r
131                     // If the model cannot be found, then maybe it's because\r
132                     // we haven't finished loading the collection yet. So wait for\r
133                     // the sort event to complete, then try again.\r
134                     collection.once("sort", function() {\r
135                         collection = xos[collection_name];\r
136                         model = collection.get(model_id);\r
137                         if (model == undefined) {\r
138                             // We tried. It's not here. Complain to the user.\r
139                             app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));\r
140                         } else {\r
141                             showModel(model);\r
142                         }\r
143                     });\r
144                 } else {\r
145                     // The collection was loaded, the user must just be asking for something we don't have.\r
146                     app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));\r
147                 }\r
148             } else {\r
149                 showModel(model);\r
150             }\r
151         }\r
152         return showModelId;\r
153     },\r
154 });
155
156 /* XOSDetailView
157       extend with:
158          app - MarionetteApplication
159          template - template (See XOSHelper.html)
160 */
161
162 XOSDetailView = Marionette.ItemView.extend({
163             tagName: "div",
164
165             events: {"click button.js-submit": "submitClicked",
166                      "change input": "inputChanged"},
167
168             events: {"click button.js-submit": "submitClicked",
169                      "change input": "inputChanged"},
170
171             /* inputChanged is watching the onChange events of the input controls. We
172                do this to track when this view is 'dirty', so we can throw up a warning\r
173                if the user tries to change his slices without saving first.\r
174             */\r
175 \r
176             inputChanged: function(e) {\r
177                 this.dirty = true;\r
178             },\r
179 \r
180             saveError: function(model, result, xhr, infoMsgId) {\r
181                 result["what"] = "save " + model.__proto__.modelName;\r
182                 result["infoMsgId"] = infoMsgId;\r
183                 this.app.showError(result);\r
184             },\r
185 \r
186             saveSuccess: function(model, result, xhr, infoMsgId) {\r
187                 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};\r
188                 result["what"] = "save " + model.__proto__.modelName;\r
189                 result["infoMsgId"] = infoMsgId;\r
190                 this.app.showSuccess(result);\r
191             },
192
193             submitClicked: function(e) {
194                 this.app.hideError();\r
195                 e.preventDefault();\r
196                 var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );\r
197                 var data = Backbone.Syphon.serialize(this);\r
198                 var that = this;\r
199                 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},\r
200                                        success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});\r
201                 this.dirty = false;\r
202             },
203
204             tabClick: function(tabId, regionName) {
205                     region = this.app[regionName];\r
206                     if (this.currentTabRegion != undefined) {\r
207                         this.currentTabRegion.$el.hide();\r
208                     }\r
209                     if (this.currentTabId != undefined) {\r
210                         $(this.currentTabId).removeClass('active');\r
211                     }\r
212                     this.currentTabRegion = region;\r
213                     this.currentTabRegion.$el.show();\r
214 \r
215                     this.currentTabId = tabId;\r
216                     $(tabId).addClass('active');\r
217             },
218
219             showTabs: function(tabs) {
220                 template = templateFromId("#xos-tabs-template", {tabs: tabs});
221                 $("#tabs").html(template(tabs));
222                 var that = this;
223
224                 _.each(tabs, function(tab) {
225                     var regionName = tab["region"];
226                     var tabId = '#xos-nav-'+regionName;
227                     $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
228                 });
229
230                 $("#tabs").show();
231             },
232
233             showLinkedItems: function() {
234                     tabs=[];
235
236                     tabs.push({name: "details", region: "detail"});
237
238                     var index=0;
239                     for (relatedName in this.model.collection.relatedCollections) {\r
240                         relatedField = this.model.collection.relatedCollections[relatedName];\r
241                         regionName = "linkedObjs" + (index+1);\r
242 \r
243                         relatedListViewClassName = relatedName + "ListView";\r
244                         assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");\r
245                         relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});\r
246                         this.app[regionName].show(new relatedListViewClass());\r
247                         if (this.app.hideTabsByDefault) {\r
248                             this.app[regionName].$el.hide();\r
249                         }\r
250                         tabs.push({name: relatedName, region: regionName});\r
251                         index = index + 1;\r
252                     }\r
253 \r
254                     while (index<4) {\r
255                         this.app["linkedObjs" + (index+1)].empty();\r
256                         index = index + 1;\r
257                     }\r
258 \r
259                     this.showTabs(tabs);\r
260                     this.tabClick('#xos-nav-detail', 'detail');\r
261               },\r
262 \r
263 });\r
264
265 /* XOSItemView
266       This is for items that will be displayed as table rows.
267       extend with:
268          app - MarionetteApplication
269          template - template (See XOSHelper.html)
270          detailClass - class of detail view, probably an XOSDetailView
271 */
272
273 XOSItemView = Marionette.ItemView.extend({
274              tagName: 'tr',
275              className: 'test-tablerow',
276
277              events: {"click": "changeItem"},
278
279              changeItem: function(e) {\r
280                     this.app.hideError();\r
281                     e.preventDefault();\r
282                     e.stopPropagation();\r
283 \r
284                     this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);\r
285              },\r
286 });
287
288 /* XOSListView:
289       extend with:
290          app - MarionetteApplication
291          childView - class of ItemView, probably an XOSItemView
292          template - template (see xosHelper.html)
293          collection - collection that holds these objects
294          title - title to display in template
295 */
296
297 XOSListView = Marionette.CompositeView.extend({
298              childViewContainer: 'tbody',\r
299 \r
300              initialize: function() {\r
301                  this.listenTo(this.collection, 'change', this._renderChildren)
302
303                  // Because many of the templates use idToName(), we need to
304                  // listen to the collections that hold the names for the ids
305                  // that we want to display.
306                  for (i in this.collection.foreignCollections) {
307                      foreignName = this.collection.foreignCollections[i];
308                      if (xos[foreignName] == undefined) {
309                          console.log("Failed to find xos class " + foreignName);
310                      }
311                      this.listenTo(xos[foreignName], 'change', this._renderChildren);
312                      this.listenTo(xos[foreignName], 'sort', this._renderChildren);
313                  }
314              },
315
316              templateHelpers: function() {
317                 return { title: this.title };
318              },\r
319 });
320
321 /* Give an id, the name of a collection, and the name of a field for models
322    within that collection, lookup the id and return the value of the field.
323 */
324
325 idToName = function(id, collectionName, fieldName) {
326     linkedObject = xos[collectionName].get(id);
327     if (linkedObject == undefined) {
328         return "#" + id;
329     } else {
330         return linkedObject.attributes[fieldName];
331     }
332 };
333
334 /* Constructs lists of <option> html blocks for items in a collection.
335
336    selectedId = the id of an object that should be selected, if any
337    collectionName = name of collection
338    fieldName = name of field within models of collection that will be displayed
339 */
340
341 idToOptions = function(selectedId, collectionName, fieldName) {
342     result=""
343     for (index in xos[collectionName].models) {
344         linkedObject = xos[collectionName].models[index];
345         linkedId = linkedObject["id"];
346         linkedName = linkedObject.attributes[fieldName];
347         if (linkedId == selectedId) {
348             selected = " selected";
349         } else {
350             selected = "";
351         }
352         result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
353     }
354     return result;
355 };
356
357 /* Constructs an html <select> and the <option>s to go with it.
358
359    variable = variable name to return to form
360    selectedId = the id of an object that should be selected, if any
361    collectionName = name of collection
362    fieldName = name of field within models of collection that will be displayed
363 */
364
365 idToSelect = function(variable, selectedId, collectionName, fieldName) {
366     result = '<select name="' + variable + '">' +
367              idToOptions(selectedId, collectionName, fieldName) +
368              '</select>';
369     return result;
370 }
371