fetches and gets are now refactored to use common viewutils code which handles 
errors,
backing off the requests (so we'll see fewer messages in our debug logs) and 
more importantly
disabling the relevant portions of the screen so user knows the data is stale / 
unavailable


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

Branch: refs/heads/0.6.0
Commit: d13dc996dab6979d9590c5d35b1a8e29b73697e9
Parents: 869239d
Author: Alex Heneveld <[email protected]>
Authored: Wed Sep 18 03:26:53 2013 +0100
Committer: Alex Heneveld <[email protected]>
Committed: Wed Sep 18 09:30:07 2013 +0100

----------------------------------------------------------------------
 .../webapp/assets/js/view/activity-details.js   |  52 +++---
 .../assets/js/view/application-explorer.js      |  24 +--
 .../webapp/assets/js/view/application-tree.js   |   8 +-
 .../webapp/assets/js/view/entity-activities.js  |   8 +-
 .../main/webapp/assets/js/view/entity-config.js |  58 +++----
 .../webapp/assets/js/view/entity-details.js     |   9 +-
 .../webapp/assets/js/view/entity-effectors.js   |   3 +
 .../webapp/assets/js/view/entity-policies.js    |  26 ++-
 .../webapp/assets/js/view/entity-sensors.js     |  64 ++++---
 .../webapp/assets/js/view/entity-summary.js     |  51 ++++--
 .../src/main/webapp/assets/js/view/home.js      |  29 ++--
 .../src/main/webapp/assets/js/view/viewutils.js | 172 +++++++++++++++++++
 12 files changed, 342 insertions(+), 162 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/activity-details.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/activity-details.js 
b/usage/jsgui/src/main/webapp/assets/js/view/activity-details.js
index 1466bca..051aa74 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/activity-details.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/activity-details.js
@@ -57,6 +57,7 @@ define([
         },
         // requires taskLink or task; breadcrumbs is optional
         initialize:function () {
+            var that = this
             this.taskLink = this.options.taskLink
             if (this.options.task) {
                 this.task = this.options.task
@@ -73,32 +74,31 @@ define([
             ViewUtils.attachToggler(this.$el)
         
             if (this.task) {
-                this.renderTask(true)
+                this.renderTask()
+                this.setUpPolling()
             } else {                
                 this.$el.css('cursor', 'wait')
-                this.refreshNow(true)
+                $.get(this.taskLink, function(data) {
+                    that.task = new TaskSummary.Model(data)
+                    that.renderTask()
+                    that.setUpPolling();
+                })
             }
-            this.renderSubtasks()
-        
-            this.callPeriodically("refresh-activities-now", function () {
-                this.refreshNow()
-            }, 1000);
 
-            if (this.collection) {
-                this.collection.on("reset", this.renderSubtasks, this);
-                // could lean on parent's poll for the task itself, but 
instead we poll (and more often)
-//                this.collection.on("reset", this.renderTask, this);
-            }
+            // initial subtasks may be available from parent, so try to render 
those
+            // (reliable polling for subtasks, and for children, is set up in 
setUpPolling ) 
+            this.renderSubtasks()
         },
         
         refreshNow: function(initial) {
             var that = this
             $.get(this.taskLink, function(data) {
                 that.task = new TaskSummary.Model(data)
-                that.renderTask(initial)
+                that.renderTask()
+                if (initial) that.setUpPolling();
             })
         },
-        renderTask: function(initial) {
+        renderTask: function() {
             // update task fields
             var that = this
             
@@ -149,19 +149,23 @@ define([
 
             if (this.task.get("children").length==0)
                 $('.toggler-region.tasks-children', this.$el).hide();
-            
-            if (initial) {
+        },
+        setUpPolling: function() {
+                var that = this
+                
                 // on first load, clear any funny cursor
                 this.$el.css('cursor', 'auto')
                 
+                this.task.url = this.taskLink;
+                this.task.on("all", this.renderTask, this)
+                ViewUtils.fetchRepeatedlyWithDelay(this, this.task, { doitnow: 
true });
+                
                 // and set up to load children (now that the task is 
guaranteed to be loaded)
                 this.children = new TaskSummary.Collection()
                 this.children.url = this.task.get("links").children
                 this.children.on("reset", this.renderChildren, this)
-                this.callPeriodically("refresh-activity-children", function () 
{
-                    that.children.fetch({reset: true});
-                }, 3000);
-                that.children.fetch({reset: true});
+                ViewUtils.fetchRepeatedlyWithDelay(this, this.children, { 
+                    fetchOptions: { reset: true }, doitnow: true, fadeTarget: 
$('.tasks-children') });
                 
                 $.get(this.task.get("links").entity, function(entity) {
                     if (that.collection==null || entity.links.activities != 
that.collection.url) {
@@ -169,14 +173,12 @@ define([
                         that.collection = new TaskSummary.Collection()
                         that.collection.url = entity.links.activities
                         that.collection.on("reset", this.renderSubtasks, this)
-                        that.callPeriodically("refresh-activity-bgtasks", 
function () {
-                            that.collection.fetch({reset: true});
-                        }, 3000);
-                        that.collection.fetch({reset: true});
+                        ViewUtils.fetchRepeatedlyWithDelay(that, 
that.collection, { 
+                            fetchOptions: { reset: true }, doitnow: true, 
fadeTarget: $('.tasks-submitted') });
                     }
                 });
-            }
         },
+        
         renderChildren: function() {
             var that = this
             var children = this.children

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js 
b/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
index 4cd2008..0ac69fa 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/application-explorer.js
@@ -4,10 +4,10 @@
  * @type {*}
  */
 define([
-    "underscore", "jquery", "backbone", 
+    "underscore", "jquery", "backbone", "view/viewutils", 
     "./application-add-wizard", "model/app-tree", "./application-tree", 
     "text!tpl/apps/page.html"
-], function (_, $, Backbone, AppAddWizard, AppTree, ApplicationTreeView, 
PageHtml) {
+], function (_, $, Backbone, ViewUtils, AppAddWizard, AppTree, 
ApplicationTreeView, PageHtml) {
 
     var ApplicationExplorerView = Backbone.View.extend({
         tagName:"div",
@@ -25,31 +25,17 @@ define([
             $(".nav1").removeClass("active");
             $(".nav1_apps").addClass("active");
 
-            this.collection.on('reset', this.render, this)
             this.treeView = new ApplicationTreeView({
                 collection:this.collection
             })
             this.$('div#app-tree').html(this.treeView.renderFull().el)
-            this.refreshApplications();
-            that.callPeriodically("entity-tree-apps", 
-                    function() { that.refreshApplicationsInPlace() }, 3000)
+            this.collection.fetch({reset: true})
+            ViewUtils.fetchRepeatedlyWithDelay(this, this.collection)
         },
         beforeClose:function () {
             this.collection.off("reset", this.render)
             this.treeView.close()
         },
-        render:function () {
-            return this
-        },
-        
-        refreshApplications:function () {
-            this.collection.fetch({reset: true})
-            return false
-        },
-        refreshApplicationsInPlace:function () {
-            this.collection.fetch()
-            return false
-        },
         show: function(entityId) {
             this.treeView.displayEntityId(entityId)
         },
@@ -64,7 +50,7 @@ define([
             }
             var wizard = new AppAddWizard({
                appRouter:that.options.appRouter,
-               callback:function() { that.refreshApplicationsInPlace() }
+               callback:function() { that.collection.fetch() }
                })
             this._modal = wizard
             this.$(".add-app #modal-container").html(wizard.render().el)

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js 
b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
index db1c679..57999f4 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/application-tree.js
@@ -47,12 +47,18 @@ define([
             this.removeNode(child.id)
         },
         modelEvent: function (eventName, event, x) {
-            if (eventName == "change" || eventName == "remove" || eventName == 
"add" ||
+            if (/^change/i.test(eventName) || eventName == "remove" || 
eventName == "add" ||
                     eventName == "reset" ||
                     // above are handled; below is no-op
                     eventName == "sync" || eventName == "request")
                 return;
 
+            if (eventName == "error") {
+                log("model error in application-tree - has the internet 
vanished?")
+                // ignore; app-explorer should clear the view
+                return;
+            }
+            
             // don't think we get other events, but just in case:
             log("unhandled model event")
             log(eventName)

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js 
b/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js
index ccb9e52..7106b3c 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/entity-activities.js
@@ -53,11 +53,9 @@ define([
             
             ViewUtils.fadeToIndicateInitialLoad($table);
             that.collection.on("reset", that.renderOnLoad, that);
-            that.callPeriodically("entity-activities", function () {
-                if (that.refreshActive)
-                    that.collection.fetch({reset: true});
-            }, 3000);
-            that.collection.fetch({reset: true});
+            ViewUtils.fetchRepeatedlyWithDelay(this, this.collection, 
+                    { fetchOptions: { reset: true }, doitnow: true, 
+                    enablement: function() { return that.refreshActive }  });
         },
         refreshNow: function() {
             this.collection.fetch({reset: true});

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/entity-config.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/entity-config.js 
b/usage/jsgui/src/main/webapp/assets/js/view/entity-config.js
index 1cadaa1..7ec7399 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/entity-config.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/entity-config.js
@@ -93,12 +93,31 @@ define([
         refreshNow:function () {
             this.updateConfigNow(this);  
         },
+        updateConfigNow:function (that) {
+            ViewUtils.get(that, that.model.getConfigUpdateUrl(), 
function(data) { that.updateWithData(that, data) },
+                    { enablement: function() { return that.refreshActive } });
+        },
         updateConfigPeriodically:function (that) {
-            var self = this;
-            that.callPeriodically("entity-config", function() {
-                if (self.refreshActive)
-                    self.updateConfigNow(that);
-            }, 3000);
+            ViewUtils.getRepeatedlyWithDelay(that, 
that.model.getConfigUpdateUrl(), function(data) { that.updateWithData(that, 
data) },
+                    { enablement: function() { return that.refreshActive } });
+        },
+        updateWithData: function (that, data) {
+            $table = that.$('#config-table');
+            ViewUtils.updateMyDataTable($table, data, function(value, name) {
+                var metadata = that.configMetadata[name]
+                if (metadata==null) {                        
+                    // TODO should reload metadata when this happens (new 
sensor for which no metadata known)
+                    // (currently if we have dynamic sensors, their metadata 
won't appear
+                    // until the page is refreshed; don't think that's a big 
problem -- mainly tooltips
+                    // for now, we just return the partial value
+                    return [name, {'name':name}, "", value]
+                } 
+                return [name, metadata,
+                    metadata["actionGetData"],
+                    value
+                ];
+            });
+            ViewUtils.processTooltips($table)
         },
         loadConfigMetadata: function(that) {
             var url =  that.model.getLinkByName('config');
@@ -114,32 +133,11 @@ define([
                 }
                 that.updateConfigNow(that);
                 that.table.find('*[rel="tooltip"]').tooltip();
-            });
+            }).fail(that.onConfigMetadataFailure);
         },
-        updateConfigNow:function (that) {
-            var url = that.model.getConfigUpdateUrl(),
-                $table = that.$('#config-table');
-            if (that.viewIsClosed) {
-                return
-            }
-            $.get(url, function (data) {
-                if (that.viewIsClosed) return
-                ViewUtils.updateMyDataTable($table, data, function(value, 
name) {
-                    var metadata = that.configMetadata[name]
-                    if (metadata==null) {                        
-                        // TODO should reload metadata when this happens (new 
sensor for which no metadata known)
-                        // (currently if we have dynamic sensors, their 
metadata won't appear
-                        // until the page is refreshed; don't think that's a 
bit problem -- mainly tooltips
-                        // for now, we just return the partial value
-                        return [name, {'name':name}, "", value]
-                    } 
-                    return [name, metadata,
-                        metadata["actionGetData"],
-                        value
-                    ];
-                });
-                ViewUtils.processTooltips($table)
-            });
+        onConfigMetadataFailure: function() {
+            log("unable to load config metadata")
+            ViewUtils.fadeToIndicateInitialLoad()
         }
     });
     return EntityConfigView;

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/entity-details.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/entity-details.js 
b/usage/jsgui/src/main/webapp/assets/js/view/entity-details.js
index 0028b40..2e139a8 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/entity-details.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/entity-details.js
@@ -15,10 +15,6 @@ define([
         },
         initialize:function () {
             this.$el.html(this.template({}))
-            this.summaryView = new SummaryView({
-                model:this.model,
-                application:this.options.application
-            })
             this.configView = new ConfigView({
                 model:this.model
             })
@@ -35,6 +31,11 @@ define([
                 model:this.model,
                 collection:new TaskSummary.Collection
             })
+            this.summaryView = new SummaryView({
+                model:this.model,
+                application:this.options.application,
+                sensors:this.sensorsView.model
+            })
             this.$("#summary").html(this.summaryView.render().el)
             this.$("#config").html(this.configView.render().el)
             this.$("#sensors").html(this.sensorsView.render().el)

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/entity-effectors.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/entity-effectors.js 
b/usage/jsgui/src/main/webapp/assets/js/view/entity-effectors.js
index b5b8569..8a68c29 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/entity-effectors.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/entity-effectors.js
@@ -30,6 +30,9 @@ define([
                 that.render()
                 ViewUtils.cancelFadeOnceLoaded(that.$('#effectors-table'));
             }})
+            // attach a fetch simply to fade this tab when not available
+            // (the table is statically rendered)
+            ViewUtils.fetchRepeatedlyWithDelay(this, this._effectors, { 
period: 10*1000 })
         },
         render:function () {
             if (this.viewIsClosed)

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/entity-policies.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/entity-policies.js 
b/usage/jsgui/src/main/webapp/assets/js/view/entity-policies.js
index 2e64075..c09c395 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/entity-policies.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/entity-policies.js
@@ -25,26 +25,19 @@ define([
         initialize:function () {
             this.$el.html(this.template({ }));
             var that = this;
-            // fetch the list of policies and create a view for each one
+            // fetch the list of policies and create a row for each one
             that._policies = new PolicySummary.Collection();
             that._policies.url = that.model.getLinkByName("policies");
             
             this.loadedData = false;
             ViewUtils.fadeToIndicateInitialLoad(this.$('#policies-table'));
-            that.callPeriodically("entity-policies", function() {
-                that.refresh();
-            }, 3000);
-            that.refresh();
-        },
-
-        refresh:function() {
-            var that = this;
             that.render();
-            that._policies.fetch({ success:function () {
-                that.loadedData = true;
-                that.render();
-                ViewUtils.cancelFadeOnceLoaded(that.$('#policies-table'));
-            }});
+            this._policies.on("all", this.render, this)
+            ViewUtils.fetchRepeatedlyWithDelay(this, this._policies,
+                    { doitnow: true, success: function() {
+                        that.loadedData = true;
+                        
ViewUtils.cancelFadeOnceLoaded(that.$('#policies-table'));
+                    }})
         },
 
         render:function () {
@@ -107,8 +100,11 @@ define([
                 // fetch the list of policy config entries
                 that._config = new PolicyConfigSummary.Collection();
                 that._config.url = policy.getLinkByName("config");
+                ViewUtils.fadeToIndicateInitialLoad($('#policy-config-table'))
+                that.showPolicyConfig(id);
                 that._config.fetch({ success:function () {
                     that.showPolicyConfig(id);
+                    ViewUtils.cancelFadeOnceLoaded($('#policy-config-table'))
                 }});
             }
         },
@@ -195,7 +191,7 @@ define([
                 type:"POST",
                 url:url,
                 success:function() {
-                    that.refresh();
+                    that._policies.fetch();
                 }
             });
         }

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/entity-sensors.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/entity-sensors.js 
b/usage/jsgui/src/main/webapp/assets/js/view/entity-sensors.js
index ea24745..c564dff 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/entity-sensors.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/entity-sensors.js
@@ -71,8 +71,8 @@ define([
             ViewUtils.addAutoRefreshButton(this.table);
             ViewUtils.addRefreshButton(this.table);
             this.loadSensorMetadata()
-                .updateSensorsPeriodically()
-                .toggleFilterEmpty();
+            this.updateSensorsPeriodically()
+            this.toggleFilterEmpty();
             return this;
         },
 
@@ -104,13 +104,33 @@ define([
             this.refreshActive = isEnabled
             return this;
         },
-
-        updateSensorsPeriodically: function() {
-            this.callPeriodically("entity-sensors", function() {
-                if (this.refreshActive)
-                    this.updateSensorsNow();
-            }, 3000);
-            return this;
+        
+        /**
+         * Loads current values for all sensors on an entity and updates 
sensors table.
+         */
+        updateSensorsNow:function () {
+            var that = this
+            ViewUtils.get(that, that.model.getSensorUpdateUrl(), 
function(data) { that.updateWithData(that, data) },
+                    { enablement: function() { return that.refreshActive } });
+        },
+        updateSensorsPeriodically:function () {
+            var that = this
+            ViewUtils.getRepeatedlyWithDelay(that, 
that.model.getSensorUpdateUrl(), function(data) { that.updateWithData(that, 
data) },
+                    { enablement: function() { return that.refreshActive } });
+        },
+        updateWithData: function (that, data) {
+            $table = that.$('#sensors-table');
+            ViewUtils.updateMyDataTable($table, data, function(value, name) {
+                var metadata = that.sensorMetadata[name]
+                if (metadata==null) {                        
+                    // TODO should reload metadata when this happens (new 
sensor for which no metadata known)
+                    // (currently if we have dynamic sensors, their metadata 
won't appear
+                    // until the page is refreshed; don't think that's a big 
problem -- mainly tooltips
+                    // for now, we just return the partial value
+                    return [name, {'name':name}, value]
+                } 
+                return [name, metadata, value];
+            });
         },
 
         /**
@@ -139,32 +159,6 @@ define([
                 that.table.find('*[rel="tooltip"]').tooltip();
             });
             return this;
-        },
-
-        /**
-         * Loads current values for all sensors on an entity and updates 
sensors table.
-         */
-        updateSensorsNow: function() {
-            var url = this.model.getSensorUpdateUrl(),
-                $table = this.$('#sensors-table'),
-                that = this;
-            $.get(url, function (data) {
-                if (that.viewIsClosed) {
-                    return
-                }
-                ViewUtils.updateMyDataTable($table, data, function(value, 
name) {
-                    var metadata = that.sensorMetadata[name]
-                    if (metadata==null) {
-                        // TODO should reload metadata when this happens (new 
sensor for which no metadata known)
-                        // (currently if we have dynamic sensors, their 
metadata won't appear
-                        // until the page is refreshed; don't think that's a 
bit problem -- mainly tooltips
-                        // for now, we just return the partial value
-                        return [name, {'name':name}, value]
-                    }
-                    return [name, metadata, value];
-                });
-            });
-            return this;
         }
     });
 

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js 
b/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js
index 6476fb7..612ead4 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/entity-summary.js
@@ -20,17 +20,35 @@ define([
             }))
             ViewUtils.updateTextareaWithData($(".for-textarea", this.$el), ej, 
true, false, 150, 400)
             ViewUtils.attachToggler(this.$el)
-            that.callPeriodically("entity-summary-sensors", 
-                    function() { that.updateSensorsNow(that) }, 3000)
-            that.updateSensorsNow(that)
+
+            // TODO we should have a backbone object exported from the sensors 
view which we can listen to here
+            // (currently we just take the URL from that view) - and do the 
same for active tasks;
+            ViewUtils.getRepeatedlyWithDelay(this, 
this.options.sensors.getSensorUpdateUrl(), 
+                    function(data) { that.updateWithData(that, data) });
+
+            // however if we only use external objects we must either 
subscribe to their errors also
+            // or do our own polling against the server, so we know when to 
disable ourselves
+//            ViewUtils.fetchRepeatedlyWithDelay(this, this.model, { period: 
10*1000 })
         },
         render:function () {
             return this
         },
-        revealIfHasValue: function(that, sensor, $div, renderer) {
+        revealIfHasValue: function(sensor, $div, renderer, values) {
             var that = this;
             if (!renderer) renderer = function(data) { return _.escape(data); }
-            $.ajax({
+            
+            if (values) {
+                var data = values[sensor]
+                if (data || data===false) {
+                    $(".value", $div).html(renderer(data))
+                    $div.show()
+                } else {
+                    $div.hide();
+                }
+                that.updateStatusIcon();
+            } else {
+              // direct ajax call not used anymore - but left just in case
+              $.ajax({
                 url: that.model.getLinkByName("sensors")+"/"+sensor,
                 contentType:"application/json",
                 success:function (data) {
@@ -41,22 +59,27 @@ define([
                         $div.hide();
                     }
                     that.updateStatusIcon();
-                }})            
+                }})
+            }
+        },
+        updateWithData: function (that, data) {
+            that.revealIfHasValue("service.isUp", that.$(".serviceUp"), null, 
data)
+            that.revealIfHasValue("service.state", that.$(".status"), null, 
data)
+            
+            that.revealIfHasValue("webapp.url", that.$(".url"),
+                    function(data) { return "<a 
href='"+_.escape(data)+"'>"+_.escape(data)+"</img>" }, data)
         },
-        updateSensorsNow: function(that) {
-            // hard-coded values for most commonly used sensors
+        updateSensorsNow: function() {
+            // hard-coded display of the most commonly used sensors
             
-            // this is redundant with values now returned from REST 
ApplicationResource.applicationTree
-            // but leaving them here until we more cleanly model that in 
javascript (e.g. lazy loading)
-            that.revealIfHasValue(that, "service.isUp", that.$(".serviceUp"))
-            that.revealIfHasValue(that, "service.state", that.$(".status"))
+            this.revealIfHasValue("service.isUp", this.$(".serviceUp"))
+            this.revealIfHasValue("service.state", this.$(".status"))
             
-            that.revealIfHasValue(that, "webapp.url", that.$(".url"),
+            this.revealIfHasValue("webapp.url", this.$(".url"),
                     function(data) { return "<a 
href='"+_.escape(data)+"'>"+_.escape(data)+"</img>" })
         },
         
         updateStatusIcon: function() {
-            // currently we use the string values from the page; messy, but it 
works
             var statusIconUrl = ViewUtils.computeStatusIcon(this.$(".serviceUp 
.value:visible").html(), this.$(".status .value:visible").html());
             if (statusIconUrl) {
                 this.$('#status-icon').html('<img src="'+statusIconUrl+'" '+

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/home.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/home.js 
b/usage/jsgui/src/main/webapp/assets/js/view/home.js
index 32f0399..828e623 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/home.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/home.js
@@ -3,12 +3,15 @@
  */
 
 define([
-    "underscore", "jquery", "backbone", "./application-add-wizard", 
"model/location",
+    "underscore", "jquery", "backbone", "view/viewutils", 
+    "./application-add-wizard", "model/location",
     "text!tpl/home/applications.html",
     "text!tpl/home/summaries.html",
     "text!tpl/home/app-entry.html",
     "bootstrap"
-], function (_, $, Backbone, AppAddWizard, Location, ApplicationsHtml, 
HomeSummariesHtml, AppEntryHtml) {
+], function (_, $, Backbone, ViewUtils,
+        AppAddWizard, Location, 
+        ApplicationsHtml, HomeSummariesHtml, AppEntryHtml) {
 
     var HomeView = Backbone.View.extend({
         tagName:"div",
@@ -33,16 +36,23 @@ define([
                locations:this.options.locations
                })
             this.renderSummaries()
+            
             this.collection.on('reset', this.render, this)
             this.options.locations.on('reset', this.renderSummaries, this)
 
+            ViewUtils.fetchRepeatedlyWithDelay(this, this.collection, 
+                    { fetchOptions: { reset: true }, doitnow: true, 
+                    /* max is short here so the console becomes usable quickly 
*/
+                    backoffMaxPeriod: 10*1000 });
+            ViewUtils.fetchRepeatedlyWithDelay(this, this.options.locations, { 
fetchOptions: { reset: true }, doitnow: true });
+
             id = $(this.$el).find("#circles-map");
             if (this.options.offline) {
                id.find("#circles-map-message").html("(map off in offline 
mode)");
             } else {
                requirejs(["googlemaps"], function (GoogleMaps) {
                    _.defer( function() {
-                       console.debug("loading google maps")
+                       log("loading google maps")
                                var map = GoogleMaps.addMapToCanvas(id[0],
                                        // brooklyn bridge
 //                                     40.7063, -73.9971, 14
@@ -60,18 +70,9 @@ define([
                }, function (error) {
                                id.find("#circles-map-message").html("(map not 
available)"); 
                });
-            }
-            
-            this.callPeriodically("home", function() {
-               that.refresh(that);                     
-            }, 5000)
-            this.refresh(this)
+            }            
         },
         
-        refresh:function (that) {
-               that.collection.fetch({reset: true})
-               that.options.locations.fetch({reset: true})
-        },
         updateCircles: function(that, locatedLocations, GoogleMaps, map) {
             locatedLocations.fetch({success:function() {
                 GoogleMaps.drawCircles(map, locatedLocations.attributes)
@@ -128,7 +129,7 @@ define([
                 this.$(".add-app #modal-container .modal")
                     .on("hidden",function () {
                         wizard.close()
-                        that.refresh(that)
+                        that.collection.fetch({reset:true});
                     }).modal('show')
             }
         },

http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d13dc996/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js
----------------------------------------------------------------------
diff --git a/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js 
b/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js
index a30635c..7867fbb 100644
--- a/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js
+++ b/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js
@@ -242,6 +242,178 @@ define([
                 // ignore - normal during tests
             }
         },
+        /* variant of $.get with automatic failure handling and recovery;
+         * options should be omitted except by getRepeatedlyWithDelay */
+        get: function(view, url, success, options) {
+            if (view.viewIsClosed) return ;
+            if (options['enablement'] && !options['enablement']()) {
+                // not enabled, just requeue
+                if (options['period']) 
+                    setTimeout(function() { ViewUtils.get(view, url, success, 
options)}, options['period'])
+                return;
+            }
+            
+            /* inspects the status object returned from an ajax call in a view;
+             * if not valid, it fades the view and increases backoff delays 
and resubmits;
+             * if it is valid, it returns true so the caller can continue
+             * (restoring things such as the view, timer, etc, if they were 
disabled);
+             * 
+             * takes some of the options as per fetchRepeatedlyWithDelay
+             * (though they are less well tested here)
+             * 
+             * note that the status text object is rarely useful; normally the 
fail(handler) is invoked,
+             * as above (#get)
+             */
+            var checkAjaxStatusObject = function(status, view, options) {
+                if (view.viewIsClosed) return false;
+                if (status == "success" || status == "notmodified") {
+                    // unfade and restore
+                    if (view._loadingProblem) {
+                        log("getting view data is back to normal - "+url)
+                        log(view)
+                        view._loadingProblem = false;
+                        
+                        var fadeTarget = view.$el;
+                        if ("fadeTarget" in options) {
+                            fadeTarget = options["fadeTarget"]
+                        }
+                        if (fadeTarget) 
ViewUtils.cancelFadeOnceLoaded(fadeTarget)
+                        
+                        if (options['originalPeriod']) 
+                            options.period = options['originalPeriod']; 
+                    }
+                    
+                    return true;
+                }
+                if (status == "error" || status == "timeout" || status == 
"parsererror") {
+                    // fade and log problem
+                    if (!view._loadingProblem) {
+                        log("error getting view data from "+url+" - is the 
server reachable?")
+                        view._loadingProblem = true;
+                    }
+                    // fade the view, on error
+                    var fadeTarget = view.$el;
+                    if ("fadeTarget" in options) {
+                        fadeTarget = options["fadeTarget"]
+                    }
+                    if (fadeTarget) 
ViewUtils.fadeToIndicateInitialLoad(fadeTarget)
+
+                    if (options['period']) {
+                        if (!options['originalPeriod']) options.originalPeriod 
= options['period'];
+                        var period = options['period'];
+                        
+                        // attempt exponential backoff up to every 15m
+                        period *= 2;
+                        var max = (options['backoffMaxPeriod'] || 15*60*1000);
+                        if (period > max) period = max;
+                        options.period = period
+                        setTimeout(function() { ViewUtils.get(view, url, 
success, options)}, period)
+                    } 
+                    
+                    return false;
+                }
+                return true;
+            }
+            
+            return $.get(url, function(data, status) {
+                if (!checkAjaxStatusObject(status, view, options)) {
+                    return;
+                }
+                if (success) success(data);
+                if (options['period']) 
+                    setTimeout(function() { ViewUtils.get(view, url, success, 
options)}, options['period'])
+            }).fail(function() {
+                checkAjaxStatusObject("error", view, options)
+            })
+        },
+        /** invokes a get against the given url repeatedly, with fading and 
backoff on failures,
+         * cf fetchRepeatedlyWithDelay, but here the user's callback function 
is invoked on success
+         */
+        getRepeatedlyWithDelay: function(view, url, success, options) {
+            if (!options) options = {}
+            if (!options['period']) options.period = 3000
+            ViewUtils.get(view, url, success, options)
+        },
+        /* invokes fetch on the model, associated with the view.
+         * automatically closes when view closes, 
+         * and fades display and exponentially-backs off on problems.
+         * options include:
+         * 
+         *   enablement (function returning t/f whether the invocation is 
enabled)
+         *   period (millis, currently 3000 = 3s default);
+         *   originalPeriod (millis, becomes the period if successful; 
primarily for internal use);
+         *   backoffMaxPeriod (millis, max time to wait between retries, 
currently 15*60*1000 = 10m default);
+         *    
+         *   doitnow (if true, kicks off a run immediately, else only after 
the timer)
+         *   
+         *   fadeTarget (jquery element to fade; defaults to view.$el; null 
can be set to prevent fade);
+         *   
+         *   fetchOptions (additional options to pass to fetch; however 
success and error should not be present);
+         *   success (function to invoke on success, before re-queueing);
+         *   error (optional function to invoke on error, before requeueing);
+         */
+        fetchRepeatedlyWithDelay: function(view, model, options) {
+            if (!options) options = {}
+            var period = options['period'] || 3000
+            var originalPeriod = options['originalPeriod'] || period
+//            log("fetching "+model.url+" with delay "+period)
+            var fetcher = function() {
+                if (view.viewIsClosed) return;
+                if (options['enablement'] && !options['enablement']()) {
+                    // not enabled, just requeue
+                    ViewUtils.fetchRepeatedlyWithDelay(view, model, options);
+                    return;
+                }
+                var fetchOptions = options['fetchOptions'] ? 
_.clone(options['fetchOptions']) : {}
+                fetchOptions.success = function(modelR,response,optionsR) {
+                        var fn = options['success']
+                        if (fn) fn(modelR,response,optionsR);
+                        if (view._loadingProblem) {
+                            log("fetching view data is back to normal - 
"+model.url)
+                            view._loadingProblem = false;
+                            
+                            var fadeTarget = view.$el;
+                            if ("fadeTarget" in options) {
+                                fadeTarget = options["fadeTarget"]
+                            }
+                            if (fadeTarget) 
ViewUtils.cancelFadeOnceLoaded(fadeTarget)
+                        }
+                        options.period = originalPeriod;
+                        ViewUtils.fetchRepeatedlyWithDelay(view, model, 
options);
+                }
+                fetchOptions.error = function(modelR,response,optionsR) {
+                        var fn = options['error']
+                        if (fn) fn(modelR,response,optionsR);
+                        if (!view._loadingProblem) {
+                            log("error fetching view data from "+model.url+" - 
is the server reachable?")
+                            log(response)
+                            view._loadingProblem = true;
+                        }
+                        // fade the view, on error
+                        var fadeTarget = view.$el;
+                        if ("fadeTarget" in options) {
+                            fadeTarget = options["fadeTarget"]
+                        }
+                        if (fadeTarget) 
ViewUtils.fadeToIndicateInitialLoad(fadeTarget)
+                        
+                        // attempt exponential backoff up to every 15m
+                        period *= 2;
+                        var max = (options['backoffMaxPeriod'] || 15*60*1000);
+                        if (period > max) period = max;
+                        options = _.clone(options)
+                        options.originalPeriod = originalPeriod;
+                        options.period = period;
+                        ViewUtils.fetchRepeatedlyWithDelay(view, model, 
options);
+                };
+                model.fetch(fetchOptions)
+            };
+            if (options['doitnow']) {
+                options.doitnow = false;
+                fetcher();
+            } else {
+                setTimeout(fetcher, period);
+            }
+        },
         computeStatusIcon: function(serviceUp, lifecycleState) {
             if (serviceUp==false || serviceUp=="false") serviceUp=false;
             else if (serviceUp===true || serviceUp=="true") serviceUp=true;

Reply via email to