onMount inside a function won't re-trigger when a reactive variable changes - google-maps

I need to get google maps autocomplete suggestion when I type a word on input.
maps.svelte
function inputChange() {
onMount(() => {
loader
.load()
.then((google) => {
const map = new google.maps.Map(document.getElementById('map'), mapOptions);
const markerPickUp = new google.maps.Marker({
position: {
lat: 14.6012,
lng: 120.975
},
map: map
});
const displaySuggestions = function (predictions, status) {
if (status != google.maps.places.PlacesServiceStatus.OK || !predictions) {
predictionsArray = [];
return;
}
predictionsArray = predictions;
console.log(predictionsArray);
};
const service = new google.maps.places.AutocompleteService();
const sessionToken = new google.maps.places.AutocompleteSessionToken();
service.getPlacePredictions(
{ input: $bookDetails.dropOffLocation, sessionToken: sessionToken },
displaySuggestions
);
})
.catch((e) => {
console.log(e);
});
});
}
$: $bookDetails.dropOffLocation, inputChange();
I tried to log $bookDetails.dropOffLocation after inputChange() just to see if it's bind on the input and it is. Also, the code works well without the onMount() but it's giving me errors on my terminal. It says windows not defined and it has to do with google maps. I think it needs to load the dom first that's why it gives error. I'm using #googlemaps/js-api-loader.

onMount registers a callback that executes when the component is mounted. It is not intended to be called multiple times and only ever will invoke the callback once during the lifecycle of a component.
If you just want to make sure that the component is already mounted when executing some logic, you can set a flag and use that as a guard. E.g.
let mounted = false;
onMount(() => mounted = true);
$: {
$bookDetails.dropOffLocation;
if (mounted) inputChange();
}

Related

PixelCompare method issue

I have an issue with PixelCompare extension.
Here, i am loading it:
viewer1 = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer1'), { extensions: [ 'GoogleMapsLocator', 'Autodesk.AEC.Minimap3DExtension','Autodesk.AEC.LevelsExtension','Autodesk.DocumentBrowser','Autodesk.ToolbarExtension' ,'Autodesk.Viewing.PixelCompare'] });
Then im trying to use it :
viewer1.compareTwoModels(data[0],data[1]);
Data is an array containing the models.
I get a "viewer.compareTwoModels is not a function" error which means either there is a typo in the function name or it doesnt exist.
Without access to the extension repo, i cannot confirm this method indeed exists.
Im at loss as to what to do and will welcome any help.
EDIT:
This snippet loads the document in the viewer to display it.
Autodesk.Viewing.Document.load(`urn:${urns[index]}`, function(doc){
var viewables=doc.getRoot().getDefaultGeometry();
doc.downloadAecModelData();
viewer1.loadDocumentNode(doc, viewables, {
placementTransform: (new THREE.Matrix4()).setPosition({ x: increment, y: 0, z: 0 }),
keepCurrentModels: true,
globalOffset: { x: 0, y: 0, z: 0 }
}).then(i => {
if(urns.length > 1) {
$(viewer1.toolbar.container).find('#toolbarXLS').hide();
}
});
increment += parseInt(sessionStorage.getItem('padding1'));
});
Then, im using this to pixel compare it:
viewer1.loadModel('urn:dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLmwwLS10X0k5UkVhbnNWRXBuLXl5Zmc_dmVyc2lvbj0x', {}, (model1) => {
console.log('test load model :' + model1)
viewer1.loadModel('urn:dXJuOmFkc2sud2lwcHJvZDpmcy5maWxlOnZmLmxXX0dONUJNVDBxdDRuOGZmRWx4SkE_dmVyc2lvbj0x', {}, async (model2) => {
const pcExt = await viewer1.loadExtension('Autodesk.Viewing.PixelCompare');
pcExt.compareTwoModels(model1, model2);
console.log()
});
});
I get an instant "file extension unsupported" error, probably on the first argument the code does not reach console.log('test load model :' + model1).
The function compareTwoModels is part of the extension object, not the viewer - see PixelCompare extension
function launchViewer() {
var options = {
env: 'Local'
};
Autodesk.Viewing.Initializer(options, () => {
viewer = new Autodesk.Viewing.GuiViewer3D(
document.getElementById('forgeViewer'), {}
);
viewer.start();
// Load 2 sheets
viewer.loadModel('scissors1.pdf', {}, (model1) => {
viewer.loadModel('scissors2.pdf', {}, async (model2) => {
// Compare them
const pcExt = await viewer.loadExtension('Autodesk.Viewing.PixelCompare');
pcExt.compareTwoModels(model1, model2);
});
});
});
}

