Query JSON-based API with user input with just javascript - javascript

I've been watching tutorials on using JSON data and JS, decided to work with an API and make a simple APP. I ran into a snag and I'm not sure what's causing the issue. The issue is around the way I'm using user input to modify the query string. When I make my endpoint something static and get rid of the 'movieSearch' function,like this:
const movies = [];
const endpoint = 'http://www.omdbapi.com/?apikey=myAPIkey=batman';
fetch(endpoint)
.then(blob => blob.json())
.then(data => movies.push(...data.Search));
It works as desired, granted it's static.
My current code is:
const movies = [];
function movieSearch() {
const replace = this.value;
const endpoint = 'http://www.omdbapi.com/?apikey=myAPIkey=' + replace;
movies.length = 0;
fetch(endpoint)
.then(blob => blob.json())
.then(data => movies.push(...data.Search));
}
function findMatches(wordToMatch, movies) {
return movies.filter(film => {
const regex = new RegExp(wordToMatch, 'gi');
return film.Title.match(regex) || film.Year.match(regex)
})
}
function displayMatches() {
const matchArray = findMatches(this.value, movies);
const html = matchArray.map(film => {
const regex = new RegExp(this.value, 'gi');
const titleName = film.Title.replace(regex, `<span class="hl">${this.value}</span>`)
const yearName = film.Year.replace(regex, `<span class="hl">${this.value}</span>`)
return `
<li>
<span class="name">${titleName}, ${yearName}</span>
<span class="population">${film.imdbID}</span>
</li>
`;
}).join('');
suggestions.innerHTML = html;
}
const searchInput = document.querySelector('.search');
const suggestions = document.querySelector('.suggestions');
searchInput.addEventListener('keyup', displayMatches);
searchInput.addEventListener('change', displayMatches);
searchInput.addEventListener('keyup', movieSearch);
The displayMatches function starts acting funny and sometimes returns the list items and other times doesn't. I can't figure out what's causing it. Whichever way I call my endpoint my movies array looks the same, so I'm thoroughly confused.
Any suggestions? Is there a better way to do this?
My HTML is fairly simple right now:
<form class="search-form">
<input type="text" class="search" placeholder="Movies">
<ul class="suggestions">
<li>test1</li>
<li>test2</li>
</ul>
</form>
Thanks!
(I'm trying to do this all in JS)
Edit:
An example of the JSON data when searching batman with the API:
{"Search":[{"Title":"Batman Begins","Year":"2005","imdbID":"tt0372784","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BZmUwNGU2ZmItMmRiNC00MjhlLTg5YWUtODMyNzkxODYzMmZlXkEyXkFqcGdeQXVyNTIzOTk5ODM#._V1_SX300.jpg"},{"Title":"Batman v Superman: Dawn of Justice","Year":"2016","imdbID":"tt2975590","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BYThjYzcyYzItNTVjNy00NDk0LTgwMWQtYjMwNmNlNWJhMzMyXkEyXkFqcGdeQXVyMTQxNzMzNDI#._V1_SX300.jpg"},{"Title":"Batman","Year":"1989","imdbID":"tt0096895","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMTYwNjAyODIyMF5BMl5BanBnXkFtZTYwNDMwMDk2._V1_SX300.jpg"},{"Title":"Batman Returns","Year":"1992","imdbID":"tt0103776","Type":"movie","Poster":"https://ia.media-imdb.com/images/M/MV5BOGZmYzVkMmItM2NiOS00MDI3LWI4ZWQtMTg0YWZkODRkMmViXkEyXkFqcGdeQXVyODY0NzcxNw##._V1_SX300.jpg"},{"Title":"Batman Forever","Year":"1995","imdbID":"tt0112462","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BNWY3M2I0YzItNzA1ZS00MzE3LThlYTEtMTg2YjNiOTYzODQ1XkEyXkFqcGdeQXVyMTQxNzMzNDI#._V1_SX300.jpg"},{"Title":"Batman & Robin","Year":"1997","imdbID":"tt0118688","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMGQ5YTM1NmMtYmIxYy00N2VmLWJhZTYtN2EwYTY3MWFhOTczXkEyXkFqcGdeQXVyNTA2NTI0MTY#._V1_SX300.jpg"},{"Title":"The Lego Batman Movie","Year":"2017","imdbID":"tt4116284","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMTcyNTEyOTY0M15BMl5BanBnXkFtZTgwOTAyNzU3MDI#._V1_SX300.jpg"},{"Title":"Batman: The Animated Series","Year":"1992–1995","imdbID":"tt0103359","Type":"series","Poster":"https://m.media-amazon.com/images/M/MV5BNzI5OWU0MjYtMmMwZi00YTRiLTljMDAtODQ0ZGYxMDljN2E0XkEyXkFqcGdeQXVyNTA4NzY1MzY#._V1_SX300.jpg"},{"Title":"Batman: Under the Red Hood","Year":"2010","imdbID":"tt1569923","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BYTdlODI0YTYtNjk5ZS00YzZjLTllZjktYmYzNWM4NmI5MmMxXkEyXkFqcGdeQXVyNTA4NzY1MzY#._V1_SX300.jpg"},{"Title":"Batman: The Dark Knight Returns, Part 1","Year":"2012","imdbID":"tt2313197","Type":"movie","Poster":"https://m.media-amazon.com/images/M/MV5BMzIxMDkxNDM2M15BMl5BanBnXkFtZTcwMDA5ODY1OQ##._V1_SX300.jpg"}],"totalResults":"344","Response":"True"}

Issues causing this behavior:
The movieSearch function is async and might not update the data in time.
The API sometimes return an error.
This solved by the code below, note that I moved everything into the fetch resolver making sure the search only executes when the API has responded.
Here is a JS Bin: https://jsbin.com/kicesivigu/1/edit?html,js,output
function findMatches(wordToMatch, movies) {
return movies.filter(film => {
console.log(film.Title, wordToMatch);
console.log(film.Title.toLowerCase().includes(wordToMatch));
return film.Title.toLowerCase().includes(wordToMatch) || film.Year.toLowerCase().includes(wordToMatch);
});
}
function displayMatches(movies, value) {
const matchArray = findMatches(value.toLowerCase(), movies);
const html = matchArray.map(film => {
const regex = new RegExp(value, 'gi');
const titleName = film.Title.replace(regex, `<span class="hl">${value}</span>`);
const yearName = film.Year.replace(regex, `<span class="hl">${value}</span>`);
return `
<li>
<span class="name">${titleName}, ${yearName}</span>
<span class="population">${film.imdbID}</span>
</li>
`;
}).join('');
suggestions.innerHTML = html;
}
const searchInput = document.querySelector('.search');
const suggestions = document.querySelector('.suggestions');
searchInput.addEventListener('keyup', () => {
const endpoint = 'https://www.omdbapi.com/?apikey=63f88e02&s=' + searchInput.value;
fetch(endpoint)
.then(blob => blob.json())
.then(data => {
console.log('response from API');
console.log(data);
if (!data.Error) displayMatches(data.Search, searchInput.value);
});
});

Related

Know how many promise are pending

My problem is the following :
I have an array of ids that I need to map to some database Ids using an HTTP request to my API like HTTP GET /foo/{id}
I need to wait for all values to show the data in my application
I'm currently doing this the following
async getValuesByIds({}, ids){
const valuesPromise = ids.map(async (id) => this.$axios.$get(`/foo/${id}`))
return Promise.all(valuesPromise)
}
And in the code before printing the values :
this.loading = true
this.getLeadsValuesByIds(idsArray).then(data => {
this.loading = false
this.values = data
})
The code is working fine but takes some times to run if i have a lot of ids.
In general, the first request ended in about 0.5 seconds and depending on the number of request, the last one can go up to 4 to 5 seconds
My goal here is to display a loading text informating the user how many request are left and how many are done.
Here is a short example using the jsonPlaceHolder API.
Basically what i want to have is instead of loading.. The number of request left (like {n} / 99 loaded
const loadData = () => {
const ids = Array.from({length: 99}, (_, i) => i + 1)
updateDataText('loading....')
const dataPromise = ids.map(async(id) => {
const post = await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
return post.data
})
Promise.all(dataPromise).then(res => {
updateDataText(JSON.stringify(res))
})
}
const updateDataText = (text) => {
const div = document.getElementById('dataText')
div.innerText = text
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
<button onclick="loadData()">LoadData</button>
<p id="dataText"></p>
Note : I'm using Nuxt, i don't know if that change something.
You could use a <progress> tag like this
I've also added inflight and finished counter if you prefer that
const loadData = () => {
const ids = Array.from({length: 99}, (_, i) => i + 1)
let nStarted = 0, nFinished=0;
const inflight = document.getElementById('inflight');
const finished = document.getElementById('finished');
const progress = document.getElementById('progress');
progress.max = ids.length;
updateDataText('loading....')
const dataPromise = ids.map(async(id) => {
nStarted++;
inflight.textContent = nStarted - nFinished;
const post = await axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
progress.value++;
nFinished++;
finished.textContent = nFinished;
inflight.textContent = nStarted - nFinished;
return post.data
})
Promise.all(dataPromise).then(res => {
updateDataText(JSON.stringify(res, null, 4))
})
}
const updateDataText = (text) => {
const div = document.getElementById('dataText')
div.innerText = text
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
<button onclick="loadData()">LoadData</button>
<progress id="progress" value=0></progress>
<div>In Flight: <span id="inflight"></span></span>
<div>Finished: <span id="finished"></span></span>
<pre id="dataText"></pre>

Async JS validation issues for html textarea

I'm trying to replicate the code in this article:
https://depth-first.com/articles/2020/08/24/smiles-validation-in-the-browser/
What I'm trying to do different is that I'm using a textarea instead of input to take multi-line input. In addition to displaying an error message, I also want to display the entry which doesn't pass the validation.
The original validation script is this:
const path = '/target/wasm32-unknown-unknown/release/smival.wasm';
const read_smiles = instance => {
return smiles => {
const encoder = new TextEncoder();
const encoded = encoder.encode(`${smiles}\0`);
const length = encoded.length;
const pString = instance.exports.alloc(length);
const view = new Uint8Array(
instance.exports.memory.buffer, pString, length
);
view.set(encoded);
return instance.exports.read_smiles(pString);
};
};
const watch = instance => {
const read = read_smiles(instance);
document.querySelector('input').addEventListener('input', e => {
const { target } = e;
if (read(target.value) === 0) {
target.classList.remove('invalid');
} else {
target.classList.add('invalid');
}
});
}
(async () => {
const response = await fetch(path);
const bytes = await response.arrayBuffer();
const wasm = await WebAssembly.instantiate(bytes, { });
watch(wasm.instance);
})();
For working with a textarea, I've changed the watch function to this and added a <p id="indicator"> element to the html to display an error:
const watch = instance => {
const read = read_smiles(instance);
document.querySelector("textarea").addEventListener('input', e => {
const { target } = e;
var lines_array = target.value.split('/n');
var p = document.getElementById("indicator");
p.style.display = "block";
p.innerHTML = "The size of the input is : " + lines_array.length;
if (read(target.value) === 0) {
target.classList.remove('invalid');
} else {
target.classList.add('invalid');
}
});
}
I'm not even able to get a count of entries that fail the validation. I believe this is async js and I'm just a beginner in JavaScript so it's hard to follow what is happening here, especially the part where the function e is referencing itself.
document.querySelector("textarea").addEventListener('input', e => {
const { target } = e;
Can someone please help me in understanding this complicated code and figuring out how to get a count of entries that fail the validation and also printing the string/index of the same for helping the user?
There is a mistake in you code to count entries in the textarea:
var lines_array = target.value.split('\n'); // replace /n with \n
You are asking about the function e is referencing itself:
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables. You can find more informations Mdn web docs - Destructuring object

How do I use the variable out of its asynchronous function scope

I need to use the variable that I declared inside the asynchronous function out of that scope. Is that possible?
movies.forEach((movie) => {
const { title, poster_path, vote_average, overview, release_date, id } = movie
async function getCredits(url) {
const res = await fetch(url)
const data = await res.json()
const directors = [];
const directorsName = directors.join(", ") // The one I want to bring out of its scope
}
const CREDITS_URL = `the credits url goes here and it uses this -> ${id}`
getCredits(CREDITS_URL)
const directorsName = directors.join(", ") // Like this
const card = document.createElement("div")
card.innerHTML = `
<div class="director">
<h3>Directed by ${directorsName}</h3> // This is where I need the variable
</div>
`
cards.appendChild(card)
})
It's possible to return something from your async function. You can move your getCredits function out of the loop, and make the loop async, something like:
async function getCredits(url) {
const res = await fetch(url)
const data = await res.json()
const directors = [];
// Do something with data?
return directors.join(", ");
}
movies.forEach(async (movie) => {
const { id } = movie
const CREDITS_URL = `the movie url goes here and it uses this -> ${id}`
const response = await getCredits(CREDITS_URL);
const card = document.createElement("div")
card.innerHTML = `
<div class="director">
<h3>Directed by ${response}</h3> // This is where I need the variable
</div>
`
cards.appendChild(card)
});
If you'd like to have the DOM be created more quickly and have a sort of "lazy loading" of directors' names, you could do something like this:
movies.forEach((movie) => {
const { title, poster_path, vote_average, overview, release_date, id } = movie;
const card = document.createElement('div');
card.innerHTML = `<div class="director">
<h3>Directed by <span class='dname'>...</span></h3>
</div>`;
const dname = card.querySelector('.dname');
async function getCredits(url) {
const res = await fetch(url);
const data = await res.json();
const directors = [];
dname.textContent = directors.join(', ');
}
const CREDITS_URL = `the credits url goes here and it uses this -> ${id}`;
getCredits(CREDITS_URL);
cards.appendChild(card);
});
Here you're creating the div and appending it immediately, but with a ... for directors' names. But you put that part in a span and then set the textContent of the span once getCredits resolves.
*Edit: This also has the added side-benefit of preventing HTML injection from your director's return, by not inserting them using innerHTML.

How to fetch and display a specific index in a JSON file using a Javascript loop

I am currently using fetch in javascript to obtain information from another site into my own. The issue I am having is, I am using a loop to display all of the indexes of the JSON file into my site. I actually want to get specific indexes to show, not all of them, for example, index 2,4 and 6.
Here is my code so far:
window.addEventListener("load", (event)=>{
const requestURL = 'https://byui-cit230.github.io/weather/data/towndata.json';
fetch(requestURL)
.then(function (response) {
return response.json();
})
.then(function (jsonObject) {
const towns = jsonObject['towns'];
for (let i = 0; i < towns.length; i++ ) {
let towninfo = document.createElement('section');
let townname = document.createElement('h2');
townname.textContent = towns[i].name;
towninfo.appendChild(townname);
document.querySelector('div.weathertowns').appendChild(towninfo);
}
});
})
This displays all of the towns in reference, but I only want to display the title of 3 specific ones. Any suggestions on how to proceed with this?
something like that
window.addEventListener("load", (event)=>
{
const requestURL = 'https://byui-cit230.github.io/weather/data/towndata.json'
, divWeathertowns = document.querySelector('div.weathertowns')
;
fetch(requestURL)
.then( response=>response.json() )
.then( jsonObject=>
{
const towns = jsonObject['towns']
;
for ( let i of [2,4,6] )
{
let towninfo = document.createElement('section')
, townname = document.createElement('h2')
;
townname.textContent = towns[i].name;
towninfo.appendChild(townname);
divWeathertowns.appendChild(towninfo);
}
});
})
<div class="weathertowns"></div>

Cannot filter empty element from an array

I have a problem with this piece of code.
I import input data from a file formated like so and store it in const input:
aabcccccaaa
aaccb
shsudbud
There are no spaces or any other white characters except from '\n' newline.
I get inputs in this way: (LiveServer inside VS Code)
const getData = async () => {
const resp = await fetch("./inputs.txt");
const data = await resp.text();
return data;
};
Then I call:
const myFunc = async () => {
const input = await getData();
const rows = input.split("\n").map(row => row);
rows.forEach(row => {
const charArr = [...row];
console.log(charArr);
});
};
After logging to console first and second row it seems like there is "" (empty string) attached to the end of each of them. The third element is fine so I guess its somehow connected with newline character.
I have also tried creating charArr by doing:
const charArr = Array.from(row);
Or
const charArr = row.split("");
But the outcome was the same.
Later I found this topic: Remove empty elements from an array in Javascript
So I tried:
const charArr = [...row].filter(Boolean);
But the "" is still at the end of charArr created from 1st and 2nd row.
const input = `aabcccccaaa
aaccb
shsudbud`;
const rows = input.split("\n").map(row => row);
rows.forEach(row => {
const charArr = [...row];
console.log(charArr);
});
In this snippet everything works fine. So here is where my questions start:
Why does .filter() method not work properly in this case?
Could this problem browser specific?
Thanks in advance.

Categories

Resources