use JSON array for datatables in sensor, activity, and config, so we have more control about when/how they update, esp for adding tasks dynamically (fnAddRow with DOM is incompatible with filtering); table prettification, and additional (and a bit more standardised) toolbar for the table
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/commit/d3722ad7 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/tree/d3722ad7 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-ui/diff/d3722ad7 Branch: refs/heads/0.5.0 Commit: d3722ad71cb0baaf3a22e559058d7cedd8109790 Parents: f837ce0 Author: Alex Heneveld <[email protected]> Authored: Sun Mar 3 17:56:46 2013 -0800 Committer: Alex Heneveld <[email protected]> Committed: Tue Mar 5 18:05:31 2013 -0800 ---------------------------------------------------------------------- .../src/main/webapp/assets/css/prettybrook.css | 105 +++++++++--- usage/jsgui/src/main/webapp/assets/js/config.js | 4 +- .../webapp/assets/js/libs/brooklyn-utils.js | 26 +++ .../assets/js/libs/dataTables.extensions.js | 26 +++ .../js/libs/dataTables.fnStandingRedraw.js | 16 -- .../webapp/assets/js/view/entity-activities.js | 86 ++++++---- .../main/webapp/assets/js/view/entity-config.js | 136 +++++++++++---- .../webapp/assets/js/view/entity-policies.js | 2 +- .../webapp/assets/js/view/entity-sensors.js | 169 +++++++++++++------ .../src/main/webapp/assets/js/view/viewutils.js | 112 ++++++++++-- .../main/webapp/assets/tpl/apps/activities.html | 10 +- .../webapp/assets/tpl/apps/activity-row.html | 5 - .../main/webapp/assets/tpl/apps/config-row.html | 6 - .../src/main/webapp/assets/tpl/apps/config.html | 8 +- .../src/main/webapp/assets/tpl/apps/page.html | 4 +- .../main/webapp/assets/tpl/apps/sensor-row.html | 24 --- .../main/webapp/assets/tpl/apps/sensors.html | 7 +- .../main/webapp/assets/tpl/catalog/page.html | 4 +- 18 files changed, 529 insertions(+), 221 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/css/prettybrook.css ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/css/prettybrook.css b/usage/jsgui/src/main/webapp/assets/css/prettybrook.css index f9182f7..d1bc19e 100644 --- a/usage/jsgui/src/main/webapp/assets/css/prettybrook.css +++ b/usage/jsgui/src/main/webapp/assets/css/prettybrook.css @@ -35,7 +35,7 @@ padding: 15px; } #application-content .tab-content { - max-height: 500px; + max-height: 720px; overflow: scroll; } #application-content .template-lozenge { @@ -426,7 +426,7 @@ input[type="color"]:focus,.uneditable-input:focus { height: auto; } .tab-content { - padding: 18px; + padding: 12px 18px 18px 18px; border-right: 1px solid #DDD; border-left: 1px solid #DDD; min-height: 300px; @@ -559,17 +559,62 @@ background-color: #FFFFFF; } table.dataTable thead th { text-align: left; +background-color: #E0E4E0; +line-height: 24px; } table.dataTable { -margin: 0 auto 4px auto; -border-width: 0px 0px 1px 0px; +margin: 0; +border-width: 2px 0px 2px 0px; border-style: solid; -border-color: gray; +border-color: black; +background-color: #fff; +overflow: scroll; +/* background-color: #fff; + border-top-width: 1px; */ +} +.bottom { + vertical-align: bottom; +} +.smallpadside { + margin-left: 0.25em; + margin-right: 0.25em; +} +.table-scroll-wrapper { + width: 100%; + overflow: scroll; + background-color: #ececec; + border-style: solid; + border-color: #e8e8e8; + border-width: 1px 1px 1px 1px; + border-bottom-left-radius: 7px; + border-bottom-right-radius: 7px; +} +.table-scroll-wrapper .dataTables_filter { + float: left; + padding-left: 6px; +} +.table-scroll-wrapper .dataTables_filter label { + margin-bottom: 5px; + margin-top: 4px; +} +.dataTables_wrapper .brook-db-top-toolbar { + font-size: 85%; + float: right; + padding-right: 6px; + padding-top: 7px; +} +.dataTables_wrapper .brook-db-bot-toolbar { + font-size: 85%; + float: right; + padding-right: 6px; + padding-left: 5px; + padding-top: 5px; } .dataTables_info { - padding-top: 2px; - padding-left: 6px; + padding-top: 5px; + padding-left: 8px; + padding-bottom: 8px; font-size: 85%; width: auto; align: center; @@ -578,11 +623,20 @@ border-color: gray; .dataTables_paginate { font-size: 85%; float: right; + margin-top: 6px; + margin-bottom: 3px; + padding-top: 1px; + padding-right: 4px; + height: 18px; + line-height: 18px; +} +.paging_full_numbers a.paginate_active { + background-color: #A8B8B0; } .dataTables_length { float: left; - padding-left: 0.5em; - padding-top: 1px; + padding-left: 1em; + padding-top: 5px; } .dataTables_length label { font-size: 85%; @@ -607,25 +661,19 @@ margin: 0; background-size:12px 12px; background-repeat: no-repeat; background-position: 8px 5px; - width: 15em; + width: 12em; font-size: 85%; padding: 1px 4px 1px 24px; - margin-bottom: 2px; + margin-bottom: 1px; + border-color: #888C88; + /* -webkit-border-radius: 1em; -moz-border-radius: 1em; border-radius: 1em; -} -.dataTables_wrapper .brook-db-top-toolbar { - font-size: 85%; - float: right; - padding-right: 6px; -} -.dataTables_wrapper .brook-db-bot-toolbar { - font-size: 85%; - float: left; - padding-right: 10px; - padding-left: 5px; - padding-top: 1px; + */ + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; } /* nonDatatables environments want to look a bit like our datatables environment */ table.nonDatatables { @@ -642,6 +690,11 @@ table.table.nonDatatables tbody > tr:first-child > td { /* need both bottom of head, and top of body, to support empty table and override non-empty row top border */ border-top: 1px black solid; } +table.dataTable tbody tr.selected, +table.dataTable tr.odd.selected td.sorting_1, +table.dataTable tr.even.selected td.sorting_1 { + background: #AC8 !important; +}, table.table.nonDatatables tbody tr.selected, table.table.nonDatatables tbody tr.selected td { background: #AC8; @@ -1103,11 +1156,13 @@ textarea { width: 15px !important; height: 15px !important; } - -.icon-refresh:hover { +.green-hover.icon-refresh:hover { background: url(../images/application-icon-refresh-hover.png) top left !important; } +.table-toolbar-icon:hover { + opacity: .7; +} #details { background-color: #f0f0f0 !important; http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/js/config.js ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/js/config.js b/usage/jsgui/src/main/webapp/assets/js/config.js index 57f7e70..48e51af 100644 --- a/usage/jsgui/src/main/webapp/assets/js/config.js +++ b/usage/jsgui/src/main/webapp/assets/js/config.js @@ -19,7 +19,7 @@ require.config({ "jquery-ba-bbq":"libs/jquery.ba-bbq.min", "handlebars":"libs/handlebars-1.0.rc.1", "brooklyn-utils":"libs/brooklyn-utils", - "datatables-fnstandingredraw":"libs/dataTables.fnStandingRedraw", + "datatables-extensions":"libs/dataTables.extensions", "googlemaps":"view/googlemaps", "text":"libs/text", "tpl":"../tpl" @@ -35,7 +35,7 @@ require.config({ deps:[ "underscore", "jquery" ], exports:"Backbone" }, - "datatables-fnstandingredraw":{ + "datatables-extensions":{ deps:[ "jquery", "jquery-datatables" ] } } http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/js/libs/brooklyn-utils.js ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/js/libs/brooklyn-utils.js b/usage/jsgui/src/main/webapp/assets/js/libs/brooklyn-utils.js index e86f75f..9ce67aa 100644 --- a/usage/jsgui/src/main/webapp/assets/js/libs/brooklyn-utils.js +++ b/usage/jsgui/src/main/webapp/assets/js/libs/brooklyn-utils.js @@ -22,3 +22,29 @@ function setVisibility(obj, visible) { if (visible) obj.show() else obj.hide() } + +/** preps data for output */ +function prep(s) { + if (s==null) return ""; + return _.escape(s); +} + +function roundIfNumberToNumDecimalPlaces(v, mantissa) { + if (typeof v !== 'number') + return v; + if (Math.round(v)==v) + return v; + + var vk = v; + for (i=0; i<mantissa; i++) { + vk *= 10; + log(vk) + if (Math.round(vk)==vk) + return vk; + } + // rounding needed + vk = Math.round(vk); + for (i=0; i<mantissa; i++) + vk /= 10; + return vk; +} http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/js/libs/dataTables.extensions.js ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/js/libs/dataTables.extensions.js b/usage/jsgui/src/main/webapp/assets/js/libs/dataTables.extensions.js new file mode 100644 index 0000000..62be398 --- /dev/null +++ b/usage/jsgui/src/main/webapp/assets/js/libs/dataTables.extensions.js @@ -0,0 +1,26 @@ +/* + * jQuery DataTables fnStandingRedraw plug-in. + * + * http://www.datatables.net/plug-ins/api#fnStandingRedraw + */ +$.fn.dataTableExt.oApi.fnStandingRedraw = function(oSettings) { + if (oSettings.oFeatures.bServerSide === false) { + var before = oSettings._iDisplayStart; + oSettings.oApi._fnReDraw(oSettings); + // iDisplayStart has been reset to zero - so lets change it back + oSettings._iDisplayStart = before; + oSettings.oApi._fnCalculateEnd(oSettings); + } + // draw the 'current' page + oSettings.oApi._fnDraw(oSettings); +}; + + +jQuery.fn.dataTableExt.oApi.fnProcessingIndicator = function ( oSettings, onoff ) +{ + if( typeof(onoff) == 'undefined' ) + { + onoff=true; + } + this.oApi._fnProcessingDisplay( oSettings, onoff ); +}; http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/js/libs/dataTables.fnStandingRedraw.js ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/js/libs/dataTables.fnStandingRedraw.js b/usage/jsgui/src/main/webapp/assets/js/libs/dataTables.fnStandingRedraw.js deleted file mode 100644 index cbd8191..0000000 --- a/usage/jsgui/src/main/webapp/assets/js/libs/dataTables.fnStandingRedraw.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * jQuery DataTables fnStandingRedraw plug-in. - * - * http://www.datatables.net/plug-ins/api#fnStandingRedraw - */ -$.fn.dataTableExt.oApi.fnStandingRedraw = function(oSettings) { - if (oSettings.oFeatures.bServerSide === false) { - var before = oSettings._iDisplayStart; - oSettings.oApi._fnReDraw(oSettings); - // iDisplayStart has been reset to zero - so lets change it back - oSettings._iDisplayStart = before; - oSettings.oApi._fnCalculateEnd(oSettings); - } - // draw the 'current' page - oSettings.oApi._fnDraw(oSettings); -}; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/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 f7389e7..9f6e82d 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 @@ -2,58 +2,84 @@ * Displays the list of activities/tasks the entity performed. */ define([ - "underscore", "backbone", "text!tpl/apps/activities.html", "text!tpl/apps/activity-row.html", - "text!tpl/apps/activity-details.html", "bootstrap", "formatJson" -], function (_, Backbone, ActivitiesHtml, ActivityRowHtml, ActivityDetailsHtml) { + "underscore", "jquery", "backbone", "view/viewutils", + "text!tpl/apps/activities.html", "text!tpl/apps/activity-details.html", + "bootstrap", "formatJson", "jquery-datatables", "datatables-extensions", "brooklyn-utils" +], function (_, $, Backbone, ViewUtils, ActivitiesHtml, ActivityDetailsHtml) { var ActivitiesView = Backbone.View.extend({ template:_.template(ActivitiesHtml), - taskRow:_.template(ActivityRowHtml), + table:null, + refreshActive:true, events:{ - "click #activities-table tr":"rowClick" + "click #activities-table tr":"rowClick", + 'click .toggleAutoRefresh':'toggleAutoRefresh' }, initialize:function () { - var that = this; - that.$el.html(that.template({})); + this.$el.html(this.template({ })); + $.ajaxSetup({ async:false }); + var that = this, + $table = that.$('#activities-table'); that.collection.url = that.model.getLinkByName("activities"); - that.collection.fetch(); + that.table = ViewUtils.myDataTable($table, { + "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) { + $(nRow).attr('id', aData[0]) + }, + "aoColumnDefs": [ + { + "mRender": function ( data, type, row ) { + return prep(data) + }, + "aTargets": [ 0, 1, 2, 3 ] + }, + { "bVisible": false, "aTargets": [ 0 ] } + ] + }); + // TODO domain-specific filters + ViewUtils.addAutoRefreshButton(that.table); + ViewUtils.addRefreshButton(that.table); + that.collection.on("reset", that.render, that); that.callPeriodically("entity-activities", function () { - that.collection.fetch(); - }, 5000); + if (self.refreshActive) + that.collection.fetch(); + }, 3000); + that.collection.fetch(); }, beforeClose:function () { this.collection.off("reset", this.render); }, + toggleAutoRefresh:function () { + ViewUtils.toggleAutoRefresh(this); + }, + enableAutoRefresh: function(isEnabled) { + this.refreshActive = isEnabled + }, render:function () { - var that = this, - $tbody = $("#activities-table tbody").empty(); - if (this.collection.length==0) { + var that = this; + if (that.table == null || this.collection.length==0) { $(".has-no-activities").show(); $("#activity-details-none-selected").hide(); } else { $(".has-no-activities").hide(); - this.collection.each(function (task) { - $tbody.append(that.taskRow({ - cid:task.get("id"), - displayName:task.get("displayName"), - submitTimeUtc:task.get("submitTimeUtc"), - startTimeUtc:task.get("startTimeUtc"), - endTimeUtc:task.get("endTimeUtc"), - currentStatus:task.get("currentStatus"), - entityDisplayName:task.get("entityDisplayName") - })); - if (that.activeTask) { - $("#activities-table tr[id='"+that.activeTask+"']").addClass("selected"); - that.showFullActivity(that.activeTask); - } else { - $("#activity-details-none-selected").show(); - } - }); + ViewUtils.updateMyDataTable(that.table, that.collection, function(task, index) { + return [ + // columns are: id, name, when submitted, status + task.get("id"), + task.get("displayName"), + task.get("submitTimeUtc"), + task.get("currentStatus") + ]; + //also have: +// startTimeUtc:task.get("startTimeUtc"), +// endTimeUtc:task.get("endTimeUtc"), +// entityDisplayName:task.get("entityDisplayName") + }) } return this; }, rowClick:function(evt) { + // TODO link row click, and details stuff var row = $(evt.currentTarget).closest("tr"); var id = row.attr("id"); $("#activities-table tr").removeClass("selected"); http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/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 394ef6a..7679bc5 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 @@ -5,39 +5,76 @@ */ define([ "underscore", "jquery", "backbone", - "view/viewutils", "model/config-summary", "text!tpl/apps/config.html", "text!tpl/apps/config-row.html", - "jquery-datatables", "datatables-fnstandingredraw" -], function (_, $, Backbone, ViewUtils, ConfigSummary, ConfigHtml, ConfigRowHtml) { + "view/viewutils", "model/config-summary", "text!tpl/apps/config.html", + "jquery-datatables", "datatables-extensions", "brooklyn-utils" +], function (_, $, Backbone, ViewUtils, ConfigSummary, ConfigHtml) { var EntityConfigView = Backbone.View.extend({ template:_.template(ConfigHtml), - configTemplate:_.template(ConfigRowHtml), + configMetadata:{}, + refreshActive:true, events:{ 'click .refresh':'refreshConfig', - 'click .filterEmpty':'toggleFilterEmpty' + 'click .filterEmpty':'toggleFilterEmpty', + 'click .toggleAutoRefresh':'toggleAutoRefresh' }, initialize:function () { this.$el.html(this.template({ })); $.ajaxSetup({ async:false }); var that = this, - configCollection = new ConfigSummary.Collection, - $table = this.$('#config-table'), - $tbody = this.$('tbody').empty(); - configCollection.url = that.model.getLinkByName('config'); - configCollection.fetch({ success:function () { - configCollection.each(function (config) { - $tbody.append(that.configTemplate({ - name:config.get("name"), - description:config.get("description"), - value:'', // will be set later - type:config.get("type") - })); - }); - $tbody.find('*[rel="tooltip"]').tooltip(); - that.updateConfigPeriodically(that); - ViewUtils.myDataTable($table); - $table.dataTable().fnAdjustColumnSizing(); - }}); + $table = this.$('#config-table'); + that.table = ViewUtils.myDataTable($table, { + "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) { + $(nRow).attr('id', aData[0]) + $('td',nRow).each(function(i,v){ + if (i==1) $(v).attr('class','config-actions'); + if (i==2) $(v).attr('class','config-value'); + }) + return nRow; + }, + "aoColumnDefs": [ + { // name, with tooltip + "mRender": function ( data, type, row ) { + // name (column 1) should have tooltip title + return '<span class="config-name" '+ + 'rel="tooltip" title="<b>'+ + prep(data['description'])+'</b><br/>('+ + prep(data['type'])+')" data-placement="left">'+ + prep(data['name'])+'</span>'; + }, + "aTargets": [ 1 ] + }, + { // actions (just one, json link hard coded here apart from url) + "mRender": function ( link, type, row ) { + if (link=="") return ""; + var text="" + var icon="icon-file" + var title="JSON direct link" + var actionsText = + "<a href='"+prep(link)+"'"+ + " class='"+prep(icon)+"'"+ + " title='"+prep(title)+"'>"+ + prep(text)+"</a>\n"; + //just one action here + return actionsText; + }, + "aTargets": [ 2 ] + }, + { // value + "mRender": function ( data, type, row ) { + return prep(roundIfNumberToNumDecimalPlaces(data, 4)) + }, + "aTargets": [ 3 ] + }, + // ID in column 0 is standard (assumed in ViewUtils) + { "bVisible": false, "aTargets": [ 0 ] /* hide id column */ }, + ] + }); + ViewUtils.addFilterEmptyButton(that.table); + ViewUtils.addAutoRefreshButton(that.table); + ViewUtils.addRefreshButton(that.table); + that.loadConfigMetadata(that); + that.updateConfigPeriodically(that); that.toggleFilterEmpty(); }, render:function () { @@ -45,34 +82,59 @@ define([ return this; }, toggleFilterEmpty:function () { - ViewUtils.toggleFilterEmpty(this.$('#config-table'), 1); + ViewUtils.toggleFilterEmpty(this.$('#config-table'), 3); + }, + toggleAutoRefresh:function () { + ViewUtils.toggleAutoRefresh(this); + }, + enableAutoRefresh: function(isEnabled) { + this.refreshActive = isEnabled }, refreshConfig:function () { this.updateConfigNow(this); }, - // register a callback to update the sensors updateConfigPeriodically:function (that) { var self = this; - that.updateConfigNow(that); that.callPeriodically("entity-config", function() { - self.updateConfigNow(that); + if (self.refreshActive) + self.updateConfigNow(that); }, 3000); }, + loadConfigMetadata: function(that) { + var url = that.model.getLinkByName('config'); + $.get(url, function (data) { + for (d in data) { + var config = data[d]; + that.configMetadata[config["name"]] = { + name:config["name"], + description:config["description"], + actionGetData:config["links"]["self"], + type:config["type"] + } + } + that.updateConfigNow(that); + that.table.find('*[rel="tooltip"]').tooltip(); + }); + }, updateConfigNow:function (that) { - // NB: this won't add new dynamic config var url = that.model.getConfigUpdateUrl(), - $table = that.$('#config-table'), - $rows = that.$("tr.config-row"); + $table = that.$('#config-table'); $.get(url, function (data) { - // iterate over the config table and update each value - $rows.each(function (index, row) { - var key = $(this).find(".config-name").text(); - var v = data[key]; - if (v === undefined) v = ''; - $table.dataTable().fnUpdate(_.escape(v), row, 1, false); + 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 + ]; }); }); - $table.dataTable().fnStandingRedraw(); } }); return EntityConfigView; http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/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 e126b6b..1913759 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 @@ -7,7 +7,7 @@ define([ "underscore", "jquery", "backbone", "model/policy-summary", "model/policy-config-summary", "view/viewutils", "view/policy-config-invoke", "text!tpl/apps/policy.html", "text!tpl/apps/policy-row.html", "text!tpl/apps/policy-config-row.html", - "jquery-datatables", "datatables-fnstandingredraw" + "jquery-datatables", "datatables-extensions" ], function (_, $, Backbone, PolicySummary, PolicyConfigSummary, ViewUtils, PolicyConfigInvokeView, PolicyHtml, PolicyRowHtml, PolicyConfigRowHtml) { var EntityPoliciesView = Backbone.View.extend({ http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/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 50b3748..398cbe0 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 @@ -5,46 +5,86 @@ */ define([ "underscore", "jquery", "backbone", - "view/viewutils", "model/sensor-summary", "text!tpl/apps/sensors.html", "text!tpl/apps/sensor-row.html", - "jquery-datatables", "datatables-fnstandingredraw" -], function (_, $, Backbone, ViewUtils, SensorSummary, SensorsHtml, SensorRowHtml) { + "view/viewutils", "model/sensor-summary", "text!tpl/apps/sensors.html", + "jquery-datatables", "datatables-extensions", "brooklyn-utils" +], function (_, $, Backbone, ViewUtils, SensorSummary, SensorsHtml) { var EntitySensorsView = Backbone.View.extend({ template:_.template(SensorsHtml), - sensorTemplate:_.template(SensorRowHtml), + sensorMetadata:{}, + refreshActive:true, events:{ 'click .refresh':'refreshSensors', - 'click .filterEmpty':'toggleFilterEmpty' + 'click .filterEmpty':'toggleFilterEmpty', + 'click .toggleAutoRefresh':'toggleAutoRefresh' }, initialize:function () { this.$el.html(this.template({ })); $.ajaxSetup({ async:false }); var that = this, - sensorsCollection = new SensorSummary.Collection, - $table = this.$('#sensors-table'), - $tbody = this.$('tbody').empty(); - sensorsCollection.url = that.model.getLinkByName('sensors'); - sensorsCollection.fetch({ success:function () { - sensorsCollection.each(function (sensor) { - var actions = {}; - _.each(sensor.get("links"), function(v,k) { - if (k.slice(0, 7) == "action:") { - actions[k.slice(7)] = v; - } - }); - $tbody.append(that.sensorTemplate({ - name:sensor.get("name"), - description:sensor.get("description"), - actions:actions, - type:sensor.get("type"), - value:'' // will be set later - })); - }); - $tbody.find('*[rel="tooltip"]').tooltip(); - that.updateSensorsPeriodically(that); - ViewUtils.myDataTable($table); - $table.dataTable().fnAdjustColumnSizing(); - }}); + $table = this.$('#sensors-table'); + that.table = ViewUtils.myDataTable($table, { + "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) { + $(nRow).attr('id', aData[0]) + $('td',nRow).each(function(i,v){ + if (i==1) $(v).attr('class','sensor-actions'); + if (i==2) $(v).attr('class','sensor-value'); + }) + return nRow; + }, + "aoColumnDefs": [ + { // name (with tooltip) + "mRender": function ( data, type, row ) { + // name (column 1) should have tooltip title + return '<span class="sensor-name" '+ + 'rel="tooltip" title="<b>'+ + prep(data['description'])+'</b><br/>('+ + prep(data['type'])+')" data-placement="left">'+ + prep(data['name'])+'</span>'; + }, + "aTargets": [ 1 ] + }, + { // actions + "mRender": function ( actions, type, row ) { + var actionsText = "" + _.each(actions, function(v,k) { + var text=k + var icon="" + var title="" + if (k=="json") { + icon="icon-file" + title="JSON direct link" + } + if (k=="open") { + icon="icon-home" + title="Open URL" + } + if (icon!="") text="" + actionsText = actionsText + + "<a href='"+prep(v)+"'"+ + " class='"+prep(icon)+"'"+ + " title='"+prep(title)+"'>"+ + prep(text)+"</a>\n"; + }) + return actionsText; + }, + "aTargets": [ 2 ] + }, + { // value + "mRender": function ( data, type, row ) { + return prep(roundIfNumberToNumDecimalPlaces(data, 4)) + }, + "aTargets": [ 3 ] + }, + // ID in column 0 is standard (assumed in ViewUtils) + { "bVisible": false, "aTargets": [ 0 ] }, + ] + }); + ViewUtils.addFilterEmptyButton(that.table); + ViewUtils.addAutoRefreshButton(that.table); + ViewUtils.addRefreshButton(that.table); + that.loadSensorMetadata(that); + that.updateSensorsPeriodically(that); that.toggleFilterEmpty(); }, render:function () { @@ -52,44 +92,65 @@ define([ return this; }, toggleFilterEmpty:function () { - ViewUtils.toggleFilterEmpty(this.$('#sensors-table'), 2); + ViewUtils.toggleFilterEmpty(this.$('#sensors-table'), 3); + }, + toggleAutoRefresh:function () { + ViewUtils.toggleAutoRefresh(this); + }, + enableAutoRefresh: function(isEnabled) { + this.refreshActive = isEnabled }, refreshSensors:function () { this.updateSensorsNow(this); }, - // register a callback to update the sensors updateSensorsPeriodically:function (that) { var self = this; - that.updateSensorsNow(that); that.callPeriodically("entity-sensors", function() { - self.updateSensorsNow(that); + if (self.refreshActive) + self.updateSensorsNow(that); }, 3000); }, - updateSensorsNow:function (that) { - // NB: this won't add new dynamic sensors - var url = that.model.getSensorUpdateUrl(), - $table = that.$('#sensors-table'), - $rows = that.$("tr.sensor-row"); + loadSensorMetadata: function(that) { + var url = that.model.getLinkByName('sensors'); $.get(url, function (data) { - // iterate over the sensors table and update each sensor - $rows.each(function (index, row) { - var key = $(this).find(".sensor-name").text(); - var v = data[key]; - if (v === undefined || v === null) v = ''; - else if (typeof v === 'number') { - if (Math.round(v)!=v) { - // not an integer, check if it needs rounding - var vk = v*1000; - if (Math.round(vk)!=vk) { - // needs rounding - v = Math.round(vk)/1000; - } + for (d in data) { + var sensor = data[d]; + var actions = {}; + _.each(sensor["links"], function(v,k) { + if (k.slice(0, 7) == "action:") { + actions[k.slice(7)] = v; } + }); + that.sensorMetadata[sensor["name"]] = { + name:sensor["name"], + description:sensor["description"], + actions:actions, + type:sensor["type"] } - $table.dataTable().fnUpdate(_.escape(v), row, 2, false); + } + that.updateSensorsNow(that); + that.table.find('*[rel="tooltip"]').tooltip(); + }); + }, + updateSensorsNow:function (that) { + var url = that.model.getSensorUpdateUrl(), + $table = that.$('#sensors-table'); + $.get(url, function (data) { + 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, + metadata["actions"], + value + ]; }); }); - $table.dataTable().fnStandingRedraw(); } }); return EntitySensorsView; http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/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 8b879f7..e4a16d0 100644 --- a/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js +++ b/usage/jsgui/src/main/webapp/assets/js/view/viewutils.js @@ -3,15 +3,15 @@ define([ ], function (_, $, Backbone) { var ViewUtils = { - myDataTable:function($table) { - $table.dataTable({ + myDataTable:function($table, extra) { + var settings = { "bDestroy": true, "iDisplayLength": 25, "sPaginationType": "full_numbers", - "sDom": "f<'brook-db-top-toolbar'>t<'brook-db-bot-toolbar'>ilp", + "sDom": "fp<'brook-db-top-toolbar'>tilp<'brook-db-bot-toolbar'>", "oLanguage": { "sSearch": "", - "sInfo": "_START_ - _END_ of _TOTAL_ ", + "sInfo": "Showing _START_ - _END_ of _TOTAL_ ", "sInfoEmpty": "<i>No data</i> ", "sEmptyTable": "<i>No matching records available</i>", "sZeroRecords": "<i>No matching records found</i>", @@ -23,17 +23,94 @@ define([ }, "sInfoFiltered": "(of _MAX_)", "sLengthMenu": '( <select>' + + '<option value="10">10</option>' + '<option value="25">25</option>' + '<option value="50">50</option>' + '<option value="-1">all</option>' + '</select> / page )' } - }); - $('.brook-db-bot-toolbar', $table.parent().parent()).html( - '<i class="refresh icon-refresh handy" rel="tooltip" title="Reload immediately."></i>' + - ' ' + - '<i class="filterEmpty icon-eye-open handy" rel="tooltip" title="Show/hide empty records"></i>' - ); + }; + for (prop in extra) { + settings[prop] = extra[prop]; + } + var result = $table.dataTable(settings); + return result; + }, + myDataTableToolbarAddHtml: function($table,html) { + $('.brook-db-bot-toolbar', $table.parent().parent()).append(html) + $('.brook-db-top-toolbar', $table.parent().parent()).append(html) + }, + addRefreshButton: function($table) { + this.myDataTableToolbarAddHtml($table, + '<i class="refresh table-toolbar-icon icon-refresh handy smallpadside" rel="tooltip" title="Reload content immediately"></i>'); + }, + addFilterEmptyButton: function($table) { + this.myDataTableToolbarAddHtml($table, + '<i class="filterEmpty table-toolbar-icon icon-eye-open handy bottom smallpadside" rel="tooltip" title="Show/hide empty records"></i>'); + }, + addAutoRefreshButton: function($table) { + this.myDataTableToolbarAddHtml($table, + '<i class="toggleAutoRefresh table-toolbar-icon icon-pause handy smallpadside" rel="tooltip" title="Toggle auto-refresh"></i>'); + }, + /* fnConvertData takes the entries in collection (value, optionalKeyOrIndex) and returns a list + * whose first element is the ID (hidden first column of table) + * and other elements are the other columns in the table; + * alternatively it can return null if the entry should be excluded + */ + updateMyDataTable: function(table, collection, fnConvertData) { + if (table==null) return; + var oldDisplayDataList = table.dataTable().fnGetData(); + var oldDisplayIndexMap = {} + var oldDisplayData = {} + for (idx in oldDisplayDataList) { + var data = oldDisplayDataList[idx] + oldDisplayIndexMap[data[0]] = idx + oldDisplayData[data[0]] = data + } + newDisplayData = {} + updateDisplayData = [] + ViewUtils.each(collection, function(data,index) { + var newRow = fnConvertData(data, index) + if (newRow!=null) { + var id = newRow[0] + + var displayIndex = oldDisplayIndexMap[id]; + if (displayIndex!=null) { + updateDisplayData[displayIndex] = newRow + delete oldDisplayIndexMap[id] + } else { + newDisplayData[id] = newRow + } + } + }) + // first update (so indices don't change) + for (prop in updateDisplayData) { + var rowProps = updateDisplayData[prop] + var oldProps = oldDisplayData[rowProps[0]] + for (idx in rowProps) { + var v = rowProps[idx] + if (!_.isEqual(v,oldProps[idx])) { + // update individual columns as values change +// log("updating "+v+" in "+prop+"["+idx+"]") + table.fnUpdate( v, Number(prop), idx, false, false ) + } else { +// log("NO CHANGE") + } + } + } + // then delete old ones + for (prop in oldDisplayIndexMap) { + var index = oldDisplayIndexMap[prop] +// log("deleting "+index) + table.fnDeleteRow( Number(index), null, false ) + } + // and now add new ones + for (prop in newDisplayData) { +// log("adding "+newDisplayData[prop]) + table.fnAddData( newDisplayData[prop] ) + } + table.fnAdjustColumnSizing(); + table.fnStandingRedraw(); }, toggleFilterEmpty: function($table, column) { var hideEmpties = $('.filterEmpty', $table.parent().parent()).toggleClass('icon-eye-open icon-eye-close').hasClass('icon-eye-close'); @@ -43,7 +120,10 @@ define([ $table.dataTable().fnFilter('.*', column, true); } }, - + toggleAutoRefresh: function(pane) { + var isEnabled = $('.toggleAutoRefresh', pane.$el).toggleClass('icon-pause icon-play').hasClass('icon-pause'); + pane.enableAutoRefresh(isEnabled); + }, attachToggler: function($scope) { var $togglers; if ($scope === undefined) { @@ -84,6 +164,16 @@ define([ } else { $div.hide(); } + }, + each: function(collection, fn) { + if (typeof collection.each == 'function') { + // some objects (such as backbone collections) are not iterable + // (either by "for x in" or "_.each") so call the "each" method explicitly on them + return collection.each(fn) + } else { + // try underscore + return _.each(collection, fn); + } } }; return ViewUtils; http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/tpl/apps/activities.html ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/activities.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/activities.html index a854421..9839826 100644 --- a/usage/jsgui/src/main/webapp/assets/tpl/apps/activities.html +++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/activities.html @@ -1,6 +1,8 @@ -<table id="activities-table" class="table table-striped table-condensed nonDatatables" style="width: 100%;"> +<div class="table-scroll-wrapper"> +<table id="activities-table" width="100%"> <thead> <tr> + <th width="10%">ID</th> <th width="40%">Task</th> <th width="30%">Submitted</th> <th width="30%">Status</th> @@ -8,6 +10,10 @@ </thead> <tbody></tbody> </table> +</div> + +<div> </div> + <div class="has-no-activities for-empty-table hide"> <i>No activities currently available on this entity</i> </div> @@ -16,5 +22,5 @@ </div> <div id="activity-details-none-selected"> - <i>Select an entity above to view details.</i> + <i>Select an entry above to view details.</i> </div> http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-row.html ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-row.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-row.html deleted file mode 100644 index 7f1dca7..0000000 --- a/usage/jsgui/src/main/webapp/assets/tpl/apps/activity-row.html +++ /dev/null @@ -1,5 +0,0 @@ -<tr class="activity-row" id="<%= cid %>"> - <td class="task-display-name"><%= displayName %></td> - <td class="submit-time"><%= submitTimeUtc %></td> - <td class="current-status"><%= currentStatus %></td> -</tr> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/tpl/apps/config-row.html ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/config-row.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/config-row.html deleted file mode 100644 index 8ee0624..0000000 --- a/usage/jsgui/src/main/webapp/assets/tpl/apps/config-row.html +++ /dev/null @@ -1,6 +0,0 @@ -<tr class="config-row"> - <td><span class="config-name" - rel="tooltip" title="<b><%= description %></b><br/>(<%= type %>)" data-placement="left" - ><%= name %></span></td> - <td class="config-value"><%= (value === undefined ? "" : value) %></td> -</tr> http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/tpl/apps/config.html ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/config.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/config.html index 7c47018..ab37a27 100644 --- a/usage/jsgui/src/main/webapp/assets/tpl/apps/config.html +++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/config.html @@ -1,9 +1,13 @@ -<table id="config-table" style="width: 554px;"> +<div class="table-scroll-wrapper"> +<table id="config-table" width="100%"> <thead> <tr> + <th>ID</th> <th>Name</th> + <th>Actions</th> <th>Value</th> </tr> </thead> <tbody></tbody> -</table> \ No newline at end of file +</table> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/tpl/apps/page.html ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/page.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/page.html index 4a20a87..a5314dd 100644 --- a/usage/jsgui/src/main/webapp/assets/tpl/apps/page.html +++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/page.html @@ -3,9 +3,9 @@ <div class="navbar_top"> <h3>Applications</h3> <div class="apps-tree-toolbar"> - <i id="add-new-application" class="icon-plus-sign handy" /> + <i id="add-new-application" class="icon-plus-sign handy green-hover" /> - <i class="icon-refresh refresh handy" /> + <i class="icon-refresh refresh handy green-hover" /> </div> </div> <div class="navbar_main_wrapper cssninja"> http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/tpl/apps/sensor-row.html ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/sensor-row.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/sensor-row.html deleted file mode 100644 index eaac4b7..0000000 --- a/usage/jsgui/src/main/webapp/assets/tpl/apps/sensor-row.html +++ /dev/null @@ -1,24 +0,0 @@ -<tr class="sensor-row"> - <td><span class="sensor-name" - rel="tooltip" title="<b><%= description %></b><br/>(<%= type %>)" data-placement="left" - ><%= name %></span></td> - <td class="sensor-actions"> - <% _.each(actions, function(v,k) { - var text=k - var icon="" - var title="" - if (k=="json") { - icon="icon-file" - title="JSON direct link" - } - if (k=="open") { - icon="icon-home" - title="Open URL" - } - if (icon!="") text="" - %> - <a href="<%= v %>" class="<%= icon %>" title="<%= title %>"><%= text %></a> - <% }) %> - </td> - <td class="sensor-value"><%= value %></td> -</tr> http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/usage/jsgui/src/main/webapp/assets/tpl/apps/sensors.html ---------------------------------------------------------------------- diff --git a/usage/jsgui/src/main/webapp/assets/tpl/apps/sensors.html b/usage/jsgui/src/main/webapp/assets/tpl/apps/sensors.html index 845adb4..819c269 100644 --- a/usage/jsgui/src/main/webapp/assets/tpl/apps/sensors.html +++ b/usage/jsgui/src/main/webapp/assets/tpl/apps/sensors.html @@ -1,10 +1,13 @@ -<table id="sensors-table" style="width: 554px;"> +<div class="table-scroll-wrapper"> +<table id="sensors-table" width="100%"> <thead> <tr> + <th>ID</th> <th>Name</th> <th>Actions</th> <th>Value</th> </tr> </thead> <tbody></tbody> -</table> \ No newline at end of file +</table> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/brooklyn-ui/blob/d3722ad7/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 18497b7..b0ef309 100644 --- a/usage/jsgui/src/main/webapp/assets/tpl/catalog/page.html +++ b/usage/jsgui/src/main/webapp/assets/tpl/catalog/page.html @@ -5,9 +5,9 @@ <div class="navbar_top"> <h3>Catalog</h3> <div class="apps-tree-toolbar"> - <i id="add-new-thing" class="icon-plus-sign handy" /> + <i id="add-new-thing" class="icon-plus-sign handy green-hover" /> - <i class="icon-refresh refresh handy" /> + <i class="icon-refresh refresh handy green-hover" /> </div> </div> <div class="navbar_main_wrapper cssninja">
