humanReadableNames in logPanel
[plstackapi.git] / planetstack / core / xoslib / static / js / xoslib / xosHelper.js
index 0ba1c3d..dc11670 100644 (file)
@@ -4,6 +4,14 @@ function assert(outcome, 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);
@@ -20,6 +28,23 @@ XOSApplication = Marionette.Application.extend({
     successTemplate: "#xos-success-template",
     logMessageCount: 0,
 
+    confirmDialog: function(view, event) {
+        $("#xos-confirm-dialog").dialog({
+           autoOpen: false,\r
+           modal: true,\r
+           buttons : {\r
+                "Confirm" : function() {\r
+                  $(this).dialog("close");\r
+                  view.trigger(event);\r
+                },\r
+                "Cancel" : function() {\r
+                  $(this).dialog("close");\r
+                }\r
+              }\r
+            });
+        $("#xos-confirm-dialog").dialog("open");
+    },
+
     hideError: function() {
         if (this.logWindowId) {
         } else {
@@ -29,34 +54,36 @@ XOSApplication = Marionette.Application.extend({
     },
 
     showSuccess: function(result) {
+         result["statusclass"] = "success";
          if (this.logTableId) {
-             result["success"] = "success";
              this.appendLogWindow(result);
          } else {
              $(this.successBoxId).show();
              $(this.successBoxId).html(_.template($(this.successTemplate).html())(result));
+             var that=this;
              $(this.successCloseButtonId).unbind().bind('click', function() {
-                 $(this.successBoxId).hide();
+                 $(that.successBoxId).hide();
              });
          }
     },
 
     showError: function(result) {
+         result["statusclass"] = "failure";
          if (this.logTableId) {
-             result["success"] = "failure";
              this.appendLogWindow(result);
          } else {
              $(this.errorBoxId).show();
              $(this.errorBoxId).html(_.template($(this.errorTemplate).html())(result));
+             var that=this;
              $(this.errorCloseButtonId).unbind().bind('click', function() {
-                 $(this.errorBoxId).hide();
+                 $(that.errorBoxId).hide();
              });
          }
     },
 
     showInformational: function(result) {
+         result["statusclass"] = "inprog";
          if (this.logTableId) {
-             result["success"] = "information";
              return this.appendLogWindow(result);
          } else {
              return undefined;
@@ -78,67 +105,71 @@ XOSApplication = Marionette.Application.extend({
             // 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.
-            console.log(result["infoMsgId"]);
-            console.log($("."+result["infoMsgId"]));
             $("#"+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;
     },
 
     hideLinkedItems: function(result) {
-        index=0;
+        var index=0;
         while (index<4) {\r
             this["linkedObjs" + (index+1)].empty();\r
             index = index + 1;\r
         }\r
     },\r
 \r
-    listViewShower: function(listViewName, regionName) {\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
-    detailShower: function(detailName, collection_name, regionName) {\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
-            showModel = function(model) {\r
-                                console.log(app);\r
-                detailViewClass = app[detailName];\r
-                detailView = new detailViewClass({model: model});\r
-                app[regionName].show(detailView);\r
-                detailView.showLinkedItems();\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
+                app[regionName].show(new HTMLView({html: "failed to load object " + model_id + " from collection " + collection_name}));\r
             } else {\r
-                showModel(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
         return showModelId;\r
@@ -154,11 +185,15 @@ XOSApplication = Marionette.Application.extend({
 XOSDetailView = Marionette.ItemView.extend({
             tagName: "div",
 
-            events: {"click button.js-submit": "submitClicked",
+            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"},
 
-            events: {"click button.js-submit": "submitClicked",
-                     "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
@@ -170,40 +205,133 @@ XOSDetailView = Marionette.ItemView.extend({
             },\r
 \r
             saveError: function(model, result, xhr, infoMsgId) {\r
-                result["what"] = "save " + model.__proto__.modelName;\r
+                result["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;\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["what"] = "save " + model.modelName + " " + model.attributes.humanReadableName;\r
                 result["infoMsgId"] = infoMsgId;\r
                 this.app.showSuccess(result);\r
             },
 
-            submitClicked: function(e) {
-                this.app.hideError();\r
-                e.preventDefault();\r
-                var infoMsgId = this.app.showInformational( {what: "save " + this.model.__proto__.modelName, status: "", statusText: "in progress..."} );\r
+            destroyError: function(model, result, xhr, infoMsgId) {
+                result["what"] = "destroy " + model.modelName + " " + model.attributes.humanReadableName;\r
+                result["infoMsgId"] = infoMsgId;\r
+                this.app.showError(result);\r
+            },\r
+\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.app.showSuccess(result);\r
+            },
+
+            submitContinueClicked: function(e) {
+                console.log("saveContinue");
+                e.preventDefault();
+                this.save();
+            },
+
+            submitLeaveClicked: function(e) {
+                console.log("saveLeave");
+                e.preventDefault();
+                this.save();
+                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 " + model.modelName + " " + model.attributes.humanReadableName, status: "", statusText: "in progress..."} );\r
                 var data = Backbone.Syphon.serialize(this);\r
                 var that = this;\r
+                var isNew = !this.model.id;\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
+                if (isNew) {\r
+                    console.log(this.model);\r
+                    this.collection.add(this.model);\r
+                    this.collection.sort();\r
+                }\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.destroyError(model,result,xhr,infoMsgId);},
+                                     success: function(model, result, xhr) { that.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
+                        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
+            },
+
+            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() {
-                    index=0;\r
+                    tabs=[];
+
+                    tabs.push({name: "details", region: "detail"});
+
+                    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
-                        if (this.app[relatedListViewClassName] == undefined) {\r
-                            console.log("warning: " + relatedListViewClassName + " not found");\r
-                        }\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["linkedObjs" + (index+1)].show(new relatedListViewClass());\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
@@ -211,8 +339,12 @@ XOSDetailView = Marionette.ItemView.extend({
                         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
 
 /* XOSItemView
       This is for items that will be displayed as table rows.
@@ -249,8 +381,31 @@ XOSItemView = Marionette.ItemView.extend({
 XOSListView = Marionette.CompositeView.extend({
              childViewContainer: 'tbody',\r
 \r
-             initialize: function() {\r
-                 this.listenTo(this.collection, 'change', this._renderChildren)
+             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) {
+                e.preventDefault();
+                this.app.Router.navigate("add" + firstCharUpper(this.collection.modelName), {trigger: true});
+             },
+\r
+\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