Why does my Ajax call not retrieve the requested value ? [duplicate] - javascript

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 6 years ago.
I have various products in a drop down list. If I change the product, then the price changes and the line cost changes commensurate with the quantities being ordered.
Problem: When I select a product, an Ajax call is made to my DB. The correct price is returned. All good. When I choose an alternative product from the drop down, the product ID and it's var value changes correctly. However,instead of returning the new correct price, priceshows the value from the previous query. That is to say, it appears that either the price is being cached, or the arraydata[0].price is not being cleared or the Ajax call is failing. I do not believe that the Ajax call is failing, as if I make then a further product change, I get a new result, BUT, again the price value from the query preceding it. Bottom line, my price is always one query behind. I have tried using cache:false but that has made difference. I also tried setting the length of the variable result to 0, that just delivered an undefined error.
$(".product_id").on('change', function() { //THIS GETS THE VALUE OF A PRODUCT FROM THE DB
var value = parseFloat($(this).val()); // product_id
var row = $(this).parents(':eq(1)').find('input').filter(".cost");
var rowPrice = $(this).parents(':eq(1)').find('input').filter(".price");
// then we use the product_id to grab the price using AJAX //
$.ajax({
type: 'GET',
url: 'product_prices/' + value,
cache:false,
success: function(data) {
data = JSON.parse(data);
var result = data[0].price;
var price = Number(result); //.toFixed(2);
row.data('price', price); // store the row's current price in memory
rowPrice.val(price); // allocate the price to the location called rowPrice
}
});
var quantity = $(this).parents(':eq(1)').find('input').filter(".quantity").val();
if (quantity > 0){
var price = row.data('price');
var cost = (price * quantity);
cost = Number(cost); //.toFixed(2);
var displaycost = $.formatNumber(cost,{format:"#,###.00",locale:"ca"});
row.val(displaycost);
var subTotal = 0;
$("input.cost").each(function(){
var ic = $(this).val();
if (ic !==''){
var inputCost = parseFloat(ic.replace(/,/g, ''));
subTotal += inputCost;}
});
var discount_percent = $("#discount").val();
if(discount_percent ==""){
discount_percent = 0;
}
var calculations = newCalculate(subTotal, discount_percent);
setValues(calculations, $("#updateValues"));
}
});

Ajax calls are asynchronous, meaning the callback function that you provide for it, is only executed later. In your case this means that this statement:
row.data('price', price);
is executed later (asynchronously) than this one:
var price = row.data('price');
...which indeed means you set the price to the previously retrieved value.
To avoid that, you'll probably want to move most of your code into the success callback function provided to $.ajax(), so that it only gets executed when you actually have received the data with the price.

Related

Why is altered value being written to original array onclick

i want to pull data from a .csv file and pass this data to a function to format every row of the .csv file to be a single object. These objects are then stored in an array called "list". So far so good. This is all working.
Next i want a button that calls a function onclick called "roll" that takes a random index from the "list" array and saves an instance of the random oject in a temporarily variable called randomItem.
Then i want to check certain properties of randomItem for specific values and if a certain condition is met it should change a specific property called "randomItem.Name". Finally i want the altered "randomItem" to be pushed into a new array called "results". These results are then being displayed on the website.
If I change the propertys value with "randomItem.Name = randomItem.Name + 'someString'" it also overwrites the original object in the "list" array. I dont want it to do this as i want to repeat the process of rolling random objects from this list several times. Therefor i need the "list" array to keep its original state. I cant get my head around why it overwrites the any list.
html
<button id="btnItem1">Roll</button>
js
$(document).ready(function() {
let list = [];
let results = [];
$('#btnItem1').click({slotName:'item1', listName:list, index:0}, roll);
// i need to pass slotName, listName and index because i have several buttons and list in the real project
getData('data.csv').then(data => formatData(data)); // get data from csv files
async function getData(url) {
const response = await fetch(url);
const data = await response.text();
return data;
};
function formatData(data) { // real formatting function taken out for stackflow
let formatted = // some formatting stuff;
list.push(formatted);
};
function roll(options) {
const slot = options.data.slotName;
const listTemp = options.data.listName;
const index = options.data.index;
if (slot.includes('item')) { // real criteria taken out for stackflow
do {
const randomNumber = Math.floor(Math.random() * (listTemp.length - 1) + 1);
let randomItem = listTemp[randomNumber];
if (1 == 1) { // real criteria taken out for stackflow
let chance = Math.round(Math.random());
if (chance) {
randomItem.Name += '(altered)';
}
}
results.splice(index, 1, randomItem);
} while (1 != 1); // real criteria taken out for stackflow
};
};
});
I expect it to write the altered "randomItem.Name" only to randomItem itself. Not even to listTemp but definitly not to the global "list". Do you have any idea why it is doing this and how i can prevent this? How can i get the object into randomItem without randomItem keeping its reference to any list. Thank you guys in advance!

