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

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));

Related

Swapping first letters in name and altering capitalization using JavaScript? (looking to optimize solution)

I was taking on a JS challenge to take a first/last name string input and do the following:
swap the first letter of first/last name
convert all characters to lowercase, except for the first characters, which need to be uppercase
Example:
input: DonAlD tRuMp
output: Tonald Drump
The following is the code I came up with:
const input = prompt("Enter a name:")
function switchFirstLetters(input) {
let stringArray = input.split('');
for(let i=0; i < stringArray.length; i++) {
if(stringArray[i - 1] === ' ') {
[stringArray[0], stringArray[i]] = [stringArray[i], stringArray[0]]; // destructuring
}
}
return result = stringArray.join('');
}
let swappedString = switchFirstLetters(input);
function capFirstLetters(swappedString) {
let stringArray = swappedString.toLowerCase();
stringArray = stringArray.split('');
stringArray[0] = stringArray[0].toUpperCase();
for(let i=0; i < stringArray.length; i++) {
if(stringArray[i - 1] === ' ') {
stringArray[i] = stringArray[i].toUpperCase();
}
}
return result = stringArray.join('');
}
let finalString = capFirstLetters(swappedString);
console.log(finalString);
My thought process for the switchFirstLetters function was:
Create an array from the string parameter
Run through the array length. If the value of the element prior the current element is equal to ' ', use destructuring to swap the current element with the element at index 0
Concatenate elements into a new string and return that value
My thought process for the capFirstLetters function:
Convert all characters in the string to lowercase (this could be handled outside of the function as well)
Create an array from the new, lowercase string
Make character at index 0 be uppercase (this could also be integrated into the for loop)
Run through the array length. If the value of the element prior to the current element is equal to ' ', convert that element to uppercase.
Concatenate array elements into a new string
The code works, but I'm still early in my coding journey and realize it's likely not an ideal solution, so I was wondering if anyone here could help me optimize this further to help me learn. Thanks!
You could also use a regular expression to replace the first letters:
let name = "DonAlD tRuMp";
let result = name.toLowerCase().replace(/(\S)(\S*\s+)(\S)/g, (_, a, b, c) =>
c.toUpperCase() + b + a.toUpperCase()
);
console.log(result);
The regular expression uses \S (a non-white-space character), \S* (zero or more of those), \s+ (one or more white-space characters) and parentheses to create capture groups. These three groups map to a,b,c parameters in the callback function that is passed to replace as second argument. With these parts the replacement string can be constructed. Both the capitalisation and the switch happen in the construction.
If the replace function is a little overwhelming, my attempt introduces the for-of loop, the substring string method, array slice as well as the && short circuit evaluation. You should also be aware you can access a given character of a string using the square bracket syntax, just like array, but string has it's own set of methods which tend to have different names.
Definitely take a look at the replace function, to make your v2.
const rawNameInput = "DonAlD jUnior tRuMp"
const nameInput = rawNameInput.trim()
const rawNameWords = nameInput.split(" ")
const nameWords = []
for (const word of rawNameWords) {
const first = word[0].toUpperCase()
const rest = word.substring(1).toLowerCase()
nameWords.push(first + rest)
}
const middleNames = nameWords.slice(1, -1).join(" ")
const lastIdx = nameWords.length - 1
const newFirstName = nameWords[lastIdx][0] + nameWords[0].substring(1)
const newLastName = nameWords[0][0] + nameWords[lastIdx].substring(1)
console.log(`${newFirstName} ${middleNames && middleNames + " "}${newLastName}`)

Remove a substring to make given word in javascript

I want to know about the algorithm for below question in JavaScript.
Check whether the given word can be "programming" or not by removing the substring between them. You can only remove one substring from the given the word.
Give answer in 'yes' and 'no'
example answer explanation
"progabcramming" yes remove substring 'abc'
"programmmeding" yes remove substring 'med'
"proasdgrammiedg" no u have to remove 2 subtring 'asd' and 'ied'
which is not allowed
"pxrogramming" yes remove substring 'x'
"pxrogramminyg" no u have to remove 2 subtring 'x' and 'y'
which is not allowed
Please tell me an algorithm to solve it
{
// will create a regexp for fuzzy search
const factory = (str) => new RegExp(str.split('').join('(.*?)'), 'i')
const re = factory('test') // re = /t(.*?)e(.*?)s(.*?)t/i
const matches = re.exec('te-abc-st') ?? [] // an array of captured groups
const result = matches
.slice(1) // first element is a full match, we don't need it
.filter(group => group.length) // we're also not interested in empty matches
// now result contains a list of captured groups
// in this particular example a single '-abc-'
}
I'm not sure how efficient this code is, but only thing i can come up with is using regular expression.
const word = 'programming';
const test = ['progabcramming', 'programmmeding', 'proasdgrammiedg', 'pxrogramming', 'pxrogramminyg', 'programming'];
// create regular expression manually
// const regexp = /^(p).+(rogramming)|(pr).+(ogramming)|(pro).+(gramming)|(prog).+(ramming)|(progr).+(amming)|(progra).+(mming)|(program).+(ming)|(programm).+(ing)|(programmi).+(ng)|(programmin).+(g)$/;
// create regular expression programmatically
let text = '/^';
word.split('').forEach((character, i) => {
text += i ? `(${word.substring(0, i)}).+(${word.substring(i)})|` : '';
});
text = text.substring(text.length - 1, 1) + '$/';
const regexp = new RegExp(text);
// content output
let content = '';
test.forEach(element => {
content += `${element}: ${regexp.test(element)}\n`;
});
document.body.innerText = content;

