Hackviking He killed Chuck Norris, he ruled dancing so he took up a new hobby…

18Mar/130

Google Maps searchbox with autocomplete

I was sitting trying to do some mods on a Queclink GL200 GPS transmitter for the MPS project. After many hours of no luck at all I gave up for the day. If anyone have some input on that please contact me!

So i started messing around with the Google Maps API demo that I made for them instead. Adding some auto complete to the search form instead. I thought I would share what I managed to do. The challange is to do a mach up of Google Maps API and jQuery to get it to work good.

The trick is to attach the jQuery handler to the object. Why?
You have to create the search box dynamically in order to push it on top of the Google Maps canvas.

Entire demo can be found here: http://jsfiddle.net/kallsbo/XgsC6/

First initialize the map and all it's settings:

var map;
var addressField;
var geocoder;

$(document).ready(function () {
    // Define map options
    var mapOptions = {
        center: new google.maps.LatLng(57.698254, 12.037024),
        zoom: 16,
        mapTypeId: google.maps.MapTypeId.HYBRID,
        panControl: true,
        zoomControl: true,
        mapTypeControl: true,
        scaleControl: true,
        streetViewControl: true,
        overviewMapControl: true
    };

    // Define map
    map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);

    // Define Gecoder
    geocoder = new google.maps.Geocoder();

    // Init searchbox
    initSearchBox();
});

function initSearchBox() {
    // Add searchbox
    var searchControlDiv = document.createElement('div');
    var searchControl = new SearchControl(searchControlDiv, map);

    searchControlDiv.index = 1;
    map.controls[google.maps.ControlPosition.TOP_CENTER].push(searchControlDiv);
}

As you can see we initialize the search box control and put it in a div at the top of the canvas. This is how we create the control and it's auto complete function:

function SearchControl(controlDiv, map) {
    // Set CSS styles for the DIV containing the control
    // Setting padding to 5 px will offset the control
    // from the edge of the map.
    controlDiv.style.padding = '5px';

    // Set CSS for the control border.
    var controlUI = document.createElement('div');
    controlUI.style.backgroundColor = 'white';
    controlUI.style.borderStyle = 'solid';
    controlUI.style.borderWidth = '2px';
    controlUI.style.cursor = 'pointer';
    controlUI.style.textAlign = 'center';
    controlUI.title = 'Sök ex: gatunamn, stad';
    controlDiv.appendChild(controlUI);

    // Create the search box
    var controlSearchBox = document.createElement('input');
    controlSearchBox.id = 'search_address';
    controlSearchBox.size = '80';
    controlSearchBox.type = 'text';
}

So when you have gotten this far in the code you have the search input box as a VAR. Now whe can use that VAR to attache the function for the auto complete to it:

    // Initiat autocomplete
    $(function () {
        $(controlSearchBox).autocomplete({
            source: function (request, response) {

                if (geocoder == null) {
                    geocoder = new google.maps.Geocoder();
                }

                geocoder.geocode({
                    'address': request.term
                }, function (results, status) {
                    if (status == google.maps.GeocoderStatus.OK) {
                        var searchLoc = results[0].geometry.location;
                        var lat = results[0].geometry.location.lat();
                        var lng = results[0].geometry.location.lng();
                        var latlng = new google.maps.LatLng(lat, lng);
                        var bounds = results[0].geometry.bounds;

                        geocoder.geocode({
                            'latLng': latlng
                        }, function (results1, status1) {
                            if (status1 == google.maps.GeocoderStatus.OK) {
                                if (results1[1]) {
                                    response($.map(results1, function (loc) {
                                        return {
                                            label: loc.formatted_address,
                                            value: loc.formatted_address,
                                            bounds: loc.geometry.bounds
                                        }
                                    }));
                                }
                            }
                        });
                    }
                });
            },
            select: function (event, ui) {
                var pos = ui.item.position;
                var lct = ui.item.locType;
                var bounds = ui.item.bounds;

                if (bounds) {
                    map.fitBounds(bounds);
                }
            }
        });
    });

Then finish up creating the object and push it the the Google Maps Canvas as a custom control:

    // Set CSS for the control interior.
    var controlText = document.createElement('div');
    controlText.style.fontFamily = 'Arial,sans-serif';
    controlText.style.fontSize = '12px';
    controlText.style.paddingLeft = '4px';
    controlText.style.paddingRight = '4px';
    controlText.appendChild(controlSearchBox);
    controlUI.appendChild(controlText);
}
14Mar/110

jQuery UI: Samples has redundant code

