I'm struggling with the order of operations with asynchronous functions and event listeners in Javascript. I have a script with a function that fetches a list of publicly-traded companies from an API endpoint ('api/company_list'). After the data is returned, it dynamically updates a dropdown menu with the list of company symbols and names, and also displays the name of the first company in the data array on the webpage. Finally, it passes that first company's symbol as a template literal to another URL in order to fetch that company's price data from a second API endpoint and display it on a chart.
I can accomplish the above by simply grabbing the first returned company from the company list data using it's index (data[0].symbol) when the page loads.
But what I need to know is - how do I incorporate an event listener so that when a different company is selected from the dropdown, it updates both the company name on the webpage and passes the newly-selected symbol to the second URL ('api/price_data') to select the chart data?
And can I do this all in the same script?
I can do one script that runs the code below when the page loads and a second script that runs the same code encapsulated inside of a function that runs when the dropdown selection changes, but this seemed highly duplicative. There's got to be a way to streamline it.
I've tried adding an onChange event listener for my dropdown ("mySelect") in the code below, but since the dropdown is populated dynamically as part of a promise, the data isn't yet available to use when I tried to grab the newly-selected value and pass it to both the page and the second URL. Clearly I'm misunderstanding the flow async functions.
I feel like this is probably a fairly simple fix, but so far I've been unable to find a solution. Thanks in advance for your help. I'd be happy to provide additional information or answer any questions.
function getData(url) {
return fetch(url)
.then(response => {
if(!response.ok) {
throw new Error('Bad Network Request');
}
return response.json();
})
.catch(error => {
console.error('Fetch error:', error);
throw error;
});
}
//fetch the list of companies from an API endpoint and dynamically populate the dropdown list:
getData('/api/company_list')
.then(function myFunction(data) {
let option = '';
for (let i = 0; i < data.length; i++) {
option += '<option value="' + data[i].symbol + '">' + data[i].stock_rank + ' - ' + data[i].company_name + ' (' + data[i].symbol + ')' + '</option>';
}
const inputElement = $("#mySelect");
inputElement.append(option);
let firstCompany = data[0].symbol;
$("company").html(data[0].company_name);
const priceUrl = `/api/price_data/?symbol=${firstCompany}`;
getData(priceUrl)
.then(function myFunction(priceData) {
//add the price data to a chart
});
});
Consolidate and reuse the get.
I use a data attribute for faster access to the company name
Ditto period in the buttons:
<button id="btnDaily" class="btn active" data-period="daily">Daily</button>
<button id="btnWeekly" class="btn" data-period="Weekly">Weekly</button>
or use radio buttons with the same name and different values
Working example
const getData = (url, callback) => console.log(url); // test
/*
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Bad Network Request');
}
return response.json();
})
.then(data => {
callback(data); // Pass the data to the callback
})
.catch(error => {
console.error('Fetch error:', error);
throw error;
});
*/
const getActiveButton = () => $('.btn.active').data('period');
const updateCompanyInfo = companyData => {
const {
symbol,
company_name
} = companyData;
$("company").html(company_name);
const priceUrl = `/api/${getActiveButton()}/?symbol=${symbol}`;
getData(priceUrl, updateChart);
};
const updateChart = priceData => {
// Logic to update the chart with priceData
console.log('Price data:', priceData);
};
const updateHandler = () => {
const selectedSymbol = $('#mySelect').val();
const selectedCompany = $('#mySelect').find(`option[value="${selectedSymbol}"]`).data('company');
updateCompanyInfo({
symbol: selectedSymbol,
company_name: selectedCompany
});
};
getData('/api/company_list', data => {
const options = data.map(
company =>
`<option value="${company.symbol}" data-company="${company.company_name}">
${company.stock_rank} - ${company.company_name} (${company.symbol})
</option>`
).join('');
$("#mySelect").html(options); // Replace the dropdown options
updateCompanyInfo(data[0]); // Initialize with the first company
});
$('.btn').on('click', function() {
$('.btn.active').removeClass('active'); // Only target the active button
$(this).removeClass('inactive').addClass('active');
updateHandler()
});
$('#mySelect').on('change', updateHandler);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<button id="btnDaily" class="btn active" data-period="daily">Daily</button>
<button id="btnWeekly" class="btn" data-period="weekly">Weekly</button>
<select id="mySelect">
<option value="AAA" data-company="AAA">AAA - BBB - CCC</option>
<option value="DDD" data-company="DDD">DDD - EEE - FFF</option>
</select>