I'm working on a mobile app with Phonegap which will use Geolocation. I used Geolocation before but this time I considered that creating a new object wrapper for it would be better as a large part of the functionality is based on this and don't want to end up with messy code.
The solution is probably straight forward but it beats me as I've never done anything very advanced in JS using objects. Here's the code (removed unneeded parts) at the moment:
function Geolocation(maximumAge, accurate) {
this.settings = {
'maximumAge': maximumAge,
'accurate': accurate
}
}
Geolocation.prototype = {
position: {
latitude: null,
longitude: null,
altitude: null,
accuracy: null,
altitudeAccuracy: null,
heading: null,
speed: null
},
lastCheck: null,
watchId: null,
onSuccess: function(position) {
this.position.latitude = position.coords.latitude;
this.position.longitude = position.coords.longitude;
this.position.altitude = position.coords.altitude;
this.position.accuracy = position.coords.accuracy;
this.position.altitudeAccuracy = position.coords.altitudeAccuracy;
this.position.heading = position.coords.heading;
this.position.speed = position.coords.speed;
this.lastCheck = new Date(position.timestamp);
},
onError: function(error) {
console.log(error.code+' '+error.message);
},
getCoordinates: function() {
if (this.watchId == null) {
this.watchId = navigator.geolocation.watchPosition(this.onSuccess, this.onError, { maximumAge: this.settings.maximumAge, enableHighAccuracy: this.settings.accurate});
}
return {
latitude: this.position.latitude,
longitude: this.position.longitude
};
}
};
As you probably noticed already, when I call getCoordinates(), the callback success function is (I'm guessing) out of the scope of the object, therefore not knowing what "this" is... Any idea of how to get around this or what the correct implementation would be?
Thank you for your time!
Later edit: I know I need to modify the function to return the coordinates only after the position was found. Not sure on how to do this at the moment, but if you have any tips that would be great!
OK, I figured it out, partly with help from this other thread regarding binding the scope for the callback function: JavaScript Callback Scope
To return the coordinates I'm using a callback function. Included the code below and I'm marking this as community wiki - but if anyone has any suggestions please go ahead and make them. Everything seems to be working fine, but maybe I'm not doing something right here.
function Geolocation(maximumAge, accurate) {
this.settings = {
'maximumAge': maximumAge,
'accurate': accurate
}
}
Geolocation.prototype = {
position: {
latitude: null,
longitude: null,
altitude: null,
accuracy: null,
altitudeAccuracy: null,
heading: null,
speed: null
},
lastCheck: null,
callback: null,
watchId: null,
onSuccess: function(position) {
this.position.latitude = position.coords.latitude;
this.position.longitude = position.coords.longitude;
this.position.altitude = position.coords.altitude;
this.position.accuracy = position.coords.accuracy;
this.position.altitudeAccuracy = position.coords.altitudeAccuracy;
this.position.heading = position.coords.heading;
this.position.speed = position.coords.speed;
this.lastCheck = new Date(position.timestamp);
var pos = {
latitude: this.position.latitude,
longitude: this.position.longitude
};
// call callback with position and accuracy parameters
this.callback(pos, this.position.accuracy);
},
onError: function(error) {
console.log(error.code+' '+error.message);
},
getCoordinates: function(callback) {
// Helper function to bind scope to callback function as seen at https://stackoverflow.com/questions/183214/javascript-callback-scope
function bind(scope, fn) {
return function () {
fn.apply(scope, arguments);
};
}
// Assign the callback function to the local member
this.callback = callback;
// watchPosition is a method that gets called each time the position changes. Making sure it's only getting called once (watchId can be used to stop the function when necessary
if (this.watchId == null) {
// notice usage of the bind function here
this.watchId = navigator.geolocation.watchPosition(bind(this, this.onSuccess), bind(this, this.onError), { maximumAge: this.settings.maximumAge, enableHighAccuracy: this.settings.accurate});
}
}
};
Usage example:
var geo = new Geolocation(3000, true);
geo.getCoordinates(createMap);
function createMap(position, accuracy) {
// Your code here
}
Related
I have a tooltip control I've written that works very nicely in Vue 3, but I need a mechanism to fire off to all other instances to tell them to close. There are delays on close, so I'm occasionally getting two tooltips to show up at the same time.
This method, which was a crutch I've used in the past, is not allowed by the compiler / build tools. I can full well understand why, but I don't know the right way:
tooltipManager: function() {
if (!window.TooltipManager) {
function tooltipManager() {
let _data = {
tooltipIndex: 0,
callbacks: {}
};
return {
register: function (callback) {
let id = "tooltip_" + _data.tooltipIndex;
_data.tooltipIndex++;
_data.callbacks[id] = callback;
return id;
},
closeOpenPopups: function (id) {
Object.keys(_data.callbacks).forEach(key => {
if (id !== key) {
_data.callbacks[key]();
}
});
},
destroy: function (id) {
delete _data.callbacks[id];
}
};
}
window.TooltipManager = tooltipManager();
}
return window.TooltipManager()
},
The first thing I tried but didn't work was a service which I imported:
export default class TooltipManager {
constructor() {
if(! TooltipManager.instance){
this._data = {
tooltipIndex: 0,
callbacks: {}
};
}
}
register (callback) {
let id = "tooltip_" + this._data.tooltipIndex;
this._data.tooltipIndex++;
this._data.callbacks[id] = callback;
return id;
}
closeOpenPopups(id) {
Object.keys(this._data.callbacks).forEach(key => {
if (id !== key) {
this._data.callbacks[key]();
}
});
}
destroy(id) {
delete this._data.callbacks[id];
}
}
Ok, I was close with the first service. It should be written this way, and I'm going to leave my console.logs in that confirmed that it is indeed a singleton even though it is running on different tooltips.
class TooltipManager {
constructor() {
if(! TooltipManager.instance){
this._data = {
tooltipIndex: 0,
callbacks: {}
};
console.log("got new instance");
} else {
console.log("got old instance");
}
}
register (callback) {
let id = "tooltip_" + this._data.tooltipIndex;
this._data.tooltipIndex++;
this._data.callbacks[id] = callback;
console.log("registered key: " + id);
return id;
}
closeOpenPopups(id) {
Object.keys(this._data.callbacks).forEach(key => {
if (id !== key) {
console.log("closed: " + key);
this._data.callbacks[key]();
}
});
}
destroy(id) {
delete this._data.callbacks[id];
}
}
export default new TooltipManager();
I got the following from console.logs:
got new instance
TooltipManager.js:19 registered key: tooltip_0
TooltipManager.js:19 registered key: tooltip_1
TooltipManager.js:19 registered key: tooltip_2
TooltipManager.js:19 registered key: tooltip_3
TooltipManager.js:26 closed: tooltip_0
TooltipManager.js:26 closed: tooltip_1
TooltipManager.js:26 closed: tooltip_2
TooltipManager.js:26 closed: tooltip_3
And indeed it solved the problem of ghost tooltips when one pops up before the other closes with a delay to prevent bounce.
In the tooltip tool I wrote, which I will later post here as an example of how easy Vue3 Teleport makes something like this to write. I want to test it a little longer before I show it off.
I just need to:
mounted() {
this.data.tooltipId = TooltipManager.register(this.forceHide);
And, which also shows some state data I use to keep track of this:
methods: {
forceHide: function() {
if (this.data.isDisplayed) {
this.data.style = '{top: -1000px, left: -1000px}';
}
this.data.hideRequested = false;
this.data.showRequested = false;
this.data.isDisplayed = false;
},
Now the next thing maybe using Vuex for this, but I may leave this in as an alternative method so it's not dependent on it.
I am trying to write a custom transformer stream in Node.js 12. Specifically I take in json objects in a stream (Database driver) and return an object transformed. But my transformer functions never get called. I have also tried this by overriding the streams.Transform class.
I want to make the custom transform generic so I am enclosing it in a closure, in order to pass in a generic function:
// transformStream.js
var through2, transformStream;
through2 = require('through2');
transformStream = (handler) => {
// Through2 in Object Mode
_transformStream = through2.obj((data, encoding, callback) => {
console.log(data); // Never called
this.push(handler(data));
return callback();
// also tried:
// return callback(null, handler(data));
});
return _transformStream;
};
module.exports = transformStream;
Here is the test rig to try it out:
// transformStream.test.js
var jsonStream, through2, transformFunc, transformStream, transformer;
through2 = require('through2');
transformStream = require('./transformStream.js');
// Convert back to a string buffer for console output.
jsonStream = through2.obj(function(chunk, encoding, callback) {
return callback(null, JSON.stringify(chunk, null, 2) + '\n');
});
transformFunc = function(data) {
console.log("called with data", data); // Never called!
data.c = data.a * data.b;
return data;
};
// deviceStream.pipe(process.stdout)
transformer = transformStream(transformFunc);
transformer.on("error", function(error) {
return console.error(`Error in Transform: ${error.message}`);
});
transformer.pipe(jsonStream).pipe(process.stdout);
transformer.push({
a: 1,
b: 2
});
The stream appears to work, never calls the actual transform code, and always returns just the original json:
{
A: 1,
b: 2
}
in the console.
I expect to see:
{ a: 1, b:2, c:2 }
EDIT: I also have another version using classes (bypassing through2) with the same exact issue:
module.exports = TransformStream = class TransformStream extends Transform {
constructor(handler, {debug, highWaterMark, ...options}) {
super({
highWaterMark: highWaterMark || 10,
autoDestroy: true,
emitClose: true,
objectMode: true,
debug: true
});
this._transform = this._transform.bind(this);
this.handler = handler;
this.debug = debug;
this.options = options;
}
};
TransformStream.prototype._transform = (data, encoding, callback) => {
if (this.debug) {
console.log(data);
}
return callback(null, this.handler(data));
};
Apparently doing a TransformClass.push calls the internal output buffer for the stream. The stream is actually expecting a .write({}) method, and appropriately calls the _transform functions.
In the test rig the final test should be:
transformer.write({
a: 1,
b: 2
});
I'm having this weird issue where when I get the result of a HTML geolocation call, I cant bind it to Vue data, but I can console.log it successfully.
Vue method:
initGeolocation: function() {
if( navigator.geolocation )
{
// Call getCurrentPosition with success and failure callbacks
navigator.geolocation.getCurrentPosition( success, fail );
}
else
{
return;
}
function success(position)
{
console.log(position.coords.latitude); //works
this.lat = position.coords.latitude; //does not work
}
function fail()
{
console.log('fail')
}
},
mounted() {
this.lat = this.initGeolocation(); // does not work
console.log(this.initGeolocation()) // returns undefined
},
Data:
lat: '',
long: '',
Any help would be very much appreciated.
The word this refers to the scope of the function. When you nest another function inside, the word this now refers to the new/ smaller scope so this.lat is no longer defined. So we capture the out this in vm and use it inside functions.
methods: {
initGeolocation: function() {
var vm = this;
if( navigator.geolocation)
{
// Call getCurrentPosition with success and failure callbacks
navigator.geolocation.getCurrentPosition( success, fail );
}
else
{
return;
}
function success(position)
{
vm.lat = position.coords.latitude; //should work now!!
}
function fail()
{
console.log('fail')
}
}
},
mounted() {
this.initGeolocation();
},
In your mounted you assign this.lat with the return of your initGeolocation() method. However this method does not return any data if it succeeds. Instead you write your result into this.lat which then will be overridden again by the void result of your method. So make sure your method initGeolocation either returns your geolocation data or you change your mounted method to call the method without assigning the return value to this.lat.
Also it seems like you just added the initGeolocation method to your component. Look into the methods property of vue components where this would belong.
So try this instead:
mounted() {
this.initGeolocation();
console.log(this.initGeolocation());
},
methods: {
initGeolocation: function() {
if( navigator.geolocation)
{
// Call getCurrentPosition with success and failure callbacks
navigator.geolocation.getCurrentPosition( success, fail );
}
else
{
return;
}
function success(position)
{
this.lat = position.coords.latitude; //does not work
}
function fail()
{
console.log('fail')
}
}
}
Making my first react app. I want to update the google maps api based on the user's location.
I am receiving the error "this is undefined". I understand using .bind(this) and wrapping in an arrow function but think this case is a bit different because I am setting state inside a nested function:
constructor(props) {
super(props);
this.state = {zip: null, lat: 40.5304 , lng: -100.6534 , zoom: 3.8 };
this.updateCurrentPosition= this.updateCurrentPosition.bind(this);
}
//...
updateCurrentPosition = () => {
navigator.geolocation.getCurrentPosition(success, error);
function success(pos) {
this.setState(`{lat: ${pos.coords.latitude}, lng: ${pos.coords.longitude}, zoom: ${3.8}`)
}
function error(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
};
}
ops = () => {
return {
center: { lat: this.state.lat, lng: this.state.lng },
zoom: this.state.zoom
}
};
Arrow functions automatically bind functions to the parent class. If a function is not binded, or not an arrow function, "this" will refer only to the function itself, even if it is nested. Your success function (and failure function too) is not bound to the parent class, as you have neither binded it or defined it as an arrow function.
The problem is that this is undefined under strict mode in Javascript. You can refer to this paragraph to read more http://2ality.com/2014/05/this.html
For your particular question, when you defined success and error, the two functions are not bound to parents.
The following modification by defining the functions as arrow functions will resolve your issue.
const success = (pos) => {
this.setState(`{lat: ${pos.coords.latitude}, lng: ${pos.coords.longitude}, zoom: ${3.8}`)
}
const error = (err) => {
console.warn(`ERROR(${err.code}): ${err.message}`);
};
So, I've instead passed the functions directly as arguments to the getCurrentPosition method and it seems to work fine.
updateCurrentPosition = () => {
navigator.geolocation.getCurrentPosition( (pos) => {
this.setState({ lat: pos.coords.latitude, lng: pos.coords.longitude, zoom: 1 })
},
(err) => {
console.warn(`ERROR(${err.code}): ${err.message}`)
}
)
}
I'm trying to get the user's location (that works) and set it to the current state in a React component (this part doesn't). I've looked through a few answers on here and can't tell what I'm doing wrong.
Here's what I have:
class Container extends Component {
constructor() {
super()
this.state = {
location: {
lat: 0,
lng: 0
}
}
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(
(position) => {
let lat = position.coords.latitude
let lng = position.coords.longitude
console.log("getCurrentPosition Success " + lat + lng) // logs position correctly
this.setState({
location: {
lat: lat,
lng: lng
}
})
},
(error) => {
this.props.displayError("Error dectecting your location");
console.error(JSON.stringify(error))
},
{enableHighAccuracy: true, timeout: 20000, maximumAge: 1000}
)
}
render() {
const location = this.state.location
return (
<div>
<Map center={location}/>
</div>
)
}
}
It looks similar to what other people have, and I've tried a few different ways, but I can't get the state to set. Is there something I'm missing or doing wrong?
The setState command is working fine.
Note that the get location is an async. call, and therefore the render() will be called twice. The first time it call, the lat/lng is zero.
You can add a logic to check it is zero and return null, if you want to render the output after getting the lat/lng from the geolocation services.