Gravity Forms use JS to count rows in List and return to field?

I am trying to make a sign up form in gravity forms that uses the list field so multiple people can be signed up at once. The problem is I also need to get a quantity of how many people are signing up so I can charge a fee for each.
With JS, how would I count the number of rows in the list and pass the value to another field? Or is there a better method to do this?
UPDATE:
Based on Obsidian Age's answer, this refreshes occasionally and outputs to the quantity field:
function updateQty() {
var rows = document.querySelectorAll('.gfield_list_group').length; // Count rows
var qty = document.querySelector('.ginput_quantity'); // Define output location
qty.value = rows; // Put row count in location
setTimeout(updateQty, 2000); // Repeat every 2 seconds
}
updateQty(); // Execute
I'm not familiar with the particular plugin's outputted markup, but you can simply grab all of the desired elements with something like .querySelectorAll(). From here, it's trivial to find the number of them by simply querying their .length. If you assign this number to a variable, you can reference it later on when you want to insert it back to a different field -- which can be done by updating the element's .innerHTML with the variable.
This can be seen in the following:
const amount = document.querySelectorAll('.row').length;
const output = document.querySelector('.output');
output.innerHTML = amount;
<div class="row">One</div>
<div class="row">Two</div>
<div class="row">Three</div>
<div class="row">Four</div>
<br />
<div class="output"></div>
<script>
jQuery.expr[':'].hasValue = function(el, index, match) {
return el.value != "";
};
function updateQty() {
var listFieldID = '#field_85_12 .gfield_list_12_cell1 input:hasValue';
var totalFieldID = '#input_85_35';
var totalRows = $(listFieldID).length; // Count rows
var totalField = $(totalFieldID);
console.log(totalRows); // For testing
$( totalField ).val( totalRows +1 ).change();
setTimeout(updateQty, 3000); // Repeat every 2 seconds
}
updateQty(); // Execute
</script>

Google apps script - using for loop to pull data from range based on condition

