API

Belgian addresses

Complete postal area
Get a list of postal areas matching the input (postcode or municipality 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.

Example address form implementation

This example demonstrates a form with autocompletion for a Belgian address. It calls the completePostalArea, completeStreet and completeHouseNumber methods.

Try…

  • Antwerpen, Sleeckxstraat 22
  • 8840, 1e Jagersstraat 8
  • Woluwe-Saint-Lambert, Rue Théodore De Cuyper 2b bte 6
  • De Haan, Wenduinesteenweg 150/0203
  • De Haan, Wenduinesteenweg 8B bus 201

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 and municipalities have been shortened. For example, 'Europalaan' may be returned as 'Europa [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-municipality-name" />

	<dl>
		<dt>
			<label for="postal-area">Postcode or municipality</label>
		</dt>
		<dd>
			<input class="input-postal-area" id="postal-area" type="text" placeholder="2610 or Antwerpen" />
		</dd>
		<dt>
			<label for="street-name">Street</label>
		</dt>
		<dd>
			<input class="input-street-name" id="street-name" type="text" placeholder="Europalaan" />
		</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, street, number, none
		completeHouseNumberValidationParameter = 'none',

		address = {
			'postcode': null,
			'municipalityNisCode': null,
			'municipalityName': null,
			'streetId': null,
			'streetName': null,
			'language': null,
			'houseNumber': null,
			'houseNumberStatus': null
		},
		postcodeFilter = null,

		// Change the following selectors to match your form elements.
		postcodeInput = $('.input-postcode'),
		municipalityInput = $('.input-municipality-name'),
		postalAreaInput = $('.input-postal-area'),
		streetNameInput = $('.input-street-name'),
		houseNumberInput = $('.input-house-number'),
		addressElement = $('.autocomplete-result'),

		// Development related elements, should not be used for your form.
		resetFormInput = $('.input-autocomplete-reset'),
		addressDataElement = $('.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/be/autocomplete/postal-area/',
		streetNameUrl = 'demo/be/autocomplete/street/',
		houseNumberUrl = 'demo/be/autocomplete/house-number/';

	$(function () // DOMReady handler.
		{
			autoCompleteAddressParts();
		}
	);

	var autoCompleteAddressParts = function ()
	{
		postalAreaInput.autocomplete({
			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.municipalityName;

							if (typeof item.postcode === 'number')
							{
								items[i].value = items[i].label = item.postcode + ' ' + item.municipalityName;
							}

							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.municipalityNisCode === null && postalAreaInput.val() !== '')
				{
					postalAreaInput.autocomplete('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.municipalityNisCode === null)
				{
					return; // Can't continue without municipalityNisCode value.
				}

				$.getJSON(
					streetNameUrl + address.municipalityNisCode + '/' + (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.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 || address.language === null)
				{
					return;
				}

				$.getJSON(
					houseNumberUrl + address.streetId +
					'/' + address.postcode +
					'/' + encodeURIComponent(address.language) +
					'/' + 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.houseNumber === null && houseNumberInput.val() !== '')
				{
					houseNumberInput.completeHouseNumber('search');
				}
			}
		);

		resetFormInput.click(
			function ()
			{
				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.municipalityName = null;
			address.municipalityNisCode = null;
			address.postcode = null;

			updateInputValues(true);
			updateValidity(postalAreaInput, false);
			updateStreetName(event, null);
		}
		else if (
			address.municipalityName !== item.municipalityName
			|| address.municipalityNisCode !== item.municipalityNisCode
			|| postcodeFilter !== (item.postcode || null)
		)
		{
			address.municipalityName = item.municipalityName;
			address.municipalityNisCode = item.municipalityNisCode;
			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;
			address.language = null;

			updateValidity(streetNameInput, false);
			updateHouseNumber(event, null);
		}
		else if (
			address.municipalityName !== item.municipalityName
			|| address.postcode !== item.postcode
			|| address.streetName !== item.streetName
			|| address.streetId !== item.streetId
			|| address.language !== item.language
		)
		{
			address.municipalityName = item.municipalityName;
			address.postcode = item.postcode;
			address.streetName = item.streetName;
			address.streetId = item.streetId;
			address.language = item.language;

			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.municipalityName !== null)
		{
			postalAreaInput.val(postcodeFilter + ' ' + address.municipalityName);
		}
		else if (address.municipalityName !== null)
		{
			postalAreaInput.val(address.municipalityName);
		}

		if (address.streetName !== null)
		{
			streetNameInput.val(address.streetName);
		}

		if (address.houseNumber !== null)
		{
			houseNumberInput.val(address.houseNumber);
		}

		municipalityInput.val(address.municipalityName || '');
		postcodeInput.val(address.postcode || '');
	};

	var renderAddress = function ()
	{
		addressElement.empty();

		if (address.postcode === null || address.municipalityName === null || address.streetName === null)
		{
			return;
		}

		addressElement.append(address.streetName);

		if (address.houseNumber !== '')
		{
			addressElement.append(' ', address.houseNumber);
		}

		addressElement.append($('<br>'), address.postcode, ' ', address.municipalityName);
	};

	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;
}