forked from Siyfion/angular-typeahead
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathangular-typeahead.js
More file actions
142 lines (123 loc) · 5.56 KB
/
Copy pathangular-typeahead.js
File metadata and controls
142 lines (123 loc) · 5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
angular.module('siyfion.sfTypeahead', [])
.directive('sfTypeahead', function () {
return {
restrict: 'AC', // Only apply on an attribute or class
require: '?ngModel', // The two-way data bound value that is returned by the directive
scope: {
options: '=', // The typeahead configuration options (https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options)
datasets: '=', // The typeahead datasets to use (https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#datasets)
suggestionKey : '@'
},
link: function (scope, element, attrs, ngModel) {
var options = scope.options || {},
datasets = (angular.isArray(scope.datasets) ? scope.datasets : [scope.datasets]) || []; // normalize to array
// Correct non-Chrome browser bug when input is inside LABEL tag
if (element[0] && element[0].parentElement.tagName == 'LABEL') {
var elm = element[0].parentElement;
$(elm).on('click',function(elm){
var inputList = $(elm.currentTarget).find('input');
if (inputList.length > 1) {
setTimeout(function(){
inputList.last().focus();
}, 0);
}
});
}
// Create the typeahead on the element
element.typeahead(scope.options, scope.datasets);
// Parses and validates what is going to be set to model (called when: ngModel.$setViewValue(value))
ngModel.$parsers.push(function (fromView) {
// Assuming that all objects are datums
// See typeahead basics: https://gist.github.com/jharding/9458744#file-the-basics-js-L15
var isDatum = angular.isObject(fromView);
if (options.editable === false) {
ngModel.$setValidity('typeahead', isDatum);
return isDatum ? fromView : undefined;
}
return fromView;
});
// Formats what is going to be displayed (called when: $scope.model = { object })
ngModel.$formatters.push(function (fromModel) {
if (angular.isObject(fromModel)) {
var found = false;
$.each(datasets, function (index, dataset) {
var query = dataset.source,
displayKey = dataset.displayKey || 'value',
value = (angular.isFunction(displayKey) ? displayKey(fromModel) : fromModel[displayKey]) || '';
if (found) return false; // break
if (!value) {
// Fakes a request just to use the same function logic
search([]);
return;
}
// Get suggestions by asynchronous request and updates the view
query(value, search);
return;
function search(suggestions) {
var exists = inArray(suggestions, fromModel);
if (exists) {
ngModel.$setViewValue(fromModel);
found = true;
} else {
ngModel.$setViewValue(options.editable === false ? undefined : fromModel);
}
// At this point, digest could be running (local, prefetch) or could not be (remote)
// As bloodhound object is inaccessible to know that, simulates an async to not conflict
// with possible running digest
if (found || index === datasets.length - 1) {
setTimeout(function () {
scope.$apply(function () {
element.typeahead('val', value);
});
}, 0);
}
}
});
return ''; // loading
} else if (fromModel == null) {
//fromModel has been set to null or undefined
element.typeahead('val', null);
}
return fromModel;
});
function inArray(array, element) {
var found = -1;
angular.forEach(array, function (value, key) {
if (angular.equals(element, value)) {
found = key;
}
});
return found >= 0;
}
function updateScope (object, suggestion, dataset) {
scope.$apply(function () {
var newViewValue = (angular.isDefined(scope.suggestionKey)) ?
suggestion[scope.suggestionKey] : suggestion;
ngModel.$setViewValue(newViewValue);
});
}
// Update the value binding when a value is manually selected from the dropdown.
element.bind('typeahead:selected', function(object, suggestion, dataset) {
updateScope(object, suggestion, dataset);
scope.$emit('typeahead:selected', suggestion, dataset);
});
// Update the value binding when a query is autocompleted.
element.bind('typeahead:autocompleted', function(object, suggestion, dataset) {
updateScope(object, suggestion, dataset);
scope.$emit('typeahead:autocompleted', suggestion, dataset);
});
// Propagate the opened event
element.bind('typeahead:opened', function() {
scope.$emit('typeahead:opened');
});
// Propagate the closed event
element.bind('typeahead:closed', function() {
scope.$emit('typeahead:closed');
});
// Propagate the cursorchanged event
element.bind('typeahead:cursorchanged', function(event, suggestion, dataset) {
scope.$emit('typeahead:cursorchanged', event, suggestion, dataset);
});
}
};
});