function controller()

in ui-modules/blueprint-composer/app/components/catalog-selector/catalog-selector.directive.js [105:370]


    function controller($scope, $element, $timeout, $q, $uibModal, $log, $templateCache, paletteApi, paletteDragAndDropService, iconGenerator, composerOverrides, recentlyUsedService) {
        this.$timeout = $timeout;

        $scope.viewModes = PALETTE_VIEW_MODES;
        $scope.viewOrders = PALETTE_VIEW_ORDERS;
        if (!$scope.state) $scope.state = {};
        if (!$scope.state.viewMode) {
            let savedViewMode;
            try {
                savedViewMode = JSON.parse(sessionStorage.getItem(SESSION_KEYS.VIEW_MODE));
            } catch(err) {
                savedViewMode = null;
            }
            $scope.state.viewMode = ((typeof savedViewMode === 'object') && (savedViewMode !== null))
                ? savedViewMode
                : PALETTE_VIEW_MODES.normal
        };

        if(!$scope.search && !$scope.iconSelects) { // Do not restore search when the icon triggers selection directly or shows popup - `iconSelects == true`.
            const savedSearch = sessionStorage.getItem(SESSION_KEYS.QUERY);
            if (typeof savedSearch === 'string' && savedSearch.length) $scope.search = savedSearch;
        }

        $scope.pagination = {
            page: 1,
            itemsPerPage: $scope.state.viewMode.itemsPerRow * ($scope.rowsPerPage || 1)  // will fill out after load
        };

        $scope.getEntityNameForPalette = function(item, entityName) {
            return (composerOverrides.getEntityNameForPalette ||
                // above can be overridden with function of signature below to customize display name in palette
                function(item, entityName, scope) { return entityName; }
            )(item, entityName, $scope);
        };

        $scope.getPlaceHolder = function () {
            return 'Search';
        };

        $scope.isLoading = true;

        $scope.$watch('search', (newValue) => {
            $scope.freeFormTile = {
                symbolicName: $scope.search,
                name: $scope.search,
                id: $scope.search,
                displayName: $scope.search,
                supertypes: ($scope.family ? [ $scope.family.superType ] : []),
            };
            if (typeof newValue === 'string') sessionStorage.setItem(SESSION_KEYS.QUERY, newValue);
        });

        $scope.getItems = function (search) {
            let defer = $q.resolve([]);

            switch ($scope.family) {
                case EntityFamily.ENTITY:
                case EntityFamily.SPEC:
                    defer = paletteApi.getTypes({params: {supertype: 'entity', fragment: search}});
                    break;
                case EntityFamily.POLICY:
                    defer = paletteApi.getTypes({params: {supertype: 'policy', fragment: search}});
                    break;
                case EntityFamily.ENRICHER:
                    defer = paletteApi.getTypes({params: {supertype: 'enricher', fragment: search}});
                    break;
                case EntityFamily.LOCATION:
                    defer = paletteApi.getLocations();
                    break;
            }

            return defer.then(data => {
                data = $scope.filterPaletteItemsForMode(data, $scope);
                data.forEach( recentlyUsedService.embellish );
                return data;

            }).catch(error => {
                return [];
            }).finally(() => {
                $scope.isLoading = false;
            });
        };
        function tryMarkUsed(item) {
            try {
                recentlyUsedService.markUsed(item);
            } catch (e) {
                // session storage can get full; usually the culprit is icons not this,
                // but we may wish to clear out old items to ensure we don't bleed here
                $log.warn("Could not mark item as used: "+item, e);
            }
        }
        $scope.mouseInfoPopover = (item, enter) => {
            if ($scope.popoverModal && $scope.popoverVisible && $scope.popover==item) {
                // ignore if modal
                return;
            }
            $scope.popoverModal = false;
            if (enter) {
                $scope.popover = item;
                $scope.popoverVisible = true;
            } else {
                $scope.popoverVisible = false;
            }
        };
        $scope.onClickItem = (item, isInfoIcon, $event) => {
            if (!isInfoIcon && $scope.iconSelects) {
                $scope.onSelectItem(item);
            } else if ($scope.popoverModal && $scope.popoverVisible && $scope.popover == item) {
                $scope.closePopover();
            } else {
                $scope.popover = item;
                $scope.popoverVisible = true;
                $scope.popoverModal = true;
            }
            if ($event) $event.stopPropagation();
        };
        $scope.closePopover = () => {
            $scope.popoverVisible = false;
            $scope.popoverModal = false;
        };
        $scope.getOnSelectText = function (item) {
            if (!($scope.onSelectText)) return "Select";
            return $scope.onSelectText({item: item});
        };
        $scope.onSelectItem = function (item) {
            $scope.closePopover();
            if (angular.isFunction($scope.onSelect)) {
                tryMarkUsed(item);
                $scope.onSelect({item: item});
            }
            $scope.search = '';
        };
        $scope.onDragItem = function (item, event) {
            let frame = document.createElement('div');
            frame.classList.add('drag-frame');
            event.target.appendChild(frame);
            setTimeout(function() {
                // can remove at end of this cycle, browser will have grabbed its drag image
                frame.parentNode.removeChild(frame);
            }, 0);
            /* have tried many other ways to get a nice drag image;
               this seems to work best, adding an empty div which forces the size to be larger,
               so when grabbing the image it grabs the drop-shadow.
               things that _didn't_ work include:
               - styling event.target now then unstyling (normally this would work, in posts on the web, but it doesn't here; angular?)
               - make a restyled cloned copy offscreen (this comes so close but remote img srcs aren't loaded
             */

            paletteDragAndDropService.dragStart(item);
        };
        $scope.onDragEnd = function (item, event) {
            paletteDragAndDropService.dragEnd();
            tryMarkUsed(item);
        };

        $scope.getOpenCatalogLink = (item) => {
            return "/brooklyn-ui-catalog/#!/bundles/"+item.containingBundle.replace(":","/")+"/types/"+item.symbolicName+"/"+item.version;
        };
        $scope.sortBy = function (order) {
            let newFirst = {};
            if (order) {
                newFirst[order.id] = order;
            }
            $scope.state.currentOrder = Object.assign(newFirst, $scope.state.currentOrder, newFirst);
            $scope.state.currentOrderFields = [];
            $scope.state.currentOrderValues = [];
            Object.values($scope.state.currentOrder).forEach( it => {
                $scope.state.currentOrderValues.push(it);
                $scope.state.currentOrderFields.push(it.field);
            });
        };
        if (!$scope.state.currentOrder) $scope.state.currentOrder = Object.assign({}, PALETTE_VIEW_ORDERS);
        $scope.sortBy();

        $scope.allowFreeForm = function () {
            return [
                EntityFamily.LOCATION
            ].indexOf($scope.family) > -1;
        };
        $scope.isReserved = function () {
            if (!$scope.reservedKeys || !angular.isArray($scope.reservedKeys)) {
                return false;
            }
            return $scope.reservedKeys.indexOf($scope.search) > -1;
        };
        $scope.onImageError = (scope, el, attrs) => {
            $log.warn("Icon for "+attrs.itemId+" at "+angular.element(el).attr("src")+" could not be loaded; generating icon");
            angular.element(el).attr("src", iconGenerator(attrs.itemId));
        };

        // Init
        $scope.items = [];
        function getDisplayTags(tags) {
            if (!Array.isArray(tags) || !tags.length) return tags;
            return tags.filter(tag => !(/[=:\[\]()]/.exec(tag)));
        }

        $scope.getItems().then((items)=> {
            // add displayTags, as any tag that doesn't contain = : or ( ) [ ]
            // any tag that is an object will be eliminated as it is toStringed to make [ object object ]
            // add display name=symbolicName for those that don't have one, to avoid issues with the 'Name' property sorting
            items.forEach(item => {
                if (item.tags) {
                    item.displayTags = getDisplayTags(item.tags);
                }
                if(!item.displayName) {
                    item.displayName = item.symbolicName;
                }
            });
            $scope.items = items;
        });

        $scope.lastUsedText = (item) => {
            if (item==null) return "";
            let l = (Number)(item.lastUsed);
            if (!l || isNaN(l) || l<=0) return "";
            if (l < 100000) return 'Preselected for inclusion in "Recent" filter.';
            return 'Last used: ' + distanceInWordsToNow(l, { includeSeconds: true, addSuffix: true });
        };

        $scope.showPaletteControls = false;
        $scope.onFiltersShown = () => {
            $timeout( () => {
                // check do we need to show the multiline
                let filters = angular.element($element[0].querySelector(".filters"));
                $scope.$apply( () => $scope.filterSettings.filtersMultilineAvailable = filters[0].scrollHeight > filters[0].offsetHeight + 6 );

                repaginate($scope, $element);
            } );
        };
        $scope.togglePaletteControls = () => {
            $scope.showPaletteControls = !$scope.showPaletteControls;
            $timeout( () => repaginate($scope, $element) );
        };
        $scope.toggleShowAllFilters = () => {
            $scope.filterSettings.showAllFilters = !$scope.filterSettings.showAllFilters;
            $timeout( () => repaginate($scope, $element) );
        };
        $scope.filterSettings = {};

        $scope.filters = [
            { label: 'Recent', icon: 'clock-o', title: "Recently used and standard favorites", limitToOnePage: true,
                filterInit: items => {
                    $scope.recentItems = items.filter( i => i.lastUsed && i.lastUsed>0 );
                    $scope.recentItems.sort( (a,b) => b.lastUsed - a.lastUsed );
                    return $scope.recentItems;
                }, enabled: false },
        ];
        $scope.disableFilters = (showFilters) => {
            $scope.filters.forEach( f => f.enabled = false );
            if (showFilters !== false) {
                $scope.showPaletteControls = true;
            }
        };

        // can be overridden to disable "open in catalog" button
        $scope.allowOpenInCatalog = true;

        // this can be overridden for palette sections/modes which show a subset of the types returned by the server;
        // this is applied when the data is received from the server.
        // it is used by catalogSelectorFiltersFilter;
        $scope.filterPaletteItemsForMode = (items) => items;

        // allow downstream to configure this controller and/or scope
        (composerOverrides.configurePaletteController || function() {})(this, $scope, $element);
    }