Dependencies | Angular, Angular-Sanitize |
---|---|
Size | 12.2kb, 4kb minified |
Bower | bower install angular-mass-autocomplete |
NPM | npm install angular-mass-autocomplete |
mass-autocomplete
- Auto complete container. Maintains the suggestion box.
mass-autocomplete-item
- Attached to an input. Requires ng-model and mass-autocomplete.
{{ cb }}
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-sanitize.js"></script>
<script src="massautocomplete.js"></script>
<!-- Optional -->
<link href="massautocomplete.theme.css" rel="stylesheet" type="text/css">
</head>
<body ng-app=app>
<div ng-controller=mainCtrl>
<div mass-autocomplete>
<input ng-model="dirty.value"
mass-autocomplete-item="autocomplete_options">
</div>
</div>
</body>
</html>
var app = angular.module('app', ['ngSanitize', 'MassAutoComplete']);
app.controller('mainCtrl', function ($scope, $sce, $q) {
$scope.dirty = {};
var states = ['Alabama', 'Alaska', 'California', /* ... */ ];
function suggest_state(term) {
var q = term.toLowerCase().trim();
var results = [];
// Find first 10 states that start with `term`.
for (var i = 0; i < states.length && results.length < 10; i++) {
var state = states[i];
if (state.toLowerCase().indexOf(q) === 0)
results.push({ label: state, value: state });
}
return results;
}
$scope.autocomplete_options = {
suggest: suggest_state
};
});
MassAutocomplete was created as part of a new project that required auto completing a lot of input fields.
Most existing auto complete implementations (1, 2) are wrapping the input field and appending a suggestion box along with several watches. This is fine as long as you want to auto-complete only one or two inputs. But, when there are several dozen inputs, the memory, DOM and watch count will start to bloat and that might impact performance.
MassAutocomplete was implemented with a different approach in mind. Instead of attaching the suggestion box to each input field, we use transclusion to maintain only one box for the entire document. This approach guarantees that additional input fields will not require additional DOM or watches for the purpose of the auto complete.
MassAutocomplete does not provide filtering, linking, ranking or sorting. Generating the suggestions is left to the application.
We performed a comparison between mass-autocomplete and the popular ui-typehead.
All examples in this section are using just one mass-autocomplete
container.
Try inserting sauth or rod iland or gorgia.
var fuzzySearch = new Fuse(states, {
shouldSort: true,
caseSensitive: false,
threshold: 0.4,
});
function fuzzy_suggest(term) {
if (!term)
return [];
return fuzzySearch
.search(term)
.slice(0, 10)
.map(function (i) {
var val = states[i];
return {
value: val,
label: $sce.trustAsHtml(highlight(val, term))
};
});
}
$scope.ac_fuse_options = {
suggest: fuzzy_suggest
};
function highlight(str, term) {
var highlight_regex = new RegExp('(' + term + ')', 'gi');
return str.replace(highlight_regex,
'<span class="highlight">$1</span>');
};
function suggest_state_with_highlight(term) {
if (term.length < 2)
return;
var suggestions = suggest_state(term);
suggestions.forEach(function (s) {
// In real life you should reuse the regexp object.
s.label = $sce.trustAsHtml(highlight(s.label, term));
});
return suggestions;
};
$scope.ac_option_highlight = {
suggest: suggest_state_with_highlight
};
function suggest_state_delimited(term) {
var ix = term.lastIndexOf(','),
lhs = term.substring(0, ix + 1),
rhs = term.substring(ix + 1),
suggestions = suggest_state(rhs);
suggestions.forEach(function (s) {
s.value = lhs + s.value;
});
return suggestions;
};
$scope.ac_option_delimited = {
suggest: suggest_state_delimited
};
function suggest_state_as_tag(term) {
var suggestions = suggest_state_delimited(term);
suggestions.forEach(function (s) {
s.label = $sce.trustAsHtml(
'<span class="badge">' + s.label + '</span>'
);
});
return suggestions;
};
$scope.ac_option_tag = {
suggest: suggest_state_as_tag
};
$scope.tags = [];
function add_tag(selected) {
$scope.tags.push(selected.value);
// Clear model
$scope.dirty.selected_tag = undefined;
};
$scope.ac_option_tag_select = {
suggest: suggest_state_as_tag,
on_select: add_tag
};
function suggest_state_remote(term) {
var deferred = $q.defer();
// Fake remote source using timeout
$timeout(function () {
deferred.resolve(suggest_state(term));
}, 500);
return deferred.promise;
}
$scope.ac_option_remote = {
suggest: suggest_state_remote,
on_error: console.log
};
{{ selected_user | json }}
var users = [
{name: 'Haki', joined: '2 month ago', email: 'Haki@email.com'},
{name: 'Ran', joined: '2 days ago', email: 'Ran123@ac.org'},
{name: 'John', joined: 'a week ago', email: 'JJ@gmail.com'},
{name: 'Mary', joined: 'Yesterday', email: 'Mary@yahoo.com'},
{name: 'Charlie', joined: 'Just now', email: 'Charlie@msn.com'},
{name: 'Rebecca', joined: 'Yesterday', email: 'Becky@mail.com'},
{name: 'James', joined: '3 month ago', email: 'James@inbox.com'}
];
function suggest_users(term) {
var q = term.toLowerCase().trim(),
results = [];
for (var i = 0; i < users.length; i++) {
var user = users[i];
if (user.name.toLowerCase().indexOf(q) !== -1 ||
user.email.toLowerCase().indexOf(q) !== -1)
results.push({
value: user.name,
// Pass the object as well. Can be any property name.
obj: user,
label: $sce.trustAsHtml(
'<div class="row">' +
' <div class="col-xs-5">' +
' <i class="fa fa-user"></i>' +
' <strong>' + highlight(user.name,term) + '</strong>'+
' </div>' +
' <div class="col-xs-7 text-right text-muted">' +
' <small>' + highlight(user.email,term) + '</small>' +
' </div>' +
' <div class="col-xs-12">' +
' <span class="text-muted">Joined</span>' +
user.joined +
' </div>' +
'</div>'
)
});
}
return results;
};
$scope.ac_options_users = {
suggest: suggest_users,
on_select: function (selected) {
$scope.selected_user = selected.obj;
}
};
<div ng-repeat="i in n_array(n) track by $index"/>
<input type="text"
placeholder="Select State"
ng-model="dirty.state[$index]"
calc-autocomplete-item="ac_option_highlight">
</div>
mass-autocomplete=options | |
---|---|
debounce_position(150) | Debounce in ms for repositioning suggestion box on window resize. |
debounce_attach(300) |
Debounce in ms for attaching input after focus. Prevents unnecessary positioning when quickly jumping between inputs using tab. |
debounce_suggest(200) |
Debounce in ms for calling suggest. Useful for remote sources and to suspend suggestions while user is typing. |
debounce_blur(150) |
Debounce in ms for detach on blur. Determines the amount of time in milliseconds before losing focus as a result of selecting from the menu until detach is invoked. |
mass-autocomplete-item=options_name | |
suggest(term) |
Receive a string and return an array of suggestions. Each suggestion must contain
The label is bound using The return value is an array of objects or a promise that resolves to an array of objects.
The object is passed to the |
on_attach() | Callback fired when the user focus a field. |
on_select(selected_item) | Callback fired when the user select an item from the suggestion box. |
on_detach(current_value) | Callback fired when the input field is blurred. Useful for purging caches. |
on_error() | Callback fired in case suggest() fails. |
auto_select_first (false) | Auto select the first option in the suggestion box. |
massAutoCompleteConfigProvider | |
position_autocomplete(container, target) | Called on attach to position the ac container relative to the target input. Some use common use cases when a custom positioning function might be desired:
|
generate_random_id(prefix) | Used to generate random Id's starting at `prefix`. Id's are generated mostly for accessibility needs. It's very unlikely you will need to change this function. |
DEBOUNCE | Set default debounce globally. Default values are:
|
The suggestion box template
<div class="ac-container" ng-show="show_autocomplete && results.length" style="position:absolute;">
<ul class="ac-menu">
<li ng-repeat="result in results"
ng-if="$index > 0"
class="ac-menu-item"
ng-class="$index == selected_index ? 'ac-state-focus' : ''">
<a href ng-click="apply_selection($index, $event)" ng-bind-html=result.label> </a>
</li>
</ul>
</div>
We recommend adding backface-visibility: hidden;
and transform: translate3d(0, 0, 0);
to .ac-container
to limit the paint
area to that of the suggestion box. See massautocomplete.theme.css
for the complete CSS.
The implementation was inspired by the dojo project implementation of autocomplete. It is also recommended to include ngAria for the built-in aria support (mainly for ng-show/ng-hide).
The following markup is used to accompany assisting technology:
<input
id="ac_element_XXXXXX"
ng-model="dirty.value"
mass-autocomplete-item />
....
<div class="ac-container"
aria-autocomplete="list"
role="listbox"
aria-labelledby="ac_element_XXXXXX"
aria-activedescendant="ac_item_XXXXX"
ng-show="show_autocomplete">
<ul class="ac-menu">
<li ng-repeat="result in results"
class="ac-menu-item"
role="option"
id="ac_item_XXXXX"
ng-class="...">
<a href ng-click="..."></a>
</li>
</ul>
</div>
up down | Previous/Next suggestion |
---|---|
tab | When suggestion box is open - next suggestion |
esc |
When suggestion box is open - close When suggestion box is closed - revert to initial value. |