Match all instances of character except the first one, without lookbehind

I’m struggling with this simple regex that is not working correctly in Safari:
(?<=\?.*)\?
It should match each ?, except of the first one.
I know that lookbehind is not working on Safari yet, but I need to find some workaround for it. Any suggestions?
You can use an alternation capture until the first occurrence of the question mark. Use that group again in the replacement to leave it unmodified.
In the second part of the alternation, match a questionmark to be replaced.
const regex = /^([^?]*\?)|\?/g;
const s = "test ? test ? test ?? test /";
console.log(s.replace(regex, (m, g1) => g1 ? g1 : "[REPLACE]"));
There are always alternatives to lookbehinds.
In this case, all you need to do is replace all instances of a character (sequence), except the first.
The .replace method accepts a function as the second argument.
That function receives the full match, each capture group match (if any), the offset of the match, and a few other things as parameters.
.indexOf can report the first offset of a match.
Alternatively, .search can also report the first offset of a match, but works with regexes.
The two offsets can be compared inside the function:
const yourString = "Hello? World? What? Who?",
yourReplacement = "!",
pattern = /\?/g,
patternString = "?",
firstMatchOffsetIndexOf = yourString.indexOf(patternString),
firstMatchOffsetSearch = yourString.search(pattern);
console.log(yourString.replace(pattern, (match, offset) => {
if(offset !== firstMatchOffsetIndexOf){
return yourReplacement;
}
return match;
}));
console.log(yourString.replace(pattern, (match, offset) => {
if(offset !== firstMatchOffsetSearch){
return yourReplacement;
}
return match;
}));
This works for character sequences, too:
const yourString = "Hello. Hello. Hello. Hello.",
yourReplacement = "Hi",
pattern = /Hello/g,
firstOffset = yourString.search(pattern);
console.log(yourString.replace(pattern, (match, offset) => {
if(offset !== firstOffset){
return yourReplacement;
}
return match;
}));
Split and join with
var s = "one ? two ? three ? four"
var l = s.split("?") // Split with ?
var first = l.shift() // Get first item and remove from l
console.log(first + "?" + l.join("<REPLACED>")) // Build the results

RegExp replace all letter but not first and last

