You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2015/02/04 19:56:59 UTC

[05/36] incubator-nifi git commit: NIFI-307: - Updating how action buttons are invoked in tables throughout the application.

NIFI-307:
- Updating how action buttons are invoked in tables throughout the application.

Project: http://git-wip-us.apache.org/repos/asf/incubator-nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-nifi/commit/9cd9d126
Tree: http://git-wip-us.apache.org/repos/asf/incubator-nifi/tree/9cd9d126
Diff: http://git-wip-us.apache.org/repos/asf/incubator-nifi/diff/9cd9d126

Branch: refs/heads/NIFI-250
Commit: 9cd9d126e34a3039fcf42215c4ad6e67551c8f25
Parents: 037f36d
Author: Matt Gilman <ma...@gmail.com>
Authored: Mon Feb 2 12:44:51 2015 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Mon Feb 2 12:44:51 2015 -0500

----------------------------------------------------------------------
 .../webapp/js/nf/cluster/nf-cluster-table.js    | 319 +++++++-------
 .../webapp/js/nf/counters/nf-counters-table.js  |  63 +--
 .../webapp/js/nf/history/nf-history-table.js    | 147 ++++---
 .../js/nf/provenance/nf-provenance-table.js     | 103 ++---
 .../js/nf/templates/nf-templates-table.js       |  73 ++-
 .../main/webapp/js/nf/users/nf-users-table.js   | 440 +++++++++----------
 6 files changed, 562 insertions(+), 583 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/9cd9d126/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
