+ 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;
+ $(this.errorCloseButtonId).unbind().bind('click', function() {
+ $(that.errorBoxId).hide();
+ });
+ }
+ },
+
+ showInformational: function(result) {
+ result["statusclass"] = "inprog";
+ if (this.logTableId) {
+ return this.appendLogWindow(result);
+ } else {
+ return undefined;
+ }
+ },
+
+ appendLogWindow: function(result) {
+ // compute a new logMessageId for this log message
+ logMessageId = "logMessage" + this.logMessageCount;
+ this.logMessageCount = this.logMessageCount + 1;
+ result["logMessageId"] = logMessageId;
+
+ logMessageTemplate=$("#xos-log-template").html();
+ assert(logMessageTemplate != undefined, "logMessageTemplate is undefined");
+ newRow = _.template(logMessageTemplate, result);
+ assert(newRow != undefined, "newRow is undefined");
+
+ if (result["infoMsgId"] != undefined) {
+ // We were passed the logMessageId of an informational message,
+ // and the caller wants us to replace that message with our own.
+ // i.e. replace an informational message with a success or an error.
+ $("#"+result["infoMsgId"]).replaceWith(newRow);
+ } else {
+ // Create a brand new log message rather than replacing one.
+ logTableBody = $(this.logTableId + " tbody");
+ logTableBody.prepend(newRow);
+ }
+
+ if (this.statusMsgId) {
+ $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
+ }
+
+ limitTableRows(this.logTableId, 5);
+
+ return logMessageId;
+ },
+
+ 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);
+ } else if (afterDelete) {
+ afterDelete();
+ }
+ });
+ },
+});
+
+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.deleteClicked.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
+ template - template (See XOSHelper.html)
+*/
+
+XOSDetailView = Marionette.ItemView.extend({
+ tagName: "div",
+
+ viewInitializers: [],
+
+ 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
+ if the user tries to change his slices without saving first.
+ */
+
+ initialize: function() {
+ this.on("saveSuccess", this.onSaveSuccess);
+ this.synchronous = false;
+ },
+
+ onShow: function() {
+ _.each(this.viewInitializers, function(initializer) {
+ initializer();
+ });
+ },
+
+ saveSuccess: function(e) {
+ // always called after a save succeeds
+ },
+
+ afterSave: function(e) {
+ // if this.synchronous, then called after the save succeeds
+ // if !this.synchronous, then called after save is initiated
+ },
+
+ onSaveSuccess: function(e) {
+ this.saveSuccess(e);
+ if (this.synchronous) {
+ 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();
+ if (this.options.noSubmitButton || this.noSubmitButton) {
+ return;
+ }
+ 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 data = Backbone_Syphon.serialize(this);
+ var that = this;
+ var isNew = !this.model.id;
+
+ console.log(data);
+
+ 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 " + this.model.modelName;
+ this.model.addToCollection = this.collection;
+ } else {
+ this.model.addToCollection = undefined;
+ }
+
+ var infoMsgId = this.app.showInformational( {what: "save " + this.model.modelName + " " + this.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);
+ 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];
+ 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) {
+ template = templateFromId("#xos-tabs-template", {tabs: tabs});
+ $("#tabs").html(template(tabs));
+ var that = this;
+
+ _.each(tabs, function(tab) {
+ var regionName = tab["region"];
+ var tabId = '#xos-nav-'+regionName;
+ $(tabId).bind('click', function() { that.tabClick(tabId, regionName); });
+ });
+
+ $("#tabs").show();
+ },
+
+ showLinkedItems: 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) {
+ 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) {
+ 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.options.detailFields || this.detailFields || this.model.detailFields,
+ fieldDisplayNames: this.options.fieldDisplayNames || this.fieldDisplayNames || this.model.fieldDisplayNames || {},
+ foreignFields: this.model.foreignFields,
+ detailLinkFields: this.model.detailLinkFields,
+ inputType: this.model.inputType,
+ model: this.model,
+ detailView: this,
+ choices: this.options.choices || this.choices || this.model.choices || {},
+ helpText: this.options.helpText || this.helpText || this.model.helpText || {},
+ }},
+});
+
+XOSDetailView_sliver = XOSDetailView.extend( {
+ events: $.extend(XOSDetailView.events,
+ {"change #field_deploymentNetwork": "onDeploymentNetworkChange"}
+ ),
+
+ onShow: function() {
+ // Note that this causes the selects to be updated a second time. The
+ // first time was when the template was originally invoked, and the
+ // selects will all have the full unfiltered set of candidates. Then
+ // onShow will fire, and we'll update them with the filtered values.
+ this.onDeploymentNetworkChange();
+ },
+
+ onDeploymentNetworkChange: function(e) {
+ var deploymentID = this.$el.find("#field_deploymentNetwork").val();
+
+ console.log("onDeploymentNetworkChange");
+ console.log(deploymentID);
+
+ filterFunc = function(model) { return (model.attributes.deployment==deploymentID); }
+ newSelect = idToSelect("node",
+ this.model.attributes.node,
+ this.model.foreignFields["node"],
+ "humanReadableName",
+ false,
+ filterFunc);
+ this.$el.find("#field_node").html(newSelect);
+
+ filterFunc = function(model) { for (index in model.attributes.deployments) {
+ item=model.attributes.deployments[index];
+ if (item.toString()==deploymentID.toString()) return true;
+ };
+ return false;
+ }
+ newSelect = idToSelect("flavor",
+ this.model.attributes.flavor,
+ this.model.foreignFields["flavor"],
+ "humanReadableName",
+ false,
+ filterFunc);
+ this.$el.find("#field_flavor").html(newSelect);
+
+ filterFunc = function(model) { for (index in xos.imageDeployments.models) {
+ imageDeployment = xos.imageDeployments.models[index];
+ if ((imageDeployment.attributes.deployment == deploymentID) && (imageDeployment.attributes.image == model.id)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ newSelect = idToSelect("image",
+ this.model.attributes.image,
+ this.model.foreignFields["image"],
+ "humanReadableName",
+ false,
+ filterFunc);
+ this.$el.find("#field_image").html(newSelect);