An Illustrated (and Musical) Guide to Map, Reduce, and Filter Array Methods

Map, reduce, and filter are three very useful array methods in JavaScript that give developers a ton of power in a short amount of space. Let’s jump right into how you can leverage (and remember how to use!) these super handy methods.

Array.map()

Array.map() updates each individual value in a given array based on a provided transformation and returns a new array of the same size. It accepts a callback function as an argument, which it uses to apply the transform.

let newArray = oldArray.map((value, index, array) => { ...
});

A mnemonic to remember this is MAP: Morph Array Piece-by-Piece.

Instead of a for-each loop to go through and apply this transformation to each value, you can use a map. This works when you want to preserve each value, but update it. We’re not potentially eliminating any values (like we would with a filter), or calculating a new output (like we would use reduce for). A map lets you morph an array piece-by-piece. Let’s take a look at an example:

[1, 4, 6, 14, 32, 78].map(val => val * 10)
// the result is: [10, 40, 60, 140, 320, 780]

In the above example, we take an initial array ([1, 4, 6, 14, 32, 78]) and map each value in it to be that value times ten (val * 10). The result is a new array with each value of the original array transformed by the equation: [10, 40, 60, 140, 320, 780].

An illustration of the code examples covered in this section.

Array.filter()

Array.filter() is a very handy shortcut when we have an array of values and want to filter those values into another array, where each value in the new array is a value that passes a specific test.

This works like a search filter. We’re filtering out values that pass the parameters we provide.

For example, if we have an array of numeric values, and want to filter them to just the values that are larger than 10, we could write:

[1, 4, 6, 14, 32, 78].filter(val => val > 10)
// the result is: [14, 32, 78]

If we were to use a map method on this array, such as in the example above, we would return an array of the same length as the original with val > 10 being the “transform,” or a test in this case. We transform each of the original values to their answer if they are greater than 10. It would look like this:

[1, 4, 6, 14, 32, 78].map(val => val > 10)
// the result is: [false, false, false, true, true, true]

A filter, however, returns only the true values. So the result is smaller than the original array or the same size if all values pass a specific test.

Think about filter like a strainer-type-of-filter. Some of the mix will pass through into the result, but some will be left behind and discarded.

An illustration of a funnel with numbers going in the top and a few coming out of the bottom next to a handwritten version of the code covered in this section.

Say we have a (very small) class of four dogs in obedience school. All of the dogs had challenges throughout obedience school and took a graded final exam. We’ll represent the doggies as an array of objects, i.e.:

const students = [ { name: "Boops", finalGrade: 80 }, { name: "Kitten", finalGrade: 45 }, { name: "Taco", finalGrade: 100 }, { name: "Lucy", finalGrade: 60 }
]

If the dogs get a score higher than 70 on their final test, they get a fancy certificate; and if they don’t, they’ll need to take the course again. In order to know how many certificates to print, we need to write a method that will return the dogs with passing grades. Instead of writing out a loop to test each object in the array, we can shorten our code with filter!

const passingDogs = students.filter((student) => { return student.finalGrade >= 70
}) /*
passingDogs = [ { name: "Boops", finalGrade: 80 }, { name: "Taco", finalGrade: 100 }
]
*/

As you can see, Boops and Taco are good dogs (actually, all dogs are good dogs), so Boops and Taco are getting certificates of achievement for passing the course! We can write this in a single line of code with our lovely implicit returns and then remove the parenthesis from our arrow function since we have single argument:

const passingDogs = students.filter(student => student.finalGrade >= 70) /*
passingDogs = [ { name: "Boops", finalGrade: 80 }, { name: "Taco", finalGrade: 100 }
]
*/

Array.reduce()

The reduce() method takes the input values of an array and returns a single value. This one is really interesting. Reduce accepts a callback function which consists of an accumulator (a value that accumulates each piece of the array, growing like a snowball), the value itself, and the index. It also takes a starting value as a second argument:

let finalVal = oldArray.reduce((accumulator, currentValue, currentIndex, array) => { ...
}), initalValue;
An illustration of a saucepan cooking ingredients next to handwritten code from the examples covered in this section.

