var app = angular.module('plunker', ['vsGoogleAutocomplete']);
app.controller('MainCtrl', function($scope) {
$scope.streetNumber = {
name: '',
place: '',
components: {
placeId: '',
streetNumber: '',
street: '',
city: '',
state: '',
countryCode: '',
country: '',
postCode: '',
district: '',
location: {
lat: '',
long: ''
}
}
};
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>vsHandyStorage Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link data-require="bootstrap-css" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.15/angular.js" data-semver="1.3.15"></script>
<!-- Google Maps JavaScript API -->
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=places"></script>
<!-- vsGoogleAutocomplete -->
<script src="vs-google-autocomplete.js"></script>
<!-- validator for autocomplete -->
<script src="vs-autocomplete-validator.js"></script>
<script src="app.js"></script>
</head>
<!--
* Dependency: Google Maps JavaScript API v3
* Google Maps API info: https://developers.google.com/maps/documentation/javascript/places-autocomplete
* Simple usage:
* 1. add <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=places"></script>
* 2. add vs-google-autocomplete.js
* 3. add vs-google-autocomplete directive to input field
* Validator usage:
* 1. add vs-autocomplete-validator.js
* 2. add vs-autocomplete-validator directive to input field
-->
<body ng-controller="MainCtrl">
<div class="container">
<h3>Example of vsGoogleAutocomplete module <u>with validator</u></h3>
<h5>Also look at <a href="http://plnkr.co/edit/sdcIaQ?p=preview">simple usage</a> of this module without embedded validator</h5>
<form name="addrForm">
<!--
* Example: vs-autocomplete-validator
* Info: checks if place is valid Google address (default config)
-->
<div ng-class="{'form-group':true, 'has-error':addrForm.place.$dirty && addrForm.place.$invalid}">
<label for="place" class="control-label">Place</label>
<input vs-google-autocomplete
vs-autocomplete-validator
ng-model="place"
type="text"
name="place"
id="place"
class="form-control"
placeholder="Enter any address">
<span class="help-block"><b><u>Valid</u>: </b>{{addrForm.place.$valid}}</span>
<span class="help-block"><b><u>NgModelController.$error</u>: </b>{{addrForm.place.$error}}</span>
<span class="help-block"><b><u>NgModelController.vsAutocompleteErorr</u>: </b>{{addrForm.place.vsAutocompleteErorr}}</span>
<span class="help-block"><b>Model: </b>{{place}}</span>
</div>
<!--
* Example: vs-autocomplete-validator="vs-street-address"
* Info: checks if place is full street address (street number, street, ...)
-->
<div ng-class="{'form-group':true, 'has-error':addrForm.streetNumber.$dirty && addrForm.streetNumber.$invalid}">
<label for="streetNumber" class="control-label">Street number</label>
<input vs-google-autocomplete="{ types: ['address'] }"
vs-autocomplete-validator="vs-street-address"
ng-model="streetNumber.name"
vs-place="streetNumber.place"
vs-place-id="streetNumber.components.placeId"
vs-street-number="streetNumber.components.streetNumber"
vs-street="streetNumber.components.street"
vs-city="streetNumber.components.city"
vs-state="streetNumber.components.state"
vs-country-short="streetNumber.components.countryCode"
vs-country="streetNumber.components.country"
vs-post-code="address.components.postCode"
vs-district="address.components.district"
vs-latitude="streetNumber.components.location.lat"
vs-longitude="streetNumber.components.location.long"
type="text"
name="streetNumber"
id="streetNumber"
class="form-control"
placeholder="Enter full address (street number, street, ...)">
<!-- validation info -->
<span class="help-block"><b><u>Valid</u>: </b>{{addrForm.streetNumber.$valid}}</span>
<span class="help-block"><b><u>NgModelController.$error</u>: </b>{{addrForm.streetNumber.$error}}</span>
<span class="help-block"><b><u>NgModelController.vsAutocompleteErorr</u>: </b>{{addrForm.streetNumber.vsAutocompleteErorr}}</span>
<!-- model -->
<span class="help-block"><b>Model: </b>{{streetNumber.name}}</span>
<span class="help-block"><b>Place id: </b>{{streetNumber.components.placeId}}</span>
<span class="help-block"><b>Street number: </b>{{streetNumber.components.streetNumber}}</span>
<span class="help-block"><b>Street: </b>{{streetNumber.components.street}}</span>
<span class="help-block"><b>City: </b>{{streetNumber.components.city}}</span>
<span class="help-block"><b>State: </b>{{streetNumber.components.state}}</span>
<span class="help-block"><b>Country code: </b>{{streetNumber.components.countryCode}}</span>
<span class="help-block"><b>Country: </b>{{streetNumber.components.country}}</span>
<span class="help-block"><b>Postcode: </b>{{address.components.postCode}}</span>
<span class="help-block"><b>District: </b>{{address.components.district}}</span>
<span class="help-block"><b>Latitude: </b>{{streetNumber.components.location.lat}}</span>
<span class="help-block"><b>Longitude: </b>{{streetNumber.components.location.long}}</span>
<pre class="help-block"><b>Place: </b>{{streetNumber.place | json}}</pre>
</div>
</form>
</div>
</body>
</html>
/* Put your css in here */
/**
* vsGoogleAutocomplete - v0.5.0 - 2015-11-29
* https://github.com/vskosp/vsGoogleAutocomplete
* Copyright (c) 2015 K.Polishchuk
* License: MIT
*/
(function (window, document) {
'use strict';
angular.module('vsGoogleAutocomplete', []);
angular.module('vsGoogleAutocomplete').service('vsGooglePlaceUtility', function() {
function isGooglePlace(place) {
if (!place)
return false;
return !!place.place_id;
}
function isContainTypes(place, types) {
var placeTypes,
placeType,
type;
if (!isGooglePlace(place))
return false;
placeTypes = place.types;
for (var i = 0; i < types.length; i++) {
type = types[i];
for (var j = 0; j < placeTypes.length; j++) {
placeType = placeTypes[j];
if (placeType === type) {
return true;
}
}
}
return false;
}
function getAddrComponent(place, componentTemplate) {
var result;
if (!isGooglePlace(place))
return;
for (var i = 0; i < place.address_components.length; i++) {
var addressType = place.address_components[i].types[0];
if (componentTemplate[addressType]) {
result = place.address_components[i][componentTemplate[addressType]];
return result;
}
}
return;
}
function getPlaceId(place) {
if (!isGooglePlace(place))
return;
return place.place_id;
}
function getStreetNumber(place) {
var COMPONENT_TEMPLATE = { street_number: 'short_name' },
streetNumber = getAddrComponent(place, COMPONENT_TEMPLATE);
return streetNumber;
}
function getStreet(place) {
var COMPONENT_TEMPLATE = { route: 'long_name' },
street = getAddrComponent(place, COMPONENT_TEMPLATE);
return street;
}
function getCity(place) {
var COMPONENT_TEMPLATE = { locality: 'long_name' },
city = getAddrComponent(place, COMPONENT_TEMPLATE);
return city;
}
function getState(place) {
var COMPONENT_TEMPLATE = { administrative_area_level_1: 'short_name' },
state = getAddrComponent(place, COMPONENT_TEMPLATE);
return state;
}
function getDistrict(place) {
var COMPONENT_TEMPLATE = { administrative_area_level_2: 'short_name' },
state = getAddrComponent(place, COMPONENT_TEMPLATE);
return state;
}
function getCountryShort(place) {
var COMPONENT_TEMPLATE = { country: 'short_name' },
countryShort = getAddrComponent(place, COMPONENT_TEMPLATE);
return countryShort;
}
function getCountry(place) {
var COMPONENT_TEMPLATE = { country: 'long_name' },
country = getAddrComponent(place, COMPONENT_TEMPLATE);
return country;
}
function getPostCode(place) {
var COMPONENT_TEMPLATE = { postal_code: 'long_name' },
postCode = getAddrComponent(place, COMPONENT_TEMPLATE);
return postCode;
}
function isGeometryExist(place) {
return angular.isObject(place) && angular.isObject(place.geometry);
}
function getLatitude(place) {
if (!isGeometryExist(place)) return;
return place.geometry.location.lat();
}
function getLongitude(place) {
if (!isGeometryExist(place)) return;
return place.geometry.location.lng();
}
return {
isGooglePlace: isGooglePlace,
isContainTypes: isContainTypes,
getPlaceId: getPlaceId,
getStreetNumber: getStreetNumber,
getStreet: getStreet,
getCity: getCity,
getState: getState,
getCountryShort: getCountryShort,
getCountry: getCountry,
getLatitude: getLatitude,
getLongitude: getLongitude,
getPostCode: getPostCode,
getDistrict: getDistrict
};
});
angular.module('vsGoogleAutocomplete').directive('vsGoogleAutocomplete', ['vsGooglePlaceUtility', '$timeout', function(vsGooglePlaceUtility, $timeout) {
return {
restrict: 'A',
require: ['vsGoogleAutocomplete', 'ngModel'],
scope: {
vsGoogleAutocomplete: '=',
vsPlace: '=?',
vsPlaceId: '=?',
vsStreetNumber: '=?',
vsStreet: '=?',
vsCity: '=?',
vsState: '=?',
vsCountryShort: '=?',
vsCountry: '=?',
vsPostCode: '=?',
vsLatitude: '=?',
vsLongitude: '=?',
vsDistrict: '=?'
},
controller: ['$scope', '$attrs', function($scope, $attrs) {
this.isolatedScope = $scope;
/**
* Updates address components associated with scope model.
* @param {google.maps.places.PlaceResult} place PlaceResult object
*/
this.updatePlaceComponents = function(place) {
$scope.vsPlaceId = !!$attrs.vsPlaceId && place ? vsGooglePlaceUtility.getPlaceId(place) : undefined;
$scope.vsStreetNumber = !!$attrs.vsStreetNumber && place ? vsGooglePlaceUtility.getStreetNumber(place) : undefined;
$scope.vsStreet = !!$attrs.vsStreet && place ? vsGooglePlaceUtility.getStreet(place) : undefined;
$scope.vsCity = !!$attrs.vsCity && place ? vsGooglePlaceUtility.getCity(place) : undefined;
$scope.vsPostCode = !!$attrs.vsPostCode && place ? vsGooglePlaceUtility.getPostCode(place) : undefined;
$scope.vsState = !!$attrs.vsState && place ? vsGooglePlaceUtility.getState(place) : undefined;
$scope.vsCountryShort = !!$attrs.vsCountryShort && place ? vsGooglePlaceUtility.getCountryShort(place) : undefined;
$scope.vsCountry = !!$attrs.vsCountry && place ? vsGooglePlaceUtility.getCountry(place) : undefined;
$scope.vsLatitude = !!$attrs.vsLatitude && place ? vsGooglePlaceUtility.getLatitude(place) : undefined;
$scope.vsLongitude = !!$attrs.vsLongitude && place ? vsGooglePlaceUtility.getLongitude(place) : undefined;
$scope.vsDistrict = !!$attrs.vsDistrict && place ? vsGooglePlaceUtility.getDistrict(place) : undefined;
};
}],
link: function(scope, element, attrs, ctrls) {
// controllers
var autocompleteCtrl = ctrls[0],
modelCtrl = ctrls[1];
// google.maps.places.Autocomplete instance (support google.maps.places.AutocompleteOptions)
var autocompleteOptions = scope.vsGoogleAutocomplete || {},
autocomplete = new google.maps.places.Autocomplete(element[0], autocompleteOptions);
// google place object
var place;
// value for updating view
var viewValue;
// updates view value and address components on place_changed google api event
google.maps.event.addListener(autocomplete, 'place_changed', function() {
place = autocomplete.getPlace();
viewValue = place.formatted_address || modelCtrl.$viewValue;
scope.$apply(function() {
scope.vsPlace = place;
autocompleteCtrl.updatePlaceComponents(place);
modelCtrl.$setViewValue(viewValue);
modelCtrl.$render();
});
});
// updates view value on focusout
element.on('blur', function(event) {
viewValue = (place && place.formatted_address) ? viewValue : modelCtrl.$viewValue;
$timeout(function() {
scope.$apply(function() {
modelCtrl.$setViewValue(viewValue);
modelCtrl.$render();
});
});
});
// prevent submitting form on enter
google.maps.event.addDomListener(element[0], 'keydown', function(e) {
if (e.keyCode == 13) {
e.preventDefault();
}
});
}
};
}]);
})(window, document);
/**
* vsGoogleAutocomplete - v0.5.0 - 2015-11-29
* https://github.com/vskosp/vsGoogleAutocomplete
* Copyright (c) 2015 K.Polishchuk
* License: MIT
*/
(function (window, document) {
'use strict';
angular.module('vsGoogleAutocomplete').factory('vsEmbeddedValidatorsInjector', ['$injector', function($injector) {
var validatorsHash = [];
/**
* Class making embedded validator.
* @constructor
* @param {string} name - validator name.
* @param {function(place)} validateMethod - function that will validate place.
*/
function EmbeddedValidator(name, validateMethod) {
this.name = name;
this.validate = validateMethod;
}
function searchValidator(validatorName) {
for (var i = 0; i < validatorsHash.length; i++) {
if(validatorsHash[i].name === validatorName)
return validatorsHash[i];
}
return;
}
function getValidator(validatorName) {
var validator = searchValidator(validatorName);
if(!validator) {
validator = new EmbeddedValidator(validatorName, $injector.get(validatorName));
validatorsHash.push(validator);
}
return validator;
}
function getValidators(validatorsNamesList) {
var validatorsList = [];
for (var i = 0; i < validatorsNamesList.length; i++) {
var validator = getValidator(validatorsNamesList[i]);
validatorsList.push(validator);
}
return validatorsList;
}
return {
get: getValidators
};
}]);
angular.module('vsGoogleAutocomplete').service('vsValidatorFactory', ['vsEmbeddedValidatorsInjector', function(vsEmbeddedValidatorsInjector) {
/**
* Class making validator associated with vsGoogleAutocomplete controller.
* @constructor
* @param {Array.<string>} validatorsNamesList - List of embedded validator names.
*/
function Validator(validatorsNamesList) {
// add default embedded validator name
validatorsNamesList.unshift('vsGooglePlace');
this._embeddedValidators = vsEmbeddedValidatorsInjector.get(validatorsNamesList);
this.error = {};
this.valid = true;
}
/**
* Runs all embedded validators and change the validity state.
* @param {google.maps.places.PlaceResult} place - PlaceResult object.
*/
Validator.prototype.validate = function(place) {
var validationErrorKey, isValid;
for (var i = 0; i < this._embeddedValidators.length; i++) {
validationErrorKey = this._embeddedValidators[i].name;
// runs embedded validator only if place is object
if (angular.isObject(place)) {
isValid = this._embeddedValidators[i].validate(place);
} else {
isValid = false;
}
this._setValidity(validationErrorKey, isValid);
}
};
/**
* Sets validity.
* @param {string} validationErrorKey - Error name.
* @param {boolean} isValid - Valid status.
*/
Validator.prototype._setValidity = function(validationErrorKey, isValid) {
// set error
if (typeof isValid != 'boolean') {
delete this.error[validationErrorKey];
} else {
if (!isValid) {
this.error[validationErrorKey] = true;
} else {
delete this.error[validationErrorKey];
}
}
// set validity
if (this.error) {
for (var e in this.error) {
this.valid = false;
return;
}
}
this.valid = true;
};
this.create = function(validatorsNamesList) {
return new Validator(validatorsNamesList);
};
}]);
angular.module('vsGoogleAutocomplete').directive('vsAutocompleteValidator', ['vsValidatorFactory', function(vsValidatorFactory) {
/**
* Parse validator names from attribute.
* @param {$compile.directive.Attributes} attrs Element attributes
* @return {Array.<string>} Returns array of normalized validator names.
*/
function parseValidatorNames(attrs) {
var attrValue = attrs.vsAutocompleteValidator,
validatorNames = (attrValue!=="") ? attrValue.trim().split(',') : [];
// normalize validator names
for (var i = 0; i < validatorNames.length; i++) {
validatorNames[i] = attrs.$normalize(validatorNames[i]);
}
return validatorNames;
}
return {
restrict: 'A',
require: ['ngModel', 'vsGoogleAutocomplete'],
link: function(scope, element, attrs, controllers) {
// controllers
var modelCtrl = controllers[0],
autocompleteCtrl = controllers[1];
// validator
var validatorNames = parseValidatorNames(attrs),
validator = vsValidatorFactory.create(validatorNames);
// add validator for ngModel
modelCtrl.$validators.vsAutocompleteValidator = function() {
return validator.valid;
};
// watch for updating place
autocompleteCtrl.isolatedScope.$watch('vsPlace', function(place) {
// validate place
validator.validate(place);
// set addr components to undefined if place is invalid
if (!validator.valid) {
autocompleteCtrl.updatePlaceComponents(undefined);
}
// call modelCtrl.$validators.vsAutocompleteValidator
modelCtrl.$validate();
});
// publish autocomplete errors
modelCtrl.vsAutocompleteErorr = validator.error;
}
};
}]);
//Validator - checks if place is valid Google address
angular.module('vsGoogleAutocomplete').factory('vsGooglePlace', ['vsGooglePlaceUtility', function(vsGooglePlaceUtility) {
function validate(place) {
return vsGooglePlaceUtility.isGooglePlace(place);
}
return validate;
}]);
//Validator - checks if place is full street address (street number, street, ...)
angular.module('vsGoogleAutocomplete').factory('vsStreetAddress', ['vsGooglePlaceUtility', function(vsGooglePlaceUtility) {
var PLACE_TYPES = ["street_address", "premise"];
function validate(place) {
return vsGooglePlaceUtility.isContainTypes(place, PLACE_TYPES);
}
return validate;
}]);
})(window, document);