Skip to content

Commit ee37380

Browse files
author
Shane Boyer
committed
initial commit
0 parents  commit ee37380

12 files changed

+9484
-0
lines changed

Autocomplete.js

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
export default class Autocomplete {
2+
constructor(rootEl, options = {}) {
3+
this.rootEl = rootEl;
4+
this.options = {
5+
numOfResults: 10,
6+
data: [],
7+
...options,
8+
};
9+
10+
this.init();
11+
}
12+
13+
/**
14+
* Given an array and a query, return a filtered array based on the query.
15+
*/
16+
getResults(query, data) {
17+
if (!query) return [];
18+
19+
// Filter for matching strings
20+
return data.filter((item) => {
21+
return item.text.toLowerCase().includes(query.toLowerCase());
22+
});
23+
}
24+
25+
onQueryChange(query) {
26+
// Get data for the dropdown
27+
let results = this.getResults(query, this.options.data);
28+
results = results.slice(0, this.options.numOfResults);
29+
30+
this.updateDropdown(results);
31+
}
32+
33+
updateDropdown(results) {
34+
this.listEl.innerHTML = '';
35+
this.listEl.appendChild(this.createResultsEl(results));
36+
}
37+
38+
createResultsEl(results) {
39+
const fragment = document.createDocumentFragment();
40+
results.forEach((result) => {
41+
const el = document.createElement('li');
42+
el.classList.add('result');
43+
el.textContent = result.text;
44+
45+
// Pass the value to the onSelect callback
46+
el.addEventListener('click', () => {
47+
const { onSelect } = this.options;
48+
if (typeof onSelect === 'function') onSelect(result.value);
49+
});
50+
51+
fragment.appendChild(el);
52+
});
53+
return fragment;
54+
}
55+
56+
createQueryInputEl() {
57+
const inputEl = document.createElement('input');
58+
inputEl.setAttribute('type', 'search');
59+
inputEl.setAttribute('name', 'query');
60+
inputEl.setAttribute('autocomplete', 'off');
61+
62+
inputEl.addEventListener('input',
63+
event => this.onQueryChange(event.target.value));
64+
65+
return inputEl;
66+
}
67+
68+
init() {
69+
// Build query input
70+
this.inputEl = this.createQueryInputEl();
71+
this.rootEl.appendChild(this.inputEl)
72+
73+
// Build results dropdown
74+
this.listEl = document.createElement('ul');
75+
this.listEl.classList.add('results');
76+
this.rootEl.appendChild(this.listEl);
77+
}
78+
}

README.md

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Ozmo Frontend Take-home Exercise
2+
3+
Hi there! Here is a coding exercise to help us assess your technical skills.
4+
Please plan to spend no more than 4 hours on this. We understand we may not be
5+
the only company asking for an exercise from you and want to be respectful of
6+
your time. The test is designed for all levels, and you could spend much longer
7+
perfecting your solution if you wanted to. We recommend you focus on the core
8+
requirements first, then work on any additional features if you have the time.
9+
10+
By 4 hours in, please feel free to stop working and explain what refactors /
11+
code organization / enhancements you would have made with more time in the
12+
SOLUTION.md file.
13+
14+
If you have any questions at any point during the exercise, please reach out to
15+
16+
17+
## Submission
18+
19+
Please commit all your changes to this git repository. When you're done:
20+
21+
1. Create a zip / tarball of this repository (excluding `node_modules`) and
22+
submit it to https://www.dropbox.com/request/RonDKCswJNCcDnzPsQTH
23+
2. Email Anna to let her know you're done.
24+
25+
## Overview
26+
27+
We’ve built a simple Autocomplete/Typeahead component in vanilla JavaScript
28+
(compiled with Babel 7) that lets you type in a query and shows a list of
29+
matching results in a dropdown, just like how Google's search box works.
30+
31+
To see this component in action, let's set up the repo:
32+
33+
1. Run `npm install`
34+
2. Run `npm start` (runs `webpack-dev-server`)
35+
3. Open `http://localhost:8080` on your browser.
36+
37+
Type "new" in the input, and you'll get a list of matching US states that start
38+
with "new".
39+
40+
41+
## Task
42+
43+
Currently, the component can only query against a static data array and only
44+
works with mouse clicks. Your task is to:
45+
46+
1. Enhance the component so that it also accepts an HTTP endpoint as data source.
47+
48+
For example, if you wire up the component to
49+
`https://api.github.com/search/users?q={query}&per_page=${numOfResults}`,
50+
and if you type `foo` in the input, the component dropdown should show
51+
Github users with logins that start with `foo`. When you select a user from
52+
the results, `item` in the `onSelect(item)` callback should be the selected
53+
Github user's id.
54+
55+
(The enhanced component only needs to work with either a data array or a
56+
HTTP source, not both.)
57+
58+
2. Implement keyboard shortcuts to navigate the results dropdown using up/down
59+
arrow keys and to select a result using the Enter key.
60+
61+
Uncomment the relevant sections in `index.js` and `index.html` to implement a
62+
demo that looks like this:
63+
64+
![Demo example screenshot](demo-example.png)
65+
66+
67+
## Requirements
68+
69+
- The component should be reusable. It should be possible to have multiple
70+
instances of the component on the same page.
71+
- The "States" example that uses a data array should continue to work.
72+
- The component should accept any HTTP endpoint, not just the
73+
`https://api.github.com/users` example above.
74+
- Your component should work correctly in Chrome, don’t worry about
75+
cross-browser compatibility.
76+
- You can use small DOM helpers like jQuery or utilities from Lodash, but not
77+
larger libraries/frameworks like React, Angular or Vue.js
78+
- You _can_ modify all parts of the existing code, but you don't _need_ to do
79+
that to provide a great solution.
80+
- Document your component in `SOLUTION.md`.