Let’s set up a cook function and a list of ingredients:

// our list of ingredients in an array
const ingredients = ['wine', 'tomato', 'onion', 'mushroom'] // a cooking function
const cook = (ingredient) => { return `cooked ${ingredient}`
}

If we want to reduce the items into a sauce (pun absolutely intended), we’ll reduce them with reduce()!

const wineReduction = ingredients.reduce((sauce, item) => { return sauce += cook(item) + ', ' }, '') // wineReduction = "cooked wine, cooked tomato, cooked onion, cooked mushroom, "

That initial value ('' in our case) is important because if we don’t have it, we don’t cook the first item. It makes our output a little wonky, so it’s definitely something to watch out for. Here’s what I mean:

const wineReduction = ingredients.reduce((sauce, item) => { return sauce += cook(item) + ', ' }) // wineReduction = "winecooked tomato, cooked onion, cooked mushroom, "

Finally, to make sure we don’t have any excess spaces at the end of our new string, we can pass in the index and the array to apply our transformation:

const wineReduction = ingredients.reduce((sauce, item, index, array) => { sauce += cook(item) if (index < array.length - 1) { sauce += ', ' } return sauce }, '') // wineReduction = "cooked wine, cooked tomato, cooked onion, cooked mushroom"

Now we can write this even more concisely (in a single line!) using ternary operators, string templates, and implicit returns:

const wineReduction = ingredients.reduce((sauce, item, index, array) => { return (index < array.length - 1) ? sauce += `${cook(item)}, ` : sauce += `${cook(item)}`
}, '') // wineReduction = "cooked wine, cooked tomato, cooked onion, cooked mushroom"

A little way to remember this is to recall how you make sauce: you reduce a few ingredients down to a single item.

Sing it with me!

I wanted to end this blog post with a song, so I wrote a little diddy about array methods that might just help you to remember them:

The post An Illustrated (and Musical) Guide to Map, Reduce, and Filter Array Methods appeared first on CSS-Tricks.

Let’s make a form that puts current location to use in a map!

I love shopping online. I can find what I need and get most things for a decent price. I am Nigerian currently working and studying in India, and two things I dread when shopping online are:

  1. Filling out a credit card form
  2. Filling out shipping and billing address forms

Maybe I’m just lazy, but these things are not without challenges! For the first one, thanks to payment processing services like PayPal and e-wallets, I neither have to type in my 12-digit credit card number for every new e-commerce site I visit, nor have to save my credit card details with them.

For the second, the only time-saving option given by most shopping websites is to save your shipping address, but you still have to fill the form (arrghh!). This is where the challenge is. I’ve had most of my orders returned because my address (which I thought was the right address) could not be located or confirmed by the app for one reason or another.

Address inputs are challenging

Getting a user’s address through an input form is a clean solution but can also be a herculean task to the user. Here’s why:

  • If the user is new in a particular city, they might not know their full address
  • If the user wants to ship to a new address which isn’t saved (e.g shipping to a workplace or a friend’s address instead of the saved home address)
  • If the user resides in a city with very difficult address systems
  • If the user is plain lazy like me

A potential solution: get the address automatically

Getting the user’s address by the tap/click of a button. Yup, that’s easy! The UX is great as it saves the user both the time and effort of filling out some form. It will also save the store owner time, effort, and even money in some cases, as there’ll likely be a reduction in the number of incorrectly placed orders or shipments.

Let’s build a mini app that gets a user’s address and shows it on a Google Map interface using vanilla JavaScript. The concept is as follows:

  1. Get the HTML button and listen for a click event
  2. Get the user’s location (latitude and longitude) on a button click
  3. Show the user’s location on a Google map
  4. Pass the latitude and longitude to the Google Maps Geocode API URL
  5. Display the returned address (or list of addresses) on the UI for the user to select one
  6. Listen for map events and repeat steps 4 and 5
  7. Pre-fill the form with the address data the user selects

Getting started and setting up

