Why is this JavaScript looping twice in Zapier? - javascript

Here is a video that shows what I'm struggling with.
Here is a high level description of the process, followed by the actual JavaScript code I've written.
PROCESS
I built 2 Zaps that each run like this:
STEP 1 - Trigger (Cognito Form, which has repeating sections)
STEP 2 - JavaScript Code (which creates an Array of the form fields for ONE of the repeating sections, and separates them into individual strings using .split)
STEP 3 - Action (creates a ZOHO CRM Task for each string)
The first Zap runs on one of the sections of the form (Visits with Sales), and the second zap runs on a different section of the form (Visits without Sales). Each of these Zaps works fine on their own so I know the code is good, but I want to combine the two Zaps into one by combining the code.
I tried to combine by making five steps:
Trigger - Code1 - Zoho1 - Code2 - Zoho2
but the Zoho2 Tasks were each repeated
I then tried to re-order the five steps:
Trigger - Code1 - Code2 - Zoho1 - Zoho2
but now Zoho1 Tasks AND Zoho2 tasks were duplicated.
Finally I tried to combine ALL the JavaScript code into one:
Tigger - CombinedCode1+2 - Zoho 1 - Zoho2
but only the strings from Arrays in "Code2" are available to me when I go to map them in Zoho1.
CODE:
if (inputData.stringVSAccount == null) {
var listVSAccountArray = [];
var listVSUnitsArray = [];
var listVSPriceArray = [];
var listVSNotesArray = [];
var listVSVisitCallArray = [];
} else {
var listVSAccountArray = inputData.stringVSAccount.split(",");
var listVSUnitsArray = inputData.stringVSUnits.split(",");
var listVSPriceArray = inputData.stringVSPrice.split(",");
var listVSNotesArray = inputData.stringVSNotes.split(",");
var listVSVisitCallArray = inputData.stringVSVisitCall.split(",");
}
var output = [];
var arrayNos = listVSAccountArray.length;
var i = 0;
do {
var thisItemVSAccount = new String(listVSAccountArray[i]);
var thisItemVSUnits = new String(listVSUnitsArray[i]);
var thisItemVSPrice = new String(listVSPriceArray[i]);
var thisItemVSNotes = new String(listVSNotesArray[i]);
var thisItemVSVisitCall = new String(listVSVisitCallArray[i]);
var thisItemObj = {};
thisItemObj.itemVSAccount = thisItemVSAccount;
thisItemObj.itemVSUnits = thisItemVSUnits;
thisItemObj.itemVSPrice = thisItemVSPrice;
thisItemObj.itemVSNotes = thisItemVSNotes;
thisItemObj.itemVSVisitCall = thisItemVSVisitCall;
output.push({ thisItemObj });
i++;
} while (i < arrayNos);
//This is where the second zaps code is pasted in the combined version
if (inputData.stringOVAccount == null) {
var listOVAccountArray = [];
var listOVNotesArray = [];
var listOVVisitCallArray = [];
} else {
var listOVAccountArray = inputData.stringOVAccount.split(",");
var listOVNotesArray = inputData.stringOVNotes.split(",");
var listOVVisitCallArray = inputData.stringOVVisitCall.split(",");
}
var output = [];
var arrayNos = listOVAccountArray.length;
var i = 0;
do {
var thisItemOVAccount = new String(listOVAccountArray[i]);
var thisItemOVNotes = new String(listOVNotesArray[i]);
var thisItemOVVisitCall = new String(listOVVisitCallArray[i]);
var thisItemObj = {};
thisItemObj.itemOVAccount = thisItemOVAccount;
thisItemObj.itemOVNotes = thisItemOVNotes;
thisItemObj.itemOVVisitCall = thisItemOVVisitCall;
output.push({ thisItemObj });
i++;
} while (i < arrayNos);
I just started learning JavaScript this week, and sense that I am missing something obvious, perhaps a set of brackets. Thanks for any assistance

