You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2015/04/10 15:43:43 UTC

[08/62] [abbrv] incubator-nifi git commit: Squashed commit of the following:

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
new file mode 100644
index 0000000..1c25460
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
@@ -0,0 +1,1317 @@
+/*
+ * 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, Slick */
+
+/**
+ * Create a property table. The options are specified in the following
+ * format:
+ *
+ * {
+ *   readOnly: true,
+ *   newPropertyDialogContainer: 'body'
+ * }
+ */
+
+/**
+ * jQuery plugin for a property table.
+ * 
+ * @param {type} $
+ */
+(function ($) {
+    var languageId = 'nfel';
+    var editorClass = languageId + '-editor';
+
+    // text editor
+    var textEditor = function (args) {
+        var scope = this;
+        var initialValue = '';
+        var previousValue;
+        var propertyDescriptor;
+        var wrapper;
+        var isEmpty;
+        var input;
+
+        this.init = function () {
+            var container = $('body');
+
+            // get the property descriptor
+            var gridContainer = $(args.grid.getContainerNode());
+            var descriptors = gridContainer.data('descriptors');
+            propertyDescriptor = descriptors[args.item.property];
+
+            // record the previous value
+            previousValue = args.item[args.column.field];
+
+            // create the wrapper
+            wrapper = $('<div></div>').css({
+                'z-index': 100000,
+                'position': 'absolute',
+                'background': 'white',
+                'padding': '5px',
+                'overflow': 'hidden',
+                'border': '3px solid #365C6A',
+                'box-shadow': '4px 4px 6px rgba(0, 0, 0, 0.9)',
+                'cursor': 'move'
+            }).draggable({
+                cancel: '.button, textarea, .nf-checkbox',
+                containment: 'parent'
+            }).appendTo(container);
+
+            // create the input field
+            input = $('<textarea hidefocus rows="5"/>').css({
+                'background': 'white',
+                'width': args.position.width + 'px',
+                'min-width': '150px',
+                'height': '80px',
+                'border-width': '0',
+                'outline': '0',
+                'overflow-y': 'auto',
+                'resize': 'both',
+                'margin-bottom': '28px'
+            }).tab().on('keydown', scope.handleKeyDown).appendTo(wrapper);
+
+            // create the button panel
+            var stringCheckPanel = $('<div class="string-check-container">');
+
+            // build the custom checkbox
+            isEmpty = $('<div class="nf-checkbox string-check"/>').appendTo(stringCheckPanel);
+            $('<span class="string-check-label">&nbsp;Empty</span>').appendTo(stringCheckPanel);
+
+            var ok = $('<div class="button button-normal">Ok</div>').on('click', scope.save);
+            var cancel = $('<div class="button button-normal">Cancel</div>').on('click', scope.cancel);
+            $('<div></div>').css({
+                'position': 'absolute',
+                'bottom': '0',
+                'left': '0',
+                'right': '0',
+                'padding': '0 3px 5px'
+            }).append(stringCheckPanel).append(ok).append(cancel).append('<div class="clear"></div>').appendTo(wrapper);
+
+            // position and focus
+            scope.position(args.position);
+            input.focus().select();
+        };
+
+        this.handleKeyDown = function (e) {
+            if (e.which === $.ui.keyCode.ENTER && !e.shiftKey) {
+                scope.save();
+            } else if (e.which === $.ui.keyCode.ESCAPE) {
+                scope.cancel();
+            }
+        };
+
+        this.save = function () {
+            args.commitChanges();
+        };
+
+        this.cancel = function () {
+            input.val(initialValue);
+            args.cancelChanges();
+        };
+
+        this.hide = function () {
+            wrapper.hide();
+        };
+
+        this.show = function () {
+            wrapper.show();
+        };
+
+        this.position = function (position) {
+            wrapper.css({
+                'top': position.top - 5,
+                'left': position.left - 5
+            });
+        };
+
+        this.destroy = function () {
+            wrapper.remove();
+        };
+
+        this.focus = function () {
+            input.focus();
+        };
+
+        this.loadValue = function (item) {
+            // determine if this is a sensitive property
+            var isEmptyChecked = false;
+            var sensitive = nf.Common.isSensitiveProperty(propertyDescriptor);
+
+            // determine the value to use when populating the text field
+            if (nf.Common.isDefinedAndNotNull(item[args.column.field])) {
+                if (sensitive) {
+                    initialValue = nf.Common.config.sensitiveText;
+                } else {
+                    initialValue = item[args.column.field];
+                    isEmptyChecked = initialValue === '';
+                }
+            }
+
+            // determine if its an empty string
+            var checkboxStyle = isEmptyChecked ? 'checkbox-checked' : 'checkbox-unchecked';
+            isEmpty.addClass(checkboxStyle);
+
+            // style sensitive properties differently
+            if (sensitive) {
+                input.addClass('sensitive').keydown(function () {
+                    var sensitiveInput = $(this);
+                    if (sensitiveInput.hasClass('sensitive')) {
+                        sensitiveInput.removeClass('sensitive');
+                        if (sensitiveInput.val() === nf.Common.config.sensitiveText) {
+                            sensitiveInput.val('');
+                        }
+                    }
+                });
+            }
+
+            input.val(initialValue);
+            input.select();
+        };
+
+        this.serializeValue = function () {
+            // if the field has been cleared, set the value accordingly
+            if (input.val() === '') {
+                // if the user has checked the empty string checkbox, use emtpy string
+                if (isEmpty.hasClass('checkbox-checked')) {
+                    return '';
+                } else {
+                    // otherwise if the property is required
+                    if (nf.Common.isRequiredProperty(propertyDescriptor)) {
+                        if (nf.Common.isBlank(propertyDescriptor.defaultValue)) {
+                            return previousValue;
+                        } else {
+                            return propertyDescriptor.defaultValue;
+                        }
+                    } else {
+                        // if the property is not required, clear the value
+                        return null;
+                    }
+                }
+            } else {
+                // if the field still has the sensitive class it means a property
+                // was edited but never modified so we should restore the previous
+                // value instead of setting it to the 'sensitive value set' string
+                if (input.hasClass('sensitive')) {
+                    return previousValue;
+                } else {
+                    // if there is text specified, use that value
+                    return input.val();
+                }
+            }
+        };
+
+        this.applyValue = function (item, state) {
+            item[args.column.field] = state;
+        };
+
+        this.isValueChanged = function () {
+            return scope.serializeValue() !== previousValue;
+        };
+
+        this.validate = function () {
+            return {
+                valid: true,
+                msg: null
+            };
+        };
+
+        // initialize the custom long text editor
+        this.init();
+    };
+
+    // nfel editor
+    var nfelEditor = function (args) {
+        var scope = this;
+        var initialValue = '';
+        var previousValue;
+        var propertyDescriptor;
+        var isEmpty;
+        var wrapper;
+        var editor;
+
+        this.init = function () {
+            var container = $('body');
+
+            // get the property descriptor
+            var gridContainer = $(args.grid.getContainerNode());
+            var descriptors = gridContainer.data('descriptors');
+            propertyDescriptor = descriptors[args.item.property];
+
+            // determine if this is a sensitive property
+            var sensitive = nf.Common.isSensitiveProperty(propertyDescriptor);
+
+            // record the previous value
+            previousValue = args.item[args.column.field];
+
+            var languageId = 'nfel';
+            var editorClass = languageId + '-editor';
+
+            // create the wrapper
+            wrapper = $('<div></div>').addClass('slickgrid-nfel-editor').css({
+                'z-index': 14000,
+                'position': 'absolute',
+                'background': 'white',
+                'padding': '5px',
+                'overflow': 'hidden',
+                'border': '3px solid #365C6A',
+                'box-shadow': '4px 4px 6px rgba(0, 0, 0, 0.9)',
+                'cursor': 'move'
+            }).draggable({
+                cancel: 'input, textarea, pre, .nf-checkbox, .button, .' + editorClass,
+                containment: 'parent'
+            }).appendTo(container);
+
+            // create the editor
+            editor = $('<div></div>').addClass(editorClass).appendTo(wrapper).nfeditor({
+                languageId: languageId,
+                width: args.position.width,
+                minWidth: 175,
+                minHeight: 100,
+                resizable: true,
+                sensitive: sensitive,
+                escape: function () {
+                    scope.cancel();
+                },
+                enter: function () {
+                    scope.save();
+                }
+            });
+
+            // create the button panel
+            var stringCheckPanel = $('<div class="string-check-container">');
+
+            // build the custom checkbox
+            isEmpty = $('<div class="nf-checkbox string-check"/>').appendTo(stringCheckPanel);
+            $('<span class="string-check-label">&nbsp;Empty</span>').appendTo(stringCheckPanel);
+
+            var ok = $('<div class="button button-normal">Ok</div>').on('click', scope.save);
+            var cancel = $('<div class="button button-normal">Cancel</div>').on('click', scope.cancel);
+            $('<div></div>').css({
+                'position': 'absolute',
+                'bottom': '0',
+                'left': '0',
+                'right': '0',
+                'padding': '0 3px 5px 1px'
+            }).append(stringCheckPanel).append(ok).append(cancel).append('<div class="clear"></div>').appendTo(wrapper);
+
+            // position and focus
+            scope.position(args.position);
+            editor.nfeditor('focus').nfeditor('selectAll');
+        };
+
+        this.save = function () {
+            args.commitChanges();
+        };
+
+        this.cancel = function () {
+            editor.nfeditor('setValue', initialValue);
+            args.cancelChanges();
+        };
+
+        this.hide = function () {
+            wrapper.hide();
+        };
+
+        this.show = function () {
+            wrapper.show();
+            editor.nfeditor('setSize', args.position.width, null).nfeditor('refresh');
+        };
+
+        this.position = function (position) {
+            wrapper.css({
+                'top': position.top - 5,
+                'left': position.left - 5
+            });
+        };
+
+        this.destroy = function () {
+            editor.nfeditor('destroy');
+            wrapper.remove();
+        };
+
+        this.focus = function () {
+            editor.nfeditor('focus');
+        };
+
+        this.loadValue = function (item) {
+            // determine if this is a sensitive property
+            var isEmptyChecked = false;
+            var sensitive = nf.Common.isSensitiveProperty(propertyDescriptor);
+
+            // determine the value to use when populating the text field
+            if (nf.Common.isDefinedAndNotNull(item[args.column.field])) {
+                if (sensitive) {
+                    initialValue = nf.Common.config.sensitiveText;
+                } else {
+                    initialValue = item[args.column.field];
+                    isEmptyChecked = initialValue === '';
+                }
+            }
+
+            // determine if its an empty string
+            var checkboxStyle = isEmptyChecked ? 'checkbox-checked' : 'checkbox-unchecked';
+            isEmpty.addClass(checkboxStyle);
+
+            editor.nfeditor('setValue', initialValue).nfeditor('selectAll');
+        };
+
+        this.serializeValue = function () {
+            var value = editor.nfeditor('getValue');
+
+            // if the field has been cleared, set the value accordingly
+            if (value === '') {
+                // if the user has checked the empty string checkbox, use emtpy string
+                if (isEmpty.hasClass('checkbox-checked')) {
+                    return '';
+                } else {
+                    // otherwise if the property is required
+                    if (nf.Common.isRequiredProperty(propertyDescriptor)) {
+                        if (nf.Common.isBlank(propertyDescriptor.defaultValue)) {
+                            return previousValue;
+                        } else {
+                            return propertyDescriptor.defaultValue;
+                        }
+                    } else {
+                        // if the property is not required, clear the value
+                        return null;
+                    }
+                }
+            } else {
+                // if the field still has the sensitive class it means a property
+                // was edited but never modified so we should restore the previous
+                // value instead of setting it to the 'sensitive value set' string
+
+                // if the field hasn't been modified return the previous value... this
+                // is important because sensitive properties contain the text 'sensitive
+                // value set' which is cleared when the value is edited. we do not 
+                // want to actually use this value
+                if (editor.nfeditor('isModified') === false) {
+                    return previousValue;
+                } else {
+                    // if there is text specified, use that value
+                    return value;
+                }
+            }
+        };
+
+        this.applyValue = function (item, state) {
+            item[args.column.field] = state;
+        };
+
+        this.isValueChanged = function () {
+            return scope.serializeValue() !== previousValue;
+        };
+
+        this.validate = function () {
+            return {
+                valid: true,
+                msg: null
+            };
+        };
+
+        // initialize the custom long nfel editor
+        this.init();
+    };
+
+    // combo editor
+    var comboEditor = function (args) {
+        var scope = this;
+        var initialValue = null;
+        var wrapper;
+        var combo;
+        var propertyDescriptor;
+
+        this.init = function () {
+            var container = $('body');
+
+            // get the property descriptor
+            var gridContainer = $(args.grid.getContainerNode());
+            var descriptors = gridContainer.data('descriptors');
+            propertyDescriptor = descriptors[args.item.property];
+
+            // create the wrapper
+            wrapper = $('<div></div>').css({
+                'z-index': 1999,
+                'position': 'absolute',
+                'background': 'white',
+                'padding': '5px',
+                'overflow': 'hidden',
+                'border': '3px solid #365C6A',
+                'box-shadow': '4px 4px 6px rgba(0, 0, 0, 0.9)',
+                'cursor': 'move'
+            }).draggable({
+                cancel: '.button, .combo',
+                containment: 'parent'
+            }).appendTo(container);
+
+            // check for allowable values which will drive which editor to use
+            var allowableValues = nf.Common.getAllowableValues(propertyDescriptor);
+
+            // show the output port options
+            var options = [];
+            if (propertyDescriptor.required === false) {
+                options.push({
+                    text: 'No value',
+                    value: null,
+                    optionClass: 'unset'
+                });
+            }
+            if ($.isArray(allowableValues)) {
+                $.each(allowableValues, function (i, allowableValue) {
+                    options.push({
+                        text: allowableValue.displayName,
+                        value: allowableValue.value,
+                        description: nf.Common.escapeHtml(allowableValue.description)
+                    });
+                });
+            }
+
+            // ensure the options there is at least one option
+            if (options.length === 0) {
+                options.push({
+                    text: 'No value',
+                    value: null,
+                    optionClass: 'unset',
+                    disabled: true
+                });
+            }
+
+            // determine the max height
+            var position = args.position;
+            var windowHeight = $(window).height();
+            var maxHeight = windowHeight - position.bottom - 16;
+
+            // build the combo field
+            combo = $('<div class="value-combo combo"></div>').combo({
+                options: options,
+                maxHeight: maxHeight
+            }).width(position.width - 16).appendTo(wrapper);
+
+            // add buttons for handling user input
+            $('<div class="button button-normal">Cancel</div>').css({
+                'margin': '0 0 0 5px',
+                'float': 'left'
+            }).on('click', scope.cancel).appendTo(wrapper);
+            $('<div class="button button-normal">Ok</div>').css({
+                'margin': '0 0 0 5px',
+                'float': 'left'
+            }).on('click', scope.save).appendTo(wrapper);
+
+            // position and focus
+            scope.position(position);
+        };
+
+        this.save = function () {
+            args.commitChanges();
+        };
+
+        this.cancel = function () {
+            args.cancelChanges();
+        };
+
+        this.hide = function () {
+            wrapper.hide();
+        };
+
+        this.show = function () {
+            wrapper.show();
+        };
+
+        this.position = function (position) {
+            wrapper.css({
+                'top': position.top - 5,
+                'left': position.left - 5
+            });
+        };
+
+        this.destroy = function () {
+            wrapper.remove();
+        };
+
+        this.focus = function () {
+        };
+
+        this.loadValue = function (item) {
+            // select as appropriate
+            if (!nf.Common.isUndefined(item.value)) {
+                initialValue = item.value;
+
+                combo.combo('setSelectedOption', {
+                    value: item.value
+                });
+            } else if (nf.Common.isDefinedAndNotNull(propertyDescriptor.defaultValue)) {
+                initialValue = propertyDescriptor.defaultValue;
+
+                combo.combo('setSelectedOption', {
+                    value: propertyDescriptor.defaultValue
+                });
+            }
+        };
+
+        this.serializeValue = function () {
+            var selectedOption = combo.combo('getSelectedOption');
+            return selectedOption.value;
+        };
+
+        this.applyValue = function (item, state) {
+            item[args.column.field] = state;
+        };
+
+        this.isValueChanged = function () {
+            var selectedOption = combo.combo('getSelectedOption');
+            return (!(selectedOption.value === "" && initialValue === null)) && (selectedOption.value !== initialValue);
+        };
+
+        this.validate = function () {
+            return {
+                valid: true,
+                msg: null
+            };
+        };
+
+        // initialize the custom long text editor
+        this.init();
+    };
+
+    /**
+     * Shows the property value for the specified row and cell.
+     * 
+     * @param {type} propertyGrid
+     * @param {type} descriptors 
+     * @param {type} row
+     * @param {type} cell
+     */
+    var showPropertyValue = function (propertyGrid, descriptors, row, cell) {
+        // remove any currently open detail dialogs
+        removeAllPropertyDetailDialogs();
+
+        // get the property in question
+        var propertyData = propertyGrid.getData();
+        var property = propertyData.getItem(row);
+
+        // ensure there is a value
+        if (nf.Common.isDefinedAndNotNull(property.value)) {
+
+            // get the descriptor to insert the description tooltip
+            var propertyDescriptor = descriptors[property.property];
+
+            // ensure we're not dealing with a sensitive property
+            if (!nf.Common.isSensitiveProperty(propertyDescriptor)) {
+
+                // get details about the location of the cell
+                var cellNode = $(propertyGrid.getCellNode(row, cell));
+                var offset = cellNode.offset();
+
+                // create the wrapper
+                var wrapper = $('<div class="property-detail"></div>').css({
+                    'z-index': 100000,
+                    'position': 'absolute',
+                    'background': 'white',
+                    'padding': '5px',
+                    'overflow': 'hidden',
+                    'border': '3px solid #365C6A',
+                    'box-shadow': '4px 4px 6px rgba(0, 0, 0, 0.9)',
+                    'cursor': 'move',
+                    'top': offset.top - 5,
+                    'left': offset.left - 5
+                }).appendTo('body');
+
+                var editor = null;
+
+                // so the nfel editor is appropriate
+                if (nf.Common.supportsEl(propertyDescriptor)) {
+                    var languageId = 'nfel';
+                    var editorClass = languageId + '-editor';
+
+                    // prevent dragging over the nf editor
+                    wrapper.draggable({
+                        cancel: 'input, textarea, pre, .button, .' + editorClass,
+                        containment: 'parent'
+                    });
+
+                    // create the editor
+                    editor = $('<div></div>').addClass(editorClass).appendTo(wrapper).nfeditor({
+                        languageId: languageId,
+                        width: cellNode.width(),
+                        content: property.value,
+                        minWidth: 175,
+                        minHeight: 100,
+                        readOnly: true,
+                        resizable: true
+                    });
+                } else {
+                    // prevent dragging over standard components
+                    wrapper.draggable({
+                        containment: 'parent'
+                    });
+
+                    // create the input field
+                    $('<textarea hidefocus rows="5" readonly="readonly"/>').css({
+                        'background': 'white',
+                        'width': cellNode.width() + 'px',
+                        'height': '80px',
+                        'border-width': '0',
+                        'outline': '0',
+                        'overflow-y': 'auto',
+                        'resize': 'both',
+                        'margin-bottom': '28px'
+                    }).text(property.value).appendTo(wrapper);
+                }
+
+                // add an ok button that will remove the entire pop up
+                var ok = $('<div class="button button-normal">Ok</div>').on('click', function () {
+                    // clean up the editor
+                    if (editor !== null) {
+                        editor.nfeditor('destroy');
+                    }
+
+                    // clean up the rest
+                    wrapper.hide().remove();
+                });
+                $('<div></div>').css({
+                    'position': 'absolute',
+                    'bottom': '0',
+                    'left': '0',
+                    'right': '0',
+                    'padding': '0 3px 5px'
+                }).append(ok).append('<div class="clear"></div>').appendTo(wrapper);
+            }
+        }
+    };
+
+    /**
+     * Removes all currently open property detail dialogs.
+     */
+    var removeAllPropertyDetailDialogs = function () {
+        $('body').children('div.property-detail').hide().remove();
+    };
+
+    var initPropertiesTable = function (table, options) {
+        // function for formatting the property name
+        var nameFormatter = function (row, cell, value, columnDef, dataContext) {
+            var nameWidthOffset = 10;
+            var cellContent = $('<div></div>');
+
+            // format the contents
+            var formattedValue = $('<span/>').addClass('table-cell').text(value).appendTo(cellContent);
+            if (dataContext.type === 'required') {
+                formattedValue.addClass('required');
+            }
+
+            // get the property descriptor
+            var descriptors = table.data('descriptors');
+            var propertyDescriptor = descriptors[dataContext.property];
+
+            // show the property description if applicable
+            if (nf.Common.isDefinedAndNotNull(propertyDescriptor)) {
+                if (!nf.Common.isBlank(propertyDescriptor.description) || !nf.Common.isBlank(propertyDescriptor.defaultValue) || !nf.Common.isBlank(propertyDescriptor.supportsEl)) {
+                    $('<img class="icon-info" src="images/iconInfo.png" alt="Info" title="" style="float: right; margin-right: 6px; margin-top: 4px;" />').appendTo(cellContent);
+                    $('<span class="hidden property-descriptor-name"></span>').text(dataContext.property).appendTo(cellContent);
+                    nameWidthOffset = 26; // 10 + icon width (10) + icon margin (6)
+                }
+            }
+
+            // adjust the width accordingly
+            formattedValue.width(columnDef.width - nameWidthOffset).ellipsis();
+
+            // return the cell content
+            return cellContent.html();
+        };
+
+        // function for formatting the property value
+        var valueFormatter = function (row, cell, value, columnDef, dataContext) {
+            var valueMarkup;
+            if (nf.Common.isDefinedAndNotNull(value)) {
+                // get the property descriptor
+                var descriptors = table.data('descriptors');
+                var propertyDescriptor = descriptors[dataContext.property];
+
+                // determine if the property is sensitive
+                if (nf.Common.isSensitiveProperty(propertyDescriptor)) {
+                    valueMarkup = '<span class="table-cell sensitive">Sensitive value set</span>';
+                } else {
+                    // if there are allowable values, attempt to swap out for the display name
+                    var allowableValues = nf.Common.getAllowableValues(propertyDescriptor);
+                    if ($.isArray(allowableValues)) {
+                        $.each(allowableValues, function (_, allowableValue) {
+                            if (value === allowableValue.value) {
+                                value = allowableValue.displayName;
+                                return false;
+                            }
+                        });
+                    }
+
+                    if (value === '') {
+                        valueMarkup = '<span class="table-cell blank">Empty string set</span>';
+                    } else {
+                        valueMarkup = '<div class="table-cell value"><pre class="ellipsis">' + nf.Common.escapeHtml(value) + '</pre></div>';
+                    }
+                }
+            } else {
+                valueMarkup = '<span class="unset">No value set</span>';
+            }
+
+            // format the contents
+            var content = $(valueMarkup);
+            if (dataContext.type === 'required') {
+                content.addClass('required');
+            }
+            content.find('.ellipsis').width(columnDef.width - 10).ellipsis();
+
+            // return the appropriate markup
+            return $('<div/>').append(content).html();
+        };
+
+        var propertyColumns = [
+            {id: 'property', field: 'displayName', name: 'Property', sortable: false, resizable: true, rerenderOnResize: true, formatter: nameFormatter},
+            {id: 'value', field: 'value', name: 'Value', sortable: false, resizable: true, cssClass: 'pointer', rerenderOnResize: true, formatter: valueFormatter}
+        ];
+
+        if (options.readOnly !== true) {
+            // custom formatter for the actions column
+            var actionFormatter = function (row, cell, value, columnDef, dataContext) {
+                var markup = '';
+
+                // allow user defined properties to be removed
+                if (dataContext.type === 'userDefined') {
+                    markup = '<img src="images/iconDelete.png" title="Delete" class="delete-property pointer" style="margin-top: 2px" />';
+                }
+
+                return markup;
+            };
+            propertyColumns.push({id: "actions", name: "&nbsp;", minWidth: 20, width: 20, formatter: actionFormatter});
+        }
+
+        var propertyConfigurationOptions = {
+            forceFitColumns: true,
+            enableTextSelectionOnCells: true,
+            enableCellNavigation: true,
+            enableColumnReorder: false,
+            editable: options.readOnly !== true,
+            enableAddRow: false,
+            autoEdit: false
+        };
+
+        // initialize the dataview
+        var propertyData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        propertyData.setItems([]);
+        propertyData.setFilterArgs({
+            searchString: '',
+            property: 'hidden'
+        });
+        propertyData.setFilter(filter);
+        propertyData.getItemMetadata = function (index) {
+            var item = propertyData.getItem(index);
+
+            // get the property descriptor
+            var descriptors = table.data('descriptors');
+            var propertyDescriptor = descriptors[item.property];
+
+            // support el if specified or unsure yet (likely a dynamic property)
+            if (nf.Common.isUndefinedOrNull(propertyDescriptor) || nf.Common.supportsEl(propertyDescriptor)) {
+                return {
+                    columns: {
+                        value: {
+                            editor: nfelEditor
+                        }
+                    }
+                };
+            } else {
+                // check for allowable values which will drive which editor to use
+                var allowableValues = nf.Common.getAllowableValues(propertyDescriptor);
+                if ($.isArray(allowableValues)) {
+                    return {
+                        columns: {
+                            value: {
+                                editor: comboEditor
+                            }
+                        }
+                    };
+                } else {
+                    return {
+                        columns: {
+                            value: {
+                                editor: textEditor
+                            }
+                        }
+                    };
+                }
+            }
+        };
+
+        // initialize the grid
+        var propertyGrid = new Slick.Grid(table, propertyData, propertyColumns, propertyConfigurationOptions);
+        propertyGrid.setSelectionModel(new Slick.RowSelectionModel());
+        propertyGrid.onClick.subscribe(function (e, args) {
+            if (propertyGrid.getColumns()[args.cell].id === 'value') {
+                if (options.readOnly === true) {
+                    var descriptors = table.data('descriptors');
+                    showPropertyValue(propertyGrid, descriptors, args.row, args.cell);
+                } else {
+                    // edits the clicked cell
+                    propertyGrid.gotoCell(args.row, args.cell, true);
+                }
+
+                // prevents standard edit logic
+                e.stopImmediatePropagation();
+            } else if (propertyGrid.getColumns()[args.cell].id === 'actions') {
+                var target = $(e.target);
+                if (target.hasClass('delete-property')) {
+                    // mark the property in question for removal
+                    var property = propertyData.getItem(args.row);
+                    property.hidden = true;
+
+                    // refresh the table
+                    propertyData.updateItem(property.id, property);
+
+                    // prevents standard edit logic
+                    e.stopImmediatePropagation();
+                }
+            }
+        });
+        propertyGrid.onKeyDown.subscribe(function(e, args) {
+            if (e.which === $.ui.keyCode.ESCAPE) {
+                var editorLock = propertyGrid.getEditorLock();
+                if (editorLock.isActive()) {
+                    editorLock.cancelCurrentEdit();
+                    
+                    // prevents standard cancel logic - standard logic does
+                    // not stop propagation when escape is pressed
+                    e.stopImmediatePropagation();
+                    e.preventDefault();
+                }
+            }
+        });
+
+        // wire up the dataview to the grid
+        propertyData.onRowCountChanged.subscribe(function (e, args) {
+            propertyGrid.updateRowCount();
+            propertyGrid.render();
+        });
+        propertyData.onRowsChanged.subscribe(function (e, args) {
+            propertyGrid.invalidateRows(args.rows);
+            propertyGrid.render();
+        });
+
+        // hold onto an instance of the grid and listen for mouse events to add tooltips where appropriate
+        table.data('gridInstance', propertyGrid).on('mouseenter', 'div.slick-cell', function (e) {
+            var infoIcon = $(this).find('img.icon-info');
+            if (infoIcon.length && !infoIcon.data('qtip')) {
+                var property = $(this).find('span.property-descriptor-name').text();
+
+                // get the property descriptor
+                var descriptors = table.data('descriptors');
+                var propertyDescriptor = descriptors[property];
+
+                // get the history
+                var history = table.data('history');
+                var propertyHistory = history[property];
+
+                // format the tooltip
+                var tooltip = nf.Common.formatPropertyTooltip(propertyDescriptor, propertyHistory);
+
+                if (nf.Common.isDefinedAndNotNull(tooltip)) {
+                    infoIcon.qtip($.extend({
+                        content: tooltip
+                    }, nf.Common.config.tooltipConfig));
+                }
+            }
+        });
+    };
+
+    var saveRow = function (table) {
+        // get the property grid to commit the current edit
+        var propertyGrid = table.data('gridInstance');
+        if (nf.Common.isDefinedAndNotNull(propertyGrid)) {
+            var editController = propertyGrid.getEditController();
+            editController.commitCurrentEdit();
+        }
+    };
+
+    /**
+     * 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) {
+        return item.hidden === false;
+    };
+
+    /**
+     * Loads the specified properties.
+     * 
+     * @param {type} table 
+     * @param {type} properties
+     * @param {type} descriptors
+     * @param {type} history
+     */
+    var loadProperties = function (table, properties, descriptors, history) {
+        // save the descriptors and history
+        table.data({
+            'descriptors': descriptors,
+            'history': history
+        });
+
+        // get the grid
+        var propertyGrid = table.data('gridInstance');
+        var propertyData = propertyGrid.getData();
+
+        // generate the properties
+        if (nf.Common.isDefinedAndNotNull(properties)) {
+            propertyData.beginUpdate();
+
+            var i = 0;
+            $.each(properties, function (name, value) {
+                // get the property descriptor
+                var descriptor = descriptors[name];
+
+                // determine the property type
+                var type = 'userDefined';
+                var displayName = name;
+                if (nf.Common.isDefinedAndNotNull(descriptor)) {
+                    if (nf.Common.isRequiredProperty(descriptor)) {
+                        type = 'required';
+                    } else if (nf.Common.isDynamicProperty(descriptor)) {
+                        type = 'userDefined';
+                    } else {
+                        type = 'optional';
+                    }
+
+                    // use the display name if possible
+                    displayName = descriptor.displayName;
+
+                    // determine the value
+                    if (nf.Common.isNull(value) && nf.Common.isDefinedAndNotNull(descriptor.defaultValue)) {
+                        value = descriptor.defaultValue;
+                    }
+                }
+
+                // add the row
+                propertyData.addItem({
+                    id: i++,
+                    hidden: false,
+                    property: name,
+                    displayName: displayName,
+                    previousValue: value,
+                    value: value,
+                    type: type
+                });
+            });
+
+            propertyData.endUpdate();
+        }
+    };
+
+    /**
+     * Clears the property table container.
+     * 
+     * @param {jQuery} propertyTableContainer
+     */
+    var clear = function (propertyTableContainer) {
+        var options = propertyTableContainer.data('options');
+        if (options.readOnly === true) {
+            removeAllPropertyDetailDialogs();
+        } else {
+            // clear any existing new property dialogs
+            if (nf.Common.isDefinedAndNotNull(options.newPropertyDialogContainer)) {
+                $(options.newPropertyDialogContainer).children('div.new-property-dialog').hide();
+            }
+        }
+
+        // clean up data
+        var table = propertyTableContainer.find('div.property-table');
+        table.removeData('descriptors history');
+
+        // clean up any tooltips that may have been generated
+        nf.Common.cleanUpTooltips(table, 'img.icon-info');
+
+        // clear the data in the grid
+        var propertyGrid = table.data('gridInstance');
+        var propertyData = propertyGrid.getData();
+        propertyData.setItems([]);
+    };
+
+    var methods = {
+        /**
+         * Initializes the tag cloud.
+         * 
+         * @argument {object} options The options for the tag cloud
+         */
+        init: function (options) {
+            return this.each(function () {
+                // ensure the options have been properly specified
+                if (nf.Common.isDefinedAndNotNull(options)) {
+                    // get the tag cloud
+                    var propertyTableContainer = $(this);
+
+                    // clear any current contents, remote events, and store options
+                    propertyTableContainer.empty().unbind().data('options', options);
+
+                    // build the component
+                    var header = $('<div class="properties-header"></div>').appendTo(propertyTableContainer);
+                    $('<div class="required-property-note">Required field</div>').appendTo(header);
+
+                    // build the table
+                    var table = $('<div class="property-table"></div>').appendTo(propertyTableContainer);
+
+                    // optionally add a add new property button
+                    if (options.readOnly !== true && nf.Common.isDefinedAndNotNull(options.newPropertyDialogContainer)) {
+                        // build the new property dialog
+                        var newPropertyDialogMarkup = 
+                                '<div class="new-property-dialog dialog">' +
+                                    '<div>' +
+                                        '<div class="setting-name">Property name</div>' +
+                                        '<div class="setting-field new-property-name-container">' +
+                                            '<input class="new-property-name" type="text"/>' +
+                                        '</div>' +
+                                    '</div>' +
+                                    '<div class="new-property-button-container">' +
+                                        '<div class="new-property-ok button button-normal">Ok</div>' +
+                                        '<div class="new-property-cancel button button-normal">Cancel</div>' +
+                                        '<div class="clear"></div>' +
+                                    '</div>' +
+                                '</div>';
+
+                        var newPropertyDialog = $(newPropertyDialogMarkup).appendTo(options.newPropertyDialogContainer);
+                        var newPropertyNameField = newPropertyDialog.find('input.new-property-name');
+
+                        var add = function () {
+                            var propertyName = $.trim(newPropertyNameField.val());
+
+                            // ensure the property name and value is specified
+                            if (propertyName !== '') {
+                                // load the property descriptor if possible
+                                if (typeof options.descriptorDeferred === 'function') {
+                                    options.descriptorDeferred(propertyName).done(function(response) {
+                                        var descriptor = response.propertyDescriptor;
+                                        
+                                        // store the descriptor for use later
+                                        var descriptors = table.data('descriptors');
+                                        if (!nf.Common.isUndefined(descriptors)) {
+                                            descriptors[descriptor.name] = descriptor;
+                                        }
+                                    });
+                                }
+                                
+                                // add a row for the new property
+                                var propertyGrid = table.data('gridInstance');
+                                var propertyData = propertyGrid.getData();
+                                propertyData.addItem({
+                                    id: propertyData.getLength(),
+                                    hidden: false,
+                                    property: propertyName,
+                                    displayName: propertyName,
+                                    previousValue: null,
+                                    value: null,
+                                    type: 'userDefined'
+                                });
+                            } else {
+                                nf.Dialog.showOkDialog({
+                                    dialogContent: 'Property name must be specified.',
+                                    overlayBackground: false
+                                });
+                            }
+
+                            // close the dialog
+                            newPropertyDialog.hide();
+                        };
+
+                        var cancel = function () {
+                            newPropertyDialog.hide();
+                        };
+
+                        // make the new property dialog draggable
+                        newPropertyDialog.draggable({
+                            cancel: 'input, textarea, pre, .button, .' + editorClass,
+                            containment: 'body'
+                        }).on('click', 'div.new-property-ok', add).on('click', 'div.new-property-cancel', cancel);
+
+                        // build the control to open the new property dialog
+                        var addProperty = $('<div class="add-property"></div>').appendTo(header);
+                        $('<div class="add-property-icon add-icon-bg"></div>').on('click', function () {
+                            // close all fields currently being edited
+                            saveRow(table);
+
+                            // clear the dialog
+                            newPropertyNameField.val('');
+
+                            // open the new property dialog
+                            newPropertyDialog.center().show();
+
+                            // set the initial focus
+                            newPropertyNameField.focus();
+                        }).on('mouseenter', function () {
+                            $(this).removeClass('add-icon-bg').addClass('add-icon-bg-hover');
+                        }).on('mouseleave', function () {
+                            $(this).removeClass('add-icon-bg-hover').addClass('add-icon-bg');
+                        }).appendTo(addProperty);
+                        $('<div class="add-property-text">New property</div>').appendTo(addProperty);
+                    }
+                    $('<div class="clear"></div>').appendTo(header);
+
+                    // initializes the properties table
+                    initPropertiesTable(table, options);
+                }
+            });
+        },
+        
+        /**
+         * Loads the specified properties.
+         * 
+         * @argument {object} properties        The properties
+         * @argument {map} descriptors          The property descriptors (property name -> property descriptor)
+         * @argument {map} history
+         */
+        loadProperties: function (properties, descriptors, history) {
+            return this.each(function () {
+                var table = $(this).find('div.property-table');
+                loadProperties(table, properties, descriptors, history);
+            });
+        },
+        
+        /**
+         * Saves the last edited row in the specified grid.
+         */
+        saveRow: function () {
+            return this.each(function () {
+                var table = $(this).find('div.property-table');
+                saveRow(table);
+            });
+        },
+        
+        /**
+         * Update the size of the grid based on its container's current size.
+         */
+        resetTableSize: function () {
+            return this.each(function () {
+                var table = $(this).find('div.property-table');
+                var propertyGrid = table.data('gridInstance');
+                if (nf.Common.isDefinedAndNotNull(propertyGrid)) {
+                    propertyGrid.resizeCanvas();
+                }
+            });
+        },
+        
+        /**
+         * Cancels the edit in the specified row.
+         */
+        cancelEdit: function () {
+            return this.each(function () {
+                var table = $(this).find('div.property-table');
+                var propertyGrid = table.data('gridInstance');
+                if (nf.Common.isDefinedAndNotNull(propertyGrid)) {
+                    var editController = propertyGrid.getEditController();
+                    editController.cancelCurrentEdit();
+                }
+            });
+        },
+        
+        /**
+         * Destroys the property table.
+         */
+        destroy: function () {
+            return this.each(function () {
+                var propertyTableContainer = $(this);
+                var options = propertyTableContainer.data('options');
+                
+                // clear the property table container
+                clear(propertyTableContainer);
+                
+                // clear any existing new property dialogs
+                if (nf.Common.isDefinedAndNotNull(options.newPropertyDialogContainer)) {
+                    $(options.newPropertyDialogContainer).children('div.new-property-dialog').remove();
+                }
+            });
+        },
+        
+        /**
+         * Clears the property table.
+         */
+        clear: function () {
+            return this.each(function () {
+                clear($(this));
+            });
+        },
+        
+        /**
+         * Determines if a save is required for the first matching element.
+         */
+        isSaveRequired: function () {
+            var isSaveRequired = false;
+
+            this.each(function () {
+                // get the property grid
+                var table = $(this).find('div.property-table');
+                var propertyGrid = table.data('gridInstance');
+                var propertyData = propertyGrid.getData();
+
+                // determine if any of the properties have changed
+                $.each(propertyData.getItems(), function () {
+                    if (this.value !== this.previousValue) {
+                        isSaveRequired = true;
+                        return false;
+                    }
+                });
+
+                return false;
+            });
+
+            return isSaveRequired;
+        },
+        
+        /**
+         * Marshalls the properties for the first matching element.
+         */
+        marshalProperties: function () {
+            // properties
+            var properties = {};
+
+            this.each(function () {
+                // get the property grid data
+                var table = $(this).find('div.property-table');
+                var propertyGrid = table.data('gridInstance');
+                var propertyData = propertyGrid.getData();
+                $.each(propertyData.getItems(), function () {
+                    if (this.hidden === true) {
+                        // hidden properties were removed by the user, clear the value
+                        properties[this.property] = null;
+                    } else if (this.value !== this.previousValue) {
+                        // the value has changed
+                        properties[this.property] = this.value;
+                    }
+                });
+
+                return false;
+            });
+
+            return properties;
+        }
+    };
+
+    $.fn.propertytable = function (method) {
+        if (methods[method]) {
+            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+        } else {
+            return methods.init.apply(this, arguments);
+        }
+    };
+})(jQuery);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tabbs/jquery.tabbs.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tabbs/jquery.tabbs.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tabbs/jquery.tabbs.js
index 2b2c1e7..e3bbb51 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tabbs/jquery.tabbs.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tabbs/jquery.tabbs.js
@@ -44,6 +44,8 @@
      *   }],
      *   select: selectHandler
      * }
