JavaScript variable scope issue when passing data - variables

I am fairly new to JavaScript, and I have been having issue with the scope of what I think is a global variable.
Here is where I receive the data from the user via Socket.IO: (server.js)
var locationData = {lat: '-20', long: '20'};
var latNumber;
var longNumber;
//SocketIO data
var chat = require('express')();
var http = require('http').Server(chat);
var io = require('socket.io')(http);
io.sockets.on('connection', function (socket) {
socket.on('new message', function (msg) {
locationData = msg;
latNumber = parseFloat(locationData.lat);
longNumber = parseFloat(locationData.long);
io.emit('message', msg.message);
});
});
http.listen(5670, function(){
console.log('listening on *:5670');
});
io.on('connection', function(socket){
console.log('a user connected');
socket.on('disconnect', function(){
console.log('user disconnected');
});
});
function getLat(){
return latNumber;
}
function getLong(){
return latNumber;
}
Here is the HTML script where I want to receive that data: (HelloWorld.html)
<script type="text/javascript" src="/server.js"></script>
<script>
var viewer = new Cesium.Viewer('cesiumContainer');
var citizensBankPark = viewer.entities.add({
name : 'Test',
position : Cesium.Cartesian3.fromDegrees(getLat(),getLong()),
point : {
pixelSize : 5,
color : Cesium.Color.RED,
outlineColor : Cesium.Color.WHITE,
outlineWidth : 2
},
label : {
text : 'Test',
font : '14pt monospace',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth : 2,
verticalOrigin : Cesium.VerticalOrigin.BOTTOM,
pixelOffset : new Cesium.Cartesian2(0, -9)
}
});
viewer.zoomTo(viewer.entities);
</script>
I am trying to read the data from latNumber and longNumber defined and set in server.js into the position assignment in HelloWorld.html. The problem I am having is that once the data gets to HelloWorld.html it is undefined, and gives an error in the browser.
Now what I am not sure about is why is it undefined. Logging the latNumber and longNumber variables in server.js work fine and have data in them. Also changing the return statements in the getLat() getLong() to a hardcoded variable such as 15 works fine, and passes it onto HelloWorld.html, where it then does what it is supposed to do. It only errors out when I combine the two together!
Any help or pointers would be much appreciated! Thanks!

You've got quite a mix of issues here. Your server.js file doesn't look like client-side code at all, it looks like it's supposed to be running the server via Node.js. Are you using Node.js to run server.js to listen on port 5670, as this code suggests?
If so, good, but get rid of the <script src="/server.js"> reference on the client code. You don't want the client reading the server's source code (and ideally it shouldn't even be served, but that's a different topic).
So now you have a new problem: Your server has getLat and getLon functions that no longer exist at all on the client. You need to transmit the information from server to client.
In server.js, you can add some code to make these numbers available via REST:
chat.get('/location', function(req, res, next) {
var response = {
lat: latNumber,
lon: lonNumber
};
res.type('application/json');
res.send(JSON.stringify(response));
});
This server code will listen for requests for the URL /location and respond with a JSON string containing the numbers.
Next, your client code should request these values from the server, asynchronously (meaning we don't lock up the browser's UI while waiting for the server to respond). This is done like so:
var viewer = new Cesium.Viewer('cesiumContainer');
Cesium.loadJson('/location').then(function(data) {
viewer.entities.add({
name : 'Test',
position : Cesium.Cartesian3.fromDegrees(data.lon, data.lat),
point : {
pixelSize : 5,
color : Cesium.Color.RED,
outlineColor : Cesium.Color.WHITE,
outlineWidth : 2
},
label : {
text : 'Test',
font : '14pt monospace',
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
outlineWidth : 2,
verticalOrigin : Cesium.VerticalOrigin.BOTTOM,
pixelOffset : new Cesium.Cartesian2(0, -9)
}
});
viewer.zoomTo(viewer.entities);
});
In the above code, loadJson returns a promise to get the data, and we add a callback function to execute when the promise resolves. The callback receives the server's data as a parameter, and builds a new entity. You can see the entity's position is based on data received from the server.
If the server's idea of the location changes, you may need to have the client periodically poll for the new location, or you can use a number of different technologies (long-polling, server-side push, EventSource, WebSockets, etc.) to get the client to update. But for starters, just reload the client page to see the new location.

