German addresses
- Complete postal area
- Get a list of postal areas matching the input (postcode or city name).
- Complete street
- Given a postal area, get a list of streets matching the specified street name.
- Complete house number
- Retrieve a list of house numbers matching the specified house number.
This example demonstrates a form with autocompletion for a German address. It calls the completePostalArea, completeStreet and completeHouseNumber methods.
Try…
- Berlin, Bismarckplatz 1
- 78266, Dörflinger Str. 2
- Hamburg, Sachsenweg 8g
- Pieschen-Nord/Trachenberge, Bremer Str. 5
- Königswalde, Pöhlbergstr. 6
Please note that this is a demo based on an alternative data set, which is not suitable for use in a production setting or unit tests. In this data set the names of some streets have been shortened. For example, 'Dorfstr.' may be returned as 'Dorfs...[demo]'.
Autocompleted JSON address data:
Code
The following example code can be used as a starting point for your implementation.
HTML
<form> <input type="hidden" class="input-postcode" /> <input type="hidden" class="input-city-name" /> <dl> <dt> <label for="postal-area">Postcode, city or district name</label> </dt> <dd> <input class="input-postal-area" id="postal-area" type="text" placeholder="14193 or Berlin" /> </dd> <dt> <label for="street-name">Street</label> </dt> <dd> <input class="input-street-name" id="street-name" type="text" placeholder="Dorfplatz" /> </dd> <dt> <label for="house-number">House number</label> </dt> <dd> <input class="input-house-number" id="house-number" type="text" placeholder="3" /> </dd> </dl> <address class="autocomplete-result"></address> </form>
Javascript
This example script is based on jQuery and jQuery UI Autocomplete.
Please make sure to change the demo resource URL's (see code comments).
(function () { 'use strict'; var $ = jQuery, // Select what type of house number validation is required, allowed values: strict, number, none completeHouseNumberValidationParameter = 'none', address = { 'postcode': null, 'cityId': null, 'cityName': null, 'streetId': null, 'streetName': null, 'houseNumber': null, 'houseNumberStatus': null }, postcodeFilter = null, // Div containing the autocomplete demo elements container = $('.demo-autocomplete-de'), // Change the following selectors to match your form elements. postcodeInput = container.find('.input-postcode'), cityNameInput = container.find('.input-city-name'), postalAreaInput = container.find('.input-postal-area'), streetNameInput = container.find('.input-street-name'), houseNumberInput = container.find('.input-house-number'), addressElement = container.find('.autocomplete-result'), // Development related elements, should not be used for your form. resetFormInput = container.find('.demo-autocomplete-reset'), addressDataElement = container.find('.autocomplete-data'), // Change the following URL's to your production URL's. // Do not use the demo URL's in your production environment or unit tests. postalAreaUrl = 'demo/de/autocomplete/postal-area/', streetNameUrl = 'demo/de/autocomplete/street/', houseNumberUrl = 'demo/de/autocomplete/house-number/'; $(function () // DOMReady handler. { if (postalAreaInput.length === 0) { return; } autoCompleteAddressParts(); } ); $.widget('custom.completePostalArea', $.ui.autocomplete, { _renderItem: function (ul, item) { var div = $('<div>', {'text': item.cityName, 'class': 'ui-menu-item-wrapper complete-postal-area'}); if (item.matchedName && item.matchedName !== item.cityName) { div.append($('<span>', {'text': item.matchedName})); } else if (item.cityAddition) { div.append($('<span>', {'text': item.cityAddition})); } return $('<li>').append(div).appendTo(ul); } }); var autoCompleteAddressParts = function () { postalAreaInput.completePostalArea({ autoFocus: true, select: updatePostalArea, change: updatePostalArea, source: function (request, responseCallback) { var sourceScope = this; $.getJSON( postalAreaUrl + encodeURIComponent(request.term), '', function (items, status, xhr) { if (typeof items.exception !== 'undefined') { return console.error(items.exception); } // Format matches. for (var i = 0, item; item = items[i]; i++) { items[i].value = items[i].label = item.cityName; if (typeof item.postcode === 'number') { items[i].value = items[i].label = item.postcode + ' ' + item.cityName; } if (typeof item.matchedName === 'string') { items[i].label += ' (' + item.matchedName + ')'; } } responseCallback(items); } ).fail(function (jqXHR) { if (jqXHR.status !== 200) { // Server might have (capacity) issues, stop sending requests to be safe. sourceScope.close(); sourceScope.disable(); } } ); } }).focus( function () { if (address.cityId === null && postalAreaInput.val() !== '') { postalAreaInput.completePostalArea('search'); } } ); $.widget('custom.completeStreet', $.ui.autocomplete, { _renderItem: function (ul, item) { var div = $('<div>', {'text': item.streetName, 'class': 'ui-menu-item-wrapper complete-street'}); if (postcodeFilter === null) { div.append($('<span>', {'text': item.postcode})); } return $('<li>').append(div).appendTo(ul); } }); streetNameInput.completeStreet({ autoFocus: true, select: updateStreetName, change: updateStreetName, source: function (request, responseCallback) { var sourceScope = this; if (address.cityId === null) { return; // Can't continue without cityId value. } $.getJSON( streetNameUrl + address.cityId + '/' + (postcodeFilter || '') + '/' + encodeURIComponent(request.term), '', function (items, status, xhr) { if (typeof items.exception !== 'undefined') { return console.error(items.exception); } // Format matches. for (var i = 0, item; item = items[i]; i++) { items[i].value = items[i].label = item.streetName; } responseCallback(items); } ).fail(function (jqXHR) { if (jqXHR.status !== 200) { // Server might have (capacity) issues, stop sending requests to be safe. sourceScope.close(); sourceScope.disable(); } } ); } }).focus( function () { if (address.cityId === null && postalAreaInput.val() !== '') { postalAreaInput.focus(); } else if (address.streetId === null && streetNameInput.val() !== '') { streetNameInput.completeStreet('search'); } } ); $.widget('custom.completeHouseNumber', $.ui.autocomplete, { _renderItem: function (ul, item) { var div = $('<div>', {'text': item.houseNumber, 'class': 'ui-menu-item-wrapper complete-house-number'}); if (item.postcode !== address.postcode) { // Display postcode if it's not the postcode selected during street completion div.append($('<span>', {'text': item.postcode})); } if (item.status === 'incomplete') { div.append($('<span>', {'text': 'select bus number…', 'class' : 'remark'})); } else if (item.status === 'unknown') { div.append($('<span>', {'text': '(unknown house number)', 'class' : 'remark'})); } return $('<li>').append(div).appendTo(ul); } }); houseNumberInput.completeHouseNumber({ autoFocus: true, select: function (event, ui) { if (ui.item.status === 'incomplete') { // Trigger a new search to complete missing house number parts. window.setTimeout(function () { houseNumberInput.completeHouseNumber('search', ui.item.houseNumber); }, 200); } updateHouseNumber(event, ui); }, change: updateHouseNumber, source: function (request, responseCallback) { var sourceScope = this; if (address.streetId === null || address.postcode === null) { return; } $.getJSON( houseNumberUrl + address.cityId + '/' + address.streetId + '/' + address.postcode + '/' + encodeURIComponent(completeHouseNumberValidationParameter) + '/' + encodeURIComponent(request.term), '', function (items, status, xhr) { if (typeof items.exception !== 'undefined') { return console.error(items.exception); } // Format matches. for (var i = 0, item; item = items[i]; i++) { items[i].label = items[i].value = item.houseNumber; } responseCallback(items); } ).fail(function (jqXHR) { if (jqXHR.status !== 200) { // Server might have (capacity) issues, stop sending requests to be safe. sourceScope.close(); sourceScope.disable(); } } ); } }).focus( function () { if (address.streetId === null && streetNameInput.val() !== '') { streetNameInput.focus(); } else if (address.houseNumber === null && houseNumberInput.val() !== '') { houseNumberInput.completeHouseNumber('search'); } } ); resetFormInput.click( function () { // Reset the street and house number parts, the rest will be reset by updatePostalArea address.houseNumber = null; address.houseNumberStatus = null; address.streetName = null; address.streetId = null; postalAreaInput.removeClass('input-error').val('').focus(); streetNameInput.removeClass('input-error').val(''); houseNumberInput.removeClass('input-error').val(''); updatePostalArea(); } ); renderAddressData(); }; /** * Update input element validity. * * @param {Element} inputElement AutoComplete input element to update validity for. * @param {Boolean} isValid If true mark "input-valid", if false mark "input-error" if input is not empty. */ var updateValidity = function (inputElement, isValid) { if (isValid) { inputElement.removeClass('input-error').addClass('input-valid'); } else { inputElement.removeClass('input-valid'); inputElement.toggleClass('input-error', inputElement.val() !== ''); } }; /** * Update PostalArea form and internal address state using selected autocomplete item. * * @param {Object} event Event that triggered update. * @param {Object|null} ui Newly selected ui item, or null if no item was selected. */ var updatePostalArea = function (event, ui) { var item = ui ? ui.item : null; if (item === null) { address.cityName = null; address.cityId = null; address.postcode = null; updateInputValues(true); updateValidity(postalAreaInput, false); updateStreetName(event, null); } else if ( address.cityName !== item.cityName || address.cityId !== item.cityId || postcodeFilter !== (item.postcode || null) ) { address.cityName = item.cityName; address.cityId = item.cityId; address.postcode = item.postcode || null; updateInputValues(true); updateValidity(postalAreaInput, true); updateStreetName(event, null); } }; /** * Update StreetName form and internal address state using selected autocomplete item. * * @param {Object} event Event that triggered update. * @param {Object|null} ui Newly selected ui item, or null if no item was selected. */ var updateStreetName = function (event, ui) { var item = ui ? ui.item : null; if (item === null) { address.streetName = null; address.streetId = null; updateValidity(streetNameInput, false); updateHouseNumber(event, null); } else if ( address.cityName !== item.cityName || address.postcode !== item.postcode || address.streetName !== item.streetName || address.streetId !== item.streetId ) { address.cityName = item.cityName; address.postcode = item.postcode; address.streetName = item.streetName; address.streetId = item.streetId; updateInputValues(); updateValidity(streetNameInput, true); updateHouseNumber(event, null); } }; /** * Update HouseNumber form and internal address state using selected autocomplete item. * * @param {Object} event Event that triggered update. * @param {Object|null} ui Newly selected ui item, or null if no item was selected. */ var updateHouseNumber = function (event, ui) { var item = ui ? ui.item : null; if (item === null) { address.houseNumber = null; address.houseNumberStatus = null; updateValidity(houseNumberInput, false); } else { address.postcode = item.postcode; address.houseNumber = item.houseNumber; address.houseNumberStatus = item.status; updateInputValues(); updateValidity(houseNumberInput, true); } renderAddress(); renderAddressData(); }; /** * Update form input values with validated address information, keep unvalidated autocomplete input fields as-is. * * @param {boolean} updatePostcodeFilter Always update postalArea postcode filter if true. Defaults to false. */ var updateInputValues = function (updatePostcodeFilter) { if (updatePostcodeFilter || postcodeFilter !== null) { postcodeFilter = address.postcode; } if (postcodeFilter !== null && address.cityName !== null) { postalAreaInput.val(postcodeFilter + ' ' + address.cityName); } else if (address.cityName !== null) { postalAreaInput.val(address.cityName); } if (address.streetName !== null) { streetNameInput.val(address.streetName); } if (address.houseNumber !== null) { houseNumberInput.val(address.houseNumber); } cityNameInput.val(address.cityName || ''); postcodeInput.val(address.postcode || ''); }; var renderAddress = function () { addressElement.empty(); if (address.postcode === null || address.cityName === null || address.streetName === null) { return; } addressElement.append(address.streetName); if (address.houseNumber !== '') { addressElement.append(' ', address.houseNumber); } addressElement.append($('<br>'), address.postcode, ' ', address.cityName); }; var renderAddressData = function () { addressDataElement.text(JSON.stringify(address, null, 4)); hljs.highlightBlock(addressDataElement.get(0)); } })();
CSS
The jQuery UI Autocomplete widget requires some functional CSS to work. Use one of jQuery UI's themes or create your own.
.ui-helper-hidden-accessible { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } .ui-widget { box-shadow: 0 10px 15px rgba(0, 0, 0, .15); } .ui-widget-content { background-color: #fff; } .ui-state-active { background-color: #f0f6fa; } .ui-menu { padding: 0; z-index: 99; } .ui-menu-item { cursor: pointer; } .ui-menu-item-wrapper { padding: 8px 12px; line-height: 1; } .ui-autocomplete { position: absolute; max-height: 300px; overflow-y: auto; overflow-x: hidden; }