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 2016/12/22 21:52:15 UTC

nifi git commit: [NIFI-96] add align horizontal and align vertical capability to components on the canvas. This closes #1354

Repository: nifi
Updated Branches:
  refs/heads/master e65aad8fe -> 5ea17d30c


[NIFI-96] add align horizontal and align vertical capability to components on the canvas. This closes #1354


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

Branch: refs/heads/master
Commit: 5ea17d30c5858fd9e56498e01a6cbd9fa3423c73
Parents: e65aad8
Author: Scott Aslan <sc...@gmail.com>
Authored: Thu Dec 22 16:06:38 2016 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Thu Dec 22 16:47:34 2016 -0500

----------------------------------------------------------------------
 .../src/main/webapp/css/common-ui.css           |   5 +
 .../src/main/webapp/js/nf/canvas/nf-actions.js  | 148 ++++++++-
 .../main/webapp/js/nf/canvas/nf-canvas-utils.js |  28 ++
 .../main/webapp/js/nf/canvas/nf-context-menu.js |  15 +-
 .../main/webapp/js/nf/canvas/nf-draggable.js    | 297 ++++++++++---------
 5 files changed, 353 insertions(+), 140 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/5ea17d30/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css
index a0f6af7..e871537 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css
@@ -97,6 +97,11 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
     margin: -2px;
 }
 
+/*shift rotated font awesome icons*/
+.fa-rotate-90 {
+    left: -2px !important;
+}
+
 body {
     display: block;
     font-family: Roboto, sans-serif;

http://git-wip-us.apache.org/repos/asf/nifi/blob/5ea17d30/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
index a9c05b8..b27e291 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
@@ -75,6 +75,13 @@ nf.Actions = (function () {
         });
     };
 
