I'm using Google Maps autocomplete feature and everything is working fine.
But I'd like to know if it's possible to run .getPlace() function from a custom string, wether it's coordinates or address. For example, instead of using an input field and click on the location to select it, I'd like to call it manually, like this:
var myAutoComplete = new google.maps.places.Autocomplete('City, Country');
And it return the same as a normal autocomplete. The reason why I want to do this, is because somethis I get users location from html5 geolocation (or other method) and I'd like to run the getPlace function with that data.
The getPlace function from google return a more complete set of data, with all the names, coordinates, pictures from that city, etc..
By the way, I'm using Google Maps with AngularJs with this module: ngMap.
Edit: Posting the code I have so far as requested on the comments.
//HTML
<input places-auto-complete on-place-changed="vm.placeChanged()" />
//Controller
function MainController(NgMap) {
var vm = this;
vm.placeChanged = function() {
var param = {
maxWidth: 1920,
maxHeight: 1080
};
var autocomplete = this.getPlace();
console.log('Result: ', autocomplete);
console.log(autocomplete.geometry.location.lat());
console.log(autocomplete.geometry.location.lng());
console.log(autocomplete.photos[0].getUrl(param));
}
}
The input automatically generate the autocomplete feature, when I select one address option, the function is called and I get all the response correctly.
What I want is to call the same function, but instead of using the autocomplete from google, I want to pass my own string and return the same data as the function.
As suggested on the comments, I tried using a custom directive to run the same autocomplete, this is my plunkr: http://plnkr.co/edit/hcRXJxB7ItN6YtISWdx3?p=preview
Autocomplete object does not support such kind of scenario, it could only be attached to the specified input text field from where the user selects the item and the correspinding place is returned.
Instead you could utilize AutocompleteService class which in my opinion suits your scenario very closely
According to official documentation AutocompleteService class
does not add any UI controls. Instead, it returns an array of
prediction objects, each containing the text of the prediction,
reference information, and details of how the result matches the user
input. This is useful if you want more control over the user interface
than is offered by the Autocomplete
The following example demonstrates how to return the details of the Place from input string entered in text box
Example
var app = angular.module('myApp', ['ngMap']);
app.controller('MyCtrl', function ($scope,NgMap) {
var vm = this;
vm.center = [0,0];
vm.types = "['establishment']";
NgMap.getMap().then(function (map) {
vm.map = map;
});
vm.addressResolved = function (value) {
$scope.$apply(function () {
vm.address = value.address_components;
vm.center = value.geometry.location;
});
}
});
app.directive('myComplete', function (NgMap) {
return {
restrict: 'A',
scope: {
map: '=',
addressResolved: '&addressResolved'
},
link: function (scope, element, attrs) {
// on blur, update the value in scope
element.bind('blur', function (blurEvent) {
var service = new google.maps.places.AutocompleteService();
service.getQueryPredictions({ input: element.val() }, function (predictions, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
if (predictions.length > 0) {
element.val(predictions[0].description);
var service = new google.maps.places.PlacesService(scope.map);
service.getDetails({
placeId: predictions[0].place_id
}, function (place, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
scope.addressResolved({ place: place });
}
});
}
}
});
});
}
};
});
<script src="https://maps.google.com/maps/api/js?libraries=placeses,visualization,drawing,geometry,places"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<script src="https://rawgit.com/allenhwkim/angularjs-google-maps/master/build/scripts/ng-map.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl as vm">
Auto Complete Type:
<select ng-model="vm.types">
<option value="['geocode']">Geodode</option>
<option value="['establishment']">Establishment</option>
<option value="['address']">Address</option>
</select><br/>
<h3>Custom directive usage</h3>
<input my-complete address-resolved="vm.addressResolved(place)" map="vm.map" />
address : <pre>{{vm.address | json}}</pre>
<ng-map zoom="12" center="{{vm.center}}"></ng-map>
</div>
Demo
Related
I am trying to create an autocomplete text field which should only provide the postal code in dropdown. Here is the documentation which I have followed:
google place autocomplete
How do i get only and all the postcodes of UK over there.
src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"
src="https://maps.googleapis.com/maps/api/js?key=your_api_key&libraries=places&sensor=false&callback=initialize"
function initialize() {
$('#adv_postcode').each(function () {
initialize2(this);
});
}
function initialize2(elementID) {
var options = {
componentRestrictions: {
country: 'uk'
}
};
var autocomplete_element = new google.maps.places.Autocomplete(elementID, options);
autocomplete_element.addListener('place_changed', function () {
elementID.value = fillInAddress(autocomplete_element);
});
}
<input type="text" name="adv_postcode" id="adv_postcode" class="addressfields"/>
Any help highly appreciated
At the moment the best option that you have is setting types parameter of autocomplete options to (regions). According to the documentation
The (regions) type collection instructs the Places service to return any result matching the following types:
locality
sublocality
postal_code
country
administrative_area_level_1
administrative_area_level_2
source: https://developers.google.com/places/supported_types#table3
Not ideal, but you will get only regions in the list of suggestions. So your code should be
var options = {
componentRestrictions: {
country: 'uk'
},
types: ['(regions)']
};
var autocomplete_element = new google.maps.places.Autocomplete(elementID, options);
autocomplete_element.addListener('place_changed', function () {
elementID.value = fillInAddress(autocomplete_element);
});
Also, note that there is a feature request in Google issue tracker to make types parameter more flexible:
https://issuetracker.google.com/issues/35820774
Feel free to star this feature request to express your interest and subscribe to notifications.
I working on a small pet project that finds exchange rates and I am having trouble passing my input to the angular controller.
Say I have a datalist input that shows a selection of countries as you type:
<label for="ajax">From</label>
<input type="text" id="ajax" list="json-datalist">
<datalist id="json-datalist"></datalist>
And the items are populated using JavaScript:
// Get the <datalist> and <input> elements.
var dataList = document.getElementById('json-datalist');
var input = document.getElementById('ajax');
var fromCode = 'USD';
// Create a new XMLHttpRequest.
var request = new XMLHttpRequest();
// Handle state changes for the request.
request.onreadystatechange = function(response) {
if (request.readyState === 4) {
if (request.status === 200) {
// Parse the JSON
var jsonOptions = JSON.parse(request.responseText);
// Loop over the JSON array.
jsonOptions.forEach(function(item) {
// Create a new <option> element.
var option = document.createElement('option');
// Set the value using the item in the JSON array.
option.value = item.name;
fromCode = item.code;
// Add the <option> element to the <datalist>.
console.log(option);
dataList.appendChild(option);
});
// Update the placeholder text.
input.placeholder = "e.g. United States Dollar - USD";
} else {
// An error occured :(
input.placeholder = "Couldn't load currency options :(";
}
}
};
// Update the placeholder text.
input.placeholder = "Loading currencies...";
// Set up and make the request.
request.open('GET', 'js/currencies.json', true);
request.send();
The currencies.json file looks like this.
I am showing the user the country name and trying to pass the country code to the angular controller so it can find the right exchange rate for that country and display it.
Now my angular controller looks like this:
var myApp = angular.module("myApp", []);
myApp.controller("myController", ['$scope', '$http', function ($scope, $http) {
$scope.from = fromCode;
$http.get("https://api.fixer.io/latest?symbols=" + $scope.from)
.then(function (response) {
$scope.newRate = response.data;
});
$http.get("https://api.fixer.io/latest?base=USD")
.then(function (response) {
$scope.currencies = response.data;
});
}]);
The 'fromCode' is set to USD by default and is supposed to be updated as the datalist item changes which it does. I want angular to perform a new lookup every-time I change my datalist selection in my input form. How do I accomplish this?
You shouldn't be passing anything from your view to your controller. All of your data should be sourced from your controller. All of that javascript you have on your html page should be in your controller.
Once you've done that, you can bind your view to your controller using ng-repeat and ng-model and the data will get updated in your controller automatically.
I'm trying to take the first example from the angular.js homepage and adding in cookie support.
This is what I have so far: https://jsfiddle.net/y7dxa6n8/8/
It is:
<div ng-app="myApp">
<div ng-controller="MyController as mc">
<label>Name:</label>
<input type="text" ng-model="mc.user" placeholder="Enter a name here">
<hr>
<h1>Hello {{mc.user}}!</h1>
</div>
</div>
var myApp = angular.module('myApp', ['ngCookies']);
myApp.controller('MyController', [function($cookies) {
this.getCookieValue = function () {
$cookies.put('user', this.user);
return $cookies.get('user');
}
this.user = this.getCookieValue();
}]);
But it's not working, ive been trying to learn angular.
Thanks
I'd suggest you create a service as such in the app module:
app.service('shareDataService', ['$cookieStore', function ($cookieStore) {
var _setAppData = function (key, data) { //userId, userName) {
$cookieStore.put(key, data);
};
var _getAppData = function (key) {
var appData = $cookieStore.get(key);
return appData;
};
return {
setAppData: _setAppData,
getAppData: _getAppData
};
}]);
Inject the shareDataService in the controller to set and get cookie value
as:
//set
var userData = { 'userId': $scope.userId, 'userName': $scope.userName };
shareDataService.setAppData('userData', userData);
//get
var sharedUserData = shareDataService.getAppData('userData');
$scope.userId = sharedUserData.userId;
$scope.userName = sharedUserData.userName;
Working Fiddle: https://jsfiddle.net/y7dxa6n8/10/
I have used the cookie service between two controllers. Fill out the text box to see how it gets utilized.
ok, examined your code once again, and here is your answer
https://jsfiddle.net/wz3kgak3/
problem - wrong syntax: notice definition of controller, not using [] as second parameter
If you are using [] in controller, you must use it this way:
myApp.controller('MyController', ['$cookies', function($cookies) {
....
}]);
this "long" format is javascript uglyfier safe, when param $cookies will become a or b or so, and will be inaccessible as $cookies, so you are telling that controller: "first parameter in my function is cookies
problem: you are using angular 1.3.x, there is no method PUT or GET in $cookies, that methods are avalaible only in angular 1.4+, so you need to use it old way: $cookies.user = 'something'; and getter: var something = $cookies.user;
problem - you are not storing that cookie value, model is updated, but cookie is not automatically binded, so use $watch for watching changes in user and store it:
$watch('user', function(newValue) {
$cookies.user = newValues;
});
or do it via some event (click, submit or i dont know where)
EDIT: full working example with $scope
https://jsfiddle.net/mwcxv820/
Inside a visual force page (as a container), I have created a custom directive in angular JS but it’s not working. In fact, the directive is not even being called!
Though this works perfectly fine in the JSFiddle
Below is the custom directive.When I use this directive in the HTML markup, it never show anything on the console log.
I have created multiple directives and seeing the same behavior.
I believe there is a bug when using custom directives inside the visualforce container.Has anyone of you have faced the same issue? Any help would be greatly appreciated.
Thanks!
-SS
UPDATE
Here is the JSFiddle for Custom directive that works fine but when I use it in visual force page, it doesn’t work.( Even though the directive has a console.log, nothing appears in console. This proves that the directive is not being called)
http://jsfiddle.net/ssah13/3y1z5943/
Please note: This directive strips off everything before and after underscore in the OppName. For example: If OppName is “111111_Test_123445" then output is “Test"
Here is the visual force page and a controller:
PAGE:
<apex:page docType="html-5.0" controller="SalesActions">
<head>
<script src="//code.jquery.com/jquery-1.11.0.min.js"/>
<apex:includeScript value="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"/>
</head>
<!-- HTML FOR APP -->
<!-- To use bootstrap with visualforce everything needs to be wrapped with "bs" class-->
<div ng-app="salesApp" class="bs">
<div ng-controller="salesController">
<div ng-repeat="sfResult in salesforceResponse">
<table>
<tr ng-repeat="opp in sfResult.opportunities">
<td>
<span opp-name="" input="opp.Name">
{{opp.Name}}
</span>
</td>
</tr>
</table>
</div>
</div>
</div>
<!-- ACTUAL ANGULAR JS APP : Later on move this to salesworkspace.js-->
<script type = "text/javascript">
var ngApp = angular.module('salesApp', []);
//Opp Name directive
ngApp.directive('oppName', function () {
return {
restrict: 'A',
scope: {
input: '='
},
link: function (scope, element, attrs) {
console.log('Input: ', scope.input);
var input = scope.input;
if (!input) {
return;
}
// AccountName_Test_123445
if (input.indexOf('_')) {
scope.input = input.split('_')[1];
}
}
};
});
ngApp.controller('salesController', ['$scope',
function($scope) {
$scope.salesforceResponse = [];
Visualforce.remoting.Manager.invokeAction(
'{!$RemoteAction.SalesActions.getAllAccounts}',
function(result, event) {
if (event.status) {
$scope.$apply(function() { //Use Apply as the scope changed outside Angular Context?
$scope.salesforceResponse = result;
console.log($scope.salesforceResponse);
});
} else {
console.log(event);
}
}
);
} //End of function
]); //End of Controller method
</script>
</apex:page>
CONTROLLER:
salesActions.cls
public with sharing class SalesActions {
public SalesActions() { } // empty constructor
#RemoteAction
public static List<accountWrapper> getAllAccounts() {
List<accountWrapper> accountResponse = new List<accountWrapper>();
List<account> accs = [SELECT Id, Name, Type, Strategic_Account_Management__c,
(SELECT Id FROM Opportunities) ,
(SELECT Name FROM Contacts)
FROM Account
Order by Name]; //Add a Filter here. WHERE ownerId = :Userinfo.getUserId();
Set<Id> accountIds = new Set<Id>();
for(account acc : accs) {
accountIds.add(acc.Id);
}
Map<Id,Opportunity> oppIdToOpp = new Map<Id,Opportunity>([
SELECT Id,Name, Account.Name, Agency__r.Name, Campaign_EVENT__c,Rate_Type__c,StageName,Amount,CurrencyISOCode,
Probability,CampaignStartDate2__c,CampaignEndDate2__c,Contact__c,Sales_Notes__c,
(SELECT SplitAmount, SplitOwner.Name,SplitPercentage, Split__c FROM OpportunitySplits)
FROM Opportunity WHERE AccountId IN :accountIds]// Remove WHERE AccountId =:accountId and Add WHERE account.ownerId=:UserInfo.getId();
);
Map<Id,List<Partner>> accountIdToPartners = new Map<Id,List<Partner>>();
for(Partner p :[SELECT AccountFromId,AccountTo.Name FROM Partner WHERE AccountFromId IN :accountIds]) {
if(accountIdToPartners.containsKey(p.AccountFromId)) {
accountIdToPartners.get(p.AccountFromId).add(p);
} else {
accountIdToPartners.put(p.AccountFromId, new List<Partner>{p});
}
}
for(Account acc : accs) {
accountWrapper accWrapper = new accountWrapper();
accWrapper.account = acc; // This will add all the accounts and related contacts
accWrapper.opportunities = new List<Opportunity>();
accWrapper.partners = new list<Partner>();
if(accountIdToPartners.containsKey(acc.Id)){
accWrapper.partners = accountIdToPartners.get(acc.Id);
}
for(Opportunity opp : acc.Opportunities) {
accWrapper.opportunities.add(oppIdToOpp.get(opp.Id)); // This will add all the opportunties and opportunitySplits
}
accountResponse.add(accWrapper);
}
return accountResponse;
}
public class accountWrapper {
public Account account { get; set; }
public List<Partner> partners { get; set; }
public List<Opportunity> opportunities { get; set; }
}
}
For me this is how I get it to work:
ngApp.directive('testDirective', function () {
return {
restrict: 'E',
scope: {
input: '='
},
template: '<p>{{input}}</p>',
link: function (scope, element, attrs) {
console.log('Input: ', scope.input);
var input = scope.input;
if (!input) {
return;
}
// AccountName_Test_123445
if (input.indexOf('_') !== -1) {
scope.input = input.split('_')[1];
}
}
};
});
and then in the html:
<test-directive input="opp.Name"></test-directive>
Those are just some small changes. Not entirely sure why it does not work in VF before. In VF I would always try to use directives as elements as VF cannot have those empty attributes as normal HTML can.
Then you should define a template or templateUrl in the directive.
(You can use VF pages as templates here as well - even with standardControllers or custom controllers firing!)
Oh and of course have a look at ngRemote again as it can help with your AngularJS application on VF.
Hope this helps!
Florian
I'm trying to figure out if it is possible to validate data client side to ensure that no duplicates are sent to the database. I have an angular app which gets data from an api call. This is my current controller for adding a new subject (functioning perfectly, but without data validation):
angular.module('myApp.controllers')
.controller('SubjectNewCtrl', ['$scope', 'SubjectsFactory', '$location', '$route',
function ($scope, SubjectsFactory, $location, $route) {
// callback for ng-click 'createNewSubject':
$scope.createNewSubject = function () {
SubjectsFactory.create($scope.subjects);
$location.path('/subjects');
}
}]);
And here is what I have been attempting for data validation:
angular.module('myApp.controllers')
.controller('SubjectNewCtrl', ['$scope', 'SubjectsFactory', '$location', '$route',
function ($scope, SubjectsFactory, $location, $route) {
// callback for ng-click 'createNewUser':
$scope.createNewSubject = function () {
var newSubject = $scope.subject.name;
var someSubject = $scope.subjects;
var oldSubject;
if(newSubject){
angular.forEach($scope.subjects, function(allSubjects){
if(newSubject.toLowerCase() == allSubjects.name.toLowerCase()){
oldSubject = true;
}
});
if (!oldSubject){
SubjectsFactory.create($scope.subjects);
}
}
}
}]);
This gives me a console error- TypeError: Cannot read property 'name' of undefined. How do I access the 'name' property of my new subject from the html? Can anyone tell me if what I am trying to do is possible/ makes sense?
If I understand your question correctly, you should use a directive for the specific field you are trying to validate. A unique email directive would be a common example. Here is one I have used in the past. Nothing fancy.
MyApp.directive('uniqueEmail', ['$http', function($http) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
//set the initial value as soon as the input comes into focus
element.on('focus', function() {
if (!scope.initialValue) {
scope.initialValue = ctrl.$viewValue;
}
});
element.on('blur', function() {
if (ctrl.$viewValue != scope.initialValue) {
var dataUrl = attrs.url + "?email=" + ctrl.$viewValue;
//you could also inject and use your 'Factory' to make call
$http.get(dataUrl).success(function(data) {
ctrl.$setValidity('isunique', data.result);
}).error(function(data, status) {
//handle server error
});
}
});
}
};
}]);
Then in your markup you could use it like so.
<input type="text" name="email" ng-model="item.email" data-unique-email="" data-url="/api/check-unique-email" />
<span class="validation-error" ng-show="form.email.$error.isunique && form.email.$dirty">Duplicate Email</span>
Hope this is what you were looking for.
I have implemented object creation in Angular js many times.
My createNew button method typically just created a new javascript Object() and set the scope.currentObject to the new Object();
In your case it appears that $scope.subject is not initialized to anything, hence the error.
I guess that there must be a html input on your form that is bound the subject.name field but without a subject Object to hold the name it is effectively unbound.
If I wanted users to enter a name then click create button to validate that the name is not used. I would bind the new Name input to a different $scope variable (perhaps $scope.newName)
Then in the createNewSubject method you can actually create a new subject like this:
$scope.subject = new Object();
$scope.subject.name = $scope.newName;
Then you can run your validation code.