find-and-replace iteration with javascript - javascript

I am making a text editor for my website, and I need a find-and-replace function that finds each time the text occurs, highlights it, and, if the user chooses to, replaces it. Then it moves onto the next occurrence, and does the same thing.
I saw this code:
var haystackText = "";
function findMyText(needle, replacement) {
if (haystackText.length == 0) {
haystackText = document.getElementById("haystack").innerHTML;
}
var match = new RegExp(needle, "ig");
var replaced = "";
if (replacement.length > 0) {
replaced = haystackText.replace(match, replacement);
}
else {
var boldText = "<div style=\"background-color: yellow; display: inline; font-weight: bold;\">" + needle + "</div>";
replaced = haystackText.replace(match, boldText);
}
document.getElementById("haystack").innerHTML = replaced;
}
but it only works with a div that has text it, and not a textarea, like I have, and also, it finds and/or replaces all of the occurrences at once, instead of iterating through them one by one.
How can I make this work?

If the code is working with <Div> tag then u should work with div tag . just make that div tag editable .
Try this:
<div contenteditable="true">This is an editable div</div>
The contenteditable attribute specifies whether the content of an element is editable or not.

Related

How would I find the innerHTML index of text highlighted from textContent?

I am trying to program a simple text editor for fun.
I am stuck on this problem.
I want to add bold or italics to highlighted text on a button click.
I figure the best way to do this is get the index of the selected text and then add the bold tag / italic tag around the tag in the innerHTML.
However, I can not seem to get the position / index of the selected tag to carry over to the innerHTML. Obviosuly, the innerHTML code is offset by the tags.
Is there an easier way to do this?
I though finding the index of the highlighted text was the way to go. Okay. Unforunately, indexOf will only find the first occurance.
var word_document = document.getElementById('word-document');
/* This code is for our bold button */
var bold_button = document.getElementById('bold-button')
bold_button.addEventListener('click', (event) => {
/* Test to see if text is highlighted */
let text = window.getSelection().toString();
console.log("Selected Text: " + text);
if (text.length > 0) {
// Find the position of the highlighted text in the word document.
let position = word_document.innerHTML.indexOf(text); // Not a good way of doing it
console.log("Pos: ", position);
// Replace the highlighted text from the document with the bold text
word_document.innerHTML.replace(text, "<b>" + text + "</b>");
}
/* If text is not highlighted, add a bold tag */
else {
// Add bold tag to bottom of document
word_document.focus();
word_document.innerHTML += "<b></b>";
word_document.selectionEnd = word_document.innerHTML.length - 6;
}
});
/* This code is for our italic button */
var italic_button = document.getElementById('italics-button');
italic_button.addEventListener('click', function() {
let text = window.getSelection().toString();
// Same issue
});
<button id="bold-button">B</button>
<button id="italics-button">I</button>
<textarea id="word-document">Starting Text</textarea>
I suppose a possible way would be to iterate over the textContent and find if any text prior to the selected text matches it, and then set a variable to skip over that many matches. Is there an easier way to do this. Ideally, I would like to create a bold tag, or italic tag and append it to the textarea in a more proper fashion. I support traversing the DOM is probably a better way. Any ideas on how this might be more easily tackled?
Thanks
I use Plain / Vanilla Javascript.
Edit: Fixed code. Adding JsFiddle here
You can try this :
<html>
<header>
</header>
<body>
<button id="bold-button" onClick="makeBold()">B</button>
<div id="word-document" contenteditable>Starting Text</div>
<script>
function makeBold() {
var inputText = document.getElementById("word-document");
var innerHTML = inputText.innerHTML;
text = window.getSelection().toString();
var index = innerHTML.indexOf(text);
if (index >= 0) {
innerHTML = innerHTML.substring(0,index) + "<span style='font-weight: bold;'>" + innerHTML.substring(index,index+text.length) + "</span>" + innerHTML.substring(index + text.length);
inputText.innerHTML = innerHTML;
}
}
</script>
</html>
the idea here is to use a fake textArea: div with content editable.
I hope it helps u,
Good Luck!
simple dummy solution. this don't work for nested tags.
I highly recommended to read this tutorial
function action({tag, classes}, event){
const text = document.getElementById("word-document");
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const before = text.innerHTML.substr(0, range.startOffset);
const after = text.innerHTML.substr(range.endOffset);
const selected = text.innerHTML.substr(range.startOffset,range.endOffset - range.startOffset );
const warpped = `<${tag} ${classes ? "class=" + classes : ""}>${selected}</${tag}>`
text.innerHTML = before + warpped + after;
}
#word-document {
border: 1px solid black;
padding: 10px;
}
.underline{
text-decoration-line: underline;
}
<button onclick="action({tag: 'b'})">B</button>
<button onclick="action({tag: 'i'})">I</button>
<button onclick="action({tag: 'span', classes:'underline'})">Under score</button>
<div id="word-document" contenteditable>Starting Text</div>

