Marking searched results with JavaScript - javascript

I made a search bar that takes everything typed into it and checks if the json file has the name, author or category that matches to it.
How could I make it so that everything typed into the search bar gets highlighted in the part where it displays matched results?
Here is a picture of the search bar for example. I typed in FPS and it found a category with FPS in it. I would want the fps part to be highlighted.
How I made the search bar work:
const search = document.getElementById("search");
const matchList = document.getElementById("match-list");
const searchStates = async searchText => {
const res = await fetch("firebase link");
const states = await res.json();
const listaInformacija2 = Object.values(states)
let matches = listaInformacija2.filter(state => {
const regex = RegExp(`^${searchText}`, "gi");
return state.autor.match(regex) || state.naziv.match(regex) || state.kategorija.match(regex);
});
if(searchText.length === 0) {
matches = []
matchList.innerHTML = "";
}
outputHtml(matches);
};
const outputHtml = matches => {
if(matches.length > 0){
const html = matches.map(match => `
<a href=kurs.html?id=${match.id} id="searchedLink">
<div class="course-col" id="allCourses">
<h4>${match.naziv}</h4>
<p>Kategorija: ${match.kategorija}</p>
<p>Autor: ${match.autor}</p>
</div>
</a>
`).join("");
matchList.innerHTML = html;
}
}
search.addEventListener("input", () => searchStates(search.value));

You can add a specific span element to the highlighted text dynamically, I would also avoid making a Firebase call each search, as it would cost a lot of money if the app scale, here is what I got:
const FIREBASE_URL = '<your firebase url>';
const $search = document.getElementById('search');
const $matchList = document.getElementById('match-list');
const data = {};
function escape(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function search(query) {
if (!query || !data.statesList) return [];
const matches = data.statesList.filter(state => query.test(state.autor) || query.test(state.naziv) || query.test(state.kategorija));
return matches;
}
function render(matches, originalValue, query) {
if (!matches.length) {
$matchList.innerHTML = '';
return;
}
const html = matches
.map(match => {
const replacer = `<span class="search-match">${originalValue}</span>`;
return `
<a href=kurs.html?id=${match.id} class="searchedLink">
<div class="course-col" class="allCourses">
<h4>${match.naziv.replace(query, replacer)}</h4>
<p>Kategorija: ${match.kategorija.replace(query, replacer)}</p>
<p>Autor: ${match.autor.replace(query, replacer)}</p>
</div>
</a>`;
})
.join('');
$matchList.innerHTML = html;
}
function inputHandler(event) {
const { value } = event.target;
let query;
if (!value) query = null;
else query = new RegExp(escape(value), 'i');
const matches = search(query);
render(matches, value, query);
}
async function init() {
const response = await fetch(FIREBASE_URL);
data.states = await response.json();
data.statesList = Object.values(data.states);
$search.addEventListener('input', inputHandler);
}
init().catch(console.error);
And your CSS could look like this:
.search-match {
background-color: #e0d392f0;
}

Related

Passing a function to a button within dangerouslySetInnerHTML doesn't work

I am trying to add a button tag between a text when a pattern is found in react, this is done inside the dangerouslySetInnerHTML attribute. The onClick only works with alert() and console.log(), it doesn't work when I passed a function to it. What am I missing?
export const detectHashTagPattern = (text) => {
if(!text) return '';
let pattern = /This is a follow-up to your previous request #[0-9]+/gi;
let hashTagPattern = /#[0-9]+/;
text = text.replace(pattern, (res) => {
return res.replace(hashTagPattern, `<button onClick={}>${hashTagPattern.exec(res)}</button>`);
});
return text;
};
Well you could change the algorithm like the following:
function clickHandler() {
console.log("do something");
}
window.clickHandler = clickHandler;
export const detectHashTagPattern = (text) => {
if (!text) return "";
let pattern = /This is a follow-up to your previous request #[0-9]+/gi;
let hashTagPattern = /#[0-9]+/;
text = text.replace(pattern, (res) => {
return res.replace(
hashTagPattern,
// use window.<fct>()
`<button onClick="window.${window.clickHandler.name}()">${hashTagPattern.exec(
res
)}</button>`
);
});
return text;
};
You shouldn't go with this approach, as it might have other issues with the rendering in your application (depends what you're doing in the handler).
A better approach would be like this:
const splitSubject = (text) => {
if (!text) {
return [""];
}
let pattern = /This is a follow-up to your previous request #[0-9]+/gi;
if (!pattern.test(text)) {
return [text];
}
let hashTagPattern = /#[0-9]+/;
let result = text.search(hashTagPattern);
return [text.slice(0, result), text.slice(result), text.slice(result + 1)];
};
const Subject = ({ subject, handler }) => {
const splitted = splitSubject(subject);
let content = null;
if (splitted.length === 1) {
content = <span>{splitted[0]}</span>;
} else {
let [info, cmd, reqReference] = splitted;
content = (
<>
<span>{info}</span>
<button onClick={() => handler?.(reqReference)}>{cmd}</button>
</>
);
}
return <p>{content}</p>;
};
export default function App() {
const requestHandler = (num) => {
console.log(`click on '${num}'`);
};
return (
<div>
<Subject
handler={requestHandler}
subject="This is a follow-up to your previous request #9"
/>
<Subject handler={requestHandler} subject="Not matching subject" />
</div>
);
}

