1 HTMLView = Marionette.ItemView.extend({
3 this.$el.append(this.options.html);
7 FilteredCompositeView = Marionette.CompositeView.extend( {
8 showCollection: function() {
10 this.collection.each(function(child, index) {
11 if (this.filter && !this.filter(child)) {
14 ChildView = this.getChildView(child);
15 this.addChild(child, ChildView, index);
21 XOSRouter = Marionette.AppRouter.extend({
22 initialize: function() {
\r
26 onRoute: function(x,y,z) {
\r
27 this.routeStack.push(Backbone.history.fragment);
\r
30 prevPage: function() {
\r
31 return this.routeStack.slice(-1)[0];
34 showPreviousURL: function() {
35 prevPage = this.prevPage();
36 console.log("showPreviousURL");
37 console.log(this.routeStack);
39 this.navigate("#"+prevPage, {trigger: false, replace: true} );
46 XOSApplication = Marionette.Application.extend({
47 detailBoxId: "#detailBox",
48 errorBoxId: "#errorBox",
49 errorCloseButtonId: "#close-error-box",
50 successBoxId: "#successBox",
51 successCloseButtonId: "#close-success-box",
52 errorTemplate: "#xos-error-template",
53 successTemplate: "#xos-success-template",
56 confirmDialog: function(view, event, callback) {
57 $("#xos-confirm-dialog").dialog({
61 "Confirm" : function() {
62 $(this).dialog("close");
70 "Cancel" : function() {
71 $(this).dialog("close");
75 $("#xos-confirm-dialog").dialog("open");
78 popupErrorDialog: function(responseText) {
80 parsed_error=$.parseJSON(responseText);
84 parsed_error=undefined;
85 width=640; // django stacktraces like wide width
88 $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
90 $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: responseText}))
93 $("#xos-error-dialog").dialog({
97 Ok: function() { $(this).dialog("close"); }
102 hideLinkedItems: function(result) {
105 this["linkedObjs" + (index+1)].empty();
110 createListHandler: function(listViewName, collection_name, regionName, title) {
113 listView = new app[listViewName];
114 app[regionName].show(listView);
115 app.hideLinkedItems();
116 $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
120 listButtons = new XOSListButtonView({linkedView: listView});
121 app["rightButtonPanel"].show(listButtons);
125 createAddHandler: function(detailName, collection_name, regionName, title) {
128 model = new xos[collection_name].model();
129 detailViewClass = app[detailName];
130 detailView = new detailViewClass({model: model, collection:xos[collection_name]});
131 app[regionName].show(detailView);
132 $("#xos-detail-button-box").show();
133 $("#xos-listview-button-box").hide();
137 createAddChildHandler: function(addChildName, collection_name) {
139 return function(parent_modelName, parent_fieldName, parent_id) {
140 app.Router.showPreviousURL();
142 console.log(parent_modelName);
143 console.log(parent_fieldName);
144 console.log(parent_id);
145 model = new xos[collection_name].model();
146 model.attributes[parent_fieldName] = parent_id;
147 model.readOnlyFields.push(parent_fieldName);
149 detailViewClass = app[addChildName];
150 var detailView = new detailViewClass({model: model, collection:xos[collection_name]});
151 detailView.dialog = $("xos-addchild-dialog");
152 app["addChildDetail"].show(detailView);
153 $("#xos-addchild-dialog").dialog({
158 "Save" : function() {
159 var addDialog = this;
160 detailView.synchronous = true;
161 detailView.afterSave = function() { console.log("afterSave"); $(addDialog).dialog("close"); }
164 //$(this).dialog("close");
166 "Cancel" : function() {
167 $(this).dialog("close");
171 $("#xos-addchild-dialog").dialog("open");
175 createDeleteHandler: function(collection_name) {
177 return function(model_id) {
178 console.log("deleteCalled");
179 collection = xos[collection_name];
180 model = collection.get(model_id);
181 assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
182 app.Router.showPreviousURL();
183 app.deleteDialog(model);
187 createDetailHandler: function(detailName, collection_name, regionName, title) {
189 showModelId = function(model_id) {
190 $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
192 collection = xos[collection_name];
193 model = collection.get(model_id);
194 if (model == undefined) {
195 app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
197 detailViewClass = app[detailName];
198 detailView = new detailViewClass({model: model});
199 app[regionName].show(detailView);
200 detailView.showLinkedItems();
201 //$("#xos-detail-button-box").show();
202 //$("#xos-listview-button-box").hide();
204 detailButtons = new XOSDetailButtonView({linkedView: detailView});
205 app["rightButtonPanel"].show(detailButtons);
211 /* error handling callbacks */
213 hideError: function() {
214 if (this.logWindowId) {
216 $(this.errorBoxId).hide();
217 $(this.successBoxId).hide();
221 showSuccess: function(result) {
222 result["statusclass"] = "success";
223 if (this.logTableId) {
224 this.appendLogWindow(result);
226 $(this.successBoxId).show();
227 $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
229 $(this.successCloseButtonId).unbind().bind('click', function() {
230 $(that.successBoxId).hide();
235 showError: function(result) {
236 result["statusclass"] = "failure";
237 if (this.logTableId) {
238 this.appendLogWindow(result);
239 this.popupErrorDialog(result.responseText);
241 // this is really old stuff
242 $(this.errorBoxId).show();
243 $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
245 $(this.errorCloseButtonId).unbind().bind('click', function() {
246 $(that.errorBoxId).hide();
251 showInformational: function(result) {
252 result["statusclass"] = "inprog";
253 if (this.logTableId) {
254 return this.appendLogWindow(result);
260 appendLogWindow: function(result) {
261 // compute a new logMessageId for this log message
262 logMessageId = "logMessage" + this.logMessageCount;
263 this.logMessageCount = this.logMessageCount + 1;
264 result["logMessageId"] = logMessageId;
266 logMessageTemplate=$("#xos-log-template").html();
267 assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
268 newRow = _.template(logMessageTemplate, result);
269 assert(newRow != undefined, "newRow is undefined");
271 if (result["infoMsgId"] != undefined) {
272 // We were passed the logMessageId of an informational message,
273 // and the caller wants us to replace that message with our own.
274 // i.e. replace an informational message with a success or an error.
275 $("#"+result["infoMsgId"]).replaceWith(newRow);
277 // Create a brand new log message rather than replacing one.
278 logTableBody = $(this.logTableId + " tbody");
279 logTableBody.prepend(newRow);
282 if (this.statusMsgId) {
283 $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
286 limitTableRows(this.logTableId, 5);
291 saveError: function(model, result, xhr, infoMsgId) {
292 console.log("saveError");
293 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
294 result["infoMsgId"] = infoMsgId;
295 this.showError(result);
298 saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
299 console.log("saveSuccess");
300 if (model.addToCollection) {
301 console.log("addToCollection");
302 console.log(model.addToCollection);
303 model.addToCollection.add(model);
304 model.addToCollection.sort();
305 model.addToCollection = undefined;
307 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
308 result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
309 result["infoMsgId"] = infoMsgId;
310 this.showSuccess(result);
313 destroyError: function(model, result, xhr, infoMsgId) {
314 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
315 result["infoMsgId"] = infoMsgId;
316 this.showError(result);
319 destroySuccess: function(model, result, xhr, infoMsgId) {
320 result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
321 result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
322 result["infoMsgId"] = infoMsgId;
323 this.showSuccess(result);
326 /* end error handling callbacks */
328 destroyModel: function(model) {
329 //console.log("destroyModel"); console.log(model);
331 var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
333 model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
334 success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
337 deleteDialog: function(model, afterDelete) {
339 assert(model!=undefined, "deleteDialog's model is undefined");
340 //console.log("deleteDialog"); console.log(model);
341 this.confirmDialog(null, null, function() {
342 //console.log("deleteConfirm"); console.log(model);
343 modelName = model.modelName;
344 that.destroyModel(model);
345 if (afterDelete=="list") {
346 that.navigate("list", modelName);
352 XOSButtonView = Marionette.ItemView.extend({
353 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
354 "click button.btn-xos-save-leave": "submitLeaveClicked",
355 "click button.btn-xos-save-another": "submitAddAnotherClicked",
356 "click button.btn-xos-delete": "deleteClicked",
357 "click button.btn-xos-add": "addClicked",
358 "click button.btn-xos-refresh": "refreshClicked",
361 submitLeaveClicked: function(e) {
362 this.options.linkedView.submitLeaveClicked.call(this.options.linkedView, e);
365 submitContinueClicked: function(e) {
366 this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
369 submitAddAnotherClicked: function(e) {
370 this.options.linkedView.submitAddAnotherClicked.call(this.options.linkedView, e);
373 submitDeleteClicked: function(e) {
374 this.options.linkedView.submitDeleteClicked.call(this.options.linkedView, e);
377 addClicked: function(e) {
378 this.options.linkedView.addClicked.call(this.options.linkedView, e);
381 refreshClicked: function(e) {
382 this.options.linkedView.refreshClicked.call(this.options.linkedView, e);
386 XOSDetailButtonView = XOSButtonView.extend({ template: "#xos-savebuttons-template" });
387 XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template" });
391 app - MarionetteApplication
392 template - template (See XOSHelper.html)
395 XOSDetailView = Marionette.ItemView.extend({
398 events: {"click button.btn-xos-save-continue": "submitContinueClicked",
399 "click button.btn-xos-save-leave": "submitLeaveClicked",
400 "click button.btn-xos-save-another": "submitAddAnotherClicked",
401 "click button.btn-xos-delete": "deleteClicked",
402 "change input": "inputChanged"},
404 /* inputChanged is watching the onChange events of the input controls. We
405 do this to track when this view is 'dirty', so we can throw up a warning
406 if the user tries to change his slices without saving first.
409 initialize: function() {
410 this.on("saveSuccess", this.onAfterSave);
411 this.synchronous = false;
414 afterSave: function(e) {
417 onAfterSave: function(e) {
421 inputChanged: function(e) {
425 submitContinueClicked: function(e) {
426 console.log("saveContinue");
428 this.afterSave = function() { };
432 submitLeaveClicked: function(e) {
433 console.log("saveLeave");
436 this.afterSave = function() {
437 that.app.navigate("list", that.model.modelName);
442 submitAddAnotherClicked: function(e) {
443 console.log("saveAnother");
447 this.afterSave = function() {
448 that.app.navigate("add", that.model.modelName);
454 this.app.hideError();
455 var data = Backbone.Syphon.serialize(this);
457 var isNew = !this.model.id;
459 this.$el.find(".help-inline").remove();
461 /* although model.validate() is called automatically by
462 model.save, we call it ourselves, so we can throw up our
463 validation error before creating the infoMsg in the log
465 errors = this.model.xosValidate(data);
467 this.onFormDataInvalid(errors);
472 this.model.attributes.humanReadableName = "new " + model.modelName;
473 this.model.addToCollection = this.collection;
475 this.model.addToCollection = undefined;
478 var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
480 this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
481 success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
482 if (that.synchronous) {
483 that.trigger("saveSuccess");
488 if (!this.synchronous) {
493 deleteClicked: function(e) {
495 this.app.deleteDialog(this.model, "list");
498 tabClick: function(tabId, regionName) {
499 region = this.app[regionName];
500 if (this.currentTabRegion != undefined) {
501 this.currentTabRegion.$el.hide();
503 if (this.currentTabId != undefined) {
504 $(this.currentTabId).removeClass('active');
506 this.currentTabRegion = region;
507 this.currentTabRegion.$el.show();
509 this.currentTabId = tabId;
510 $(tabId).addClass('active');
513 showTabs: function(tabs) {
514 template = templateFromId("#xos-tabs-template", {tabs: tabs});
515 $("#tabs").html(template(tabs));
518 _.each(tabs, function(tab) {
519 var regionName = tab["region"];
520 var tabId = '#xos-nav-'+regionName;
521 $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
527 showLinkedItems: function() {
530 tabs.push({name: "details", region: "detail"});
532 makeFilter = function(relatedField, relatedId) {
533 return function(model) { return model.attributes[relatedField] == relatedId; }
537 for (relatedName in this.model.collection.relatedCollections) {
538 var relatedField = this.model.collection.relatedCollections[relatedName];
539 var relatedId = this.model.id;
540 regionName = "linkedObjs" + (index+1);
542 relatedListViewClassName = relatedName + "ListView";
543 assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
544 relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
545 filter: makeFilter(relatedField, relatedId),
546 parentModel: this.model});
547 this.app[regionName].show(new relatedListViewClass());
548 if (this.app.hideTabsByDefault) {
549 this.app[regionName].$el.hide();
551 tabs.push({name: relatedName, region: regionName});
556 this.app["linkedObjs" + (index+1)].empty();
561 this.tabClick('#xos-nav-detail', 'detail');
564 onFormDataInvalid: function(errors) {
566 var markErrors = function(value, key) {
567 console.log("name='" + key + "'");
568 var $inputElement = self.$el.find("[name='" + key + "']");
569 var $inputContainer = $inputElement.parent();
570 //$inputContainer.find(".help-inline").remove();
571 var $errorEl = $("<span>", {class: "help-inline error", text: value});
572 $inputContainer.append($errorEl).addClass("error");
574 _.each(errors, markErrors);
577 templateHelpers: function() { return { modelName: this.model.modelName,
578 collectionName: this.model.collectionName,
579 addFields: this.model.addFields,
580 listFields: this.model.listFields,
581 detailFields: this.model.detailFields,
582 foreignFields: this.model.foreignFields,
583 detailLinkFields: this.model.detailLinkFields,
584 inputType: this.model.inputType,
591 This is for items that will be displayed as table rows.
593 app - MarionetteApplication
594 template - template (See XOSHelper.html)
597 XOSItemView = Marionette.ItemView.extend({
599 className: 'test-tablerow',
601 templateHelpers: function() { return { modelName: this.model.modelName,
602 collectionName: this.model.collectionName,
603 listFields: this.model.listFields,
604 addFields: this.model.addFields,
605 detailFields: this.model.detailFields,
606 foreignFields: this.model.foreignFields,
607 detailLinkFields: this.model.detailLinkFields,
608 inputType: this.model.inputType,
615 app - MarionetteApplication
616 childView - class of ItemView, probably an XOSItemView
617 template - template (see xosHelper.html)
618 collection - collection that holds these objects
619 title - title to display in template
622 XOSListView = FilteredCompositeView.extend({
623 childViewContainer: 'tbody',
626 events: {"click button.btn-xos-add": "addClicked",
627 "click button.btn-xos-refresh": "refreshClicked",
630 _fetchStateChange: function() {
631 if (this.collection.fetching) {
632 $("#xos-list-title-spinner").show();
634 $("#xos-list-title-spinner").hide();
638 addClicked: function(e) {
640 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
643 refreshClicked: function(e) {
645 this.collection.refresh(refreshRelated=true);
648 initialize: function() {
649 this.listenTo(this.collection, 'change', this._renderChildren)
650 this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
651 this.listenTo(this.collection, 'add', function() { console.log("add"); })
652 this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
654 // Because many of the templates use idToName(), we need to
655 // listen to the collections that hold the names for the ids
656 // that we want to display.
657 for (i in this.collection.foreignCollections) {
658 foreignName = this.collection.foreignCollections[i];
659 if (xos[foreignName] == undefined) {
660 console.log("Failed to find xos class " + foreignName);
662 this.listenTo(xos[foreignName], 'change', this._renderChildren);
663 this.listenTo(xos[foreignName], 'sort', this._renderChildren);
667 getAddChildHash: function() {
668 if (this.parentModel) {
669 parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
670 parentFieldName = parentFieldName || "unknown";
672 /*parentFieldName = "unknown";
674 for (fieldName in this.collection.foreignFields) {
675 cname = this.collection.foreignFields[fieldName];
676 if (cname = this.collection.collectionName) {
677 parentFieldName = fieldName;
680 return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
686 templateHelpers: function() {
687 return { title: this.title,
688 addChildHash: this.getAddChildHash(),
689 foreignFields: this.collection.foreignFields,
690 listFields: this.collection.listFields,
691 detailLinkFields: this.collection.detailLinkFields, };
695 idToName = function(id, collectionName, fieldName) {
696 return xos.idToName(id, collectionName, fieldName);
699 /* Constructs lists of <option> html blocks for items in a collection.
701 selectedId = the id of an object that should be selected, if any
702 collectionName = name of collection
703 fieldName = name of field within models of collection that will be displayed
706 idToOptions = function(selectedId, collectionName, fieldName) {
708 for (index in xos[collectionName].models) {
709 linkedObject = xos[collectionName].models[index];
710 linkedId = linkedObject["id"];
711 linkedName = linkedObject.attributes[fieldName];
712 if (linkedId == selectedId) {
713 selected = " selected";
717 result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
722 /* Constructs an html <select> and the <option>s to go with it.
724 variable = variable name to return to form
725 selectedId = the id of an object that should be selected, if any
726 collectionName = name of collection
727 fieldName = name of field within models of collection that will be displayed
730 idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly) {
732 readOnly = " readonly";
736 result = '<select name="' + variable + '"' + readOnly + '>' +
737 idToOptions(selectedId, collectionName, fieldName) +