Highlight word based on the word count in javascript - javascript

If I have the following string: This is another song you should listen to
I was trying to write a function to only highlight the word based on the word count. For example, I want to highlight the 4th word using a method like: highlightWord(originalString,nthWord). And the returned string would be:
This is another <span class=\"highlighted\">song</span> you should listen to
I tried using a highlightMatch function that I wrote, one by passing a search string (regexMatch = "is"), but the problem is that it highlights the is in This instead of is:
function highlightMatch(originalValue,regexMatch) {
var tempInnerHTML = stripTags(originalValue);
originalValue.innerHTML = tempInnerHTML.replace(regexMatch,'<span class="highlighted">'+regexMatch+'</span>');
}
I also tried using this highlightNthWord method:
function highlightNthWord(string,n) {
var m = string.match(new RegExp('^(?:\\w+\\W+){' + n + '}(\\w+)'));
return m && '<b>'+m[1]+'</b>';
}
But this one just returns only the nth word highlighted, not the whole sentence with the nth word highlighted.
I'm hoping to stick with javascript only (no jquery).

You can split the string be space and then build the string back:
function highlightNthWord(string,n) {
var m = string.split(new RegExp(/\s+/));
return m.splice(0, n-1).join(" ") + (' <b>'+m.splice(0, 1)+'</b> ') + m.join(" ");
}
console.log(highlightNthWord('This is another song you should listen to', 4))

Try this
function highlightMatch(originalValue, matchString) {
var re = new RegExp(matchString, "igm");
var replace = '<span class="highlighted">'+matchString+'</span>';
return originalValue.replace(re ,replace);
}

Related

Vue: Make matching part of input bold, including special hyphens

