JavaScript stop referencing object after pass it to a function - javascript

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.

Related

Why is this JavaScript looping twice in Zapier?

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]
});
});

Updating the value of an object inside a loop using javascript

I'm currently facing a difficulty in my codes.
First i have an array of objects like this [{Id:1, Name:"AML", allowedToView:"1,2"}, {Id:2, Name:"Res", allowedToView:"1"}...] which came from my service
I assign it in variable $scope.listofResource
Then inside of one of my objects I have that allowedToView key which is a collection of Id's of users that I separate by comma.
Then I have this code...
Javascript
$scope.listofResource = msg.data
for (var i = 0; i < msg.data.length; i++) {
First I run a for loop so I can separate the Id's of every user in allowedToView key
var allowed = msg.data[i].allowedToView.split(",");
var x = [];
Then I create a variable x so I can push a new object to it with a keys of allowedId that basically the Id of the users and resId which is the Id of the resource
for (var a = 0; a < allowed.length; a++) {
x.push({ allowedId: allowed[a], resId: msg.data[i].Id });
}
Then I put it in Promise.all because I have to get the Name of that "allowed users" base on their Id's using a service
Promise.all(x.map(function (prop) {
var d = {
allowedId: parseInt(prop.allowedId)
}
return ResourceService.getAllowedUsers(d).then(function (msg1) {
msg1.data[0].resId = prop.resId;
Here it returns the Id and Name of the allowed user. I have to insert the resId so it can pass to the return object and it will be displayed in .then() below
return msg1.data[0]
});
})).then(function (result) {
I got the result that I want but here is now my problem
angular.forEach(result, function (val) {
angular.forEach($scope.listofResource, function (vv) {
vv.allowedToView1 = [];
if (val.resId === vv.Id) {
vv.allowedToView1.push(val);
I want to update $scope.listofResource.allowedToView1 which should hold an array of objects and it is basically the info of the allowed users. But whenever I push a value here vv.allowedToView1.push(val); It always updates the last object of the array.
}
})
})
});
}
So the result of my code is always like this
[{Id:1, Name:"AML", allowedToView:"1,2", allowedToView:[]}, {Id:2, Name:"Res", allowedToView:"1", allowedToView:[{Id:1, Name:" John Doe"}]}...]
The first result is always blank. Can anyone help me?
Here is the plunker of it... Plunkr
Link to the solution - Plunkr
for (var i = 0; i < msg.length; i++) {
var allowed = msg[i].allowedToView.split(",");
msg[i].allowedToView1 = [];
var x = [];
Like Aleksey Solovey correctly pointed out, the initialization of the allowedToView1 array is happening at the wrong place. It should be shifted to a place where it is called once for the msg. I've shifted it to after allowedToView.split in the first loop as that seemed a appropriate location to initialize it.

Underscore.js `filter` not working

I have the following code snippet I use to choose which suburbs from a list the user has selected (with irrelevant code omitted):
var allSuburbsList = new Array([{"SuburbID":1,"SuburbAreaID":3,"SuburbName":"Alberante","SuburbActive":true,"Area":null,"Agents":[]},{"SuburbID":4,"SuburbAreaID":3,"SuburbName":"Alberton North","SuburbActive":true,"Area":null,"Agents":[]}]);
var a3burbs = _.filter(allSuburbsList, function(s) { return s.SuburbAreaID === 3; });
// 3 is a test value. All the test suburbs so far fall under area no. 3.
With this filter, a3burbs comes out as an empty array, []. If I cheat and make the filter:
var a3burbs = _.filter(allSuburbsList, function(s) { return true; });
then a3bubrs comes out an exact copy of allSuburbsList, with all suburbs included. What could I be doing wrong? I'm using the same syntax as indicated on the Underscore.js home page.
Btw, would the way I populate allSuburbsList from a viewmodel array property have anything to do with this:
var allSuburbsList = new Array(#Html.Raw(JsonConvert.SerializeObject(Model.AllSuburbs)));
Just for interest, my first attempt was the hideous code below, but it worked:
var a3burbs = [];
#{for (var i = 0; i < Model.AllSuburbs.Length; i++) {
#:if (allSuburbsList[#i].SuburbAreaID === 3) {
#:a3burbs.push(allSuburbsList[#i]);
};
};
You're creating a new array and passing in an array.
Change it to
var allSuburbsList = [{"SuburbID":1,"SuburbAreaID":3,"SuburbName":"Alberante","SuburbActive":true,"Area":null,"Agents":[]},{"SuburbID":4,"SuburbAreaID":3,"SuburbName":"Alberton North","SuburbActive":true,"Area":null,"Agents":[]}];
Using new Array([{}]), you are creating an array inside another array. Just instantiate that without new Array()

remove items from array with the same id one by one