To build this app, we’re going to use the Materialize CSS framework to save us some time fussing with styles. Materialize is a modern responsive front-end framework based on Google’s Material Design system. The beta version works with vanilla JavaScript.

A basic setup using Materialize’s CSS and JavaScript files in a document is like this:

<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Address Locator</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css"> <link rel="stylesheet" href="css/main.css">
</head>
<body> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script> <script src="js/main.js"></script>
</body>
</html>

We’re also going to use the Google Maps API for displaying the map and getting the user’s human-readable address. We’ll need an API key to do this. Here’s how to get one:

  1. Sign in to your Google Developer’s Console Account
  2. Create a new project or select an existing one
  3. Click on “Enable APIs and Services”
  4. Select the “Maps Javascript API” option
  5. Click “Enable” on the new page that comes up. Go back to the previous page and do a search for “Geocoding API,” click on it and enable it as well
  6. Then, on the right nav of the page, click on Credentials, copy the API key on the page and save it to a file

Now, let’s update our document to show the map and let the user know it can be used to get their current location as the address. Also, we will add a form that’s pre-filled with the address the user selects.

... <body> <div class="container"> <h3>Shipping Address</h3> <p>You can click the button below to use your current location as your shipping address</p> <div id="map"> </div> <button id="showMe" class="btn">Use My Location</button> <form id="shippingAddress"> <div id="locationList"></div> <br> <div class="input-field"> <textarea class="input_fields materialize-textarea" id="address" type="text"></textarea> <label class="active" for="address">Address (Area and Street)</label> </div> <div class="input-field"> <input class="input_fields" id="locality" type="text"> <label class="active" for="locality">Locality</label> </div> <div class="input-field"> <input class="input_fields" id="city" type="text"> <label class="active" for="city">City/District/Town</label> </div> <div class="input-field"> <input class="input_fields" id="postal_code" type="text"> <label class="active" for="pin_code">Pin Code</label> </div> <div class="input-field"> <input class="input_fields" id="landmark" type="text"> <label class="active" for="landmark">Landmark</label> </div> <div class="input-field"> <input class="input_fields" id="state" type="text"> <label class="active" for="State">State</label> </div> </form> <!-- You could add a fallback address gathering form here --> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script> <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script> <script src="js/main.js"></script>
</body> </html>

While we are here, let’s style things a bit to make this look a little better:

.container { width: 50%; max-width: 800px;
} #map { height: 50vh; margin-bottom: 10px; display: none;
} #locationList .card { padding: 10px;
} #toast-container { top: 50%; bottom: unset;
} .toast { background-color: rgba(0, 0, 0, 0.8);
} @media only screen and (max-width: 768px) { .container { width: 80%; }
}

This CSS hides the map until we are ready to view it. Our app should look like this:

Let’s plan this out

Our app will be making use of the HTML5 Geolocation API to determine our user’s current location as well as Google’s Geocode API with a technique called Reverse Geocoding. The Geocode API takes a human-readable address and changes it into geographical (latitudinal and longitudinal) coordinates and marks the spot in the map.

Reverse Geocoding does the reverse. It takes the latitude and longitude and converts them to human-readable addresses. Geocoding and Reverse Geocoding are well documented.

Here’s how our app will work:

  1. The user clicks on the “Use My Location” button
  2. The user is located with the HTML5 Geolocation API (navigator.geolocation)
  3. We get the user’s geographic coordinates
  4. We pass the coordinates to the geocode request API
  5. We display the resulting addresses to the user

Most times, the geocode returns more than one address, so we would have to show the user all the returned addresses and let them choose the most accurate one.

Phew! Finally, we can get to the fun part of actually writing the JavaScript. Let’s go through each of the steps we outlined.

Step 1: Clicking the button

In our main.js file, let’s get a reference to the HTML button. While we are at it, we’ll set up some other variables we’ll need, like our API key.

