Text Highlighting - Content Editable - javascript

I've been experimenting with contenteditable fields in javascript. As you can probably guess, I'm trying to build a WYSIWYG editor. Piecing together answers I found on Stack Overflow, I managed to build this test case. The test case's job is to manually and programmatically highlight text and embolden it. There is just one small problem. If you click the "bold" button once, a substring is highlighted and subsequently made bold. However, if you click it again, it prompts an error:
"Error: Failed to execute 'setStart' on 'Range': There is no child at offset 12.
at Error (native)
at HTMLInputElement.<anonymous> (http://run.jsbin.io/runner:15:13)
at HTMLDocument.oa (http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js:18:373)
at HTMLDocument.c.event.handle (http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js:55:307)
at HTMLDocument.j.handle.o (http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js:49:363)"
After the first button press, the text field representing the WYSIWYG editor's content contains the following:
Some text he<span style="font-weight: bold;">re for y</span>ou to bold
Upon testing (by outputting the first child's length), my hypothesis is that it's trying to highlight only the first part - "Some text he." Further testing, including commenting out the statement to embolden the selection, confirmed this, I believe. In fact, when this is commented out the editor works well.
I searched on Google and Stack Exchange, yet partially due to my ignorance as to why this is happening I could find nothing.
My question is therefore - why is this error appearing, and what can I do to avoid it and reach my goal of programmatically highlighting and embolding this text?

Referring to your linked demo code, you're setting the range with this code:
range.setStart(mainDiv.firstChild, 12);
range.setEnd(mainDiv.firstChild, 20);
But mainDiv.firstChild is the first text node of main div. On initial run that text node is
Some text here for you to bold
After the first run that text node gets broken into three pieces: 1 before the bold, one inside the bold, and one after.
Some text he<span style="font-weight: bold;">re for y</span>ou to bold
So your mainDiv.firstChild becomes
Some text he
For that reason the call to range.setEnd(mainDiv.firstChild, 20) is out of range.
To avoid this, don't hard-code your calls to setRange. Instead get your ranges directly from user selection (a very common case) or after inspecting the actual DOM just prior to making the call.

Related

Using a Chrome extension to replace characters spanning more than one line in a Facebook status update

I have created a Google Chrome extension to allow users to select text in a component. This works great for most sites. However, Facebook handles its status updates differently. It seems that even though you are filling in what seems to be a single text box, it is actually using a div > div > span > span construct for every single line in this text box. I have no idea why they chose to do this but it makes replacing multiple lines of text much more complex.
Is there a way to select multiple lines (or even contiguous portions of multiple lines) of text in a Facebook status update and replace the data?
The relevant portion of my code looks like this:
function replace_text(language){
let selection = window.getSelection();
string = selection.toString();
/* This section contains code that uses string.replace to replace characters in the string. */
document.execCommand("insertText", false, string);
}
Based on the way my code works now, if I replace text on a single line I have no problems. But, if I replace text that spans multiple lines I end up with a blank unusable input box. Undoubtedly it is because it is removing portions of the html code. How can I fix my code so that the replacement process works properly not only for other sites but also for Facebook?
As of this moment, the one common theme among all status updates (and comments) are that their texts reside within a single or set of span elements with the attribute data-text set to true. So let's target those:
document.querySelectorAll("span[data-text='true']");
For me, I've typed into the status field 3 lines and comment field 1 line of dummy text. So when I execute the above code into the console it returns an array of those four cumulative lines:
>>> (4) [span, span, span, span]
With that array, I can use the Array.prototype.forEach() method to iterate through the spans and replace the innerText:
document.querySelectorAll("span[data-text='true']").forEach(function(element) {
element.innerText = element.innerText.replace('Lorem ipsum','Hello world');
});
However, it is important to note that these changes are being made in the HTML itself and Facebook doesn't store all of its data directly in the HTML. Therefore it can cause undesirable events to occur when you type text into a field, unfocus, change the text in the field, and refocus that field. When you refocus I believe it grabs data of what the text was, before you unfocused that field, from an ulterior source like React's Virtual DOM. To deter it from doing that, the changes either need to be made after clicking the field (real or simulate) or as the user is typing using some sort of MutationObserver (src).

use window.getSelection get selected or cursor located text line from textarea

I am dynamically filling textarea using Ajax call's. In my code, I want to get the cursor's current line content using window.getSelection. I tried following code,
var range = window.getSelection().toString;
alert (typeof(range));
But, It returns function as alert Message. or Any other better way, to get the cursor's current line content from textarea that code need to support in all browser's.? Once I get the current line content I will find out line number and update new content on it.
Firstly, textareas have a different selection API from regular content: use selectionStart and selectionEnd properties of the textarea rather than window.getSelection().
Secondly, getting the current line requires some creative coding if you're accounting for the browser's auotmatic wrapping of content. I've seen a few questions about this in Stack Overflow. Here's one example:
finding "line-breaks" in textarea that is word-wrapping ARABIC text

dynamic highlighting using javascript/uiwebview ios

I have some text content displayed on a UIWebView which is plain html. The current paragraph is highlighted in yellow and the user has selected the word 'If the'. (link to image: http://i.stack.imgur.com/GKp9h.png)
1) When the user selects some text on the uiwebiew, how do I perform dynamic highlighting? i.e. as the user is selecting text, what ever text is selected gets highlighted in purple?
For instance, I like the words 'If the' to be highlighted in purple (maybe using window.getSelection() ? ) and that this behaviour is dynamic such that as the user selects subsequent words, these words under selection gets highlighted in purple.
What I am struggling with at the moment is:
1) What event handler (JavaScript or iOS) should I listen to, when the user is selecting some text on the uiwebview? This is before the uimenucontroller opens up.
2) Once, I get the selected text (using window.getSelection(), how do I modify the DOM in a clean efficient way such that the selected text gets highlighted?
I suppose for 2) I cannot directly use style.backgroundColor=<hex code of purple>
There's a very similar question here: How to get selection text from UIWebView?
From the accepted answer there, looks like this might answer your question: http://zaldzbugz.posterous.com/how-to-mark-or-get-the-highlighted-string-ins/ There's a discussion about getting and styling the text selection in a web view.

