Skip to content

Commit 939c0b8

Browse files
authored
Merge pull request #206 from gbanaag/main
Create build-a-cafe-finder-with-javascript.mdx
2 parents fb1bd6c + b441dd3 commit 939c0b8

File tree

1 file changed

+394
-0
lines changed

1 file changed

+394
-0
lines changed
Lines changed: 394 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
---
2+
title: Build a Cafe Finder with JavaScript
3+
author: Gabriela Banaag
4+
uid:
5+
datePublished: 2025-08-06
6+
description:
7+
header: https://imgur.com/a/iz4inIW
8+
tags:
9+
---
10+
11+
<BannerImage
12+
link="https://imgur.com/a/iz4inIW"
13+
description=""
14+
uid={false}
15+
cl="for-sidebar"
16+
/>
17+
18+
# Project Title
19+
20+
<AuthorAvatar
21+
author_name="Gabriela Banaag"
22+
author_avatar=""
23+
username="gabtot"
24+
uid={false}
25+
/>
26+
27+
<BannerImage
28+
link="https://imgur.com/a/iz4inIW"
29+
description=""
30+
uid={false}
31+
/>
32+
33+
**Prerequisites: HTML, CSS, JavaScript**
34+
35+
**Read Time:** 30 minutes
36+
37+
# ## Introduction
38+
39+
If you’re an iced-bev-meets-coworking-space fiend like me and want to shake up what cafe you head to next, this is the perfect project for you. We’ll be using our trilogy of web dev tools: ****HTML****, ****CSS****, and ****JavaScript****, and an API from the Google Maps Platform called ****Places****. We’ll also make some quick animations and implement swiping recognition with a JavaScript library called ****Hammer****!
40+
41+
## About the Google Maps Platform
42+
43+
The Google Maps Platform is a collection of Application Programming Interfaces (APIs) and Software Development Kits (SDKs). Each tool on the platform lets users use Google Maps functions and data in their own projects!
44+
45+
Some popular apps that use the Google Maps Platform include Uber and Airbnb, which access a user’s location to calculate rides and show rentals in specific areas!
46+
47+
The [Places API](https://developers.google.com/maps/documentation/places/web-service/overview) is a smaller API under the Google Maps API. It fetches location data like location names, addresses, photos, and many more attributes! It’s a great tool for accessing updated data and finding multiple places without manually copying all of their information into a project. ****We’ll be using this in our project! ****
48+
49+
# ## Setup
50+
51+
## ### Workspace
52+
53+
Start by creating a new HTML/CSS/JS project in [[Codédex Builds](https://www.codedex.io/builds)](https://www.codedex.io/builds). You can also use a Code editor of your choice, like [VSCode](https://code.visualstudio.com/).
54+
55+
## ### Google Cloud Console
56+
57+
Set up your project in the [[Google Cloud Console](https://developers.google.com/maps/documentation/elevation/cloud-setup)](https://developers.google.com/maps/documentation/elevation/cloud-setup). You’ll enter a project name and billing information. Your website won’t work as it should unless you enable billing, but you won’t be charged if the API doesn’t get called past the monthly limit (we'll teach you how to cap it at the limit, which is usually 10,000 API calls).
58+
59+
## ### Clone Template
60+
61+
Head over to [[this GitHub link](https://github.com/gbanaag/codedex-cafe-finder-template/tree/main)](https://github.com/gbanaag/codedex-cafe-finder-template/tree/main) and clone the repository. The repo has all the basic HTML and CSS set up so we don’t have to worry about it later and focus on API implementation.
62+
63+
You can clone the template in 3 different ways:
64+
65+
- Downloading the folder and adding it to your local workspace
66+
- GitHub Desktop app
67+
- ``git clone`` with command line
68+
69+
If you don’t have VS Code and don’t know how to use it, head over to our [[VS Code setup tutorial](https://www.codedex.io/projects/set-up-your-local-environment-in-python#download-vs-code)](https://www.codedex.io/projects/set-up-your-local-environment-in-python#download-vs-code).
70+
71+
# ## Accessing Locations with JavaScript
72+
73+
Time to write some back-end code!
74+
75+
## ### API Key
76+
77+
Here’s how to create and protect your API key:
78+
79+
1. Head to the [Google Cloud Console](https://console.cloud.google.com/) and open the project picker, then click on ****new project**** in the corner.
80+
2. Come up with a name for your project! _Note that the name can’t be changed later._
81+
3. Make sure you’re on your cafe finder project and select the APIs & Services button.
82+
4. On the left menu, click on library.
83+
5. Search for the Places API (new) and click on it. Make sure the Places API is enabled.
84+
6. Click on the hamburger menu and look for the APIs & Services button again. Then click on ****create** **credentials ****, then API key.
85+
7. After your key is generated, click on edit key. Restrict your application to websites.
86+
8. Add the domain your project is hosted on. In my case, mine is hosted on Codédex builds.
87+
88+
Put your API key at the top of your JavaScript file. It should look like this:
89+
90+
```jsx
91+
const apiKey = "STRING_OF_RANDOM_CHARACTERS_HERE";
92+
```
93+
94+
<aside>
95+
⚠️
96+
97+
****Make sure your API key is restricted to just your project (step 7), or else anyone can use it freely and you’ll get charged for it!****
98+
99+
</aside>
100+
101+
## ### cors-anywhere
102+
103+
Head to [https://cors-anywhere.herokuapp.com/](https://cors-anywhere.herokuapp.com/). Make sure to click on the ``Request temporary access to the demo server`` button to get set up with a temporary server for your project.
104+
105+
Then, add these lines to the top of your JavaScript file.
106+
107+
```jsx
108+
const useProxy = true;
109+
const proxy = "https://cors-anywhere.herokuapp.com/";
110+
```
111+
112+
## ### Geolocation
113+
114+
To see cafes nearby, the browser needs access to your location. There’s a built in JavaScript function that takes care of that for you called ``useLocation()`` and it takes your device’s latitude and longitude coordinates.
115+
116+
```jsx
117+
function getLocation() {
118+
const cache = JSON.parse(localStorage.getItem('cachedLocation') || '{}');
119+
const now = Date.now();
120+
```
121+
122+
Next, we’ll check if any location data has been cached (saved to the browser) and is less than 10 minutes old.
123+
124+
```jsx
125+
if (cache.timestamp && now - cache.timestamp < 10 * 60 * 1000) {
126+
useLocation(cache.lat, cache.lng);
127+
}
128+
```
129+
130+
If there’s no cached location information found, the browser pulls your current latitude and longitude from a built in function.
131+
132+
```jsx
133+
else {
134+
navigator.geolocation.getCurrentPosition(pos => {
135+
const lat = pos.coords.latitude;
136+
const lng = pos.coords.longitude;
137+
```
138+
139+
We’ll save the location and timestamp in ``localStorage`` in case we use our cafe finder website in the near future. Then we’ll add error handling - an alert that lets you know if your location can’t be accessed.
140+
141+
```jsx
142+
localStorage.setItem('cachedLocation', JSON.stringify({ lat, lng, timestamp: now }));
143+
useLocation(lat, lng);
144+
}, () => alert("Location access denied or unavailable."));
145+
}
146+
}
147+
```
148+
149+
## ### Using the API
150+
151+
We’ll start implementing the Google Places API to take locations and their information from Google Maps itself! We’ll start by writing the `useLocation()` function.
152+
153+
We’ll create an ****endpoint****, which can send requests for specific actions and functions to an API. Our endpoint will reference the Google Places API by inserting your API key and saved latitude and longitude.
154+
155+
```jsx
156+
async function useLocation(lat, lng) {
157+
const endpoint = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&radius=1500&type=cafe&key=${apiKey}`;
158+
const url = useProxy ? proxy + endpoint : endpoint;
159+
```
160+
161+
We’ll call the API to find nearby cafes and *_fetch_* their urls.
162+
163+
```jsx
164+
try {
165+
const response = await fetch(url);
166+
const data = await response.json();
167+
```
168+
169+
If the API finds results for nearby cafes, we’ll take that data and insert it into cards (we'll write the `displayCards` function next!)
170+
171+
```jsx
172+
if (data.results) {
173+
displayCards(data.results);
174+
} else {
175+
alert("No cafes found.");
176+
}
177+
} catch (e) {
178+
console.error("Error fetching Places API:", e);
179+
alert("Error fetching cafes.");
180+
}
181+
}
182+
```
183+
184+
# ## Cafe UI and Animations
185+
186+
We’re gonna create a wrapper for our cards, and inject data from the API into the wrapper as we go. To start, let’s make a function called ``displayCards()`` that renders a div container whenever the website starts up.
187+
188+
<ImageZoom src="https://imgur-url-here.png" style={{ width: "60%", height: "auto" }} alt="alt text here"/>
189+
190+
We’ll start by creating a container that takes the first saved cafe in our cafe options. We’ll make the content of this card blank for now, and fill it in with information when we use the Places API.
191+
192+
```jsx
193+
function displayCards(cafes) {
194+
const container = document.querySelector('.cards');
195+
container.innerHTML = '';
196+
```
197+
198+
We’ll then make a for loop that iterates through the cafe list to…
199+
200+
- Create a new div to wrap each cafe card
201+
- Add a class (``swipe-wrapper``) to each card to keep card styles consistent
202+
- Adjust the card’s ****z-index**** (display order) so that new cards appear under old ones
203+
204+
```jsx
205+
cafes.forEach((cafe, i) => {
206+
const wrapper = document.createElement('div');
207+
wrapper.className = 'swipe-wrapper';
208+
wrapper.style.zIndex = 200 - i;
209+
210+
var newCards = document.querySelectorAll('.location-card:not(.removed)');
211+
var allCards = document.querySelectorAll('.location-card');
212+
});
213+
}
214+
```
215+
216+
We’ll add more inside the function that inserts location information into each card by calling the Places API. Add a line that creates a card div and another line that assigns the card’s class name to be ``location-card``.
217+
218+
Then, add a line that takes the URL of a photo of the cafe and saves it into the ``imgUrl`` variable.
219+
220+
```jsx
221+
const card = document.createElement('div');
222+
card.className = 'location-card';
223+
224+
const imgUrl = cafe.photos?.[0]?.photo_reference
225+
? `https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photoreference=${cafe.photos[0].photo_reference}&key=${apiKey}`
226+
: 'https://via.placeholder.com/250x150?text=No+Image';
227+
```
228+
229+
We’ll grab each cafe location’s photo, place id, image URL, and rating. We’ll save all the information in an object called ``cafeData``.
230+
231+
```jsx
232+
const cafeData = {
233+
name: cafe.name,
234+
place_id: cafe.place_id,
235+
photo: imgUrl,
236+
rating: cafe.rating || 'N/A'
237+
};
238+
```
239+
240+
Putting in tags between the ``` symbol when changing an element’s ``innerHTML`` allows you to render HTML elements when functions run. We’ll append (connect) the card to its wrapper, and append its wrapper to its container.
241+
242+
```jsx
243+
card.innerHTML = `
244+
<img src="${imgUrl}" alt="${cafe.name}" />
245+
<h3>${cafe.name}</h3>
246+
<p>⭐️ Rating: ${cafe.rating || 'N/A'}</p>
247+
<p><small>Swipe right to save 💖</small></p>
248+
`;
249+
250+
wrapper.appendChild(card);
251+
container.appendChild(wrapper);
252+
```
253+
254+
## ### Hammer
255+
256+
[Hammer](http://hammerjs.github.io/) is a library used to recognize touch gestures in the browser!
257+
258+
<aside>
259+
💡
260+
261+
A ****library**** is a collection of reusable resources like functions, classes, and pieces of pre-written code. An **API****** is a an interface that lets your code communicate with another application.
262+
263+
</aside>
264+
265+
To make sure we have swiping gestures detected on the site, we’re gonna import the library by adding this line in the ``<head>`` of your HTML file:
266+
267+
```jsx
268+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/hammer.min.js"></script>
269+
```
270+
271+
[gif]
272+
273+
We’re going to add a function that detects left and right swipes and makes each cafe card flick and fade away. Add this to the bottom of your `displayCards()` function to make it happen:
274+
275+
```jsx
276+
const hammertime = new Hammer(wrapper);
277+
hammertime.on('swipeleft', () => {
278+
wrapper.style.transform = 'translateX(-150%) rotate(-15deg)';
279+
wrapper.style.opacity = 0;
280+
setTimeout(() => wrapper.remove(), 100);
281+
});
282+
hammertime.on('swiperight', () => {
283+
saveCafe(JSON.stringify(cafeData));
284+
wrapper.style.transform = 'translateX(150%) rotate(15deg)';
285+
wrapper.style.opacity = 0;
286+
setTimeout(() => wrapper.remove(), 100);
287+
});
288+
```
289+
290+
## ### Saving Cafes
291+
292+
We’ll be using ``localStorage`**`** to save cafes to the browser. It automatically caches (saves) specific interactions from your site to your browser that you choose to save without needing a database.
293+
294+
** **JSON Parsing**** is used to take strings and save them as data structures like objects or arrays. To implement this, we'll add a line that stores the string as a cafe **object.** Then we'll save the cafe object into an array called ``saved``.
295+
296+
```jsx
297+
function saveCafe(cafeJSON) {
298+
const cafe = JSON.parse(cafeJSON);
299+
let saved = JSON.parse(localStorage.getItem('savedCafes') || '[]');
300+
```
301+
302+
Next, write an `if` statement that searches your array of saved cafes and makes sure that the cafe hasn’t been saved yet. This is done by using the ``find`` function, which sees if a cafe’s `place_id` exists in the array.
303+
304+
If the cafe isn’t saved, the function will ``push()`` (insert) the cafe into the ``saved`` array of cafes, and alert the user that the cafe has been saved.
305+
306+
```jsx
307+
if (!saved.find(c => c.place_id === cafe.place_id)) {
308+
saved.push(cafe);
309+
localStorage.setItem('savedCafes', JSON.stringify(saved));
310+
alert(`${cafe.name} saved!`);
311+
}
312+
```
313+
314+
We’ll need a fallback in case the cafe is already saved. Write an `else` statement that alerts the user if a cafe has already been saved, so it doesn’t get duplicated into the saved list of cafes.
315+
316+
```jsx
317+
else {
318+
alert(`${cafe.name} is already saved.`);
319+
}
320+
}
321+
```
322+
323+
To check what cafes we have saved, we’ll make a function called ``showSaved()``. Since all the cafe information isn’t saved in HTML and card form, we need to pull the saved info from `localStorage` and use ****DOM manipulation**** to create HTML elements like each cafe’s image, name, and rating of the spot when prompted.
324+
325+
DOM manipulation is a JavaScript process that changes a specific part of a webpage’s content, style, or structure. In this case, we’ll be inserting cafe information into ``<div>`` blocks onto your webpage.
326+
327+
Start by writing a function called ``showSaved()``. Similar to the ``displayCards()`` function, we’ll make a container and leave the content blank to start.
328+
329+
```jsx
330+
function showSaved() {
331+
const container = document.querySelector('.cards');
332+
container.innerHTML = '';
333+
```
334+
335+
Then, add this line to parse through the list of saved cafes and save the information in a variable.
336+
337+
```jsx
338+
const saved = JSON.parse(localStorage.getItem('savedCafes') || '[]');
339+
```
340+
341+
If the saved array is empty, it’ll update the page by adding a ``<p>`` tag to say no cafes have been saved yet.
342+
343+
```jsx
344+
if (saved.length === 0) {
345+
container.innerHTML = '<p>No saved cafes yet 😢</p>';
346+
return;
347+
}
348+
```
349+
350+
For each cafe in the saved array, we’ll make a card div, add the `location-card` class name, and update the card’s content with `innerHTML` to include the current cafe’s information.
351+
352+
```jsx
353+
saved.forEach(cafe => {
354+
const card = document.createElement('div');
355+
card.className = 'location-card';
356+
card.innerHTML = `
357+
<img src="${cafe.photo}" alt="${cafe.name}" />
358+
<h3>${cafe.name}</h3>
359+
<p>⭐️ Rating: ${cafe.rating}</p>
360+
`;
361+
```
362+
363+
We then use the `appendChild()` function to add the cafe card that was just generated to its container to live in.
364+
365+
```jsx
366+
container.appendChild(card);
367+
});
368+
}
369+
```
370+
371+
# ## Conclusion
372+
373+
Here's my final result:
374+
375+
<ImageZoom src="https://imgur.com/a/TuZpvsR" />
376+
377+
You did it! Enjoy your newly built cafe finder and be sure to treat yourself to a coffee, chai, matcha, or boba to celebrate your hard work. 🍵 
378+
379+
If you’re looking for another challenge or a way to spice up your project, you could try…
380+
381+
- A [insert favorite cuisine here] restaurant finder
382+
- Adding a map of places next to the cards
383+
- Animating your cards further
384+
385+
## ### Troubleshooting
386+
387+
- Make sure you’re granted temporary access on https://cors-anywhere.herokuapp.com/.
388+
- Make sure to allow location access when you try out the site.
389+
390+
## ### Additional Resources
391+
392+
- GitHub repository
393+
- YouTube video (coming soon!)
394+
- [Places API Documentation](https://developers.google.com/maps/documentation/places/web-service/overview#how-use)

0 commit comments

Comments
 (0)