I have made a simple select component in Vue with a search/filter system. Based on the user input I'm showing some Belgium city suggestions.
Working example: https://codesandbox.io/s/wandering-lake-lecok?file=/src/components/Select.vue (Sometimes there is an error message in Codesandbox. Refresh the build in browser and it should work)
I want to take the UX one step further and show the matching part of the user input bold and underlined. Therefore I have a working makeBold function. By splitting the suggestion string into multiple parts I can add a bold and underline tag and return the suggestion.
computed: {
results() {
return this.options.filter((option) =>
option.display_name
.replaceAll("-'", "")
.toLowerCase()
.includes(this.searchInput.replaceAll("-'", "").toLowerCase())
);
},
},
methods: {
makeBold(str, query) {
const n = str.toUpperCase();
const q = query.toUpperCase();
const x = n.indexOf(q);
if (!q || x === -1) {
return str;
}
const l = q.length;
return (
str.substr(0, x) + "<b><u>" + str.substr(x, l) + "</u></b>" + str.substr(x + l)
);
},
}
One problem, a lot of cities in Belgium use dashes and/or apostrophes. In the suggestions function I'm removing this characters so a user doesn't need to type them. But in the makeBold function I would like to make this characters bold and underlined.
For example:
When the input is 'sint j', 'sintj' or 'Sint-j' I want the suggestions to look like 'Sint-Jans-Molenbeek' and 'Sint-Job in't Goor'
Is there someone who can give me a breakdown on how to achieve this?
I would propose using a mask, to save the city name structure, and after you find the start and end index of substring in city name, restore the original string from mask, inserting the appropriate tags at the start and end index using a replacer function. this way you would not worry about any other non-word characters or other unexpected user input.
Here is the makeBold function:
makeBold(str, query) {
// mask all word characters in city name
const city_mask = str.replace(/\w/g, "#");
// strip city and query string from any non-word character
let query_stripped = query.toLowerCase().replace(/\W/g, "");
let string_stripped = str.replace(/\W/g, "");
// find the index of querystring in city name
let index = string_stripped.toLowerCase().indexOf(query_stripped);
if (index > -1 && query_stripped.length) {
// find the end position of substring in stripped city name
let end_index = index + query_stripped.length - 1;
// replacer function for each masked character.
// it will add to the start and end character of substring the corresponding tags,
// replacing all masked characters with the original one.
function replacer(i) {
let repl = string_stripped[i];
if (i === index) {
repl = "<b><u>" + repl;
}
if (i === end_index) {
repl = repl + "</u></b>";
}
return repl;
}
let i = -1;
// restore masked string
return city_mask.replace(/#/g, (_) => {
i++;
return replacer(i);
});
}
return str;
}
And here is the working sandbox. I've changed a bit your computed results to strip all non-word characters.
One way is to transform your search string into a RegExp object and use replace(regexp, replacerFunction) overload of string to achieve this.
For example the search string is "sintg"
new RegExp(this.searchInput.split("").join("-?"), "i");
Turns it into /s-?i-?n-?t-?g/gi
-? indicates optional - character and
"i" at the end is the RegExp case insensitive flag
Applied to codesandbox code you get this
computed: {
results() {
const regex = new RegExp(this.searchInput.split("").join("-?"), "i");
return this.options.filter((option) => option.display_name.match(regex));
},
},
methods: {
makeBold(str, query) {
const regex = new RegExp(query.split("").join("-?"), "i");
return str.replace(regex, (match) => "<b><u>" + match + "</u></b>");
},
},
Which gives this result
However there is a caveat: There will be errors thrown if the user puts a RegExp special symbol in the search box
To avoid this the initial search input text needs to get RegExp escape applied.
Such as:
new RegExp(escapeRegExp(this.searchInput).split("").join("-?"), "i");
But there is no native escapeRegExp method.
You can find one in Escape string for use in Javascript regex
There is also an escapeRegExp function in lodash library if it's already in your list of dependencies (saves you from adding another function)
You could create a function that removes all spaces and - in the query and city string. If the city includes the query, split the query string on the last letter and get the occurences of that letter in the query. Calculate the length to slice and return the matching part of the original city string.
const findMatch = (q, c) => {
const query = q.toLowerCase().replace(/[\s-]/g, "");
const city = c.toLowerCase().replace(/[\s-]/g, "");
if (city.includes(query)) {
const last = query.charAt(query.length - 1); // last letter
const occ = query.split(last).length - 1; // get occurences
// calculate slice length
const len = c.toLowerCase().split(last, occ).join(" ").length + 1;
return c.slice(0, len);
}
return "No matching city found."
}
const city = "Sint-Jan Test";
console.log(findMatch("sint j", city));
console.log(findMatch("sintj", city));
console.log(findMatch("Sint Jan t", city));
console.log(findMatch("sint-j", city));
console.log(findMatch("Sint-J", city));
console.log(findMatch("SintJan te", city));

Assign ID to html text