Related

A better way to handle async saving to backend server and cloud storage from React Native app

In my React Native 0.63.2 app, after user uploads images of artwork, the app will do 2 things:
1. save artwork record and image records on backend server
2. save the images into cloud storage
Those 2 things are related and have to be done successfully all together. Here is the code:
const clickSave = async () => {
console.log("save art work");
try {
//save artwork to backend server
let art_obj = {
_device_id,
name,
description,
tag: (tagSelected.map((it) => it.name)),
note:'',
};
let img_array=[], oneImg;
imgs.forEach(ele => {
oneImg = {
fileName:"f"+helper.genRandomstring(8)+"_"+ele.fileName,
path: ele.path,
width: ele.width,
height: ele.height,
size_kb:Math.ceil(ele.size/1024),
image_data: ele.image_data,
};
img_array.push(oneImg);
});
art_obj.img_array = [...img_array];
art_obj = JSON.stringify(art_obj);
//assemble images
let url = `${GLOBAL.BASE_URL}/api/artworks/new`;
await helper.getAPI(url, _result, "POST", art_obj); //<<==#1. send artwork and image record to backend server
//save image to cloud storage
var storageAccessInfo = await helper.getStorageAccessInfo(stateVal.storageAccessInfo);
if (storageAccessInfo && storageAccessInfo !== "upToDate")
//update the context value
stateVal.updateStorageAccessInfo(storageAccessInfo);
//
let bucket_name = "oss-hz-1"; //<<<
const configuration = {
maxRetryCount: 3,
timeoutIntervalForRequest: 30,
timeoutIntervalForResource: 24 * 60 * 60
};
const STSConfig = {
AccessKeyId:accessInfo.accessKeyId,
SecretKeyId:accessInfo.accessKeySecret,
SecurityToken:accessInfo.securityToken
}
const endPoint = 'oss-cn-hangzhou.aliyuncs.com'; //<<<
const last_5_cell_number = _myself.cell.substring(myself.cell.length - 5);
let filePath, objkey;
img_array.forEach(item => {
console.log("init sts");
AliyunOSS.initWithSecurityToken(STSConfig.SecurityToken,STSConfig.AccessKeyId,STSConfig.SecretKeyId,endPoint,configuration)
//console.log("before upload", AliyunOSS);
objkey = `${last_5_cell_number}/${item.fileName}`; //virtual subdir and file name
filePath = item.path;
AliyunOSS.asyncUpload(bucket_name, objkey, filePath).then( (res) => { //<<==#2 send images to cloud storage with callback. But no action required after success.
console.log("Success : ", res) //<<==not really necessary to have console output
}).catch((error)=>{
console.log(error)
})
})
} catch(err) {
console.log(err);
return false;
};
};
The concern with the code above is that those 2 async calls may take long time to finish while user may be waiting for too long. After clicking saving button, user may just want to move to next page on user interface and leaves those everything behind. Is there a way to do so? is removing await (#1) and callback (#2) able to do that?
if you want to do both tasks in the background, then you can't use await. I see that you are using await on sending the images to the backend, so remove that and use .then().catch(); you don't need to remove the callback on #2.
If you need to make sure #1 finishes before doing #2, then you will need to move the code for #2 intp #1's promise resolving code (inside the .then()).
Now, for catching error. You will need some sort of error handling that alerts the user that an error had occurred and the user should trigger another upload. One thing you can do is a red banner. I'm sure there are packages out there that can do that for you.

ExpressJS - Small curiosity

My thing is a small project.
In main what it does is that the "server" will get a call from the link directly what will run some functions that will update the database and the data that has to be shown.
I will show what I mean:
function updateData(){
connection.query(`SELECT * FROM muzica WHERE melodie = "${updateList()}"`, function (error, rezultat, fields) {
if (error) {console.log('err la selectare')};
//express output
let data = {
melodie: rezultat[0].melodie,
likes: rezultat[0].likes
}
console.log(data.likes);
app.get('/like', (req,res) =>{
res.json(`${data.likes}`);
});
}
setInterval(()=>{
updateData();
}, 20000)
Uhh, how to explain it, I'm so bad at this...
So, in main, I'm new to back-end work, everything that I did was based on their Documentation as I learn way faster by my needs than some guides and so on.
So, when I or someone does my http://website/like it should show just data.likes, cause that is all that I need, don't count data.melodie (i will clean that later on) after I finish all the code.
Anyway, whenever I do website/like data.likes is not updating to the new database data.likes.
For example, data.likes before were 5, in a few minutes it can be 2 but whenever I call website/like show "5" than its new value 2.
Don't be hash on me, I'm new and I want to learn as much as I can, but I can't understand the above case, by my logic it should ALWAYS show what its in database when it refreshes each 10 seconds(I run this in localhost so I will not stress any online server).
But if there is any better way to check for databases update than "setInterval" please notice me.
It's hard to learn alone without a mentor or someone else to talk about this domain.
Thank you for your time!
Kind regards,
Pulsy
You have things a bit inside out. A request handler such as app.get('/like', ...) goes at the top level and you only ever call it once. What that statement does is register an event handler for any incoming requests with the /like path. When the server receives an incoming request for /like, it will then call the function for this route handler.
You then put inside that route handler the code that you want to run to generate the response and send the response back to the client.
app.get('/like', (req, res) => {
connection.query(`SELECT * FROM muzica WHERE melodie = "${updateList()}"`, function (error, rezultat, fields) {
if (error) {
console.log(error);
res.sendStatus(500);
} else {
//express output
let data = {
melodie: rezultat[0].melodie,
likes: rezultat[0].likes
}
res.json(data);
}
});
});
The endpoints need to be outside of any functions in express.
For example, if you look at the express "hello world" example here, you will see that they have a basic app that only has a single GET endpoint defined which is "/" so you would access it by running "localhost/" or "127.0.0.1/".
In your case, you want your endpoint to be "/like", so you must define something like:
const express = require('express')
const app = express()
const port = 3000
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
app.get('/like', (req, res) => {
// do database stuff and assign data variable
// res.json(data);
}

Unable to find element and send keys

So just a brief overview, I'm unable to send keys to a edit text field for android. I've successfully sent keys to this element via browser but in order to test the mobile application fully, I'd like to run e2e tests on a device using Appium.
I've successfully got Appium to click button elements but am having a hard time getting it to send keys to an edit field element.
Am I able to find elements by model when testing with android as I have set in my forgot-pin-page.js?
pin-reset-page.js
var pinResetPage = function() {
describe('The Reset Pin Flow', function () {
forgotPinPage = forgotPinPageBuilder.getForgotPinPage(),
describe('The Forgot Pin Page', function () {
it('should allow the user to enter their MSISDN and continue',
function () {
forgotPinPage.enterMsisdn('123123123');
forgotPinPage.doForgotPin();
expect(securityPage.isOnSecurityPage()).toBe(true);
});
});
}
forgot-pin-page.js
'use strict';
var ForgotPin = function () {
var forgotPinPageContent = element(by.id('forgot')),
msisdnInput = element(by.model('data.msisdn')),
return {
enterMsisdn: function (msisdn) {
return msisdnInput.sendKeys(msisdn);
}
};
module.exports.getForgotPinPage = function () {
return new ForgotPin();
};
The error i'm getting is
? should allow the user to enter their MSISDN and continue
- Error: Timeout - Async callback was not invoked within timeout spe
cified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Not sure if this is the correct solution but it worked for me. I downgraded jasmine2 to jasmine and that seemed to resolved the async timeouts I was having.

backbone view in router lost after creation

When I try to associate my router's public variable this.currentView to a newly created view, the view gets lost, the public variable is null instead of containing the newly created view.
var self=this;
var watchListsCollection = new WatchlistCollection;
watchListsCollection.url = "watchlists";
user.fetch().done(function() {
watchListsCollection.fetch().done(function () {
loggedUser.fetch().done(function () {
self.currentView = new UserView(user, watchListsCollection,loggedUser);
});
});
});
alert(this.currentView); //null
The fetch() calls you do are firing asynchronous AJAX requests, meaning the code in your done handlers are not going to be executed untill the server calls return. Once you've executed user.fetch() the browser will fire off a request and then continue running your program and alert this.currentView without waiting for the requests to finish.
The sequence of events is basically going to be
call user.fetch()
alert this.currentView
call watchListsCollection.fetch()
call loggedUser.fetch()
set the value of self.currentView
You will not be able to see the value of your currentView before the last server request have completed.
If you change your code to
var self=this;
var watchListsCollection = new WatchlistCollection;
watchListsCollection.url = "watchlists";
user.fetch().done(function() {
watchListsCollection.fetch().done(function () {
loggedUser.fetch().done(function () {
self.currentView = new UserView(user, watchListsCollection,loggedUser);
alertCurrentView();
});
});
});
function alertCurrentView() {
alert(this.currentView);
}
You should see the correct value displayed. Now, depending on what you intend to use your this.currentView for that might or might not let you fix whatever issue you have, but there's no way you're not going to have to wait for all the requests to complete before it's available. If you need to do something with it straight away you should create your UserView immediately and move the fetch() calls into that view's initialize().
fetch() is asynchronous, but you check your variable right after you've started your task. Probably these tasks, as they supposed to be just reads, should be run in parallel. And forget making a copy of this, try _.bind instead according to the Airbnb styleguide: https://github.com/airbnb/javascript
var tasks = [];
tasks.push(user.fetch());
tasks.push(watchListsCollection.fetch());
tasks.push(loggedUser.fetch());
Promise.all(tasks).then(_.bind(function() {
this.currentView = new UserView(user, watchListsCollection, loggedUser);
}, this));
or using ES6 generators:
function* () {
var tasks = [];
tasks.push(user.fetch());
tasks.push(watchListsCollection.fetch());
tasks.push(loggedUser.fetch());
yield Promise.all(tasks);
this.currentView = new UserView(user, watchListsCollection, loggedUser);
}

WebRTC - Change device/camera in realtime

I'm having a problem trying to change my camera in real time, It works for the local video, but the remote person cannot see the new camera, and still sees the old one. I tried to stop the stream and init again but still not working. This is just some of my code.
I have searched everywhere and I can't find a solution. Can someone help me out?
function init() {
getUserMedia(constraints, connect, fail);
}
$(".webcam-devices").on('change', function() {
var deviceID = this.value;
constraints.video = {
optional: [{
sourceId: deviceID
}]
};
stream.getTracks().forEach(function (track) { track.stop(); });
init();
});
You need to actually change the track you're sending in the PeerConnection. In Firefox, you can use RTPSender.replaceTrack(new_track); to change without renegotiation (this is being added to the spec now). Otherwise, you need to add the new stream/track to the RTCPeerConnection, and remove the old one, and then process the onnegotiationneeded event and renegotatiate
See one of #jib's fiddles: Jib's replaceTrack() fiddle:
function flip() {
flipped = 1 - flipped;
return pc1.getSenders()[0].replaceTrack(streams[flipped].getVideoTracks()[0])
.then(() => log("Flip! (notice change in dimensions & framerate!)"))
.catch(failed);
}