the problem is that I have multiple objects with the same id. As you can see this works when it comes to removing all the items with the same id. How I can remove the objects one by one no matter if they are the same ID...thanks
individualObjects:[],
actions:{
increment:function(){
var obj = this.get('object');
this.get('individualObjects').pushObject(obj);
},
decrement:function(){
var obj = this.get('object');
var filter = this.get('individualObjects').findBy('obj_id', obj.get('obj_id'));
this.get('individualObjects').removeObject(filter);
}
}
Well to filter array you would need to use Array.filter to find out the items that do not belong in the "individualObjects" and later simply remove them by using "removeObjects"
decrement:function(){
var objects = this.get('individualObjects')
var notWanted = objects.filterBy('obj_id', this.get('object.obj_id'));
this.get('individualObjects').removeObjects(notWanted);
}
and solution 2
decrement:function(){
var removeObj = this.get('object');
var objects = this.get('individualObjects')
// As the condition is true given object is returned
var notWanted = objects.filter(obj => { return obj.get('obj_id') === removeObj.get('obj_id')  });
this.get('individualObjects').removeObjects(notWanted);
}
Ok so you want to remove items one by one. Weird but can be accomplished
first get the length for
var notWantedCount = objects.filterBy('obj_id', this.get('object.obj_id')).length;
Now
for(var i=0; i <= notWantedCount; i++) {
var toRemove = individualObjects.findBy('obj_id', obj.get('obj_id'));
individualObjects.removeObject(toRemove);
// Make some custom actions one by one.
}
I don't know ember, but you'll want to do a foreach on the array, and then test for id on each one. It should be something like this:
decrement:function(){
var obj = this.get('object');
self = this;
this.get('individualObjects').each(function(individualObject) {
if (individualObject.get('obj_id') == obj.get('obj_id'))
... you want to do something here? ...
self.get('individualObjects').removeObject(individualObject);
}
}
That way you can remove each object individually. Running any necessary code before or after it's removed. If you want to sort it first, you can do that before running the each function.

javascript push inside init method not updating array length correctly

I'm trying to create an array of objects with an init method, and when I push an object into an array, it should push another object into the array, while keeping track of the length of the array.. The problem is that it doesn't update the length of the array until all the objects have been added, so when each object tries to grab the length, they all get 0.
How can I have it update the length in this process?
here's the jfiddle: http://jsfiddle.net/bg3Vg/13/
As you can see it gives a message showing that the grouptotal is 5, but it seems as if the total counts up from the last pushed object to the first.. I need it to work in the correct order so that the last pushed object can retrieve the correct length.
var colorGroup = [];
var grouptotal = 0;
colorGroup.push(new groupdata(0) );
alert(grouptotal+","+colorGroup[colorGroup.length-1].parent);
function groupdata(parent) {
this.parent = parent;
this.refnum;
this.init = function()
{
grouptotal++;
this.refnum = colorGroup.length;
if(grouptotal<5)colorGroup.push(new groupdata( this.refnum ) );
}
this.init();
}
edit:
ok, I found a way to solve my problem I think. Let me know how horrid this solution is..http://jsfiddle.net/EqAqv/1/
var colorGroup = [];
var grouptotal = 0;
var colorGroupWait = [];
colorGroup.push(new groupdata(0) );
while(colorGroupWait.length>0){
var newcolorGroup = colorGroupWait.shift();
colorGroup.push(new groupdata(newcolorGroup) );
}
alert(grouptotal+","+colorGroup[colorGroup.length-1].parent);
alert(grouptotal+","+colorGroup[colorGroup.length-2].parent);
function groupdata(parent) {
this.parent = parent;
this.refnum;
this.init = function()
{
grouptotal++;
this.refnum = colorGroup.length;
if(colorGroup.length<5)colorGroupWait.unshift( this.refnum );
}
this.init();
}
JavaScript arrays do update the length property as soon as you push something on to them. The problem is that you're recursively calling the constructor, so the statement this.refnum = colorGroup.length is getting executed for each initialization BEFORE any push occurs.
In other words, JavaScript is working as expected.
Is there any particular reason you are doing it in this convoluted manner? It be more straightforward (and achieve the result you're looking for) if you just did it like this:
for(grouptotal=0; grouptotal<5; grouptotal++){
colorGroup.push( new groupdata(grouptotal) );
}
Also, it is convention in JavaScript to name object constructors with a capital letter. So, while groupdata is not invalid syntax, it is confusing: you should consider naming it Groupdata.
#EthanBrown has already pointed out the problems. Here is a solution that puts all the logic in the constructor, and avoids the problem of pushing the instance after having it created from a wrong number.
function GroupData(parentnum) {
this.parentnum = parentnum;
this.refnum = GroupData.colorGroup.length;
GroupData.colorGroup.push(this);
if (GroupData.colorGroup.length < 5)
new GroupData(this.refnum);
}
GroupData.colorGroup = [];
var root = new GroupData(0);
alert(GroupData.colorGroup.length+", "
+GroupData.colorGroup[GroupData.colorGroup.length-1].parentnum);

Categories

Resources