David here, from the Zapier Platform team. You're running into a confusing and largely undocumented feature where items after a code step run for each item returned. This is usually desired behavior - when you return 3 submissions you want to create 3 records.
In your case, it's also running subsequent unrelated actions multiple times, which sounds like it's undesired. In that case, it might be easier to have 2 zaps. Or, if "Zoho2" only ever happens once, put it first and let the branch happen downstream.
Separately, I've got some unsolicited javascript advice (since you mentioned you're a beginner). Check out Array.forEach (docs), which will let you iterate through arrays without having to manage as many variables (your own i every time). Also, try to use let and const over var when possible - it keeps your variables scoped as small as possible so you don't accidentally leak values into other areas.
​Let me know if you've got any other questions!

Just a note - you are declaring the same array variable output in both segments of your code block - the second declaration will be ignored.
Use the .forEach() method to iterate over your arrays, it will significantly cleanup you code. You also don't need to painstakingly construct the objects to be pushed into the output arrays.
This may not fix your issue but the code is far easier on the eye.
var listVSAccountArray = [],
listVSUnitsArray = [],
listVSPriceArray = [],
listVSNotesArray = [],
listVSVisitCallArray = [],
output = [];
if (typeof inputData.stringVSAccount === 'string') {
listVSAccountArray = inputData.stringVSAccount.split(',');
listVSUnitsArray = inputData.stringVSUnits.split(',');
listVSPriceArray = inputData.stringVSPrice.split(',');
listVSNotesArray = inputData.stringVSNotes.split(',');
listVSVisitCallArray = inputData.stringVSVisitCall.split(',');
}
// iterate over the array using forEach()
listVSAccountArray.forEach(function(elem, index){
// elem is listVSAccountArray[index]
output.push({
itemVSAccount: elem,
itemVSUnits: listVSUnitsArray[index],
itemVSPrice: listVSPriceArray[index],
itemVSNotes: listVSNotesArray[index],
itemVSVisitCall: listVSVisitCallArray[index]
})
})
//This is where the second zaps code is pasted in the combined version
var listOVAccountArray = [],
listOVNotesArray = [],
listOVVisitCallArray = [],
output_two = []; // changed the name of the second output array
if (typeof inputData.stringOVAccount === 'string') {
listOVAccountArray = inputData.stringOVAccount.split(',');
listOVNotesArray = inputData.stringOVNotes.split(',');
listOVVisitCallArray = inputData.stringOVVisitCall.split(',');
}
// iterate over the array using forEach()
listOVAccountArray.forEach(function(elem, index){
// elem is listOVAccountArray[index]
output_two.push({
itemOVAccount: elem,
itemOVNotes: listOVNotesArray[index],
itemOVVisitCall: listOVVisitCallArray[index]
});
});

Related

Creating a leaderboard with Firebase

