1 function assert(outcome, description) {
3 console.log(description);
7 function templateFromId(id) {
8 return _.template($(id).html());
11 function firstCharUpper(s) {
12 return s.charAt(0).toUpperCase() + s.slice(1);
15 HTMLView = Marionette.ItemView.extend({
17 this.$el.append(this.options.html);
21 XOSApplication = Marionette.Application.extend({
22 detailBoxId: "#detailBox",
23 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",
31 confirmDialog: function(view, event) {
32 $("#xos-confirm-dialog").dialog({
36 "Confirm" : function() {
\r
37 $(this).dialog("close");
\r
38 view.trigger(event);
\r
40 "Cancel" : function() {
\r
41 $(this).dialog("close");
\r
45 $("#xos-confirm-dialog").dialog("open");
48 hideError: function() {
49 if (this.logWindowId) {
51 $(this.errorBoxId).hide();
52 $(this.successBoxId).hide();
56 showSuccess: function(result) {
57 result["statusclass"] = "success";
58 if (this.logTableId) {
59 this.appendLogWindow(result);
61 $(this.successBoxId).show();
62 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
64 $(this.successCloseButtonId).unbind().bind('click', function() {
65 $(that.successBoxId).hide();
70 showError: function(result) {
71 result["statusclass"] = "failure";
72 if (this.logTableId) {
73 this.appendLogWindow(result);
75 $(this.errorBoxId).show();
76 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
78 $(this.errorCloseButtonId).unbind().bind('click', function() {
79 $(that.errorBoxId).hide();
84 showInformational: function(result) {
85 result["statusclass"] = "inprog";
86 if (this.logTableId) {
87 return this.appendLogWindow(result);
93 appendLogWindow: function(result) {
94 // compute a new logMessageId for this log message
95 logMessageId = "logMessage" + this.logMessageCount;
96 this.logMessageCount = this.logMessageCount + 1;
97 result["logMessageId"] = logMessageId;
99 logMessageTemplate=$("#xos-log-template").html();
100 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
101 newRow = _.template(logMessageTemplate, result);
102 assert(newRow != undefined, "newRow is undefined");
104 if (result["infoMsgId"] != undefined) {
105 // We were passed the logMessageId of an informational message,
106 // and the caller wants us to replace that message with our own.
107 // i.e. replace an informational message with a success or an error.
108 $("#"+result["infoMsgId"]).replaceWith(newRow);
110 // Create a brand new log message rather than replacing one.
111 logTableBody = $(this.logTableId + " tbody");
112 logTableBody.prepend(newRow);
115 if (this.statusMsgId) {
116 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
119 limitTableRows(this.logTableId, 5);
124 hideLinkedItems: function(result) {
127 this["linkedObjs" + (index+1)].empty();
\r
132 listViewShower: function(listViewName, collection_name, regionName, title) {
\r
134 return function() {
\r
135 app[regionName].show(new app[listViewName]);
\r
136 app.hideLinkedItems();
\r
137 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
\r
138 $("#detail").show();
\r
139 $("#xos-listview-button-box").show();
\r
141 $("#xos-detail-button-box").hide();
\r
145 addShower: function(detailName, collection_name, regionName, title) {
\r
147 return function() {
\r
148 model = new xos[collection_name].model();
\r
149 detailViewClass = app[detailName];
\r
150 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
\r
151 app[regionName].show(detailView);
\r
152 $("#xos-detail-button-box").show();
\r
153 $("#xos-listview-button-box").hide();
\r
157 detailShower: function(detailName, collection_name, regionName, title) {
\r
159 showModelId = function(model_id) {
\r
160 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
\r
162 collection = xos[collection_name];
\r
163 model = collection.get(model_id);
\r
164 if (model == undefined) {
\r
165 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
\r
167 detailViewClass = app[detailName];
\r
168 detailView = new detailViewClass({model: model});
\r
169 app[regionName].show(detailView);
\r
170 detailView.showLinkedItems();
\r
171 $("#xos-detail-button-box").show();
\r
172 $("#xos-listview-button-box").hide();
\r
175 return showModelId;
\r
181 app - MarionetteApplication
182 template - template (See XOSHelper.html)
185 XOSDetailView = Marionette.ItemView.extend({
188 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
189 "click button.btn-xos-save-leave": "submitLeaveClicked",
190 "click button.btn-xos-save-another": "submitAddAnotherClicked",
191 "click button.btn-xos-delete": "deleteClicked",
192 "change input": "inputChanged"},
194 initialize: function() {
195 this.on('deleteConfirmed', this.deleteConfirmed);
198 /* inputChanged is watching the onChange events of the input controls. We
199 do this to track when this view is 'dirty', so we can throw up a warning
\r
200 if the user tries to change his slices without saving first.
\r
203 inputChanged: function(e) {
\r
207 saveError: function(model, result, xhr, infoMsgId) {
\r
208 result["what"] = "save " + model.__proto__.modelName;
\r
209 result["infoMsgId"] = infoMsgId;
\r
210 this.app.showError(result);
\r
213 saveSuccess: function(model, result, xhr, infoMsgId) {
\r
214 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
\r
215 result["what"] = "save " + model.__proto__.modelName;
\r
216 result["infoMsgId"] = infoMsgId;
\r
217 this.app.showSuccess(result);
\r
220 destroyError: function(model, result, xhr, infoMsgId) {
221 result["what"] = "destroy " + model.__proto__.modelName;
\r
222 result["infoMsgId"] = infoMsgId;
\r
223 this.app.showError(result);
\r
226 destroySuccess: function(model, result, xhr, infoMsgId) {
\r
227 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
\r
228 result["what"] = "destroy " + model.__proto__.modelName;
\r
229 result["infoMsgId"] = infoMsgId;
\r
230 this.app.showSuccess(result);
\r
233 submitContinueClicked: function(e) {
234 console.log("saveContinue");
239 submitLeaveClicked: function(e) {
240 console.log("saveLeave");
243 this.app.navigate("list", this.model.modelName);
246 submitAddAnotherClicked: function(e) {
247 console.log("saveAnother");
250 this.app.navigate("add", this.model.modelName);
254 this.app.hideError();
255 var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );
\r
256 var data = Backbone.Syphon.serialize(this);
\r
258 var isNew = !this.model.id;
\r
259 this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},
\r
260 success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});
\r
262 console.log(this.model);
\r
263 this.collection.add(this.model);
\r
264 this.collection.sort();
\r
266 this.dirty = false;
\r
269 destroyModel: function() {
270 this.app.hideError();
271 var infoMsgId = this.app.showInformational( {what: "destroy " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );
273 this.model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
274 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
277 deleteClicked: function(e) {
279 \r this.app.confirmDialog(this, "deleteConfirmed");
282 \r deleteConfirmed: function() {
283 \r modelName = this.model.modelName;
284 \r this.destroyModel();
285 \r this.app.navigate("list", modelName);
288 tabClick: function(tabId, regionName) {
289 region = this.app[regionName];
\r
290 if (this.currentTabRegion != undefined) {
\r
291 this.currentTabRegion.$el.hide();
\r
293 if (this.currentTabId != undefined) {
\r
294 $(this.currentTabId).removeClass('active');
\r
296 this.currentTabRegion = region;
\r
297 this.currentTabRegion.$el.show();
\r
299 this.currentTabId = tabId;
\r
300 $(tabId).addClass('active');
\r
303 showTabs: function(tabs) {
304 template = templateFromId("#xos-tabs-template", {tabs: tabs});
305 $("#tabs").html(template(tabs));
308 _.each(tabs, function(tab) {
309 var regionName = tab["region"];
310 var tabId = '#xos-nav-'+regionName;
311 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
317 showLinkedItems: function() {
320 tabs.push({name: "details", region: "detail"});
323 for (relatedName in this.model.collection.relatedCollections) {
\r
324 relatedField = this.model.collection.relatedCollections[relatedName];
\r
325 regionName = "linkedObjs" + (index+1);
\r
327 relatedListViewClassName = relatedName + "ListView";
\r
328 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
\r
329 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});
\r
330 this.app[regionName].show(new relatedListViewClass());
\r
331 if (this.app.hideTabsByDefault) {
\r
332 this.app[regionName].$el.hide();
\r
334 tabs.push({name: relatedName, region: regionName});
\r
339 this.app["linkedObjs" + (index+1)].empty();
\r
343 this.showTabs(tabs);
\r
344 this.tabClick('#xos-nav-detail', 'detail');
\r
350 This is for items that will be displayed as table rows.
352 app - MarionetteApplication
353 template - template (See XOSHelper.html)
354 detailClass - class of detail view, probably an XOSDetailView
357 XOSItemView = Marionette.ItemView.extend({
359 className: 'test-tablerow',
361 events: {"click": "changeItem"},
363 changeItem: function(e) {
\r
364 this.app.hideError();
\r
365 e.preventDefault();
\r
366 e.stopPropagation();
\r
368 this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);
\r
374 app - MarionetteApplication
375 childView - class of ItemView, probably an XOSItemView
376 template - template (see xosHelper.html)
377 collection - collection that holds these objects
378 title - title to display in template
381 XOSListView = Marionette.CompositeView.extend({
382 childViewContainer: 'tbody',
\r
384 events: {"click button.btn-xos-add": "addClicked",
\r
385 "click button.btn-xos-refresh": "refreshClicked",
\r
388 _fetchStateChange: function() {
\r
389 if (this.collection.fetching) {
\r
390 $("#xos-list-title-spinner").show();
\r
392 $("#xos-list-title-spinner").hide();
\r
396 addClicked: function(e) {
398 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
401 \r refreshClicked: function(e) {
402 \r e.preventDefault();
403 \r this.collection.refresh(refreshRelated=true);
406 \r initialize: function() {
407 \r this.listenTo(this.collection, 'change', this._renderChildren)
408 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
410 // Because many of the templates use idToName(), we need to
411 // listen to the collections that hold the names for the ids
412 // that we want to display.
413 for (i in this.collection.foreignCollections) {
414 foreignName = this.collection.foreignCollections[i];
415 if (xos[foreignName] == undefined) {
416 console.log("Failed to find xos class " + foreignName);
418 this.listenTo(xos[foreignName], 'change', this._renderChildren);
419 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
423 templateHelpers: function() {
424 return { title: this.title };
428 /* Give an id, the name of a collection, and the name of a field for models
429 within that collection, lookup the id and return the value of the field.
432 idToName = function(id, collectionName, fieldName) {
433 linkedObject = xos[collectionName].get(id);
434 if (linkedObject == undefined) {
437 return linkedObject.attributes[fieldName];
441 /* Constructs lists of <option> html blocks for items in a collection.
443 selectedId = the id of an object that should be selected, if any
444 collectionName = name of collection
445 fieldName = name of field within models of collection that will be displayed
448 idToOptions = function(selectedId, collectionName, fieldName) {
450 for (index in xos[collectionName].models) {
451 linkedObject = xos[collectionName].models[index];
452 linkedId = linkedObject["id"];
453 linkedName = linkedObject.attributes[fieldName];
454 if (linkedId == selectedId) {
455 selected = " selected";
459 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
464 /* Constructs an html <select> and the <option>s to go with it.
466 variable = variable name to return to form
467 selectedId = the id of an object that should be selected, if any
468 collectionName = name of collection
469 fieldName = name of field within models of collection that will be displayed
472 idToSelect = function(variable, selectedId, collectionName, fieldName) {
473 result = '<select name="' + variable + '">' +
474 idToOptions(selectedId, collectionName, fieldName) +