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