+     * 
+     * @argument {object} options 
      */
     $.fn.tabbs = function (options) {
         return this.each(function () {

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tagcloud/jquery.tagcloud.css
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tagcloud/jquery.tagcloud.css b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tagcloud/jquery.tagcloud.css
new file mode 100644
index 0000000..dda7474
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tagcloud/jquery.tagcloud.css
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+/*
+    Styles for the Nifi tag cloud.
+*/
+
+ul.tag-cloud {
+    font-size: 90%;
+    padding: 2px;
+    margin-left: -5px;
+    overflow: hidden;
+}
+
+ul.tag-cloud li {
+    float: left;
+    list-style-type: none;
+    margin: 0 3px;
+    height: 20px;
+    line-height: 20px;
+    overflow: hidden;
+    white-space: nowrap;
+}
+
+div.tag-cloud-separator {
+    height: 1px;
+    border-bottom: 1px solid #aaa;
+    margin: 3px 0;
+}
+
+ul.tag-filter {
+    overflow: hidden;
+}
+
+ul.tag-filter li {
+    height: 18px;
+    line-height: 18px;
+    padding: 2px;
+}
+
+div.selected-tag-text {
+    float: left;
+}
+
+img.remove-selected-tag {
+    float: right;
+    margin-top: 1px;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tagcloud/jquery.tagcloud.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tagcloud/jquery.tagcloud.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tagcloud/jquery.tagcloud.js
new file mode 100644
index 0000000..5c45eda
--- /dev/null
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/tagcloud/jquery.tagcloud.js
@@ -0,0 +1,226 @@
+/*
+ * 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 */
+
+/**
+ * jQuery plugin for a NiFi style tag cloud. The options are specified in the following
+ * format:
+ *
+ * {
+ *   tags: ['attributes', 'copy', 'regex', 'xml', 'copy', 'xml', 'attributes'],
+ *   select: selectHandler,
+ *   remove: removeHandler
+ * }
+ * 
+ * @param {type} $
+ * @returns {undefined}
+ */
+(function ($) {
+
+    var isUndefined = function (obj) {
+        return typeof obj === 'undefined';
+    };
+
+    var isNull = function (obj) {
+        return obj === null;
+    };
+
+    var isDefinedAndNotNull = function (obj) {
+        return !isUndefined(obj) && !isNull(obj);
+    };
+
+    var isBlank = function (str) {
+        return isUndefined(str) || isNull(str) || str === '';
+    };
+
+    var config = {
+        maxTags: 25,
+        maxTagFontSize: 2,
+        minTagFontSize: 1,
+        minWidth: 20
+    };
+    
+    /**
+     * Adds the specified tag filter.
+     * 
+     * @argument {jQuery} cloudContainer    The tag cloud container
+     * @argument {string} tag               The tag to add
+     */
+    var addTagFilter = function (cloudContainer, tag) {
+        var config = cloudContainer.data('options');
+        var tagFilter = cloudContainer.find('ul.tag-filter');
+        
+        // ensure this tag hasn't already been added
+        var tagFilterExists = false;
+        tagFilter.find('li div.selected-tag-text').each(function () {
+            if (tag === $(this).text()) {
+                tagFilterExists = true;
+                return false;
+            }
+        });
+
+        // add this tag filter if applicable
+        if (!tagFilterExists) {
+            // create the list item content
+            var tagText = $('<div class="selected-tag-text"></div>').text(tag);
+            var removeTagIcon = $('<img src="images/iconDelete.png" class="remove-selected-tag pointer"></img>').click(function () {
+                // remove this tag
+                $(this).closest('li').remove();
+
+                // fire a remove event if applicable
+                if (isDefinedAndNotNull(config.remove)) {
+                    config.remove.call(cloudContainer, tag);
+                }
+            });
+            var selectedTagItem = $('<div></div>').append(tagText).append(removeTagIcon);
+
+            // create the list item and update the tag filter list
+            $('<li></li>').append(selectedTagItem).appendTo(tagFilter);
+
+            // fire a select event if applicable
+            if (isDefinedAndNotNull(config.select)) {
+                config.select.call(cloudContainer, tag);
+            }
+        }
+    };
+
+    var methods = {
+        
+        /**
+         * Initializes the tag cloud.
+         * 
+         * @argument {object} options The options for the tag cloud
+         */
+        init: function (options) {
+            return this.each(function () {
+                // ensure the options have been properly specified
+                if (isDefinedAndNotNull(options) && isDefinedAndNotNull(options.tags)) {
+                    // get the tag cloud
+                    var cloudContainer = $(this);
+                    
+                    // clear any current contents, remote events, and store options
+                    cloudContainer.empty().unbind().data('options', options);
+
+                    // build the component
+                    var cloud = $('<ul class="tag-cloud"></ul>').appendTo(cloudContainer);
+                    $('<div class="tag-cloud-separator">').appendTo(cloudContainer);
+                    var filter = $('<ul class="tag-filter"></ul>').appendTo(cloudContainer);
+
+                    var tagCloud = {};
+                    var tags = [];
+                    
+                    // count the frequency of each tag for this type
+                    $.each(options.tags, function (i, tag) {
+                        var normalizedTagName = tag.toLowerCase();
+
+                        if (nf.Common.isDefinedAndNotNull(tagCloud[normalizedTagName])) {
+                            tagCloud[normalizedTagName].count = tagCloud[normalizedTagName].count + 1;
+                        } else {
+                            var tagCloudEntry = {
+                                term: normalizedTagName,
+                                count: 1
+                            };
+                            tags.push(tagCloudEntry);
+                            tagCloud[normalizedTagName] = tagCloudEntry;
+                        }
+                    });
+                    
+                    // handle the case when no tags are present
+                    if (tags.length > 0) {
+                        // sort the tags by frequency to limit the less frequent tags
+                        tags.sort(function (a, b) {
+                            return b.count - a.count;
+                        });
+
+                        // limit to the most frequest tags
+                        if (tags.length > config.maxTags) {
+                            tags = tags.slice(0, config.maxTags);
+                        }
+
+                        // determine the max frequency
+                        var maxFrequency = tags[0].count;
+
+                        // sort the tags alphabetically
+                        tags.sort(function (a, b) {
+                            var compA = a.term.toUpperCase();
+                            var compB = b.term.toUpperCase();
+                            return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
+                        });
+
+                        // set the tag content
+                        $.each(tags, function (i, tag) {
+                            // determine the appropriate font size
+                            var fontSize = Math.log(tag.count) / Math.log(maxFrequency) * (config.maxTagFontSize - config.minTagFontSize) + config.minTagFontSize;
+                            var minWidth = config.minWidth * fontSize;
+
+                            // create the tag cloud entry
+                            $('<li></li>').append($('<span class="link"></span>').text(tag.term).css({
+                                'font-size': fontSize + 'em'
+                            })).css({
+                                'min-width': minWidth + 'px'
+                            }).click(function () {
+                                // ensure we don't exceed 5 selected
+                                if (filter.children('li').length < 5) {
+                                    var tagText = $(this).children('span').text();
+                                    addTagFilter(cloudContainer, tagText);
+                                }
+                            }).appendTo(cloud).ellipsis();
+                        });
+                    } else {
+                        // indicate when no tags are found
+                        $('<li><span class="unset">No tags specified</span></li>').appendTo(cloud);
+                    }
+                }
+            });
+        },
+        
+        /**
+         * Resets the selected tags from the tag cloud.
+         */
+        clearSelectedTags: function () {
+            return this.each(function() {
+                var cloudContainer = $(this);
+                cloudContainer.find('img.remove-selected-tag').trigger('click');
+            });
+        },
+        
+        /**
+         * Returns the selected tags of the first matching element.
+         */
+        getSelectedTags: function () {
+            var tags = [];
+
+            this.each(function () {
+                var cloudContainer = $(this);
+                cloudContainer.find('div.selected-tag-text').each(function() {
+                    tags.push($(this).text());
+                });
+            });
+
+            return tags;
+        }
+    };
+
+    $.fn.tagcloud = function (method) {
+        if (methods[method]) {
+            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+        } else {
+            return methods.init.apply(this, arguments);
+        }
+    };
+})(jQuery);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/bulletin-board/nf-bulletin-board.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/bulletin-board/nf-bulletin-board.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/bulletin-board/nf-bulletin-board.js
index f181016..85e52b0 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/bulletin-board/nf-bulletin-board.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/bulletin-board/nf-bulletin-board.js
@@ -14,6 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+/* global nf, top */
+
 $(document).ready(function () {
     // initialize the bulletin board
     nf.BulletinBoard.init();

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/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/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
index 880d767..47532ad 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
@@ -14,6 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+/* global nf, d3 */
+
 nf.Actions = (function () {
 
     var config = {

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/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/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-birdseye.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-birdseye.js
index b317a6f..01abd31 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-birdseye.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-birdseye.js
@@ -14,6 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+/* global nf, d3 */
+
 nf.Birdseye = (function () {
 
     var birdseyeGroup;

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/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/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/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
index 335b145..ff5d2f2 100644
--- a/nifi/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/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-header.js
@@ -14,6 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+/* global nf, d3 */
+
 nf.CanvasHeader = (function () {
 
     var config = {
@@ -58,13 +61,11 @@ nf.CanvasHeader = (function () {
             });
 
             // mouse over for the flow settings link
-            if (nf.Common.isDFM()) {
-                nf.Common.addHoverEffect('#flow-settings-link', 'flow-settings-link', 'flow-settings-link-hover').click(function () {
+            nf.Common.addHoverEffect('#flow-settings-link', 'flow-settings-link', 'flow-settings-link-hover').click(function () {
+                nf.Settings.loadSettings().done(function () {
                     nf.Settings.showSettings();
                 });
-            } else {
-                $('#flow-settings-link').addClass('flow-settings-link-disabled');
-            }
+            });
 
             // mouse over for the users link
             if (nf.Common.isAdmin()) {
@@ -96,29 +97,9 @@ nf.CanvasHeader = (function () {
                 nf.Shell.showPage('bulletin-board');
             });
 
-            // setup the tooltip for the refresh icon
-            $('#refresh-required-icon').qtip($.extend({
-                content: 'This flow has been modified by another user. Please refresh.'
-            }, nf.CanvasUtils.config.systemTooltipConfig));
-
             // setup the refresh link actions
             $('#refresh-required-link').click(function () {
-                nf.Canvas.reload().done(function () {
-                    // update component visibility
-                    nf.Canvas.View.updateVisibility();
-
-                    // refresh the birdseye
-                    nf.Birdseye.refresh();
-
-                    // hide the refresh link
-                    $('#stats-last-refreshed').removeClass('alert');
-                    $('#refresh-required-container').hide();
-                }).fail(function () {
-                    nf.Dialog.showOkDialog({
-                        dialogContent: 'Unable to refresh the current group.',
-                        overlayBackground: true
-                    });
-                });
+                nf.CanvasHeader.reloadAndClearWarnings();
             });
 
             // get the about details
@@ -235,6 +216,9 @@ nf.CanvasHeader = (function () {
                         $('#fill-color').minicolors('value', '');
                     }
                 }
+            }).draggable({
+                containment: 'parent',
+                handle: '.dialog-header'
             });
 
             // initialize the fill color picker
@@ -299,7 +283,7 @@ nf.CanvasHeader = (function () {
                     var delta = 0;
                     if (nf.Common.isDefinedAndNotNull(evt.originalEvent.detail)) {
                         delta = -evt.originalEvent.detail;
-                    } else if (nf.Common.isDefinedAndNotNull(evnt.originalEvent.wheelDelta)) {
+                    } else if (nf.Common.isDefinedAndNotNull(evt.originalEvent.wheelDelta)) {
                         delta = evt.originalEvent.wheelDelta;
                     }
 
@@ -324,6 +308,32 @@ nf.CanvasHeader = (function () {
                     }
                 }
             });
+        },
+        
+        /**
+         * 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

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbar.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbar.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbar.js
index b5702af..8347f37 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbar.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbar.js
@@ -14,6 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+/* global nf, d3 */
+
 nf.CanvasToolbar = (function () {
 
     var actions;

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbox.js
----------------------------------------------------------------------
diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbox.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbox.js
index 7de17f4..2812142 100644
--- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbox.js
+++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-toolbox.js
@@ -14,16 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+/* global nf, Slick */
+
 nf.CanvasToolbox = (function () {
 
     var config = {
-        /**
-         * Tag properties configuration.
-         */
-        maxTags: 25,
-        maxTagFontSize: 2,
-        minTagFontSize: 1,
-        minWidth: 20,
         filterText: 'Filter',
         type: {
             processor: 'Processor',
@@ -71,7 +67,7 @@ nf.CanvasToolbox = (function () {
                 return $('<div class="toolbox-icon"></div>').addClass(dragCls).appendTo('body');
             },
             'containment': 'body',
-            'start': function(e, ui) {
+            'start': function (e, ui) {
                 // hide the context menu if necessary
                 nf.ContextMenu.hide();
             },
@@ -92,49 +88,12 @@ nf.CanvasToolbox = (function () {
                         x: x,
                         y: y
                     });
-                } 
+                }
             }
         }).appendTo(toolbox);
     };
 
     /**
-     * Adds the specified tag filter.
-     * 
-     * @argument {string} tag       The tag to add
-     */
-    var addTagFilter = function (tag) {
-        // ensure this tag hasn't already been added
-        var tagFilter = $('#tag-filter');
-        var tagFilterExists = false;
-        tagFilter.find('li div.selected-tag-text').each(function () {
-            if (tag === $(this).text()) {
-                tagFilterExists = true;
-                return false;
-            }
-        });
-
-        // add this tag filter if applicable
-        if (!tagFilterExists) {
-            // create the list item content
-            var tagText = $('<div class="selected-tag-text"></div>').text(tag);
-            var removeTagIcon = $('<img src="images/iconDelete.png" class="remove-selected-tag pointer"></img>').click(function () {
-                // remove this tag
-                $(this).closest('li').remove();
-
-                // re-apply the filter
-                applyFilter();
-            });
-            var selectedTagItem = $('<div></div>').append(tagText).append(removeTagIcon);
-
-            // create the list item and update the tag filter list
-            $('<li></li>').append(selectedTagItem).appendTo(tagFilter);
-
-            // re-apply the filter
-            applyFilter();
-        }
-    };
-
-    /**
      * Filters the processor type table.
      */
     var applyFilter = function () {
@@ -192,7 +151,7 @@ nf.CanvasToolbox = (function () {
         // determine if the row matches the selected tags
         var matchesTags = true;
         if (matchesFilter) {
-            var tagFilters = $('#tag-filter li');
+            var tagFilters = $('#processor-tag-cloud').tagcloud('getSelectedTags');
             var hasSelectedTags = tagFilters.length > 0;
             if (hasSelectedTags) {
                 matchesTags = matchesSelectedTags(tagFilters, item['tags']);
@@ -221,13 +180,13 @@ nf.CanvasToolbox = (function () {
     /**
      * Determines if the specified tags match all the tags selected by the user.
      * 
-     * @argument {jQuery} tagFilters    The tag filters
-     * @argument {string} tags          The tags to test
+     * @argument {string[]} tagFilters      The tag filters
+     * @argument {string} tags              The tags to test
      */
     var matchesSelectedTags = function (tagFilters, tags) {
         var selectedTags = [];
-        tagFilters.each(function () {
-            selectedTags.push($(this).text());
+        $.each(tagFilters, function (_, filter) {
+            selectedTags.push(filter);
         });
 
         // normalize the tags
@@ -280,14 +239,13 @@ nf.CanvasToolbox = (function () {
      * Resets the filtered processor types.
      */
     var resetProcessorDialog = function () {
-        // clear and selected tags
-        var tagFilter = $('#tag-filter');
-        tagFilter.empty();
-
+        // clear the selected tag cloud
+        $('#processor-tag-cloud').tagcloud('clearSelectedTags');
+        
         // clear any filter strings
         $('#processor-type-filter').addClass(config.styles.filterList).val(config.filterText);
 
-        // reset the filter before closing
+        // reapply the filter
         applyFilter();
 
         // clear the selected row
@@ -295,7 +253,7 @@ nf.CanvasToolbox = (function () {
         $('#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([]);
@@ -987,6 +945,20 @@ nf.CanvasToolbox = (function () {
                     }
                 });
 
+                // 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);
 
@@ -997,7 +969,6 @@ nf.CanvasToolbox = (function () {
                     url: config.urls.processorTypes,
                     dataType: 'json'
                 }).done(function (response) {
-                    var tagCloud = {};
                     var tags = [];
 
                     // begin the update
@@ -1016,21 +987,9 @@ nf.CanvasToolbox = (function () {
                             tags: documentedType.tags.join(', ')
                         });
 
-
                         // count the frequency of each tag for this type
                         $.each(documentedType.tags, function (i, tag) {
-                            var normalizedTagName = tag.toLowerCase();
-
-                            if (nf.Common.isDefinedAndNotNull(tagCloud[normalizedTagName])) {
-                                tagCloud[normalizedTagName].count = tagCloud[normalizedTagName].count + 1;
-                            } else {
-                                var tagCloudEntry = {
-                                    term: normalizedTagName,
-                                    count: 1
-                                };
-                                tags.push(tagCloudEntry);
-                                tagCloud[normalizedTagName] = tagCloudEntry;
-                            }
+                            tags.push(tag.toLowerCase());
                         });
                     });
 
@@ -1040,65 +999,12 @@ nf.CanvasToolbox = (function () {
                     // set the total number of processors
                     $('#total-processor-types, #displayed-processor-types').text(response.processorTypes.length);
 
-                    // handle the case when no tags are present
-                    if (tags.length > 0) {
-                        // sort the tags by frequency to limit the less frequent tags
-                        tags.sort(function (a, b) {
-                            return b.count - a.count;
-                        });
-
-                        // limit to the most frequest tags
-                        if (tags.length > config.maxTags) {
-                            tags = tags.slice(0, config.maxTags);
-                        }
-
-                        // determine the max frequency
-                        var maxFrequency = tags[0].count;
-
-                        // sort the tags alphabetically
-                        tags.sort(function (a, b) {
-                            var compA = a.term.toUpperCase();
-                            var compB = b.term.toUpperCase();
-                            return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
-                        });
-
-                        // set the tag content
-                        $.each(tags, function (i, tag) {
-                            // determine the appropriate font size
-                            var fontSize = Math.log(tag.count) / Math.log(maxFrequency) * (config.maxTagFontSize - config.minTagFontSize) + config.minTagFontSize;
-                            var minWidth = config.minWidth * fontSize;
-
-                            // create the tag cloud entry
-                            $('<li></li>').append($('<span class="link"></span>').text(tag.term).css({
-                                'font-size': fontSize + 'em'
-                            })).css({
-                                'min-width': minWidth + 'px'
-                            }).click(function () {
-                                // ensure we don't exceed 5 selected
-                                if ($('#tag-filter').children('li').length < 5) {
-                                    var tagText = $(this).children('span').text();
-                                    addTagFilter(tagText);
-                                }
-                            }).appendTo('#tag-cloud').ellipsis();
-                        });
-                    } else {
-                        // indicate when no tags are found
-                        $('<li><span class="unset">No tags specified</span></li>').appendTo('#tag-cloud');
-                    }
-
-                    // 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();
+                    // create the tag cloud
+                    $('#processor-tag-cloud').tagcloud({
+                        tags: tags,
+                        select: applyFilter,
+                        remove: applyFilter
                     });
-                    processorTypesData.syncGridSelection(processorTypesGrid, false);
                 }).fail(nf.Common.handleAjaxError);
 
                 // define the function for filtering the list

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/e9647717/nifi/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/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/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
index 720a23e..f1cb458 100644
--- a/nifi/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/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
@@ -14,6 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+/* global nf, d3 */
+
 nf.CanvasUtils = (function () {
 
     var config = {