Skip to content

Commit 453b808

Browse files
author
Soshi Katsuta
committed
06: use MVVM pattern
Signed-off-by: Soshi Katsuta <skatsuta@amazon.com>
1 parent 906b126 commit 453b808

File tree

1 file changed

+68
-21
lines changed

1 file changed

+68
-21
lines changed

06 - Type Ahead/index.js

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,77 @@
1-
class Suggester {
1+
class Notifier {
22
constructor() {
3-
this.cities = [];
4-
const endpoint = 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json';
3+
this.handlers = [];
4+
}
5+
6+
observe(handler) {
7+
this.handlers.push(handler);
8+
}
9+
10+
fire() {
11+
this.handlers.forEach(handler => handler());
12+
}
13+
}
14+
15+
class Model {
16+
constructor() {
17+
this._cities = [];
18+
this._matchedCities = [];
19+
this.matchedCitiesChanged = new Notifier;
20+
}
21+
22+
get matchedCities() {
23+
return this._matchedCities;
24+
}
25+
26+
set matchedCities(cities) {
27+
this._matchedCities = cities;
28+
this.matchedCitiesChanged.fire();
29+
}
30+
31+
loadCities() {
32+
const endpoint = 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29' +
33+
'/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json';
534
fetch(endpoint)
635
.then(resp => resp.json())
7-
.then(data => this.cities.push(...data));
36+
.then(data => this._cities.push(...data));
37+
}
38+
39+
match(wordRegex) {
40+
this.matchedCities = this._cities.filter(place =>
41+
wordRegex.test(place.city) || wordRegex.test(place.state)
42+
);
43+
}
44+
}
45+
46+
class ViewModel {
47+
constructor() {
48+
this.model = new Model();
49+
this.matchedCities = this.model.matchedCities;
50+
51+
// register handler to observer
52+
this.model.matchedCitiesChanged.observe(() => this.matchedCities = this.model.matchedCities);
53+
54+
// add event handlers
55+
this.searchInput = document.querySelector('input.search');
56+
this.suggestions = document.querySelector('ul.suggestions');
57+
['change', 'keyup'].forEach(event =>
58+
this.searchInput.addEventListener(event, () => this.render(this.searchInput.value))
59+
);
60+
61+
this.model.loadCities();
862
}
963

10-
listMatches(wordToMatch) {
64+
render(wordToMatch) {
1165
const regex = new RegExp(wordToMatch, 'gi');
12-
const html = this.cities
13-
.filter(place => regex.test(place.city) || regex.test(place.state))
14-
.map(place => {
15-
const highlight = `<span class="hl">${wordToMatch}</span>`;
16-
const cityName = place.city.replace(regex, highlight);
17-
const stateName = place.state.replace(regex, highlight);
18-
return `<li><span class="name">${cityName}, ${stateName}</span></li>`;
19-
})
20-
.join("\n");
21-
return html;
66+
this.model.match(regex);
67+
68+
this.suggestions.innerHTML = this.matchedCities.map(place => {
69+
const highlight = `<span class="hl">${wordToMatch}</span>`;
70+
const cityName = place.city.replace(regex, highlight);
71+
const stateName = place.state.replace(regex, highlight);
72+
return `<li><span class="name">${cityName}, ${stateName}</span></li>`;
73+
}).join("\n");
2274
}
2375
}
2476

25-
const searchInput = document.querySelector('input.search');
26-
const suggestions = document.querySelector('ul.suggestions');
27-
const suggester = new Suggester();
28-
const displayMatches = () => suggestions.innerHTML = suggester.listMatches(searchInput.value);
29-
searchInput.addEventListener('change', displayMatches);
30-
searchInput.addEventListener('keyup', displayMatches);
77+
new ViewModel();

0 commit comments

Comments
 (0)