index 13b0e5f..2968919 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/cluster/nf-cluster-table.js
@@ -110,6 +110,22 @@ nf.ClusterTable = (function () {
     };
 
     /**
+     * Prompts to verify node connection.
+     * 
+     * @argument {object} node     The node
+     */
+    var promptForConnect = function (node) {
+        // prompt to connect
+        nf.Dialog.showYesNoDialog({
+            dialogContent: 'Connect \'' + formatNodeAddress(node) + '\' to this cluster?',
+            overlayBackground: false,
+            yesHandler: function () {
+                connect(node.nodeId);
+            }
+        });
+    };
+
+    /**
      * Connects the node in the specified row.
      * 
      * @argument {string} nodeId     The node id
@@ -133,6 +149,22 @@ nf.ClusterTable = (function () {
     };
 
     /**
+     * Prompts to verify node disconnection.
+     * 
+     * @argument {object} node     The node
+     */
+    var promptForDisconnect = function (node) {
+        // prompt for disconnect
+        nf.Dialog.showYesNoDialog({
+            dialogContent: 'Disconnect \'' + formatNodeAddress(node) + '\' from the cluster?',
+            overlayBackground: false,
+            yesHandler: function () {
+                disconnect(node.nodeId);
+            }
+        });
+    };
+
+    /**
      * Disconnects the node in the specified row.
      * 
      * @argument {string} nodeId     The node id
@@ -156,6 +188,22 @@ nf.ClusterTable = (function () {
     };
 
     /**
+     * Prompts to verify node disconnection.
+     * 
+     * @argument {object} node     The node
+     */
+    var promptForRemoval = function (node) {
+        // prompt for disconnect
+        nf.Dialog.showYesNoDialog({
+            dialogContent: 'Remove \'' + formatNodeAddress(node) + '\' from the cluster?',
+            overlayBackground: false,
+            yesHandler: function () {
+                remove(node.nodeId);
+            }
+        });
+    };
+
+    /**
      * Disconnects the node in the specified row.
      * 
      * @argument {string} nodeId     The node id
@@ -230,6 +278,86 @@ nf.ClusterTable = (function () {
         // perform the filter
         return item[args.property].search(filterExp) >= 0;
     };
+    
+    /**
+     * Show the node details.
+     * 
+     * @argument {object} item     The item
+     */
+    var showNodeDetails = function (item) {
+        $.ajax({
+            type: 'GET',
+            url: config.urls.nodes + '/' + encodeURIComponent(item.nodeId),
+            dataType: 'json'
+        }).done(function (response) {
+            var node = response.node;
+
+            // update the dialog fields
+            $('#node-id').text(node.nodeId);
+            $('#node-address').text(formatNodeAddress(node));
+
+            // format the events
+            var events = $('#node-events');
+            if ($.isArray(node.events) && node.events.length > 0) {
+                var eventMessages = [];
+                $.each(node.events, function (i, event) {
+                    eventMessages.push(event.timestamp + ": " + event.message);
+                });
+                $('<div></div>').append(nf.Common.formatUnorderedList(eventMessages)).appendTo(events);
+            } else {
+                events.append('<div><span class="unset">None</span></div>');
+            }
+
+            // show the dialog
+            $('#node-details-dialog').modal('show');
+        }).fail(nf.Common.handleAjaxError);
+    };
+    
+    /**
+     * Makes the specified node the primary node of the cluster.
+     * 
+     * @argument {object} item     The node item
+     */
+    var makePrimary = function (item) {
+        $.ajax({
+            type: 'PUT',
+            url: config.urls.nodes + '/' + encodeURIComponent(item.nodeId),
+            data: {
+                primary: true
+            },
+            dataType: 'json'
+        }).done(function (response) {
+            var grid = $('#cluster-table').data('gridInstance');
+            var data = grid.getData();
+
+            var node = response.node;
+
+            // start the update
+            data.beginUpdate();
+            data.updateItem(node.nodeId, node);
+
+            // need to find the previous primary node
+            // get the property grid data
+            var clusterItems = data.getItems();
+            $.each(clusterItems, function (i, otherNode) {
+                // attempt to identify the previous primary node
+                if (node.nodeId !== otherNode.nodeId && otherNode.primary === true) {
+                    // reset its primary status
+                    otherNode.primary = false;
+                    otherNode.status = 'CONNECTED';
+
+                    // set the new node state
+                    data.updateItem(otherNode.nodeId, otherNode);
+
+                    // no need to continue processing
+                    return false;
+                }
+            });
+
+            // end the update
+            data.endUpdate();
+        }).fail(nf.Common.handleAjaxError);
+    };
 
     return {
         /**
@@ -292,7 +420,7 @@ nf.ClusterTable = (function () {
 
             // define a custom formatter for the more details column
             var moreDetailsFormatter = function (row, cell, value, columnDef, dataContext) {
-                return '<img src="images/iconDetails.png" title="View Details" class="pointer" style="margin-top: 4px;" onclick="javascript:nf.ClusterTable.showNodeDetails(\'' + row + '\');"/>';
+                return '<img src="images/iconDetails.png" title="View Details" class="pointer show-node-details" style="margin-top: 4px;"/>';
             };
 
             // define a custom formatter for the run status column
@@ -348,11 +476,11 @@ nf.ClusterTable = (function () {
 
                     // return the appropriate markup
                     if (canConnect) {
-                        return '<img src="images/iconConnect.png" title="Connect" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.ClusterTable.promptForConnect(\'' + row + '\');"/>&nbsp;<img src="images/iconDelete.png" title="Remove" class="pointer" onclick="javascript:nf.ClusterTable.promptForRemoval(\'' + row + '\');"/>';
+                        return '<img src="images/iconConnect.png" title="Connect" class="pointer prompt-for-connect" style="margin-top: 2px;"/>&nbsp;<img src="images/iconDelete.png" title="Remove" class="pointer prompt-for-removal"/>';
                     } else if (canDisconnect) {
-                        var actions = '<img src="images/iconDisconnect.png" title="Disconnect" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.ClusterTable.promptForDisconnect(\'' + row + '\');"/>';
+                        var actions = '<img src="images/iconDisconnect.png" title="Disconnect" class="pointer prompt-for-disconnect" style="margin-top: 2px;"/>';
                         if (canBecomePrimary) {
-                            actions += '&nbsp;<img src="images/iconPrimary.png" title="Make Primary" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.ClusterTable.makePrimary(\'' + row + '\');"/>';
+                            actions += '&nbsp;<img src="images/iconPrimary.png" title="Make Primary" class="pointer make-primary" style="margin-top: 2px;"/>';
                         }
                         return actions;
                     } else {
@@ -360,7 +488,7 @@ nf.ClusterTable = (function () {
                     }
                 };
 
-                columnModel.push({id: 'action', label: '&nbsp;', formatter: actionFormatter, resizable: false, sortable: false, width: 80, maxWidth: 80});
+                columnModel.push({id: 'actions', label: '&nbsp;', formatter: actionFormatter, resizable: false, sortable: false, width: 80, maxWidth: 80});
             }
 
             var clusterOptions = {
@@ -398,6 +526,31 @@ nf.ClusterTable = (function () {
                     sortAsc: args.sortAsc
                 }, clusterData);
             });
+            
+            // configure a click listener
+            clusterGrid.onClick.subscribe(function (e, args) {
+                var target = $(e.target);
+
+                // get the node at this row
+                var item = clusterData.getItem(args.row);
+
+                // determine the desired action
+                if (clusterGrid.getColumns()[args.cell].id === 'actions') {
+                    if (target.hasClass('prompt-for-connect')) {
+                        promptForConnect(item);
+                    } else if (target.hasClass('prompt-for-removal')) {
+                        promptForRemoval(item);
+                    } else if (target.hasClass('prompt-for-disconnect')) {
+                        promptForDisconnect(item);
+                    } else if (target.hasClass('make-primary')) {
+                        makePrimary(item);
+                    }
+                } else if (clusterGrid.getColumns()[args.cell].id === 'moreDetails') {
+                    if (target.hasClass('show-node-details')) {
+                        showNodeDetails(item);
+                    }
+                }
+            });
 
             // wire up the dataview to the grid
             clusterData.onRowCountChanged.subscribe(function (e, args) {
@@ -420,122 +573,6 @@ nf.ClusterTable = (function () {
         },
         
         /**
-         * Prompts to verify node connection.
-         * 
-         * @argument {string} row     The row
-         */
-        promptForConnect: function (row) {
-            var grid = $('#cluster-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var node = data.getItem(row);
-
-                // prompt to connect
-                nf.Dialog.showYesNoDialog({
-                    dialogContent: 'Connect \'' + formatNodeAddress(node) + '\' to this cluster?',
-                    overlayBackground: false,
-                    yesHandler: function () {
-                        connect(node.nodeId);
-                    }
-                });
-            }
-
-        },
-        
-        /**
-         * Prompts to verify node disconnection.
-         * 
-         * @argument {string} row     The row
-         */
-        promptForDisconnect: function (row) {
-            var grid = $('#cluster-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var node = data.getItem(row);
-
-                // prompt for disconnect
-                nf.Dialog.showYesNoDialog({
-                    dialogContent: 'Disconnect \'' + formatNodeAddress(node) + '\' from the cluster?',
-                    overlayBackground: false,
-                    yesHandler: function () {
-                        disconnect(node.nodeId);
-                    }
-                });
-            }
-        },
-        
-        /**
-         * Makes the specified node the primary node of the cluster.
-         * 
-         * @argument {string} row     The row
-         */
-        makePrimary: function (row) {
-            var grid = $('#cluster-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
-
-                $.ajax({
-                    type: 'PUT',
-                    url: config.urls.nodes + '/' + encodeURIComponent(item.nodeId),
-                    data: {
-                        primary: true
-                    },
-                    dataType: 'json'
-                }).done(function (response) {
-                    var node = response.node;
-
-                    // start the update
-                    data.beginUpdate();
-                    data.updateItem(node.nodeId, node);
-
-                    // need to find the previous primary node
-                    // get the property grid data
-                    var clusterItems = data.getItems();
-                    $.each(clusterItems, function (i, otherNode) {
-                        // attempt to identify the previous primary node
-                        if (node.nodeId !== otherNode.nodeId && otherNode.primary === true) {
-                            // reset its primary status
-                            otherNode.primary = false;
-                            otherNode.status = 'CONNECTED';
-
-                            // set the new node state
-                            data.updateItem(otherNode.nodeId, otherNode);
-
-                            // no need to continue processing
-                            return false;
-                        }
-                    });
-
-                    // end the update
-                    data.endUpdate();
-                }).fail(nf.Common.handleAjaxError);
-            }
-        },
-        
-        /**
-         * Prompts to verify node disconnection.
-         * 
-         * @argument {string} row     The row
-         */
-        promptForRemoval: function (row) {
-            var grid = $('#cluster-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var node = data.getItem(row);
-
-                // prompt for disconnect
-                nf.Dialog.showYesNoDialog({
-                    dialogContent: 'Remove \'' + formatNodeAddress(node) + '\' from the cluster?',
-                    overlayBackground: false,
-                    yesHandler: function () {
-                        remove(node.nodeId);
-                    }
-                });
-            }
-        },
-        
-        /**
          * Update the size of the grid based on its container's current size.
          */
         resetTableSize: function () {
@@ -575,46 +612,6 @@ nf.ClusterTable = (function () {
                     $('#total-nodes').text('0');
                 }
             }).fail(nf.Common.handleAjaxError);
-        },
-        
-        /**
-         * Populate the expanded row.
-         * 
-         * @argument {string} row     The row
-         */
-        showNodeDetails: function (row) {
-            var grid = $('#cluster-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
-
-                $.ajax({
-                    type: 'GET',
-                    url: config.urls.nodes + '/' + encodeURIComponent(item.nodeId),
-                    dataType: 'json'
-                }).done(function (response) {
-                    var node = response.node;
-
-                    // update the dialog fields
-                    $('#node-id').text(node.nodeId);
-                    $('#node-address').text(formatNodeAddress(node));
-
-                    // format the events
-                    var events = $('#node-events');
-                    if ($.isArray(node.events) && node.events.length > 0) {
-                        var eventMessages = [];
-                        $.each(node.events, function (i, event) {
-                            eventMessages.push(event.timestamp + ": " + event.message);
-                        });
-                        $('<div></div>').append(nf.Common.formatUnorderedList(eventMessages)).appendTo(events);
-                    } else {
-                        events.append('<div><span class="unset">None</span></div>');
-                    }
-
-                    // show the dialog
-                    $('#node-details-dialog').modal('show');
-                }).fail(nf.Common.handleAjaxError);
-            }
         }
     };
 }());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/9cd9d126/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/counters/nf-counters-table.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/counters/nf-counters-table.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/counters/nf-counters-table.js