I'm having an issue pulling the correct values out of a for loop in Google Sheets.
Here's my code:
Note: this is a snippet from a larger function
function sendEmails() {
var trackOriginSheet = SpreadsheetApp.getActiveSpreadsheet().getName();
var getMirSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Miranda");
//Set a new object to hold conditional data
var holdingData = new Object();
//Create function to get values from origin sheet
var returnedValues = function (trackOriginSheet) {
//Load dynamic variables into an object via returnedValues()
if (trackOriginSheet === getMirSheet) {
var startMirRow = 2; // First row of data to process
var numRowsMir = 506; // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(startMirRow, 1, numRowsMir, 26);
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k in dataMir) {
var secondRowMir = dataMir[k];
var intRefDescMir = secondRowMir[3];
var intAdminActionsMir = secondRowMir[4];
//Push returned data to holdingData Object
holdingData.selectedData = secondRowMir;
holdingData.refDesc = intRefDescMir;
holdingData.adminActions = intAdminActionsMir;
}
}
}
Here's a copy of the sheet I'm working on
What I need to have happened here first, is track the origin sheet, then create an object to hold data returned from the returnedValues() function. Later, I'll call the properties of this object into a send email function.
The problem is that I need to be able to pull data from the selected sheet dynamically (the "Miranda" sheet in this case.) In other words, when a user selects the "Yes" option in column I of the Miranda sheet, the first thing this script needs to do is pull the values of the variables at the top of the for loop within the same row that the user selected "Yes." Then, I'm pushing that data to a custom object to be called later.
It's apparent to me, that I'm doing it wrong. There's, at least, something wrong with my loop. What have I done? :)
EDIT:
After reviewing the suggestion by VyTautas, here's my attempt at a working loop:
for (var k = 0; k < dataMir.length; k++) {
var mirColI = dataMir[k][8];
var mirRefDesc = dataMir[k][2];
var mirAdminActions = dataMir[k][3];
var mirDates = dataMir[k][4];
if (mirColI === "Yes") {
var activeRowMir = mirColI.getActiveSelection.getRowIndex();
//Pull selected values from the active row when Yes is selected
var mirRefDescRange = getMirSheet.getRange(activeRowMir, mirRefDesc);
var mirRefDescValues = mirRefDescRange.getValues();
var mirAdminActionsRange = getMirSheet.getRange(activeRowMir, mirAdminActions);
var mirAdminActionsValues = mirAdminActionsRange.getValues();
var mirDatesRange = getMirSheet.getRange(activeRowMir, mirDates);
var mirDatesValues = mirAdminActionsRange.getValues();
var mirHoldingArray = [mirRefDescValues, mirAdminActionsValues, mirDatesValues];
//Push mirHoldingArray values to holdingData
holdingData.refDesc = mirHoldingArray[0];
holdingData.adminActions = mirHoldingArray[1];
holdingData.dates = mirHoldingArray[2];
}
}
Where did all that whitespace go in the actual script editor? :D
You already correctly use .getValues() to pull the entire table into an array. What you need to do now is have a for loop go through dataMir[k][8] and simply fetch the data if dataMir[k][8] === 'Yes'. I also feel that it's not quite necessary to use for (var k in dataMir) as for (var k = 0; k < dataMir.length; k++) is a lot cleaner and you have a for loop that guarantees control (though that's probably more a preference thing).
You can also reduce the number of variables you use by having
holdingData.selectedData = mirData[k]
holdingData.refDesc = mirData[k][2] //I assume you want the 3rd column for this variable, not the 4th
holdingData.adminActions = mirData[k][3] //same as above
remember, that the array starts with 0, so if you mirData[k][0] is column A, mirData[k][1] is column B and so on.
EDIT: what you wrote in your edits seems like doubling down on the code. You already have the data, but you are trying to pull it again and some variables you use should give you an error. I will cut the code from the if, although I don't really see why you need to both get the active sheet and sheet by name. If you know the name will be constant, then just always get the correct sheet by name (or index) thus eliminating the possibility of working with the wrong sheet.
var titleMirRows = 1; // First row of data to process
var numRowsMir = getMirSheet.getLastRow(); // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(titleMirRows + 1, 1, numRowsMir - titleMirRows, 26); // might need adjusting but now it will only get as many rows as there is data, you can do the same for columns too
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k = 0; k < dataMir.length; k++) {
if (dataMir[k][7] === 'Yes') { //I assume you meant column i
holdingData.refDesc = dataMir[k] //this will store the entire row
holdingData.adminActions = dataMir[k][3] //this stores column D
holdingData.dates = dataMir[k][4] //stores column E
}
}
Double check if the columns I have added to those variables are what you want. As I understood the object stores the entire row array, the value in column called Administrative Actions and the value in column Dates/Periods if Applicable. If not please adjust accordingly, but as you can see, we minimize the work we do with the sheet itself by simply manipulating the entire data array. Always make as few calls to Google Services as possible.

How do I access a javascript value across functions/variables?

So, three small parts:
1) a MaxMind geo IP lookup that gets us the country code via the IP address:
var onSuccess = function(x){
var ip = x.traits.ip_address;
document.getElementById('ip_address').value = ip;
var country_code = x.country.iso_code;
document.getElementById('ip_country_code').value = country_code;
…
};
2) an array of country references with tax percent decimals:
// Array of values for tax rates
var tax_rates= new Array();
tax_rates["noteu"]=0.0;
tax_rates["ES"]=21.0;
tax_rates["AU"]=20.5;
tax_rates["BE"]=21.7;
…
3) a TaxPrice function that takes one of those decimals to calculating tax and then total payable in a subscription form. Notice the XXXXX:
function TaxPrice()
{
var taxprice=0;
XXXXX
return taxprice;
}
The document.getElementById bit in 1) can obviously update a hidden field or some other HTML element.
I know what to do with XXXXX if it's a manual drop down the user has to select.
But how do I get the tax decimal out of the array and into the TaxPrice function based on the IP address country code? (i.e. within the javascript, not updating an HTML element).
Happy New Year to all.
UPDATE: Just to be clear, I don't need to know how to get it into a drop down, I can do that already and in this use case, the user should not be allowed to choose his own tax country, it should be set automatically based on the IP address. So the non-code wording would go something like:
taxprice EQUALS tax_rate.value ACCORDING TO ip_address_code
Are you looking for something like element properties?
Mydiv.tax=taxvalue;
Properties of Elements are an elegant way of communicating between different functions.
You can assign any value to any element.
You can retrieve the value from any function in JavaScript as long as the Basic element lives.
One way you can do it is to set a global selectedCountryCode variable inside your success callback, and reference tax_rates[selectedCountryCode] in your TaxPrice array (which should be an object, as nnnnnn pointed out)
(function () {
var selectedCountryCode = "";
var onSuccess = function(x) {
var ip = x.traits.ip_address;
document.getElementById('ip_address').value = ip;
selectedCountryCode = x.country.iso_code; // <-- Set selectedCountryCode for later use
document.getElementById('ip_country_code').value = selectedCountryCode; // <-- Set dropdown value
};
document.getElementById("ip_country_code").addEventListener("change", function() {
selectedCountryCode = this.value;
console.log(TaxPrice());
});
// Object of values for tax rates
var tax_rates = {};
tax_rates["noteu"] = 0.0;
tax_rates["ES"] = 21.0;
tax_rates["AU"] = 20.5;
tax_rates["BE"] = 21.7;
function TaxPrice() {
var taxprice = 0;
taxprice = tax_rates[selectedCountryCode];
return taxprice;
}
})();
Change Me: <select id="ip_country_code">
<option value="noteu">noteu</option>
<option value="ES">ES</option>
<option value="AU">AU</option>
<option value="BE">BE</option>
</select>
So, thanks for your suggestions. Not sure if I understand how this is working exactly, but after some poking around, it now is. Compared to the code in the original question, I had to:
1) Add a global variable at the top, above everything else, unrelated to the IP lookup code (i.e. there is now no reference to country_tax within the IP onSuccess variable):
var country_tax;
2) Replace XXXXX in the TaxPrice function with:
var country_tax = document.getElementById("ip_country_code").value;
var taxprice = taxes_from_database[country_tax];
So the full TaxPrice function ends up as:
function TaxPrice()
{
var taxprice = 0;
var country_tax = document.getElementById("ip_country_code").value;
var taxprice = tax_rates[country_tax];
return taxprice;
}
No need, it seems, for nested functions or closures or anything very complicated. It doesn't matter (to the code) if the tax_rates are set up as an array or an object, the outcome is the same, although I would like to understand why you recommend an object over an array in this case.
And—given TaxPrice gets the value from the form field and not from within the IP onSuccess function—I don't know why I need the global variable declaration at the top, if anyone wants to have a go at explaining that…

