http://git-wip-us.apache.org/repos/asf/nifi/blob/1df8fe44/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-output-port-component.js
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-output-port-component.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-output-port-component.js
new file mode 100644
index 0000000..5a27d48
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-output-port-component.js
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* global nf, d3 */
+
+nf.ng.OutputPortComponent = (function () {
+
+    function OutputPortComponent(serviceProvider) {
+
+        /**
+         * Create the input port and add to the graph.
+         *
+         * @argument {string} portName          The output port name.
+         * @argument {object} pt                The point that the output port 
was dropped.
+         */
+        var createOutputPort = function (portName, pt) {
+            var outputPortEntity = {
+                'revision': nf.Client.getRevision(),
+                'component': {
+                    'name': portName,
+                    'position': {
+                        'x': pt.x,
+                        'y': pt.y
+                    }
+                }
+            };
+
+            // create a new processor of the defined type
+            $.ajax({
+                type: 'POST',
+                url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.api + 
'/process-groups/' + encodeURIComponent(nf.Canvas.getGroupId()) + 
'/output-ports',
+                data: JSON.stringify(outputPortEntity),
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function (response) {
+                if (nf.Common.isDefinedAndNotNull(response.component)) {
+                    // update the revision
+                    nf.Client.setRevision(response.revision);
+
+                    // add the port to the graph
+                    nf.Graph.add({
+                        'outputPorts': [response]
+                    }, true);
+
+                    // update component visibility
+                    nf.Canvas.View.updateVisibility();
+
+                    // update the birdseye
+                    nf.Birdseye.refresh();
+                }
+            }).fail(nf.Common.handleAjaxError);
+        };
+
+        function OutputPortComponent() {
+        };
+        OutputPortComponent.prototype = {
+            constructor: OutputPortComponent,
+
+            /**
+             * The output port component's modal.
+             */
+            modal: {
+                
+                /**
+                 * Gets the modal element.
+                 *
+                 * @returns {*|jQuery|HTMLElement}
+                 */
+                getElement: function () {
+                    return $('#new-port-dialog'); //Reuse the input port 
dialog....
+                },
+
+                /**
+                 * Initialize the modal.
+                 */
+                init: function () {
+                    //Reuse the input port dialog....
+                },
+
+                /**
+                 * Updates the modal config.
+                 *
+                 * @param {string} name             The name of the property 
to update.
+                 * @param {object|array} config     The config for the `name`.
+                 */
+                update: function (name, config) {
+                    this.getElement().modal(name, config);
+                },
+
+                /**
+                 * Show the modal.
+                 */
+                show: function () {
+                    this.getElement().modal('show');
+                },
+
+                /**
+                 * Hide the modal.
+                 */
+                hide: function () {
+                    this.getElement().modal('hide');
+                }
+            },
+
+            /**
+             * Gets the component.
+             *
+             * @returns {*|jQuery|HTMLElement}
+             */
+            getElement: function () {
+                return $('#port-out-component');
+            },
+
+            /**
+             * Enable the component.
+             */
+            enabled: function () {
+                this.getElement().attr('disabled', false);
+            },
+
+            /**
+             * Disable the component.
+             */
+            disabled: function () {
+                this.getElement().attr('disabled', true);
+            },
+
+            /**
+             * Handler function for when component is dropped on the canvas.
+             *
+             * @argument {object} pt        The point that the component was 
dropped.
+             */
+            dropHandler: function (pt) {
+                this.promptForOutputPortName(pt);
+            },
+
+            /**
+             * Prompts the user to enter the name for the output port.
+             *
+             * @argument {object} pt        The point that the output port was 
dropped.
+             */
+            promptForOutputPortName: function (pt) {
+                var self = this;
+                var addOutputPort = function () {
+                    // get the name of the output port and clear the textfield
+                    var portName = $('#new-port-name').val();
+
+                    // hide the dialog
+                    self.modal.hide();
+
+                    // create the output port
+                    createOutputPort(portName, pt);
+                };
+
+                this.modal.update('setButtonModel', [{
+                    buttonText: 'Add',
+                    handler: {
+                        click: addOutputPort
+                    }
+                }, {
+                    buttonText: 'Cancel',
+                    handler: {
+                        click: function () {
+                            self.modal.hide();
+                        }
+                    }
+                }]);
+
+                // update the port type
+                $('#new-port-type').text('Output');
+
+                // set the focus and show the dialog
+                this.modal.show();
+
+                // set up the focus and key handlers
+                $('#new-port-name').focus().off('keyup').on('keyup', function 
(e) {
+                    var code = e.keyCode ? e.keyCode : e.which;
+                    if (code === $.ui.keyCode.ENTER) {
+                        addOutputPort();
+                    }
+                });
+            }
+        };
+        var outputPortComponent = new OutputPortComponent();
+        return outputPortComponent;
+    }
+
+    OutputPortComponent.$inject = ['serviceProvider'];
+
+    return OutputPortComponent;
+}());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/1df8fe44/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js
new file mode 100644
index 0000000..7451c75
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-processor-component.js
@@ -0,0 +1,573 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* global nf, d3 */
+
+nf.ng.ProcessorComponent = (function () {
+
+    function ProcessorComponent(serviceProvider) {
+
+        /**
+         * Filters the processor type table.
+         */
+        var applyFilter = function () {
+            // get the dataview
+            var processorTypesGrid = 
$('#processor-types-table').data('gridInstance');
+
+            // ensure the grid has been initialized
+            if (nf.Common.isDefinedAndNotNull(processorTypesGrid)) {
+                var processorTypesData = processorTypesGrid.getData();
+
+                // update the search criteria
+                processorTypesData.setFilterArgs({
+                    searchString: getFilterText()
+                });
+                processorTypesData.refresh();
+
+                // update the selection if possible
+                if (processorTypesData.getLength() > 0) {
+                    processorTypesGrid.setSelectedRows([0]);
+                }
+            }
+        };
+
+        /**
+         * Determines if the item matches the filter.
+         *
+         * @param {object} item     The item to filter.
+         * @param {object} args     The filter criteria.
+         * @returns {boolean}       Whether the item matches the filter.
+         */
+        var matchesRegex = function (item, args) {
+            if (args.searchString === '') {
+                return true;
+            }
+
+            try {
+                // perform the row filtering
+                var filterExp = new RegExp(args.searchString, 'i');
+            } catch (e) {
+                // invalid regex
+                return false;
+            }
+
+            // determine if the item matches the filter
+            var matchesLabel = item['label'].search(filterExp) >= 0;
+            var matchesTags = item['tags'].search(filterExp) >= 0;
+            return matchesLabel || matchesTags;
+        };
+
+        /**
+         * Performs the filtering.
+         *
+         * @param {object} item     The item subject to filtering.
+         * @param {object} args     Filter arguments.
+         * @returns {Boolean}       Whether or not to include the item.
+         */
+        var filter = function (item, args) {
+            // determine if the item matches the filter
+            var matchesFilter = matchesRegex(item, args);
+
+            // determine if the row matches the selected tags
+            var matchesTags = true;
+            if (matchesFilter) {
+                var tagFilters = 
$('#processor-tag-cloud').tagcloud('getSelectedTags');
+                var hasSelectedTags = tagFilters.length > 0;
+                if (hasSelectedTags) {
+                    matchesTags = matchesSelectedTags(tagFilters, 
item['tags']);
+                }
+            }
+
+            // determine if this row should be visible
+            var matches = matchesFilter && matchesTags;
+
+            // if this row is currently selected and its being filtered
+            if (matches === false && $('#selected-processor-type').text() === 
item['type']) {
+                // clear the selected row
+                $('#processor-type-description').text('');
+                $('#processor-type-name').text('');
+                $('#selected-processor-name').text('');
+                $('#selected-processor-type').text('');
+
+                // clear the active cell the it can be reselected when its 
included
+                var processTypesGrid = 
$('#processor-types-table').data('gridInstance');
+                processTypesGrid.resetActiveCell();
+            }
+
+            return matches;
+        };
+
+        /**
+         * Determines if the specified tags match all the tags selected by the 
user.
+         *
+         * @argument {string[]} tagFilters      The tag filters.
+         * @argument {string} tags              The tags to test.
+         */
+        var matchesSelectedTags = function (tagFilters, tags) {
+            var selectedTags = [];
+            $.each(tagFilters, function (_, filter) {
+                selectedTags.push(filter);
+            });
+
+            // normalize the tags
+            var normalizedTags = tags.toLowerCase();
+
+            var matches = true;
+            $.each(selectedTags, function (i, selectedTag) {
+                if (normalizedTags.indexOf(selectedTag) === -1) {
+                    matches = false;
+                    return false;
+                }
+            });
+
+            return matches;
+        };
+
+        /**
+         * Sorts the specified data using the specified sort details.
+         *
+         * @param {object} sortDetails
+         * @param {object} data
+         */
+        var sort = function (sortDetails, data) {
+            // defines a function for sorting
+            var comparer = function (a, b) {
+                var aString = 
nf.Common.isDefinedAndNotNull(a[sortDetails.columnId]) ? 
a[sortDetails.columnId] : '';
+                var bString = 
nf.Common.isDefinedAndNotNull(b[sortDetails.columnId]) ? 
b[sortDetails.columnId] : '';
+                return aString === bString ? 0 : aString > bString ? 1 : -1;
+            };
+
+            // perform the sort
+            data.sort(comparer, sortDetails.sortAsc);
+        };
+
+        /**
+         * Get the text out of the filter field. If the filter field doesn't
+         * have any text it will contain the text 'filter list' so this method
+         * accounts for that.
+         */
+        var getFilterText = function () {
+            var filterText = '';
+            var filterField = $('#processor-type-filter');
+            if 
(!filterField.hasClass(serviceProvider.headerCtrl.toolboxCtrl.config.styles.filterList))
 {
+                filterText = filterField.val();
+            }
+            return filterText;
+        };
+
+        /**
+         * Resets the filtered processor types.
+         */
+        var resetProcessorDialog = function () {
+            // clear the selected tag cloud
+            $('#processor-tag-cloud').tagcloud('clearSelectedTags');
+
+            // clear any filter strings
+            
$('#processor-type-filter').addClass(serviceProvider.headerCtrl.toolboxCtrl.config.styles.filterList).val(serviceProvider.headerCtrl.toolboxCtrl.config.filterText);
+
+            // reapply the filter
+            applyFilter();
+
+            // clear the selected row
+            $('#processor-type-description').text('');
+            $('#processor-type-name').text('');
+            $('#selected-processor-name').text('');
+            $('#selected-processor-type').text('');
+
+            // unselect any current selection
+            var processTypesGrid = 
$('#processor-types-table').data('gridInstance');
+            processTypesGrid.setSelectedRows([]);
+            processTypesGrid.resetActiveCell();
+        };
+
+        /**
+         * Create the processor and add to the graph.
+         *
+         * @argument {string} name              The processor name.
+         * @argument {string} processorType     The processor type.
+         * @argument {object} pt                The point that the processor 
was dropped.
+         */
+        var createProcessor = function (name, processorType, pt) {
+            var processorEntity = {
+                'revision': nf.Client.getRevision(),
+                'component': {
+                    'type': processorType,
+                    'name': name,
+                    'position': {
+                        'x': pt.x,
+                        'y': pt.y
+                    }
+                }
+            };
+
+            // create a new processor of the defined type
+            $.ajax({
+                type: 'POST',
+                url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.api + 
'/process-groups/' + encodeURIComponent(nf.Canvas.getGroupId()) + '/processors',
+                data: JSON.stringify(processorEntity),
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function (response) {
+                if (nf.Common.isDefinedAndNotNull(response.component)) {
+                    // update the revision
+                    nf.Client.setRevision(response.revision);
+
+                    // add the processor to the graph
+                    nf.Graph.add({
+                        'processors': [response]
+                    }, true);
+
+                    // update component visibility
+                    nf.Canvas.View.updateVisibility();
+
+                    // update the birdseye
+                    nf.Birdseye.refresh();
+                }
+            }).fail(nf.Common.handleAjaxError);
+        };
+
+        function ProcessorComponent() {
+        };
+        ProcessorComponent.prototype = {
+            constructor: ProcessorComponent,
+
+            /**
+             * The processor component's modal.
+             */
+            modal: {
+                
+                /**
+                 * The processor component modal's filter.
+                 */
+                filter: {
+                    
+                    /**
+                     * Initialize the filter.
+                     */
+                    init: function () {
+                        // define the function for filtering the list
+                        $('#processor-type-filter').focus(function () {
+                            if 
($(this).hasClass(serviceProvider.headerCtrl.toolboxCtrl.config.styles.filterList))
 {
+                                
$(this).removeClass(serviceProvider.headerCtrl.toolboxCtrl.config.styles.filterList).val('');
+                            }
+                        }).blur(function () {
+                            if ($(this).val() === '') {
+                                
$(this).addClass(serviceProvider.headerCtrl.toolboxCtrl.config.styles.filterList).val(serviceProvider.headerCtrl.toolboxCtrl.config.filterText);
+                            }
+                        
}).addClass(serviceProvider.headerCtrl.toolboxCtrl.config.styles.filterList).val(serviceProvider.headerCtrl.toolboxCtrl.config.filterText);
+
+                        // initialize the processor type table
+                        var processorTypesColumns = [
+                            {id: 'type', name: 'Type', field: 'label', 
sortable: true, resizable: true},
+                            {id: 'tags', name: 'Tags', field: 'tags', 
sortable: true, resizable: true}
+                        ];
+                        var processorTypesOptions = {
+                            forceFitColumns: true,
+                            enableTextSelectionOnCells: true,
+                            enableCellNavigation: true,
+                            enableColumnReorder: false,
+                            autoEdit: false,
+                            multiSelect: false
+                        };
+
+                        // initialize the dataview
+                        var processorTypesData = new Slick.Data.DataView({
+                            inlineFilters: false
+                        });
+                        processorTypesData.setItems([]);
+                        processorTypesData.setFilterArgs({
+                            searchString: getFilterText()
+                        });
+                        processorTypesData.setFilter(filter);
+
+                        // initialize the sort
+                        sort({
+                            columnId: 'type',
+                            sortAsc: true
+                        }, processorTypesData);
+
+                        // initialize the grid
+                        var processorTypesGrid = new 
Slick.Grid('#processor-types-table', processorTypesData, processorTypesColumns, 
processorTypesOptions);
+                        processorTypesGrid.setSelectionModel(new 
Slick.RowSelectionModel());
+                        processorTypesGrid.registerPlugin(new 
Slick.AutoTooltips());
+                        processorTypesGrid.setSortColumn('type', true);
+                        processorTypesGrid.onSort.subscribe(function (e, args) 
{
+                            sort({
+                                columnId: args.sortCol.field,
+                                sortAsc: args.sortAsc
+                            }, processorTypesData);
+                        });
+                        
processorTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+                            if ($.isArray(args.rows) && args.rows.length === 
1) {
+                                var processorTypeIndex = args.rows[0];
+                                var processorType = 
processorTypesGrid.getDataItem(processorTypeIndex);
+
+                                // set the processor type description
+                                if 
(nf.Common.isDefinedAndNotNull(processorType)) {
+                                    if 
(nf.Common.isBlank(processorType.description)) {
+                                        
$('#processor-type-description').attr('title', '').html('<span class="unset">No 
description specified</span>');
+                                    } else {
+                                        
$('#processor-type-description').html(processorType.description).ellipsis();
+                                    }
+
+                                    // populate the dom
+                                    
$('#processor-type-name').text(processorType.label).ellipsis();
+                                    
$('#selected-processor-name').text(processorType.label);
+                                    
$('#selected-processor-type').text(processorType.type);
+                                }
+                            }
+                        });
+
+                        // wire up the dataview to the grid
+                        
processorTypesData.onRowCountChanged.subscribe(function (e, args) {
+                            processorTypesGrid.updateRowCount();
+                            processorTypesGrid.render();
+
+                            // update the total number of displayed processors
+                            $('#displayed-processor-types').text(args.current);
+                        });
+                        processorTypesData.onRowsChanged.subscribe(function 
(e, args) {
+                            processorTypesGrid.invalidateRows(args.rows);
+                            processorTypesGrid.render();
+                        });
+                        
processorTypesData.syncGridSelection(processorTypesGrid, false);
+
+                        // hold onto an instance of the grid
+                        $('#processor-types-table').data('gridInstance', 
processorTypesGrid);
+
+                        // load the available processor types, this select is 
shown in the
+                        // new processor dialog when a processor is dragged 
onto the screen
+                        $.ajax({
+                            type: 'GET',
+                            url: 
serviceProvider.headerCtrl.toolboxCtrl.config.urls.processorTypes,
+                            dataType: 'json'
+                        }).done(function (response) {
+                            var tags = [];
+
+                            // begin the update
+                            processorTypesData.beginUpdate();
+
+                            // go through each processor type
+                            $.each(response.processorTypes, function (i, 
documentedType) {
+                                var type = documentedType.type;
+
+                                // create the row for the processor type
+                                processorTypesData.addItem({
+                                    id: i,
+                                    label: nf.Common.substringAfterLast(type, 
'.'),
+                                    type: type,
+                                    description: 
nf.Common.escapeHtml(documentedType.description),
+                                    tags: documentedType.tags.join(', ')
+                                });
+
+                                // count the frequency of each tag for this 
type
+                                $.each(documentedType.tags, function (i, tag) {
+                                    tags.push(tag.toLowerCase());
+                                });
+                            });
+
+                            // end the udpate
+                            processorTypesData.endUpdate();
+
+                            // set the total number of processors
+                            $('#total-processor-types, 
#displayed-processor-types').text(response.processorTypes.length);
+
+                            // create the tag cloud
+                            $('#processor-tag-cloud').tagcloud({
+                                tags: tags,
+                                select: applyFilter,
+                                remove: applyFilter
+                            });
+                        }).fail(nf.Common.handleAjaxError);
+                    }
+                },
+
+                /**
+                 * Gets the modal element.
+                 *
+                 * @returns {*|jQuery|HTMLElement}
+                 */
+                getElement: function () {
+                    return $('#new-processor-dialog');
+                },
+
+                /**
+                 * Initialize the modal.
+                 */
+                init: function () {
+                    this.filter.init();
+
+                    // configure the new processor dialog
+                    this.getElement().modal({
+                        headerText: 'Add Processor',
+                        overlayBackground: false
+                    });
+                },
+
+                /**
+                 * Updates the modal config.
+                 *
+                 * @param {string} name             The name of the property 
to update.
+                 * @param {object|array} config     The config for the `name`.
+                 */
+                update: function (name, config) {
+                    this.getElement().modal(name, config);
+                },
+
+                /**
+                 * Show the modal
+                 */
+                show: function () {
+                    this.getElement().modal('show');
+                },
+
+                /**
+                 * Hide the modal
+                 */
+                hide: function () {
+                    this.getElement().modal('hide');
+                }
+            },
+
+            /**
+             * Gets the component.
+             *
+             * @returns {*|jQuery|HTMLElement}
+             */
+            getElement: function () {
+                return $('#processor-component');
+            },
+
+            /**
+             * Enable the component.
+             */
+            enabled: function () {
+                this.getElement().attr('disabled', false);
+            },
+
+            /**
+             * Disable the component.
+             */
+            disabled: function () {
+                this.getElement().attr('disabled', true);
+            },
+
+            /**
+             * Handler function for when component is dropped on the canvas.
+             *
+             * @argument {object} pt        The point that the component was 
dropped
+             */
+            dropHandler: function (pt) {
+                this.promptForProcessorType(pt);
+            },
+
+            /**
+             * Prompts the user to select the type of new processor to create.
+             *
+             * @argument {object} pt        The point that the processor was 
dropped
+             */
+            promptForProcessorType: function (pt) {
+                var self = this;
+                // handles adding the selected processor at the specified point
+                var addProcessor = function () {
+                    // get the type of processor currently selected
+                    var name = $('#selected-processor-name').text();
+                    var processorType = $('#selected-processor-type').text();
+
+                    // ensure something was selected
+                    if (name === '' || processorType === '') {
+                        nf.Dialog.showOkDialog({
+                            dialogContent: 'The type of processor to create 
must be selected.',
+                            overlayBackground: false
+                        });
+                    } else {
+                        // create the new processor
+                        createProcessor(name, processorType, pt);
+                    }
+
+                    // hide the dialog
+                    self.modal.hide();
+                };
+
+                // get the grid reference
+                var grid = $('#processor-types-table').data('gridInstance');
+
+                // add the processor when its double clicked in the table
+                var gridDoubleClick = function (e, args) {
+                    var processorType = grid.getDataItem(args.row);
+
+                    $('#selected-processor-name').text(processorType.label);
+                    $('#selected-processor-type').text(processorType.type);
+
+                    addProcessor();
+                };
+
+                // register a handler for double click events
+                grid.onDblClick.subscribe(gridDoubleClick);
+
+                // update the button model
+                this.modal.update('setButtonModel', [{
+                    buttonText: 'Add',
+                    handler: {
+                        click: addProcessor
+                    }
+                }, {
+                    buttonText: 'Cancel',
+                    handler: {
+                        click: function () {
+                            $('#new-processor-dialog').modal('hide');
+                        }
+                    }
+                }]);
+
+                // set a new handler for closing the the dialog
+                this.modal.update('setHandler', {
+                    close: function () {
+                        // remove the handler
+                        grid.onDblClick.unsubscribe(gridDoubleClick);
+
+                        // clear the current filters
+                        resetProcessorDialog();
+                    }
+                });
+
+                // show the dialog
+                this.modal.show();
+
+                // setup the filter
+                $('#processor-type-filter').focus().off('keyup').on('keyup', 
function (e) {
+                    var code = e.keyCode ? e.keyCode : e.which;
+                    if (code === $.ui.keyCode.ENTER) {
+                        addProcessor();
+                    } else {
+                        applyFilter();
+                    }
+                });
+
+                // adjust the grid canvas now that its been rendered
+                grid.resizeCanvas();
+                grid.setSelectedRows([0]);
+            }
+        };
+        var processorComponent = new ProcessorComponent();
+        return processorComponent;
+    }
+
+    ProcessorComponent.$inject = ['serviceProvider'];
+
+    return ProcessorComponent;
+}());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/1df8fe44/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-remote-process-group-component.js
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-remote-process-group-component.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-remote-process-group-component.js
new file mode 100644
index 0000000..3588e85
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-remote-process-group-component.js
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* global nf, d3 */
+
+nf.ng.RemoteProcessGroupComponent = (function () {
+
+    function RemoteProcessGroupComponent(serviceProvider) {
+
+        /**
+         * Create the controller and add to the graph.
+         *
+         * @argument {string} remoteProcessGroupUri         The remote group 
uri.
+         * @argument {object} pt                            The point that the 
remote group was dropped.
+         */
+        var createRemoteProcessGroup = function (remoteProcessGroupUri, pt) {
+            var remoteProcessGroupEntity = {
+                'revision': nf.Client.getRevision(),
+                'component': {
+                    'targetUri': remoteProcessGroupUri,
+                    'position': {
+                        'x': pt.x,
+                        'y': pt.y
+                    }
+                }
+            };
+
+            // create a new processor of the defined type
+            $.ajax({
+                type: 'POST',
+                url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.api + 
'/process-groups/' + encodeURIComponent(nf.Canvas.getGroupId()) + 
'/remote-process-groups',
+                data: JSON.stringify(remoteProcessGroupEntity),
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function (response) {
+                if (nf.Common.isDefinedAndNotNull(response.component)) {
+                    // update the revision
+                    nf.Client.setRevision(response.revision);
+
+                    // add the processor to the graph
+                    nf.Graph.add({
+                        'remoteProcessGroups': [response]
+                    }, true);
+
+                    // update component visibility
+                    nf.Canvas.View.updateVisibility();
+
+                    // update the birdseye
+                    nf.Birdseye.refresh();
+                }
+            }).fail(nf.Common.handleAjaxError);
+        };
+
+        function RemoteProcessGroupComponent() {
+        };
+        RemoteProcessGroupComponent.prototype = {
+            constructor: RemoteProcessGroupComponent,
+
+            /**
+             * The remote group component's modal.
+             */
+            modal: {
+                
+                /**
+                 * Gets the modal element.
+                 *
+                 * @returns {*|jQuery|HTMLElement}
+                 */
+                getElement: function () {
+                    return $('#new-remote-process-group-dialog');
+                },
+
+                /**
+                 * Initialize the modal.
+                 */
+                init: function () {
+                    // configure the new remote process group dialog
+                    this.getElement().modal({
+                        headerText: 'Add Remote Process Group',
+                        overlayBackground: false,
+                        handler: {
+                            close: function () {
+                                $('#new-remote-process-group-uri').val('');
+                            }
+                        }
+                    });
+                },
+
+                /**
+                 * Updates the modal config.
+                 *
+                 * @param {string} name             The name of the property 
to update.
+                 * @param {object|array} config     The config for the `name`.
+                 */
+                update: function (name, config) {
+                    this.getElement().modal(name, config);
+                },
+
+                /**
+                 * Show the modal.
+                 */
+                show: function () {
+                    this.getElement().modal('show');
+                },
+
+                /**
+                 * Hide the modal.
+                 */
+                hide: function () {
+                    this.getElement().modal('hide');
+                }
+            },
+
+            /**
+             * Gets the component.
+             *
+             * @returns {*|jQuery|HTMLElement}
+             */
+            getElement: function () {
+                return $('#group-remote-component');
+            },
+
+            /**
+             * Enable the component.
+             */
+            enabled: function () {
+                this.getElement().attr('disabled', false);
+            },
+
+            /**
+             * Disable the component.
+             */
+            disabled: function () {
+                this.getElement().attr('disabled', true);
+            },
+
+            /**
+             * Handler function for when component is dropped on the canvas.
+             *
+             * @argument {object} pt        The point that the component was 
dropped.
+             */
+            dropHandler: function (pt) {
+                this.promptForRemoteProcessGroupUri(pt);
+            },
+
+            /**
+             * Prompts the user to enter the URI for the remote process group.
+             *
+             * @argument {object} pt        The point that the remote group 
was dropped.
+             */
+            promptForRemoteProcessGroupUri: function (pt) {
+                var self = this;
+                var addRemoteProcessGroup = function () {
+                    // get the uri of the controller and clear the textfield
+                    var remoteProcessGroupUri = 
$('#new-remote-process-group-uri').val();
+
+                    // hide the dialog
+                    self.modal.hide();
+
+                    // create the remote process group
+                    createRemoteProcessGroup(remoteProcessGroupUri, pt);
+                };
+
+                this.modal.update('setButtonModel', [{
+                    buttonText: 'Add',
+                    handler: {
+                        click: addRemoteProcessGroup
+                    }
+                }, {
+                    buttonText: 'Cancel',
+                    handler: {
+                        click: function () {
+                            self.modal.hide();
+                            ;
+                        }
+                    }
+                }]);
+
+                // show the dialog
+                this.modal.show();
+
+                // set the focus and key handlers
+                
$('#new-remote-process-group-uri').focus().off('keyup').on('keyup', function 
(e) {
+                    var code = e.keyCode ? e.keyCode : e.which;
+                    if (code === $.ui.keyCode.ENTER) {
+                        addRemoteProcessGroup();
+                    }
+                });
+            }
+        };
+        var remoteProcessGroupComponent = new RemoteProcessGroupComponent();
+        return remoteProcessGroupComponent;
+    }
+
+    RemoteProcessGroupComponent.$inject = ['serviceProvider'];
+
+    return RemoteProcessGroupComponent;
+}());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/1df8fe44/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-template-component.js
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-template-component.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-template-component.js
new file mode 100644
index 0000000..96523df
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-template-component.js
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* global nf, d3 */
+
+nf.ng.TemplateComponent = (function () {
+
+    function TemplateComponent(serviceProvider) {
+
+        /**
+         * Instantiates the specified template.
+         *
+         * @argument {string} templateId        The template id.
+         * @argument {object} pt                The point that the template 
was dropped.
+         */
+        var createTemplate = function (templateId, pt) {
+            var instantiateTemplateInstance = {
+                'revision': nf.Client.getRevision(),
+                'templateId': templateId,
+                'originX': pt.x,
+                'originY': pt.y
+            };
+
+            // create a new instance of the new template
+            $.ajax({
+                type: 'POST',
+                url: serviceProvider.headerCtrl.toolboxCtrl.config.urls.api + 
'/process-groups/' + encodeURIComponent(nf.Canvas.getGroupId()) + 
'/template-instance',
+                data: JSON.stringify(instantiateTemplateInstance),
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function (response) {
+                // update the revision
+                nf.Client.setRevision(response.revision);
+
+                // populate the graph accordingly
+                nf.Graph.add(response.flow, true);
+
+                // update component visibility
+                nf.Canvas.View.updateVisibility();
+
+                // update the birdseye
+                nf.Birdseye.refresh();
+            }).fail(nf.Common.handleAjaxError);
+        };
+
+        function TemplateComponent() {
+        };
+        TemplateComponent.prototype = {
+            constructor: TemplateComponent,
+
+            /**
+             * The template component's modal.
+             */
+            modal: {
+                
+                /**
+                 * Gets the modal element.
+                 *
+                 * @returns {*|jQuery|HTMLElement}
+                 */
+                getElement: function () {
+                    return $('#instantiate-template-dialog');
+                },
+
+                /**
+                 * Initialize the modal.
+                 */
+                init: function () {
+                    // configure the instantiate template dialog
+                    this.getElement().modal({
+                        headerText: 'Instantiate Template',
+                        overlayBackgroud: false
+                    });
+                },
+
+                /**
+                 * Updates the modal config.
+                 *
+                 * @param {string} name             The name of the property 
to update.
+                 * @param {object|array} config     The config for the `name`.
+                 */
+                update: function (name, config) {
+                    this.getElement().modal(name, config);
+                },
+
+                /**
+                 * Show the modal.
+                 */
+                show: function () {
+                    this.getElement().modal('show');
+                },
+
+                /**
+                 * Hide the modal.
+                 */
+                hide: function () {
+                    this.getElement().modal('hide');
+                }
+            },
+
+            /**
+             * Gets the component.
+             *
+             * @returns {*|jQuery|HTMLElement}
+             */
+            getElement: function () {
+                return $('#template-component');
+            },
+
+            /**
+             * Enable the component.
+             */
+            enabled: function () {
+                this.getElement().attr('disabled', false);
+            },
+
+            /**
+             * Disable the component.
+             */
+            disabled: function () {
+                this.getElement().attr('disabled', true);
+            },
+
+            /**
+             * Handler function for when component is dropped on the canvas.
+             *
+             * @argument {object} pt        The point that the component was 
dropped.
+             */
+            dropHandler: function (pt) {
+                this.promptForTemplate(pt);
+            },
+
+            /**
+             * Prompts the user to select a template.
+             *
+             * @argument {object} pt        The point that the template was 
dropped.
+             */
+            promptForTemplate: function (pt) {
+                var self = this;
+                $.ajax({
+                    type: 'GET',
+                    url: 
serviceProvider.headerCtrl.toolboxCtrl.config.urls.api + '/process-groups/' + 
encodeURIComponent(nf.Canvas.getGroupId()) + '/templates',
+                    dataType: 'json'
+                }).done(function (response) {
+                    var templates = response.templates;
+                    if (nf.Common.isDefinedAndNotNull(templates) && 
templates.length > 0) {
+                        var options = [];
+                        $.each(templates, function (_, template) {
+                            options.push({
+                                text: template.name,
+                                value: template.id,
+                                description: 
nf.Common.escapeHtml(template.description)
+                            });
+                        });
+
+                        // configure the templates combo
+                        $('#available-templates').combo({
+                            maxHeight: 300,
+                            options: options
+                        });
+
+                        // update the button model
+                        self.modal.update('setButtonModel', [{
+                            buttonText: 'Add',
+                            handler: {
+                                click: function () {
+                                    // get the type of processor currently 
selected
+                                    var selectedOption = 
$('#available-templates').combo('getSelectedOption');
+                                    var templateId = selectedOption.value;
+
+                                    // hide the dialog
+                                    self.modal.hide();
+
+                                    // instantiate the specified template
+                                    createTemplate(templateId, pt);
+                                }
+                            }
+                        }, {
+                            buttonText: 'Cancel',
+                            handler: {
+                                click: function () {
+                                    self.modal.hide();
+                                }
+                            }
+                        }]);
+
+                        // show the dialog
+                        self.modal.show();
+                    } else {
+                        nf.Dialog.showOkDialog({
+                            headerText: 'Instantiate Template',
+                            dialogContent: 'No templates have been loaded into 
this NiFi.',
+                            overlayBackground: false
+                        });
+                    }
+
+                }).fail(nf.Common.handleAjaxError);
+            }
+        };
+        var templateComponent = new TemplateComponent();
+        return templateComponent;
+    }
+
+    TemplateComponent.$inject = ['serviceProvider'];
+
+    return TemplateComponent;
+}());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/1df8fe44/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 59f56fc..9d90a61 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
@@ -49,9 +49,6 @@ nf.Actions = (function () {
                     $('#drop-request-status-dialog').modal('setButtonModel', 
[]);
                 }
             }
-        }).draggable({
-            containment: 'parent',
-            handle: '.dialog-header'
         });
     };
     
@@ -418,10 +415,10 @@ nf.Actions = (function () {
                     }));
                 });
 
