Highlight matching text - javascript

When posting users text to webpage (using Mongodb and node and js) I'm trying to highlight any of their text that matches a store name from an array of stores. The code for looping through the db and posting to page:
<% posts.forEach(function(post) { %>
<div class="post">
<h4 class="date">
<span><%= post.created.toDateString() %></span>
</h4>
<p class="post_text"><%- post.body %></p>
</div>
<% }); %>
I have some practice js console code I used to match words from an array but am having difficulty moving forward with putting the text back together with the highlighted word(s). 2 word store names are another issue...
var blogInput = "We went to target last night, also to publix";
var array1 = blogInput.split(" ");
var array2 = ["kroger", "lums", "marlows", "eats", "burger king",
"home", "wendys", "publix", "donut circus", "jewelry store",
"target"];
function getMatch(a, b) {
var matches = [];
for ( var i = 0; i < a.length; i++ ) {
for ( var e = 0; e < b.length; e++ ) {
if ( a[i] === b[e] ) {
var x = a[i];
matches.push( x );
}
}
}
return matches;
}
getMatch(array1, array2);
(2) ["target", "publix"]
Using this example I would then like to put the string sentence back together and post to page with 'target' and 'publix' text in blue. Any hints or words of wisdom would be helpful. Thanks!

You will need to surround the highlighted words in a span with an specific class that changes its color.
A function that could rebuild your post back with those spans could be similar to this one.
let blogInput = "We went to target last night, also to publix";
let blogWords = blogInput.split(" ");
let searchedWords = ["kroger", "lums", "marlows", "eats", "burger king",
"home", "wendys", "publix", "donut circus", "jewelry store",
"target"];
function getMatch(a, b) { ... }
let matchedWords = getMatch(blogWords, searchedWords);
let blogText = '';
blogWords.forEach(function(word) {
if (matchedWords.indexOf(word) != -1) {
blogText += ' <span class="highlight">' + word + '</span>';
} else {
blogText += ' ' + word;
}
});
// Remove leading space
blogText = blogText.trim();
You should then use the blogText as your post text. You will also need adding a CSS rule similar to this one:
span.highlight {
color: blue;
}

You don't need two loops, just work with blogInput as a string instead of splitting it into individual words and use indexOf (or includes) to determine if the keyword is in the string. This also solves the issues of trying to find store names with multiple words.
var blogInput = "We went to target last night, also to publix";
var array2 = ["kroger", "lums", "marlows", "eats", "burger king",
"home", "wendys", "publix", "donut circus", "jewelry store",
"target"];
// filter out any store names that aren't included in blogInput
var matches = array2.filter(function(store) {
return blogInput.indexOf(store) > -1;
});
You also may want to blogInput.toLowerCase() and store.toLowerCase() to resolve casing issues.
If you are targeting new browsers with ES6 support or using a transpiler like Babel you can simplify to:
const blogInput = "We went to target last night, also to publix";
const storeNames = ["kroger", "lums", "marlows", "eats", "burger king",
"home", "wendys", "publix", "donut circus", "jewelry store",
"target"];
// filter out any store names that aren't included in blogInput
var matches = storeNames.filter(store => blogInput.includes(store));

Related

Add newline return or linebreak to string (Typescript & React)