+    // determine if the source of this connection is part of the selection
+    var isSourceSelected = function (connection, selection) {
+        return selection.filter(function (d) {
+                return nf.CanvasUtils.getConnectionSourceComponentId(connection) === d.id;
+            }).size() > 0;
+    };
+
     return {
         /**
          * Initializes the actions.
@@ -867,7 +874,7 @@ nf.Actions = (function () {
 
                             // refresh the birdseye
                             nf.Birdseye.refresh();
-                            
+
                             // inform Angular app values have changed
                             nf.ng.Bridge.digest();
                         }).fail(nf.Common.handleAjaxError);
@@ -1095,6 +1102,145 @@ nf.Actions = (function () {
         },
 
         /**
+         * Aligns the components in the specified selection vertically along the center of the components.
+         *
+         * @param {array} selection      The selection
+         */
+        alignVertical: function (selection) {
+            var updates = d3.map();
+            // ensure every component is writable
+            if (nf.CanvasUtils.canModify(selection) === false) {
+                nf.Dialog.showOkDialog({
+                    headerText: 'Component Position',
+                    dialogContent: 'Must be authorized to modify every component selected.'
+                });
+                return;
+            }
+            // determine the extent
+            var minX = null, maxX = null;
+            selection.each(function (d) {
+                if (d.type !== "Connection") {
+                    if (minX === null || d.position.x < minX) {
+                        minX = d.position.x;
+                    }
+                    var componentMaxX = d.position.x + d.dimensions.width;
+                    if (maxX === null || componentMaxX > maxX) {
+                        maxX = componentMaxX;
+                    }
+                }
+            });
+            var center = (minX + maxX) / 2;
+
+            // align all components left
+            selection.each(function(d) {
+                if (d.type !== "Connection") {
+                    var delta = {
+                        x: center - (d.position.x + d.dimensions.width / 2),
+                        y: 0
+                    };
+                    // if this component is already centered, no need to updated it
+                    if (delta.x !== 0) {
+                        // consider any connections
+                        var connections = nf.Connection.getComponentConnections(d.id);
+                        $.each(connections, function(_, connection) {
+                            var connectionSelection = d3.select('#id-' + connection.id);
+
+                            if (!updates.has(connection.id) && nf.CanvasUtils.getConnectionSourceComponentId(connection) === nf.CanvasUtils.getConnectionDestinationComponentId(connection)) {
+                                // this connection is self looping and hasn't been updated by the delta yet
+                                var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
+                                if (connectionUpdate !== null) {
+                                    updates.set(connection.id, connectionUpdate);
+                                }
+                            } else if (!updates.has(connection.id) && connectionSelection.classed('selected') && nf.CanvasUtils.canModify(connectionSelection)) {
+                                // this is a selected connection that hasn't been updated by the delta yet
+                                if (nf.CanvasUtils.getConnectionSourceComponentId(connection) === d.id || !isSourceSelected(connection, selection)) {
+                                    // the connection is either outgoing or incoming when the source of the connection is not part of the selection
+                                    var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
+                                    if (connectionUpdate !== null) {
+                                        updates.set(connection.id, connectionUpdate);
+                                    }
+                                }
+                            }
+                        });
+                        updates.set(d.id, nf.Draggable.updateComponentPosition(d, delta));
+                    }
+                }
+            });
+            nf.Draggable.refreshConnections(updates);
+        },
+
+        /**
+         * Aligns the components in the specified selection horizontally along the center of the components.
+         *
+         * @param {array} selection      The selection
+         */
+        alignHorizontal: function (selection) {
+            var updates = d3.map();
+            // ensure every component is writable
+            if (nf.CanvasUtils.canModify(selection) === false) {
+                nf.Dialog.showOkDialog({
+                    headerText: 'Component Position',
+                    dialogContent: 'Must be authorized to modify every component selected.'
+                });
+                return;
+            }
+
+            // determine the extent
+            var minY = null, maxY = null;
+            selection.each(function (d) {
+                if (d.type !== "Connection") {
+                    if (minY === null || d.position.y < minY) {
+                        minY = d.position.y;
+                    }
+                    var componentMaxY = d.position.y + d.dimensions.height;
+                    if (maxY === null || componentMaxY > maxY) {
+                        maxY = componentMaxY;
+                    }
+                }
+            });
+            var center = (minY + maxY) / 2;
+
+            // align all components with top most component
+            selection.each(function(d) {
+                if (d.type !== "Connection") {
+                    var delta = {
+                        x: 0,
+                        y: center - (d.position.y + d.dimensions.height / 2)
+                    };
+
+                    // if this component is already centered, no need to updated it
+                    if (delta.y !== 0) {
+                        // consider any connections
+                        var connections = nf.Connection.getComponentConnections(d.id);
+                        $.each(connections, function(_, connection) {
+                            var connectionSelection = d3.select('#id-' + connection.id);
+
+                            if (!updates.has(connection.id) && nf.CanvasUtils.getConnectionSourceComponentId(connection) === nf.CanvasUtils.getConnectionDestinationComponentId(connection)) {
+                                // this connection is self looping and hasn't been updated by the delta yet
+                                var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
+                                if (connectionUpdate !== null) {
+                                    updates.set(connection.id, connectionUpdate);
+                                }
+                            } else if (!updates.has(connection.id) && connectionSelection.classed('selected') && nf.CanvasUtils.canModify(connectionSelection)) {
+                                // this is a selected connection that hasn't been updated by the delta yet
+                                if (nf.CanvasUtils.getConnectionSourceComponentId(connection) === d.id || !isSourceSelected(connection, selection)) {
+                                    // the connection is either outgoing or incoming when the source of the connection is not part of the selection
+                                    var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
+                                    if (connectionUpdate !== null) {
+                                        updates.set(connection.id, connectionUpdate);
+                                    }
+                                }
+                            }
+                        });
+                        updates.set(d.id, nf.Draggable.updateComponentPosition(d, delta));
+                    }
+                }
+            });
+
+            nf.Draggable.refreshConnections(updates);
+        },
+
+        /**
          * Opens the fill color dialog for the component in the specified selection.
          *
          * @param {type} selection      The selection

http://git-wip-us.apache.org/repos/asf/nifi/blob/5ea17d30/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
index 1fb262e..e743d3d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
@@ -545,6 +545,34 @@ nf.CanvasUtils = (function () {
                 tip.style('display', 'none');
             });
         },
+
+        /**
+         * Determines if the specified selection is alignable (in a single action).
+         *
+         * @param {selection} selection     The selection
+         * @returns {boolean}
+         */
+        canAlign: function(selection) {
+            var canAlign = true;
+
+            // determine if the current selection is entirely connections
+            var selectedConnections = selection.filter(function(d) {
+                var connection = d3.select(this);
+                return nf.CanvasUtils.isConnection(connection);
+            });
+
+            // require multiple selections besides connections
+            if (selection.size() - selectedConnections.size() < 2) {
+                canAlign = false;
+            }
+
+            // require write permissions
+            if (nf.CanvasUtils.canModify(selection) === false) {
+                canAlign = false;
+            }
+
+            return canAlign;
+        },
         
         /**
          * Determines if the specified selection is colorable (in a single action).

http://git-wip-us.apache.org/repos/asf/nifi/blob/5ea17d30/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
index 8100678..b51ddb0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
@@ -158,8 +158,17 @@ nf.ContextMenu = (function () {
     };
 
     /**
+     * Determines whether the components in the specified selection are alignable.
+     *
+     * @param {selection} selection          The selection
+     */
+    var canAlign = function (selection) {
+        return nf.CanvasUtils.canAlign(selection);
+    };
+
+    /**
      * Determines whether the components in the specified selection are colorable.
-     * 
+     *
      * @param {selection} selection          The selection
      */
     var isColorable = function (selection) {
@@ -435,7 +444,9 @@ nf.ContextMenu = (function () {
         {condition: canMoveToParent, menuItem: {clazz: 'fa fa-arrows', text: 'Move to parent group', action: 'moveIntoParent'}},
         {condition: canListQueue, menuItem: {clazz: 'fa fa-list', text: 'List queue', action: 'listQueue'}},
         {condition: canEmptyQueue, menuItem: {clazz: 'fa fa-minus-circle', text: 'Empty queue', action: 'emptyQueue'}},
-        {condition: isDeletable, menuItem: {clazz: 'fa fa-trash', text: 'Delete', action: 'delete'}}
+        {condition: isDeletable, menuItem: {clazz: 'fa fa-trash', text: 'Delete', action: 'delete'}},
+        {condition: canAlign, menuItem: {clazz: 'fa fa-align-center', text: 'Align vertical', action: 'alignVertical'}},
+        {condition: canAlign, menuItem: {clazz: 'fa fa-align-center fa-rotate-90', text: 'Align horizontal', action: 'alignHorizontal'}}
     ];
 
     return {

http://git-wip-us.apache.org/repos/asf/nifi/blob/5ea17d30/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-draggable.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-draggable.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-draggable.js
index f6ae344..08b4590 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-draggable.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-draggable.js
@@ -23,7 +23,7 @@ nf.Draggable = (function () {
 
     /**
      * Updates the positioning of all selected components.
-     * 
+     *
      * @param {selection} dragSelection The current drag selection
      */
     var updateComponentsPosition = function (dragSelection) {
@@ -40,107 +40,6 @@ nf.Draggable = (function () {
         if (delta.x === 0 && delta.y === 0) {
             return;
         }
-        
-        var updateComponentPosition = function(d) {
-            var newPosition = {
-                'x': d.position.x + delta.x,
-                'y': d.position.y + delta.y
-            };
-
-            // build the entity
-            var entity = {
-                'revision': nf.Client.getRevision(d),
-                'component': {
-                    'id': d.id,
-                    'position': newPosition
-                }
-            };
-
-            // update the component positioning
-            return $.Deferred(function (deferred) {
-                $.ajax({
-                    type: 'PUT',
-                    url: d.uri,
-                    data: JSON.stringify(entity),
-                    dataType: 'json',
-                    contentType: 'application/json'
-                }).done(function (response) {
-                    // update the component
-                    nf[d.type].set(response);
-
-                    // resolve with an object so we can refresh when finished
-                    deferred.resolve({
-                        type: d.type,
-                        id: d.id
-                    });
-                }).fail(function (xhr, status, error) {
-                    if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
-                        nf.Dialog.showOkDialog({
-                            headerText: 'Component Position',
-                            dialogContent: nf.Common.escapeHtml(xhr.responseText)
-                        });
-                    } else {
-                        nf.Common.handleAjaxError(xhr, status, error);
-                    }
-
-                    deferred.reject();
-                });
-            }).promise();
-        };
-        
-        var updateConnectionPosition = function(d) {
-            // only update if necessary
-            if (d.bends.length === 0) {
-                return null;
-            }
-
-            // calculate the new bend points
-            var newBends = $.map(d.bends, function (bend) {
-                return {
-                    x: bend.x + delta.x,
-                    y: bend.y + delta.y
-                };
-            });
-
-            var entity = {
-                'revision': nf.Client.getRevision(d),
-                'component': {
-                    id: d.id,
-                    bends: newBends
-                }
-            };
-
-            // update the component positioning
-            return $.Deferred(function (deferred) {
-                $.ajax({
-                    type: 'PUT',
-                    url: d.uri,
-                    data: JSON.stringify(entity),
-                    dataType: 'json',
-                    contentType: 'application/json'
-                }).done(function (response) {
-                    // update the component
-                    nf.Connection.set(response);
-
-                    // resolve with an object so we can refresh when finished
-                    deferred.resolve({
-                        type: d.type,
-                        id: d.id
-                    });
-                }).fail(function (xhr, status, error) {
-                    if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
-                        nf.Dialog.showOkDialog({
-                            headerText: 'Component Position',
-                            dialogContent: nf.Common.escapeHtml(xhr.responseText)
-                        });
-                    } else {
-                        nf.Common.handleAjaxError(xhr, status, error);
-                    }
-
-                    deferred.reject();
-                });
-            }).promise();
-        };
 
         var selectedConnections = d3.selectAll('g.connection.selected');
         var selectedComponents = d3.selectAll('g.component.selected');
@@ -156,55 +55,30 @@ nf.Draggable = (function () {
 
         // go through each selected connection
         selectedConnections.each(function (d) {
-            var connectionUpdate = updateConnectionPosition(d);
+            var connectionUpdate = nf.Draggable.updateConnectionPosition(d, delta);
             if (connectionUpdate !== null) {
                 updates.set(d.id, connectionUpdate);
             }
         });
-        
+
         // go through each selected component
         selectedComponents.each(function (d) {
             // consider any self looping connections
             var connections = nf.Connection.getComponentConnections(d.id);
             $.each(connections, function(_, connection) {
                 if (!updates.has(connection.id) && nf.CanvasUtils.getConnectionSourceComponentId(connection) === nf.CanvasUtils.getConnectionDestinationComponentId(connection)) {
-                    var connectionUpdate = updateConnectionPosition(nf.Connection.get(connection.id));
+                    var connectionUpdate = nf.Draggable.updateConnectionPosition(nf.Connection.get(connection.id), delta);
                     if (connectionUpdate !== null) {
                         updates.set(connection.id, connectionUpdate);
                     }
                 }
             });
-            
+
             // consider the component itself
-            updates.set(d.id, updateComponentPosition(d));
+            updates.set(d.id, nf.Draggable.updateComponentPosition(d, delta));
         });
 
-        // wait for all updates to complete
-        $.when.apply(window, updates.values()).done(function () {
-            var dragged = $.makeArray(arguments);
-            var connections = d3.set();
-
-            // refresh this component
-            $.each(dragged, function (_, component) {
-                // check if the component in question is a connection
-                if (component.type === 'Connection') {
-                    connections.add(component.id);
-                } else {
-                    // get connections that need to be refreshed because its attached to this component
-                    var componentConnections = nf.Connection.getComponentConnections(component.id);
-                    $.each(componentConnections, function (_, connection) {
-                        connections.add(connection.id);
-                    });
-                }
-            });
-
-            // refresh the connections
-            connections.forEach(function (connectionId) {
-                nf.Connection.refresh(connectionId);
-            });
-        }).always(function(){
-            nf.Birdseye.refresh();
-        });
+        nf.Draggable.refreshConnections(updates);
     };
 
     /**
@@ -246,7 +120,7 @@ nf.Draggable = (function () {
 
                         // lazily create the drag selection box
                         if (dragSelection.empty()) {
-                            // get the current selection 
+                            // get the current selection
                             var selection = d3.selectAll('g.component.selected');
 
                             // determine the appropriate bounding box
@@ -327,10 +201,159 @@ nf.Draggable = (function () {
                         dragSelection.remove();
                     });
         },
-        
+
+        /**
+         * Update the component's position
+         *
+         * @param d     The component
+         * @param delta The change in position
+         * @returns {*}
+         */
+        updateComponentPosition: function(d, delta) {
+            var newPosition = {
+                'x': d.position.x + delta.x,
+                'y': d.position.y + delta.y
+            };
+
+            // build the entity
+            var entity = {
+                'revision': nf.Client.getRevision(d),
+                'component': {
+                    'id': d.id,
+                    'position': newPosition
+                }
+            };
+
+            // update the component positioning
+            return $.Deferred(function (deferred) {
+                $.ajax({
+                    type: 'PUT',
+                    url: d.uri,
+                    data: JSON.stringify(entity),
+                    dataType: 'json',
+                    contentType: 'application/json'
+                }).done(function (response) {
+                    // update the component
+                    nf[d.type].set(response);
+
+                    // resolve with an object so we can refresh when finished
+                    deferred.resolve({
+                        type: d.type,
+                        id: d.id
+                    });
+                }).fail(function (xhr, status, error) {
+                    if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
+                        nf.Dialog.showOkDialog({
+                            headerText: 'Component Position',
+                            dialogContent: nf.Common.escapeHtml(xhr.responseText)
+                        });
+                    } else {
+                        nf.Common.handleAjaxError(xhr, status, error);
+                    }
+
+                    deferred.reject();
+                });
+            }).promise();
+        },
+
+        /**
+         * Update the connection's position
+         *
+         * @param d     The connection
+         * @param delta The change in position
+         * @returns {*}
+         */
+        updateConnectionPosition: function(d, delta) {
+            // only update if necessary
+            if (d.bends.length === 0) {
+                return null;
+            }
+
+            // calculate the new bend points
+            var newBends = $.map(d.bends, function (bend) {
+                return {
+                    x: bend.x + delta.x,
+                    y: bend.y + delta.y
+                };
+            });
+
+            var entity = {
+                'revision': nf.Client.getRevision(d),
+                'component': {
+                    id: d.id,
+                    bends: newBends
+                }
+            };
+
+            // update the component positioning
+            return $.Deferred(function (deferred) {
+                $.ajax({
+                    type: 'PUT',
+                    url: d.uri,
+                    data: JSON.stringify(entity),
+                    dataType: 'json',
+                    contentType: 'application/json'
+                }).done(function (response) {
+                    // update the component
+                    nf.Connection.set(response);
+
+                    // resolve with an object so we can refresh when finished
+                    deferred.resolve({
+                        type: d.type,
+                        id: d.id
+                    });
+                }).fail(function (xhr, status, error) {
+                    if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
+                        nf.Dialog.showOkDialog({
+                            headerText: 'Component Position',
+                            dialogContent: nf.Common.escapeHtml(xhr.responseText)
+                        });
+                    } else {
+                        nf.Common.handleAjaxError(xhr, status, error);
+                    }
+
+                    deferred.reject();
+                });
+            }).promise();
+        },
+
+        /**
+         * Refresh the connections after dragging a component
+         *
+         * @param updates
+         */
+        refreshConnections: function(updates) {
+            // wait for all updates to complete
+            $.when.apply(window, updates.values()).done(function () {
+                var dragged = $.makeArray(arguments);
+                var connections = d3.set();
+
+                // refresh this component
+                $.each(dragged, function (_, component) {
+                    // check if the component in question is a connection
+                    if (component.type === 'Connection') {
+                        connections.add(component.id);
+                    } else {
+                        // get connections that need to be refreshed because its attached to this component
+                        var componentConnections = nf.Connection.getComponentConnections(component.id);
+                        $.each(componentConnections, function (_, connection) {
+                            connections.add(connection.id);
+                        });
+                    }
+                });
+
+                // refresh the connections
+                connections.forEach(function (connectionId) {
+                    nf.Connection.refresh(connectionId);
+                });
+            }).always(function(){
+                nf.Birdseye.refresh();
+            });
+        },
+
         /**
          * Activates the drag behavior for the components in the specified selection.
-         * 
+         *
          * @param {selection} components
          */
         activate: function (components) {
@@ -339,7 +362,7 @@ nf.Draggable = (function () {
 
         /**
          * Deactivates the drag behavior for the components in the specified selection.
-         * 
+         *
          * @param {selection} components
          */
         deactivate: function (components) {