I'm trying to build a top 10 leaderboard using the Firebase Realtime DB - I am able to pull the top 10 ordered by score (last 10 due to the way firebase stores in ascending order) however when I attempt to place them in the page they all appear in key order.
If I was a betting man I'd guess it's to do with the for loop I have to create the elements - but I'm not good enough at Javascript to work out where the issue is I've spent the last 3 hours on MDN and W3Schools and I can't for the life of me work it out.
Either that or I need to run a For Each loop on the actual data query? but I feel like I could avoid that as I'm already collecting the score data so I could just arrange that somehow?
I was sort of expecting everything to appear in ascending order - meaning I would have to go back and prepend my JQuery but instead I've managed to accidentally create a new problem for myself.
Any suggestions will be GREATLY appreciated
Here is my current code:
var db = firebase.database()
var ref = db.ref('images')
ref.orderByChild('score').limitToLast(10).on('value', gotData, errData);
function gotData(data) {
var scores = data.val();
var keys = Object.keys(scores);
var currentRow;
for (var i = 0; i < keys.length; i++){
var currentObject = scores[keys[i]];
if(i % 1 == 0 ){
currentRow = document.createElement("div");
$(currentRow).addClass("pure-u-1-5")
$("#content").append(currentRow);
}
var col = document.createElement("div")
$(col).addClass("col-lg-5");
var image = document.createElement("img")
image.src=currentObject.url;
$(image).addClass("contentImage")
var p = document.createElement("P")
$(p).html(currentObject.score)
$(p).addClass("contentScore");
$(col).append(image);
$(col).append(p);
$(currentRow).append(col);
}
}
Use .sort() beforehand, then iterate over each score object to add it to the page:
function gotData(data) {
const scores = data.val();
const keys = Object.keys(scores);
const sortedKeys = keys.sort((keyA, keyB) => scores[keyB].score - scores[keyA].score);
const content = document.querySelector('#content');
sortedKeys.map(sortedKey => scores[sortedKey])
.forEach(scoreObj => {
const row = content.appendChild(document.createElement('div'));
row.classList.add('pure-u-1-5'); // better done in the CSS if possible
const col = row.appendChild(document.createElement('div'));
col.classList.add('col-lg-5');
const img = col.appendChild(document.createElement('img'));
img.src = scoreObj.url;
img.classList.add('contentScore');
col.appendChild(document.createElement('p')).textContent = scoreObj.score;
});
}
For loops have worse abstraction, require manual iteration, and have hoisting problems when you use var - use the array methods instead when you can.

How to dynamically add object to array (closure in loop)

I read couple posts about the closure in loop but still not really get it how to apply to my situation.
I have three feed urls defined in HTML and using JavaScript promise to return the response when it's ready without blocking the UI. I am able to get two blog entries data per feed url. Now, each returned blog entry has its published date and I would like to sort them from latest to oldest. However, I keep getting the last value when I pushed the object to array. I know this is something to do with closure and since I'm not familiar with closure, I have difficulty to solve this problem. Any help is great appreciated!
var itemArray = [];
var entryObj = {};
promise.then(function (response) {
var parser = new DOMParser();
xml = parser.parseFromString(response, "text/xml");
var items = xml.getElementsByTagName("item");
for (var x = 0; x < items.length && x < limits; x++) {
title = items[x].getElementsByTagName("title")[0].innerHTML;
link = items[x].getElementsByTagName("link")[0].innerHTML;
pubDate = items[x].getElementsByTagName("pubDate")[0].innerHTML;
creator = items[x].getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", "creator")[0].innerHTML;
entryObj.title = title;
entryObj.link = link;
entryObj.pubDate = pubDate;
entryObj.creator = creator;
itemArray.push(entryObj);
// output: all 6 objects contain last value
console.log(itemArray);
}
});
In short : Move the object creation inside the loop.
It's nothing to do with closure. The issue is, you are pushing the same object.
You need a new object to be pushed. So create the object inside the for loop. So that every time you get a new object and it gets pushed to the array.
Code-
var itemArray = [];
promise.then(function (response) {
var parser = new DOMParser();
xml = parser.parseFromString(response, "text/xml");
var items = xml.getElementsByTagName("item");
for (var x = 0; x < items.length && x < limits; x++) {
var entryObj = {};
title = items[x].getElementsByTagName("title")[0].innerHTML;
link = items[x].getElementsByTagName("link")[0].innerHTML;
pubDate = items[x].getElementsByTagName("pubDate")[0].innerHTML;
creator = items[x].getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", "creator")[0].innerHTML;
entryObj.title = title;
entryObj.link = link;
entryObj.pubDate = pubDate;
entryObj.creator = creator;
itemArray.push(entryObj);
// output: Now all values are unique
console.log(itemArray);
}
});
Move var entryObj = {}; into your for loop.

How do I move an object from one array to another?

