Is it possible to select multiple values from angular ui bootstrap typeahead?
2 Answers
Hi without changing the codebase probably not - you could try https://github.com/rayshan/ui-multiselect
1 Comment
I recently had the same requirement and was able to solve it by overriding the internal bootstrap implementation via an alternate popup-template. I created a new directive (multi-select-typeahead) to encapsulate the change.
The template uses an ng-init to pass the scope reference (of the typeahead popup directive) to the multi-select-typeahead directive. There the directive overrides the parent's scope. $scope.$parent in this case is the bootstrap typeahead directive itself. The custom directive provides a new implementation of select() which is called internally by angular bootstrap. The new implementation prevents the popup from closing and removes selected items from the list.
The alternate popup I provided is almost entirely the same as the default angular bootstrap typeahead template "uib/template/typeahead/typeahead-popup.html". The only modification was the addition of the ng-init which passes its scope to the multi-select-typeahead directive.
I'm sure if you are clever enough you could render the angular bootstrap default template by reference and inject the ng-init part, removing the duplicated bootstrap code. This would make the solution a bit more resilient to future angular bootstrap changes. That being said, the solution is already quite a hack and is prone to breaking in future major releases.
Hope this is useful to someone!
angular.module('typeahead.demo', [
'ngAnimate',
'ngSanitize',
'ui.bootstrap'
]);
angular
.module('typeahead.demo')
.controller('TypeaheadDemo', TypeaheadDemo);
function TypeaheadDemo($scope) {
$scope.addItem = addItem;
$scope.itemApi = itemApi;
$scope.items = [];
function addItem(item) {
$scope.items.push(item);
}
function itemApi() {
return [
{ name: 'apple' },
{ name: 'orange' },
{ name: 'grape' }
];
}
}
angular
.module('typeahead.demo')
.directive('multiSelectTypeahead', multiSelectTypeahead);
function multiSelectTypeahead() {
return {
templateUrl: 'multi-select-typeahead.html',
scope: {
searchApi: '&',
displayNameField: '@',
onSelect: '&',
inputPlaceholder: '@?'
},
link: function ($scope) {
var uibTypeaheadScope;
$scope.initializeScope = initializeScope;
$scope.$watch('isOpen', function (newValue) {
if (!newValue) {
$scope.searchTerm = '';
}
});
function initializeScope(typeaheadPopupScope) {
uibTypeaheadScope = typeaheadPopupScope.$parent;
uibTypeaheadScope.select = selectItem;
}
function selectItem(index, event) {
var selectedItem = uibTypeaheadScope.matches[index].model;
event.stopPropagation();
if (event.type === 'click') {
event.target.blur();
}
uibTypeaheadScope.matches.splice(index, 1);
$scope.onSelect({ item: selectedItem });
}
}
};
}
<!doctype html>
<html ng-app="typeahead.demo">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-animate.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-sanitize.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-2.5.0.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<script type="text/ng-template" id="typeahead-search-results.html">
<ul ng-init="$parent.$parent.initializeScope(this)"
class="dropdown-menu"
ng-show="isOpen() && !moveInProgress"
ng-style="{ top: position().top + 'px', left: position().left + 'px' }"
role="listbox"
aria-hidden="{{ !isOpen() }}">
<li class="uib-typeahead-match"
ng-repeat="match in matches track by $index"
ng-class="{ active: isActive($index) }"
ng-mouseenter="selectActive($index)"
ng-click="selectMatch($index, $event)"
role="option"
id="{{ ::match.id }}">
<div uib-typeahead-match
index="$index"
match="match"
query="query"
template-url="templateUrl"></div>
</li>
</ul>
</script>
<script type="text/ng-template" id="multi-select-typeahead.html">
<input type="text"
placeholder="{{::inputPlaceholder}}"
ng-model="searchTerm"
ng-model-options="{debounce: 500}"
uib-typeahead="result as result[displayNameField] for result in searchApi({ searchText: $viewValue })"
typeahead-is-open="isOpen"
class="form-control"
typeahead-popup-template-url="typeahead-search-results.html" />
</script>
<body>
<div ng-controller="TypeaheadDemo" style="padding-top: 15px;">
<multi-select-typeahead class="col-xs-6"
search-api="itemApi(searchText)"
display-name-field="name"
on-select="addItem(item)"
input-placeholder="Search Items...">
</multi-select-typeahead>
<div class="col-xs-6">
<ul class="list-group">
<li class="list-group-item" ng-repeat="item in items">
{{ item.name }}
</li>
</ul>
</div>
</div>
</body>
</html>