//This div will display Google map
const mapArea = document.getElementById('map'); //This button will set everything into motion when clicked
const actionBtn = document.getElementById('showMe'); //This will display all the available addresses returned by Google's Geocode Api
const locationsAvailable = document.getElementById('locationList'); //Let's bring in our API_KEY
const __KEY = 'YOUR_API_KEY'; //Let's declare our Gmap and Gmarker variables that will hold the Map and Marker Objects later on
let Gmap;
let Gmarker; //Now we listen for a click event on our button
actionBtn.addEventListener('click', e => { // hide the button actionBtn.style.display = "none"; // call Materialize toast to update user M.toast({ html: 'fetching your current location', classes: 'rounded' }); // get the user's position getLocation();
});

When the click handler for our button runs, it:

  1. Hides the button
  2. Alerts the user that we are getting their current location (a “toast” in Materialize is like a popup notification)
  3. Calls the getLocation function

Step 2: Get the user’s location (latitude and longitude)

When our getLocation function is invoked, we need to do some more work. First, let’s check if we can even use the Geolocation API:

getLocation = () => { // check if user's browser supports Navigator.geolocation if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(displayLocation, showError, options); } else { M.toast({ html: "Sorry, your browser does not support this feature... Please Update your Browser to enjoy it", classes: "rounded" }); }
}

When we have support, it calls geolocation’s getCurrentPosition method. If it doesn’t have support, then the user is alerted that there’s no browser support.

If there is support, then the getCurrentLocation method is used to get the current location of the device. The syntax is like this:

navigator.geolocation.getCurrentPosition(*success, error, [options]*)
  • success : This is a callback function that takes a position as its only parameter. For us, our success callback function is the displayLocation function.
  • error : [optional] This is a callback function that takes a PositionError as its sole input parameter. You can read more about this here. Our error callback function is the showError function.
  • options : [optional] This is an object which describes the options property to be passed to the getCurrentPosition method. You can read more about this here. Our options parameter is the options object.

Before writing our displayLocation function, let’s handle the showError function and options object:

// Displays the different error messages
showError = (error) => { mapArea.style.display = "block" switch (error.code) { case error.PERMISSION_DENIED: mapArea.innerHTML = "You denied the request for your location." break; case error.POSITION_UNAVAILABLE: mapArea.innerHTML = "Your Location information is unavailable." break; case error.TIMEOUT: mapArea.innerHTML = "Your request timed out. Please try again" break; case error.UNKNOWN_ERROR: mapArea.innerHTML = "An unknown error occurred please try again after some time." break; }
}
//Makes sure location accuracy is high
const options = { enableHighAccuracy: true
}

Now, let’s write the code for our displayLocation function inside our main.js file:

displayLocation = (position) => { const lat = position.coords.latitude; const lng = position.coords.longitude;
}

We now have our user’s latitude and longitude and we can view them in the console by writing the code below inside displayLocation:

console.log( `Current Latitude is ${lat} and your longitude is ${lng}` );

Step 3: Show the user’s current location on a Google Map

To do this, we will be adding these lines of code to our displayLocation function.

const latlng = {lat, lng}
showMap(latlng);
createMarker(latlng);
mapArea.style.display = "block";

The first line takes our lat and lng values and encapsulates it in the latlng object literal. This makes it easy for us to use in our app.

The second line of code calls a showMap function which accepts a latlng argument. In here, we get to instantiate our Google map and render it in our UI.

The third line invokes a createMarker function which also accepts our object literal (latlng) as its argument and uses it to create a Google Maps Marker for us.

The fourth line makes the mapArea visible so that our user can now see the location.

displayLocation = (position) => { const lat = position.coords.latitude; const lng = position.coords.longitude; const latlng = { lat, lng } showMap(latlng); createMarker(latlng); mapArea.style.display = "block";
}

Now, let’s get to creating our functions. We will start with the showMap function.

showMap = (latlng) => { let mapOptions = { center: latlng, zoom: 17 }; Gmap = new google.maps.Map(mapArea, mapOptions);
}

The showMap function creates a mapOptions objects that contain the map center (which is the lat and lng coordinates we got from displayLocation) and the zoom level of the map. Finally, we create an instance of the Google Maps class and pass it on to our map. In fewer words, we instantiate the Google Maps class.