I'm trying to move an object from one array(triviaDataArray) to another(answeredQuestions). I've replaced the actual questions with numbers to shorten code. When I run this code it seems to remove a position in the triviaDataArray, and adds one to the answeredQuestion array. But it doesn't seem to remove the correct one. When a new question is loaded it sometimes repeats itself, which is not what I want. I want it to ask a question, and then when it is answered move it to the answeredQuestion array.
I can only use JavaScript. Can someone please help me. I've been struggling with this for quite some time.
This portion is from a data reader file I have to load a random question and it's answers from an array and the second is to open the array.
TriviaDataRecords.prototype.loadRandomRecord = function () {
this.position = Math.floor(Math.random() * this.records.length);
};
function openTriviaRecords(triviaData) {
return new TriviaDataRecords(triviaData);
}
global arrays and variables
var triviaDataArray = [1, 2, 3, 4, 5, 6, 7]
var answeredQuestions = []
var triviaRecords = openTriviaRecords(triviaDataArray);
function that loads a question and files the fields of the html.
function loadQuestion(){
var randomRecord = triviaRecords.loadRandomRecord();
var buttonA = document.getElementById("answer1");
var buttonB = document.getElementById("answer2");
var buttonC = document.getElementById("answer3");
var buttonD = document.getElementById("answer4");
var buttonE = document.getElementById("answer5");
document.getElementById('question').innerHTML = triviaRecords.getQuestion();
document.getElementById('answer1').innerHTML = triviaRecords.getAnswerA();
document.getElementById('answer2').innerHTML = triviaRecords.getAnswerB();
document.getElementById('answer3').innerHTML = triviaRecords.getAnswerC();
document.getElementById('answer4').innerHTML = triviaRecords.getAnswerD();
document.getElementById('answer5').innerHTML = triviaRecords.getAnswerE();
portion where I'm trying to move the question from one array to another.
var index = triviaDataArray.indexOf(randomRecord);
if (index == -1) {
triviaDataArray.splice(randomRecord, 1);
answeredQuestions.push(randomRecord);
}

Node.js array instantiation

So, I have been trying for a while now and no luck. Currently I have an associative array as based on PSN profile data:
var PROFILE = {};
PROFILE.profileData = {};
PROFILE.titles = {};
and is used like this further down in the code:
PROFILE.profileData.onlineId = profileData.onlineId;
PROFILE.profileData.region = profileData.region;
PROFILE.titles[title.npCommunicationId] = title; //For looped, can be many
PROFILE.titles[title.npCommunicationId].trophies = {};
PROFILE.titles[title.npCommunicationId].trophies = trophyData.trophies; //any where from 10 - 50+ of these, for looped
Problem is, if I want to have multiple profiles, this doesn't work as it just inserts them in the same profile. I need 'PROFILE' to be an array that has all the above elements at each index.
PROFILEarray[n].profileData = {};
PROFILEarray[n].profileData.onlineId = profileData.onlineId;
PROFILEarray[n].profileData.region = profileData.region;
Something like this is what I need^
But for the above I get this error
Cannot read property 'profileData' of undefined
Once this is complete, it's saved into a file in JSON format to then be used by PHP code I've written to insert into a db.
This is a small snippet of the json output: http://textuploader.com/5zpbk (had to cut, too bit to upload)
you must define PROFILEarray[n] as object. JavaScript objects are containers for named values. You can not set value of undefined
PROFILEarray[n] is undefined in this case. Initialize it as {}(object)
Try this:
var PROFILEarray = [];
for (var n = 0; n < 5; n++) {
PROFILEarray[n] = {};
PROFILEarray[n].profileData = {};
PROFILEarray[n].profileData.onlineId = n;
PROFILEarray[n].profileData.region = 'Region' + n;
}
alert(JSON.stringify(PROFILEarray));

JavaScript stop referencing object after pass it to a function

I know JavaScript passes Objects by reference and thus I'm having a lot of trouble with the following code:
function doGradeAssignmentContent(dtos) {
var x = 5;
var allPages = [];
var stage = new App.UI.PopUpDisplay.PopUpStageAssignmentGrader(null, that);// pass launch element
for(var i = 0; i < dtos[0].result.students.length; ++i) {
var pagesSet = [];
for(var j = 0; j < dtos[0].result.questions.length; ++j) {
var questionObject = jQuery.extend(true, {}, new Object());
questionObject = dtos[0].result.questions[j];
if(dtos[0].result.students[i].answers[j].assignmentQuestionId === questionObject.questionId) {// expected, if not here something is wrong
questionObject.answer = dtos[0].result.students[i].answers[j].studentAnswer;
questionObject.pointsReceived = dtos[0].result.students[i].answers[j].pointsReceived;
} else {
var theAnswer = findAssociatedStudentAnswer(questionObject.questionId, dtos[0].result.students[i].answers[j]);
if(theAnswer !== null) {
questionObject.answer = theAnswer.studentAnswer;
questionObject.pointsReceived = theAnswer.pointsReceived;
} else {
alert("Unexpected error. Please refresh and try again.");
}
}
pagesSet[pagesSet.length] = new App.UI.PopUpDisplay.StageAssignmentGradingPages[dtos[0].result.questions[j].questionType.charAt(0).toUpperCase() + dtos[0].result.questions[j].questionType.slice(1) + "QuestionAssignmentGradingPage"](j + 1, questionObject);
}
var studentInfo = {};
studentInfo.avatar = dtos[0].result.students[i].avatar;
studentInfo.displayName = dtos[0].result.students[i].displayName;
stage.addPageSet(pagesSet, studentInfo);
}
stage.launch();
}
First let me show you what the result (dtos) looks like so you can better understand how this function is parsing it:
The result (dtos) is an Object and looks something like:
dtos Array
dtos[0], static always here
dtos[0].result, static always here
dtos[0].questions Array
dtos[0].questions.index0 - indexN. This describes our Questions, each one is an Object
dtos[0].students Array
dtos[0].students[0]-[n].answers Array. Each student array/Object has an Answers array. Each student will have as many elements in this answers Array that there were questions in dtos[0].questions. Each element is an Object
Now what we do in this here is create this Object stage. Important things here are it has an array called "this.studentsPages". This array will ultimately have as many entries as there were students in dtos[0].students.
So we loop through this for loop disecting the dtos array and creating a pagesSet array. Here comes my problem. On the first iteration through the for loop I create this questionObject element. I also have tried just doing var questionObject = {}, but what you see now was just an attempt to fix the problem I was seeing, but it didn't work either.
So at the end of the first iteration of the outer for loop I call stage.addPageSet, this is what happens here:
var pageObject = [];
pageObject["questions"] = pageSet;
pageObject["displayName"] = studentInfo.displayName;
this.studentsPages[this.studentsPages.length] = pageObject;
if(this.studentsPages.length === 1) {// first time only
for(var i = 0; i < pageSet.length; ++i) {
this.addPage(pageSet[i]);
}
}
The important thing to take notice of here is where I add pageObject on to this.studentsPages which was an empty array before the first call. pageObject now has pageSet plus a little bit more information. Remember, pageSet was an Object and thus passed by reference.
On the next iteration of the for loop, when I hit this line:
questionObject.answer = dtos[0].result.students[i].answers[j].studentAnswer;
It goes wrong. This changes the local copy of questionObject, BUT it also changes the copy of questionObjec that was passed to addPageSet and added to the studentsPages array in the first iteration. So, if I only had 2 students coming in, then when all is said and done, studentsPages hold 2 identical Objects. This should not be true.
The problem is questionObject in the doGradeAssignmentContent function is keeping a reference to the Object created on the previous iteration and then overrides it on all subsequent iterations.
What can I do to fix this?
Thanks for the help!
With out having looked at it too closely I believe you need to change the following:
// Before:
var questionObject = jQuery.extend(true, {}, new Object());
questionObject = dtos[0].result.questions[j];
// After:
var questionObject = jQuery.extend(true, {}, dtos[0].result.questions[j]);
I didn't look too closely if there are other instances in the code where this needs to be applied, but the core concept is to utilize jQuery's deep copy to generate a duplicate of the object you do not wish to retain a reference to.

Categories

Resources