Angular2 Observable.map capture value of local variable - javascript

How can one capture value of a local variable for use inside Observable.map() callback? For example, in my Anglar2 application I would love to capture value of quantity for use in findItem().map():
let quantity : number = 1; //<-- needs to be captured
let ordered : string[] = [];
//not interested in actual results, just adding queried items to cart
let queries : Observable<void>[] = [];
//res.entities are NLP classified entities, either item_id:string or quantity:number
for (let entity of res.entities) {
if(entity.entity == 'item') {
queries.push(
this.findItem(entity.value).map(item => {
if(item != null)
{
for(let i : number = quantity; i > 0; i--) this.cart.addItem(item);
}
ordered.push(quantity + 'x ' + item.fullName);//NL message for user
}));
quantity = 1;//reset quantity
}
else if(entity.entity == 'quantity') quantity = entity.value;
}
return Observable.forkJoin(queries, ...);
The ordered will show quantity 1 for all items (because value of quantity is always 1 at the end of the loop).
My research shows that this is very common problem in JavaScript, and there are plenty of examples how to capture a variable in for loops. I wasn't able, however, to find any information how to capture variable values for use in callbacks, such as Observable.map()

Use a const within your for loop.
for (let entity of res.entities) {
if(entity.entity == 'item') {
const q = quantity; //here or somewhere
queries.push(
//..... use q instead
}
else if(entity.entity == 'quantity') quantity = entity.value;
}

Related

Target an object in an array by one of its key's values (a product id) to change another of its values (increase count)?

I'm working on a shopping basket with local storage. This is the step during which I am checking whether a similar product is already in the basket/local storage.
I'm not sure how to target the specific object of color+model combo to increase the count.
for (let loggedProduct of products) {
console.log('for of 'exists' loop runs'); // DEL
if (loggedProduct.id === newAdd.id) {
if (loggedProduct.color === newAdd.color) {
product_exists = true;
// loggedProduct.count is a string. Work around to make it a number:
loggedProduct.count = parseInt(loggedProduct.count);
loggedProduct.count += 1;
// I would like to make the part above specific to the id/color combo, but I'm not sure how.
}
}
This was my solution after redditers helped me find the thing I was missing.
for (let loggedProduct of products) {
if (
loggedProduct.id === newAdd.id &&
loggedProduct.color === newAdd.color
) {
product_exists = true;
// loggedProduct.count is a string. Work around to make it a number:
loggedProduct.count = parseInt(loggedProduct.count);
loggedProduct.count += 1;
// console.log('count after: ', loggedProduct.count); // DEL
}
}

Weird issue with Vue / Javascript variables

I honestly don't even know how to search for this question (what search param to write) but either way its bit weird issue and I am desperate for help.
So I am trying to do something simple, event sends "form-change" and when it does, we set new value in "this.data" object. Fairly simple. I don't expect this.data to be reactive I just want to update it.
// Find our data object which we want to update/change
if (form.field.includes('.')) {
let find = form.field.split('.'), level = this.data;
for (let index = 0; index < find.length; index++) {
if (level[find[index]] !== undefined) {
level = level[find[index]];
} else {
level = undefined;
}
}
if (level !== undefined)
level = setFieldData();
}
This is fairly simple, we have name of field "inspect.book" and when update comes (Event) we just use dots to split into multi tree and update "this.data.inspect.book" to new value. But it does not work. Value does not change.
But the value from actual this.data.inspect.book comes out just fine using:
console.log(level);
However, if I do this:
this.data[ form.field.split( '.' )[ 0 ] ][ form.field.split( '.' )[ 1 ] ] = setFieldData();
It works fine. So "reference" to variable does not work... how is this possible? Looks like a bug in javascript or is it something with vue/reactivity?
Does anyone have better idea how to get this to work?
So you are trying to update form data using to notation ?
i would do something like that :
_update(fieldName, value) {
// We split segments using dot notation (.)
let segments = fieldName.split(".");
// We get the last one
let lastSegment = segments.pop();
// We start with this.data as root
let current = this.data
if(current) {
// We update current by going down each segment
segments.forEach((segment) => {
current = current[segment];
});
// We update the last segment once we are down the correct depth
current[lastSegment] = val;
}
},
if i take your example :
if (form.field.includes('.')) {
let find = form.field.split('.'), level = this.data;
for (let index = 0; index < find.length - 1; index++) {
if (level[find[index]] !== undefined) {
level = level[find[index]];
} else {
level = undefined;
}
}
if (level !== undefined)
level[find.pop()] = setFieldData();
}
i replaced find.length by find.length - 1
and replaced level = setFieldData() by level[find.pop()] = setFieldData()
you should update the property of the object, without actually overriding the value,
because if you override the value, the original value will not get updated.

Stumped. LocalStorage.key string won't match stringified number

I have a localStorage file made up of numeric keys. To write a new key, value pair to the file, I want to make sure the key does not already exist. If it does exist, I add one to the key and test again if it's present. It's a small set, so I thought this could be handled with a straightforward iteration of all keys and a check for match, e.g.:
var CheckItAintThere = function() {
for ( var i = 0, len = window.localStorage.length; i < len; ++i )
{
var Foundit = false;
console.log('checking for duplicate to see if '+window.localStorage.key(i)+' is equal to '+SaveNumber);
if (window.localStorage.getItem( window.localStorage.key(i) ) == SaveNumber.toString())
{
console.log('FOUNDIT TRUE');
Foundit = true;
}
}
while (Foundit == true) {
SaveNumber = SaveNumber + 1;
if (localStorage.getItem( localStorage.key( i ) ) !== SaveNumber.toString())
{
console.log('SaveNumber = '+SaveNumber+' LocalStoragekey = '+localStorage.key(i));
Foundit = false;
}
}
}
But Foundit never tests true, even when the console.log reports:
> checking for duplicate to see if 0 is equal to 3
> checking for duplicate to see if 1 is equal to 3
> checking for duplicate to see if 3 is equal to 3
I tried adding .toString() to the key, but as I understand it keys are stored as strings anyway, and this had no impact.
I have a feeling I'm going to have a palmprint on my forehead, but what am I doing wrong?

JSON.stringify() causing an infinite loop (Edit: NO. Bad logic is.)

My product object looks somewhat like this:
{name: 'abc', description: 'def', price: 100, quantity: 1, comment: '', customizations: []}
The customizations key is an array that has other such product objects in it. You may ignore it for this question. As you might have noticed, the comment and customizations keys are the keys that make the (theoretically) same product (practically) different when adding to cart.
I want to make a function to add such products to an array called cart_items[]. If the (practically) same product is chosen, I only want to increment the quantity inside the cart_items[i], else add a new object.
This is my function:
$scope.add_to_cart = function(product) {
// if the cart is empty, skip the brouhaha
if ($scope.cart_items.length === 0) {
$scope.cart_items.push(angular.copy(product));
} else {
for (var i = 0; i < $scope.cart_items.length; i++) {
// copy the original quantity and set it to 1 for comparison
var qty = $scope.cart_items[i].quantity;
$scope.cart_items[i].quantity = 1;
if (JSON.stringify(product) === JSON.stringify($scope.cart_items[i])) {
$scope.cart_items[i].quantity = qty + 1;
} else {
$scope.cart_items[i].quantity = qty;
$scope.cart_items.push(angular.copy(product));
}
}
}
};
The problem: First product adds successfully. Adding another causes an infinite loop. I replaced if(JSON...) with if(1 === 1) and the infinite loop didn't occur. I don't know where am I going wrong. Any help?
The problem you have is that you're increasing the size of the array in the loop and the stop condition is i being $scope.cart_items.length.
If your goal is to add the object if it's not yet present, what you want is probably this :
boolean found = false;
for (var i = 0; i < $scope.cart_items.length; i++) {
var qty = $scope.cart_items[i].quantity;
$scope.cart_items[i].quantity = 1;
if (JSON.stringify(product) === JSON.stringify($scope.cart_items[i])) {
$scope.cart_items[i].quantity = qty + 1;
found = true;
break;
}
}
if (!found) {
var item = angular.copy(product);
item.quantity = 1;
$scope.cart_items.push();
}
Note that two identical objects (i.e. objects with same property values) should not give the same JSON string because the order of property iteration isn't specified. It usually works (especially if the cloning is done the obvious way) but there's no guarantee. You really should compare based on the properties, not using JSON stringification.
You probably enter in a recursive loop, because of objects contained in customizations...
A [ customizations = B ]
B [ customizations = C ]
C [ customizations = A ]
----------------------------
Infinite loop

synchronicity problems with Internet Explorer

I'm developing a javascript widget using the UWA widget format. Unfortunately this makes it impossible to jsFiddle my code but I've commented it in detail so that, hopefully, you can follow its fairly straightforward sequence.
HighestClass = {};
HighestClass.array = [];
HighestClass.url = "http://our.url.local/frog/pointsByWeek.php?cmd=highestClass&students=";
HighestClass.init = function(groupPrefix) {
var count = 0;
/* Using the group prefix, i.e. "CLS 9", from the drop-down box,
get a list of all of the classes in that year group */
/* First time round, count the number of groups that match this
syntax because there are no parameters available to filter
this API */
Frog.API.get('groups.getAll',{
'onSuccess': function(data){
for (var i = 0; i < data.length; i++) {
if (data[i].name.indexOf(groupPrefix) != -1)
count++;
}
});
/* Now that these classes have been counted, run through the API
call again to push each class ID through to another function */
var run_through = 0;
Frog.API.get('groups.getAll',{
'onSuccess': function(data){
for (var i = 0; i < data.length; i++) {
if (data[i].name.indexOf(groupPrefix) != -1) {
var end = false;
run_through++;
/* When it gets to the last class group, i.e. the run_through
variable becomes equal to count, let the getClassPoints
function know */
if( run_through == count )
end = true;
HighestClass.getClassPoints( data[i].name, data[i].id, end );
}
}
}
});
}
HighestClass.getClassPoints = function(name, id, end) {
var list = '';
/* Using the ID of the class group, create a comma-separated list
of students for use in our MySQL query */
Frog.API.get("users.search", {
"params": {
"group": id
},
"onSuccess": function (data){
for (var i = 0; i < data.length; i++)
list += data[i].id + ",";
}
});
/* If the list exists... */
if( typeof list === "string" && list.length > 0 ) {
list = list.slice(0,-1);
/* Run an AJAX call to our PHP script which simply returns an integer
value of the SUM of reward points earned by that list of students */
UWA.Data.getJson(HighestClass.url + list, function(res){
if (res === false || res === "") res = 0;
/* Push this data into an array of objects alongside the class name */
var obj = { "name": name, "points": res };
HighestClass.array.push(obj);
});
}
/* As this function is being called asynchronously multiple times we need to
determine when the last call is run so that we can deal with our array
of data. We do this thanks to the count/run_through variables in earlier
function which will trigger end=true in this function */
if( end === true )
HighestClass.display();
}
HighestClass.display = function() {
/* Once we've put our array of objects together, we need to sort it so that
the class with the highest number of points are at array entry 0 */
function compare(a,b) {
if (a.points < b.points)
return 1;
if (a.points > b.points)
return -1;
return 0;
}
/* IF I PUT AN ALERT HERE, INTERNET EXPLORER WORKS, LOL? */
HighestClass.array.sort(compare);
/* We can then display the data of array entry 0 which should be our highest
point-scoring class */
$('#display').html( '<h1>' + HighestClass.array[0].name + '</h1><h3>' + HighestClass.array[0].points + '</h3>' );
}
/* equivalent of document ready */
widget.onLoad = function(){
/* Choose the year group from the drop down box */
$("select").change(function(){
var val = $('select option:selected').val();
$("#display").html('<h1><img width="60" height="60" src="http://logd.tw.rpi.edu/files/loading.gif" />Loading...</h1>');
HighestClass.init(val);
});
}
In essence the script does the following:
Retrieve a list of students for each class group
Run an AJAX call to our PHP script/MySQL database to return the SUM of points for those students
Add the name and points info to an array of objects
Sort the array of objects so that the highest point-scoring class is our first array entry
Display the name of the class and their points from array entry 0
The problem is, the only way I can think about doing it (because of limitations of the APIs) is to run asynchronous API calls and chain AJAX calls off these. I then use a counting variable to determine when the last asynchronous call is made.
Now, importantly, this script works perfectly well in FireFox. However, in Internet Explorer - which is where I need it to work - the script displays our "loading" DIV/image and goes no further.
The strange thing is, if I put an alert in the code (where I've commented it in capital letters), Internet Explorer works correctly.
This must be an issue with synchronicity and timing but I have no experience or knowledge of it.
Can anyone suggest a solution? Hacky is fine, if necessary.
Cheers,
First thing is: /!\ When use the callback pattern, your "flow" need to re-begin in the callback function.
I can see that you have problems with the Asynchronous and callback approach. When you $.getJSON but also every time you make a Frog.API call, example:
Frog.API.get("users.search", {
"params": {
"group": id
},
"onSuccess": function (data){
for (var i = 0; i < data.length; i++)
list += data[i].id + ",";
}
});
Here you retrieve data, and put them in a list with the onSuccess callback function. My guess is that this call is also asynchronous. If this call takes too long:
if( typeof list === "string" && list.length > 0 ) {
won't pass. So nothing will happening and your display will try to get values of an undefined object => error, the JavaScript stops, no update of your view.
You need to getJSON after your list is retrieve, in the onSuccess callback. And this will help because you make the same mistake after:
In what follow you ask to have a display, but you absolutely don't know if your calls are finished. The fact it asked for the last call does not mean any of the calls are finished.
if( end === true )
HighestClass.display();
So you just need to add that after:
HighestClass.array.push(obj);
which is in your $.getJSON call.
Ajax call are usually Asynchronous and your problem is that you try to update the display synchronously with the current flow, without waiting for your server to answer.
/!\ When use the callback pattern, your "flow" need to re-begin in the callback function. Using that, you will always be sure that the code you are running has all the data it needs to achieve it's duty.
PS: Here is all the code modified. I also modified your function init. You do not need to call your API again to redo the same thing. just loop twice on the data or put the only relevant data aside in an array then loop on it.
HighestClass = {};
HighestClass.array = [];
HighestClass.url = "http://our.url.local/frog/pointsByWeek.php?cmd=highestClass&students=";
HighestClass.init = function(groupPrefix) {
/* Using the group prefix, i.e. "CLS 9", from the drop-down box,
get a list of all of the classes in that year group */
Frog.API.get('groups.getAll',{
'onSuccess': function(data){
var i = 0,
l = 0,
count = 0,
group = [];
/* First time round, count the number of groups that match this
syntax because there are no parameters available to filter
this API */
for (i = 0, l = data.length; i < l; i++) {
if (data[i].name.indexOf(groupPrefix) != -1)
group.push(data[i]);
}
/* Now that these classes have been counted, run through the API
call again to push each class ID through to another function */
l = group.length;
count = l - 1;
for (i = 0; i < l; i++) {
// i == count will be true when it is the last one
HighestClass.getClassPoints( group[i].name, group[i].id, i == count);
}
});
}
HighestClass.getClassPoints = function(name, id, end) {
/* Using the ID of the class group, create a comma-separated list
of students for use in our MySQL query */
Frog.API.get("users.search", {
"params": {
"group": id
},
"onSuccess": function (data){
var list = '';
// We have data and build our string
for (var i = 0; i < data.length; i++)
list += data[i].id + ",";
/* If the list exists... */
if( typeof list === "string" && list.length > 0 ) {
list = list.slice(0,-1);
/* Run an AJAX call to our PHP script which simply returns an integer
value of the SUM of reward points earned by that list of students */
UWA.Data.getJson(HighestClass.url + list, function(res){
if (res === false || res === "") res = 0;
/* Push this data into an array of objects alongside the class name */
var obj = { "name": name, "points": res };
HighestClass.array.push(obj);
/* As this function is being called asynchronously multiple times we need to
determine when the last call is run so that we can deal with our array
of data. We do this thanks to the count/run_through variables in earlier
function which will trigger end=true in this function */
if( end === true )
HighestClass.display();
});
}
}
});
}
HighestClass.display = function() {
/* Once we've put our array of objects together, we need to sort it so that
the class with the highest number of points are at array entry 0 */
function compare(a,b) {
if (a.points < b.points)
return 1;
if (a.points > b.points)
return -1;
return 0;
}
/* IF I PUT AN ALERT HERE, INTERNET EXPLORER WORKS, LOL? */
HighestClass.array.sort(compare);
/* We can then display the data of array entry 0 which should be our highest
point-scoring class */
if (HighestClass.array.length > 0)
$('#display').html( '<h1>' + HighestClass.array[0].name + '</h1><h3>' + HighestClass.array[0].points + '</h3>' );
else
$('#display').html( '<h1>No data available</h1>' );
}
/* equivalent of document ready */
widget.onLoad = function(){
/* Choose the year group from the drop down box */
$("select").change(function(){
var val = $('select option:selected').val();
$("#display").html('<h1><img width="60" height="60" src="http://logd.tw.rpi.edu/files/loading.gif" />Loading...</h1>');
try {
HighestClass.init(val);
} catch (e) {
$("#display").html('<h1>Sorry, an error occured while retrieving data</h1>');
}
});
}
The fact that an alert "fixes" the problem does indicate that it's something to do with a timing issue. It looks like one of your functions isn't returning in time and not populating the array variable correctly.
Try making the count and end variables global and seeing if that helps. I think it's something to do with scope.
It's most likely because your Ajax call is async here:
UWA.Data.getJson(HighestClass.url + list, function(res){
if (res === false || res === "") res = 0;
/* Push this data into an array of objects alongside the class name */
var obj = { "name": name, "points": res };
HighestClass.array.push(obj);
});
and HighestClass.array is empty at when HighestClass.display(); is called unless you wait for your ajax call to complete. You can make your ajax call synchronous or put this HighestClass.display(); in the Ajax callback.

Categories

Resources