Rangy: Creating a new highlight is remembering the old highlight

I am using the highlighter module in Rangy.
I have a div element, which has some html. The html is actually loaded from a file using ajax, I have a button which does this loading.
Once the text is loaded, I can select a portion of the displayed html and press my "Highlight" button. This calls some Rangy code and highlights the text as desired...
//called on document load
rangy.init();
cssApplier = rangy.createCssClassApplier(highlightClassName, { normalize: true });
highlighter = rangy.createHighlighter(document, "TextRange");
highlighter.addClassApplier(cssApplier);
//called on "Highlight" button click
highlighter.highlightSelection(highlightClassName, selection);
For the purpose of replicating, please select a large portion for first highlight.
Next, I click my load html button to reload the html. The highlight is gone, as expected. But now I select another bit of text, which happens to overlap the first highlight that I did. Now when I press the "Highlight" button, for some reason the highlight is the one from the previous highlight. Why is this happening?
I know there must be something to do with the merging, but I can't understand why. When I debug the JS I can see that the selection (from rangy.getSelection()) is what I expect it to be.
Here is a JSFiddle replication of the problem
The reason this is happening is because each highlight exists as a pair of character offsets rather than having references to actual ranges in the DOM, meaning that when some part of the DOM is replaced, existing highlights remain blissfully unaware and continue to assume they are applied to the original character range.
Your workaround is fine. Another way would be to call the highlighter's removeHighlights() method:
highlighter.removeHighlights(highlighter.highlights);
Demo: http://jsfiddle.net/8pMEt/1/
I'm going to add a removeAllHighlights() method that will do the same thing.
One thing that the documentation doesn't make clear is that highlighting is designed to work on static DOMs, or at least DOMs with text content that doesn't change. Changing the DOM after highlights have been created could obviously throw character offsets off and the whole thing falls down.
I solved this problem by re-creating the highlighter prior to each highlightSelection call. I don't know why this works, but the highlighter must store some data regarding previous highlights that is uses for highlight merging or something.
The code in my question can be changed as follows to solve the problem:
//called on document load
rangy.init();
cssApplier = rangy.createCssClassApplier(highlightClassName, { normalize: true });
//called on "Highlight" button click
highlighter = rangy.createHighlighter(document, "TextRange");
highlighter.addClassApplier(cssApplier);
highlighter.highlightSelection(highlightClassName, selection);

Determine visible characters to be highlighted using html source

I'm using TinyMCE to let users edit and format text, output is html code.
The html is then sent to the server and is pushed to other clients that can follow the edit progress on a webpage, the html is inserted into a div so users can see the format but they are not able to edit.
Now I want the cursor position and any selection the user makes in the editor to show up on the readonly page using highlight(background color) if selected or inserting an empty span with a black border between characters to imitate the cursor position.
I made an attempt using
editor.tinymce().selection.getRng()
which gives me the start and end position of what the user sees(formatting characters are not counted)
Then I traversed the DOM and counted the characters in every text element wrapping the ones selected with a highlight span. This resulted in messy code but worked pretty well until I hit a non ascii or encoded character in the textblock.
Example html
<p>abc <b>de</b>fg</p>
looks like this to the user
abc defg
Say user selected character c to d (selection covers c, a blank, first half of the bold tag and d),
tinymce will return range start:2 end:5
but the actual characters behind would be start:5 end:16.
Just wrapping the text from char 5 to 16 in a highlight span will result in bad html.
Checking for words starting with & and ending with ; and adding number of positions will turn into a mess. There has to be a simpler way
How do I calculate how many "visible" characters a set of html character will turn into?
Or maybe you would attack the problem in another way
Thanks
PS 1
I've looked into the various jquery highlight plugins but they seem to highlight based on a search string. Those does not work in the case user selects one character and that character exists several times, they will then highlight all occurences.
I've tackled this problem in my Rangy library: the TextRange module concerns itself with the text the user sees. It sounds like you need the selectCharacters() and toCharacterRange() methods of Rangy's Range objects.

Categories