Login Service implementation in angularjs not working

This is my controller which is calling the login service
mod.controller("loginCtrl",function($scope,loginService,$http)
{
$scope.Userlogin = function()
{
var User = {
userid :$scope.uname,
pass:$scope.pass
};
var res = UserloginService(User);
console.log(res);
alert("login_succ");
}
});
And this is the login service code which takes the User variable and checks for username & password
mod.service("loginService",function($http,$q) {
UserloginService = function(User) {
var deffered = $q.defer();
$http({
method:'POST',
url:'http://localhost:8080/WebApplication4_1/login.htm',
data:User
}).then(function(data) {
deffered.resolve(data);
}).error(function(status) {
deffered.reject({
status:status
});
});
return deffered.promise;
// var response = $http({
//
// method:"post",
// url:"http://localhost:8080/WebApplication4_1/login.htm",
// data:JSON.stringify(User),
// dataType:"json"
// });
// return "Name";
}
});
I have created a rest api using springs which upon passing json return back the username and password in json like this
Console shows me this error for angular
You need to enable CORS for your application for guidance see this link
https://htet101.wordpress.com/2014/01/22/cors-with-angularjs-and-spring-rest/
I prefer to use Factory to do what you're trying to do, which would be something like this:
MyApp.factory('MyService', ["$http", function($http) {
var urlBase = "http://localhost:3000";
return {
getRecent: function(numberOfItems) {
return $http.get(urlBase+"/things/recent?limit="+numberOfItems);
},
getSomethingElse: function(url) {
return $http.get(urlBase+"/other/things")
},
search: function (searchTerms) {
return $http.get(urlBase+"/search?q="+searchTerms);
}
}
}]);
And then in your controller you can import MyService and then use it in this way:
MyService.getRecent(10).then(function(res) {
$scope.things = res.data;
});
This is a great way to handle it, because you're putting the .then in your controller and you are able to control the state of the UI during a loading state if you'd like, like this:
// initialize the loading var, set to false
$scope.loading = false;
// create a reuseable update function, and inside use a promise for the ajax call,
// which is running inside the `Factory`
$scope.updateList = function() {
$scope.loading = true;
MyService.getRecent(10).then(function(res) {
$scope.loading = false;
$scope.things = res.data;
});
};
$scope.updateList();
The error in the console shows two issues with your code:
CORS is not enabled in your api. To fix this you need to enable CORS using Access-Control-Allow-Origin header to your rest api.
Unhandled rejection error, as the way you are handling errors with '.error()' method is deprecated.
'Promise.error()' method is deprecated according to this and this commit in Angular js github repo.
Hence you need to change the way you are handling errors as shown below :
$http().then(successCallback, errorCallback);
function successCallback (res) {
return res;
}
function errorCallback (err) {
return err;
}
One more thing in your code which can be avoided is you have defined a new promise and resolving it using $q methods, which is not required. $http itself returns a promise by default, which you need not define again inside it to use it as a Promise. You can directly use $http.then().

vuejs2 reusable code in N tabs

I have 5 tabs with the same user's data. Each tab has an input to search by term. How can reuse code for fetching users and searching them in opened tab. Code is in this JSFiddle:
var listing = Vue.extend({
data: function () {
return {
query: '',
list: [],
user: '',
}
},
computed: {
computedList: function () {
var vm = this;
return this.list.filter(function (item) {
return item.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
})
}
},
created: function () {
this.loadItems();
},
methods: {
loadItems: function () {
this.list = ['mike','bill','tony'],
},
}
});
var list1 = new listing({
template: '#users-template'
});
var list2 = new listing({
template: '#users-template2'
});
Vue.component('list1', list1);
Vue.component('list2', list2)
var app = new Vue({
el: ".lists-wrappers",
});
query - string of term to search
ComputedList - array of filtered data by search term.
But getting error for "query" and "ComputedList".
[Vue warn]: Property or method "query" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in root instance).
You were really close with what you had. The reason for the query error is you were using query in what looked like, to Vue, the root instances scope. You shouldn't put templates inside of other templates. Always have them outside of it (preferably as a string in your component definition).
You can read about that a bit here: https://vuejs.org/guide/components.html#DOM-Template-Parsing-Caveats
Here's how I'd approach your situation: https://jsfiddle.net/crswll/apokjqxx/6/

