1 HTMLView = Marionette.ItemView.extend({
3 this.$el.append(this.options.html);
7 XOSRouter = Marionette.AppRouter.extend({
8 initialize: function() {
\r
12 onRoute: function(x,y,z) {
\r
13 this.routeStack.push(Backbone.history.fragment);
\r
16 prevPage: function() {
\r
17 return this.routeStack.slice(-2)[0];
23 XOSApplication = Marionette.Application.extend({
24 detailBoxId: "#detailBox",
25 errorBoxId: "#errorBox",
26 errorCloseButtonId: "#close-error-box",
27 successBoxId: "#successBox",
28 successCloseButtonId: "#close-success-box",
29 errorTemplate: "#xos-error-template",
30 successTemplate: "#xos-success-template",
33 confirmDialog: function(view, event, callback) {
34 $("#xos-confirm-dialog").dialog({
38 "Confirm" : function() {
39 $(this).dialog("close");
47 "Cancel" : function() {
48 $(this).dialog("close");
52 $("#xos-confirm-dialog").dialog("open");
55 popupErrorDialog: function(responseText) {
57 parsed_error=$.parseJSON(responseText);
61 parsed_error=undefined;
62 width=640; // django stacktraces like wide width
65 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
67 $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: responseText}))
70 $("#xos-error-dialog").dialog({
74 Ok: function() { $(this).dialog("close"); }
79 hideLinkedItems: function(result) {
82 this["linkedObjs" + (index+1)].empty();
87 listViewShower: function(listViewName, collection_name, regionName, title) {
90 app[regionName].show(new app[listViewName]);
91 app.hideLinkedItems();
92 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
94 $("#xos-listview-button-box").show();
96 $("#xos-detail-button-box").hide();
100 addShower: function(detailName, collection_name, regionName, title) {
103 model = new xos[collection_name].model();
104 detailViewClass = app[detailName];
105 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
106 app[regionName].show(detailView);
107 $("#xos-detail-button-box").show();
108 $("#xos-listview-button-box").hide();
112 deleteShower: function(collection_name) {
114 return function(model_id) {
115 console.log("deleteCalled");
116 collection = xos[collection_name];
117 model = collection.get(model_id);
118 assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
119 app.deleteDialog(model,"back");
123 detailShower: function(detailName, collection_name, regionName, title) {
125 showModelId = function(model_id) {
126 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
128 collection = xos[collection_name];
129 model = collection.get(model_id);
130 if (model == undefined) {
131 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
133 detailViewClass = app[detailName];
134 detailView = new detailViewClass({model: model});
135 app[regionName].show(detailView);
136 detailView.showLinkedItems();
137 $("#xos-detail-button-box").show();
138 $("#xos-listview-button-box").hide();
144 /* error handling callbacks */
146 hideError: function() {
147 if (this.logWindowId) {
149 $(this.errorBoxId).hide();
150 $(this.successBoxId).hide();
154 showSuccess: function(result) {
155 result["statusclass"] = "success";
156 if (this.logTableId) {
157 this.appendLogWindow(result);
159 $(this.successBoxId).show();
160 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
162 $(this.successCloseButtonId).unbind().bind('click', function() {
163 $(that.successBoxId).hide();
168 showError: function(result) {
169 result["statusclass"] = "failure";
170 if (this.logTableId) {
171 this.appendLogWindow(result);
172 this.popupErrorDialog(result.responseText);
174 // this is really old stuff
175 $(this.errorBoxId).show();
176 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
178 $(this.errorCloseButtonId).unbind().bind('click', function() {
179 $(that.errorBoxId).hide();
184 showInformational: function(result) {
185 result["statusclass"] = "inprog";
186 if (this.logTableId) {
187 return this.appendLogWindow(result);
193 appendLogWindow: function(result) {
194 // compute a new logMessageId for this log message
195 logMessageId = "logMessage" + this.logMessageCount;
196 this.logMessageCount = this.logMessageCount + 1;
197 result["logMessageId"] = logMessageId;
199 logMessageTemplate=$("#xos-log-template").html();
200 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
201 newRow = _.template(logMessageTemplate, result);
202 assert(newRow != undefined, "newRow is undefined");
204 if (result["infoMsgId"] != undefined) {
205 // We were passed the logMessageId of an informational message,
206 // and the caller wants us to replace that message with our own.
207 // i.e. replace an informational message with a success or an error.
208 $("#"+result["infoMsgId"]).replaceWith(newRow);
210 // Create a brand new log message rather than replacing one.
211 logTableBody = $(this.logTableId + " tbody");
212 logTableBody.prepend(newRow);
215 if (this.statusMsgId) {
216 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
219 limitTableRows(this.logTableId, 5);
224 saveError: function(model, result, xhr, infoMsgId) {
225 console.log("saveError");
226 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
227 result["infoMsgId"] = infoMsgId;
228 this.showError(result);
231 saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
232 console.log("saveSuccess");
233 if (model.addToCollection) {
234 console.log("addToCollection");
235 model.addToCollection.add(model);
236 model.addToCollection.sort();
237 model.addToCollection = undefined;
239 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
240 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
241 result["infoMsgId"] = infoMsgId;
242 this.showSuccess(result);
245 destroyError: function(model, result, xhr, infoMsgId) {
246 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
247 result["infoMsgId"] = infoMsgId;
248 this.showError(result);
251 destroySuccess: function(model, result, xhr, infoMsgId) {
252 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
253 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
254 result["infoMsgId"] = infoMsgId;
255 this.showSuccess(result);
258 /* end error handling callbacks */
260 destroyModel: function(model) {
261 //console.log("destroyModel"); console.log(model);
263 var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
265 model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
266 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
269 deleteDialog: function(model, afterDelete) {
272 console.log(Backbone.history.fragment);
273 assert(model!=undefined, "deleteDialog's model is undefined");
274 //console.log("deleteDialog"); console.log(model);
275 this.confirmDialog(null, null, function() {
276 //console.log("deleteConfirm"); console.log(model);
277 modelName = model.modelName;
278 that.destroyModel(model);
279 if (afterDelete=="list") {
280 that.navigate("list", modelName);
281 } else if (afterDelete=="back") {
282 prevPage = that.Router.prevPage();
284 that.Router.navigate("#"+prevPage, {trigger: false, replace: true} );
294 app - MarionetteApplication
295 template - template (See XOSHelper.html)
298 XOSDetailView = Marionette.ItemView.extend({
301 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
302 "click button.btn-xos-save-leave": "submitLeaveClicked",
303 "click button.btn-xos-save-another": "submitAddAnotherClicked",
304 "click button.btn-xos-delete": "deleteClicked",
305 "change input": "inputChanged"},
307 /*initialize: function() {
308 this.on('deleteConfirmed', this.deleteConfirmed);
311 /* inputChanged is watching the onChange events of the input controls. We
312 do this to track when this view is 'dirty', so we can throw up a warning
313 if the user tries to change his slices without saving first.
316 inputChanged: function(e) {
320 submitContinueClicked: function(e) {
321 console.log("saveContinue");
326 submitLeaveClicked: function(e) {
327 console.log("saveLeave");
330 this.app.navigate("list", this.model.modelName);
333 submitAddAnotherClicked: function(e) {
334 console.log("saveAnother");
337 this.app.navigate("add", this.model.modelName);
341 this.app.hideError();
342 var data = Backbone.Syphon.serialize(this);
344 var isNew = !this.model.id;
346 this.$el.find(".help-inline").remove();
348 /* although model.validate() is called automatically by
349 model.save, we call it ourselves, so we can throw up our
350 validation error before creating the infoMsg in the log
352 errors = this.model.xosValidate(data);
354 this.onFormDataInvalid(errors);
359 this.model.attributes.humanReadableName = "new " + model.modelName;
360 this.model.addToCollection = this.collection;
362 this.model.addToCollection = undefined;
365 var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
367 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
368 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);}});
372 /*destroyModel: function() {
373 this.app.hideError();
374 var infoMsgId = this.app.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
376 this.model.destroy({error: function(model, result, xhr) { that.app.destroyError(model,result,xhr,infoMsgId);},
377 success: function(model, result, xhr) { that.app.destroySuccess(model,result,xhr,infoMsgId);}});
380 deleteClicked: function(e) {
382 this.app.confirmDialog(this, "deleteConfirmed");
385 deleteConfirmed: function() {
386 modelName = this.model.modelName;
388 this.app.navigate("list", modelName);
391 deleteClicked: function(e) {
393 this.app.deleteDialog(this.model, "list");
396 tabClick: function(tabId, regionName) {
397 region = this.app[regionName];
398 if (this.currentTabRegion != undefined) {
399 this.currentTabRegion.$el.hide();
401 if (this.currentTabId != undefined) {
402 $(this.currentTabId).removeClass('active');
404 this.currentTabRegion = region;
405 this.currentTabRegion.$el.show();
407 this.currentTabId = tabId;
408 $(tabId).addClass('active');
411 showTabs: function(tabs) {
412 template = templateFromId("#xos-tabs-template", {tabs: tabs});
413 $("#tabs").html(template(tabs));
416 _.each(tabs, function(tab) {
417 var regionName = tab["region"];
418 var tabId = '#xos-nav-'+regionName;
419 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
425 showLinkedItems: function() {
428 tabs.push({name: "details", region: "detail"});
431 for (relatedName in this.model.collection.relatedCollections) {
432 relatedField = this.model.collection.relatedCollections[relatedName];
433 regionName = "linkedObjs" + (index+1);
435 relatedListViewClassName = relatedName + "ListView";
436 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
437 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});
438 this.app[regionName].show(new relatedListViewClass());
439 if (this.app.hideTabsByDefault) {
440 this.app[regionName].$el.hide();
442 tabs.push({name: relatedName, region: regionName});
447 this.app["linkedObjs" + (index+1)].empty();
452 this.tabClick('#xos-nav-detail', 'detail');
455 onFormDataInvalid: function(errors) {
457 var markErrors = function(value, key) {
458 console.log("name='" + key + "'");
459 var $inputElement = self.$el.find("[name='" + key + "']");
460 var $inputContainer = $inputElement.parent();
461 //$inputContainer.find(".help-inline").remove();
462 var $errorEl = $("<span>", {class: "help-inline error", text: value});
463 $inputContainer.append($errorEl).addClass("error");
465 _.each(errors, markErrors);
471 This is for items that will be displayed as table rows.
473 app - MarionetteApplication
474 template - template (See XOSHelper.html)
475 detailClass - class of detail view, probably an XOSDetailView
478 XOSItemView = Marionette.ItemView.extend({
480 className: 'test-tablerow',
482 templateHelpers: function() { return { modelName: this.model.modelName,
483 collectionName: this.model.collectionName,
489 app - MarionetteApplication
490 childView - class of ItemView, probably an XOSItemView
491 template - template (see xosHelper.html)
492 collection - collection that holds these objects
493 title - title to display in template
496 XOSListView = Marionette.CompositeView.extend({
497 childViewContainer: 'tbody',
499 events: {"click button.btn-xos-add": "addClicked",
500 "click button.btn-xos-refresh": "refreshClicked",
503 _fetchStateChange: function() {
504 if (this.collection.fetching) {
505 $("#xos-list-title-spinner").show();
507 $("#xos-list-title-spinner").hide();
511 addClicked: function(e) {
513 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
516 refreshClicked: function(e) {
518 this.collection.refresh(refreshRelated=true);
521 initialize: function() {
522 this.listenTo(this.collection, 'change', this._renderChildren)
523 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
525 // Because many of the templates use idToName(), we need to
526 // listen to the collections that hold the names for the ids
527 // that we want to display.
528 for (i in this.collection.foreignCollections) {
529 foreignName = this.collection.foreignCollections[i];
530 if (xos[foreignName] == undefined) {
531 console.log("Failed to find xos class " + foreignName);
533 this.listenTo(xos[foreignName], 'change', this._renderChildren);
534 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
538 templateHelpers: function() {
539 return { title: this.title };
543 /* Give an id, the name of a collection, and the name of a field for models
544 within that collection, lookup the id and return the value of the field.
547 idToName = function(id, collectionName, fieldName) {
548 linkedObject = xos[collectionName].get(id);
549 if (linkedObject == undefined) {
552 return linkedObject.attributes[fieldName];
556 /* Constructs lists of <option> html blocks for items in a collection.
558 selectedId = the id of an object that should be selected, if any
559 collectionName = name of collection
560 fieldName = name of field within models of collection that will be displayed
563 idToOptions = function(selectedId, collectionName, fieldName) {
565 for (index in xos[collectionName].models) {
566 linkedObject = xos[collectionName].models[index];
567 linkedId = linkedObject["id"];
568 linkedName = linkedObject.attributes[fieldName];
569 if (linkedId == selectedId) {
570 selected = " selected";
574 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
579 /* Constructs an html <select> and the <option>s to go with it.
581 variable = variable name to return to form
582 selectedId = the id of an object that should be selected, if any
583 collectionName = name of collection
584 fieldName = name of field within models of collection that will be displayed
587 idToSelect = function(variable, selectedId, collectionName, fieldName) {
588 result = '<select name="' + variable + '">' +
589 idToOptions(selectedId, collectionName, fieldName) +