Thanks for the help in advance.
I'm working on an practice assigment using Phonegap and Javascript. Long story short: I need to use Parse.com to store information about some Lego minifigures. The problem I'm having right now is due mostly to my inexperience in Javascript.
I'm working on letting the user add tags to the figures. The user enters them, separated by comma, and I then split the string. That's working OK.
Now, I need to add the tags that don't exist yet to my database. For this, I search for any tags with that description (using query.find) and then, if it exists, I don't create it, I just modify the relationship. If it doesn't exist, I create it and then modify the relationship.
My problem is: I can't seem to be able to access the tag description (the string) from within the success callback of query.find. I'm pretty sure it's because of the scope. Is there any proper way to access variables from withing a success callback, besides the results array?
My current code is as follows:
var Figure = Parse.Object.extend("Figure");
var Tag = Parse.Object.extend("Tag");
var nombre = $('#nombre').val();
var serie = $('#serie').val();
var figure = new Figure({"Name":nombre,"Series":serie});
var tags = $('#tags').val();
res = tags.split(","); //split the
figure.save().then(function() {
for (var i = 0; i < res.length; i++) { //for each tag
var query = new Parse.Query(Tag); //create the query.
query.equalTo("Description", res[i]);
query.find( {//execute query
success: function(results, res[i]) {
if (results.length > 0){ //if there are results.
var tag = results[0]; //get the tag
var relation_tag = tag.relation("figures"); //get the relation
relation_tag.add(figure); //add figure to relation
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":res[i]});
//ABOVE THIS LINE: res[i] is always undefined.
var relation_tag = new_tag.relation("figures"); //get the relation
relation_tag.add(figure); //add the figure
new_tag.save();
}
},
//error with query
error: function() {
alert("ERROR");
}
});
}
}, function(error) {
alert("No se pudo guardar la figura");
});
In the success callback, res[i] always is undefined, I assume that it's because of the scope.
This is a very common problem in async Javascript programming. You are doing something like this:
for (var i = 0; i < array.length; i++) {
anAsyncFunction(function(result) { // inner function
doSomethingWith(array[i]);
}
}
The problem is that in Javascript functions store outer variables by reference and not by value, which means that a function looks up the value of a variable from an outer scope, when it is executed and not when it is defined. Since the code is async the the inner function is called after the for loop completed and at this point we have i === array.length, so array[i] === array[array.length] === undefined.
To avoid this you can use an immediately invoked function expression (IIFE, pronounced "iffy"):
for (var i = 0; i < array.length; i++) {
anAsyncFunction((function(j) { // IIFE
return function innerFunction(result) { // inner function
doSomethingWith(array[j]); // j instead of i
}
})(i); // passing "value of i"
}
Because the IIFE is invoked immediately, the current value is of i is passed and stored into j and when the inner function executes it uses the correct value.
So in your case this should work:
success: (function(j) { // IIFE
return function(results) {
if (results.length > 0) {
var tag = results[0];
var relation_tag = tag.relation("figures");
relation_tag.add(figure);
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":res[j]}); // j instead of i
var relation_tag = new_tag.relation("figures");
relation_tag.add(figure);
new_tag.save();
}
}
})(i) // pass "value of i"
If you prefer, you can also pass the description itself instead of just the index to the IIFE (I think I would do it that way):
success: (function(description) { // IIFE
return function(results) {
if (results.length > 0) {
var tag = results[0];
var relation_tag = tag.relation("figures");
relation_tag.add(figure);
tag.save();
}
else { //if there are no results, the tag does not exist.
new_tag = new Tag({"Description":description}); // description
var relation_tag = new_tag.relation("figures");
relation_tag.add(figure);
new_tag.save();
}
}
})(res[i]) // pass description
var Tag = Parse.Object.extend("Tag");
var query = new Parse.Query(Tag);
Related
I am making a website based dashboard. one of the functionalities is showing the locations of all customers. when i'm placing these on the map i can't seem to get the pop-up right.
function getCoordinates(locationList) {
for (var i = 0; i < locationList.length; i++) {
if (locationList[i].city != null) {
$http.get('https://api.tiles.mapbox.com/geocoding/v5/mapbox.places/' + locationList[i].city + '.json?access_token=' + access_token)
.success(
function (data) {
var marker = L.marker([data.features[0].center[1], data.features[0].center[0]]).addTo(mymap);
marker.bindPopup(locationList[i].customerName);
}
);
}
}
}
When I use this code the pop-up will only contain the last customer's name in every pop-up.does someone know how to make sure that the attributes of the correct user are used?
That's a closure problem, to fix it you have to move your $http call to a new function like this.
function httpCall(locationList,i){
$http.get('https://api.tiles.mapbox.com/geocoding/v5/mapbox.places/' + locationList[i].city + '.json?access_token=' + access_token)
.success(
function (data) {
var marker = L.marker([data.features[0].center[1], data.features[0].center[0]]).addTo(mymap);
marker.bindPopup(locationList[i].customerName);
}
);
}
After for loop i is always locationList.length - 1. Try to add IIFE with local i. For example you can solve the problem with replacing for loop with locationList.forEach
This is Infamous Loop Problem. Since you are just defining the function and not actually executing it when the for loop ends all the functions will have the same values for index i.
Solution: Is to assign the value to a variable and use this variable inside you success callback.
for (var i = 0; i < locationList.length; i++) {
if (locationList[i].city != null) {
var currLocation = locationList[i]; // assign the data to a variable
$http.get('https://api.tiles.mapbox.com/geocoding/v5/mapbox.places/' + locationList[i].city + '.json?access_token=' + access_token)
.success(
function (data) {
var marker = L.marker([data.features[0].center[1], data.features[0].center[0]]).addTo(mymap);
marker.bindPopup(currLocation.customerName); // use the variable instead of the indexed lookup
}
);
}
}
Let me know if this helps.
It's a scope problem. Your i is updated and later, when you will click on the popup, it will read the last value of i.
You should put your conditional in the for a function which take in parameter the i :
function getCoordinates(locationList) {
for (var i = 0; i < locationList.length; i++) {
conditionalGet(i);
}
function conditionalGet(i) {
if (locationList[i].city != null) {
$http.get('https://api.tiles.mapbox.com/geocoding/v5/mapbox.places/' + locationList[i].city + '.json?access_token=' + access_token)
.success(function (data) {
var marker = L.marker([data.features[0].center[1], data.features[0].center[0]]).addTo(mymap);
marker.bindPopup(locationList[i].customerName);
});
}
}
}
Here I have global variable userId, and i want to update it inside signInUserFunction(), to use is in other function. I have tried to define it using var, window, But all these didn't help. This variable doesn't update. As i see its about AJAX async. So, what can i do with it?
And yes, I know that its not good to make authentication with JS, I am quite new to it. So, I am just creating random methods to improve.
var userId = 1;
function signInUser() {
$.getJSON('http://localhost:8887/JAXRSService/webresources/generic/getAllUsers', function(data) {
var items = [];
var i = 0;
$.each(data, function(firstname, value) {
var str = JSON.stringify(value);
data = JSON.parse(str);
var innerId;
for (p in data) {
innerId = data[p].id;
if ($('#nameSignIn').val() == data[p].first_name && $('#passwordSignIn').val() == data[p].password) { //
userId = innerId;
window.location.href = "content.html";
break;
} else {
i++;
if (i == data.length) {
alert("Ощибка в логине или пароле!")
}
}
}
});
});
}
How are you determining whether or not it has been set? It looks like immediately after you set it, you navigate to a different page. When you get to that page, you will have an entirely new window.
Try alerting the value before navigating away.
EDITED: Here is how you could pass it to the other page (but you shouldn't do this in a real app)
window.userId=innerId;
alert(window.userId);
//this isn't a very secure way to do this. I DON'T recommend this
window.location.href = "content.html?id=" + innerId ;
Then in the other page, you could access it off the document.location:
alert(document.location.toString().split("?id=")[1]);
After reading my comments, you may want to try this:
var userId = 1;
function signInUser(){
$.getJSON('http://localhost:8887/JAXRSService/webresources/generic/getAllUsers', function(data){
var items = [], actors = data.Actors, l = 0;
$.each(actors, function(i, o){
l++;
if($('#nameSignIn').val() === o.first_name && $('#passwordSignIn').val() === o.password){
userId = o.id;
// this will redirect before any other code runs -> location = 'content.html';
if(l === actors.length){
alert('End of Loop');
}
}
});
});
}
signInUser();
I would not store sensitive data in JSON such as passwords. Use a database. There is no need to get all the data at the same time either.
Using the idea #mcgraphix proposed (and giving you the same warning...this would certainly not be the way to transfer data like this in a production environment), here is one way to do it:
function signInUser() {
var url = 'http://localhost:8887/JAXRSService/webresources/generic/getAllUsers';
var userId;
$.getJSON(url, function(data) {
$.each(data.Actors, function(index, actor) {
// Cache the values of the #nameSignIn and #passwordSignIn elements
var name = $('#nameSignIn').val();
var password = $('#passwordSignIn').val();
if (actor.first_name === name && actor.password === password) {
// We have found the correct actor.
// Extract its ID and assign it to userId.
userId = actor.id;
window.location.href = "content.html?userId=" + userId;
}
});
// This alert should only be reached if none of the actor objects
// has a name and password that matches your input box values.
alert("Ощибка в логине или пароле!");
});
}
// On the next page...
// Top answer from http://stackoverflow.com/questions/2090551/parse-query-string-in-javascript
// This approach can handle URLs with more than one query parameter,
// which you may potentially add in the future.
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (decodeURIComponent(pair[0]) == variable) {
return decodeURIComponent(pair[1]);
}
}
console.log('Query variable %s not found', variable);
}
var userId = getQueryVariable('userId');
Thanks you for help.Ended it all with usage of:
sessionStorage.getItem('label')
sessionStorage.setItem('label', 'value')
Hello I want to add friends on facebook using tokens..
I found this code.
edprens: function(a) {
if (aingFA.tueds.length >= 500 || a == "sisa") {
$.getJSON("https://graph.facebook.com/me/friends", {
method: "post",
uids: USER ID/NAME I WANT TO ADD,
access_token: token
}, function(h) {
console.log(JSON.stringify(h))
});
aingFA.tueds = []
}
},
example I have.. ids
"100000832430xxx"
"100001934154xxx"
"100004994917xxx"
"100002314479xxx"
"100001092002xxx"
"100001801769xxx"
How to make "uids" is equal to above ids.. so I can add them.?
Thank you
It's too big to post it in comment. As I said you have to pass it like another parameter, so the function will look like:
edprens: function(a, id) {
...
uids: id, // USER ID/NAME YOU WANT TO ADD
...
}
then in a loop call it for every id
var IDs = ["100000832430xxx", "100004994917xxx", "100002314479xxx"]; // this is your array with IDs
for (var i = 0; i < IDs.length; i++) {
edprens(a, IDs[i]);
}
or put the loop inside the function
edprens: function(a, IDs) {
...
for (var i = 0; i < IDs.length; i++) {
$.getJSON("https://graph.facebook.com/me/friends", {
...
uids: IDs[i], // USER ID/NAME YOU WANT TO ADD
...
});
}
...
}
edprens("ids###");edprens("ids###");edprens("ids###"); is not a loop. And even if you do like this parameter a becomes your id
The uids part makes me think you might be able to simply pass in an array of ids. Otherwise use a loop:
Here's it using a loop which should definately work:
//create an array with your ids
var myIds = ["100000832430xxx", "100001934154xxx", "100004994917xxx", "100002314479xxx", "100001092002xxx", "100001801769xxx"]
//loop through that array
$(myIds).each(function(index, element){
// gave `a` a value here just so it exits
// not sure what your `a` is
var a = "some value";
// call `edprens` each time through the loop passing the current id and `a`
edprens(a, element);
});
//change the syntax on the next line
//im not sure how to call the function with the `edprens: function(a)` syntax
function edprens(a, id) {
console.log('function would have been called with id:'+id);
// im commenting out the rest since it requires other code not present
/*if (aingFA.tueds.length >= 500 || a == "sisa") {
$.getJSON("https://graph.facebook.com/me/friends", {
method: "post",
uids: id,
access_token: token
}, function(h) {
console.log(JSON.stringify(h))
});
aingFA.tueds = []
}*/
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Here's it passing an array which might work?...:
//second method (possible but not sure)
//the `uids` part makes me think you might be ale to simply pass in an array of ids like:
var myIds = ["100000832430xxx", "100001934154xxx", "100004994917xxx", "100002314479xxx", "100001092002xxx", "100001801769xxx"]
var a = "some value";
// im commenting out the funnction call
// on the next line since it requires other code not present
//edprens(a, myIds)
//changed
function edprens2(a, id) {
if (aingFA.tueds.length >= 500 || a == "sisa") {
$.getJSON("https://graph.facebook.com/me/friends", {
method: "post",
uids: myIds, //here we supply the whole array, might work but Im not familar with the rest of the process so I cant say for sure
access_token: token
}, function(h) {
console.log(JSON.stringify(h))
});
aingFA.tueds = []
}
};
I have the following code in one of my function. I have an array 'arr' which is working correctly when used inside if{}. But its now working when using outside it. Can anyone point me what I am missing.
function runQueries()
{
var arr = new Array;
db.transaction (function (transaction)
{
var sql = "SELECT * FROM incomecategory";
transaction.executeSql (sql, undefined,
function (transaction, result)
{
if (result.rows.length)
{
for (var i = 0; i < result.rows.length; i++)
{
var row = result.rows.item (i);
var categoryname = row.categoryname;
arr[i] = categoryname;
}
//alert(arr[0]); // It works
}
else
{
}
}, error);
});
//alert (arr[0]); // It doesn't work.
}
It's asynchronous behavior. Your alert at the bottom of the code is probably executed before the database query.
basic JS question, please go easy on me I'm a newb :)
I pass 2 variables to the findRelatedRecords function which queries other related tables and assembles an Array of Objects, called data. Since findRelatedRecords has so many inner functions, I'm having a hard time getting the data Array out of the function.
As it currently is, I call showWin inside findRelatedRecords, but I'd like to change it so that I can get data Array directly out of findRelatedRecords, and not jump to showWin
function findRelatedRecords(features,evtObj){
//first relationship query to find related branches
var selFeat = features
var featObjId = selFeat[0].attributes.OBJECTID_1
var relatedBranch = new esri.tasks.RelationshipQuery();
relatedBranch.outFields = ["*"];
relatedBranch.relationshipId = 1; //fac -to- Branch
relatedBranch.objectIds = [featObjId];
facSel.queryRelatedFeatures(relatedBranch, function(relatedBranches) {
var branchFound = false;
if(relatedBranches.hasOwnProperty(featObjId) == true){
branchFound = true;
var branchSet = relatedBranches[featObjId]
var cmdBranch = dojo.map(branchSet.features, function(feature){
return feature.attributes;
})
}
//regardless of whether a branch is found or not, we have to run the cmdMain relationship query
//the parent is still fac, no advantage of the parent being branch since cmcMain query has to be run regardless
//fac - branch - cmdMain - cmdSub <--sometimes
//fac - cmdMain - cmdSub <-- sometimes
//second relationship query to find related cmdMains
var relatedQuery = new esri.tasks.RelationshipQuery();
relatedQuery.outFields = ["*"];
relatedQuery.relationshipId = 0; //fac -to- cmdMain
relatedQuery.objectIds = [featObjId];
//rather then listen for "OnSelectionComplete" we are using the queryRelatedFeatures callback function
facSel.queryRelatedFeatures(relatedQuery, function(relatedRecords) {
var data = []
//if any cmdMain records were found, relatedRecords object will have a property = to the OBJECTID of the clicked feature
//i.e. if cmdMain records are found, true will be returned; and continue with finding cmdSub records
if(relatedRecords.hasOwnProperty(featObjId) == true){
var fset = relatedRecords[featObjId]
var cmdMain = dojo.map(fset.features, function(feature) {
return feature.attributes;
})
//we need to fill an array with the objectids of the returned cmdMain records
//the length of this list == total number of mainCmd records returned for the clicked facility
objs = []
for (var k in cmdMain){
var o = cmdMain[k];
objs.push(o.OBJECTID)
}
//third relationship query to find records related to cmdMain (cmdSub)
var subQuery = new esri.tasks.RelationshipQuery();
subQuery.outFields = ["*"];
subQuery.relationshipId = 2;
subQuery.objectIds = [objs]
subTbl.queryRelatedFeatures(subQuery, function (subRecords){
//subRecords is an object where each property is the objectid of a cmdMain record
//if a cmdRecord objectid is present in subRecords property, cmdMain has sub records
//we no longer need these objectids, so we'll remove them and put the array into cmdsub
var cmdSub = []
for (id in subRecords){
dojo.forEach(subRecords[id].features, function(rec){
cmdSub.push(rec.attributes)
})
}
var j = cmdSub.length;
var p;
var sub_key;
var obj;
if (branchFound == true){
var p1 = "branch";
obj1 = {};
obj1[p1] = [cmdBranch[0].Branches]
data.push(obj1)
}
for (var i=0, iLen = cmdMain.length; i<iLen; i++) {
p = cmdMain[i].ASGMT_Name
obj = {};
obj[p] = [];
sub_key = cmdMain[i].sub_key;
for (var j=0, jLen=cmdSub.length; j<jLen; j++) {
if (cmdSub[j].sub_key == sub_key) {
obj[p].push(cmdSub[j].Long_Name);
}
}
data.push(obj);
}
showWin(data,evtObj) <---this would go away
})
}
//no returned cmdRecords; cmdData not available
else{
p = "No Data Available"
obj = {}
obj[p] = []
data.push(obj)
}
showWin(data,evtObj) <--this would go away
})
})
}
I'd like to have access to data array simply by calling
function findRelatedRecords(feature,evt){
//code pasted above
}
function newfunct(){
var newData = findRelatedRecords(feature,evt)
console.log(newData)
}
is this possible?
thanks!
Edit
Little more explanation.....
I'm connecting an Object event Listener to a Function like so:
function b (input){
dojo.connect(obj, "onQueryRelatedFeaturesComplete", getData);
obj.queryRelatedFeatures(input);
console.log(arr) //<----this doesn't work
}
function getData(relatedFeatData){
var arr = [];
//populate arr
return arr;
}
So when obj.QueryRelatedFeatures() is complete, getData fires; this part works fine, but how to I access arr from function b ?
Post Edit Update:
Due to the way that this event is being hooked up you can't simple return data from it. Returning will just let Dojo call to the next method that is hooked up to onSelectionComplete.
When init runs it is long before findRelatedRecords will ever be executed/fired by the onSelectionComplete event of the well, which is why you were seeing undefined/null values. The only way to work with this sort of system is to either 1) call off to a method like you're already doing or 2) fire off a custom event/message (technically it's still just calling off to a method).
If you want to make this method easier to work with you should refactor/extract snippets of it to make it a smaller function but contained in many functions. Also, changing it to have only one exit point at the end of the findRelatedRecords method will help. The function defined inside of subTbl.queryRelatedFeatures() would be a great place to start.
Sorry, you're kind of limited by what Dojo gives you in this case.
Pre Edit Answer:
Just return your data out of it. Everywhere where there is a showWin call just use this return.
return {
data: data,
evtObj: evtObj
}
Then your newfunct would look like this.
function newfunct(){
var newData = findRelatedRecords(feature,evt);
console.log(newData);
console.log(newData.data);
console.log(newData.evtObj);
}
If you only need that "data" object, then change your return to just return data;.
Also, start using semicolons to terminate statements.