blob: 89d0d3be11c3eead2df18ebcb2e6a3677f6f22cf [file] [log] [blame]
Scott Baker0bf96b22014-11-04 15:41:47 -08001function assert(outcome, description) {
2 if (!outcome) {
3 console.log(description);
4 }
5}
6
Scott Baker9d37d562014-11-04 23:20:48 -08007function templateFromId(id) {
8 return _.template($(id).html());
9}
10
Scott Baker7ce23652014-11-07 16:40:30 -080011function firstCharUpper(s) {
12 return s.charAt(0).toUpperCase() + s.slice(1);
13}
14
Scott Baker0bf96b22014-11-04 15:41:47 -080015HTMLView = Marionette.ItemView.extend({
16 render: function() {
17 this.$el.append(this.options.html);
18 },
19});
20
Scott Bakerfdaee922014-11-03 09:43:23 -080021XOSApplication = Marionette.Application.extend({
Scott Bakerc16b4c12014-11-03 23:54:24 -080022 detailBoxId: "#detailBox",
Scott Bakerfdaee922014-11-03 09:43:23 -080023 errorBoxId: "#errorBox",
24 errorCloseButtonId: "#close-error-box",
25 successBoxId: "#successBox",
26 successCloseButtonId: "#close-success-box",
27 errorTemplate: "#xos-error-template",
28 successTemplate: "#xos-success-template",
Scott Baker0bf96b22014-11-04 15:41:47 -080029 logMessageCount: 0,
Scott Bakerfdaee922014-11-03 09:43:23 -080030
Scott Baker0bf96b22014-11-04 15:41:47 -080031 hideError: function() {
32 if (this.logWindowId) {
33 } else {
34 $(this.errorBoxId).hide();
35 $(this.successBoxId).hide();
36 }
Scott Bakerfdaee922014-11-03 09:43:23 -080037 },
38
39 showSuccess: function(result) {
Scott Baker01c9d612014-11-14 16:13:43 -080040 result["statusclass"] = "success";
Scott Baker0bf96b22014-11-04 15:41:47 -080041 if (this.logTableId) {
Scott Baker0bf96b22014-11-04 15:41:47 -080042 this.appendLogWindow(result);
43 } else {
44 $(this.successBoxId).show();
45 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
Scott Baker9d37d562014-11-04 23:20:48 -080046 var that=this;
Scott Baker0bf96b22014-11-04 15:41:47 -080047 $(this.successCloseButtonId).unbind().bind('click', function() {
Scott Baker9d37d562014-11-04 23:20:48 -080048 $(that.successBoxId).hide();
Scott Baker0bf96b22014-11-04 15:41:47 -080049 });
50 }
Scott Bakerfdaee922014-11-03 09:43:23 -080051 },
52
53 showError: function(result) {
Scott Baker01c9d612014-11-14 16:13:43 -080054 result["statusclass"] = "failure";
Scott Baker0bf96b22014-11-04 15:41:47 -080055 if (this.logTableId) {
Scott Baker0bf96b22014-11-04 15:41:47 -080056 this.appendLogWindow(result);
57 } else {
58 $(this.errorBoxId).show();
59 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
Scott Baker9d37d562014-11-04 23:20:48 -080060 var that=this;
Scott Baker0bf96b22014-11-04 15:41:47 -080061 $(this.errorCloseButtonId).unbind().bind('click', function() {
Scott Baker9d37d562014-11-04 23:20:48 -080062 $(that.errorBoxId).hide();
Scott Baker0bf96b22014-11-04 15:41:47 -080063 });
64 }
Scott Bakerfdaee922014-11-03 09:43:23 -080065 },
Scott Baker0bf96b22014-11-04 15:41:47 -080066
67 showInformational: function(result) {
Scott Baker01c9d612014-11-14 16:13:43 -080068 result["statusclass"] = "inprog";
Scott Baker0bf96b22014-11-04 15:41:47 -080069 if (this.logTableId) {
Scott Baker0bf96b22014-11-04 15:41:47 -080070 return this.appendLogWindow(result);
71 } else {
72 return undefined;
73 }
74 },
75
76 appendLogWindow: function(result) {
77 // compute a new logMessageId for this log message
78 logMessageId = "logMessage" + this.logMessageCount;
79 this.logMessageCount = this.logMessageCount + 1;
80 result["logMessageId"] = logMessageId;
81
82 logMessageTemplate=$("#xos-log-template").html();
83 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
84 newRow = _.template(logMessageTemplate, result);
85 assert(newRow != undefined, "newRow is undefined");
86
87 if (result["infoMsgId"] != undefined) {
88 // We were passed the logMessageId of an informational message,
89 // and the caller wants us to replace that message with our own.
90 // i.e. replace an informational message with a success or an error.
Scott Baker0bf96b22014-11-04 15:41:47 -080091 $("#"+result["infoMsgId"]).replaceWith(newRow);
92 } else {
93 // Create a brand new log message rather than replacing one.
94 logTableBody = $(this.logTableId + " tbody");
95 logTableBody.prepend(newRow);
96 }
Scott Bakerb38a8322014-11-05 22:13:36 -080097
98 if (this.statusMsgId) {
99 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
100 }
101
Scott Baker0bf96b22014-11-04 15:41:47 -0800102 return logMessageId;
103 },
104
105 hideLinkedItems: function(result) {
Scott Baker9d37d562014-11-04 23:20:48 -0800106 var index=0;
Scott Baker0bf96b22014-11-04 15:41:47 -0800107 while (index<4) {
108 this["linkedObjs" + (index+1)].empty();
109 index = index + 1;
110 }
111 },
112
Scott Baker9d37d562014-11-04 23:20:48 -0800113 listViewShower: function(listViewName, collection_name, regionName, title) {
Scott Baker0bf96b22014-11-04 15:41:47 -0800114 var app=this;
115 return function() {
116 app[regionName].show(new app[listViewName]);
117 app.hideLinkedItems();
Scott Baker9d37d562014-11-04 23:20:48 -0800118 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
119 $("#detail").show();
Scott Bakere49f08c2014-11-07 13:01:43 -0800120 $("#xos-listview-button-box").show();
Scott Baker9d37d562014-11-04 23:20:48 -0800121 $("#tabs").hide();
Scott Bakere49f08c2014-11-07 13:01:43 -0800122 $("#xos-detail-button-box").hide();
Scott Baker0bf96b22014-11-04 15:41:47 -0800123 }
124 },
125
Scott Baker7ce23652014-11-07 16:40:30 -0800126 addShower: function(detailName, collection_name, regionName, title) {
127 var app=this;
128 return function() {
Scott Bakera34d8c42014-11-11 18:02:35 -0800129 model = new xos[collection_name].model();
Scott Baker7ce23652014-11-07 16:40:30 -0800130 detailViewClass = app[detailName];
Scott Bakera34d8c42014-11-11 18:02:35 -0800131 detailView = new detailViewClass({model: model});
Scott Baker7ce23652014-11-07 16:40:30 -0800132 app[regionName].show(detailView);
Scott Bakera34d8c42014-11-11 18:02:35 -0800133 $("#xos-detail-button-box").show();
134 $("#xos-listview-button-box").hide();
Scott Baker7ce23652014-11-07 16:40:30 -0800135 }
136 },
137
Scott Baker9d37d562014-11-04 23:20:48 -0800138 detailShower: function(detailName, collection_name, regionName, title) {
Scott Baker0bf96b22014-11-04 15:41:47 -0800139 var app=this;
140 showModelId = function(model_id) {
Scott Baker66aaad42014-11-13 15:52:02 -0800141 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
142
143 collection = xos[collection_name];
144 model = collection.get(model_id);
145 if (model == undefined) {
146 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
147 } else {
Scott Baker0bf96b22014-11-04 15:41:47 -0800148 detailViewClass = app[detailName];
149 detailView = new detailViewClass({model: model});
150 app[regionName].show(detailView);
151 detailView.showLinkedItems();
Scott Bakere49f08c2014-11-07 13:01:43 -0800152 $("#xos-detail-button-box").show();
153 $("#xos-listview-button-box").hide();
Scott Baker0bf96b22014-11-04 15:41:47 -0800154 }
Scott Baker0bf96b22014-11-04 15:41:47 -0800155 }
156 return showModelId;
157 },
Scott Bakerfdaee922014-11-03 09:43:23 -0800158});
159
Scott Bakerc16b4c12014-11-03 23:54:24 -0800160/* XOSDetailView
161 extend with:
162 app - MarionetteApplication
163 template - template (See XOSHelper.html)
164*/
165
166XOSDetailView = Marionette.ItemView.extend({
167 tagName: "div",
168
Scott Bakere49f08c2014-11-07 13:01:43 -0800169 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
170 "click button.btn-xos-save-leave": "submitLeaveClicked",
171 "click button.btn-xos-save-another": "submitAddAnotherClicked",
Scott Bakerc16b4c12014-11-03 23:54:24 -0800172 "change input": "inputChanged"},
173
Scott Bakerc16b4c12014-11-03 23:54:24 -0800174 /* inputChanged is watching the onChange events of the input controls. We
175 do this to track when this view is 'dirty', so we can throw up a warning
176 if the user tries to change his slices without saving first.
177 */
178
179 inputChanged: function(e) {
180 this.dirty = true;
181 },
182
Scott Baker0bf96b22014-11-04 15:41:47 -0800183 saveError: function(model, result, xhr, infoMsgId) {
184 result["what"] = "save " + model.__proto__.modelName;
185 result["infoMsgId"] = infoMsgId;
Scott Bakerc16b4c12014-11-03 23:54:24 -0800186 this.app.showError(result);
187 },
188
Scott Baker0bf96b22014-11-04 15:41:47 -0800189 saveSuccess: function(model, result, xhr, infoMsgId) {
190 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
191 result["what"] = "save " + model.__proto__.modelName;
192 result["infoMsgId"] = infoMsgId;
193 this.app.showSuccess(result);
Scott Bakerc16b4c12014-11-03 23:54:24 -0800194 },
195
Scott Bakere49f08c2014-11-07 13:01:43 -0800196 submitContinueClicked: function(e) {
197 console.log("saveContinue");
198 e.preventDefault();
199 this.save();
200 },
201
202 submitLeaveClicked: function(e) {
203 console.log("saveLeave");
204 e.preventDefault();
205 this.save();
Scott Baker01c9d612014-11-14 16:13:43 -0800206 this.app.Router.navigate(this.listNavLink, {trigger: true});
207 console.log("route to " + this.listNavLink);
Scott Bakere49f08c2014-11-07 13:01:43 -0800208 },
209
210 submitAddAnotherClicked: function(e) {
211 console.log("saveAnother");
212 e.preventDefault();
213 this.save();
214 },
215
216 save: function() {
217 this.app.hideError();
Scott Baker0bf96b22014-11-04 15:41:47 -0800218 var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );
Scott Bakerc16b4c12014-11-03 23:54:24 -0800219 var data = Backbone.Syphon.serialize(this);
Scott Baker0bf96b22014-11-04 15:41:47 -0800220 var that = this;
221 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},
222 success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});
Scott Bakerc16b4c12014-11-03 23:54:24 -0800223 this.dirty = false;
224 },
225
Scott Baker9d37d562014-11-04 23:20:48 -0800226 tabClick: function(tabId, regionName) {
227 region = this.app[regionName];
228 if (this.currentTabRegion != undefined) {
229 this.currentTabRegion.$el.hide();
230 }
231 if (this.currentTabId != undefined) {
232 $(this.currentTabId).removeClass('active');
233 }
234 this.currentTabRegion = region;
235 this.currentTabRegion.$el.show();
236
237 this.currentTabId = tabId;
238 $(tabId).addClass('active');
239 },
240
241 showTabs: function(tabs) {
242 template = templateFromId("#xos-tabs-template", {tabs: tabs});
243 $("#tabs").html(template(tabs));
244 var that = this;
245
246 _.each(tabs, function(tab) {
247 var regionName = tab["region"];
248 var tabId = '#xos-nav-'+regionName;
249 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
250 });
251
252 $("#tabs").show();
253 },
254
Scott Bakerc16b4c12014-11-03 23:54:24 -0800255 showLinkedItems: function() {
Scott Baker9d37d562014-11-04 23:20:48 -0800256 tabs=[];
257
258 tabs.push({name: "details", region: "detail"});
259
260 var index=0;
Scott Bakerc16b4c12014-11-03 23:54:24 -0800261 for (relatedName in this.model.collection.relatedCollections) {
262 relatedField = this.model.collection.relatedCollections[relatedName];
Scott Baker9d37d562014-11-04 23:20:48 -0800263 regionName = "linkedObjs" + (index+1);
Scott Bakerc16b4c12014-11-03 23:54:24 -0800264
265 relatedListViewClassName = relatedName + "ListView";
Scott Baker9d37d562014-11-04 23:20:48 -0800266 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
Scott Bakerc16b4c12014-11-03 23:54:24 -0800267 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});
Scott Baker9d37d562014-11-04 23:20:48 -0800268 this.app[regionName].show(new relatedListViewClass());
Scott Baker62077312014-11-04 23:34:32 -0800269 if (this.app.hideTabsByDefault) {
270 this.app[regionName].$el.hide();
271 }
Scott Baker9d37d562014-11-04 23:20:48 -0800272 tabs.push({name: relatedName, region: regionName});
Scott Bakerc16b4c12014-11-03 23:54:24 -0800273 index = index + 1;
274 }
275
276 while (index<4) {
277 this.app["linkedObjs" + (index+1)].empty();
278 index = index + 1;
279 }
Scott Baker9d37d562014-11-04 23:20:48 -0800280
281 this.showTabs(tabs);
282 this.tabClick('#xos-nav-detail', 'detail');
283 },
284
285});
Scott Bakerc16b4c12014-11-03 23:54:24 -0800286
287/* XOSItemView
288 This is for items that will be displayed as table rows.
289 extend with:
290 app - MarionetteApplication
291 template - template (See XOSHelper.html)
292 detailClass - class of detail view, probably an XOSDetailView
293*/
294
295XOSItemView = Marionette.ItemView.extend({
296 tagName: 'tr',
297 className: 'test-tablerow',
298
299 events: {"click": "changeItem"},
300
301 changeItem: function(e) {
302 this.app.hideError();
303 e.preventDefault();
304 e.stopPropagation();
305
306 this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);
307 },
308});
309
310/* XOSListView:
311 extend with:
312 app - MarionetteApplication
313 childView - class of ItemView, probably an XOSItemView
314 template - template (see xosHelper.html)
315 collection - collection that holds these objects
316 title - title to display in template
317*/
318
319XOSListView = Marionette.CompositeView.extend({
320 childViewContainer: 'tbody',
321
Scott Baker7ce23652014-11-07 16:40:30 -0800322 events: {"click button.btn-xos-add": "addClicked",
323 },
324
325 addClicked: function(e) {
326 console.log("add");
327 e.preventDefault();
Scott Bakera34d8c42014-11-11 18:02:35 -0800328 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
Scott Baker7ce23652014-11-07 16:40:30 -0800329 },
330
Scott Bakerc16b4c12014-11-03 23:54:24 -0800331 initialize: function() {
332 this.listenTo(this.collection, 'change', this._renderChildren)
333
334 // Because many of the templates use idToName(), we need to
335 // listen to the collections that hold the names for the ids
336 // that we want to display.
337 for (i in this.collection.foreignCollections) {
338 foreignName = this.collection.foreignCollections[i];
339 if (xos[foreignName] == undefined) {
340 console.log("Failed to find xos class " + foreignName);
341 }
342 this.listenTo(xos[foreignName], 'change', this._renderChildren);
343 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
344 }
345 },
346
347 templateHelpers: function() {
348 return { title: this.title };
349 },
350});
351
Scott Bakerfdaee922014-11-03 09:43:23 -0800352/* Give an id, the name of a collection, and the name of a field for models
353 within that collection, lookup the id and return the value of the field.
354*/
355
356idToName = function(id, collectionName, fieldName) {
357 linkedObject = xos[collectionName].get(id);
358 if (linkedObject == undefined) {
359 return "#" + id;
360 } else {
361 return linkedObject.attributes[fieldName];
362 }
363};
364
365/* Constructs lists of <option> html blocks for items in a collection.
366
367 selectedId = the id of an object that should be selected, if any
368 collectionName = name of collection
369 fieldName = name of field within models of collection that will be displayed
370*/
371
372idToOptions = function(selectedId, collectionName, fieldName) {
373 result=""
374 for (index in xos[collectionName].models) {
375 linkedObject = xos[collectionName].models[index];
376 linkedId = linkedObject["id"];
377 linkedName = linkedObject.attributes[fieldName];
378 if (linkedId == selectedId) {
379 selected = " selected";
380 } else {
381 selected = "";
382 }
383 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
384 }
385 return result;
386};
387
388/* Constructs an html <select> and the <option>s to go with it.
389
390 variable = variable name to return to form
391 selectedId = the id of an object that should be selected, if any
392 collectionName = name of collection
393 fieldName = name of field within models of collection that will be displayed
394*/
395
396idToSelect = function(variable, selectedId, collectionName, fieldName) {
397 result = '<select name="' + variable + '">' +
398 idToOptions(selectedId, collectionName, fieldName) +
399 '</select>';
400 return result;
401}
402