To create a map instance, we specify two parameters in the constructor: the div the map will be displayed and the mapOptions. In our case, our div is called mapArea and our mapOptions is called mapOptions. After this, our created map will show up, but without a marker. We need a marker so the user can identify their current position on the map.

Let’s create our marker using the createMarker function:

createMarker = (latlng) => { let markerOptions = { position: latlng, map: Gmap, animation: google.maps.Animation.BOUNCE, clickable: true }; Gmarker = new google.maps.Marker(markerOptions);
}

A few things to note in this code:

  1. The position property positions the marker at the specified latlng
  2. The map property specifies the map instance where the marker should be rendered (in our case, it’s Gmap)
  3. The animation property adds a little BOUNCE to our marker
  4. The clickable property set to true means our marker can be clicked
  5. Finally, we instantiate the Marker class in our Gmarker instance variable

So far, our user’s location has been fetched, the map has rendered and the user can see their current location on the map. Things are looking good! 🕺

Step 4: Pass the latitude and longitude to the Geocode API

Google’s Geocoding API will be used to convert our user’s numeric geographical coordinates to a formatted, human-readable address using the reverse geocoding process we covered earlier.

The URL takes this form:

https://maps.googleapis.com/maps/api/geocode/outputFormat?parameters

…where the outputFormat may either be a json or xml which determines the the format used to deliver the data. The parameters part is a list of parameters needed for the request.

Our request URL will look like this:

https://maps.googleapis.com/maps/api/geocode/json?latlng=${latlng}&key=${__KEY}

Let’s go ahead and connect to the API. We would do this in a function called getGeolocation.

getGeolocation = (lat, lng) => { const latlng = lat + "," + lng; fetch( `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latlng}&key=${__KEY}` ) .then(res => res.json()) .then(data => console.log(data.results));
}

The getGeolocation function takes two arguments ( lat and lng } concatenates them to form a new latlng variable that is passed to the URL.

Using the Fetch API (more on this here), we add the new latlng and __KEY into the Geocode request URL. Then, on the response object we get back, we pass the .json method to resolve the promise with JSON. Finally, we log the response in our console.

To make use of our newly created function, we have to call it in the displayLocation function. So let’s update our displayLocation function to contain the getGeolocation function call:

displayLocation = (position) => { const lat = position.coords.latitude; const lng = position.coords.longitude; const latlng = { lat, lng } showMap(latlng); createMarker(latlng); mapArea.style.display = "block"; getGeolocation(lat, lng)// our new function call
}

The returned data should look something like this:

{ "results" : { "address_components": { "long_name": "1600", "short_name": "1600", "types": ["street_number"] }, { "long_name": "Amphitheatre Pkwy", "short_name": "Amphitheatre Pkwy", "types": ["route"] }, { "long_name": "Mountain View", "short_name": "Mountain View", "types": ["locality", "political"] }, { "long_name": "Santa Clara County", "short_name": "Santa Clara County", "types": ["administrative_area_level_2", "political"] }, { "long_name": "California", "short_name": "CA", "types": ["administrative_area_level_1", "political"] }, { "long_name": "United States", "short_name": "US", "types": ["country", "political"] }, { "long_name": "94043", "short_name": "94043", "types": ["postal_code"] } ], "formatted_address": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA", "geometry": { "location": { "lat": 37.4224764, "lng": -122.0842499 }, "location_type": "ROOFTOP", "viewport": { "northeast": { "lat": 37.4238253802915, "lng": -122.0829009197085 }, "southwest": { "lat": 37.4211274197085, "lng": -122.0855988802915 } } }, "place_id": "ChIJ2eUgeAK6j4ARbn5u_wAGqWA", "types": ["street_address"] } ], "status" : "OK"
}

Step 5: Display the returned address(es) for the user to select

At this stage, we have made a request to Google’s Geocoding API and have gotten our result logged in the console. Now, we have to display the results in a UI for our user. This requires two things:

  1. Create a new function that handles creating HTML elements
  2. Update our getGeolocation function to make the function call

Let’s create the function that would take care of creating the HTML elements and updating the DOM.

