Skip to content

Commit 2ea966c

Browse files
Tom Bertrandrunning-coder
authored andcommitted
Feat #487 multi source async results (#488)
* Feat #487 multi source async results added new option `asyncResult` when enabled will start displaying the source groups in the result list as they become available instead of waiting for the last group to be received * add `asyncResult` tests
1 parent be12844 commit 2ea966c

File tree

5 files changed

+660
-60
lines changed

5 files changed

+660
-60
lines changed

dist/jquery.typeahead.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/demo-asyncResult_v1.html

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
<!doctype html>
2+
3+
<html lang="en">
4+
<head>
5+
<meta charset="utf-8">
6+
7+
<title></title>
8+
<meta name="description" content="">
9+
<meta name="author" content="">
10+
11+
<link rel="stylesheet" href="../src/jquery.typeahead.css">
12+
13+
<script src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
14+
<!--script src="../dist/jquery.typeahead.min.js"></script-->
15+
<script src="../src/jquery.typeahead.js"></script>
16+
17+
</head>
18+
<body>
19+
20+
<div style="width: 100%; max-width: 800px; margin: 0 auto;">
21+
22+
<h1>AsyncResult_v1 Demo</h1>
23+
24+
<ul>
25+
<li>
26+
<a href="http://www.runningcoder.org/jquerytypeahead/documentation/">Documentation</a>
27+
</li>
28+
<li>
29+
<a href="http://www.runningcoder.org/jquerytypeahead/demo/">Demos</a>
30+
</li>
31+
</ul>
32+
33+
<div class="js-result-container"></div>
34+
35+
<form>
36+
<div class="typeahead__container">
37+
<div class="typeahead__field">
38+
<div class="typeahead__query">
39+
<input class="js-typeahead"
40+
name="q"
41+
autofocus
42+
autocomplete="off">
43+
</div>
44+
<div class="typeahead__button">
45+
<button type="submit">
46+
<span class="typeahead__search-icon"></span>
47+
</button>
48+
</div>
49+
</div>
50+
</div>
51+
</form>
52+
53+
<script>
54+
55+
var data = {
56+
countries: ["Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda",
57+
"Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh",
58+
"Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia",
59+
"Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burma",
60+
"Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Central African Republic", "Chad",
61+
"Chile", "China", "Colombia", "Comoros", "Congo, Democratic Republic", "Congo, Republic of the",
62+
"Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti",
63+
"Dominica", "Dominican Republic", "East Timor", "Ecuador", "Egypt", "El Salvador",
64+
"Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Fiji", "Finland", "France", "Gabon",
65+
"Gambia", "Georgia", "Germany", "Ghana", "Greece", "Greenland", "Grenada", "Guatemala", "Guinea",
66+
"Guinea-Bissau", "Guyana", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India",
67+
"Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan",
68+
"Kazakhstan", "Kenya", "Kiribati", "Korea, North", "Korea, South", "Kuwait", "Kyrgyzstan", "Laos",
69+
"Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
70+
"Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
71+
"Mauritania", "Mauritius", "Mexico", "Micronesia", "Moldova", "Mongolia", "Morocco", "Monaco",
72+
"Mozambique", "Namibia", "Nauru", "Nepal", "Netherlands", "New Zealand", "Nicaragua", "Niger",
73+
"Nigeria", "Norway", "Oman", "Pakistan", "Panama", "Papua New Guinea", "Paraguay", "Peru",
74+
"Philippines", "Poland", "Portugal", "Romania", "Russia", "Rwanda", "Samoa", "San Marino",
75+
"Sao Tome", "Saudi Arabia", "Senegal", "Serbia and Montenegro", "Seychelles", "Sierra Leone",
76+
"Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "Spain",
77+
"Sri Lanka", "Sudan", "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan",
78+
"Tajikistan", "Tanzania", "Thailand", "Togo", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey",
79+
"Turkmenistan", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States",
80+
"Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "Yemen", "Zambia", "Zimbabwe"],
81+
capitals: ["Abu Dhabi", "Abuja", "Accra", "Adamstown", "Addis Ababa", "Algiers", "Alofi", "Amman", "Amsterdam",
82+
"Andorra la Vella", "Ankara", "Antananarivo", "Apia", "Ashgabat", "Asmara", "Astana", "Asunción", "Athens",
83+
"Avarua", "Baghdad", "Baku", "Bamako", "Bandar Seri Begawan", "Bangkok", "Bangui", "Banjul", "Basseterre",
84+
"Beijing", "Beirut", "Belgrade", "Belmopan", "Berlin", "Bern", "Bishkek", "Bissau", "Bogotá", "Brasília",
85+
"Bratislava", "Brazzaville", "Bridgetown", "Brussels", "Bucharest", "Budapest", "Buenos Aires", "Bujumbura",
86+
"Cairo", "Canberra", "Caracas", "Castries", "Cayenne", "Charlotte Amalie", "Chisinau", "Cockburn Town",
87+
"Conakry", "Copenhagen", "Dakar", "Damascus", "Dhaka", "Dili", "Djibouti", "Dodoma", "Doha", "Douglas",
88+
"Dublin", "Dushanbe", "Edinburgh of the Seven Seas", "El Aaiún", "Episkopi Cantonment", "Flying Fish Cove",
89+
"Freetown", "Funafuti", "Gaborone", "George Town", "Georgetown", "Georgetown", "Gibraltar", "King Edward Point",
90+
"Guatemala City", "Gustavia", "Hagåtña", "Hamilton", "Hanga Roa", "Hanoi", "Harare", "Hargeisa", "Havana",
91+
"Helsinki", "Honiara", "Islamabad", "Jakarta", "Jamestown", "Jerusalem", "Juba", "Kabul", "Kampala",
92+
"Kathmandu", "Khartoum", "Kiev", "Kigali", "Kingston", "Kingston", "Kingstown", "Kinshasa", "Kuala Lumpur",
93+
"Kuwait City", "Libreville", "Lilongwe", "Lima", "Lisbon", "Ljubljana", "Lomé", "London", "Luanda", "Lusaka",
94+
"Luxembourg", "Madrid", "Majuro", "Malabo", "Malé", "Managua", "Manama", "Manila", "Maputo", "Marigot",
95+
"Maseru", "Mata-Utu", "Mbabane Lobamba", "Melekeok Ngerulmud", "Mexico City", "Minsk", "Mogadishu", "Monaco",
96+
"Monrovia", "Montevideo", "Moroni", "Moscow", "Muscat", "Nairobi", "Nassau", "Naypyidaw", "N'Djamena",
97+
"New Delhi", "Niamey", "Nicosia", "Nicosia", "Nouakchott", "Nouméa", "Nukuʻalofa", "Nuuk", "Oranjestad",
98+
"Oslo", "Ottawa", "Ouagadougou", "Pago Pago", "Palikir", "Panama City", "Papeete", "Paramaribo", "Paris",
99+
"Philipsburg", "Phnom Penh", "Plymouth Brades Estate", "Podgorica Cetinje", "Port Louis", "Port Moresby",
100+
"Port Vila", "Port-au-Prince", "Port of Spain", "Porto-Novo Cotonou", "Prague", "Praia", "Cape Town",
101+
"Pristina", "Pyongyang", "Quito", "Rabat", "Reykjavík", "Riga", "Riyadh", "Road Town", "Rome", "Roseau",
102+
"Saipan", "San José", "San Juan", "San Marino", "San Salvador", "Sana'a", "Santiago", "Santo Domingo",
103+
"São Tomé", "Sarajevo", "Seoul", "Singapore", "Skopje", "Sofia", "Sri Jayawardenepura Kotte", "St. George's",
104+
"St. Helier", "St. John's", "St. Peter Port", "St. Pierre", "Stanley", "Stepanakert", "Stockholm", "Sucre",
105+
"Sukhumi", "Suva", "Taipei", "Tallinn", "Tarawa Atoll", "Tashkent", "Tbilisi", "Tegucigalpa", "Tehran",
106+
"Thimphu", "Tirana", "Tiraspol", "Tokyo", "Tórshavn", "Tripoli", "Tskhinvali", "Tunis", "Ulan Bator", "Vaduz",
107+
"Valletta", "The Valley", "Vatican City", "Victoria", "Vienna", "Vientiane", "Vilnius", "Warsaw",
108+
"Washington, D.C.", "Wellington", "West Island", "Willemstad", "Windhoek", "Yamoussoukro", "Yaoundé", "Yaren",
109+
"Yerevan", "Zagreb"]
110+
};
111+
112+
typeof $.typeahead === 'function' && $.typeahead({
113+
input: ".js-typeahead",
114+
minLength: 1,
115+
order: "asc",
116+
group: true,
117+
maxItemPerGroup: 3,
118+
asyncResult: true,
119+
groupOrder: function (node, query, result, resultCount, resultCountPerGroup) {
120+
121+
var scope = this,
122+
sortGroup = [];
123+
124+
for (var i in result) {
125+
sortGroup.push({
126+
group: i,
127+
length: result[i].length
128+
});
129+
}
130+
131+
sortGroup.sort(
132+
scope.helper.sort(
133+
["length"],
134+
false, // false = desc, the most results on top
135+
function (a) {
136+
return a.toString().toUpperCase()
137+
}
138+
)
139+
);
140+
141+
return $.map(sortGroup, function (val, i) {
142+
return val.group
143+
});
144+
},
145+
hint: true,
146+
dropdownFilter: "All",
147+
href: "https://en.wikipedia.org/?title={{display}}",
148+
template: "{{display}}, <small><em>{{group}}</em></small>",
149+
emptyTemplate: "no result for {{query}}",
150+
source: {
151+
user: {
152+
dynamic: true,
153+
template: "{{login}}, <small><em>{{group}}</em></small>",
154+
href: "https://www.github.com/{{username}}",
155+
data: [{
156+
"id": 415849,
157+
"username": "an inserted user that is not inside the database",
158+
"avatar": "https://avatars3.githubusercontent.com/u/415849"
159+
}],
160+
display: ['login'],
161+
ajax: {
162+
url: "https://api.github.com/repos/running-coder/jquery-typeahead/contributors",
163+
}
164+
},
165+
country: {
166+
data: data.countries
167+
},
168+
capital: {
169+
data: function() {
170+
var deferred = $.Deferred();
171+
// Gather your data, and resolve the deferred object with an array of objects
172+
// setTimeout is used only to show that the data is async
173+
setTimeout(function() {
174+
deferred.resolve(data.capitals);
175+
}, 3000);
176+
deferred.always(function() {
177+
console.log("yay! :D");
178+
});
179+
return deferred;
180+
}
181+
}
182+
},
183+
callback: {
184+
onClickAfter: function (node, a, item, event) {
185+
event.preventDefault();
186+
187+
var r = confirm("You will be redirected to:\n" + item.href + "\n\nContinue?");
188+
if (r == true) {
189+
window.open(item.href);
190+
}
191+
192+
$('.js-result-container').text('');
193+
194+
},
195+
onResult: function (node, query, obj, objCount) {
196+
197+
console.log(objCount)
198+
199+
var text = "";
200+
if (query !== "") {
201+
text = objCount + ' elements matching "' + query + '"';
202+
}
203+
$('.js-result-container').text(text);
204+
205+
},
206+
onLayoutBuiltBefore: function (node, query, result, resultHtmlList) {
207+
if (!resultHtmlList
208+
|| !this.generateGroups.length
209+
|| this.displayEmptyTemplate
210+
|| this.generatedGroupCount === this.generateGroups.length
211+
) return;
212+
213+
214+
this.generateGroups.forEach(function (group) {
215+
if (!resultHtmlList.find('[data-search-group="' + group + '"]').length) {
216+
resultHtmlList.append('<li class="typeahead__group" data-search-group="' + group + '">\
217+
<a href="javascript:;" tabindex="-1">' + group + '</a>\
218+
</li>\
219+
<li class="typeahead__item" data-group="" data-index="-1">\
220+
<span style="padding: 0.5rem 0.75rem;display: block;">Loading...</span>\
221+
</li>'
222+
);
223+
}
224+
});
225+
226+
return resultHtmlList;
227+
228+
}
229+
},
230+
debug: true
231+
});
232+
233+
</script>
234+
235+
</div>
236+
237+
</body>
238+
</html>

src/jquery.typeahead.js

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Licensed under the MIT license
55
*
66
* @author Tom Bertrand
7-
* @version 2.10.7 (2019-10-19)
7+
* @version 2.10.7 (2019-10-22)
88
* @link http://www.runningcoder.org/jquerytypeahead/
99
*/
1010
(function (factory) {
@@ -75,6 +75,7 @@
7575
emptyTemplate: false, // Display an empty template if no result
7676
cancelButton: true, // If text is detected in the input, a cancel button will be available to reset the input (pressing ESC also cancels)
7777
loadingAnimation: true, // Display a loading animation when typeahead is doing request / searching for results
78+
asyncResult: false, // If set to true, the search results will be displayed as they are beging received from the requests / async data function
7879
filter: true, // Set to false or function to bypass Typeahead filtering. WARNING: accent, correlativeTemplate, offset & matcher will not be interpreted
7980
matcher: null, // Add an extra filtering function after the typeahead functions
8081
source: null, // Source of data for Typeahead to filter
@@ -218,7 +219,7 @@
218219
this.label = {}; // The label object
219220
this.hasDragged = false; // Will cancel mouseend events if true
220221
this.focusOnly = false; // Focus the input preventing any operations
221-
this.displayEmptyTemplate // Display the empty template in the result list
222+
this.displayEmptyTemplate; // Display the empty template in the result list
222223

223224
this.__construct();
224225
};
@@ -832,6 +833,9 @@
832833

833834
generateSource: function (generateGroups) {
834835
this.filterGenerateSource();
836+
837+
this.generatedGroupCount = 0;
838+
835839
if (Array.isArray(generateGroups) && generateGroups.length) {
836840
this.generateGroups = generateGroups;
837841
} else if (!this.generateGroups.length) {
@@ -840,7 +844,6 @@
840844
}
841845

842846
this.requestGroups = [];
843-
this.generatedGroupCount = 0;
844847
this.options.loadingAnimation && this.container.addClass("loading");
845848

846849
if (!this.helper.isEmpty(this.xhr)) {
@@ -866,6 +869,10 @@
866869
cache = groupSource.cache;
867870
compression = groupSource.compression;
868871

872+
if (this.options.asyncResult) {
873+
delete this.source[group];
874+
}
875+
869876
if (cache) {
870877
dataInStorage = window[cache].getItem(
871878
"TYPEAHEAD_" + this.selector + ":" + group
@@ -941,6 +948,10 @@
941948
this.handleRequests();
942949
}
943950

951+
if (this.options.asyncResult && this.searchGroups.length !== this.generateGroups) {
952+
this.node.trigger("search" + this.namespace);
953+
}
954+
944955
return !!this.generateGroups.length;
945956
},
946957

@@ -1507,28 +1518,33 @@
15071518
);
15081519
}
15091520

1510-
this.incrementGeneratedGroup();
1521+
this.incrementGeneratedGroup(group);
15111522
},
15121523

1513-
incrementGeneratedGroup: function () {
1524+
incrementGeneratedGroup: function (group) {
15141525
this.generatedGroupCount++;
1515-
if (this.generatedGroupCount !== this.generateGroups.length) {
1526+
if (this.generatedGroupCount !== this.generateGroups.length && !this.options.asyncResult) {
15161527
return;
15171528
}
15181529

1519-
this.xhr = {};
1530+
if (this.xhr && this.xhr[group]) {
1531+
delete this.xhr[group];
1532+
}
15201533

15211534
for (var i = 0, ii = this.generateGroups.length; i < ii; i++) {
15221535
this.source[this.generateGroups[i]] = this.tmpSource[
15231536
this.generateGroups[i]
1524-
];
1537+
];
15251538
}
15261539

15271540
if (!this.hasDynamicGroups) {
15281541
this.buildDropdownItemLayout("dynamic");
15291542
}
15301543

1531-
this.options.loadingAnimation && this.container.removeClass("loading");
1544+
if (this.generatedGroupCount === this.generateGroups.length) {
1545+
this.xhr = {};
1546+
this.options.loadingAnimation && this.container.removeClass("loading");
1547+
}
15321548
this.node.trigger("search" + this.namespace);
15331549
},
15341550

@@ -1849,6 +1865,8 @@
18491865
this.options.source[group].matcher) ||
18501866
matcher;
18511867

1868+
if (!this.source[group]) continue;
1869+
18521870
for (var k = 0, kk = this.source[group].length; k < kk; k++) {
18531871
if (this.resultItemCount >= maxItem && !this.options.callback.onResult) break;
18541872
if (hasDynamicFilters && !this.dynamicFilter.validate.apply(this, [this.source[group][k]])) continue;
@@ -2144,7 +2162,7 @@
21442162
}
21452163

21462164
var emptyTemplate;
2147-
if (!this.result.length) {
2165+
if (!this.result.length && this.generatedGroupCount === this.generateGroups.length) {
21482166
if (
21492167
this.options.multiselect &&
21502168
this.options.multiselect.limit &&

0 commit comments

Comments
 (0)