2
\$\begingroup\$

If you notice, there are variables of street_address, route, intersection re-used in the entire code base. They are actually from Google Maps geocoder address components.

How can I refactor this one so that I don't have to declare them one by one?

Note that in the geocoder address components, those 3 mentioned above are just part of the 20+ components. I just removed the rest for simplicity sake.

I have tried declaring the address components in a new array, and using for loop, but it gets messy and more complicated.

$(function() { $('#shop_formatted_address').autocomplete({ // This bit uses the geocoder to fetch address values source: function(request, response) { geocoder.geocode( {'address': request.term }, function(results, status) { response($.map(results, function(item) { // Get address_components for (var i = 0; i < item.address_components.length; i++) { var addr = item.address_components[i]; var get_street_address, get_route, get_intersection; if (addr.types[0] == 'street_address') get_street_address = addr.long_name; if (addr.types[0] == 'route') get_route = addr.long_name; if (addr.types[0] == 'intersection') get_intersection = addr.long_name; } return { label: item.formatted_address, value: item.formatted_address, lat: item.geometry.location.lat(), lng: item.geometry.location.lng(), street_address: get_street_address, route: get_route, intersection: get_intersection } })); }) }, // This bit is executed upon selection of an address select: function(event, ui) { clearValue(); // Get values $('#shop_lat').val(ui.item.lat); $('#shop_lng').val(ui.item.lng); $('#shop_street_address').val(ui.item.street_address); $('#shop_route').val(ui.item.route); $('#shop_intersection').val(ui.item.intersection); var location = new google.maps.LatLng(ui.item.lat, ui.item.lng); marker.setPosition(location); map.setCenter(location); }, // Changes the current marker when autocomplete dropdown list is focused focus: function(event, ui) { var location = new google.maps.LatLng(ui.item.lat, ui.item.lng); marker.setPosition(location); map.setCenter(location); } }); }); // Add listener to marker for reverse geocoding google.maps.event.addListener(marker, 'drag', function() { geocoder.geocode({'latLng': marker.getPosition()}, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { clearValue(); if (results[0]) { // Get address_components for (var i = 0; i < results[0].address_components.length; i++) { var addr = results[0].address_components[i]; if (addr.types[0] == 'street_address') $('#shop_street_address').val(addr.long_name); if (addr.types[0] == 'route') $('#shop_route').val(addr.long_name); if (addr.types[0] == 'intersection') $('#shop_intersection').val(addr.long_name); } $('#shop_formatted_address').val(results[0].formatted_address); $('#shop_lat').val(marker.getPosition().lat()); $('#shop_lng').val(marker.getPosition().lng()); } else { alert('No results found. Please revise the address or adjust the map marker.'); } } }); }); 
\$\endgroup\$
3
  • 1
    \$\begingroup\$ I was about to write up an answer, but then I noticed that inside the $.map(results, ... you're declaring these variables var get_street_address, get_route, get_intersection; but you're overwriting them in the for loop (assuming the loop runs more than once) so when the values are used in the returned object, you're only getting the latest values. Are you certain that's what you want? \$\endgroup\$ Commented Oct 3, 2012 at 15:54
  • \$\begingroup\$ Actually I could have put it outside the loop. \$\endgroup\$ Commented Oct 3, 2012 at 23:23
  • \$\begingroup\$ Whether the declaration is inside or outside the loop won't make a difference. Variables are scoped to the function they're in. Either way, you're just overwriting the values in each iteration, and returning whatever the values were after the last iteration. To use all the values, you'd need to assign some sort of collection type to each variable, and add items to the collections in each iteration. \$\endgroup\$ Commented Oct 3, 2012 at 23:25

2 Answers 2

1
\$\begingroup\$

You could refactor this using a few arrays to avoid as much typing, but I think readability is paramount, and I don't see many ways for it to be more readable than what you have submitted.

You could try using filter instead, but it will be over 3 times slower (most of the time this won't matter), (N times slower where N is the number of components):

 function getValueOfAddressType(values, addressType) { var candidates = $.filter(values, function(i) { return values[i].types[0] === addressType; }); return candidates[0].long_name; } $('#shop_formatted_address').autocomplete({ This bit uses the geocoder to fetch address values source: function(request, response) { geocoder.geocode( {'address': request.term }, function(results, status) { response($.map(results, function(item) { return { label: item.formatted_address, value: item.formatted_address, lat: item.geometry.location.lat(), lng: item.geometry.location.lng(), street_address: getValueOfAddressType(item.address_components, 'street_address'), route: getValueOfAddressType(item.address_components, 'route'), intersection: getValueOfAddressType(item.address_components, 'intersection') } 
\$\endgroup\$
1
  • \$\begingroup\$ You are one crazy guy! I will use your approach. Thanks. \$\endgroup\$ Commented Oct 5, 2012 at 8:34
1
\$\begingroup\$

Got a little carried away, but here's my take. Haven't been able to test it though, so consider it more as a draft of a different approach, than a drop-in replacement (although if it just works, that's cool too).

Basically, I've DRY'ed the code as much as I could, breaking the logic into several functions, so the final event handlers are as straightforward as possible. I also ran with the fact that you've ID'ed your elements with the names of the values you want them to display, just prefixed with shop_.

$(function () { // Get all the elements once and for all var elements = (function () { var elements = {}; $.each(["lat", "lng", "street_address", "route", "intersection", "formatted_address"], function (id) { elements[id] = $("#shop_" + id); }); return elements; }()); // Generic result-extraction function // Takes an array of results, and an array of the address component // types it should collect function extract(results, types) { // Internal function to extract the address components function extractAddressComponents(result) { var i, l, address, type, extracted = {}; for( i = 0, l = result.address_components.length ; i < l ; i++ ) { address = result.address_components[i]; type = address.types[0]; // Uncomment this next line, if you want to avoid what user1689607 mentions in the comments // if( extracted[type] ) { continue } if( types.indexOf(type) !== -1 ) { extracted[type] = address.long_name; } } return extracted; } return $.map(results, function (result) { $.extend({ label: result.formatted_address, value: result.formatted_address, lat: result.geometry.location.lat(), lng: result.geometry.location.lng(), latlng: result.geometry.location, formatted_address: result.formatted_address }, extractAddressComponents(result)); }); } // Function to set the values of the elements function populate(obj) { var id; for( id in elements ) { if( !elements.hasOwnProperty(id) ) { continue; } if( obj.hasOwnProperty(id) ) { elements[id].val(obj[id]); } else { elements[id].val(""); } } } // Center the map and marker function center(latlng) { if( latlng ) { marker.setPosition(latlng); map.setCenter(latlng); } } // A little convenience wrapping of the geocoding functions var geocode = (function () { function geocode(query, callback) { geocoder.geocode(query, function (results, status) { if( status === google.maps.GeocoderStatus.OK ) { callback(extract(results, ["street_address", "route", "intersection"])); } else { callback([]); } }); } return { address: function (address, callback) { geocode({address: address}, callback); }, reverse: function (latlng, callback) { geocode({latLng: latlng}, callback); } }; }()); // Hook it all up here elements.formatted_address.autocomplete({ source: function(request, response) { response(geocode.address(request.term)); }, select: function (event, ui) { clearValue(); // Don't know what this one does, but I left it in delete ui.item.formatted_address; populate(ui.item); center(ui.item.latlng); }, focus: function (event, ui) { center(ui.item.latlng); } }); google.maps.event.addListener(marker, 'drag', function() { var latlng = marker.getPosition(), results = geocode.reverse(latlng), result; clearValue(); // Don't know what this one does, but I left it in if( results.length ) { result = results[0]; $.extend(result, { lat: latlng.lat(), lng: latlng.lng() }); populate(result); } else { alert('No results found. Please revise the address or adjust the map marker.'); } }); }); 

In the end there's more code, but hopefully it'll seem cleaner, and be more maintainable. And there's very, very little repetition, which I believe is what you originally asked for (as I said, I got carried away).

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.