move some code around
[plstackapi.git] / planetstack / core / xoslib / static / js / xoslib / xosHelper.js
index 89d0d3b..d9e23a6 100644 (file)
@@ -28,7 +28,109 @@ XOSApplication = Marionette.Application.extend({
     successTemplate: "#xos-success-template",
     logMessageCount: 0,
 
-    hideError: function() {
+    confirmDialog: function(view, event, callback) {
+        $("#xos-confirm-dialog").dialog({
+           autoOpen: false,\r
+           modal: true,\r
+           buttons : {\r
+                "Confirm" : function() {\r
+                  $(this).dialog("close");\r
+                  if (event) {\r
+                      view.trigger(event);\r
+                  }\r
+                  if (callback) {\r
+                      callback();\r
+                  }\r
+                },\r
+                "Cancel" : function() {\r
+                  $(this).dialog("close");\r
+                }\r
+              }\r
+            });
+        $("#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) {\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, collection:xos[collection_name]});\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
+            $("#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
+                app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));\r
+            } else {\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
+        return showModelId;\r
+    },\r
+\r
+    /* error handling callbacks */\r
+\r
+    hideError: function() {\r
         if (this.logWindowId) {
         } else {
             $(this.errorBoxId).hide();
@@ -48,13 +150,15 @@ XOSApplication = Marionette.Application.extend({
                  $(that.successBoxId).hide();
              });
          }
-    },
-
-    showError: function(result) {
+    },\r
+\r
+    showError: function(result) {\r
          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;
@@ -62,9 +166,9 @@ XOSApplication = Marionette.Application.extend({
                  $(that.errorBoxId).hide();
              });
          }
-    },
-
-    showInformational: function(result) {
+    },\r
+\r
+    showInformational: function(result) {\r
          result["statusclass"] = "inprog";
          if (this.logTableId) {
              return this.appendLogWindow(result);
@@ -99,64 +203,66 @@ XOSApplication = Marionette.Application.extend({
             $(this.statusMsgId).html( templateFromId("#xos-status-template")(result) );
         }
 
-        return logMessageId;
-    },
+        limitTableRows(this.logTableId, 5);
 
-    hideLinkedItems: function(result) {
-        var index=0;
-        while (index<4) {\r
-            this["linkedObjs" + (index+1)].empty();\r
-            index = index + 1;\r
-        }\r
+        return logMessageId;
     },\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
+    saveError: function(model, result, xhr, infoMsgId) {\r
+        console.log("saveError");\r
+        result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;\r
+        result["infoMsgId"] = infoMsgId;\r
+        this.showError(result);\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
+    saveSuccess: function(model, result, xhr, infoMsgId, addToCollection) {\r
+        console.log("saveSuccess");\r
+        if (addToCollection) {\r
+            addToCollection.add(model);\r
+            addToCollection.sort();\r
         }\r
+        result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};\r
+        result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;\r
+        result["infoMsgId"] = infoMsgId;\r
+        this.showSuccess(result);\r
+    },
+
+    destroyError: function(model, result, xhr, infoMsgId) {
+        result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;\r
+        result["infoMsgId"] = infoMsgId;\r
+        this.showError(result);\r
     },\r
 \r
-    detailShower: function(detailName, collection_name, regionName, title) {\r
-        var app=this;\r
-        showModelId = function(model_id) {\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
-                app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));\r
-            } else {\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
-        return showModelId;\r
+    destroySuccess: function(model, result, xhr, infoMsgId) {\r
+        result = {status: xhr.xhr.status, statusText: xhr.xhr.statusText};\r
+        result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;\r
+        result["infoMsgId"] = infoMsgId;\r
+        this.showSuccess(result);\r
     },\r
-});
+\r
+    /* end error handling callbacks */\r
+\r
+    destroyModel: function(model) {\r
+         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, navToListAfterDelete) {
+        var that=this;
+        this.confirmDialog(this, callback=function() {
+            modelName = model.modelName;
+            that.destroyModel(model);
+            if (navToListAfterDelete) {
+                that.navigate("list", modelName);
+            }
 
+        });
+    },
+});
+\r
 /* XOSDetailView
       extend with:
          app - MarionetteApplication
@@ -169,8 +275,13 @@ XOSDetailView = 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",
                      "change input": "inputChanged"},
 
+            initialize: function() {
+                this.on('deleteConfirmed', this.deleteConfirmed);
+            },
+
             /* 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
@@ -180,20 +291,7 @@ XOSDetailView = Marionette.ItemView.extend({
                 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
-            },
-
-            submitContinueClicked: function(e) {
+            submitContinueClicked: function(e) {\r
                 console.log("saveContinue");
                 e.preventDefault();
                 this.save();
@@ -203,26 +301,67 @@ XOSDetailView = Marionette.ItemView.extend({
                 console.log("saveLeave");
                 e.preventDefault();
                 this.save();
-                this.app.Router.navigate(this.listNavLink, {trigger: true});
-                console.log("route to " + this.listNavLink);
+                this.app.navigate("list", this.model.modelName);
             },
 
             submitAddAnotherClicked: function(e) {
                 console.log("saveAnother");
                 e.preventDefault();
                 this.save();
+                this.app.navigate("add", this.model.modelName);
             },
 
             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
+                var isNew = !this.model.id;\r
+\r
+                this.$el.find(".help-inline").remove();\r
+\r
+                /* although model.validate() is called automatically by\r
+                   model.save, we call it ourselves, so we can throw up our\r
+                   validation error before creating the infoMsg in the log\r
+                */\r
+                errors =  this.model.xosValidate(data);\r
+                if (errors) {\r
+                    this.onFormDataInvalid(errors);\r
+                    return;\r
+                }\r
+\r
+                if (isNew) {\r
+                    this.model.attributes.humanReadableName = "new " + model.modelName;\r
+                    this.model.addToCollection = this.collection;\r
+                } else {\r
+                    this.model.addToCollection = undefined;\r
+                }\r
+\r
+                var infoMsgId = this.app.showInformational( {what: "save " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );\r
+\r
+                this.model.save(data, {error: function(model, result, xhr) { that.app.saveError(model,result,xhr,infoMsgId);},\r
+                                       success: function(model, result, xhr) { that.app.saveSuccess(model,result,xhr,infoMsgId);}});\r
                 this.dirty = false;\r
             },
 
