How to implement auto-complete of city information using city or zip code.

  • admin
  • Tag 1
    Tag 2
    Tag 3
    Tag 4

    One of the best ways that you can ensure people will use and share your site, is be ensuring you provide an awesome user-friendly experience. This means making your site as convenient and functional as possible. Many things factor into this experience, such as a good clean layout and an intuitive interface. Other little things, such as auto-completion and search suggestions also round things out and make for an excellent experience. Today, I want to write about how to implement auto-completion, specifically for address lookup. In this article, I’ll be focusing on searching within the US, but this can easily be tweaked to branch out to other countries.

    The goal of this project was a simple one: to provide address suggestions as users typed in either a city name, state or zip code. To achieve this goal, we needed 2 things:

    1) An auto-completion script, that does a lookup for address information as the user types. For this project, we want the function to trigger after the user types a minimum of 3 characters, to ensure we’re not making a lot of unnecessary calls and grabbing too much data too soon.

    2) A method of taking user input, and grabbing relevant data to return based on that data. This can done via a database call, a prefabricated set of data, and ajax call or any other method you can think of. In this case, we’ll be utilizing geonames free API to gather data.

    So first off, we start with developing the auto-complete. There are tonnes of pre-rolled options out there, including a package in the jQuery library. However in this instance, I didn’t want to have to use the extra libraries to support jQuery’s version of auto-complete, so I utilized a script I found on GitHub.

    For the sake of the article, here’s a copy of the auto-complete base script, grabbed from Pixabay:

    
    /*
    	jQuery autoComplete v1.0.7
        Copyright (c) 2014 Simon Steinberger / Pixabay
        GitHub: https://github.com/Pixabay/jQuery-autoComplete
    	License: http://www.opensource.org/licenses/mit-license.php
    */
    
    (function($){
        $.fn.autoComplete = function(options){
            var o = $.extend({}, $.fn.autoComplete.defaults, options);
    
            // public methods
            if (typeof options == 'string') {
                this.each(function(){
                    var that = $(this);
                    if (options == 'destroy') {
                        $(window).off('resize.autocomplete', that.updateSC);
                        that.off('blur.autocomplete focus.autocomplete keydown.autocomplete keyup.autocomplete');
                        if (that.data('autocomplete'))
                            that.attr('autocomplete', that.data('autocomplete'));
                        else
                            that.removeAttr('autocomplete');
                        $(that.data('sc')).remove();
                        that.removeData('sc').removeData('autocomplete');
                    }
                });
                return this;
            }
    
            return this.each(function(){
                var that = $(this);
                // sc = 'suggestions container'
                that.sc = $('<div class="autocomplete-suggestions '+o.menuClass+'"></div>');
                that.data('sc', that.sc).data('autocomplete', that.attr('autocomplete'));
                that.attr('autocomplete', 'off');
                that.cache = {};
                that.last_val = '';
    
                that.updateSC = function(resize, next){
                    that.sc.css({
                        top: that.offset().top + that.outerHeight(),
                        left: that.offset().left,
                        width: that.outerWidth()
                    });
                    if (!resize) {
                        that.sc.show();
                        if (!that.sc.maxHeight) that.sc.maxHeight = parseInt(that.sc.css('max-height'));
                        if (!that.sc.suggestionHeight) that.sc.suggestionHeight = $('.autocomplete-suggestion', that.sc).first().outerHeight();
                        if (that.sc.suggestionHeight)
                            if (!next) that.sc.scrollTop(0);
                            else {
                                var scrTop = that.sc.scrollTop(), selTop = next.offset().top - that.sc.offset().top;
                                if (selTop + that.sc.suggestionHeight - that.sc.maxHeight > 0)
                                    that.sc.scrollTop(selTop + that.sc.suggestionHeight + scrTop - that.sc.maxHeight);
                                else if (selTop < 0)
                                    that.sc.scrollTop(selTop + scrTop);
                            }
                    }
                }
                $(window).on('resize.autocomplete', that.updateSC);
    
                that.sc.appendTo('body');
    
                that.sc.on('mouseleave', '.autocomplete-suggestion', function (){
                    $('.autocomplete-suggestion.selected').removeClass('selected');
                });
    
                that.sc.on('mouseenter', '.autocomplete-suggestion', function (){
                    $('.autocomplete-suggestion.selected').removeClass('selected');
                    $(this).addClass('selected');
                });
    
                that.sc.on('mousedown click', '.autocomplete-suggestion', function (e){
                    var item = $(this), v = item.data('val');
                    if (v || item.hasClass('autocomplete-suggestion')) { // else outside click
                        that.val(v);
                        o.onSelect(e, v, item);
                        that.sc.hide();
                    }
                    return false;
                });
    
                that.on('blur.autocomplete', function(){
                    try { over_sb = $('.autocomplete-suggestions:hover').length; } catch(e){ over_sb = 0; } // IE7 fix :hover
                    if (!over_sb) {
                        that.last_val = that.val();
                        that.sc.hide();
                        setTimeout(function(){ that.sc.hide(); }, 350); // hide suggestions on fast input
                    } else if (!that.is(':focus')) setTimeout(function(){ that.focus(); }, 20);
                });
    
                if (!o.minChars) that.on('focus.autocomplete', function(){ that.last_val = '\n'; that.trigger('keyup.autocomplete'); });
    
                function suggest(data){
                    var val = that.val();
                    that.cache[val] = data;
                    if (data.length && val.length >= o.minChars) {
                        var s = '';
                        for (var i=0;i<data.length;i++) s += o.renderItem(data[i], val);
                        that.sc.html(s);
                        that.updateSC(0);
                    }
                    else
                        that.sc.hide();
                }
    
                that.on('keydown.autocomplete', function(e){
                    // down (40), up (38)
                    if ((e.which == 40 || e.which == 38) && that.sc.html()) {
                        var next, sel = $('.autocomplete-suggestion.selected', that.sc);
                        if (!sel.length) {
                            next = (e.which == 40) ? $('.autocomplete-suggestion', that.sc).first() : $('.autocomplete-suggestion', that.sc).last();
                            that.val(next.addClass('selected').data('val'));
                        } else {
                            next = (e.which == 40) ? sel.next('.autocomplete-suggestion') : sel.prev('.autocomplete-suggestion');
                            if (next.length) { sel.removeClass('selected'); that.val(next.addClass('selected').data('val')); }
                            else { sel.removeClass('selected'); that.val(that.last_val); next = 0; }
                        }
                        that.updateSC(0, next);
                        return false;
                    }
                    // esc
                    else if (e.which == 27) that.val(that.last_val).sc.hide();
                    // enter or tab
                    else if (e.which == 13 || e.which == 9) {
                        var sel = $('.autocomplete-suggestion.selected', that.sc);
                        if (sel.length && that.sc.is(':visible')) { o.onSelect(e, sel.data('val'), sel); setTimeout(function(){ that.sc.hide(); }, 20); }
                    }
                });
    
                that.on('keyup.autocomplete', function(e){
                    if (!~$.inArray(e.which, [13, 27, 35, 36, 37, 38, 39, 40])) {
                        var val = that.val();
                        if (val.length >= o.minChars) {
                            if (val != that.last_val) {
                                that.last_val = val;
                                clearTimeout(that.timer);
                                if (o.cache) {
                                    if (val in that.cache) { suggest(that.cache[val]); return; }
                                    // no requests if previous suggestions were empty
                                    for (var i=1; i<val.length-o.minChars; i++) {
                                        var part = val.slice(0, val.length-i);
                                        if (part in that.cache && !that.cache[part].length) { suggest([]); return; }
                                    }
                                }
                                that.timer = setTimeout(function(){ o.source(val, suggest) }, o.delay);
                            }
                        } else {
                            that.last_val = val;
                            that.sc.hide();
                        }
                    }
                });
            });
        }
    
        $.fn.autoComplete.defaults = {
            source: 0,
            minChars: 3,
            delay: 150,
            cache: 1,
            menuClass: '',
            renderItem: function (item, search){
                // escape special characters
                search = search.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
                var re = new RegExp("(" + search.split(' ').join('|') + ")", "gi");
                return '<div class="autocomplete-suggestion" data-val="' + item + '">' + item.replace(re, "<b>$1</b>") + '</div>';
            },
            onSelect: function(e, term, item){}
        };
    }(jQuery));
    

    So now that we’ve got our base auto-complete script, we still need to code our own script. We need to write the basic code to bind our autocomplete script to an element and additionally we need to code it so the auto-complete makes the ajax query to geonames. This is what our script looks like:

    
    $('input.select_city').autoComplete({ // binds autocomplete script to input field with class 'select_city'
    		 
        	minChars: 3, // number of characters before autocomplete script triggers. 
    
    	    source: function(term, suggest){ 
                    
                    // The source function is used by the script to populate results to return to the user. It passes 2 parameters. term is the text typed in by the user, which we will
                    // use to populate our ajax call. Suggest is a method/callback used to return results to the autocomplete script. We use that below in our ajax query to return address results                    
                    // to the autocomplete script to display to the user. 
    			
    	
    				var choices = []; // builds our array that we use to push results into. 
    				
    				$.ajax({    // It's vitally important to note that we MUST urlencode the term before passing it to the ajax call, to account for spaces. 
    		                url: "http://api.geonames.org/postalCodeSearchJSON?placename_startsWith="+ encodeURIComponent(term) + "&username=demo&country=US&maxRows=10",
    		                cache: false,
    		                dataType: "json", // geonames allows different formats, such as xml. In this case we want json encoded results. 
    		                type: "GET",
    	                	success: function(result, success) {
    									
    		                	$.each( result.postalCodes, function( key, value ) {
                                                // As we selected json as the data format, geonames returns data in object format. Below we parse the address data, pick out what we want and 
                                                // pass it back to the autocomplete script. 
    
      								choices.push(value['placeName'] + ", " + " " + value['adminCode1']+ " " + value['postalCode']);
      								
    							});
    
    						 	suggest(choices); // as per above, this method returns the parsed address data back to the user and displays it. 
    	                	},
    	                	error: function(result) {
    	                	
    	                	}
    		            });
    
    			
    	    }
    	    
    	});
    
    

    Awesome, with all the scripting done, we just add a little styling like so:

    .autocomplete-suggestions {
        text-align: left; cursor: default; border: 1px solid #ccc; border-top: 0; background: #fff; box-shadow: -1px 1px 3px rgba(0,0,0,.1);
    
        /* core styles should not be changed */
        position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box;
    }
    .autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; }
    .autocomplete-suggestion b { font-weight: normal; color: #1f8dd6; }
    .autocomplete-suggestion.selected { background: #f0f0f0; }
    

    And we’re done. Now with the script and styling loaded, simply add the following to your form:

    <input type='text' name='address' class='select_city' placeholder='enter your address please'>
    

    And we’re done. The end result will look like this:

    Leave a comment

  • (required)