brooklyn-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From henev...@apache.org
Subject [05/10] git commit: Further improvements to jsgui catalog page
Date Fri, 11 Jul 2014 04:14:12 GMT
Further improvements to jsgui catalog page

* Displays as much as possible of catalogue item when selected, then
  refreshes model and only updates display if model contents change.
* Can delete models if button with class delete is clicked.
* Improved presentation of errors loading models
* Removes add-new-thing catalog button, to return later


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/6dc52f55
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/6dc52f55
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/6dc52f55

Branch: refs/heads/master
Commit: 6dc52f55d4e0d1295c80faf817ed7c54cdb98782
Parents: 02a60db
Author: Sam Corbett <sam.corbett@cloudsoftcorp.com>
Authored: Tue Jul 8 13:16:46 2014 +0100
Committer: Sam Corbett <sam.corbett@cloudsoftcorp.com>
Committed: Wed Jul 9 14:30:30 2014 +0100

----------------------------------------------------------------------
 .../src/main/webapp/assets/js/view/catalog.js   | 194 ++++++++++++-------
 .../assets/tpl/catalog/details-entity.html      |  30 +--
 .../assets/tpl/catalog/details-location.html    |  15 +-
 .../webapp/assets/tpl/catalog/nav-entry.html    |   2 +-
 .../main/webapp/assets/tpl/catalog/page.html    |   2 -
 5 files changed, 153 insertions(+), 90 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6dc52f55/usage/jsgui/src/main/webapp/assets/js/view/catalog.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/catalog.js b/usage/jsgui/src/main/webapp/assets/js/view/catalog.js
