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
Related
I need to parse a string, which contains both text content and specific tags.
Expected result must be an array containing items, with separation between texts and tags.
An example of string to parse
There is user [[user-foo]][[/user-foo]] and user [[user-bar]]label[[/user-bar]].
Some informations:
user- tag is static.
Following part (foo or bar) is dynamic and can be any string.
Same for the text parts.
Tags can receive some text as child.
Expected result
[
'There is user ',
'[[user-foo]][[/user-foo]]',
' and user ',
'[[user-bar]]label[[/user-bar]]',
'.'
]
What I tried
Here is a regex I created:
/\[\[user-[^\]]+]][A-Za-z]*\[\[\/user-[^\]]+\]\]/g
It's visible/editable here: https://regex101.com/r/ufwVV1/1
It identifies all tag parts, and returns two matches, related to the two tags I have. But, text content is not included. I don't know if this first approach is correct.
Maybe there's a better solution in terms of efficiency... But at least, that works.
Get the tags using regex
Get the tags position (start/end) within the string
Use those positions against the string
const string = "There is user [[user-foo]][[/user-foo]] and user [[user-bar]]label[[/user-bar]]."
// Get the tags using regex
const matches = string.match(/\[\[[a-z-\/]+\]\]/g)
console.log(matches)
// Get the tags position (start/end) within the string
const matchPositions = matches.map((match) => ({start: string.indexOf(match), end: string.indexOf(match) + match.length}))
console.log(matchPositions)
// Use those positions against the string
let currentPos = 0
let result = []
for(let i=0; i<matchPositions.length; i+=2){
const position = matchPositions[i]
const secondPosition = matchPositions[i+1]
// Get the substring in front of the current tag (if any)
if(position.start !== currentPos){
const firstSubString = string.slice(currentPos, position.start)
if(firstSubString !== ""){
result.push(firstSubString)
}
}
// Get the substring from the opening tag start to the closing tag end
result.push(string.slice(position.start, secondPosition.end))
currentPos = secondPosition.end
// Get the substring at the end of the string (if any)
if(i === matchPositions.length-2){
const lastSubString = string.slice(secondPosition.end)
if(lastSubString !== ""){
result.push(lastSubString)
}
}
}
console.log(result)
Here is my solution, inspired from #louys-patrice-bessette answer.
const string = 'There is user [[user-foo]][[/user-foo]] and user [[user-bar]]label[[/user-bar]].';
const regex = /\[\[user-[^\]]+\]\][A-Za-z0-9_ ]*\[\[\/user-[^\]]+\]\]/g;
const { index, items } = [...string.matchAll(regex)].reduce(
(result, regExpMatchArray) => {
const [match] = regExpMatchArray;
const { index: currentIndex } = regExpMatchArray;
if (currentIndex === undefined) {
return result;
}
return {
items: [
...result.items,
string.substring(result.index, currentIndex),
match,
],
index: currentIndex + match.length,
};
},
{
index: 0,
items: [],
}
);
if (index !== string.length) {
items.push(string.substring(index, string.length));
}
console.log(items);
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'))
Suppose i have the following markdown
# Comman mark is **just great**
You can try CommonMark here. This dingus is powered by
[commonmark.js](https://github.com/commonmark/commonmark.js), the
JavaScript reference implementation.
## Try CommonMark
1. item one
2. item two
- sublist
- sublist
I want to get the first h1 tag and first p tag for making them title and description of the post receptively.
I can not use browser API, because it is running on the Node server
To get the first h1 tag, I used commonmark.js.
document.getElementById('btn').addEventListener('click', function (e) {
let parsed = reader.parse(md);
let result = writer.render(parsed);
let walker = parsed.walker();
let event, node;
while ((event = walker.next())) {
node = event.node;
// h1 tags
if (event.entering && node.type === 'heading' && node.level == 1) {
console.log('h1', '--', node?.firstChild?.literal);
}
// p tags
if (event.entering && node.type === 'text') {
console.log('p', '--', node?.literal);
}
}
});
For the above markdown the output I got on the console.
You can see that, the first h1 returned is Common mark is, but it should be actually # Comman mark is **just great**
Same thing for p tag, how can I solve this problem?
See live - https://stackblitz.com/edit/js-vegggl?file=index.js
const regex = {
title: /^#\s+.+/,
heading: /^#+\s+.+/,
custom: /\$\$\s*\w+/,
ol: /\d+\.\s+.*/,
ul: /\*\s+.*/,
task: /\*\s+\[.]\s+.*/,
blockQuote: /\>.*/,
table: /\|.*/,
image: /\!\[.+\]\(.+\).*/,
url: /\[.+\]\(.+\).*/,
codeBlock: /\`{3}\w+.*/,
};
const isTitle = (str) => regex.title.test(str);
const isHeading = (str) => regex.heading.test(str);
const isCustom = (str) => regex.custom.test(str);
const isOl = (str) => regex.ol.test(str);
const isUl = (str) => regex.ul.test(str);
const isTask = (str) => regex.task.test(str);
const isBlockQuote = (str) => regex.blockQuote.test(str);
const isImage = (str) => regex.image.test(str);
const isUrl = (str) => regex.url.test(str);
const isCodeBlock = (str) => regex.codeBlock.test(str);
export function getMdTitle(md) {
if (!md) return "";
let tokens = md.split("\n");
for (let i = 0; i < tokens.length; i++) {
if (isTitle(tokens[i])) return tokens[i];
}
return "";
}
export function getMdDescription(md) {
if (!md) return "";
let tokens = md.split("\n");
for (let i = 0; i < tokens.length; i++) {
if (
isHeading(tokens[i]) ||
isCustom(tokens[i]) ||
isOl(tokens[i]) ||
isUl(tokens[i]) ||
isTask(tokens[i]) ||
isBlockQuote(tokens[i]) ||
isImage(tokens[i]) ||
isUrl(tokens[i]) ||
isCodeBlock(tokens[i])
)
continue;
return tokens[i];
}
return "";
}
Since you are already in the Node.js world, I suggest you check out the unified collective's remark and rehype processors. These processors support parsing markdown and HTML respectively to/from syntax trees. All such processors in the unified collective support custom and third-party 'plugins' that enable you to inspect and manipulate the intermediary syntax trees. Powerful stuff. Bit of a learning curve though. However, at some point, RegEx breaks down with non-regular languages like markdown. Syntax trees can save the day.
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())