Reputation: 170
I need to compare two material-ui textfield contents and highlight the characters that are changed in both. This is basically the same question asked here, but for ReactJS instead of C# Windows Forms:
How to compare two rich text box contents and highlight the characters that are changed?
Upvotes: 1
Views: 3798
Reputation: 170
Much thanks to the people who replied. I amusingly came to a solution. Hopefully this helps other people with this problem.
The concept is that I have two editable textboxes with content, and I highlight the differences between the two. Firstly, let's address the highlighting. This is straightforward. I used Squiggs's method of comparing two strings by using the following package:
https://github.com/kpdecker/jsdiff
Utilizing the Diff
package, I created a function that compares the two strings and returns a result:
function compareStrings(string1, string2) {
let results = Diff.diffChars(string1, string2);
let output = "";
results.forEach((item) => {
if (item.removed) {
output += `<span style="background-color:yellow">${item.value}</span>`;
} else if (!item.added) {
output += `${item.value}`;
}
});
return output;
}
When using the Diff
package, it separates the identified differences and similarities into different items. It makes working with the findings relatively easy. In the code above, the results in results
are iterated over. If an item has removed
set to true, it's an item in string1
that's not in string2
. If added
is set to true, its an item in string2
that's not in string1
. If neither are set to true, it's an item in both strings. The output of my function will contain all the characters of string1
. If a item has added
set to true, the item is ignored. For example, if string1
is cat
and string2
is bat
, output
will be cat
but with c
highlighted in yellow.
So how do I insert this highlighted text into a Material-Ui textbox? Well, I chose to go down the route of utilizing a contentEditable control. You can transform a p
tag (and other tags) to an editable textbox that renders HTML. This isn't a solution that I love since it opens up the hassle of dealing with XSS, but it seems unavoidable. If anyone follows this solution, I implore you to implement client-side and server-side input sanitation to prevent XSS. The latter is especially important because client-side validation can be bypassed rather easily through various methods, such as modifying the page with inspect element or by utilizing packet crafting tools.
Here's how a contentEditable textbox looks like in HTML:
<p
id="textbox1"
className="textbox"
variant="outlined"
contentEditable={true}
/>
From here, I can assign the results of compareStrings
to it:
document.getElementById("textbox1").innerHTML = htmlData
So the end result would have two contentEditable textboxes. I would have to invoke compareStrings twice and apply the results to both.
Of course, I've only detailed just the essentials to solve this problem. There's other quality of life features such as copy and paste as detailed below:
Highlight value of @material-ui/core TextField in React at specific indices
Upvotes: 2
Reputation: 91
If you could supply a bit of your code then it'd be easier to understand how your code is currently set up and how to implement the feature. I tried to create something along the lines of what you mentioned (sandbox: https://codesandbox.io/s/sam-cards-forked-jyrlv?file=/src/App.js:0-1786)
Explanation:
First, we'll need to distinguish a way to find the differences between the two strings. That's pretty easy, and can be done by iterating character-by-character for each string and comparing the two together.
const findDiff = (string1, string2) => {
// we will use this to keep an eye on the two strings
let index = 0
while (index < string1.length || index < string2.length) {
const left_char = string1[index]
const right_char = string2[index]
if (left_char !== right_char) {
// they are different
} else {
// they are the same character
}
index++
}
return // return something here
}
findDiff(string1, string2);
Now, we have the question of rendering them to the screen and highlighting them. One way (it's ugly but it does it) is to:
I set it up to display on a button click, you can see it here: https://codesandbox.io/s/sam-cards-forked-jyrlv?file=/src/App.js:0-1786
import "./styles.css";
import { useState, useEffect } from "react";
const string1 = "abcdefg";
const string2 = "adcelig123";
const strar1 = string1.split("");
const strar2 = string2.split("");
export const YourComponent = () => {
const [isHighlightActive, setIsHighlightActive] = useState(false);
const [wrongCharIndexes] = useState(new Set());
// re-calculate the differences between strings whenever they change
useEffect(() => {
findDiff(string1, string2);
}, [string1, string2]);
const findDiff = (string1, string2) => {
// we will use this to keep an eye on the two strings
let index = 0;
while (index < string1.length || index < string2.length) {
const left_char = string1[index];
const right_char = string2[index];
if (left_char === right_char) {
wrongCharIndexes.add(index);
}
index++;
}
return;
};
return (
<div className="App">
{isHighlightActive ? (
// map through the two strings and render the highlighted character or regular character
<>
<p className="flex">
{strar1.map((char, index) => {
return wrongCharIndexes.has(index) ? (
<span className="highlighted">{char}</span>
) : (
<>{char}</>
);
})}
</p>
<p className="flex">
{strar2.map((char, index) => {
return wrongCharIndexes.has(index) ? (
<span className="highlighted">{char}</span>
) : (
<>{char}</>
);
})}
</p>
</>
) : (
<div>
<p>{string1}</p>
<p>{string2}</p>
</div>
)}
<button onClick={() => setIsHighlightActive((prev) => !prev)}>
Click me
</button>
</div>
);
};
Upvotes: -1