-                // refresh the toolbar once the updates have completed
+                // inform Angular app once the updates have completed
                 if (enableRequests.length > 0) {
                     $.when.apply(window, enableRequests).always(function () {
-                        nf.CanvasToolbar.refresh();
+                        nf.ng.Bridge.digest();
                     });
                 }
             }
@@ -460,10 +457,10 @@ nf.Actions = (function () {
                     }));
                 });
 
-                // refresh the toolbar once the updates have completed
+                // inform Angular app once the updates have completed
                 if (disableRequests.length > 0) {
                     $.when.apply(window, disableRequests).always(function () {
-                        nf.CanvasToolbar.refresh();
+                        nf.ng.Bridge.digest();
                     });
                 }
             }
@@ -549,10 +546,10 @@ nf.Actions = (function () {
                         }));
                     });
 
-                    // refresh the toolbar once the updates have completed
+                    // inform Angular app once the updates have completed
                     if (startRequests.length > 0) {
                         $.when.apply(window, startRequests).always(function () 
{
-                            nf.CanvasToolbar.refresh();
+                            nf.ng.Bridge.digest();
                         });
                     }
                 }
@@ -623,10 +620,10 @@ nf.Actions = (function () {
                         }));
                     });
 
-                    // refresh the toolbar once the updates have completed
+                    // inform Angular app once the updates have completed
                     if (stopRequests.length > 0) {
                         $.when.apply(window, stopRequests).always(function () {
-                            nf.CanvasToolbar.refresh();
+                            nf.ng.Bridge.digest();
                         });
                     }
                 }