Using callbacks in a non-async-context

I have a service with a method called "getGmapsDistance()". Here im using the google maps api to get the distance between an origin an an destination.
export default Ember.Service.extend({
getShortestDistanceInMeters: function(location) {
var service = new google.maps.DistanceMatrixService();
service.getDistanceMatrix({
...
}, this.callback); //<<<<<< !!!
},
callback: function(response, status) {
....
}
});
In my controller if got a array with locations and now I want to iterate over it and want check each element if the distance is <= the max destination.
locationsNearby: Ember.computed('locations', function() {
//...
var filteredResult = [];
locations.forEach(function(locat) {
if (this.get('distanceService').getShortestDistanceInMeters(locat) <= maxDistance) {
filteredResult.pushObject(locat);
}
});
return filteredResult;
})
Unfortunately the GMaps API for distance calculation uses a callback so the request is async.
How can I solve that problem?
You can not make an async call synchronous! This is an javascript language limitation and is important to understand! Javascript has only one thread, so this can't be changed by a library or so!
The fancy new way to handle callbacks are Promises.
You really really should checkout the specifications!
It's one of the most beautiful specifications you will ever read!
Ember uses Promises heavily! For example a routes model hook waits for a Promise to resolve before going on with the transition.
In your case you want to update the computed property when the promise resolves. Because ember-data causes this to happen often they provide two fancy classes: PromiseObject and PromiseArray. A computed property depending on a computed property that returns a PromiseObject/Array will recompute when the promise resolves:
locationsNearby: Ember.computed('locations', {
get() {
let promise = Ember.RSVP.all(this.get('locations').map(location => Ember.RSVP.hash(({
location,
distance: this.get('distanceService').getShortestDistanceInMeters(location)
})))).then(hashs => hashs.filter(hash => hash.distance <= maxDistance).map(hash => hash.location));
return DS.PromiseArray.create({promise});
}
})
To explain it a little:
I build an array with hash's of the location and a promise to the distance:
let locationsWithDistancePromise = this.get('locations').map(location => {
distance: this.get('distanceService').getShortestDistanceInMeters(location),
location
})
Then I use RSVP.hash on all of them to get an array of promises that will resolve to an array of hashes with distance and location:
let hashPromiseArr = locationsWithDistancePromise.map(h => Ember.RSVP.hash(h));
Now I use Ember.RSVP.all to get an promise that will resolve to an array of hashes with location and distance:
let hashArrPromise = Ember.RSVP.all(hashPromiseArr);
An finally I .then on the promise and filter the nearby locations. Also I map the hash to a array of locations.
let promise = hashArrPromise.then(hashs => {
return hashs.filter(hash => hash.distance <= maxDistance)
.map(hash => hash.location);
});
And wrap it as an PromiseArray
return DS.PromiseArray.create({promise});
You can just loop over this Computed Property from handlebars with {{#each}} or use it in another Computed Property:
allNearbyLocations: Ember.computed('locationsNearby.[]', {
get() {
return this.get('locationsNearby').toArray().join(' - ');
}
}
Of course you need to rewrite getShortestDistanceInMeters so that it returns a Promise:
getShortestDistanceInMeters(location) {
var service = new google.maps.DistanceMatrixService();
return new Ember.RSVP.Promise((resolve, reject) => {
service.getDistanceMatrix({
//...
}, (response, status) => {
if(status.error) {
reject(response);
} else {
resolve(response);
}
});
});
}

How to load google maps api asynchronously in Angular2

Usually in a plain javascript site, I can use the following script to reference google maps api and set the callback function with initMap.
<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap"></script>
What I observed is the initMap function in the plain javascript site is under the window scope, and it can be referenced in the script parameter settings - ?callback=initMap, but once I write a component in angular2 with a component method called initMap, the initMap will be under the scope of my component. Then the async loading script I set up in the index will not be able to catch my component initMap method.
Specifically, I 'd like to know how to achieve the same thing in Angular2?
PS: I know there is an angular2-google-maps component available in alpha via npm, but it currently is shipped with limited capability, so I 'd like to know how to load it in an easier way without using another component so I can just use google maps api to implement my project.
I see you don't want another component, but polymer has components that work well with google apis. I have angular2 code that uses the polymer youtube data api. I had help getting it setup. Here is the plunker that got me started. I think the hardpart is getting setup for that callback, I'm sure you can do it without polymer. The example shows the tricky part an angular service is used to hook everything up.
const url = 'https://apis.google.com/js/client.js?onload=__onGoogleLoaded'
export class GoogleAPI {
loadAPI: Promise<any>
constructor(){
this.loadAPI = new Promise((resolve) => {
window['__onGoogleLoaded'] = (ev) => {
console.log('gapi loaded')
resolve(window.gapi);
}
this.loadScript()
});
}
doSomethingGoogley(){
return this.loadAPI.then((gapi) => {
console.log(gapi);
});
}
loadScript(){
console.log('loading..')
let node = document.createElement('script');
node.src = url;
node.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(node);
}
}
I came across this while trying to develop a progressive web app, i.e. where there was a possibility of not being online. There was an error in the code examples: onload in the google maps script should be callback. So my modification of user2467174 led to
map-loader.service.ts
const url = 'http://maps.googleapis.com/maps/api/js?key=xxxxx&callback=__onGoogleLoaded';
#Injectable()
export class GoogleMapsLoader {
private static promise;
public static load() {
// First time 'load' is called?
if (!GoogleMapsLoader.promise) {
// Make promise to load
GoogleMapsLoader.promise = new Promise( resolve => {
// Set callback for when google maps is loaded.
window['__onGoogleLoaded'] = (ev) => {
resolve('google maps api loaded');
};
let node = document.createElement('script');
node.src = url;
node.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(node);
});
}
// Always return promise. When 'load' is called many times, the promise is already resolved.
return GoogleMapsLoader.promise;
}
}
And then I have a component with
import { GoogleMapsLoader } from './map/map-loader.service';
constructor() {
GoogleMapsLoader.load()
.then(res => {
console.log('GoogleMapsLoader.load.then', res);
this.mapReady = true;
})
And a template
<app-map *ngIf='mapReady'></app-map>
This way the map div is only put into the dom if online.
And then in the map.component.ts we can wait until the component is placed into the DOM before loading the map itself.
ngOnInit() {
if (typeof google !== 'undefined') {
console.log('MapComponent.ngOnInit');
this.loadMap();
}
}
Just in case you'd like to make it a static function, which always returns a promise, but only gets the api once.
const url = 'https://maps.googleapis.com/maps/api/js?callback=__onGoogleMapsLoaded&ey=YOUR_API_KEY';
export class GoogleMapsLoader {
private static promise;
public static load() {
// First time 'load' is called?
if (!GoogleMapsLoader.promise) {
// Make promise to load
GoogleMapsLoader.promise = new Promise((resolve) => {
// Set callback for when google maps is loaded.
window['__onGoogleMapsLoaded'] = (ev) => {
console.log('google maps api loaded');
resolve(window['google']['maps']);
};
// Add script tag to load google maps, which then triggers the callback, which resolves the promise with windows.google.maps.
console.log('loading..');
let node = document.createElement('script');
node.src = url;
node.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(node);
});
}
// Always return promise. When 'load' is called many times, the promise is already resolved.
return GoogleMapsLoader.promise;
}
}
This is how you can get the api in other scripts:
GoogleMapsLoader.load()
.then((_mapsApi) => {
debugger;
this.geocoder = new _mapsApi.Geocoder();
this.geocoderStatus = _mapsApi.GeocoderStatus;
});
This is what I'm currently using:
loadMapsScript(): Promise<void> {
return new Promise(resolve => {
if (document.querySelectorAll(`[src="${mapsScriptUrl}"]`).length) {
resolve();
} else {
document.body.appendChild(Object.assign(document.createElement('script'), {
type: 'text/javascript',
src: mapsScriptUrl,
onload: doMapInitLogic();
}));
}
});
}
See my more comprehensive instructions here