How do I go about assigning certain words a unique id tag using vanilla javascript? For example:
<p>All the king's horses ran away.</p>
The word "All" gets the id "term1", "King's" gets "term2", "away" gets "term3", etc. but not every word in the sentence will get assigned an id.
I am currently using the replace method but I think it's the wrong approach:
var str = document.getElementsByTagName('p')[0].innerHTML;
function addId() {
txt = str.replace(/all/i, '<span id="term1">$&</span>').replace(/king's/i, '<span id="term2">$&</span>').replace(/away/i, '<span id="term3">$&</span>');
document.getElementsByTagName('p')[0].innerHTML = txt;
}
window.onload = function() {
addId();
};
<p>All the king's horses ran away.</p>
This forces me to chain a bunch of replace commands, changing the id name each time. I don't think this is the best solution. What is best way to do this? Thanks for your help!
I believe this should make the job done. You can replace blacklist with whitelist. That will depend on your use case. Also, addIds can use Array.map instead of Array.forEach that will make the whole function a one-liner. This example is imperative because it will be more readable.
// string - string where we want to add ids
// blackList - words we want to skip (can be whiteliste black list is more general)
function addIds(string, blackList = ['the', 'a']) {
const stringArray = string.split(' ') // split string into separate words
let stringWithIds = string // this will be final string
stringArray.forEach((item, index) => {
// skip word if black listed
if (blackList.indexOf(item.toLowerCase()) !== -1) {
return
}
stringWithIds = stringWithIds.replace(item, `<span id="term${index}">${item}</span>`) // add id to word if not black listed
})
// replace string with our string with ids
document.getElementsByTagName('p')[0].innerHTML = stringWithIds;
}
window.onload = function() {
const str = document.getElementsByTagName('p')[0].innerHTML;
addIds(str); // you can pass custom blacklist as optional second parameter
};
<p>All the king's horses ran away.</p>
I think this function would be flexible enough for the task:
// Here, your logic to decide if the word must be tagged
const shouldBeTagged = word => {
return true
}
function addId(str) {
// Split the string into individual words to deal with each of them
// one by one
let txt = str.split(' ').map((word, i) => {
// Should this word be tagged
if (shouldBeTagged(word)) {
// If so, tag it
return '<span id="term' + i + '">' + word + '</span>'
} else {
// Otherwise, return the naked word
return word
}
// Join the words together again
}).join(' ')
document.getElementsByTagName('p')[0].innerHTML = txt;
}
window.onload = function() {
addId(document.getElementsByTagName('p')[0].innerHTML);
};
<p>All the king's horses ran away.</p>

Split only first part after /

I am using split function like this :
function getFirstPart(str) {
return str.split('/')[0];
}
function getSecondPart(str) {
return str.split('/')[1];
}
For first part it is working as expected, but for second part i want everything behind first /.
For example in /stub/787878/hello, I want stub as first part and /787878/hello as second part.
How to make pattern for such condition.
Instead of trying to use split, find the slash, and take the string to the left and right of it:
function divideAtSlash(str) {
const index = str.indexOf('/', 1);
return [str.slice(0, index), str.slice(index)];
}
The second argument (1) to indexOf tells it to start matching at the second character, because in this case we want to skip over the leading slash.
The first element of the returned tuple will be /stub, not stub. If you want the latter, then
return [str.slice(1, index), str.slice(index)];
This is what you are looking for:
const str = '/stub/787878/hello/911';
const [, firstPart, ...rest] = str.split('/');
const secondPart = '/' + rest.join('/')
console.log('first part: ', firstPart);
console.log('second part: ', secondPart);
i guess getSecondPart() is returning 787878 in your example.
you need to check how many parts you have in your arrayy after splitting the string and then return every part except the first.
function getSecondPart(str) {
var astr = str.split('/');
str = "";
for(var i = 0; i < astr.length; i++) {
str += "/" + astr[i];
}
return(str);
}
CODE IS NOT TESTED, I just want to give you an idea.

Capturing String Segments between Special Characters using Regular Expressions

I have the following string of text:
textString1:textString2:textString3:textString4
I'm looking to capture each text string and assign them to variables.
I've somehow managed to come up with the following:
var errorText = 'AAAA:BBBB:CCCC:DDDD';
var subString, intro, host, priority, queue = '';
var re = /(.+?\:)/g;
subString = errorText.match(re);
intro = subString[0];
host = subString[1];
priority = subString[2];
//queue = subString[3];
console.log(intro + " " + host + " " + priority);
JS Bin Link
However, I'm having problems with:
capturing the last group, since there is no : at the end
the variables contain : which I'd like to strip
You don't need a regex for this - just use errorText.split(':') to split by a colon. It will return an array.
And if you then want to add them together with spaces, you could do a simple replace instead: errorText.replace(/:/g,' ').
use split method for this.it will return array of string then iterate through array to get string:
var errorText = 'AAAA:BBBB:CCCC:DDDD';
var strArr=errorText.split(':');
console.log(errorText.split(':'));
for(key in strArr){
console.log(strArr[key]);
}

Javascript replace, ignore the first match

I have the following script in javascript
var idText;
var idText1;
idText = "This_is_a_test";
idText1 = idText.replace(/_/g, " ");
alert(idText1);
When I show idText1, it replaces all of the underscores and puts in a space where they were. However, I am trying to keep the very first underscore, so I get "This_is a test". Is this possible at all?
It is certainly possible, here is one option:
var n = 0;
idText1 = idText.replace(/_/g, function($0) {
n += 1;
return n === 1 ? $0 : " ";
});
This uses a callback for the replacement that increments a counter for each match, and replaces the first match with the original text by checking the value of that counter.

Categories