I have a variable called resultElem which is created after reading an array. As it reads the array, I want to add a newline '\n' or at the end of it, following a space.
for (let l=0; l<answer.questions.length; l++) {
var questionMap = [answer.questions[l].questionId, answer.questions[l].answers];
let resultElem :any;
for (let m =0; m< employmentAnswerMap[l].answerKVs.length; m++){
if (questionMap[1].includes(employmentAnswerMap[l].answerKVs[m].id)) {
resultElem = resultElem + employmentAnswerMap[l].answerKVs[m].text + ' ' + '\n';// I have tried + '\n' and + 'br'
}
}
result.push(resultElem);
}
I have also tried .appendChild() but I didn't like it since I just want to use resultElem to make the data.
return (
<dl>
<dt>Health Needs</dt>
<dd>{result[0]}</dd>
</dl>
...more ui code
It gets displayed, but as one big text paragraph without newlines, like so
healthNeed1 healthNeed2 healthNeeds3
healthNeed1
healthNeed2
healthNeeds3
I am able to print the strings in seperate line by using '\n'. I am adding demo below. Can you please update that as per the issue you are facing so that we can look into that.
Demo :
const answer = {
questions: [{
questionId: 1,
answers: ['answer1', 'answer2', 'answer3']
}]
};
const employmentAnswerMap = [
{
answerKVs: [{
id: 1,
text: 'healthNeed1'
}, {
id: 2,
text: 'healthNeed2'
}, {
id: 3,
text: 'healthNeed3'
}]
}
];
const result = [];
for (let l=0; l<answer.questions.length; l++) {
var questionMap = [answer.questions[l].questionId, answer.questions[l].answers];
let resultElem = '';
for (let m =0; m < employmentAnswerMap[l].answerKVs.length; m++){
if (questionMap[1].find((elem) => elem.includes(employmentAnswerMap[l].answerKVs[m].id)).length > 0) {
resultElem = resultElem + employmentAnswerMap[l].answerKVs[m].text + ' ' + '\n';
}
}
result.push(resultElem);
}
console.log(result[0]);
HTML doesn't really like to add line breaks, even if you include them. If you have to include them, template literals are the easiest way. To use those, use backticks(`) instead of single(') or double quotes("). Template Literals take literally what you type as the string, and can only be escaped at the end of the string, or by encapsulating your escaped javascript with ${}. It looks like this:
let bob=true;
`Look at me,
I'm using backticks! ${bob ? "No literals here!" : "But you
can use the 'ternary operator'!"}`
Unfortunately, it doesn't look like the code sample likes backticks. But I assure you, they work! Of course, even with the return inside the template literals, HTML is going to mostly ignore them unless you use the new CSS white-space property. You could also add append a new <p> tag for each one, which can be styled however you like, including to make them have a new line.
I had to wrap my HTML around this div in order for the newlines ("/n") to display properly
<div style={{whiteSpace: "pre-wrap"}}>

Finding multiple whole words with .split and .includes with Javascript

I'm using javascript and html to develop a simple chatbot. The code below works and uses .split to check for a whole word, however, this means that anything entered longer than one word such as "how are you?" no longer works. How can change this so that it'll allow multiple words but still check for whole words like "hi" so that they aren't picked up in bigger words such as "high" etc.
Any help would be much appreciated! Thanks!
var know = {
<!--General Phrases-->
"Hi": "Hello! &#128075",
"Hey": "Hello! &#128075",
"Hello":"Hello &#128075 How can I help?",
"how are you":"Not bad, thanks!",
"Bye":"Have a nice day!",
"Goodbye":"See you later!",
<!--Directory-->
"Help": `You can find help by searching below or by clicking here`,
"contact": `You can contact us by clicking here`,
"About": `You can find our About Us page by clicking here`
};
function goo() {
var userBox = document.getElementById('userBox');
var userInput = userBox.value;
var chatLog = document.getElementById('chatLog');
var chatLogContent = "";
if (!userInput) {
chatLogContent = ''
}
var hasKeyword = false;
for (var key in know) {
if (userInput.toLowerCase()
.replace(/[.,\/#!$%\^&\*;:{}=\-_`~ ()]/g,"")
.split(/\s+/)
.includes(key.toLowerCase())) {
hasKeyword = true;
break;
} else {
hasKeyword = false;
}
}
if (hasKeyword) {
chatLogContent += know[key] + "<br>"; //or use know.key
} else {
chatLogContent += "No results found. Please enter another search term below.<br>";
}
var server = document.createElement('div');
server.setAttribute('class', 'server');
server.innerHTML = chatLogContent;
document.getElementById('chatLog').innerHTML = '';
chatLog.appendChild(server);
}
You can use the regex:
var yourText = "how are you";
var youKeyRegexEscaped = "how are you"
yourText.match(new RegExp('(^|\\s)'+youKeyRegexEscaped+'(\\s|$)'))
Rexgex explanation:
(^|\s) -> begining of the string or space
(\s|$) -> space or end of string
To escape the key, just look at Is there a RegExp.escape function in JavaScript?

Avoid errors when comparing title field to an array

The following code gets the first word from my page title, compares it to the values of an array and outputs me a final value :
var fulltitle = "Deep Blue Shoes";
var arr = fulltitle.split(' ').slice(0, 1);
var title = arr && arr.length ? arr[0] : "";
//test variables
testarray = ["blue", "top", "110", "in stock", "deep blue", "down", "111", "in stock"]
//function
function testfunction(array, variable) {
var varindex = array.indexOf(variable.toLowerCase())
return array[varindex + 2]
}
//calling the function
var finalvalue = testfunction(testarray, title);
console.log( finalvalue )
In this case, if my title is Deep Blue shoes, the system cuts the title too early and try to compare the value 'deep' with the values into the array. But the value deep doesn't exist.
I'm trying to find a solution for this and similiar problems that may occur, since my variables can be like 'blue', 'deep blue', 'deep blue sky'. We work with exact matches only.
How would you fix this ?
See also https://jsfiddle.net/Francesco82/hg10a3wy/
You could use a String (or RegEx) comparison?
But in the first example this matches with both "blue" as well as "deep blue" (and in the second "acquacalda" as well as "acqua").
What logic should we use in those cases? If it matches with more than one, choose the one with the most words if one has more words than the others (i.e., in that case, choose "deep blue") and if the matches are the same number of words choose the longer of the two words? i.e. In the second case choose "acquacalda" over "acqua"? (in general, always choosing the more specific answer)
See below and https://jsfiddle.net/alexander_L/gx83mrtL/3/
findPage("Deep Blue Shoes");
findPage("Acquacalda");
function findPage(fulltitle){
//test variables
const testarray = ["blue", "top", "110", "in stock", "deep blue", "down", "111", "in stock", "acqua", "top", "112", "in stock", "acquacalda", "down", "113", "in stock"]
const altMatches = testarray.reduce((aggArr, item) => {
if (fulltitle.toLowerCase().includes(item)){
console.log('we have a match with ' + item);
aggArr.push(item);
}
return aggArr;
}, []);
//then we can choose the "largest" match:
const finalMatch = altMatches.reduce((aggMatch, item) => {
if (aggMatch == null || (aggMatch.split(' ').length < item.split(' ').length) || (aggMatch.length < item.length)){
return item;
}
return aggMatch;
}, null);
console.log('the final match is ' + finalMatch);
const finalPage = testarray.indexOf(finalMatch) + 2;
console.log('the final match page number is ' + testarray[finalPage]);
}
OUTPUT:
"we have a match with blue"
"we have a match with deep blue"
"the final match is deep blue"
"the final match page number is 111"
"we have a match with acqua"
"we have a match with acquacalda"
"the final match is acquacalda"
"the final match page number is 113"
I was able to fix the problem with a trick just by modifing this code at the very beginning
var fulltitle = (document.title);
var arr = fulltitle.split(':').slice(0, 1);
Since the part of title we need is always before the ':' , in this way I'm able to get the right result without making advanced comparisons.

splitting an string list with proper aligning the string elements

Example Company 1,company ltd 2,company, Inc.,company Nine nine, ltd,company ew So here is example of the string, I want to split it like that it consider Company 1 as one company and company, Inc. as one, but here got situation in company, Inc. it condidering 2 companies while this logic. how can I resolve this? Lke with such strings company, Inc. I want to consider it one element only
const company = company.split(",");
Here the string can be anything, this is just example for the string, but it can be any name. So I am looking for generic logic which works for any string, having same structure of string.
Note $ ==(,) represents as separation point, kept to get clarity that from that point I need to separate the string
Object:
Example 1
{
_id: 5de4debcccea611e4d14d4d5
companies: One Bros. Inc. & Might Bros. Dist. Corp.$Pages, Inc.$Google Inc. Search$Aphabet Inc. tech.
}
Example 2
{
_id: 5de4debccc333611e4d14d4f5
companies: Google Comp. Inc.$Google Comp. Inc. Estd.$Tree, Ltd.$Tree, Ltd.
}
First I split on 'ompany' rather than 'company', because you have one instance of 'Company' with a capital C -- see the output of the first console log within a comment below.
Then I put things back together using reduce -- map is not the right choice here, as I need an array that is one fewer than the size of the fragments I generated. Then though since I need an array that corresponds to the number of strings we want to return, which is one fewer than the number of fragments, the first thing I do inside my reduce is ensure I do not look beyond the end of the array.
Then I split each fragment and pop off the last element, which just puts either "C" or "c" back together with "ompany". Then I replace any trailing ',c' from the next fragment with an empty string, and add the result to the company. Finally I add the entire result to the array I'm generating with reduce. See comment results at bottom. Also here it is on repl.it: https://repl.it/#dexygen/splitOnCompanyStringLiteral
This is a fairly concise way to do this but again if you can do anything to improve your data, you won't have to use such unnecessarily complicated code.
const companiesStr = "Company 1,company ltd 2,company, Inc.,company Nine nine, ltd,company ew";
const companySuffixFragments = companiesStr.split("ompany");
console.log(companySuffixFragments);
/*
[ 'C', ' 1,c', ' ltd 2,c', ', Inc.,c', ' Nine nine, ltd,c', ' ew' ]
*/
const companiesArr = companySuffixFragments.reduce((companies, fragment, index, origArr) => {
if (index < companySuffixFragments.length - 1) {
let company = fragment.split(',').pop() + 'ompany'
company = company + origArr[index + 1].replace(/,c$/, '');
companies.push(company);
}
return companies
}, []);
console.log(companiesArr);
/*
[ 'Company 1',
'company ltd 2',
'company, Inc.',
'company Nine nine, ltd',
'company ew' ]
*/
First change , with any other symbol. I am using & here and then split string with ,
var str= 'Company 1,company ltd 2,company, Inc.,company Nine nine, ltd,company ew';
str = str.replace(', Inc.','& Inc.');
/*str = str.replace(', ltd','& ltd');*/
console.log(str.split(',').map((e)=>{return e.replace('&',',').trim()}));
try with the below solution.
var str = ["company 1","company ltd 2","company", "Inc.","company Nine nine", "ltd","company ews"];
var str2 =str.toString()
var str3 = str2.split("company")
function myFunction(item, index,arr){if(item !=""){let var2 = item.replace(/,/g," ");var2 = "Company"+var2;arr[index]=var2;} }
str3.forEach(myFunction)
OUtput:
str3
(6) ["", "Company 1 ", "Company ltd 2 ", "Company Inc. ", "Company Nine nine ltd ", "Company ews"]
And remove the first element of the array.
As has been commented I'd try to get a more clean String so that you don't have to write "strange" code to get what you need.
If you can't do that right now this code should solve your problem:
let string = 'Company 1,company ltd 2,company, Inc.,company Nine nine, ltd,company
ew';
let array = string.split(',');
const filterFnc = (array) => {
let newArr = [],
i = 0;
for(i = 0; i < array.length; i++) {
if(array[i].toLowerCase().indexOf('company') !== -1) {
newArr.push(array[i]);
} else {
newArr.splice(newArr.length - 1, 1, `${array[i - 1]}, ${array[i]}`);
}
}
return newArr;
};
let filteredArray = filterFnc(array);

Use regex to interpret different type of variable and do a find and replace with jquery

ok i have difficulty to understand the rejex and how it work, I try to do a basic dictionnary/glossary for a web site and i past too many time on it already.
There my code :
// MY MULTIPLE ARRAY
var myDictionnary = new Object();
myDictionnary.myDefinition = new Array();
myDictionnary.myDefinition.push({'term':"*1", 'definition':"My description here"});
myDictionnary.myDefinition.push({'term':"word", 'definition':"My description here"});
myDictionnary.myDefinition.push({'term':"my dog doesn't move", 'definition':"My description here"});
// MY FUNCTION
$.each(myDictionnary.myDefinition, function(){
var myContent = $('#content_right').html();
var myTerm = this.term;
var myRegTerm = new RegExp(myTerm,'g');
$('#content_right').html(myContent.replace(myRegTerm,'<span class="tooltip" title="'+this.definition+'"> *'+this.term+'</span>'));
});
I create my array and for each result i search in my div#content_right for the same content and replace it by span with title and tooltip. I put my regex empty to not confuse with what i have try before.
In my array 'term' you can see what kind of text i will research. It work for searching normal text like 'word'.
But for the regular expression like 'asterix' it bug, when i find a way to past it, he add it to text, i try many way to interpret my 'asterix' like a normal caracter but it doesn't work.
There what i want :
Interpret the variable literally whatever the text inside my var 'myTerm'. It this possible? if it not, what kind solution i shouldn use.
Thank in advance,
P.S. sorry for my poor english...(im french)
Alex
* is a special character in a Regex, meanining "0 or more of the previous character". Since it's the first character, it is invalid and you get an error.
Consider implementing preg_quote from PHPJS to escape such special characters and make them valid for use in a regex.
HOWEVER, the way you are doing this is extremely bad. Not only because you're using innerHTML multiple times, but because what if you come across something like this?
Blah blah blah <img src="blah/word/blah.png" />
Your resulting output would be:
Blah blah blah <img src="blah/<span class="tooltip" title="My description here"> *word</span>/blah.png" />
The only real way to do what you're attempting is to specifically scan through the text nodes, then use .splitText() on the text node to extract the word match, then put that into its own <span> tag.
Here's an example implementation of the above explanation:
function definitions(rootnode) {
if( !rootnode) rootnode = document.body;
var dictionary = [
{"term":"*1", "definition":"My description here"},
{"term":"word", "definition":"My description here"},
{"term":"my dog doesn't move", "definition":"My description here"}
], dl = dictionary.length, recurse = function(node) {
var c = node.childNodes, cl = c.length, i, j, s, io;
for( i=0; i<cl; i++) {
if( c[i].nodeType == 1) { // ELEMENT NODE
if( c[i].className.match(/\btooltip\b/)) continue;
// ^ Exclude elements that are already tooltips
recurse(c[i]);
continue;
}
if( c[i].nodeType == 3) { // TEXT NODE
for( j=0; j<dl; j++) {
if( (io = c[i].nodeValue.indexOf(dictionary[j].term)) > -1) {
c[i].splitText(io+dictionary[j].term.length);
c[i].splitText(io);
cl += 2; // we created two new nodes
i++; // go to next sibling, the matched word
s = document.createElement('span');
s.className = "tooltip";
s.title = dictionary[j].definition;
node.insertBefore(s,c[i]);
i++;
s.appendChild(c[i]); // place the text node inside the span
// Note that now when i++ it will point to the tail,
// so multiple matches in the same node are possible.
}
}
}
}
};
recurse(rootnode);
}
Now you can call definitions() to replace all matches in the document, or something like definitions(document.getElementById("myelement")) to only replace matches in a certain container.
By adding another variable, it will fix the problem:
for( j=0; j<dl; j++) {
debutI =i;
if((io =c[i].nodeValue.indexOf(dictionary[j].term)) != -1) {
c[i].splitText(io+dictionary[j].term.length);
c[i].splitText(io);
cl += 2; // we created two new nodes
i++; // go to next sibling, the matched word
s = document.createElement('span');
s.className = "tooltip";
s.title = dictionary[j].definition;
node.insertBefore(s,c[i]);
i++;
s.appendChild(c[i]); // place the text node inside the span
// Note that now when i++ it will point to the tail,
// so multiple matches in the same node are possible.
}
i = debutI;
}
Comme ça, on n'aura pas à mettre tous les spans partout dans le site à la main. >_>

Categories