SOLUTION.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Solution Docs
2+
3+
<!-- Include documentation, additional setup instructions, notes etc. here -->

babel.config.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module.exports = ({
2+
presets: [
3+
'@babel/preset-env',
4+
],
5+
plugins: [
6+
'@babel/plugin-syntax-dynamic-import',
7+
'@babel/plugin-syntax-import-meta',
8+
'@babel/plugin-proposal-class-properties',
9+
'@babel/plugin-proposal-json-strings',
10+
['@babel/plugin-proposal-decorators', { legacy: true }],
11+
'@babel/plugin-proposal-function-sent',
12+
'@babel/plugin-proposal-export-namespace-from',
13+
'@babel/plugin-proposal-numeric-separator',
14+
'@babel/plugin-proposal-throw-expressions',
15+
'@babel/plugin-proposal-export-default-from',
16+
'@babel/plugin-proposal-logical-assignment-operators',
17+
'@babel/plugin-proposal-optional-chaining',
18+
['@babel/plugin-proposal-pipeline-operator', { proposal: 'minimal' }],
19+
'@babel/plugin-proposal-nullish-coalescing-operator',
20+
'@babel/plugin-proposal-do-expressions',
21+
],
22+
});

demo-example.png

63.4 KB
Loading

index.html

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Autocomplete</title>
6+
</head>
7+
<body>
8+
<!-- Using an array object -->
9+
<div class="state-group form-group">
10+
<label>State:</label>
11+
<div id="state"></div>
12+
</div>
13+
14+
<!-- Using a HTTP endpoint -->
15+
<!-- <div class="gh-users-group form-group">
16+
<label>Github User:</label>
17+
<div id="gh-user"></div>
18+
</div> -->
19+
20+
<script src="/dist/bundle.js"></script>
21+
</body>
22+
</html>

index.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* eslint-disable no-new */
2+
import Autocomplete from './Autocomplete';
3+
import usStates from './us-states';
4+
import './main.css';
5+
6+
7+
// US States
8+
const data = usStates.map(state => ({
9+
text: state.name,
10+
value: state.abbreviation,
11+
}));
12+
new Autocomplete(document.getElementById('state'), {
13+
data,
14+
onSelect: (stateCode) => {
15+
console.log('selected state:', stateCode);
16+
},
17+
});
18+
19+
20+
// Github Users
21+
// new Autocomplete(document.getElementById('gh-user'), {
22+
// onSelect: (ghUserId) => {
23+
// console.log('selected github user id:', ghUserId);
24+
// },
25+
// });

main.css

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
body {
2+
padding: 20px;
3+
}
4+
5+
* {
6+
box-sizing: border-box;
7+
}
8+
9+
.form-group {
10+
width: 200px;
11+
}
12+
13+
ul {
14+
list-style-type: none;
15+
padding: 0;
16+
margin: 0;
17+
}
18+
19+
.results {
20+
border: 1px solid #ccc;
21+
border-top: 0;
22+
width: 100%;
23+
padding: 5px 0;
24+
max-height: 200px;
25+
overflow-y: scroll;
26+
}
27+
28+
.result {
29+
padding: 5px;
30+
cursor: pointer;
31+
}
32+
.result:hover {
33+
background-color: #eee;
34+
}
35+
36+
form {
37+
display: flex;
38+
}
39+
40+
.form-group + .form-group {
41+
margin-left: 50px;
42+
}
43+
44+
input {
45+
display: block;
46+
width: 100%;
47+
padding: 5px;
48+
border: 1px solid #ccc;
49+
}

0 commit comments

Comments
 (0)