-function assert(outcome, description) {
- if (!outcome) {
- console.log(description);
- }
-}
-
-function templateFromId(id) {
- return _.template($(id).html());
-}
-
-function firstCharUpper(s) {
- return s.charAt(0).toUpperCase() + s.slice(1);
-}
-
HTMLView = Marionette.ItemView.extend({
render: function() {
this.$el.append(this.options.html);
},
});
+FilteredCompositeView = Marionette.CompositeView.extend( {
+ showCollection: function() {
+ var ChildView;
+ this.collection.each(function(child, index) {
+ if (this.filter && !this.filter(child)) {
+ return;
+ }
+ ChildView = this.getChildView(child);
+ this.addChild(child, ChildView, index);
+ }, this);
+
+ },
+});
+
+XOSRouter = Marionette.AppRouter.extend({
+ initialize: function() {\r
+ this.routeStack=[];\r
+ },\r
+\r
+ onRoute: function(x,y,z) {\r
+ this.routeStack.push(Backbone.history.fragment);\r
+ },\r
+\r
+ prevPage: function() {\r
+ return this.routeStack.slice(-1)[0];
+ },
+
+ showPreviousURL: function() {
+ prevPage = this.prevPage();
+ console.log("showPreviousURL");
+ console.log(this.routeStack);
+ if (prevPage) {
+ this.navigate("#"+prevPage, {trigger: false, replace: true} );
+ }
+ },
+
+ navigate: function(href, options) {
+ if (options.force) {
+ Marionette.AppRouter.prototype.navigate.call(this, "nowhere", {trigger: false, replace: true});
+ }
+ Marionette.AppRouter.prototype.navigate.call(this, href, options);
+ },
+ });\r
+
+
+
XOSApplication = Marionette.Application.extend({
detailBoxId: "#detailBox",
errorBoxId: "#errorBox",
successTemplate: "#xos-success-template",
logMessageCount: 0,
+ confirmDialog: function(view, event, callback) {
+ $("#xos-confirm-dialog").dialog({
+ autoOpen: false,
+ modal: true,
+ buttons : {
+ "Confirm" : function() {
+ $(this).dialog("close");
+ if (event) {
+ view.trigger(event);
+ }
+ if (callback) {
+ callback();
+ }
+ },
+ "Cancel" : function() {
+ $(this).dialog("close");
+ }
+ }
+ });
+ $("#xos-confirm-dialog").dialog("open");
+ },
+
+ popupErrorDialog: function(responseText) {
+ try {
+ parsed_error=$.parseJSON(responseText);
+ width=300;
+ }
+ catch(err) {
+ parsed_error=undefined;
+ width=640; // django stacktraces like wide width
+ }
+ if (parsed_error) {
+ $("#xos-error-dialog").html(templateFromId("#xos-error-response")(parsed_error));
+ } else {
+ $("#xos-error-dialog").html(templateFromId("#xos-error-rawresponse")({responseText: responseText}))
+ }
+
+ $("#xos-error-dialog").dialog({
+ modal: true,
+ width: width,
+ buttons: {
+ Ok: function() { $(this).dialog("close"); }
+ }
+ });
+ },
+
+ hideLinkedItems: function(result) {
+ var index=0;
+ while (index<4) {
+ this["linkedObjs" + (index+1)].empty();
+ index = index + 1;
+ }
+ },
+
+ hideTabs: function() { $("#tabs").hide(); },
+ showTabs: function() { $("#tabs").show(); },
+
+ createListHandler: function(listViewName, collection_name, regionName, title) {
+ var app=this;
+ return function() {
+ listView = new app[listViewName];
+ app[regionName].show(listView);
+ app.hideLinkedItems();
+ $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));
+ $("#detail").show();
+ app.hideTabs();
+
+ listButtons = new XOSListButtonView({linkedView: listView});
+ app["rightButtonPanel"].show(listButtons);
+ }
+ },
+
+ createAddHandler: function(detailName, collection_name, regionName, title) {
+ var app=this;
+ return function() {
+ console.log("addHandler");
+
+ app.hideLinkedItems();
+ app.hideTabs();
+
+ model = new xos[collection_name].model();
+ detailViewClass = app[detailName];
+ detailView = new detailViewClass({model: model, collection:xos[collection_name]});
+ app[regionName].show(detailView);
+
+ detailButtons = new XOSDetailButtonView({linkedView: detailView});
+ app["rightButtonPanel"].show(detailButtons);
+ }
+ },
+
+ createAddChildHandler: function(addChildName, collection_name) {
+ var app=this;
+ return function(parent_modelName, parent_fieldName, parent_id) {
+ app.Router.showPreviousURL();
+ model = new xos[collection_name].model();
+ model.attributes[parent_fieldName] = parent_id;
+ model.readOnlyFields.push(parent_fieldName);
+ detailViewClass = app[addChildName];
+ var detailView = new detailViewClass({model: model, collection:xos[collection_name]});
+ detailView.dialog = $("xos-addchild-dialog");
+ app["addChildDetail"].show(detailView);
+ $("#xos-addchild-dialog").dialog({
+ autoOpen: false,
+ modal: true,
+ width: 640,
+ buttons : {
+ "Save" : function() {
+ var addDialog = this;
+ detailView.synchronous = true;
+ detailView.afterSave = function() { console.log("addChild afterSave"); $(addDialog).dialog("close"); }
+ detailView.save();
+
+ //$(this).dialog("close");
+ },
+ "Cancel" : function() {
+ $(this).dialog("close");
+ }
+ }
+ });
+ $("#xos-addchild-dialog").dialog("open");
+ }
+ },
+
+ createDeleteHandler: function(collection_name) {
+ var app=this;
+ return function(model_id) {
+ console.log("deleteCalled");
+ collection = xos[collection_name];
+ model = collection.get(model_id);
+ assert(model!=undefined, "failed to get model " + model_id + " from collection " + collection_name);
+ app.Router.showPreviousURL();
+ app.deleteDialog(model);
+ }
+ },
+
+ createDetailHandler: function(detailName, collection_name, regionName, title) {
+ var app=this;
+ showModelId = function(model_id) {
+ $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));
+
+ collection = xos[collection_name];
+ model = collection.get(model_id);
+ if (model == undefined) {
+ app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));
+ } else {
+ detailViewClass = app[detailName];
+ detailView = new detailViewClass({model: model});
+ app[regionName].show(detailView);
+ detailView.showLinkedItems();
+
+ detailButtons = new XOSDetailButtonView({linkedView: detailView});
+ app["rightButtonPanel"].show(detailButtons);
+ }
+ }
+ return showModelId;
+ },
+
+ /* error handling callbacks */
+
hideError: function() {
if (this.logWindowId) {
} else {
},
showSuccess: function(result) {
- result["success"] = "success";
+ result["statusclass"] = "success";
if (this.logTableId) {
this.appendLogWindow(result);
} else {
},
showError: function(result) {
- result["success"] = "failure";
+ result["statusclass"] = "failure";
if (this.logTableId) {
this.appendLogWindow(result);
+ this.popupErrorDialog(result.responseText);
} else {
+ // this is really old stuff
$(this.errorBoxId).show();
$(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
var that=this;
},
showInformational: function(result) {
- result["success"] = "information";
+ result["statusclass"] = "inprog";
if (this.logTableId) {
return this.appendLogWindow(result);
} else {
$(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
}
+ limitTableRows(this.logTableId, 5);
+
return logMessageId;
},
- hideLinkedItems: function(result) {
- var index=0;
- while (index<4) {\r
- this["linkedObjs" + (index+1)].empty();\r
- index = index + 1;\r
- }\r
- },\r
-\r
- listViewShower: function(listViewName, collection_name, regionName, title) {\r
- var app=this;\r
- return function() {\r
- app[regionName].show(new app[listViewName]);\r
- app.hideLinkedItems();\r
- $("#contentTitle").html(templateFromId("#xos-title-list")({"title": title}));\r
- $("#detail").show();\r
- $("#xos-listview-button-box").show();\r
- $("#tabs").hide();\r
- $("#xos-detail-button-box").hide();\r
- }\r
- },\r
-\r
- addShower: function(detailName, collection_name, regionName, title) {\r
- var app=this;\r
- return function() {\r
- model = new xos[collection_name].model();\r
- detailViewClass = app[detailName];\r
- detailView = new detailViewClass({model: model});\r
- app[regionName].show(detailView);\r
- $("#xos-detail-button-box").show();\r
- $("#xos-listview-button-box").hide();\r
- }\r
- },\r
-\r
- detailShower: function(detailName, collection_name, regionName, title) {\r
- var app=this;\r
- showModelId = function(model_id) {\r
- showModel = function(model) {\r
- detailViewClass = app[detailName];\r
- detailView = new detailViewClass({model: model});\r
- app[regionName].show(detailView);\r
- detailView.showLinkedItems();\r
- $("#xos-detail-button-box").show();\r
- $("#xos-listview-button-box").hide();\r
- }\r
-\r
- $("#contentTitle").html(templateFromId("#xos-title-detail")({"title": title}));\r
-\r
- collection = xos[collection_name];\r
- model = collection.get(model_id);\r
- if (model == undefined) {\r
- if (!collection.isLoaded) {\r
- // If the model cannot be found, then maybe it's because\r
- // we haven't finished loading the collection yet. So wait for\r
- // the sort event to complete, then try again.\r
- collection.once("sort", function() {\r
- collection = xos[collection_name];\r
- model = collection.get(model_id);\r
- if (model == undefined) {\r
- // We tried. It's not here. Complain to the user.\r
- app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));\r
- } else {\r
- showModel(model);\r
- }\r
- });\r
- } else {\r
- // The collection was loaded, the user must just be asking for something we don't have.\r
- app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));\r
- }\r
- } else {\r
- showModel(model);\r
- }\r
- }\r
- return showModelId;\r
- },\r
+ saveError: function(model, result, xhr, infoMsgId) {
+ console.log("saveError");
+ result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
+ result["infoMsgId"] = infoMsgId;
+ this.showError(result);
+ },
+
+ saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {
+ console.log("saveSuccess");
+ if (model.addToCollection) {
+ console.log("addToCollection");
+ console.log(model.addToCollection);
+ model.addToCollection.add(model);
+ model.addToCollection.sort();
+ model.addToCollection = undefined;
+ }
+ result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
+ result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;
+ result["infoMsgId"] = infoMsgId;
+ this.showSuccess(result);
+ },
+
+ destroyError: function(model, result, xhr, infoMsgId) {
+ result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
+ result["infoMsgId"] = infoMsgId;
+ this.showError(result);
+ },
+
+ destroySuccess: function(model, result, xhr, infoMsgId) {
+ result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};
+ result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;
+ result["infoMsgId"] = infoMsgId;
+ this.showSuccess(result);
+ },
+
+ /* end error handling callbacks */
+
+ destroyModel: function(model) {
+ //console.log("destroyModel"); console.log(model);
+ this.hideError();
+ var infoMsgId = this.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
+ var that = this;
+ model.destroy({error: function(model, result, xhr) { that.destroyError(model,result,xhr,infoMsgId);},
+ success: function(model, result, xhr) { that.destroySuccess(model,result,xhr,infoMsgId);}});
+ },
+
+ deleteDialog: function(model, afterDelete) {
+ var that=this;
+ assert(model!=undefined, "deleteDialog's model is undefined");
+ //console.log("deleteDialog"); console.log(model);
+ this.confirmDialog(null, null, function() {
+ //console.log("deleteConfirm"); console.log(model);
+ modelName = model.modelName;
+ that.destroyModel(model);
+ if (afterDelete=="list") {
+ that.navigate("list", modelName);
+ }
+ });
+ },
});
+XOSButtonView = Marionette.ItemView.extend({
+ events: {"click button.btn-xos-save-continue": "submitContinueClicked",
+ "click button.btn-xos-save-leave": "submitLeaveClicked",
+ "click button.btn-xos-save-another": "submitAddAnotherClicked",
+ "click button.btn-xos-delete": "deleteClicked",
+ "click button.btn-xos-add": "addClicked",
+ "click button.btn-xos-refresh": "refreshClicked",
+ },
+
+ submitLeaveClicked: function(e) {
+ this.options.linkedView.submitLeaveClicked.call(this.options.linkedView, e);
+ },
+
+ submitContinueClicked: function(e) {
+ this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
+ },
+
+ submitAddAnotherClicked: function(e) {
+ this.options.linkedView.submitAddAnotherClicked.call(this.options.linkedView, e);
+ },
+
+ submitDeleteClicked: function(e) {
+ this.options.linkedView.submitDeleteClicked.call(this.options.linkedView, e);
+ },
+
+ addClicked: function(e) {
+ this.options.linkedView.addClicked.call(this.options.linkedView, e);
+ },
+
+ refreshClicked: function(e) {
+ this.options.linkedView.refreshClicked.call(this.options.linkedView, e);
+ },
+ });
+
+XOSDetailButtonView = XOSButtonView.extend({ template: "#xos-savebuttons-template" });
+XOSListButtonView = XOSButtonView.extend({ template: "#xos-listbuttons-template" });
+
/* XOSDetailView
extend with:
app - MarionetteApplication
events: {"click button.btn-xos-save-continue": "submitContinueClicked",
"click button.btn-xos-save-leave": "submitLeaveClicked",
"click button.btn-xos-save-another": "submitAddAnotherClicked",
+ "click button.btn-xos-delete": "deleteClicked",
"change input": "inputChanged"},
/* inputChanged is watching the onChange events of the input controls. We
- do this to track when this view is 'dirty', so we can throw up a warning\r
- if the user tries to change his slices without saving first.\r
- */\r
-\r
- inputChanged: function(e) {\r
- this.dirty = true;\r
- },\r
-\r
- saveError: function(model, result, xhr, infoMsgId) {\r
- result["what"] = "save " + model.__proto__.modelName;\r
- result["infoMsgId"] = infoMsgId;\r
- this.app.showError(result);\r
- },\r
-\r
- saveSuccess: function(model, result, xhr, infoMsgId) {\r
- result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};\r
- result["what"] = "save " + model.__proto__.modelName;\r
- result["infoMsgId"] = infoMsgId;\r
- this.app.showSuccess(result);\r
+ do this to track when this view is 'dirty', so we can throw up a warning
+ if the user tries to change his slices without saving first.
+ */
+
+ initialize: function() {
+ this.on("saveSuccess", this.onAfterSave);
+ this.synchronous = false;
+ },
+
+ afterSave: function(e) {
+ },
+
+ onAfterSave: function(e) {
+ this.afterSave(e);
+ },
+
+ inputChanged: function(e) {
+ this.dirty = true;
},
submitContinueClicked: function(e) {
console.log("saveContinue");
e.preventDefault();
+ this.afterSave = function() { };
this.save();
},
submitLeaveClicked: function(e) {
console.log("saveLeave");
e.preventDefault();
+ var that=this;
+ this.afterSave = function() {
+ that.app.navigate("list", that.model.modelName);
+ }
this.save();
},
submitAddAnotherClicked: function(e) {
console.log("saveAnother");
+ console.log(this);
e.preventDefault();
+ var that=this;
+ this.afterSave = function() {
+ console.log("addAnother afterSave");
+ that.app.navigate("add", that.model.modelName);
+ }
this.save();
},
save: function() {
this.app.hideError();
- var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );\r
- var data = Backbone.Syphon.serialize(this);\r
- var that = this;\r
- this.model.save(data, {error: function(model, result, xhr) { that.saveError(model,result,xhr,infoMsgId);},\r
- success: function(model, result, xhr) { that.saveSuccess(model,result,xhr,infoMsgId);}});\r
- this.dirty = false;\r
+ var data = Backbone.Syphon.serialize(this);
+ var that = this;
+ var isNew = !this.model.id;
+
+ this.$el.find(".help-inline").remove();
+
+ /* although model.validate() is called automatically by
+ model.save, we call it ourselves, so we can throw up our
+ validation error before creating the infoMsg in the log
+ */
+ errors = this.model.xosValidate(data);
+ if (errors) {
+ this.onFormDataInvalid(errors);
+ return;
+ }
+
+ if (isNew) {
+ this.model.attributes.humanReadableName = "new " + model.modelName;
+ this.model.addToCollection = this.collection;
+ } else {
+ this.model.addToCollection = undefined;
+ }
+
+ var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
+
+ this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},
+ success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);
+ if (that.synchronous) {
+ that.trigger("saveSuccess");
+ }
+ }});
+ this.dirty = false;
+
+ if (!this.synchronous) {
+ this.afterSave();
+ }
+ },
+
+ deleteClicked: function(e) {
+ e.preventDefault();
+ this.app.deleteDialog(this.model, "list");
},
tabClick: function(tabId, regionName) {
- region = this.app[regionName];\r
- if (this.currentTabRegion != undefined) {\r
- this.currentTabRegion.$el.hide();\r
- }\r
- if (this.currentTabId != undefined) {\r
- $(this.currentTabId).removeClass('active');\r
- }\r
- this.currentTabRegion = region;\r
- this.currentTabRegion.$el.show();\r
-\r
- this.currentTabId = tabId;\r
- $(tabId).addClass('active');\r
+ region = this.app[regionName];
+ if (this.currentTabRegion != undefined) {
+ this.currentTabRegion.$el.hide();
+ }
+ if (this.currentTabId != undefined) {
+ $(this.currentTabId).removeClass('active');
+ }
+ this.currentTabRegion = region;
+ this.currentTabRegion.$el.show();
+
+ this.currentTabId = tabId;
+ $(tabId).addClass('active');
},
showTabs: function(tabs) {
tabs.push({name: "details", region: "detail"});
+ makeFilter = function(relatedField, relatedId) {
+ return function(model) { return model.attributes[relatedField] == relatedId; }
+ };
+
var index=0;
- for (relatedName in this.model.collection.relatedCollections) {\r
- relatedField = this.model.collection.relatedCollections[relatedName];\r
- regionName = "linkedObjs" + (index+1);\r
-\r
- relatedListViewClassName = relatedName + "ListView";\r
- assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");\r
- relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName].filterBy(relatedField,this.model.id)});\r
- this.app[regionName].show(new relatedListViewClass());\r
- if (this.app.hideTabsByDefault) {\r
- this.app[regionName].$el.hide();\r
- }\r
- tabs.push({name: relatedName, region: regionName});\r
- index = index + 1;\r
- }\r
-\r
- while (index<4) {\r
- this.app["linkedObjs" + (index+1)].empty();\r
- index = index + 1;\r
- }\r
-\r
- this.showTabs(tabs);\r
- this.tabClick('#xos-nav-detail', 'detail');\r
- },\r
-\r
-});\r
+ for (relatedName in this.model.collection.relatedCollections) {
+ var relatedField = this.model.collection.relatedCollections[relatedName];
+ var relatedId = this.model.id;
+ regionName = "linkedObjs" + (index+1);
+
+ relatedListViewClassName = relatedName + "ListView";
+ assert(this.app[relatedListViewClassName] != undefined, relatedListViewClassName + " not found");
+ relatedListViewClass = this.app[relatedListViewClassName].extend({collection: xos[relatedName],
+ filter: makeFilter(relatedField, relatedId),
+ parentModel: this.model});
+ this.app[regionName].show(new relatedListViewClass());
+ if (this.app.hideTabsByDefault) {
+ this.app[regionName].$el.hide();
+ }
+ tabs.push({name: relatedName, region: regionName});
+ index = index + 1;
+ }
+
+ while (index<4) {
+ this.app["linkedObjs" + (index+1)].empty();
+ index = index + 1;
+ }
+
+ this.showTabs(tabs);
+ this.tabClick('#xos-nav-detail', 'detail');
+ },
+
+ onFormDataInvalid: function(errors) {
+ var self=this;
+ var markErrors = function(value, key) {
+ console.log("name='" + key + "'");
+ var $inputElement = self.$el.find("[name='" + key + "']");
+ var $inputContainer = $inputElement.parent();
+ //$inputContainer.find(".help-inline").remove();
+ var $errorEl = $("<span>", {class: "help-inline error", text: value});
+ $inputContainer.append($errorEl).addClass("error");
+ }
+ _.each(errors, markErrors);
+ },
+
+ templateHelpers: function() { return { modelName: this.model.modelName,
+ collectionName: this.model.collectionName,
+ addFields: this.model.addFields,
+ listFields: this.model.listFields,
+ detailFields: this.model.detailFields,
+ foreignFields: this.model.foreignFields,
+ detailLinkFields: this.model.detailLinkFields,
+ inputType: this.model.inputType,
+ model: this.model,
+ }},
+
+ applyConstraints: function() {
+ for (constraint in this.model.constraints) {
+ // constraint syntax: "operator,destField.destSubField,srcField.srcSubField"
+ // i.e. equal,node.deployment,deploymentNetwork.id
+ parts = constraints.split(",");
+ operator = parts[0];
+ parts1 = parts[1].split(".");
+ destField = parts1[0];
+ destSubField = parts1[1];
+ parts2 = parts[2].split(".");
+ srcField = parts2[0];
+ srcID = this.$el.find(srcModel).val();
+ if (operator == "equal"):
+ filterMaker = function makeFilter(destSubField,srcID) { return function(linkedObj) { return (linkedObj.attributes[destSubField] == srcID); } };
+ filterFunc = filterMaker(destSubField, srcID);
+ else:
+ continue;
+
+ newSelect = idToSelect(destField,
+ model.attributes[destField],
+ model.foreignFields[destField],
+ "humanReadableName",
+ false,
+ filterFunc);
+
+ this.$el.find(destFieldName).html(newSelect);
+ },
+
+});
/* XOSItemView
This is for items that will be displayed as table rows.
extend with:
app - MarionetteApplication
template - template (See XOSHelper.html)
- detailClass - class of detail view, probably an XOSDetailView
*/
XOSItemView = Marionette.ItemView.extend({
tagName: 'tr',
className: 'test-tablerow',
- events: {"click": "changeItem"},
-
- changeItem: function(e) {\r
- this.app.hideError();\r
- e.preventDefault();\r
- e.stopPropagation();\r
-\r
- this.app.navigateToModel(this.app, this.detailClass, this.detailNavLink, this.model);\r
- },\r
+ templateHelpers: function() { return { modelName: this.model.modelName,
+ collectionName: this.model.collectionName,
+ listFields: this.model.listFields,
+ addFields: this.model.addFields,
+ detailFields: this.model.detailFields,
+ foreignFields: this.model.foreignFields,
+ detailLinkFields: this.model.detailLinkFields,
+ inputType: this.model.inputType,
+ model: this.model,
+ }},
});
/* XOSListView:
title - title to display in template
*/
-XOSListView = Marionette.CompositeView.extend({
- childViewContainer: 'tbody',\r
-\r
- events: {"click button.btn-xos-add": "addClicked",\r
+XOSListView = FilteredCompositeView.extend({
+ childViewContainer: 'tbody',
+ parentModel: null,
+
+ events: {"click button.btn-xos-add": "addClicked",
+ "click button.btn-xos-refresh": "refreshClicked",
},
+ _fetchStateChange: function() {
+ if (this.collection.fetching) {
+ $("#xos-list-title-spinner").show();
+ } else {
+ $("#xos-list-title-spinner").hide();
+ }
+ },
+
addClicked: function(e) {
- console.log("add");
e.preventDefault();
this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
},
-\r
- initialize: function() {\r
+
+ refreshClicked: function(e) {
+ e.preventDefault();
+ this.collection.refresh(refreshRelated=true);
+ },
+
+ initialize: function() {
this.listenTo(this.collection, 'change', this._renderChildren)
+ this.listenTo(this.collection, 'sort', function() { console.log("sort"); })
+ this.listenTo(this.collection, 'add', function() { console.log("add"); })
+ this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
// Because many of the templates use idToName(), we need to
// listen to the collections that hold the names for the ids
}
},
+ getAddChildHash: function() {
+ if (this.parentModel) {
+ parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
+ parentFieldName = parentFieldName || "unknown";
+
+ /*parentFieldName = "unknown";
+
+ for (fieldName in this.collection.foreignFields) {
+ cname = this.collection.foreignFields[fieldName];
+ if (cname = this.collection.collectionName) {
+ parentFieldName = fieldName;
+ }
+ }*/
+ return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
+ } else {
+ return null;
+ }
+ },
+
templateHelpers: function() {
- return { title: this.title };
- },\r
+ return { title: this.title,
+ addChildHash: this.getAddChildHash(),
+ foreignFields: this.collection.foreignFields,
+ listFields: this.collection.listFields,
+ detailLinkFields: this.collection.detailLinkFields, };
+ },
});
-/* Give an id, the name of a collection, and the name of a field for models
- within that collection, lookup the id and return the value of the field.
-*/
+XOSDataTableView = Marionette.View.extend( {
+ el: '<div style="overflow: hidden">' +
+ '<h3 class="xos-list-title title_placeholder"></h3>' +
+ '<div class="header_placeholder"></div>' +
+ '<table></table>' +
+ '<div class="footer_placeholder"></div>' +
+ '</div>',
+
+ filter: undefined,
+
+ events: {"click button.btn-xos-add": "addClicked",
+ "click button.btn-xos-refresh": "refreshClicked",
+ },
+
+ _fetchStateChange: function() {
+ if (this.collection.fetching) {
+ $("#xos-list-title-spinner").show();
+ } else {
+ $("#xos-list-title-spinner").hide();
+ }
+ },
+
+ addClicked: function(e) {
+ e.preventDefault();
+ this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
+ },
+
+ refreshClicked: function(e) {
+ e.preventDefault();
+ this.collection.refresh(refreshRelated=true);
+ },
+
+
+ initialize: function() {
+ $(this.el).find(".footer_placeholder").html( xosListFooterTemplate({addChildHash: this.getAddChildHash()}) );
+ $(this.el).find(".header_placeholder").html( xosListHeaderTemplate() );
+
+ this.listenTo(this.collection, 'fetchStateChange', this._fetchStateChange);
+ },
+
+ render: function() {
+ var view = this;
+
+ view.columnsByIndex = [];
+ view.columnsByFieldName = {};
+ _.each(this.collection.listFields, function(fieldName) {
+ mRender = undefined;
+ mSearchText = undefined;
+ sTitle = fieldNameToHumanReadable(fieldName);
+ bSortable = true;
+ if (fieldName=="backend_status") {
+ mRender = function(x,y,z) { return xosBackendStatusIconTemplate(z); };
+ sTitle = "";
+ bSortable = false;
+ } else if (fieldName in view.collection.foreignFields) {
+ var foreignCollection = view.collection.foreignFields[fieldName];
+ mSearchText = function(x) { return idToName(x, foreignCollection, "humanReadableName"); };
+ }
+ if ($.inArray(fieldName, view.collection.detailLinkFields)>=0) {
+ var collectionName = view.collection.collectionName;
+ mRender = function(x,y,z) { return '<a href="#' + collectionName + '/' + z.id + '">' + x + '</a>'; };
+ }
+ thisColumn = {sTitle: sTitle, bSortable: bSortable, mData: fieldName, mRender: mRender, mSearchText: mSearchText};
+ view.columnsByIndex.push( thisColumn );
+ view.columnsByFieldName[fieldName] = thisColumn;
+ });
+
+ deleteColumn = {sTitle: "", bSortable: false, mRender: function(x,y,z) { return xosDeleteButtonTemplate({modelName: view.collection.modelName, id: z.id}); }, mData: function() { return "delete"; }};
+ view.columnsByIndex.push(deleteColumn);
+ view.columnsByFieldName["delete"] = deleteColumn;
+
+ oTable = $(this.el).find("table").dataTable( {
+ "bJQueryUI": true,
+ "bStateSave": true,
+ "bServerSide": true,
+ "aoColumns": view.columnsByIndex,
+
+ fnServerData: function(sSource, aoData, fnCallback, settings) {
+ var compareColumns = function(sortCols, sortDirs, a, b) {
+ a = a[sortCols[0]];
+ b = b[sortCols[0]];
+ result = (a==b) ? 0 : ((a<b) ? -1 : 1);
+ if (sortDirs[0] == "desc") {
+ result = -result;
+ }
+ return result;
+ };
+
+ var searchMatch = function(row, sSearch) {
+ for (fieldName in row) {
+ if (fieldName in view.columnsByFieldName) {
+ try {
+ value = row[fieldName].toString();
+ } catch(e) {
+ continue;
+ }
+ if (value.indexOf(sSearch) >= 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ //console.log(aoData);
+\r
+ // function used to populate the DataTable with the current\r
+ // content of the collection\r
+ var populateTable = function()\r
+ {\r
+ console.log("populatetable!");\r
+\r
+ // clear out old row views\r
+ rows = [];\r
+\r
+ sSearch = null;\r
+ iDisplayStart = 0;\r
+ iDisplayLength = 1000;\r
+ sortDirs = [];\r
+ sortCols = [];\r
+ _.each(aoData, function(param) {\r
+ if (param.name == "sSortDir_0") {\r
+ sortDirs = [param.value];\r
+ } else if (param.name == "iSortCol_0") {\r
+ sortCols = [view.columnsByIndex[param.value].mData];\r
+ } else if (param.name == "iDisplayStart") {\r
+ iDisplayStart = param.value;\r
+ } else if (param.name == "iDisplayLength") {\r
+ iDisplayLength = param.value;\r
+ } else if (param.name == "sSearch") {\r
+ sSearch = param.value;\r
+ }\r
+ });\r
+\r
+ aaData = view.collection.toJSON();\r
+\r
+ // apply backbone filtering on the models\r
+ if (view.filter) {\r
+ aaData = aaData.filter( function(row) { model = {}; model.attributes = row; return view.filter(model); } );\r
+ }\r
+\r
+ var totalSize = aaData.length;\r
+\r
+ // turn the ForeignKey fields into human readable things\r
+ for (rowIndex in aaData) {\r
+ row = aaData[rowIndex];\r
+ for (fieldName in row) {\r
+ if (fieldName in view.columnsByFieldName) {\r
+ mSearchText = view.columnsByFieldName[fieldName].mSearchText;\r
+ if (mSearchText) {\r
+ row[fieldName] = mSearchText(row[fieldName]);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // apply datatables search\r
+ if (sSearch) {\r
+ aaData = aaData.filter( function(row) { return searchMatch(row, sSearch); });\r
+ }\r
+\r
+ var filteredSize = aaData.length;\r
+\r
+ // apply datatables sort\r
+ aaData.sort(function(a,b) { return compareColumns(sortCols, sortDirs, a, b); });\r
+\r
+ // slice it for pagination\r
+ aaData = aaData.slice(iDisplayStart, iDisplayStart+iDisplayLength);\r
+\r
+ return fnCallback({iTotalRecords: totalSize,\r
+ iTotalDisplayRecords: filteredSize,\r
+ aaData: aaData});\r
+ };\r
+\r
+ aoData.shift(); // ignore sEcho
+ populateTable();
+
+ view.listenTo(view.collection, 'change', populateTable);
+ view.listenTo(view.collection, 'add', populateTable);
+ view.listenTo(view.collection, 'remove', populateTable);
+ },
+ } );
+
+ return this;
+ },
+
+ getAddChildHash: function() {
+ if (this.parentModel) {
+ parentFieldName = this.parentModel.relatedCollections[this.collection.collectionName];
+ parentFieldName = parentFieldName || "unknown";
+
+ /*parentFieldName = "unknown";
+
+ for (fieldName in this.collection.foreignFields) {
+ cname = this.collection.foreignFields[fieldName];
+ if (cname = this.collection.collectionName) {
+ parentFieldName = fieldName;
+ }
+ }*/
+ return "#addChild" + firstCharUpper(this.collection.modelName) + "/" + this.parentModel.modelName + "/" + parentFieldName + "/" + this.parentModel.id; // modelName, fieldName, id
+ } else {
+ return null;
+ }
+ },
+
+});
idToName = function(id, collectionName, fieldName) {
- linkedObject = xos[collectionName].get(id);
- if (linkedObject == undefined) {
- return "#" + id;
- } else {
- return linkedObject.attributes[fieldName];
- }
+ return xos.idToName(id, collectionName, fieldName);
};
/* Constructs lists of <option> html blocks for items in a collection.
fieldName = name of field within models of collection that will be displayed
*/
-idToOptions = function(selectedId, collectionName, fieldName) {
+idToOptions = function(selectedId, collectionName, fieldName, filterFunc) {
result=""
for (index in xos[collectionName].models) {
linkedObject = xos[collectionName].models[index];
} else {
selected = "";
}
+ if ((filterFunc) && (!filterFunc(linkedObject)) {
+ continue;
+ }
result = result + '<option value="' + linkedId + '"' + selected + '>' + linkedName + '</option>';
}
return result;
fieldName = name of field within models of collection that will be displayed
*/
-idToSelect = function(variable, selectedId, collectionName, fieldName) {
- result = '<select name="' + variable + '">' +
- idToOptions(selectedId, collectionName, fieldName) +
+idToSelect = function(variable, selectedId, collectionName, fieldName, readOnly. filterFunc) {
+ if (readOnly) {
+ readOnly = " readonly";
+ } else {
+ readOnly = "";
+ }
+ result = '<select name="' + variable + '"' + readOnly + '>' +
+ idToOptions(selectedId, collectionName, fieldName, filterFunc) +
'</select>';
return result;
}