I have to replace all letters of name on ****.
Example:
Jeniffer -> J****r
I try $(this).text( $(this).text().replace(/([^\w])\//g, "*"))
Also, if name is Ron -> R****n
You can use a regular expression for this, by capturing the first and last letters in a capture group and ignoring all letters between them, then using the capture groups in the replacement:
var updated = name.replace(/^(.).*(.)$/, "$1****$2");
Live Example:
function obscure(name) {
return name.replace(/^(.).*(.)$/, "$1****$2");
}
function test(name) {
console.log(name, "=>", obscure(name));
}
test("Ron");
test("Jeniffer");
But it's perhaps easier without:
var updated = name[0] + "****" + name[name.length - 1];
Live Example:
function obscure(name) {
return name[0] + "****" + name[name.length - 1];;
}
function test(name) {
console.log(name, "=>", obscure(name));
}
test("Ron");
test("Jeniffer");
Both of those do assume the names will be at least two characters long. I pity the fool who tries this on Mr. T's surname.
Since, you need to have four asterisk on each condition, you can create a reusable function that will create this format for you:
function replace(str){
var firstChar = str.charAt(0);
var lastChar = str.charAt(str.length-1);
return firstChar + '****' + lastChar;
}
var str = 'Jeniffer';
console.log(replace(str));
str = 'America';
console.log(replace(str))
Appears that you're looking for regex lookaround
Regex: (?<=\w)(\w+)(?=\w) - group 1 matches all characters which follow one character and followed by another one.
Tests: https://regex101.com/r/PPeEqx/2/
More Info: https://www.regular-expressions.info/lookaround.html
Find first and last chars and append **** to the first one and add the last one:
const firstName = 'Jeniffer';
const result = firstName.match(/^.|.$/gi).reduce((s, c, i) => `${s}${!i ? `${c}****` : c }`, '');
console.log(result);

How to capitalize the last letter of each word in a string

I am still rather new to JavaScript and I am having an issue of getting the first character of the string inside the array to become uppercase.
I have gotten to a point where I have gotten all the texted lowercase, reversed the text character by character, and made it into a string. I need to get the first letter in the string to uppercase now.
function yay () {
var input = "Party like its 2015";
return input.toLowerCase().split("").reverse().join("").split(" ");
for(var i = 1 ; i < input.length ; i++){
input[i] = input[i].charAt(0).toUpperCase() + input[i].substr(1);
}
}
console.log(yay());
I need the output to be "partY likE itS 2015"
Frustrating that you posted your initial question without disclosing the desired result. Lots of turmoil because of that. Now, that the desired result is finally clear - here's an answer.
You can lowercase the whole thing, then split into words, rebuild each word in the array by uppercasing the last character in the word, then rejoin the array:
function endCaseWords(input) {
return input.toLowerCase().split(" ").map(function(item) {
return item.slice(0, -1) + item.slice(-1).toUpperCase();
}).join(" ");
}
document.write(endCaseWords("Party like its 2015"));
Here's a step by step explanation:
Lowercase the whole string
Use .split(" ") to split into an array of words
Use .map() to iterate the array
For each word, create a new word that is the first part of the word added to an uppercased version of the last character in the word
.join(" ") back together into a single string
Return the result
You could also use a regex replace with a custom callback:
function endCaseWords(input) {
return input.toLowerCase().replace(/.\b/g, function(match) {
return match.toUpperCase();
});
}
document.write(endCaseWords("Party like its 2015"));
FYI, there are lots of things wrong with your original code. The biggest mistake is that as soon as you return in a function, no other code in that function is executed so your for loop was never executed.
Then, there's really no reason to need to reverse() the characters because once you split into words, you can just access the last character in each word.
Instead of returning the result splitting and reversing the string, you need to assign it to input. Otherwise, you return from the function before doing the loop that capitalizes the words.
Then after the for loop you should return the joined string.
Also, since you've reverse the string before you capitalize, you should be capitalizing the last letter of each word. Then you need to reverse the array before re-joining it, to get the words back in the original order.
function yay () {
var input = "Party like its 2015";
input = input.toLowerCase().split("").reverse().join("").split(" ");
for(var i = 1 ; i < input.length ; i++){
var len = input[i].length-1;
input[i] = input[i].substring(0, len) + input[i].substr(len).toUpperCase();
}
return input.reverse().join(" ");
}
alert(yay());
You can use regular expression for that:
input.toLowerCase().replace(/[a-z]\b/g, function (c) { return c.toUpperCase() });
Or, if you can use arrow functions, simply:
input.toLowerCase().replace(/[a-z]\b/g, c => c.toUpperCase())
Here's what I would do:
Split the sentence on the space character
Transform the resulting array using .map to capitalize the first character and lowercase the remaining ones
Join the array on a space again to get a string
function yay () {
var input = "Party like its 2015";
return input.split(" ").map(function(item) {
return item.charAt(0).toUpperCase() + item.slice(1).toLowerCase();
}).join(" ");
}
console.log(yay());
Some ugly, but working code:
var text = "Party like its 2015";
//partY likE itS 2015
function yay(input) {
input = input.split(' ');
arr = [];
for (i = 0; i < input.length; i++) {
new_inp = input[i].charAt(0).toLowerCase() + input[i].substring(1, input[i].length - 1) + input[i].charAt(input[i].length - 1).toUpperCase();
arr.push(new_inp);
}
str = arr.join(' ');
return str;
}
console.log(yay(text));
Try using ucwords from PHP.js. It's quite simple, actually.
String.prototype.ucwords = function() {
return (this + '')
.replace(/^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, function($1) {
return $1.toUpperCase();
});
}
var input = "Party like its 2015";
input = input.charAt(0).toLowerCase() + input.substr(1);
input = input.split('').reverse().join('').ucwords();
input = input.split('').reverse().join('');
Note: I modified their function to be a String function so method chaining would work.
function yay(str)
{
let arr = str.split(' ');
let farr = arr.map((item) =>{
let x = item.split('');
let len = x.length-1
x[len] = x[len].toUpperCase();
x= x.join('')
return x;
})
return farr.join(' ')
}
var str = "Party like its 2015";
let output = yay(str);
console.log(output) /// "PartY likE itS 2015"
You can split and then map over the array perform uppercase logic and retun by joining string.
let string = "Party like its 2015";
const yay = (string) => {
let lastCharUpperCase = string.split(" ").map((elem) => {
elem = elem.toLowerCase();
return elem.replace(elem[elem.length - 1], elem[elem.length - 1].toUpperCase())
})
return lastCharUpperCase.join(" ");
}
console.log(yay(string))

Categories