/** * An element which displays a box to search for an entity (org, user, robot, team). This control * allows for filtering of the entities found and whether to allow selection by e-mail. */ angular.module('quay').directive('entitySearch', function () { var number = 0; var directiveDefinitionObject = { priority: 0, templateUrl: '/static/directives/entity-search.html', replace: false, transclude: false, restrict: 'C', require: '?ngModel', link: function(scope, element, attr, ctrl) { scope.ngModel = ctrl; }, scope: { 'namespace': '=namespace', 'placeholder': '=placeholder', // Default: ['user', 'team', 'robot'] 'allowedEntities': '=allowedEntities', 'currentEntity': '=currentEntity', 'entitySelected': '&entitySelected', 'emailSelected': '&emailSelected', // When set to true, the contents of the control will be cleared as soon // as an entity is selected. 'autoClear': '=autoClear', // Set this property to immediately clear the contents of the control. 'clearValue': '=clearValue', // Whether e-mail addresses are allowed. 'allowEmails': '=allowEmails', 'emailMessage': '@emailMessage', // True if the menu should pull right. 'pullRight': '@pullRight' }, controller: function($rootScope, $scope, $element, Restangular, UserService, ApiService, UtilService, Config, CreateService) { $scope.lazyLoading = true; $scope.teams = null; $scope.robots = null; $scope.isAdmin = false; $scope.isOrganization = false; $scope.includeTeams = true; $scope.includeRobots = true; $scope.includeOrgs = false; $scope.currentEntityInternal = $scope.currentEntity; $scope.Config = Config; var isSupported = function(kind, opt_array) { return $.inArray(kind, opt_array || $scope.allowedEntities || ['user', 'team', 'robot']) >= 0; }; $scope.lazyLoad = function() { if (!$scope.namespace || !$scope.lazyLoading) { return; } // Reset the cached teams and robots. $scope.teams = null; $scope.robots = null; // Load the organization's teams (if applicable). if ($scope.isOrganization && isSupported('team')) { // Note: We load the org here again so that we always have the fully up-to-date // teams list. ApiService.getOrganization(null, {'orgname': $scope.namespace}).then(function(resp) { $scope.teams = resp.teams; }); } // Load the user/organization's robots (if applicable). if ($scope.isAdmin && isSupported('robot')) { ApiService.getRobots($scope.isOrganization ? $scope.namespace : null).then(function(resp) { $scope.robots = resp.robots; $scope.lazyLoading = false; }, function() { $scope.lazyLoading = false; }); } else { $scope.lazyLoading = false; } }; $scope.createTeam = function() { CreateService.askCreateTeam($scope.namespace, function(created) { $scope.setEntity(created.name, 'team', false, created.avatar); $scope.teams[teamname] = created; }); }; $scope.createRobot = function() { CreateService.askCreateRobot($scope.namespace, function(created) { $scope.setEntity(created.name, 'user', true, created.avatar); $scope.robots.push(created); }); }; $scope.setEntity = function(name, kind, is_robot, avatar) { var entity = { 'name': name, 'kind': kind, 'is_robot': is_robot, 'avatar': avatar }; if ($scope.isOrganization) { entity['is_org_member'] = true; } $scope.setEntityInternal(entity, false); }; $scope.clearEntityInternal = function() { $scope.currentEntityInternal = null; $scope.currentEntity = null; $scope.entitySelected({'entity': null}); if ($scope.ngModel) { $scope.ngModel.$setValidity('entity', false); } }; $scope.setEntityInternal = function(entity, updateTypeahead) { if (updateTypeahead) { $(input).typeahead('val', $scope.autoClear ? '' : entity.name); } else { $(input).val($scope.autoClear ? '' : entity.name); } if (!$scope.autoClear) { $scope.currentEntityInternal = entity; $scope.currentEntity = entity; } $scope.entitySelected({'entity': entity}); if ($scope.ngModel) { $scope.ngModel.$setValidity('entity', !!entity); } }; // Setup the typeahead. var input = $element[0].firstChild.firstChild; (function() { // Create the bloodhound search query system. $rootScope.__entity_search_counter = (($rootScope.__entity_search_counter || 0) + 1); var entitySearchB = new Bloodhound({ name: 'entities' + $rootScope.__entity_search_counter, remote: { url: '/api/v1/entities/%QUERY', replace: function (url, uriEncodedQuery) { var namespace = $scope.namespace || ''; url = url.replace('%QUERY', uriEncodedQuery); url += '?namespace=' + encodeURIComponent(namespace); if ($scope.isOrganization && isSupported('team')) { url += '&includeTeams=true' } if (isSupported('org')) { url += '&includeOrgs=true' } return url; }, filter: function(data) { var datums = []; for (var i = 0; i < data.results.length; ++i) { var entity = data.results[i]; var found = 'user'; if (entity.kind == 'user') { found = entity.is_robot ? 'robot' : 'user'; } else if (entity.kind == 'team') { found = 'team'; } else if (entity.kind == 'org') { found = 'org'; } if (!isSupported(found)) { continue; } datums.push({ 'value': entity.name, 'tokens': [entity.name], 'entity': entity }); } return datums; } }, datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.val); }, queryTokenizer: Bloodhound.tokenizers.whitespace }); entitySearchB.initialize(); // Setup the typeahead. $(input).typeahead({ 'highlight': true }, { source: entitySearchB.ttAdapter(), templates: { 'empty': function(info) { // Only display the empty dialog if the server load has finished. if (info.resultKind == 'remote') { var val = $(input).val(); if (!val) { return null; } if (UtilService.isEmailAddress(val)) { if ($scope.allowEmails) { return '
'; } else { return '