How to pass parameters from async function in javascript? [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 7 years ago.
I am trying to pass two values to a function from two async functions and I am not sure how to proceed. Here is the code:
var btcPriceInUSD;
var priceExchangeMXN;
var btcLink = "https://blockchain.info/ticker";
var exchangeRateLink = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.xchange%20where%20pair%20in%20%28%22USDMXN%22%29&env=store://datatables.org/alltableswithkeys&format=json";
//Get btc price in USD
$.getJSON(btcLink, function(btcData)
{
btcPriceInUSD = btcData.USD.last;
//document.write(btcPriceInUSD);
});
//Get current USD/MXN exchange rate
$.getJSON(exchangeRateLink, function(exchangeData)
{
priceExchangeMXN = exchangeData.query.results.rate.Rate;
//document.write(priceExchangeMXN);
});
//Convert btc price to MXN
function convertToMXN(btc,toMXN){
var result = parseFloat(btc) * parseFloat(toMXN);
document.write(result);
}
convertToMXN(btcPriceInUSD,priceExchangeMXN)
I know the issue is that I am calling the function outside of the async ones so it is not recieving the numbers and it is giving me a NAN (not a number) but I don't know how I would correctly pass those two parameters that are each retrieved in different functions, is it possible to combine the btcPriceInUSD and priceExchangeMXN in one and call it from there?
Thanks in advance!
Try using $.when() , .then() , substituting returning value at complete function for declaring variables outside scope of asynchronous functions ; also adding an error handler
$.when($.getJSON(btcLink, function(btcData) {
return btcData.USD.last
})
, $.getJSON(exchangeRateLink, function(exchangeData) {
return exchangeData.query.results.rate.Rate
}))
.then(convertToMXN, function err() {console.log(arguments)})
try this (simply chaining the ajax calls and finally calling the method when both values are available)
var btcPriceInUSD;
var priceExchangeMXN;
var btcLink = "https://blockchain.info/ticker";
var exchangeRateLink = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.xchange%20where%20pair%20in%20%28%22USDMXN%22%29&env=store://datatables.org/alltableswithkeys&format=json";
//Get btc price in USD
$.getJSON(btcLink, function(btcData)
{
btcPriceInUSD = btcData.USD.last;
//document.write(btcPriceInUSD);
//Get current USD/MXN exchange rate
$.getJSON(exchangeRateLink, function(exchangeData)
{
priceExchangeMXN = exchangeData.query.results.rate.Rate;
//document.write(priceExchangeMXN);
convertToMXN(btcPriceInUSD,priceExchangeMXN);
});
});
//Convert btc price to MXN
function convertToMXN(btc,toMXN){
var result = parseFloat(btc) * parseFloat(toMXN);
document.write(result);
}

Categories