I re-factored my code to be more OO.
Does it have any more room for improvement (naming, readability, oop, etc.)?
I'm importing Gmap.js
in the head tags and the main.js
just before the closing body tag of the HTML document.
Gmap.js
'use strict';
function Gmap(element, options) {
if (!(typeof window.google === 'object' && typeof window.google.maps === 'object')) {
throw Error('The Google Maps JavaScript API v3 library is required.');
}
this.googleMap = new google.maps.Map(element, options), //TODO: make private
this.currentLocation = options.center,
this.markers = []; //TODO: make private
}
Gmap.prototype = {
addMarker: function (location, animateDrop, bounceOnClick) {
animateDrop = (typeof animateDrop === 'undefined') ? true : animateDrop;
bounceOnClick = (typeof bounceOnClick === 'undefined') ? true : bounceOnClick;
var marker = new google.maps.Marker({
map: this.googleMap,
position: location
});
if (animateDrop) {
marker.setAnimation(google.maps.Animation.DROP);
}
if (bounceOnClick) {
google.maps.event.addListener(marker, 'click', function () {
if (marker.getAnimation() !== null) {
marker.setAnimation(null);
} else {
marker.setAnimation(google.maps.Animation.BOUNCE);
}
});
}
this.markers.push(marker);
},
deleteAllMarkers: function () {
for (var i = 0; i < this.markers.length; i++) {
this.markers[i].setMap(null);
}
},
getCenter: function () {
return this.googleMap.getCenter();
},
setCenter: function (latLng) {
this.googleMap.setCenter(latLng);
},
fitBounds: function (latLngBounds) {
this.googleMap.fitBounds(latLngBounds);
},
triggerEvent: function (event) {
google.maps.event.trigger(this.googleMap, event);
}
}
Gmap.geocode = function (geocodeRequest, callback) {
var googleGeocoder = new google.maps.Geocoder();
googleGeocoder.geocode(geocodeRequest, function (results, status) {
callback(results, status);
});
};
Gmap.geocodeStatus = {
OK: google.maps.GeocoderStatus.OK,
ZERO_RESULTS: google.maps.GeocoderStatus.ZERO_RESULTS,
OVER_QUERY_LIMIT: google.maps.GeocoderStatus.OVER_QUERY_LIMIT,
REQUEST_DENIED: google.maps.GeocoderStatus.REQUEST_DENIED,
UNKNOWN_ERROR: google.maps.GeocoderStatus.UNKNOWN_ERROR
};
main.js
'use strict';
(function (window, document, Gmap) {
var MAP_CANVAS_ID = 'map-canvas',
ADDRESS_INPUT_ID = 'address-input',
SEARCH_BUTTON_ID = 'search-button';
var gMap,
mapCanvas = document.getElementById(MAP_CANVAS_ID),
addressInput = document.getElementById(ADDRESS_INPUT_ID);
if (!mapCanvas.hasAttribute('data-default-address')) {
throw new Error('The default address attribute must be present and not empty.');
}
if (!mapCanvas.getAttribute('data-default-address').trim()) {
throw new Error('The default address attribute must not be empty.');
}
Gmap.geocode({ 'address': mapCanvas.getAttribute('data-default-address') }, function (results, status) {
if (status !== Gmap.geocodeStatus.OK) {
if (status === Gmap.geocodeStatus.ZERO_RESULTS) {
throw new Error('The address could not be located.');
}
throw new Error('Geocode was unsuccessful: ' + status);
}
gMap = new Gmap(mapCanvas, {
// required
center: results[0].geometry.location,
zoom: 10,
// disable direct GUI interaction
disableDefaultUI: true,
navigationControl: false,
mapTypeControl: false,
scaleControl: false,
scrollwheel: false,
draggable: false,
zoomControl: false,
disableDoubleClickZoom: true,
suppressInfoWindows: true
});
addressInput.value = results[0].formatted_address;
gMap.addMarker(results[0].geometry.location);
});
// center map responsively
window.addEventListener('resize', function () {
var center = gMap.getCenter();
gMap.triggerEvent('resize');
gMap.setCenter(center);
});
addressInput.onkeydown = function (e) {
if (e.keyCode === 13) {
addressInput.blur();
processAddressInput();
}
};
document.getElementById(SEARCH_BUTTON_ID).onclick = function () {
processAddressInput();
};
function processAddressInput() {
Gmap.geocode({ 'address': addressInput.value }, function (results, status) {
if (status !== Gmap.geocodeStatus.OK) {
if (status === Gmap.geocodeStatus.ZERO_RESULTS) {
return;
}
throw new Error('Geocode was unsuccessful: ' + status);
}
if (results[0].geometry.location.equals(gMap.currentLocation)) {
addressInput.value = results[0].formatted_address;
return;
}
gMap.deleteAllMarkers();
gMap.fitBounds(results[0].geometry.viewport);
gMap.setCenter(results[0].geometry.location);
gMap.addMarker(results[0].geometry.location);
gMap.currentLocation = results[0].geometry.location;
addressInput.value = results[0].formatted_address;
});
}
}(window, document, Gmap));
1 Answer 1
Some quick thoughts:
Gmap
constructor: I'd prefer semicolons instead of commas when you're setting properties onthis
.addMarker
: I think movinganimateDrop
&bounceOnClick
into anoptions
object would be good, especially if you ever add additional options. In ES6, the syntax makes this all the more apparent:function addMarker(location, {animateDrop = true, bounceOnClick = true} = {}) { ... }
typeof animateDrop === 'undefined'
is fine, but you can also useanimateDrop === undefined
as there's no risk thatanimateDrop
isn't declared.deleteMarkers
: Cache the length:for (var i=0, l=this.markers.length; i<l; i++) { ... }
Consider using promises for
geocode
.If you're just aliasing all the status constants, why not this:
Gmap.geocodeStatus = google.maps.GeocoderStatus;
'address'
as an object key doesn't need to be quoted.
-
\$\begingroup\$ Can you elaborate point 5? Promises? \$\endgroup\$Kid Diamond– Kid Diamond2015年03月11日 20:47:33 +00:00Commented Mar 11, 2015 at 20:47
-
\$\begingroup\$ Although the callback pattern works, it's easy to get into callback hell where callbacks are nested within callbacks, etc. Promises alleviate this by allowing you to write
geocode({address:...}).then(function(results){}).catch(function(err){});
Come ES7, this becomes even nicer with theawait
keyword:let result = await geocode({address:....});
. Granted; you're not in danger of callback hell yet, but thought I would bring it up, especially with the future direction of ES. \$\endgroup\$Kerri Shotts– Kerri Shotts2015年03月11日 20:59:50 +00:00Commented Mar 11, 2015 at 20:59
Explore related questions
See similar questions with these tags.