The autocomplete script:
const search = document.getElementById('search');
const matchList = document.getElementById('match-list');
let states;
// Get states
const getStates = async () => {
const res = await fetch('../complete/data/pictures.json');
states = await res.json();
};
// FIlter states
const searchStates = searchText => {
// Get matches to current text input
let matches = states.filter(state => {
const regex = new RegExp(`^${searchText}`, 'gi');
return state.title.match(regex);
});
// Clear when input or matches are empty
if (searchText.length === 0) {
matches = [];
matchList.innerHTML = '';
}
outputHtml(matches);
};
// Show results in HTML
const outputHtml = matches => {
if (matches.length > 0) {
const html = matches
.map(
match => `<div class="card card-body mb-1">
<h4>${match.title}</h4>
</div>`
)
.join('');
matchList.innerHTML = html;
}
};
window.addEventListener('DOMContentLoaded', getStates);
search.addEventListener('input', () => searchStates(search.value));
The code above generates autocomplete HTML suggestions. I want to add clickable functionality which would insert a clicked html element into the search bar.
The closest I've gotten is this:
matchList.addEventListener('click', () => {search.value = matchList.textContent.trim()})
While this works if there's only one suggestion, if there's more, all the suggestions are inserted together.
The problem seems to be the fact that matchList is an object which returns a single HTML element.
How do I return an object which I can iterate over and place an onclick on every HTML suggestion within that object?
Or, how do I return multiple objects, each containing one HTML suggestion on which I can then place an onclick?
Or some third option?
matchList.addEventListener('click', e => search.value = e.target.textContent.trim())
Related
I've been learning JavaScript for only one week so please be gentle! Tried searching for an answer but no luck.
I am writing a names generator and I want the user to click on a button "girlButton" to see a random girl name, same with boys. The problem is that it is generating one name for each button and to see another name, I need to refresh the page. I want to be able to smash the buttons and see a different name every time. I am pretty sure it's something basic, but I am stuck so please could you help? :) I am not pasting Arrays because they are pretty long. Thanks!
const girlsNamesArray = girlsNames.split(' ');
console.log(girlsNamesArray);
const boysNamesArray = boysNames.split(' ');
console.log(boysNamesArray);
let randomGirl = girlsNamesArray[Math.floor(Math.random() * girlsNamesArray.length)];
const girlButton = document.querySelector('.names__button--girl');
let randomBoy = boysNamesArray[Math.floor(Math.random() * boysNamesArray.length)];
const boyButton = document.querySelector('.names__button--boy');
let proposedName = document.querySelector('.names__idea');
girlButton.addEventListener('click', e => proposedName.innerText = `Nazwij córkę ${randomGirl.slice(0, -1)}`);
boyButton.addEventListener('click', e => proposedName.innerText = `Nazwij syna ${randomBoy.slice(0, -1)}`);
I want to be able to hit buttons multiple times and see different values from the array.
Please try with following code block
const girlsNamesArray = girlsNames.split(' ');
console.log(girlsNamesArray);
const boysNamesArray = boysNames.split(' ');
console.log(boysNamesArray);
const girlButton = document.querySelector('.names__button--girl');
const boyButton = document.querySelector('.names__button--boy');
let proposedName = document.querySelector('.names__idea');
girlButton.addEventListener('click', e => {
const string = girlsNamesArray[Math.floor(Math.random() * girlsNamesArray.length)];
proposedName.innerText = `Nazwij córkę ${string}`
});
boyButton.addEventListener('click', e => {
const string = boysNamesArray[Math.floor(Math.random() * boysNamesArray.length)];
proposedName.innerText = `Nazwij syna ${string}`
});
Here you have to write the logic of random inside the event binding.
When a button is clicked, all it does right now is set the innerText of the proposedName element. It doesn't run the random number generator again. So you need to group the code to re-run it:
let girlsNames = "gloriax deborahx fayex"
let boysNames = "jackx petex stefanx"
const girlsNamesArray = girlsNames.split(' ');
console.log(girlsNamesArray);
const boysNamesArray = boysNames.split(' ');
console.log(boysNamesArray);
let randomGirl;
const girlButton = document.querySelector('.names__button--girl');
let randomBoy;
const boyButton = document.querySelector('.names__button--boy');
let proposedName = document.querySelector('.names__idea');
function selectNewNames() {
randomBoy = boysNamesArray[Math.floor(Math.random() * boysNamesArray.length)];
randomGirl = girlsNamesArray[Math.floor(Math.random() * girlsNamesArray.length)];
}
girlButton.addEventListener('click', e => {selectNewNames(); proposedName.innerText = `Nazwij córkę ${randomGirl.slice(0, -1)}`});
boyButton.addEventListener('click', e => {selectNewNames(); proposedName.innerText = `Nazwij syna ${randomBoy.slice(0, -1)}`});
<button type="button" class="names__button--girl">New girl</button>
<button type="button" class="names__button--boy">New boy</button>
<h2 class="names__idea"></h2>
Firstly all the best for your journey.To achieve this, you can move the logic for generating random names inside the event listeners so that every time the button is clicked, a new random name is generated .The code for the same will be something like this.
const girlButton = document.querySelector('.names__button--girl');
const boyButton = document.querySelector('.names__button--boy');
const randomIndex = (arr) = >{
return Math.floor(Math.random() * arr.length);
}
girlButton.addEventListener('click', e = >{
let randomGirl = girlsNamesArray[randomIndex(girlsNamesArray)];
proposedName.innerText = `Nazwij córkę$ {
randomGirl.slice(0, -1)
}`;
});
boyButton.addEventListener('click', e = >{
let randomBoy = boysNamesArray[randomIndex(boysNamesArray)];
proposedName.innerText = `Nazwij syna $ {
randomBoy.slice(0, -1)
}`;
});
`
I have a web page that returns a list of objects like:
date.pdf
names.csv
address.pdf
age.csv
cost.csv
budget.csv
data.pdf
race.pdf
contractors.csv
When a user checks budget.csv, I want every object with the .csv extension from that point to be pushed into csv_files[]. If they select names.csv, then every .csv including and after names is pushed into the array.
So the only data that gets pushed into the array is from the selected object downwards. How can I implement this?
Current code
const csv_files = []
$scope.listAllobjects = (err, data) => {
$.each(data.Contents, (index, value) => {
if (value.Key.endsWith("csv")) {
csv_files = [];
}
// Handle click on selection checkbox
$("#filesobjects-table tbody").on("click", 'input[type="checkbox"]', (e1) => {
const checkbox = e1.currentTarget;
const $row = $(checkbox).closest("tr");
const data = $tb.DataTable().row($row).data();
let index = -1;
// Prevent click event from propagating to parent
e1.stopPropagation();
// Find matching key in currently checked rows
index = $scope.view.keys_selected.findIndex((e2) => e2.Key === data.Key);
if (checkbox.checked && data.Key.endsWith("csv")) {
console.log(selected csv)
}
});
}
There's a few ways, I suppose, to approach this problem, but the most intuitive to me is this:
const csvList = ["date.pdf","names.csv","address.pdf","age.csv","cost.csv","budget.csv","data.pdf","race.pdf","contractors.csv"];
const selectedCsv = 'budget.csv';
function getCsvsAfter(csvList, selectedCsv) {
const filteredCsvs = [];
let found = false;
for (let csv of csvList) {
if (csv === selectedCsv) found = true;
if (found) filteredCsvs.push(csv);
}
return filteredCsvs;
}
console.log(getCsvsAfter(csvList, selectedCsv));
Iterate over every csv, and when you've hit the one you're trying to match, set a variable called found to true. Once it's true, you can add every following csv onto the list.
const list = ['date.pdf','names.csv','address.pdf','age.csv','cost.csv','budget.csv','data.pdf','race.pdf','contractors.csv'];
const selected = 'budget.csv'
const csv_files = list.slice(list.indexOf(selected))
console.log(csv_files)
Here you go with a pure JavaScript solution (Descriptive comments has been added in the below code snippet).
var contentData = ["date.pdf", "names.csv", "address.pdf", "age.csv", "cost.csv", "budget.csv", "data.pdf", "race.pdf", "contractors.csv"];
var myDiv = document.getElementById("cboxes");
for (var i = 0; i < contentData.length; i++) {
var checkBox = document.createElement("input");
var label = document.createElement("label");
checkBox.type = "checkbox";
checkBox.value = contentData[i];
myDiv.appendChild(checkBox);
myDiv.appendChild(label);
label.appendChild(document.createTextNode(contentData[i]));
}
// Event to handle the checkbox click
document.getElementById('getResult').addEventListener('click', () => {
document.getElementById('showResult').innerHTML = getCheckedValues();
});
function getCheckedValues() {
// filtered out the checked items.
const element = Array.from(document.querySelectorAll('input[type="checkbox"]'))
.filter((checkbox) => checkbox.checked).map((checkbox) => checkbox.value);
// element[0] will always return the first checked element and then we are getting index of that.
const checkedElemIndex = contentData.indexOf(element[0]);
// Slice the content data to get the elements from the checked element index.
return contentData.slice(checkedElemIndex, contentData.length)
}
<div id="cboxes"></div>
<button id="getResult">Get Result</button>
<pre id="showResult"></pre>
I watched a tutorial on how to use Javascript with Autocomplete using a JSON file and Fetch. Everything works fine; except for the following:
If I clear the input, it shows all of the results in the file. I have included a comment in the code where I try to clear the results but it doesn't work.
The example on JSFiddle doesn't work because I can't add any assets.
Here is the code that should be clearing the data when no characters are in the input box:
if (matches.length === 0) {
matchList.innerHTML = ''; // Line 31: This doesn't clear the results when no input is entered.
}
But in the CSS field, I have hard coded some of the JSON file for your reference.
Thanks in advance,
Matt
using onChange() you can check final length of the keyword written in input tag, and for NULL you can just ignore the suggestion.
I played around with the code and researched it. I had to separate the code into two events. The one that was missing was when the DOM is loaded, then grab a list of states. Here is the revised code:
const search = document.getElementById('search');
const matchList = document.getElementById('match-list');
let states;
// Get states
const getStates = async () => {
const res = await fetch('states.json');
states = await res.json();
};
// FIlter states
const searchStates = (searchText) => {
// Get matches to current text input
let matches = states.filter((state) => {
const regex = new RegExp(`^${searchText}`, 'gi');
return state.name.match(regex) || state.abbr.match(regex);
});
// Clear when input or matches are empty
if (searchText.length === 0) {
matches = [];
matchList.innerHTML = '';
}
outputHtml(matches);
};
// Show results in HTML
const outputHtml = (matches) => {
if (matches.length > 0) {
const html = matches
.map(
(matt) => `<div class="card card-body mb-1">
<h4>${matt.name} (${matt.abbr})
<span class="text-primary">${matt.capital}</span></h4>
<small>Lat: ${matt.lat} / Long: ${matt.long}</small>
</div>`
)
.join('');
matchList.innerHTML = html;
}
};
window.addEventListener('DOMContentLoaded', getStates);
search.addEventListener('input', () => searchStates(search.value));
There is a React component -
'function Product (props) {
const {
prod_id: id,
prod_name : title,
prod_status: status,
prod_price: price,
prod_oldprice : oldPrice,
} = props;
let oldPriceChecker = (oldPriceValue) => {
if (oldPriceValue) {
let oldPriceStr = oldPriceValue + ' zł';
return(oldPriceStr);
}else {
return('');
}
}
let statusChecker = (value) => {
if (value != undefined){
let string = value;
let array = string.split(',');
console.log(array);
array.map(n => <div className="status">{n}</div>)
}
}
return (
<div className="row">
<div className="card">
<div className="card-image">
<img src="https://via.placeholder.com/150" />
</div>
<div className="card-content">
<span className="card-title">{title}</span>
<hr className="card_hr" />
<p className="card_price" >{price} zł</p>
<div className="card_price old_price">{oldPriceChecker(oldPrice)}</div>
{statusChecker(status)}
</div>
</div>
)
}
export {Product}
Question: The variable prod_status: status can contain several values (for example, "new,promotion", if so, you need to split it into two words and create a separate block for each, since now the block comes with the whole line
It is necessary like this (new, saleout, etc. in separate blocks)
I tried to create a function but it just outputs an array to the console
I think I'm not using the property "map" correctly
The problem:
The function you have created statusChecker does not return anything. Therefore when you want to print it ({statusChecker(status)}) it doesn't do anything.
let statusChecker = (value) => {
if (value != undefined){
let string = value;
let array = string.split(',');
console.log(array);
//This is what you want to return but is not returned
array.map(n => <div className="status">{n}</div>)
}
}
Solution:
return the mapped array from the function.
let statusChecker = (value) => {
if (value != undefined){
let string = value;
let array = string.split(',');
console.log(array);
//This is what you want to return but is not returned
return array.map(n => <div className="status">{n}</div>)
}
}
The main problem with your code is that you are trying to create an html element just by writing it, and that is not possible. The closest thing to that is for you to use innerHTML. for example: parent.innerHTML = <div className="status">${n}</div>. being "parent" an html element previously created (document.createEement()) or already existing in the DOM.
In my solution I used document.createElement() to create a div and insert the text into it. The function returns an array with the div elements and their respective words inside but only that, it doesn't print anything on the screen. If all you want is to display it on the screen the process is a bit different.
let statusChecker = (value) => {
// Instead of doing " if (value != undefined) I used
let nodes = value?.split(',').map(n => {
// first you have to create an element
let div = document.createElement('div')
// add the class status
div.classList.add('status')
// insert each word into the div
div.textContent = n;
return div
})
return nodes
}
console.log(statusChecker('new,promotion'))
I am unable to turn off auto suggestions if the input field value doesn't match the pulled 'suggestions' anymore. I am getting data from a json file.
Example, 'Piranha' is a valid entry in the json file, so if i start typing P.I.R... it correctly shows 'Piranha' as a suggestion. But it keeps showing it even if I go beyond the letters and type something like 'Piranhat' (image attached for reference).
Code:
const specieInput = document.querySelector('#specie_input');
const suggestions = document.querySelector('.suggestions');
const searchSpecies = async searchText => {
let my_data = networkJSON.full_data;
let matches = my_data.filter(my_data => {
const regex = new RegExp(`^${searchText}`, 'gi');
return my_data.specie.match(regex);
});
if (searchText.length === 0){
matches = [];
suggestions.innerHTML = '';
}
outputHTML(matches);
};
//Show results in HTML
const outputHTML = matches => {
if(matches.length > 0) {
const html = matches.map(match => `
<div class="card card-body">
<h4>${match.specie} - ${match.rate}</h4>
</div>
`
).join('');
suggestions.innerHTML = html;
}
}
specieInput.addEventListener('input', () => searchSpecies(specieInput.value));
you are not resetting the html when 0 matches found.
if (matches.length === 0) {
suggestions.innerHTML = '';
}
or you can also try to reset it at the first line of searchSpecies method