index 536ffdb..2b26aaa 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/counters/nf-counters-table.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/counters/nf-counters-table.js
@@ -110,6 +110,26 @@ nf.CountersTable = (function () {
         // perform the filter
         return item[args.property].search(filterExp) >= 0;
     };
+    
+    /**
+     * Resets the specified counter.
+     * 
+     * @argument {object} item     The counter item
+     */
+    var resetCounter = function (item) {
+        $.ajax({
+            type: 'PUT',
+            url: config.urls.counters + '/' + encodeURIComponent(item.id),
+            dataType: 'json'
+        }).done(function (response) {
+            var counter = response.counter;
+
+            // get the table and update the row accordingly
+            var countersGrid = $('#counters-table').data('gridInstance');
+            var countersData = countersGrid.getData();
+            countersData.updateItem(counter.id, counter);
+        }).fail(nf.Common.handleAjaxError);
+    };
 
     return {
         /**
@@ -159,7 +179,7 @@ nf.CountersTable = (function () {
             if (nf.Common.isDFM()) {
                 // function for formatting the actions column
                 var actionFormatter = function (row, cell, value, columnDef, dataContext) {
-                    return '<img src="images/iconResetCounter.png" title="Reset" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.CountersTable.resetCounter(\'' + row + '\');"/>';
+                    return '<img src="images/iconResetCounter.png" title="Reset" class="pointer reset-counter" style="margin-top: 2px;"/>';
                 };
 
                 // add the action column
@@ -202,6 +222,21 @@ nf.CountersTable = (function () {
                     sortAsc: args.sortAsc
                 }, countersData);
             });
+            
+            // configure a click listener
+            countersGrid.onClick.subscribe(function (e, args) {
+                var target = $(e.target);
+
+                // get the node at this row
+                var item = countersData.getItem(args.row);
+
+                // determine the desired action
+                if (countersGrid.getColumns()[args.cell].id === 'actions') {
+                    if (target.hasClass('reset-counter')) {
+                        resetCounter(item);
+                    }
+                }
+            });
 
             // wire up the dataview to the grid
             countersData.onRowCountChanged.subscribe(function (e, args) {
@@ -224,32 +259,6 @@ nf.CountersTable = (function () {
         },
         
         /**
-         * Resets the specified counter.
-         * 
-         * @argument {string} row     The row
-         */
-        resetCounter: function (row) {
-            var grid = $('#counters-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
-
-                $.ajax({
-                    type: 'PUT',
-                    url: config.urls.counters + '/' + encodeURIComponent(item.id),
-                    dataType: 'json'
-                }).done(function (response) {
-                    var counter = response.counter;
-
-                    // get the table and update the row accordingly
-                    var countersGrid = $('#counters-table').data('gridInstance');
-                    var countersData = countersGrid.getData();
-                    countersData.updateItem(counter.id, counter);
-                }).fail(nf.Common.handleAjaxError);
-            }
-        },
-        
-        /**
          * Update the size of the grid based on its container's current size.
          */
         resetTableSize: function () {

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/9cd9d126/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/history/nf-history-table.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/history/nf-history-table.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/history/nf-history-table.js
index be0ea73..72fc549 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/history/nf-history-table.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/history/nf-history-table.js
@@ -244,7 +244,7 @@ nf.HistoryTable = (function () {
 
         // define a custom formatter for the more details column
         var moreDetailsFormatter = function (row, cell, value, columnDef, dataContext) {
-            return '<img src="images/iconDetails.png" title="View Details" class="pointer" style="margin-top: 4px;" onclick="javascript:nf.HistoryTable.showActionDetails(\'' + row + '\');"/>';
+            return '<img src="images/iconDetails.png" title="View Details" class="pointer show-action-details" style="margin-top: 4px;"/>';
         };
 
         // initialize the templates table
@@ -283,6 +283,21 @@ nf.HistoryTable = (function () {
         });
         historyGrid.setSortColumn('timestamp', false);
 
+        // configure a click listener
+        historyGrid.onClick.subscribe(function (e, args) {
+            var target = $(e.target);
+
+            // get the node at this row
+            var item = historyModel.getItem(args.row);
+
+            // determine the desired action
+            if (historyGrid.getColumns()[args.cell].id === 'moreDetails') {
+                if (target.hasClass('show-action-details')) {
+                    showActionDetails(item);
+                }
+            }
+        });
+
         // listen for when the viewport changes so we can fetch the appropriate records
         historyGrid.onViewportChanged.subscribe(function (e, args) {
             var vp = historyGrid.getViewport();
@@ -325,6 +340,68 @@ nf.HistoryTable = (function () {
             nf.HistoryTable.loadHistoryTable();
         }).fail(nf.Common.handleAjaxError);
     };
+    
+    /**
+     * Shows the details for the specified action.
+     * 
+     * @param {object} action
+     */
+    var showActionDetails = function (action) {
+        // create the markup for the dialog
+        var detailsMarkup = $('<div></div>').append(
+                $('<div class="action-detail"><div class="history-details-name">Id</div>' + nf.Common.escapeHtml(action.sourceId) + '</div>'));
+
+        // get any component details
+        var componentDetails = action.componentDetails;
+
+        // inspect the operation to determine if there are any component details
+        if (nf.Common.isDefinedAndNotNull(componentDetails)) {
+            if (action.sourceType === 'Processor') {
+                detailsMarkup.append(
+                        $('<div class="action-detail"><div class="history-details-name">Type</div>' + nf.Common.escapeHtml(componentDetails.type) + '</div>'));
+            } else if (action.sourceType === 'RemoteProcessGroup') {
+                detailsMarkup.append(
+                        $('<div class="action-detail"><div class="history-details-name">Uri</div>' + nf.Common.formatValue(componentDetails.uri) + '</div>'));
+            }
+        }
+
+        // get any action details
+        var actionDetails = action.actionDetails;
+
+        // inspect the operation to determine if there are any action details
+        if (nf.Common.isDefinedAndNotNull(actionDetails)) {
+            if (action.operation === 'Configure') {
+                detailsMarkup.append(
+                        $('<div class="action-detail"><div class="history-details-name">Name</div>' + nf.Common.formatValue(actionDetails.name) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Value</div>' + nf.Common.formatValue(actionDetails.value) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Previous Value</div>' + nf.Common.formatValue(actionDetails.previousValue) + '</div>'));
+            } else if (action.operation === 'Connect' || action.operation === 'Disconnect') {
+                detailsMarkup.append(
+                        $('<div class="action-detail"><div class="history-details-name">Source Id</div>' + nf.Common.escapeHtml(actionDetails.sourceId) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Source Name</div>' + nf.Common.formatValue(actionDetails.sourceName) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Source Type</div>' + nf.Common.escapeHtml(actionDetails.sourceType) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Relationship(s)</div>' + nf.Common.formatValue(actionDetails.relationship) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Destination Id</div>' + nf.Common.escapeHtml(actionDetails.destinationId) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Destination Name</div>' + nf.Common.formatValue(actionDetails.destinationName) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Destination Type</div>' + nf.Common.escapeHtml(actionDetails.destinationType) + '</div>'));
+            } else if (action.operation === 'Move') {
+                detailsMarkup.append(
+                        $('<div class="action-detail"><div class="history-details-name">Group</div>' + nf.Common.formatValue(actionDetails.group) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Group Id</div>' + nf.Common.escapeHtml(actionDetails.groupId) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Previous Group</div>' + nf.Common.formatValue(actionDetails.previousGroup) + '</div>')).append(
+                        $('<div class="action-detail"><div class="history-details-name">Previous Group Id</div>' + nf.Common.escapeHtml(actionDetails.previousGroupId) + '</div>'));
+            } else if (action.operation === 'Purge') {
+                detailsMarkup.append(
+                        $('<div class="action-detail"><div class="history-details-name">End Date</div>' + nf.Common.escapeHtml(actionDetails.endDate) + '</div>'));
+            }
+        }
+
+        // populate the dialog
+        $('#action-details').append(detailsMarkup);
+
+        // show the dialog
+        $('#action-details-dialog').modal('show');
+    };
 
     return {
         init: function () {
@@ -356,74 +433,6 @@ nf.HistoryTable = (function () {
 
             // request refresh of the current 'page'
             historyGrid.onViewportChanged.notify();
-        },
-        
-        /**
-         * Shows the details for the specified action.
-         * 
-         * @param {object} index
-         */
-        showActionDetails: function (index) {
-            var historyGrid = $('#history-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(historyGrid)) {
-                var historyModel = historyGrid.getData();
-                var action = historyModel.getItem(index);
-
-                // create the markup for the dialog
-                var detailsMarkup = $('<div></div>').append(
-                        $('<div class="action-detail"><div class="history-details-name">Id</div>' + nf.Common.escapeHtml(action.sourceId) + '</div>'));
-
-                // get any component details
-                var componentDetails = action.componentDetails;
-
-                // inspect the operation to determine if there are any component details
-                if (nf.Common.isDefinedAndNotNull(componentDetails)) {
-                    if (action.sourceType === 'Processor') {
-                        detailsMarkup.append(
-                                $('<div class="action-detail"><div class="history-details-name">Type</div>' + nf.Common.escapeHtml(componentDetails.type) + '</div>'));
-                    } else if (action.sourceType === 'RemoteProcessGroup') {
-                        detailsMarkup.append(
-                                $('<div class="action-detail"><div class="history-details-name">Uri</div>' + nf.Common.formatValue(componentDetails.uri) + '</div>'));
-                    }
-                }
-
-                // get any action details
-                var actionDetails = action.actionDetails;
-
-                // inspect the operation to determine if there are any action details
-                if (nf.Common.isDefinedAndNotNull(actionDetails)) {
-                    if (action.operation === 'Configure') {
-                        detailsMarkup.append(
-                                $('<div class="action-detail"><div class="history-details-name">Name</div>' + nf.Common.formatValue(actionDetails.name) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Value</div>' + nf.Common.formatValue(actionDetails.value) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Previous Value</div>' + nf.Common.formatValue(actionDetails.previousValue) + '</div>'));
-                    } else if (action.operation === 'Connect' || action.operation === 'Disconnect') {
-                        detailsMarkup.append(
-                                $('<div class="action-detail"><div class="history-details-name">Source Id</div>' + nf.Common.escapeHtml(actionDetails.sourceId) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Source Name</div>' + nf.Common.formatValue(actionDetails.sourceName) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Source Type</div>' + nf.Common.escapeHtml(actionDetails.sourceType) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Relationship(s)</div>' + nf.Common.formatValue(actionDetails.relationship) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Destination Id</div>' + nf.Common.escapeHtml(actionDetails.destinationId) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Destination Name</div>' + nf.Common.formatValue(actionDetails.destinationName) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Destination Type</div>' + nf.Common.escapeHtml(actionDetails.destinationType) + '</div>'));
-                    } else if (action.operation === 'Move') {
-                        detailsMarkup.append(
-                                $('<div class="action-detail"><div class="history-details-name">Group</div>' + nf.Common.formatValue(actionDetails.group) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Group Id</div>' + nf.Common.escapeHtml(actionDetails.groupId) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Previous Group</div>' + nf.Common.formatValue(actionDetails.previousGroup) + '</div>')).append(
-                                $('<div class="action-detail"><div class="history-details-name">Previous Group Id</div>' + nf.Common.escapeHtml(actionDetails.previousGroupId) + '</div>'));
-                    } else if (action.operation === 'Purge') {
-                        detailsMarkup.append(
-                                $('<div class="action-detail"><div class="history-details-name">End Date</div>' + nf.Common.escapeHtml(actionDetails.endDate) + '</div>'));
-                    }
-                }
-
-                // populate the dialog
-                $('#action-details').append(detailsMarkup);
-
-                // show the dialog
-                $('#action-details-dialog').modal('show');
-            }
         }
     };
 }());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/9cd9d126/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js
index 759bcda..2880375 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/provenance/nf-provenance-table.js
@@ -553,7 +553,7 @@ nf.ProvenanceTable = (function () {
 
         // define a custom formatter for the more details column
         var moreDetailsFormatter = function (row, cell, value, columnDef, dataContext) {
-            return '<img src="images/iconDetails.png" title="View Details" class="pointer" style="margin-top: 4px;" onclick="javascript:nf.ProvenanceTable.showEventDetailsByIndex(\'' + row + '\');"/>';
+            return '<img src="images/iconDetails.png" title="View Details" class="pointer show-event-details" style="margin-top: 4px;"/>';
         };
 
         // define how general values are formatted
@@ -570,12 +570,12 @@ nf.ProvenanceTable = (function () {
 
             // conditionally include the cluster node id
             if (nf.Common.SUPPORTS_SVG) {
-                markup += '<img src="images/iconLineage.png" title="Show Lineage" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.ProvenanceTable.showLineage(\'' + row + '\');"/>';
+                markup += '<img src="images/iconLineage.png" title="Show Lineage" class="pointer show-lineage" style="margin-top: 2px;"/>';
             }
 
             // conditionally support going to the component
             if (isInShell && nf.Common.isDefinedAndNotNull(dataContext.groupId)) {
-                markup += '&nbsp;<img src="images/iconGoTo.png" title="Go To" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.ProvenanceTable.goTo(\'' + row + '\');"/>';
+                markup += '&nbsp;<img src="images/iconGoTo.png" title="Go To" class="pointer go-to" style="margin-top: 2px;"/>';
             }
 
             return markup;
@@ -599,7 +599,7 @@ nf.ProvenanceTable = (function () {
 
         // conditionally show the action column
         if (nf.Common.SUPPORTS_SVG || isInShell) {
-            provenanceColumns.push({id: 'action', name: '&nbsp;', formatter: showLineageFormatter, resizable: false, sortable: false, width: 50, maxWidth: 50});
+            provenanceColumns.push({id: 'actions', name: '&nbsp;', formatter: showLineageFormatter, resizable: false, sortable: false, width: 50, maxWidth: 50});
         }
 
         var provenanceOptions = {
@@ -641,6 +641,27 @@ nf.ProvenanceTable = (function () {
                 sortAsc: args.sortAsc
             }, provenanceData);
         });
+        
+        // configure a click listener
+        provenanceGrid.onClick.subscribe(function (e, args) {
+            var target = $(e.target);
+
+            // get the node at this row
+            var item = provenanceData.getItem(args.row);
+
+            // determine the desired action
+            if (provenanceGrid.getColumns()[args.cell].id === 'actions') {
+                if (target.hasClass('show-lineage')) {
+                    nf.ProvenanceLineage.showLineage(item.flowFileUuid, item.eventId.toString(), item.clusterNodeId);
+                } else if (target.hasClass('go-to')) {
+                    goTo(item);
+                }
+            } else if (provenanceGrid.getColumns()[args.cell].id === 'moreDetails') {
+                if (target.hasClass('show-event-details')) {
+                    nf.ProvenanceTable.showEventDetails(item);
+                }
+            }
+        });
 
         // wire up the dataview to the grid
         provenanceData.onRowCountChanged.subscribe(function (e, args) {
@@ -884,6 +905,25 @@ nf.ProvenanceTable = (function () {
         }
     };
 
+    /**
+     * Goes to the specified component if possible.
+     * 
+     * @argument {object} item       The event it
+     */
+    var goTo = function (item) {
+        // ensure the component is still present in the flow
+        if (nf.Common.isDefinedAndNotNull(item.groupId)) {
+            // only attempt this if we're within a frame
+            if (top !== window) {
+                // and our parent has canvas utils and shell defined
+                if (nf.Common.isDefinedAndNotNull(parent.nf) && nf.Common.isDefinedAndNotNull(parent.nf.CanvasUtils) && nf.Common.isDefinedAndNotNull(parent.nf.Shell)) {
+                    parent.nf.CanvasUtils.showComponent(item.groupId, item.componentId);
+                    parent.$('#shell-close-button').click();
+                }
+            }
+        }
+    };
+
     return {
         /**
          * The max delay between requests.
@@ -910,31 +950,6 @@ nf.ProvenanceTable = (function () {
         },
         
         /**
-         * Goes to the specified component if possible.
-         * 
-         * @argument {string} row       The row
-         */
-        goTo: function (row) {
-            var grid = $('#provenance-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
-
-                // ensure the component is still present in the flow
-                if (nf.Common.isDefinedAndNotNull(item.groupId)) {
-                    // only attempt this if we're within a frame
-                    if (top !== window) {
-                        // and our parent has canvas utils and shell defined
-                        if (nf.Common.isDefinedAndNotNull(parent.nf) && nf.Common.isDefinedAndNotNull(parent.nf.CanvasUtils) && nf.Common.isDefinedAndNotNull(parent.nf.Shell)) {
-                            parent.nf.CanvasUtils.showComponent(item.groupId, item.componentId);
-                            parent.$('#shell-close-button').click();
-                        }
-                    }
-                }
-            }
-        },
-        
-        /**
          * Update the size of the grid based on its container's current size.
          */
         resetTableSize: function () {
@@ -1096,36 +1111,6 @@ nf.ProvenanceTable = (function () {
         },
         
         /**
-         * Shows the lineage for the event in the specified row.
-         * 
-         * @param {type} row
-         */
-        showLineage: function (row) {
-            var grid = $('#provenance-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
-                nf.ProvenanceLineage.showLineage(item.flowFileUuid, item.eventId.toString(), item.clusterNodeId);
-            }
-        },
-        
-        /**
-         * Gets the event details and shows the details dialog.
-         * 
-         * @param {long} index
-         */
-        showEventDetailsByIndex: function (index) {
-            var provenanceGrid = $('#provenance-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(provenanceGrid)) {
-                var provenanceModel = provenanceGrid.getData();
-                var event = provenanceModel.getItem(index);
-
-                // show the event details
-                nf.ProvenanceTable.showEventDetails(event);
-            }
-        },
-        
-        /**
          * Shows the details for the specified action.
          * 
          * @param {object} event

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/9cd9d126/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
index 1756207..ec8f49e 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
@@ -54,6 +54,22 @@ nf.TemplatesTable = (function () {
     };
 
     /**
+     * Prompts the user before attempting to delete the specified template.
+     * 
+     * @argument {object} template     The template
+     */
+    var promptToDeleteTemplate = function (template) {
+        // prompt for deletion
+        nf.Dialog.showYesNoDialog({
+            dialogContent: 'Delete template \'' + nf.Common.escapeHtml(template.name) + '\'?',
+            overlayBackground: false,
+            yesHandler: function () {
+                deleteTemplate(template.id);
+            }
+        });
+    };
+
+    /**
      * Deletes the template with the specified id.
      * 
      * @argument {string} templateId     The template id
@@ -177,11 +193,11 @@ nf.TemplatesTable = (function () {
 
             // function for formatting the actions column
             var actionFormatter = function (row, cell, value, columnDef, dataContext) {
-                var markup = '<img src="images/iconExport.png" title="Download" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.TemplatesTable.exportTemplate(\'' + row + '\');"/>';
+                var markup = '<img src="images/iconExport.png" title="Download" class="pointer export-template" style="margin-top: 2px;"/>';
 
                 // all DFMs to remove templates
                 if (nf.Common.isDFM()) {
-                    markup += '&nbsp;<img src="images/iconDelete.png" title="Remove Template" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.TemplatesTable.promptToDeleteTemplate(\'' + row + '\');"/>';
+                    markup += '&nbsp;<img src="images/iconDelete.png" title="Remove Template" class="pointer prompt-to-delete-template" style="margin-top: 2px;"/>';
                 }
                 return markup;
             };
@@ -230,6 +246,23 @@ nf.TemplatesTable = (function () {
                 }, templatesData);
             });
 
+            // configure a click listener
+            templatesGrid.onClick.subscribe(function (e, args) {
+                var target = $(e.target);
+
+                // get the node at this row
+                var item = templatesData.getItem(args.row);
+
+                // determine the desired action
+                if (templatesGrid.getColumns()[args.cell].id === 'actions') {
+                    if (target.hasClass('export-template')) {
+                        window.open(config.urls.templates + '/' + encodeURIComponent(item.id));
+                    } else if (target.hasClass('prompt-to-delete-template')) {
+                        promptToDeleteTemplate(item);
+                    }
+                }
+            });
+
             // wire up the dataview to the grid
             templatesData.onRowCountChanged.subscribe(function (e, args) {
                 templatesGrid.updateRowCount();
@@ -261,42 +294,6 @@ nf.TemplatesTable = (function () {
         },
         
         /**
-         * Exports the specified template.
-         * 
-         * @argument {string} row     The row
-         */
-        exportTemplate: function (row) {
-            var grid = $('#templates-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
-                window.open(config.urls.templates + '/' + encodeURIComponent(item.id));
-            }
-        },
-        
-        /**
-         * Prompts the user before attempting to delete the specified template.
-         * 
-         * @argument {string} row     The row
-         */
-        promptToDeleteTemplate: function (row) {
-            var grid = $('#templates-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var template = data.getItem(row);
-
-                // prompt for deletion
-                nf.Dialog.showYesNoDialog({
-                    dialogContent: 'Delete template \'' + nf.Common.escapeHtml(template.name) + '\'?',
-                    overlayBackground: false,
-                    yesHandler: function () {
-                        deleteTemplate(template.id);
-                    }
-                });
-            }
-        },
-        
-        /**
          * Load the processor templates table.
          */
         loadTemplatesTable: function () {

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/9cd9d126/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
index 996544f..88ea225 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
@@ -483,7 +483,7 @@ nf.UsersTable = (function () {
 
         // define a custom formatter for the more details column
         var moreDetailsFormatter = function (row, cell, value, columnDef, dataContext) {
-            return '<img src="images/iconDetails.png" title="View Details" class="pointer" style="margin-top: 4px;" onclick="javascript:nf.UsersTable.showUserDetails(\'' + row + '\');"/>';
+            return '<img src="images/iconDetails.png" title="View Details" class="pointer show-user-details" style="margin-top: 4px;"/>';
         };
 
         // function for formatting the last accessed time
@@ -566,20 +566,20 @@ nf.UsersTable = (function () {
 
             // if this represents a grouped row
             if (nf.Common.isDefinedAndNotNull(dataContext.userGroup) && grouped) {
-                var actions = '<img src="images/iconEdit.png" title="Edit Access" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.UsersTable.updateGroupAccess(\'' + row + '\');"/>&nbsp;<img src="images/iconRevoke.png" title="Revoke Access" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.UsersTable.revokeGroupAccess(\'' + row + '\');"/>&nbsp;&nbsp;<img src="images/ungroup.png" title="Ungroup" class="pointer" onclick="javascript:nf.UsersTable.ungroup(\'' + row + '\');"/>';
+                var actions = '<img src="images/iconEdit.png" title="Edit Access" class="pointer update-group-access" style="margin-top: 2px;"/>&nbsp;<img src="images/iconRevoke.png" title="Revoke Access" class="pointer revoke-group-access" style="margin-top: 2px;"/>&nbsp;&nbsp;<img src="images/ungroup.png" title="Ungroup" class="pointer ungroup"/>';
             } else {
                 // return the appropriate markup for an individual user
-                var actions = '<img src="images/iconEdit.png" title="Edit Access" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.UsersTable.updateUserAccess(\'' + row + '\');"/>';
+                var actions = '<img src="images/iconEdit.png" title="Edit Access" class="pointer update-user-access" style="margin-top: 2px;"/>';
 
                 if (dataContext.status === 'ACTIVE') {
-                    actions += '&nbsp;<img src="images/iconRevoke.png" title="Revoke Access" class="pointer" onclick="javascript:nf.UsersTable.revokeUserAccess(\'' + row + '\');"/>';
+                    actions += '&nbsp;<img src="images/iconRevoke.png" title="Revoke Access" class="pointer revoke-user-access"/>';
 
                     // add an ungroup active if appropriate
                     if (nf.Common.isDefinedAndNotNull(dataContext.userGroup)) {
-                        actions += '&nbsp;&nbsp;<img src="images/ungroup.png" title="Ungroup" class="pointer" style="margin-top: 2px;" onclick="javascript:nf.UsersTable.ungroupUser(\'' + row + '\');"/>';
+                        actions += '&nbsp;&nbsp;<img src="images/ungroup.png" title="Ungroup" class="pointer ungroup-user" style="margin-top: 2px;"/>';
                     }
                 } else {
-                    actions += '&nbsp;<img src="images/iconDelete.png" title="Delete Account" class="pointer" onclick="javascript:nf.UsersTable.deleteUserAccount(\'' + row + '\');"/>';
+                    actions += '&nbsp;<img src="images/iconDelete.png" title="Delete Account" class="pointer delete-user-account"/>';
                 }
             }
 
@@ -632,6 +632,37 @@ nf.UsersTable = (function () {
                 sortAsc: args.sortAsc
             }, usersData);
         });
+        
+        // configure a click listener
+        usersGrid.onClick.subscribe(function (e, args) {
+            var target = $(e.target);
+
+            // get the node at this row
+            var item = usersData.getItem(args.row);
+
+            // determine the desired action
+            if (usersGrid.getColumns()[args.cell].id === 'actions') {
+                if (target.hasClass('update-group-access')) {
+                    updateGroupAccess(item);
+                } else if (target.hasClass('revoke-group-access')) {
+                    revokeGroupAccess(item);
+                } else if (target.hasClass('ungroup')) {
+                    ungroup(item);
+                } else if (target.hasClass('update-user-access')) {
+                    updateUserAccess(item);
+                } else if (target.hasClass('revoke-user-access')) {
+                    revokeUserAccess(item);
+                } else if (target.hasClass('ungroup-user')) {
+                    ungroupUser(item);
+                } else if (target.hasClass('delete-user-account')) {
+                    deleteUserAccount(item);
+                }
+            } else if (usersGrid.getColumns()[args.cell].id === 'moreDetails') {
+                if (target.hasClass('show-user-details')) {
+                    showUserDetails(item);
+                }
+            }
+        });
 
         // wire up the dataview to the grid
         usersData.onRowCountChanged.subscribe(function (e, args) {
@@ -804,193 +835,192 @@ nf.UsersTable = (function () {
         }
     };
 
-    return {
-        init: function () {
-            initUserDetailsDialog();
-            initUserRolesDialog();
-            initGroupRolesDialog();
-            initUserRevokeDialog();
-            initUserDeleteDialog();
-            initUserGroupDialog();
-            initGroupRevokeDialog();
-            initUsersTable();
-        },
-        
-        /**
-         * Disables the specified user's account.
-         * 
-         * @argument {string} row        The row
-         */
-        revokeUserAccess: function (row) {
-            var grid = $('#users-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
+    /**
+     * Shows details for the specified user.
+     * 
+     * @param {object} user
+     */
+    var showUserDetails = function (user) {
+        var grouped = $('#group-collaspe-checkbox').hasClass('checkbox-checked');
+
+        // update the dialog fields
+        $('#user-name-details-dialog').text(user.userName);
+        $('#user-dn-details-dialog').text(user.dn);
+
+        // handle fields that could vary for groups
+        if (nf.Common.isDefinedAndNotNull(user.creation)) {
+            $('#user-created-details-dialog').text(user.creation);
+        } else if (grouped && nf.Common.isDefinedAndNotNull(user.userGroup)) {
+            $('#user-created-details-dialog').html('<span class="unset">Multiple users with different creation timestamps.</span>');
+        } else {
+            $('#user-created-details-dialog').html('<span class="unset">No creation timestamp set</span>');
+        }
 
-                // populate the users info
-                $('#user-id-revoke-dialog').val(item.id);
-                $('#user-name-revoke-dialog').text(item.userName);
+        if (nf.Common.isDefinedAndNotNull(user.lastVerified)) {
+            $('#user-verified-details-dialog').text(user.lastVerified);
+        } else if (grouped && nf.Common.isDefinedAndNotNull(user.userGroup)) {
+            $('#user-verified-details-dialog').html('<span class="unset">Multiple users with different last verified timestamps.</span>');
+        } else {
+            $('#user-verified-details-dialog').html('<span class="unset">No last verified timestamp set.</span>');
+        }
 
-                // show the dialog
-                $('#user-revoke-dialog').modal('show');
-            }
-        },
-        
-        /**
-         * Delete's the specified user's account.
-         * 
-         * @argument {string} row        The row
-         */
-        deleteUserAccount: function (row) {
-            var grid = $('#users-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
+        if (nf.Common.isDefinedAndNotNull(user.justification)) {
+            $('#user-justification-details-dialog').text(user.justification);
+        } else if (grouped && nf.Common.isDefinedAndNotNull(user.userGroup)) {
+            $('#user-justification-details-dialog').html('<span class="unset">Multiple users with different justifications.</span>');
+        } else {
+            $('#user-justification-details-dialog').html('<span class="unset">No justification set.</span>');
+        }
 
-                // populate the users info
-                $('#user-id-delete-dialog').val(item.id);
-                $('#user-name-delete-dialog').text(item.userName);
+        // show the dialog
+        $('#user-details-dialog').modal('show');
+    };
+    
+    /**
+     * Updates the specified groups level of access.
+     * 
+     * @argument {object} item        The user item
+     */
+    var updateGroupAccess = function (item) {
+        // record the current group
+        $('#group-name-roles-dialog').text(item.userGroup);
 
-                // show the dialog
-                $('#user-delete-dialog').modal('show');
-            }
-        },
-        
-        /**
-         * Disables the specified group's account.
-         * 
-         * @argument {string} row        The row
-         */
-        revokeGroupAccess: function (row) {
-            var grid = $('#users-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
+        // show the dialog
+        $('#group-roles-dialog').modal('show');
+    };
+    
+    /**
+     * Disables the specified group's account.
+     * 
+     * @argument {object} item        The user item
+     */
+    var revokeGroupAccess = function (item) {
+        // record the current group
+        $('#group-name-revoke-dialog').text(item.userGroup);
 
-                // record the current group
-                $('#group-name-revoke-dialog').text(item.userGroup);
+        // show the dialog
+        $('#group-revoke-dialog').modal('show');
+    };
 
-                // show the dialog
-                $('#group-revoke-dialog').modal('show');
+    /**
+     * Ungroups the specified group.
+     * 
+     * @argument {object} item        The user item
+     */
+    var ungroup = function (item) {
+        // prompt for ungroup
+        nf.Dialog.showYesNoDialog({
+            dialogContent: 'Remove all users from group \'' + nf.Common.escapeHtml(item.userGroup) + '\'?',
+            overlayBackground: false,
+            yesHandler: function () {
+                $.ajax({
+                    type: 'DELETE',
+                    url: config.urls.userGroups + '/' + encodeURIComponent(item.userGroup),
+                    dataType: 'json'
+                }).done(function (response) {
+                    nf.UsersTable.loadUsersTable();
+                }).fail(nf.Common.handleAjaxError);
             }
-        },
-        
-        /**
-         * Updates the specified users's level of access.
-         * 
-         * @argument {string} row        The row
-         */
-        updateUserAccess: function (row) {
-            var grid = $('#users-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
-
-                // populate the user info
-                $('#user-id-roles-dialog').val(item.id);
-                $('#user-name-roles-dialog').attr('title', item.dn).text(item.userName);
-                $('#user-justification-roles-dialog').html(nf.Common.formatValue(item.justification));
-
-                // function for checking a checkbox
-                var check = function (domId) {
-                    $('#' + domId).removeClass('checkbox-unchecked').addClass('checkbox-checked');
-                };
-
-                // go through each user role
-                $.each(item.authorities, function (i, authority) {
-                    if (authority === 'ROLE_ADMIN') {
-                        check('role-admin-checkbox');
-                    } else if (authority === 'ROLE_DFM') {
-                        check('role-dfm-checkbox');
-                    } else if (authority === 'ROLE_PROVENANCE') {
-                        check('role-provenance-checkbox');
-                    } else if (authority === 'ROLE_MONITOR') {
-                        check('role-monitor-checkbox');
-                    } else if (authority === 'ROLE_NIFI') {
-                        check('role-nifi-checkbox');
-                    } else if (authority === 'ROLE_PROXY') {
-                        check('role-proxy-checkbox');
-                    }
-                });
+        });
+    };
+    
+    /**
+     * Updates the specified users's level of access.
+     * 
+     * @argument {object} item        The user item
+     */
+    var updateUserAccess = function (item) {
+        // populate the user info
+        $('#user-id-roles-dialog').val(item.id);
+        $('#user-name-roles-dialog').attr('title', item.dn).text(item.userName);
+        $('#user-justification-roles-dialog').html(nf.Common.formatValue(item.justification));
+
+        // function for checking a checkbox
+        var check = function (domId) {
+            $('#' + domId).removeClass('checkbox-unchecked').addClass('checkbox-checked');
+        };
 
-                // show the dialog
-                $('#user-roles-dialog').modal('show');
+        // go through each user role
+        $.each(item.authorities, function (i, authority) {
+            if (authority === 'ROLE_ADMIN') {
+                check('role-admin-checkbox');
+            } else if (authority === 'ROLE_DFM') {
+                check('role-dfm-checkbox');
+            } else if (authority === 'ROLE_PROVENANCE') {
+                check('role-provenance-checkbox');
+            } else if (authority === 'ROLE_MONITOR') {
+                check('role-monitor-checkbox');
+            } else if (authority === 'ROLE_NIFI') {
+                check('role-nifi-checkbox');
+            } else if (authority === 'ROLE_PROXY') {
+                check('role-proxy-checkbox');
             }
-        },
-        
-        /**
-         * Updates the specified groups level of access.
-         * 
-         * @argument {string} row        The row
-         */
-        updateGroupAccess: function (row) {
-            var grid = $('#users-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
+        });
 
-                // record the current group
-                $('#group-name-roles-dialog').text(item.userGroup);
+        // show the dialog
+        $('#user-roles-dialog').modal('show');
+    };
+    
+    /**
+     * Disables the specified user's account.
+     * 
+     * @argument {object} item        The user item
+     */
+    var revokeUserAccess = function (item) {
+        // populate the users info
+        $('#user-id-revoke-dialog').val(item.id);
+        $('#user-name-revoke-dialog').text(item.userName);
 
-                // show the dialog
-                $('#group-roles-dialog').modal('show');
-            }
-        },
-        
-        /**
-         * Prompts to verify group removal.
-         * 
-         * @argument {string} row        The row
-         */
-        ungroupUser: function (row) {
-            var grid = $('#users-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
-
-                // prompt for ungroup
-                nf.Dialog.showYesNoDialog({
-                    dialogContent: 'Remove user \'' + nf.Common.escapeHtml(item.userName) + '\' from group \'' + nf.Common.escapeHtml(item.userGroup) + '\'?',
-                    overlayBackground: false,
-                    yesHandler: function () {
-                        $.ajax({
-                            type: 'DELETE',
-                            url: config.urls.userGroups + '/' + encodeURIComponent(item.userGroup) + '/users/' + encodeURIComponent(item.id),
-                            dataType: 'json'
-                        }).done(function (response) {
-                            nf.UsersTable.loadUsersTable();
-                        }).fail(nf.Common.handleAjaxError);
-                    }
-                });
-            }
-        },
-        
-        /**
-         * Ungroups the specified group.
-         * 
-         * @argument {string} row        The row
-         */
-        ungroup: function (row) {
-            var grid = $('#users-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(grid)) {
-                var data = grid.getData();
-                var item = data.getItem(row);
-
-                // prompt for ungroup
-                nf.Dialog.showYesNoDialog({
-                    dialogContent: 'Remove all users from group \'' + nf.Common.escapeHtml(item.userGroup) + '\'?',
-                    overlayBackground: false,
-                    yesHandler: function () {
-                        $.ajax({
-                            type: 'DELETE',
-                            url: config.urls.userGroups + '/' + encodeURIComponent(item.userGroup),
-                            dataType: 'json'
-                        }).done(function (response) {
-                            nf.UsersTable.loadUsersTable();
-                        }).fail(nf.Common.handleAjaxError);
-                    }
-                });
+        // show the dialog
+        $('#user-revoke-dialog').modal('show');
+    };
+    
+    /**
+     * Prompts to verify group removal.
+     * 
+     * @argument {object} item        The user item
+     */
+    var ungroupUser = function (item) {
+        // prompt for ungroup
+        nf.Dialog.showYesNoDialog({
+            dialogContent: 'Remove user \'' + nf.Common.escapeHtml(item.userName) + '\' from group \'' + nf.Common.escapeHtml(item.userGroup) + '\'?',
+            overlayBackground: false,
+            yesHandler: function () {
+                $.ajax({
+                    type: 'DELETE',
+                    url: config.urls.userGroups + '/' + encodeURIComponent(item.userGroup) + '/users/' + encodeURIComponent(item.id),
+                    dataType: 'json'
+                }).done(function (response) {
+                    nf.UsersTable.loadUsersTable();
+                }).fail(nf.Common.handleAjaxError);
             }
+        });
+    };
+
+    /**
+     * Delete's the specified user's account.
+     * 
+     * @argument {object} item        The user item
+     */
+    var deleteUserAccount = function (item) {
+        // populate the users info
+        $('#user-id-delete-dialog').val(item.id);
+        $('#user-name-delete-dialog').text(item.userName);
+
+        // show the dialog
+        $('#user-delete-dialog').modal('show');
+    };
+
+    return {
+        init: function () {
+            initUserDetailsDialog();
+            initUserRolesDialog();
+            initGroupRolesDialog();
+            initUserRevokeDialog();
+            initUserDeleteDialog();
+            initUserGroupDialog();
+            initGroupRevokeDialog();
+            initUsersTable();
         },
         
         /**
@@ -1037,54 +1067,6 @@ nf.UsersTable = (function () {
                     $('#total-users').text('0');
                 }
             }).fail(nf.Common.handleAjaxError);
-        },
-        
-        /**
-         * Shows details for the specified user.
-         * 
-         * @param {string} row
-         */
-        showUserDetails: function (row) {
-            var usersGrid = $('#users-table').data('gridInstance');
-            if (nf.Common.isDefinedAndNotNull(usersGrid)) {
-                var usersData = usersGrid.getData();
-
-                // get the user
-                var user = usersData.getItem(row);
-                var grouped = $('#group-collaspe-checkbox').hasClass('checkbox-checked');
-
-                // update the dialog fields
-                $('#user-name-details-dialog').text(user.userName);
-                $('#user-dn-details-dialog').text(user.dn);
-
-                // handle fields that could vary for groups
-                if (nf.Common.isDefinedAndNotNull(user.creation)) {
-                    $('#user-created-details-dialog').text(user.creation);
-                } else if (grouped && nf.Common.isDefinedAndNotNull(user.userGroup)) {
-                    $('#user-created-details-dialog').html('<span class="unset">Multiple users with different creation timestamps.</span>');
-                } else {
-                    $('#user-created-details-dialog').html('<span class="unset">No creation timestamp set</span>');
-                }
-
-                if (nf.Common.isDefinedAndNotNull(user.lastVerified)) {
-                    $('#user-verified-details-dialog').text(user.lastVerified);
-                } else if (grouped && nf.Common.isDefinedAndNotNull(user.userGroup)) {
-                    $('#user-verified-details-dialog').html('<span class="unset">Multiple users with different last verified timestamps.</span>');
-                } else {
-                    $('#user-verified-details-dialog').html('<span class="unset">No last verified timestamp set.</span>');
-                }
-
-                if (nf.Common.isDefinedAndNotNull(user.justification)) {
-                    $('#user-justification-details-dialog').text(user.justification);
-                } else if (grouped && nf.Common.isDefinedAndNotNull(user.userGroup)) {
-                    $('#user-justification-details-dialog').html('<span class="unset">Multiple users with different justifications.</span>');
-                } else {
-                    $('#user-justification-details-dialog').html('<span class="unset">No justification set.</span>');
-                }
-
-                // show the dialog
-                $('#user-details-dialog').modal('show');
-            }
         }
     };
 }());
\ No newline at end of file