@@ -823,9 +820,10 @@ nf.Actions = (function () {
                             }
                         }
 
-                        // refresh the birdseye/toolbar
+                        // refresh the birdseye
                         nf.Birdseye.refresh();
-                        nf.CanvasToolbar.refresh();
+                        // inform Angular app values have changed
+                        nf.ng.Bridge.digest();
                     }).fail(nf.Common.handleAjaxError);
                 } else {
                     // create a snippet for the specified component and link 
to the data flow
@@ -873,9 +871,10 @@ nf.Actions = (function () {
                                 
nf.Connection.remove(components.get('Connection'));
                             }
 
-                            // refresh the birdseye/toolbar
+                            // refresh the birdseye
                             nf.Birdseye.refresh();
-                            nf.CanvasToolbar.refresh();
+                            // inform Angular app values have changed
+                            nf.ng.Bridge.digest();
                         }).fail(function (xhr, status, error) {
                             // unable to acutally remove the components so 
attempt to
                             // unlink and remove just the snippet - if 
unlinking fails
@@ -1158,7 +1157,7 @@ nf.Actions = (function () {
                 var origin = nf.CanvasUtils.getOrigin(selection);
 
                 var pt = {'x': origin.x, 'y': origin.y};
-                $.when(nf.CanvasToolbox.promptForGroupName(pt)).done(function 
(processGroup) {
+                
$.when(nf.ng.Bridge.get('appCtrl.serviceProvider.headerCtrl.toolboxCtrl.groupComponent').promptForGroupName(pt)).done(function
 (processGroup) {
                     var group = d3.select('#id-' + processGroup.id);
                     nf.CanvasUtils.moveComponents(selection, group);
                 });

http://git-wip-us.apache.org/repos/asf/nifi/blob/1df8fe44/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-birdseye.js
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-birdseye.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-birdseye.js
index 1b23262..9cbe228 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-birdseye.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-birdseye.js
@@ -119,17 +119,17 @@ nf.Birdseye = (function () {
 
         // update the brush
         d3.select('rect.birdseye-brush')
-                .attr({
-                    'width': screenWidth,
-                    'height': screenHeight,
-                    'stroke-width': (2 / birdseyeScale),
-                    'transform': function (d) {
-                        d.x = brushTranslate[0];
-                        d.y = brushTranslate[1];
-
-                        return 'translate(' + brushTranslate + ')';
-                    }
-                });
+            .attr({
+                'width': screenWidth,
+                'height': screenHeight,
+                'stroke-width': (2 / birdseyeScale),
+                'transform': function (d) {
+                    d.x = brushTranslate[0];
+                    d.y = brushTranslate[1];
+
+                    return 'translate(' + brushTranslate + ')';
+                }
+            });
 
         // redraw the canvas
         var canvasElement = d3.select('#birdseye-canvas').node();
@@ -183,7 +183,7 @@ nf.Birdseye = (function () {
         // process groups
         context.fillStyle = '#294c58';
         $.each(components.processGroups, function (_, d) {
-            context.fillRect(d.x, d.y, d.dimensions.width, 
d.dimensions.height);
+            context.fillRect(d.position.x, d.position.y, d.dimensions.width, 
d.dimensions.height);
         });
 
         // processors
@@ -198,7 +198,7 @@ nf.Birdseye = (function () {
             }
 
             context.fillStyle = color;
-            context.fillRect(d.x, d.y, d.dimensions.width, 
d.dimensions.height);
+            context.fillRect(d.position.x, d.position.y, d.dimensions.width, 
d.dimensions.height);
         });
 
         context.restore();
@@ -210,131 +210,89 @@ nf.Birdseye = (function () {
     return {
         init: function () {
             var birdseye = $('#birdseye');
-            var birdseyeContainer = $('#birdseye-container');
-            $('#birdseye-collapse').click(function () {
-                // update the outline collapse icon
-                if (birdseye.is(':visible')) {
-                    
$(this).removeClass('birdseye-expanded-hover').addClass('birdseye-collapsed-hover');
-
-                    // hide the outline
-                    birdseye.hide();
-                    birdseyeContainer.hide();
-                    visible = false;
-
-                    // shift the counts position
-                    $('#controller-counts').css('margin-right', '-13px');
-                } else {
-                    
$(this).removeClass('birdseye-collapsed-hover').addClass('birdseye-expanded-hover');
-
-                    // shift the counts position
-                    $('#controller-counts').css('margin-right', '195px');
-
-                    // show the outline
-                    birdseye.show();
-                    birdseyeContainer.show();
-                    visible = true;
-
-                    // refresh the birdseye as it may have changed
-                    refresh(nf.Graph.get());
-                }
-            }).mouseover(function () {
-                // update the outline collapse icon
-                if (birdseye.is(':visible')) {
-                    
$(this).removeClass('birdseye-expanded').addClass('birdseye-expanded-hover');
-                } else {
-                    
$(this).removeClass('birdseye-collapsed').addClass('birdseye-collapsed-hover');
-                }
-            }).mouseout(function () {
-                // update the outline collapse icon
-                if (birdseye.is(':visible')) {
-                    
$(this).removeClass('birdseye-expanded-hover').addClass('birdseye-expanded');
-                } else {
-                    
$(this).removeClass('birdseye-collapsed-hover').addClass('birdseye-collapsed');
-                }
-            });
 
             d3.select('#birdseye').append('canvas')
-                    .attr('id', 'birdseye-canvas')
-                    .attr('width', birdseye.width())
-                    .attr('height', birdseye.height());
+                .attr('id', 'birdseye-canvas')
+                .attr('width', birdseye.width())
+                .attr('height', birdseye.height());
 
             // build the birdseye svg
             var svg = d3.select('#birdseye').append('svg')
-                    .attr('width', birdseye.width())
-                    .attr('height', birdseye.height());
+                .attr('width', birdseye.width())
+                .attr('height', birdseye.height());
 
             // group birdseye components together
             birdseyeGroup = svg.append('g')
-                    .attr('class', 'birdseye');
+                .attr('class', 'birdseye');
 
             // processor in the birdseye
             componentGroup = birdseyeGroup.append('g')
-                    .attr('pointer-events', 'none');
+                .attr('pointer-events', 'none');
 
             // define the brush drag behavior
             var brush = d3.behavior.drag()
-                    .origin(function (d) {
-                        return {
-                            x: d.x,
-                            y: d.y
-                        };
-                    })
-                    .on('dragstart', function () {
-                        // hide the context menu
-                        nf.ContextMenu.hide();
-                    })
-                    .on('drag', function (d) {
-                        d.x += d3.event.dx;
-                        d.y += d3.event.dy;
-
-                        // update the location of the brush
-                        d3.select(this).attr('transform', function () {
-                            return 'translate(' + d.x + ', ' + d.y + ')';
-                        });
-                        // get the current transformation
-                        var scale = nf.Canvas.View.scale();
-                        var translate = nf.Canvas.View.translate();
-
-                        // update the translation according to the delta
-                        translate = [(-d3.event.dx * scale) + translate[0], 
(-d3.event.dy * scale) + translate[1]];
-
-                        // record the current transforms
-                        nf.Canvas.View.translate(translate);
-
-                        // refresh the canvas
-                        nf.Canvas.View.refresh({
-                            persist: false,
-                            transition: false,
-                            refreshComponents: false,
-                            refreshBirdseye: false
-                        });
-                    })
-                    .on('dragend', function () {
-                        // update component visibility
-                        nf.Canvas.View.updateVisibility();
-
-                        // persist the users view
-                        nf.CanvasUtils.persistUserView();
-
-                        // refresh the birdseye
-                        nf.Birdseye.refresh();
+                .origin(function (d) {
+                    return {
+                        x: d.x,
+                        y: d.y
+                    };
+                })
+                .on('dragstart', function () {
+                    // hide the context menu
+                    nf.ContextMenu.hide();
+                })
+                .on('drag', function (d) {
+                    d.x += d3.event.dx;
+                    d.y += d3.event.dy;
+
+                    // update the location of the brush
+                    d3.select(this).attr('transform', function () {
+                        return 'translate(' + d.x + ', ' + d.y + ')';
                     });
+                    // get the current transformation
+                    var scale = nf.Canvas.View.scale();
+                    var translate = nf.Canvas.View.translate();
+
+                    // update the translation according to the delta
+                    translate = [(-d3.event.dx * scale) + translate[0], 
(-d3.event.dy * scale) + translate[1]];
+
+                    // record the current transforms
+                    nf.Canvas.View.translate(translate);
+
+                    // refresh the canvas
+                    nf.Canvas.View.refresh({
+                        persist: false,
+                        transition: false,
+                        refreshComponents: false,
+                        refreshBirdseye: false
+                    });
+                })
+                .on('dragend', function () {
+                    // update component visibility
+                    nf.Canvas.View.updateVisibility();
+
+                    // persist the users view
+                    nf.CanvasUtils.persistUserView();
+
+                    // refresh the birdseye
+                    nf.Birdseye.refresh();
+                });
 
             // context area
             birdseyeGroup.append('g')
-                    .attr({
-                        'pointer-events': 'all',
-                        'class': 'birdseye-brush-container'
-                    })
-                    .append('rect')
-                    .attr('class', 'birdseye-brush moveable')
-                    .datum({
-                        x: 0,
-                        y: 0
-                    })
-                    .call(brush);
+                .attr({
+                    'pointer-events': 'all',
+                    'class': 'birdseye-brush-container'
+                })
+                .append('rect')
+                .attr('class', 'birdseye-brush moveable')
+                .datum({
+                    x: 0,
+                    y: 0
+                })
+                .call(brush);
         },
-        
+
         /**
          * Handles rendering of the birdseye tool.
          */
@@ -342,6 +300,29 @@ nf.Birdseye = (function () {
             if (visible) {
                 refresh(nf.Graph.get());
             }
+        },
+
+        /**
+         * Function that needs to be call when the birdseye visibility changes.
+         *
+         * @param {boolean} isVisible
+         */
+        updateBirdseyeVisibility: function (isVisible) {
+            var birdseye = $('#birdseye');
+
+            // update the outline collapse icon
+            if (isVisible) {
+                // show the outline
+                birdseye.show();
+                visible = true;
+
+                // refresh the birdseye as it may have changed
+                refresh(nf.Graph.get());
+            } else {
+                // hide the outline
+                birdseye.hide();
+                visible = false;
+            }
         }
     };
 }());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/1df8fe44/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.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-header.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
deleted file mode 100644
index fbe4b7d..0000000
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* global nf, d3 */
-
-nf.CanvasHeader = (function () {
-
-    var MIN_TOOLBAR_WIDTH = 640;
-
-    var config = {
-        urls: {
-            helpDocument: '../nifi-docs/documentation'
-        }
-    };
-
-    return {
-        /**
-         * Initialize the canvas header.
-         * 
-         * @argument {boolean} supportsLogin Whether login is supported
-         */
-        init: function (supportsLogin) {
-            // mouse over for the reporting link
-            nf.Common.addHoverEffect('#reporting-link', 'reporting-link', 
'reporting-link-hover').click(function () {
-                nf.Shell.showPage('summary');
-            });
-
-            // mouse over for the counters link
-            nf.Common.addHoverEffect('#counters-link', 'counters-link', 
'counters-link-hover').click(function () {
-                nf.Shell.showPage('counters');
-            });
-
-            // mouse over for the history link
-            nf.Common.addHoverEffect('#history-link', 'history-link', 
'history-link-hover').click(function () {
-                nf.Shell.showPage('history');
-            });
-
-            // mouse over for the provenance link
-            if (nf.Common.canAccessProvenance()) {
-                nf.Common.addHoverEffect('#provenance-link', 
'provenance-link', 'provenance-link-hover').click(function () {
-                    nf.Shell.showPage('provenance');
-                });
-            } else {
-                $('#provenance-link').addClass('provenance-link-disabled');
-            }
-
-            // mouse over for the templates link
-            nf.Common.addHoverEffect('#templates-link', 'templates-link', 
'templates-link-hover').click(function () {
-                nf.Shell.showPage('templates?' + $.param({
-                    groupId: nf.Canvas.getGroupId()
-                }));
-            });
-
-            // mouse over for the flow settings link
-            nf.Common.addHoverEffect('#flow-settings-link', 
'flow-settings-link', 'flow-settings-link-hover').click(function () {
-                nf.Settings.loadSettings().done(function () {
-                    nf.Settings.showSettings();
-                });
-            });
-
-            // mouse over for the cluster link
-            if (nf.Canvas.isClustered()) {
-                nf.Common.addHoverEffect('#cluster-link', 'cluster-link', 
'cluster-link-hover').click(function () {
-                    nf.Shell.showPage('cluster');
-                });
-
-                // show the connected nodes
-                $('#connected-nodes-element').show();
-            } else {
-                $('#cluster-link').hide();
-            }
-
-            // mouse over for the reporting link
-            nf.Common.addHoverEffect('#bulletin-board-link', 
'bulletin-board-link', 'bulletin-board-hover').click(function () {
-                nf.Shell.showPage('bulletin-board');
-            });
-
-            // setup the refresh link actions
-            $('#refresh-required-link').click(function () {
-                nf.CanvasHeader.reloadAndClearWarnings();
-            });
-
-            // configure the about dialog
-            $('#nf-about').modal({
-                overlayBackground: true,
-                buttons: [{
-                        buttonText: 'Ok',
-                        handler: {
-                            click: function () {
-                                $('#nf-about').modal('hide');
-                            }
-                        }
-                    }]
-            });
-
-            // show about dialog
-            $('#about-link').click(function () {
-                $('#nf-about').modal('show');
-            });
-
-            // download the help documentation
-            $('#help-link').click(function () {
-                nf.Shell.showPage(config.urls.helpDocument);
-            });
-
-            // login link
-            $('#login-link').click(function () {
-                nf.Shell.showPage('login', false);
-            });
-            
-            // logout link
-            $('#logout-link').click(function () {
-                nf.Storage.removeItem("jwt");
-                window.location = '/nifi';
-            });
-
-            // if the user is not anonymous or accessing via http
-            if ($('#current-user').text() !== nf.Common.ANONYMOUS_USER_TEXT || 
location.protocol === 'http:') {
-                $('#login-link-container').css('display', 'none');
-            }
-
-            // if accessing via http, don't show the current user
-            if (location.protocol === 'http:') {
-                $('#current-user-container').css('display', 'none');
-            }
-
-            // initialize the new template dialog
-            $('#new-template-dialog').modal({
-                headerText: 'Create Template',
-                overlayBackground: false
-            });
-
-            // configure the fill color dialog
-            $('#fill-color-dialog').modal({
-                headerText: 'Fill',
-                overlayBackground: false,
-                buttons: [{
-                        buttonText: 'Apply',
-                        handler: {
-                            click: function () {
-                                var selection = nf.CanvasUtils.getSelection();
-
-                                // color the selected components
-                                selection.each(function (d) {
-                                    var selected = d3.select(this);
-                                    var selectedData = selected.datum();
-
-                                    // get the color and update the styles
-                                    var color = 
$('#fill-color').minicolors('value');
-
-                                    // ensure the color actually changed
-                                    if (color !== 
selectedData.component.style['background-color']) {
-                                        // build the request entity
-                                        var entity = {
-                                            'revision': 
nf.Client.getRevision(),
-                                            'component': {
-                                                'id': selectedData.id,
-                                                'style': {
-                                                    'background-color': color
-                                                }
-                                            }
-                                        };
-
-                                        // update the style for the specified 
component
-                                        $.ajax({
-                                            type: 'PUT',
-                                            url: selectedData.component.uri,
-                                            data: JSON.stringify(entity),
-                                            dataType: 'json',
-                                            contentType: 'application/json'
-                                        }).done(function (response) {
-                                            // update the revision
-                                            
nf.Client.setRevision(response.revision);
-
-                                            // update the component
-                                            
nf[selectedData.type].set(response);
-                                        }).fail(function (xhr, status, error) {
-                                            if (xhr.status === 400 || 
xhr.status === 404 || xhr.status === 409) {
-                                                nf.Dialog.showOkDialog({
-                                                    dialogContent: 
nf.Common.escapeHtml(xhr.responseText),
-                                                    overlayBackground: true
-                                                });
-                                            }
-                                        });
-                                    }
-                                });
-
-                                // close the dialog
-                                $('#fill-color-dialog').modal('hide');
-                            }
-                        }
-                    }, {
-                        buttonText: 'Cancel',
-                        handler: {
-                            click: function () {
-                                // close the dialog
-                                $('#fill-color-dialog').modal('hide');
-                            }
-                        }
-                    }],
-                handler: {
-                    close: function () {
-                        // clear the current color
-                        $('#fill-color-value').val('');
-                        $('#fill-color').minicolors('value', '');
-                    }
-                }
-            }).draggable({
-                containment: 'parent',
-                handle: '.dialog-header'
-            });
-
-            // initialize the fill color picker
-            $('#fill-color').minicolors({
-                inline: true,
-                change: function (hex, opacity) {
-                    // update the value
-                    $('#fill-color-value').val(hex);
-
-                    // always update the preview
-                    $('#fill-color-processor-preview, 
#fill-color-label-preview').css({
-                        'border-color': hex,
-                        'background': 'linear-gradient(to bottom, #ffffff, ' + 
hex + ')',
-                        'filter': 
'progid:DXImageTransform.Microsoft.gradient(gradientType=0, 
startColorstr=#ffffff, endColorstr=' + hex + ')'
-                    });
-                }
-            });
-
-            // updates the color if its a valid hex color string
-            var updateColor = function () {
-                var hex = $('#fill-color-value').val();
-
-                // only update the fill color when its a valid hex color string
-                // #[six hex characters|three hex characters] case insensitive
-                if (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex)) {
-                    $('#fill-color').minicolors('value', hex);
-                }
-            };
-
-            // apply fill color from field on blur and enter press
-            $('#fill-color-value').on('blur', updateColor).on('keyup', 
function (e) {
-                var code = e.keyCode ? e.keyCode : e.which;
-                if (code === $.ui.keyCode.ENTER) {
-                    updateColor();
-                }
-            });
-
-            var toolbar = $('#toolbar');
-            var groupButton = $('#action-group');
-            $(window).on('resize', function () {
-                if (toolbar.width() < MIN_TOOLBAR_WIDTH && 
groupButton.is(':visible')) {
-                    toolbar.find('.secondary').hide();
-                } else if (toolbar.width() > MIN_TOOLBAR_WIDTH && 
groupButton.is(':hidden')) {
-                    toolbar.find('.secondary').show();
-                }
-            });
-
-            // set up the initial visibility
-            if (toolbar.width() < MIN_TOOLBAR_WIDTH) {
-                toolbar.find('.secondary').hide();
-            }
-        },
-        /**
-         * Reloads and clears any warnings.
-         */
-        reloadAndClearWarnings: function () {
-            nf.Canvas.reload().done(function () {
-                // update component visibility
-                nf.Canvas.View.updateVisibility();
-
-                // refresh the birdseye
-                nf.Birdseye.refresh();
-
-                // hide the refresh link on the canvas
-                $('#stats-last-refreshed').removeClass('alert');
-                $('#refresh-required-container').hide();
-
-                // hide the refresh link on the settings
-                $('#settings-last-refreshed').removeClass('alert');
-                $('#settings-refresh-required-icon').hide();
-            }).fail(function () {
-                nf.Dialog.showOkDialog({
-                    dialogContent: 'Unable to refresh the current group.',
-                    overlayBackground: true
-                });
-            });
-        }
-    };
-}());
\ No newline at end of file

Reply via email to