diff --git a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
index 580477ddec5a28555ba86a7cb1fdd6ce88306335..7e806a677e1b979e6f1fc5a84c811b966de21b36 100644
--- a/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
+++ b/src/main/resources/META-INF/xnat/spawner/site-admin-elements.yaml
@@ -789,7 +789,15 @@ pluginTable:
         dataModelBeans:
             label: Contents
             cells:
-                html: <a href="#!" class="view-plugin-info link">View Plugin Info</a>
+                apply: JSON.stringify  # function that accepts value as sole argument
+                html: >
+                    <a href="#!" class="view-plugin-info link">View Plugin Info</a>
+                    <div class="hidden plugin-json-string">__VALUE__</div>
+    messages:
+        noData: >
+            There are no plugins installed in this XNAT. Try searching for plugins on XNAT Marketplace.
+        error: >
+            An error occurred retrieving information for installed plugins.
 
 pluginTableScript:
     tag: script
@@ -806,12 +814,13 @@ pluginTableScript:
                     language: 'json'
                 }).openEditor({
                     title: 'Plugin Info',
+                    classes: 'plugin-json',
                     footerContent: '(read-only)',
                     buttons: { close: { label: 'Close' } }
                 });
             });
         });
-        $body.on('focus', 'textarea.ace_text-input', function(){
+        $body.on('focus', '.plugin-json textarea.ace_text-input', function(){
             this.disabled = true;
         });
 
diff --git a/src/main/webapp/scripts/globals.js b/src/main/webapp/scripts/globals.js
index e909ea26e3a1b0783613d1367fe661f267f200d2..86e852b64185759ad2b90f43d3195f6a59758d9b 100644
--- a/src/main/webapp/scripts/globals.js
+++ b/src/main/webapp/scripts/globals.js
@@ -156,6 +156,12 @@ function isEmptyArray( arr ){
     return isArray(arr) && arr.length === 0;
 }
 function isEmpty( x, args ){
+    if (typeof x === 'boolean') {
+        return false;
+    }
+    if (isNumeric(x)) {
+        return false;
+    }
     if (isString(x)){
         return x === '';
     }
@@ -163,7 +169,7 @@ function isEmpty( x, args ){
         return isEmptyObject(x);
     }
     if (isArray(x)){
-        return isEmptyArray(x);
+        return !x.length;
     }
     // does a function return an 'empty' value?
     if (isFunction(x)){
diff --git a/src/main/webapp/scripts/xnat/ui/table.js b/src/main/webapp/scripts/xnat/ui/table.js
index e503399648f6882683a2e5460e7e338389c0d527..711a971af9f87f40971849b65fe3700280b76ba8 100755
--- a/src/main/webapp/scripts/xnat/ui/table.js
+++ b/src/main/webapp/scripts/xnat/ui/table.js
@@ -367,6 +367,7 @@ var XNAT = getObject(XNAT);
         }
 
         opts.element = extend(true, {
+            id: opts.id || randomID('t', false),
             style: {
                 width: opts.width || '100%'
             }
@@ -375,6 +376,10 @@ var XNAT = getObject(XNAT);
         // initialize the table
         var newTable = new Table(opts.element);
 
+        // create a div to hold the table
+        // or message (if no data or error)
+        var tableContainer = spawn('div.data-table-container', [newTable.table]);
+
         function createTable(rows){
             var props = [], objRows = [];
             // convert object list to array list
@@ -422,13 +427,18 @@ var XNAT = getObject(XNAT);
                 newTable.tr();
                 props.forEach(function(name){
                     var cellObj = { className: name };
-                    var itemObj = item[name];
+                    var itemVal = item[name];
                     if (opts.items && opts.items[name].cells) {
                         extend(true, cellObj, opts.items[name].cells);
                     }
                     else {
-                        cellObj.html = itemObj;
+                        cellObj.html = itemVal;
+                    }
+                    if (cellObj.apply) {
+                        itemVal = eval(cellObj.apply).apply(item, itemVal);
                     }
+                    // special __VALUE__ string gets replaced
+                    cellObj.html = cellObj.html.replace(/__VALUE__/, itemVal);
                     newTable.td(cellObj);
                     if (opts.items[name] === '~') {
                         addClassName(newTable.last.td, 'hidden');
@@ -438,6 +448,26 @@ var XNAT = getObject(XNAT);
             });
         }
 
+
+        function showMessage(){
+            tableContainer.innerHTML = '';
+            return {
+                noData: function(msg){
+                    tableContainer.innerHTML = '' +
+                        '<div class="no-data">' +
+                        (msg || 'Data not available.') +
+                        '</div>';
+                },
+                error: function(msg, error){
+                    tableContainer.innerHTML = '' +
+                        '<div class="error">' +
+                        (msg || '') +
+                        (error ? '<br><br>' + error : '') +
+                        '</div>';
+                }
+            };
+        }
+
         // if 'tableData' is a string, use as the url
         if (typeof tableData == 'string') {
             opts.url = tableData;
@@ -457,7 +487,18 @@ var XNAT = getObject(XNAT);
                         // handle data returned in ResultSet.Result array
                         json = (json.ResultSet && json.ResultSet.Result) ? json.ResultSet.Result : json;
                     }
-                    createTable(json);
+                    // make sure there's data before rendering the table
+                    if (isEmpty(json)) {
+                        showMessage().noData(opts.messages ? opts.messages.noData || opts.messages.empty : '')
+                    }
+                    else {
+                        createTable(json);
+                    }
+                },
+                error: function(obj, status, message){
+                    var _msg = opts.messages ? opts.messages.error : '';
+                    var _err = 'Error: ' + message;
+                    showMessage().error(_msg);
                 }
             });
         }
@@ -467,16 +508,16 @@ var XNAT = getObject(XNAT);
         }
 
         if (opts.container) {
-            $$(opts.container).append(newTable.table);
+            $$(opts.container).append(tableContainer);
         }
 
         // add properties for Spawner compatibility
-        newTable.element = newTable.spawned = newTable.table;
+        newTable.element = newTable.spawned = tableContainer;
         newTable.get = function(){
-            return newTable.table;
+            return tableContainer;
         };
 
-        return newTable;
+        return tableContainer;
 
     };