populateCard = (geoResults) => { geoResults.map(geoResult => { // first create the input div container const addressCard = document.createElement('div'); // then create the input and label elements const input = document.createElement('input'); const label = document.createElement('label'); // then add materialize classes to the div and input addressCard.classList.add("card"); input.classList.add("with-gap"); // add attributes to them label.setAttribute("for", geoResult.place_id); label.innerHTML = geoResult.formatted_address; input.setAttribute("name", "address"); input.setAttribute("type", "radio"); input.setAttribute("value", geoResult.formatted_address); input.setAttribute("id", geoResult.place_id); addressCard.appendChild(input); addressCard.appendChild(label) return ( // append the created div to the locationsAvailable div locationsAvailable.appendChild(addressCard) ); })
}

In this function, we iterate through our results and create some HTML elements (div , input and a label), append the input and the label to the div and finally append the new div to a parent div (which is locationsAvailable). Once we get the result from our API call, our DOM will be created and displayed to the user.

Next, we update our getGeolocation function to call our populateCard function by replacing the last line of getGeolocation with this:

.then(data => populateCard(data.results));

…which means our updated function should look this:

getGeolocation = (lat, lng) => { const latlng = lat + "," + lng; fetch( `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latlng}&key=${__KEY}` ) .then(res => res.json()) .then(data => populateCard(data.results));
}

At this point, everything should be working fine. Our user clicks a button, gets a location displayed on the map along with a list of addresses that match the current location.

Step 6: Listen for map events and repeat steps 4 and 5

If our user decides to move the map or marker, nothing happens to the UI — new addresses aren’t not displayed and everything remains static. We’ve got to fix this, so let’s make our app dynamic by listening for map events. You can read all about Google Map Events here.

There are three events we want to listen for:

  1. drag: This is fired once the user starts dragging and continues to drag the map
  2. dragend: This is fired once the user stops dragging the map
  3. idle: This is fired once every event has been fired and the map is idle

Quick Question: Why are these events best suited for our app?

Quick Answer: The first two events will make sure that our map marker stays in the center of the map during the drag event while the idle event will make a geocoding request with the new coordinates.

To listen for these events we have to update the showMap function with the following:

Gmap.addListener('drag', function () { Gmarker.setPosition(this.getCenter()); // set marker position to map center
});
Gmap.addListener('dragend', function () { Gmarker.setPosition(this.getCenter()); // set marker position to map center
});
Gmap.addListener('idle', function () { Gmarker.setPosition(this.getCenter()); // set marker position to map center if (Gmarker.getPosition().lat() !== lat || Gmarker.getPosition().lng() !== lng) { setTimeout(() => { updatePosition(this.getCenter().lat(), this.getCenter().lng()); // update position display }, 2000); }
});

As explained above, the first two event listeners simply ensure that the marker remains in the center of our map. Pay closer attention to the idle event listener because that is where the action is.

Once the idle event is fired, the marker goes to the center, then a check is done to find out if the current position of the marker is the same with the lat or lng values received by the displayLocation function. If it is not the same, then we call the updatePosition function after two seconds of idleness.

Having said that, we have to make a few updates to the showMap function. First, on the function header, we have to include more parameters and on the showMap function call. We need to add the new arguments there, too. Our showMap function should look like this:

showMap = (latlng, lat, lng) => { let mapOptions = { center: latlng, zoom: 17 }; Gmap = new google.maps.Map(mapArea, mapOptions); Gmap.addListener('drag', function () { Gmarker.setPosition(this.getCenter()); // set marker position to map center }); Gmap.addListener('dragend', function () { Gmarker.setPosition(this.getCenter()); // set marker position to map center }); Gmap.addListener('idle', function () { Gmarker.setPosition(this.getCenter()); // set marker position to map center if (Gmarker.getPosition().lat() !== lat || Gmarker.getPosition().lng() !== lng) { setTimeout(() => { updatePosition(this.getCenter().lat(), this.getCenter().lng()); // update position display }, 2000); } });
}

And our displayLocation function should look like this:

displayLocation = (position) => { const lat = position.coords.latitude; const lng = position.coords.longitude; const latlng = { lat, lng } showMap(latlng, lat, lng); //passed lat and lng as the new arguments to the function createMarker(latlng); mapArea.style.display = "block"; getGeolocation(lat, lng);
}

Having listened for the map events, let’s repeat Step 4 and Step 5.

We start by writing our updatePosition function. This function will perform only one action for now, which is to pass the new lat and lng values to the getGeolocation function:

updatePosition = (lat, lng) => { getGeolocation(lat, lng);
}

After getting the new position and fetching addresses, our DOM should re-render for the user, right? Well, it doesn’t. And to fix that, we create a function that will force the DOM to re-render:

// check if the container has a child node to force re-render of dom
function removeAddressCards(){ if (locationsAvailable.hasChildNodes()) { while (locationsAvailable.firstChild) { locationsAvailable.removeChild(locationsAvailable.firstChild); } }
}

It checks if the locationsAvailable div has any childNodes, and if it does, it deletes them before creating new address cards. The populateCard function is now updated to this:

populateCard = (geoResults) => { // check if a the container has a child node to force re-render of dom removeAddressCards(); geoResults.map(geoResult => { // first create the input div container const addressCard = document.createElement('div'); // then create the input and label elements const input = document.createElement('input'); const label = document.createElement('label'); // then add materialize classes to the div and input addressCard.classList.add("card"); input.classList.add("with-gap"); // add attributes to them label.setAttribute("for", geoResult.place_id); label.innerHTML = geoResult.formatted_address; input.setAttribute("name", "address"); input.setAttribute("type", "radio"); input.setAttribute("value", geoResult.formatted_address); input.setAttribute("id", geoResult.place_id); addressCard.appendChild(input); addressCard.appendChild(label); return ( locationsAvailable.appendChild(addressCard); ); })
}

We are done and are now able to fully get and display the user’s address!

Step 7: Pre-fill the form with the address data the user selects

The final step is to fill the form with the address the user selects. We need to add a click event listener to the address card and pass the address as argument to the callback function.

Here’s how we add the event listener in the populateCard function:

input.addEventListener('click', () => inputClicked(geoResult));

You should note that the geoResult argument in the above callback is the selected address object from the results array. That said, update the populateCard function to accommodate our new line of code.

The inputClicked function uses a series of if statements to assign values to our form elements. so before working on it, let’s bring our form elements into the equation:

const inputAddress = document.getElementById('address'), inputLocality = document.getElementById('locality'), inputPostalCode = document.getElementById('postal_code'), inputLandmark = document.getElementById('landmark'), inputCity = document.getElementById('city'), inputState = document.getElementById('state');

Having done this, let us now work on pre-filling the form with the address_components in the inputClicked function.

inputClicked = result => { result.address_components.map(component => { const types = component.types if (types.includes('postal_code')) { inputPostalCode.value = component.long_name } if (types.includes('locality')) { inputLocality.value = component.long_name } if (types.includes('administrative_area_level_2')) { inputCity.value = component.long_name } if (types.includes('administrative_area_level_1')) { inputState.value = component.long_name } if (types.includes('point_of_interest')) { inputLandmark.value = component.long_name } }); inputAddress.value = result.formatted_address; // to avoid labels overlapping pre-filled input contents M.updateTextFields(); // removes the address cards from the UI removeAddressCards();
}

The above block of code iterates over the clicked (or selected) address component, checks the types of components and finally assigns them to the input fields if they match.

M.updateTextFields() function is from Materialize and it ensures that the label does not overlap with the input fields values and the removeAddressCards() function removes the address cards from the UI.

With this, we are done with our app and have saved our users lots of typing and headaches! Surely they will thank us for implementing such a hassle free solution.

This whole UX experiment can be seen as a shortcut that will help the user pre-fill the shipping address form. We should clearly state here that the returned addresses isn’t always 100% accurate. But that’s why we allow the address to be manually edited in the UI.

Demo!

See the Pen Prefill Address Form with Geolocation and Google Maps by CSS-Tricks (@css-tricks) on CodePen.

The post Let’s make a form that puts current location to use in a map! appeared first on CSS-Tricks.