jquery find and replace input field is replacing all of my content not just individual paragraphs

I have some paragraphs and an input box. Basically the user types into the field, and when they press "ENTER" it runs the jquery function seen a bit further down.
Essentially what happens is, when a match is found between the paragraph and the user input, the HTML of the paragraph is, for want of a better phrase, replaced by itself but this time round with a span wrapped round the matching text for the purpose of CSS to highlight it.
That's great, but the problem I'm having is that when the text is replaced, it replaces is with every single HTML element of that type on the page. Kind of hard to put into words so have a look at the behaviour with this fiddle. Enter in some text that is obviously appearing in the paragraph and see what it does.
How can I make the text regenerate element by element rather than taking everything in the world and reproducing it?
<input type="text" id="searchbox">
<p>Let's get some text to test</p>
<p>This is another paragraph</p>
function searchHighlight(searchText){
if(searchText){
// Declare variable for the question content
var content = $('p').text();
// Declare variable for the search phrase
var searchExp = new RegExp(searchText, "ig");
// Declare a variable for when a match is found
var matches = content.match(searchExp);
// If some of the text is found in the QUESTION do the following...
if(matches){
$("p").html(content.replace(searchExp, function(match){
return "<span class='selected'>" + match + "</span>";
}))
}
else{
$("#searchbox").css("border", "1px solid red")
}
}
Here is a fiddle
https://jsfiddle.net/awv5r1f0/1/
To achieve expected result , use below option using each, to find search the word and replace that particular p html
Issue: $("p").html() will update all p tags which is replace all of your p tags with same text
Update with looping all p elements
Using $(this).html update only p element with that matching word
$("p").each(function() {
$(this).html($(this).text().replace(searchExp, function(match) {
return "<span class='selected'>" + match + "</span>";
}))
})
code sample
$(function() {
// Create event for enter key in searchbox
$("#searchbox").on("keydown", function(event) {
if (event.which == 13) {
// run function
searchHighlight($(this).val())
}
})
})
function searchHighlight(searchText) {
if (searchText) {
// Declare variable for the question content
var content = $('p').text();
// Declare variable for the search phrase
var searchExp = new RegExp(searchText, "ig");
// Declare a variable for when a match is found
var matches = content.match(searchExp);
// If some of the text is found in the QUESTION do the following...
if (matches) {
$("p").each(function() {
$(this).html($(this).text().replace(searchExp, function(match) {
return "<span class='selected'>" + match + "</span>";
}))
})
} else {
$("#searchbox").css("border", "1px solid red")
}
}
}
.selected{
background: yellow;
font-weight: 700;
}
#searchbox{
border: 1px solid #ccc;
outline: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" id="searchbox">
<p>Let's get some text to test</p>
<p>This is another paragraph</p>
I'm not sure this is the best solution but it does what you requested.
The problem was that you were changing the html of every p you have. Not only for the one where the word was matched.
I used an each method to search through all the p you have. And insert the span where the match was found. This would work even if you have a bunch of questions (p) that have the match.
See below
$(function() {
// Create event for enter key in searchbox
$("#searchbox").on("keydown", function(event) {
if (event.which == 13) {
// run function
searchHighlight($(this).val())
}
})
})
function searchHighlight(searchText) {
if (searchText) {
// Declare variable for the question content
var content = $('p').text();
// Declare variable for the search phrase
var searchExp = new RegExp(searchText, "ig");
// Declare a variable for when a match is found
var matches = content.match(searchExp);
// If some of the text is found in the QUESTION do the following...
$('p').each(function() {
if ($(this).text().match(searchExp)) {
$(this).html($(this).html().replace(searchExp, function(match) {
return "<span class='selected'>" + match + "</span>";
}))
} else {
$("#searchbox").css("border", "1px solid red")
}
})
}
}
.selected {
background: yellow;
font-weight: 700;
}
#searchbox {
border: 1px solid #ccc;
outline: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" id="searchbox">
<p>Let's get some text to test</p>
<p>This is another paragraph</p>

Add html tags around highligted text in contenteditable div

I have a contenteditable div and i would like to add some html tags around highlighted text, after user select the text and click the button..
Here is the sample code. It has some javascript codes but i couldnt make it work as desired. And i played with a lot actually.
https://codepen.io/anon/pen/ybzzXZ
P.S. I'm going to add , or like html tags after when we solve the how to add html tags around it.
Some of that js codes which i found in stackoverflow.
function getSelectionText() {
var text = "";
if (window.getSelection) {
text = window.getSelection().text;
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
return text;
}
and the other one is
function replaceSelectionWithHtml(html) {
var range;
if (window.getSelection && window.getSelection().getRangeAt) {
range = window.getSelection().getRangeAt(0);
range.deleteContents();
var div = document.createElement("div");
div.innerHTML = html;
var frag = document.createDocumentFragment(), child;
while ( (child = div.firstChild) ) {
frag.appendChild(child);
}
range.insertNode(frag);
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
range.pasteHTML(html);
}
}
There are several challenges with the problem you present.
First off you need to gather the selected text value. You have posted some examples of that - that is fairly well documented elsewhere so I will leave that up to you to isolate that issue.
Next you need to highlight the selected text. Often to highlight something in HTML we wrap that text that we wish to highlight in a simple element such as a span, then give that span some class - for example often this is used to give a background color to some text. <span style='background-color:yellow'>some text</span> - not so difficult to understand that portion.
The challenge with this then is to combine your "discovered text" with the highlight. Pretty easy to wrap that text as in the span example provided earlier. One issue however is that if that text is previously within some other HTML elements, we need to ensure that the text choice in the discovery is for example not contained within another element AND if so, handle that issue. Let's illustrate that with this span: Howdy <span style='background-color:yellow'>some text</span> Some more.
Now for this example suppose we wish to highlight the text "Howdy some" - a portion of that text is previously within a span with our desired markup, thus we must first extract that, remove that "highlight" and henceforth highlight the new text "choice" of "Howdy some".
To provide an illustration of that. Type the words "This I want" into the text box and see how it gets highlighted.
This is not exactly your problem however it provides the "highlight" which you could potentially combine with your selector. I have NOT fully vetted this for bugs such as typing in HTML in to "highlight".
/* highlight words */
function findStringLimit(searchChar, searchCharIndex, searchedString) {
return searchedString.substring(0, searchedString.lastIndexOf(searchChar, searchCharIndex));
};
function highlightWords(wordsy, text) { /* eliminate a bug with parenthesis */
wordsy = wordsy.replace("(", "");
wordsy = wordsy.replace(")", ""); /* escape other characters for bug */
text = text.replace(";", "");
text = text.replace("'", "'");
text = text.replace("<", "<");
text = text.replace(">", ">");
text = text.replace("<span", "<span");
text = text.replace('autoCompleteWord">', 'autoCompleteWord">');
text = text.replace("</span", "</span");
text = text.replace('span>', 'span>');
var re = '(' + wordsy + ')(?![^<]*(?:<\/span class=\"autoCompleteWord\"|>))';
var regExp = new RegExp(re, 'ig');
var sTag = '<span class="autoCompleteWord">';
var eTag = "</span>";
return text.replace(regExp, sTag + '$&' + eTag);
};
function parseAndHighlight(wordstring, htmlString) {
var htmlStringUn = htmlString;
var found = htmlStringUn.toLowerCase().indexOf(wordstring.toLowerCase(), 0);
if (found >= 0) {
htmlStringUn = highlightWords(wordstring, htmlStringUn);
}
else {
//split and parse the beast
var words = wordstring.split(/\W+/);
var allPhrases = [];
allPhrases.push(wordstring);
var i = 0;
i = words.length;
while (i--) {
allPhrases.push(findStringLimit(" ", allPhrases[(words.length - i) - 1].length, allPhrases[(words.length - i) - 1]));
};
i = allPhrases.length;
while (i--) {
if (allPhrases[i] != "") words = words.concat(allPhrases[i]);
};
i = words.length;
while (i--) {
htmlStringUn = highlightWords(words[i], htmlStringUn);
};
};
return htmlStringUn;
}
$(document).on('change', '#myInput', function() {
var myValue = $('#myInput').val(); //get what was typed
$('#found').text(myValue);
myValue = myValue.replace(/^\s+|\s+$/g, ""); //strip whitespace on ends
$('#found').text(myValue + ':stripped:');
var showText = $('#origshower').text();
var newShowString = parseAndHighlight(myValue, showText); //my original highlighter
$('#shower').html(newShowString);
});
#holder{border:red solid 2px; padding: 5px;}
#myInput{width:200px; background-color: aqua;}
span.autoCompleteWord /* this is the word(s) found */
{
font-weight: bold;
background-color: yellow;
}
#shower{border:lime 2px solid;}
<script
src="https://code.jquery.com/jquery-1.12.4.min.js"
integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="
crossorigin="anonymous"></script>
<div id='holder'>
<input id='myInput' type='text' cols='60' rows='2' />Enter Text to match
</div>
<div id='origshower'>This is the span thistle with the whistle that I want matched is this neat</div>
<div id='shower'>none</div>
<div id='found'>enter</div>
You can just call executeCommand with formatBlock. You can find more information here:
https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand

replacing hashtags with anchor links in realtime

I want text within a textarea that has the hash # character to be replaced with an anchor link as they're typing.
eg. something #somethingelse somethingsomethingelse its actual code would be
something #somethingelse somethingelse
but in the textarea, I'd only want #somethingelse to be highlighted as I don't want it to actually have anchor code, kinda like how twitter and fb does it.
how does it work?
$('textarea').on("keyup", function() {
var str = $(this).val();
if (!str.match(/(http|ftp|https):\/\/[\w-]+(\.[\w-]+)+([\w.,#?^=%&:\/~+#-]*[\w#?^=%&\/~+#-])?#([a-zA-Z0-9]+)/g)) {
if (!str.match(/#([a-zA-Z0-9_]+)#/g)) {
str = str.replace(/#([a-zA-Z0-9_]+)/g, '#$1');
$('textarea').html(str)
}
}
});
.hashtag {
background: #000;
color: #fff;
}
I write it in javascript :
Html :
<textarea id="textArea" onkeyup="changeHash()"></textarea>
Javascript :
var outputString="";
function changeHash(){
var getObject= document.getElementById('textArea');
outputString =getObject.value.toString();
var checkTheLastChar = outputString.slice(-1);
if(checkTheLastChar=="#"){
outputString = outputString.substring(0, outputString.length - 1);
outputString += "⚓"; //Change it with anything you want
}
getObject.value = "";
getObject.value = outputString;
}
https://jsfiddle.net/emilvr/q27xgshe/1/
What you need to do is have a div below a transparent text area and duplicate the text from the textarea into the div with the links appended. If you append the text for a html tag to a text area it won't render because anything in a textarea only renders as editable text.

Regex join elements if directly next to each other

I have the following script which allows me to select text, and will then visually highlight it by wrapping the selected text in a span tag.
This normally works fine, but if there is a highlight tag separated from another highlight tag by only a space, it joins the two highlights together.
Javascript
var HVleftPanelContent = $("#highlight-view .top .content");
HVoutputUl = $("#highlight-view .contentBottom ul");
$("p").on("copy", highlight);
function highlight() {
var text = window.getSelection().toString();
var selection = window.getSelection().getRangeAt(0);
var selectedText = selection.extractContents();
var textStr = selectedText.textContent;
if (textStr == "\n") {
clearSelection();
return false;
} else if (textStr[textStr.length - 1] == "\n") {
textStr = textStr.slice(0, -1);
var reg = new RegExp("\n", "g");
textStr = textStr.replace(reg, "\n<b data='
'></b>") + "\n";
} else if (textStr.indexOf("\n") >= 0) {
var reg = new RegExp("\n", "g");
textStr = textStr.replace(reg, "\n<b data='
'></b>");
}
var span = $("<span class='highlight'>" + textStr + "</span>");
selection.insertNode(span[0]);
if (selectedText.childNodes[1] != undefined) {
$(selectedText.childNodes[1]).remove();
}
var txt = HVleftPanelContent.html();
HVleftPanelContent.html(txt.replace(/<\/span>(?:\s)*<span class="highlight">/g, ''));
HVoutputUl.html("");
$("#highlight-view .top .content .highlight").each(function () {
$("#highlight-view .contentBottom ul").append("<li><span>" + $(this).html() + "</span></li>");
});
saveIt();
clearSelection();
}
Recap
If HTML looks like this:
This is a short paragraph
And I highlight "is", the markup changes to:
This <span>is</span> a short paragraph
And then I highlight either "this" or "a", the markup erroneously changes to:
This <span>isa</short> paragraph
Instead of how it should change:
This <span>is</span> <span>a</span> paragraph
Potential Problem
I assume the problem lays in this line:
HVleftPanelContent.html(txt.replace(/<\/span>(?:\s)*<span class="highlight">/g, ''));
Where the Regex statement is joining <span> tags that are next to each other, which it should so that if two span tags are directly next to each other, it becomes one span, but the Regex isn't limiting the joining to only when they're directly next to each other.
So, basically, how can I change the Regex to only join span tags if they're directly next to each other.
Fairly simple, replace:
HVleftPanelContent.html(txt.replace(/<\/span>(?:\s)*<span class="highlight">/g, ''));
With:
HVleftPanelContent.html(txt.replace(/<\/span><span class="highlight">/g, ''));
The problem was (?:\s)*, which means match any white space 0 or more times, which means that it would match even the spans that are separated with spaces.

Categories

Resources