index 3048a2f..85fe1a5 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/catalog.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/catalog.js
@@ -12,66 +12,71 @@ define([
 ], function(_, $, Backbone, FormatJSON, Location, Entity, AddLocationModalView,
         CatalogPageHtml, DetailsEntityHtml, DetailsGenericHtml, EntryHtml, LocationDetailsHtml)
{
 
+    // Holds the currently active details type, e.g. applications, policies
+    var activeDetails;
+
     // TODO: Loading item's details should perform page navigation
     var DetailsView = Backbone.View.extend({
 
-        genericDetails: _.template(DetailsGenericHtml),
-        entityDetails: _.template(DetailsEntityHtml),
-        locationDetails: _.template(LocationDetailsHtml),
+        events: {
+            "click .delete": "deleteItem"
+        },
 
-        render: function() {
-            this.$el.html("<div class='catalog-details'><h3>Select an entry on
the left</h3></div>");
+        initialize: function() {
+            _.bindAll(this);
         },
 
-        showDetailsFor: function(event, type) {
-            var $event = $(event.currentTarget);
-            if ($event.hasClass("active")) return;
+        render: function(extraMessage) {
+            this.$el.html("<div class='catalog-details'>" +
+                "<h3>Select an entry on the left</h3>" +
+                (extraMessage ? extraMessage : "") +
+                "</div>");
+        },
 
-            $(".accordion-nav-row").removeClass("active");
-            $event.addClass('active');
-            var chosenId = $event.attr('id');
-            var url, template, Model;
-            if (type == 'applications' || type == 'entities') {
-                // app templates are just normal entities, in the API
-                url = '/v1/catalog/entities/' + chosenId;
-                template = this.entityDetails;
-                Model = Entity.Model;
-            } else if (type == 'locations') {
-                url = chosenId;
-                template = this.locationDetails;
-                Model = Location.Model;
-            } else {
-                url = '/v1/catalog/' + type + '/' + chosenId;
-                template = this.genericDetails;
-            }
+        show: function(model, template) {
+            // Keep the previously open section open between items
+            var open = this.$(".in").attr("id");
+            var newHtml = $(template({model: model}));
+            $(newHtml).find("#"+open).addClass("in");
+            this.$el.html(newHtml);
 
-            // TODO: Set 'Loading' template
-            //this.$el.html(this.genericDetails({title: chosenId}));
+            // rewire events. previous callbacks are removed automatically.
+            this.delegateEvents()
+        },
+
+        showDetailsFor: function(model, template) {
+            this.activeModel = model;
             var that = this;
-            $.ajax({ url: url,
-                success: function (data) {
-                    var defaults = {
-                        "description": undefined,
-                        "planYaml": undefined,
-                        "sensors": [],
-                        "effectors": [],
-                        "id": undefined,
-                        "name": undefined,
-                        "spec": undefined,
-                        "config": undefined
-                    };
-                    if (Model) {
-                        defaults['model'] = new Model(data);
-                    }
-                    that.$el.html(template(_.extend(defaults, data)))
-                },
-                error: function (xhr, textStatus, error) {
-                    that.$el.html(that.genericDetails({
-                        title: chosenId,
-                        json: FormatJSON({ "status": textStatus, "error": error })
-                    }));
+            // Load the view with currently available data and refresh once the load is complete.
+            // Only refreshes the view if the model changes and the user hasn't selected
another
+            // item while the load was executing.
+            this.show(model, template);
+            model.on("change", function() {
+                if (that.activeModel.cid === model.cid) {
+                    that.show(model, template);
                 }
             });
+            model.fetch()
+                .fail(function(xhr, textStatus, errorThrown) {
+                    console.log("error loading", model.id, ":", errorThrown);
+                    if (that.activeModel.cid === model.cid) {
+                        model.error = true;
+                        that.show(model, template);
+                    }
+                })
+                // Runs after the change event fires, or after the xhr completes
+                .always(function () {
+                    model.off("change");
+                });
+        },
+
+        deleteItem: function(event) {
+            // Could use wait flag to block removal of model from collection
+            // until server confirms deletion and success handler to perform
+            // removal. Useful if delete fails for e.g. lack of entitlement.
+            this.activeModel.destroy();
+            var displayName = $(event.currentTarget).data("name");
+            this.render(displayName ? "Deleted " + displayName : "");
         }
     });
 
@@ -81,6 +86,7 @@ define([
             if (!this.name) {
                 throw new Error("Catalog collection must know its name");
             }
+            _.bindAll(this);
         },
         url: function() {
             return "/v1/catalog/" + this.name;
@@ -109,20 +115,32 @@ define([
 
             // Generic templates
             this.template = _.template(this.options.template || EntryHtml);
-            // Returns template applied to function arguments. Alter if collection altered.
Will be run
-            // in the context of the AccordionItemView.
-            this.templateFn = this.options.templateFn || function(model, index) {
-                return this.template({type: model.get("type"), id: model.get("id")});
+            this.detailsTemplate = _.template(this.options.detailsTemplate || DetailsGenericHtml);
+
+            // Returns template applied to function arguments. Alter if collection altered.
+            // Will be run in the context of the AccordionItemView.
+            this.templateArgs = this.options.templateArgs || function(model, index) {
+                return {type: model.get("type"), id: model.get("id")};
             };
 
-            // undefined argument is for existing models
-            this.collection = this.options.collection || new Catalog(undefined, {"name":
this.name});
-            this.collection.on("sync", this.renderEntries);
+            // undefined argument is used for existing model items
+            var collectionModel = this.options.model || Backbone.Model;
+            this.collection = this.options.collection || new Catalog(undefined, {
+                name: this.name,
+                model: collectionModel
+            });
+            // Refreshes entries list when the collection is synced with the server or
+            // any of its members are destroyed.
+            this.collection
+                .on("sync", this.renderEntries)
+                .on("destroy", this.renderEntries);
             this.refresh();
         },
+
         beforeClose: function() {
             this.collection.off();
         },
+
         render: function() {
             this.$el.html(accordionBodyTemplate({
                 name: this.name,
@@ -131,19 +149,43 @@ define([
             this.renderEntries();
             return this;
         },
+
         renderEntries: function() {
-            var elements = this.collection.map(this.templateFn, this);
-            this.$el.find(".accordion-body")
+            var templater = function(model, index) {
+                var args = _.extend({cid: model.cid}, this.templateArgs(model));
+                return this.template(args);
+            };
+            var elements = this.collection.map(templater, this);
+            this.$(".accordion-body")
                 .empty()
                 .append(elements.join(''));
+            // Rehighlight active model
+            if (this.activeCid && activeDetails === this.name) {
+                $(".accordion-nav-row").removeClass("active");
+                this.setActiveItem(this.$("[data-cid='"+this.activeCid+"'"));
+            }
         },
+
         refresh: function() {
             this.collection.fetch();
         },
+
+        setActiveItem: function($element) {
+            $(".accordion-nav-row").removeClass("active");
+            $element.addClass("active");
+            activeDetails = this.name;
+        },
+
         showDetails: function(event) {
-            // TODO: Incorporate model from view collection.
-            this.options.details.showDetailsFor(event, this.name);
+            var $event = $(event.currentTarget);
+            if (!$event.hasClass("active")) {
+                this.setActiveItem($event);
+                var cid = this.activeCid = $(event.currentTarget).data("cid");
+                var model = this.collection.get(cid);
+                this.options.details.showDetailsFor(model, this.detailsTemplate);
+            }
         },
+
         toggle: function() {
             var body = this.$(".accordion-body");
             var hidden = this.hidden = body.css("display") == "none";
@@ -162,7 +204,7 @@ define([
         className:"container container-fluid",
         entryTemplate:_.template(EntryHtml),
 
-        events:{
+        events: {
             'click .refresh':'refresh',
             'click #add-new-thing':'createNewThing',
             'click #add-new-entity':'addNewCatalogResource',
@@ -176,19 +218,34 @@ define([
             $(".nav1_catalog").addClass("active");
             this.detailsView = new DetailsView();
             this.accordion = this.options.accordion || [
-                new AccordionItemView({name: "applications", details: this.detailsView, autoOpen:
true}),
-                new AccordionItemView({name: "entities", details: this.detailsView}),
-                new AccordionItemView({name: "policies", details: this.detailsView}),
+                new AccordionItemView({
+                    name: "applications",
+                    details: this.detailsView,
+                    detailsTemplate: DetailsEntityHtml,
+                    model: Entity.Model,
+                    autoOpen: true
+                }),
+                new AccordionItemView({
+                    name: "entities",
+                    details: this.detailsView,
+                    detailsTemplate: DetailsEntityHtml,
+                    model: Entity.Model
+                }),
+                new AccordionItemView({
+                    name: "policies",
+                    detailsTemplate: DetailsGenericHtml,
+                    details: this.detailsView
+                }),
                 new AccordionItemView({
                     name: "locations",
                     details: this.detailsView,
+                    detailsTemplate: LocationDetailsHtml,
                     collection: this.options.locations,
-                    templateFn: function(location, index) {
-                        // this reference is AccordionItemView intentionally
-                        return this.template({
+                    templateArgs: function(location, index) {
+                        return {
                             type: location.getPrettyName(),
                             id: location.getLinkByName("self")
-                        });
+                        };
                     }
                 })
             ];
@@ -207,7 +264,6 @@ define([
             _.each(this.accordion, function(child) {
                 parent.append(child.render().$el);
             });
-//            this.accordion[0].toggle();
             return this
         },
 

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6dc52f55/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-entity.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-entity.html b/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-entity.html
index ab02b6e..8aeaf49 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-entity.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-entity.html
@@ -20,9 +20,9 @@ under the License.
 
 <div class="catalog-details">
 
-    <h2><%= name %></h2>
-    <p><%= id %></p>
-    <p><%= description %></p>
+    <h2><%= model.get("name") %></h2>
+    <p><%= model.get("id") %></p>
+    <p><%= model.get("description") %></p>
 
     <div id="catalog-details-accordion" class="accordion">
         <div class="accordion-group">
@@ -33,8 +33,8 @@ under the License.
             </div>
             <div id="collapseYaml" class="accordion-body collapse">
                 <div class="accordion-inner">
-                    <% if (planYaml) { %>
-                    <textarea rows="15"><%= planYaml %></textarea>
+                    <% if (model.get("planYaml")) { %>
+                    <textarea rows="15" readonly><%= model.get("planYaml") %></textarea>
                     <% } else { %>
                     <p>No plan</p>
                     <% } %>
@@ -49,7 +49,11 @@ under the License.
             </div>
             <div id="collapseConfiguration" class="accordion-body collapse">
                 <div class="accordion-inner">
-                    <% if (!config || _.isEmpty(config)) { %>
+                    <% if (model.error) { %>
+                    <p><i class="icon-exclamation-sign"></i> Could not
load configuration</p>
+                    <% } else if (!model.get("config")) { %>
+                        <p>Loading...</p>
+                    <% } else if (_.isEmpty(model.get("config"))) { %>
                         <p>None available</p>
                     <% } else { %>
                         <% var skip = [
@@ -59,7 +63,7 @@ under the License.
                             'priority',
                             'reconfigurable'
                         ]; %>
-                        <% _.each(config, function(object, index) { %>
+                        <% _.each(model.get("config"), function(object, index) { %>
                         <p><strong><%= object.name %></strong>: <%=
object.description %></p>
                         <table class="table table-striped table-condensed nonDatatables">
                             <tbody>
@@ -86,7 +90,9 @@ under the License.
             </div>
             <div id="collapseSensors" class="accordion-body collapse">
                 <div class="accordion-inner">
-                    <% if (!sensors || _.isEmpty(sensors)) { %>
+                    <% if (model.error) { %>
+                    <p><i class="icon-exclamation-sign"></i> Could not
load sensors</p>
+                    <% } else if (!model.get("sensors") || _.isEmpty(model.get("sensors")))
{ %>
                         <p>No sensors</p>
                     <% } else { %>
                         <table class="table table-striped table-condensed nonDatatables">
@@ -98,7 +104,7 @@ under the License.
                                 </tr>
                             </thead>
                             <tbody>
-                            <% _.each(sensors, function(object, index) { %>
+                            <% _.each(model.get("sensors"), function(object, index) {
%>
                                 <tr>
                                     <td><%= object.name %></td>
                                     <td><%= object.type %></td>
@@ -119,10 +125,12 @@ under the License.
             </div>
             <div id="collapseEffectors" class="accordion-body collapse">
                 <div class="accordion-inner">
-                <% if (!effectors || _.isEmpty(effectors)) { %>
+                <% if (model.error) { %>
+                    <p><i class="icon-exclamation-sign"></i> Could not
load effectors</p>
+                <% } else if (!model.get("effectors") || _.isEmpty(model.get("effectors")))
{ %>
                     <p>No effectors</p>
                 <% } else { %>
-                    <% _.each(effectors, function(object, index) { %>
+                    <% _.each(model.get("effectors"), function(object, index) { %>
                         <p><strong><%= object.name %></strong>: <%=
object.description %></p>
                         <% if (!object.parameters || _.isEmpty(object.parameters)) { %>
                             <p>No parameters</p>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6dc52f55/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-location.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-location.html b/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-location.html
index 3a5cf66..a9474fc 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-location.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/catalog/details-location.html
@@ -2,18 +2,19 @@
 
     <h3><%= model.getPrettyName() %></h3>
 
-    <!-- TODO -->
-    <!--<div class="float-right"><button id="<%= id %>" class="btn btn-danger
delete-location">Delete</button></div>-->
+    <div class="float-right">
+        <button data-name="<%= model.getPrettyName() %>" class="btn btn-danger delete">Delete</button>
+    </div>
 
-<% if (typeof config === 'undefined' || _.isEmpty(config)) { %>
+<% if (!model.get("config") || _.isEmpty(model.get("config"))) { %>
     <em>No special configuration</em>
 <% } else { %>
 
     <br/>
     <table>
-        <tr><td><b>ID:</b>&nbsp;&nbsp;</td><td><%=
id %></td></tr>
-        <tr><td><b>Name:</b>&nbsp;&nbsp;</td><td><%=
name !== undefined ? name : "" %></td></tr>
-        <tr><td><b>Spec:</b>&nbsp;&nbsp;</td><td><%=
spec !== undefined ? spec : "" %></td></tr>
+        <tr><td><strong>ID:</strong>&nbsp;&nbsp;</td><td><%=
model.get("id") || "" %></td></tr>
+        <tr><td><strong>Name:</strong>&nbsp;&nbsp;</td><td><%=
model.get("name") || "" %></td></tr>
+        <tr><td><strong>Spec:</strong>&nbsp;&nbsp;</td><td><%=
model.get("spec") || "" %></td></tr>
     </table>
     
     <br/>
@@ -23,7 +24,7 @@
             <th>Value</th>
         </tr></thead>
         <tbody>
-        <% _.each(config, function(value, key){ %>
+        <% _.each(model.get("config"), function(value, key) { %>
         <tr>
             <td style="border-left:none; width:50%;"><%- key%></td>
             <td><%- value%></td>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6dc52f55/usage/jsgui/src/main/webapp/assets/tpl/catalog/nav-entry.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/catalog/nav-entry.html b/usage/jsgui/src/main/webapp/assets/tpl/catalog/nav-entry.html
index 16cb043..02f0242 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/catalog/nav-entry.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/catalog/nav-entry.html
@@ -1 +1 @@
-<div id="<%= id %>" class="accordion-nav-row"><%= type %></div>
+<div data-cid="<%= cid %>" class="accordion-nav-row"><%= type %></div>

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6dc52f55/usage/jsgui/src/main/webapp/assets/tpl/catalog/page.html
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/tpl/catalog/page.html b/usage/jsgui/src/main/webapp/assets/tpl/catalog/page.html
index 0fa8af4..0a375a9 100644
--- a/usage/jsgui/src/main/webapp/assets/tpl/catalog/page.html
+++ b/usage/jsgui/src/main/webapp/assets/tpl/catalog/page.html
@@ -6,8 +6,6 @@
                 <h3>Catalog</h3>
 
                 <div class="apps-tree-toolbar">
-                    <i id="add-new-thing" class="icon-br-plus-sign handy"/>
-                    &nbsp;
                     <i class="icon-br-refresh refresh handy"/>
                 </div>
             </div>


Mime
View raw message