+            destroyModel: function() {
+                 this.app.hideError();
+                 var infoMsgId = this.app.showInformational( {what: "destroy " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );
+                 var that = this;
+                 this.model.destroy({error: function(model, result, xhr) { that.app.destroyError(model,result,xhr,infoMsgId);},
+                                     success: function(model, result, xhr) { that.app.destroySuccess(model,result,xhr,infoMsgId);}});
+            },
+
+             deleteClicked: function(e) {
+                 e.preventDefault();
+\r                 this.app.confirmDialog(this, "deleteConfirmed");
+\r             },
+\r
+\r             deleteConfirmed: function() {
+\r                 modelName = this.model.modelName;
+\r                 this.destroyModel();
+\r                 this.app.navigate("list", modelName);
+\r             },
+\r
             tabClick: function(tabId, regionName) {
                     region = this.app[regionName];\r
                     if (this.currentTabRegion != undefined) {\r
@@ -282,6 +421,19 @@ XOSDetailView = Marionette.ItemView.extend({
                     this.tabClick('#xos-nav-detail', 'detail');\r
               },\r
 \r
+            onFormDataInvalid: function(errors) {\r
+                var self=this;\r
+                var markErrors = function(value, key) {\r
+                    console.log("name='" + key + "'");\r
+                    var $inputElement = self.$el.find("[name='" + key + "']");\r
+                    var $inputContainer = $inputElement.parent();\r
+                    //$inputContainer.find(".help-inline").remove();\r
+                    var $errorEl = $("<span>", {class: "help-inline error", text: value});\r
+                    $inputContainer.append($errorEl).addClass("error");\r
+                }\r
+                _.each(errors, markErrors);\r
+            },\r
+\r
 });\r
 
 /* XOSItemView
@@ -296,15 +448,9 @@ 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,
+                                         }},
 });
 
 /* XOSListView:
@@ -320,16 +466,30 @@ XOSListView = Marionette.CompositeView.extend({
              childViewContainer: 'tbody',\r
 \r
              events: {"click button.btn-xos-add": "addClicked",\r
-                     },
-
+                      "click button.btn-xos-refresh": "refreshClicked",\r
+                     },\r
+\r
+             _fetchStateChange: function() {\r
+                 if (this.collection.fetching) {\r
+                    $("#xos-list-title-spinner").show();\r
+                 } else {\r
+                    $("#xos-list-title-spinner").hide();\r
+                 }\r
+             },\r
+\r
              addClicked: function(e) {
-                console.log("add");
                 e.preventDefault();
                 this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
              },
 \r
-             initialize: function() {\r
-                 this.listenTo(this.collection, 'change', this._renderChildren)
+\r             refreshClicked: function(e) {
+\r                 e.preventDefault();
+\r                 this.collection.refresh(refreshRelated=true);
+\r             },
+\r
+\r             initialize: function() {
+\r                 this.listenTo(this.collection, 'change', this._renderChildren)
+                 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