I have messed around a lot with .Net MVC, jQuery and jQuery UI for some time now. I just discovered that one of the examples includes redundant code. I don't know if all of them do but you need to watch your self. I have seen a few sites that run the jQuery examples straight up and they may have problems with this. It's really not that big of a deal but redundant code can become a comp ability issue and it's always bad practice. You also send more data to each client and you waste bandwidth.

The example I found was the tabs control with the ability to add and remove tabs. This is the code they published:

	#dialog label, #dialog input { display:block; }
	#dialog label { margin-top: 0.5em; }
	#dialog input, #dialog textarea { width: 95%; }
	#tabs { margin-top: 1em; }
	#tabs li .ui-icon-close { float: left; margin: 0.4em 0.2em 0 0; cursor: pointer; }
	#add_tab { cursor: pointer; }
	</style>
	<script>
	$(function() {
		var $tab_title_input = $( "#tab_title"),
			$tab_content_input = $( "#tab_content" );
		var tab_counter = 2;

		// tabs init with a custom tab template and an "add" callback filling in the content
		var $tabs = $( "#tabs").tabs({
			tabTemplate: "<li><a href='#{href}'>#{label}</a> <span class='ui-icon ui-icon-close'>Remove Tab</span></li>",
			add: function( event, ui ) {
				var tab_content = $tab_content_input.val() || "Tab " + tab_counter + " content.";
				$( ui.panel ).append( "<p>" + tab_content + "</p>" );
			}
		});

		// modal dialog init: custom buttons and a "close" callback reseting the form inside
		var $dialog = $( "#dialog" ).dialog({
			autoOpen: false,
			modal: true,
			buttons: {
				Add: function() {
					addTab();
					$( this ).dialog( "close" );
				},
				Cancel: function() {
					$( this ).dialog( "close" );
				}
			},
			open: function() {
				$tab_title_input.focus();
			},
			close: function() {
				$form[ 0 ].reset();
			}
		});

		// addTab form: calls addTab function on submit and closes the dialog
		var $form = $( "form", $dialog ).submit(function() {
			addTab();
			$dialog.dialog( "close" );
			return false;
		});

		// actual addTab function: adds new tab using the title input from the form above
		function addTab() {
			var tab_title = $tab_title_input.val() || "Tab " + tab_counter;
			$tabs.tabs( "add", "#tabs-" + tab_counter, tab_title );
			tab_counter++;
		}

		// addTab button: just opens the dialog
		$( "#add_tab" )
			.button()
			.click(function() {
				$dialog.dialog( "open" );
			});

		// close icon: removing the tab on click
		// note: closable tabs gonna be an option in the future - see http://dev.jqueryui.com/ticket/3924
		$( "#tabs span.ui-icon-close" ).live( "click", function() {
			var index = $( "li", $tabs ).index( $( this ).parent() );
			$tabs.tabs( "remove", index );
		});
	});
	</script>

<div>
	<div id="dialog" title="Tab data">
		<form>
			<fieldset>
				<label for="tab_title">Title</label>
				<input type="text" name="tab_title" id="tab_title" value="" />
				<label for="tab_content">Content</label>
				<textarea name="tab_content" id="tab_content"></textarea>
			</fieldset>
		</form>
	</div>

	<button id="add_tab">Add Tab</button>

	<div id="tabs">
		<ul>
			<li><a href="#tabs-1">Nunc tincidunt</a> <span>Remove Tab</span></li>
		</ul>
		<div id="tabs-1">
			<p>Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.</p>
		</div>
	</div>

</div><!-- End demo -->

The problem is that the register the submit action for the form twice. This code initializes the model popup including the form. I have highlighted the peace of code thats interesting here.

		var $dialog = $( "#dialog" ).dialog({
			autoOpen: false,
			modal: true,
			buttons: {
				<span style="color: #ff0000;">Add: function() {
					addTab();
					$( this ).dialog( "close" );
				},</span>
				Cancel: function() {
					$( this ).dialog( "close" );
				}
			},
			open: function() {
				$tab_title_input.focus();
			},
			close: function() {
				$form[ 0 ].reset();
			}
		});

That peace of code does the exact same thing as this peace of code:

 var $form = $( "form", $dialog ).submit(function() {
			addTab();
			$dialog.dialog( "close" );
			return false;
		});

The first one is a must to initialize the module popup, this is just redundant. I have played around a lot with this and I can't find any impact in functionality. If you know any or find anything please give me a comment back, but I can't see that there would be any issues. Then in the end I'm rather corrected and admitting that I'm wrong then running code that isn't OK.