I want to add all results to csv, but it only adds the first page results

I am very new to Coding. Currently I am learning some web scraping tutorials.
I have some issues in my code. I want to add all results to csv, but it only adds the first page results.
const rp = require('request-promise');
const otcsv = require('objects-to-csv');
const cheerio = require('cheerio');
const { Console } = require('console');
const baseUrl = ['https://clutch.co/directory/mobile-application-developers?page=1','https://clutch.co/directory/mobile-application-developers?page=2'];
const getCompanies = async () => {
for (n = 0; n < baseUrl.length; n++) {
const html = await rp(baseUrl[n]);
const businessMap = cheerio('h3 > a', html).map(async (i, e) => {
const link = "https://clutch.co" + e.attribs.href;
const innerHtml = await rp(link);
let title = cheerio('.h2_title', innerHtml).text().replace(/\s\s+/g, ' ');
let rating = cheerio('#summary_section > div > div.col-md-6.summary-description > div:nth-child(2) > div.col-md-9 > div > span', innerHtml).text();
let name = e.children[0].data;
//console.log(name)
return {
title,
rating,
name
}
}).get();
return Promise.all(businessMap);
}
};
getCompanies()
.then(result => {
const transformed = new otcsv(result);
return transformed.toDisk('./output.csv');
})
.then(() => console.log('Done'));
And one more question: Can anyone please explain this line
const businessMap = cheerio('h3 > a', html).map(async (i, e) => {
Thanks in advance
You are already returning the first result in your FOR-Loop in your function getCompanies. Once you return inside a function it is finished so the other iterations are not executed. Therefore you only get the results for the first page.
You can create a buffer array outside the FOR-Loop and add results from inside the FOR-Loop and then return all results below the FOR-Loop.
For example (without testing your code):
const getCompanies = async () => {
const results = [];
for (n = 0; n < baseUrl.length; n++) {
const html = await rp(baseUrl[n]);
const businessMap = cheerio('h3 > a', html).map(async (i, e) => {
const link = "https://clutch.co" + e.attribs.href;
const innerHtml = await rp(link);
let title = cheerio('.h2_title', innerHtml).text().replace(/\s\s+/g, ' ');
let rating = cheerio('#summary_section > div > div.col-md-6.summary-description > div:nth-child(2) > div.col-md-9 > div > span', innerHtml).text();
let name = e.children[0].data;
//console.log(name)
return {
title,
rating,
name
}
}).get();
const result = await Promise.all(businessMap);
results.concat(result);
}
return results;
};

React text splitting with html generation

My input is
const text = 'Hello #kevin12 How are you?'
How I render it
<span>{text}</span>
How I want to render it
<span>Hello <em>#kevin12</em> How are you?</span>
My parsing function (incomplete)
const parseText = value => {
const mention = value.substring(value.lastIndexOf('#')+1)
const words = value.split(mention)
return words.map(word => word === mention ? React.createElement('em', {}, word) : word)
}
...
<span>{parseText(text)}</span>
Please help me complete this rendering function.
const parseText = value => {
const words = value.split(' ')
return words.map((word, index) => {
if (index !== words.length - 1) {
word += " "
}
return word[0] === '#' ? <em>{word}</em> : word;
})
}
Split by words, iterate over the array and find the item that start with #
export default function App() {
const text = "Hello #kevin12 How are you?";
const parseText = value => {
const mention = value.split(" ");
return mention.map(w => (w[0] === "#" ? <em> {w} </em> : <> {w}</>));
};
return (
<span>
<span>{parseText(text)}</span>
</span>
);
}
I will suggest going through the regex, here is the sample code
const text = 'Hello #kevin12 How are you?';
let finalResult = test;
let re =/(?:^|\W)#(\w+)(?!\w)/g;
let match,matches =[];
while(match =re.exec(test)){
let found ='#'+match[1];
let replace ='<em>'+found+'</em>';
finalresult =finalResult.replace(found,replace);
}
console.log(finalResult) ;

How to prevent using onclick when tag created?

I have a problem. When I try to create a tag through document.createElement, I try to add onclick. When function is active, it starts all onclick in this code
const setLiked = (name) => {
console.log(name);
};
const deleteLiked = (name) => {
console.log(name);
};
const getRate = () => {
if ('content' in document.createElement('template')) {
const root = document.querySelector('#root');
const tempExc = document.querySelector('#getRate');
const exc = tempExc.content.querySelector('.exchange');
for (let key in newRate) {
const p = document.createElement('p');
p.textContent = newRate[key].Name + ': ';
const span = document.createElement('span');
span.textContent = newRate[key].Value;
p.appendChild(span);
if (liked.find((el) => el === key)) {
const like = document.createElement('button');
like.textContent = ' Избранное';
like.className = 'liked';
like.onclick = deleteLiked(key);
p.appendChild(like);
exc.prepend(p);
} else {
const like = document.createElement('button');
like.textContent = ' В избранное';
like.className = 'like';
like.onclick = setLiked(key);
p.appendChild(like);
exc.append(p);
}
}
const clone = document.importNode(tempExc.content, true);
root.appendChild(clone);
}
};
So like.onclick is used, when getRate function starts. How I can prevent it?
Currently you're immediately executing the functions and setting the returned value of the function as the onclick value. Your functions don't return anything so the returned value will be undefined.
like.onclick = undefined;
Instead, wrap a function around the callback you're trying to assign.
like.onclick = () => deleteLiked(key);

Node.js How to retrieve data from http.get response

I am doing some practice in node.js. In this exercise I been asked to find a country name through a GET Http Request to an endpoint passing a page integer as a parameter.
Where the important response structs are these {page, total_pages, data}.
page is the current page,
total_pages is the last page,
data is an array of 10 country object.
In getCountryName func I am able to retrieve the right answer only if the answer is on the 1st page, the 1 iteration of the loop. So, why the loop only happens once?
Aditional, I wanted to retrieve the total_pages to replace the hardcode '25' value but I do not figure it out how to return it along with the search.
Any hint you wanna give me? The whole problem is in getCountryCode func.
'use strict';
const { Console } = require('console');
const https = require('https');
function makeRequest(page){
return new Promise(resolve => {
let obj='';
https.get('https://jsonmock.hackerrank.com/api/countries?page='+page, res => {
let data ='';
res.on('data',function(chunk){
data+=chunk;
});
res.on('end',function(){
obj=JSON.parse(data);
resolve(obj);
});
});
});
}
async function getCountryName(code) {
var res = '';
var pages = 25;
var i = 1;
while(i <= pages && res == ''){
console.log(i);
res = makeRequest(i)
.then(data => {
let f = ''
let p = data['total_pages'];
let search = data['data'].find(o => o.alpha3Code === code);
f = search != null ? search['name'] : f;
return f;
});
i++;
}
return res;
}
async function main() {
const name = await getCountryName('ARG');
console.log(`${name}\n`);
}
main();
Without modifying your code too much, this is how you do it:
'use strict';
const { Console } = require('console');
const https = require('https');
function makeRequest(page){
return new Promise(resolve => {
let obj='';
https.get('https://jsonmock.hackerrank.com/api/countries?page='+page, res => {
let data ='';
res.on('data',function(chunk){
data+=chunk;
});
res.on('end',function(){
obj=JSON.parse(data);
resolve(obj);
});
});
});
}
async function getCountryName(code) {
const pages = 25;
var i = 1;
let f = null
while(i <= pages && f === null){
console.log(i);
const data = await makeRequest(i) // put in try/catch
const p = data['total_pages'];
const search = data['data'].find(o => o.alpha3Code === code);
f = search !== null ? search['name'] : null;
i++;
}
return res;
}
async function main() {
const name = await getCountryName('ARG');
console.log(`${name}\n`);
}
main();

Categories