I am trying to create jquery which will allow to highlight selected text by mouse including several selection (e.g. three separate sections). Currently I have the following code, but experienced the following issues with this code:
it allows single selection (while I would like to have a possibility to select first sentence, then third one and have them both marked independently).
as it works based on regexp'es, when I select single letter (e.g. 'a') all letters are selected.
styles (e.g. bold) are not preserved.
Please advise how I can improve this script to address above issues.
Thank you!
js code:
enableHighlights(".mainPane");
function enableHighlights(thisDiv) {
$(thisDiv).on("mouseup", function () {
var selectedText = getSelectionText();
var selectedTextRegExp = new RegExp(selectedText);
var text = $(this).text().replace(selectedTextRegExp, "<span class='red'>" + selectedText + "</span>");
$(this).html(text);
$('#selection').html(selectedText);
});
}
function getSelectionText() {
var text = "";
if (window.getSelection) {
text = window.getSelection().toString();
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().htmlText;
}
return text;
}
this is as close as I could get to the correct answer: DEMO
enableHighlights(".mainPane");
var sText=[],
sStart=[];
function enableHighlights(thisDiv) {
var initialHTML=$(thisDiv).html();
var theHTML='';
$(thisDiv).on("mouseup", function () {
var selectedText = getSelectionText();
var selectedRange=getSelectionRange($(thisDiv)[0]);
sText.push(selectedText);
sStart.push(selectedRange.start);
theHTML=initialHTML;
$.each(sText,function(i,v){
var selectedTextRegExp = new RegExp(v);
var usingHTML=theHTML.slice(sStart[i]-1);
var text = usingHTML.replace(selectedTextRegExp, "<span class='red'>" + v + "</span>");
theHTML=theHTML.slice(0,sStart[i]-1)+text;
});
$(this).html(theHTML);
$('#selection').html(selectedText);
});
}
function getSelectionText() {
var text = "";
if (window.getSelection) {
text = window.getSelection().toString();
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().htmlText;
}
return text;
}
function getSelectionRange(element) {
var start = 0, end = 0;
var sel, range, priorRange;
if (typeof window.getSelection != "undefined") {
range = window.getSelection().getRangeAt(0);
priorRange = range.cloneRange();
priorRange.selectNodeContents(element);
priorRange.setEnd(range.startContainer, range.startOffset);
start = priorRange.toString().length;
end = start + range.toString().length;
} else if (typeof document.selection != "undefined" &&
(sel = document.selection).type != "Control") {
range = sel.createRange();
priorRange = document.body.createTextRange();
priorRange.moveToElementText(element);
priorRange.setEndPoint("EndToStart", range);
start = priorRange.text.length;
end = start + range.text.length;
}
return {
start: start,
end: end
};
}
the only problem is when you try to select a bold and normal text together which I was unable to find a workaround!
Related
What's up guys, how's it going? I'm having trouble saving the cursor position and inserting dynamic tags.I'm using the Emojiarea plugin to create a div where I can write texts, insert emojis, templates and tags. https://github.com/mervick/emojionearea
I use the following function below to create a div on my textarea:
$("#email_campaign_description").emojioneArea({
search: false,
recentEmojis: false,
pickerPosition: "right",
events: {
blur: function (editor, event) {
$scope.lastPosition = getCaretCharacterOffsetWithin(editor[0])
},
}
});
The next function returns the last position of my cursor when I click
somewhere in the text:
function getCaretCharacterOffsetWithin(element) {
var caretOffset = 0;
var doc = element.ownerDocument;
var win = doc.defaultView;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
} else if ( (sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
debugger
return caretOffset;
}
And the last function adds dynamic tags to the text:
$scope.chooseTag = function (label) {
var domElement = $('#email_campaign_description');
var emojiElement = domElement[0].emojioneArea;
if (document.selection) {
domElement.focus();
var sel = document.selection.createRange();
sel.text = $scope.tags.model;
domElement.focus();
} else if ($scope.lastPosition) {
var startPos = $scope.lastPosition;
var endPos = startPos + $scope.tags.model[0].length;
emojiElement.setText(emojiElement.getText().substring(0, startPos) + ' ' + $scope.tags.model + ' ' + emojiElement.getText().substring(endPos, emojiElement.getText().length));
domElement.focus();
} else {
emojiElement.setText($scope.tags.model);
domElement.focus();
}
if ($scope.tags.model === '[vendor_name]') {
emojiElement.setText(domElement.val().replace('[vendor_name]', $scope.vendor.name));
}
$scope.campaign.description = $('#email_campaign_description').val();
};
The problem happens that the function that stores the click position in the text does not read emojis. So, if I write a text like: "Hello [emoji], welcome!" and at the end of that text I try to add a tag, my function will not read the emoji, and will insert the tag over the last character of my sentence, in this case "!". Likewise if I add two emojis, my function will not read and insert the tag over the last two characters in this case "o!". The correct thing would be my function to read these two emojis, and add my tag exactly in the desired location, that is: "Hello [emoji], welcome! [Tag]"
What can I do for my function getCaretCharacterOffsetWithin(element) to read emojis as a character, or a space occupied?
The problem is that javascript isn't great at handling Unicode strings.
For example:
"hello".length === 5
"👩🏻🦰".length === 7
There are several libraries that can help accurately measure the length of unicode strings. Graphemer is one of them (full disclosure: I published this library).
To fix your getCaretCharacterOffsetWithin(element) function do the following:
Import and instantiate the Graphemer library.
import Graphemer from 'graphemer';
const splitter = new Graphemer();
function getCaretCharacterOffsetWithin(element) {...}
Update the first instance where you count the string length.
caretOffset = preCaretRange.toString().length; // original
caretOffset = splitter.countGraphemes(preCaretRange.toString()); // updated
Update the second instance where you count the string length.
caretOffset = preCaretTextRange.text.length; // original
caretOffset = splitter.countGraphemes(preCaretTextRange.text); // updated
A library-free (Typescript) solution:
correctUnicodeOffset(offset: number, str: string): number {
if (offset < 1) return offset;
return Array.from(str.substr(0, offset)).length;
}
Use it like this:
myOffset = this.correctUnicodeOffset(myOffset, myStr);
var input = document.getElementById("div_id");
var username = "TEST";
input.focus();
var selection = window.getSelection();
var range = selection.getRangeAt(0);
var span = document.createElement('span');
span.setAttribute('class', 'tagged-user');
span.setAttribute('id', 55);
span.innerHTML = username;
//var initialText = input.textContent;
//var before_caret = initialText.slice(0,getCaretPosition(input));
// var after_caret = initialText.slice(getCaretPosition(input), initialText.length);
// // var before_caret = input.textContent;
// console.log("before_caret " + before_caret);
// *******************************
//this is the regex that match last #something before caret and it work good.
//var droped_at = before_caret.replace(/#\w+(?!.*#\w+)/g, '');
// *******************************
// input.textContent = droped_at + after_caret;
// console.log("before_caret " + before_caret);
range.deleteContents();
range.insertNode(span);
range.collapse(false);
range.insertNode(document.createTextNode("\u00a0"));
// cursor at the last
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
.tagged-user {
color: #1e81d6;
font-weight: bold;
}
<div contenteditable="true" id="div_id">
First person <span class="tagged-user" id="1">#Joe </span> and #te and <span class="tagged-user" id="2">#Emilie</span> want to go to the beach.
</div>
An example of a text:
hello man, #te #emilie how are you?( #emilie is a span tag)
In this example if the cursor is after #te, I want to replace #te by TEST.
Hello,
I am trying to make a facebook like mention system. I'm able to trigger a message that shows a list of all my contacts as soon as I type "#".
Everything is working fine, the only problem is when I type "#te" for finding "TEST" in the list, it should be able to replace "#te" with "TEST". It inputs #teTEST, any clue how to do it ? I'm using a div with contenteditable.
Thanks a lot.
It seems to be sooo complicated to get/set caret position in a content-editable div element.
Anyway, I created a working snippet with the functions I found in these topics…
Get caret position in contentEditable div
Set Caret Position in 'contenteditable' div that has children
… and added a function oninput to make the replacement you wanted:
(See comments in my code for more details)
var input = document.getElementById("div_id");
var replaced = "#te"; // TAKIT: Added this variable
var replacer = "#TEST"; // TAKIT: Renamed this variable
var replacer_tags = ['<span class="tagged-user" id="0">', '</span>']; // TAKIT: Added after comment
input.focus();
// TAKIT: Added functions from these solutions:
// https://stackoverflow.com/questions/3972014/get-caret-position-in-contenteditable-div
function GetCaretPosition(element) {
var caretOffset = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
} else if ( (sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
// https://stackoverflow.com/questions/36869503/set-caret-position-in-contenteditable-div-that-has-children
function SetCaretPosition(el, pos) {
// Loop through all child nodes
for (var node of el.childNodes) {
if (node.nodeType == 3) { // we have a text node
if (node.length >= pos) {
// finally add our range
var range = document.createRange(),
sel = window.getSelection();
range.setStart(node, pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return -1; // we are done
} else {
pos -= node.length;
}
} else {
pos = SetCaretPosition(node, pos);
if (pos == -1) {
return -1; // no need to finish the for loop
}
}
}
return pos; // needed because of recursion stuff
}
// TAKIT: Added this whole function
input.oninput = function() {
var caretPos = GetCaretPosition(input); // Gets caret position
if (this.innerHTML.includes(replaced)) { // Filters the calling of the function
this.innerHTML = this.innerHTML.replace(replaced, replacer_tags.join(replacer) + ' '); // Replace
caretPos += replacer.length - replaced.length + 1; // Offset due to strings length difference
SetCaretPosition(input, caretPos); // Restores caret position
}
// console.clear(); console.log(this.innerHTML); // For tests
}
.tagged-user {
color: #1e81d6;
font-weight: bold;
}
<div contenteditable="true" id="div_id">
First person <span class="tagged-user" id="1">#Joe</span> and (add "te" to test) # and <span class="tagged-user" id="2">#Emilie</span> want to go to the beach.
</div>
Hope it helps.
I am having a few issues with my code regarding caret positioning, content editable div and HTML tags in it.
What I am trying to achieve
I'd like to have a content editable div, which allows for line breaks and multiple HTML tags inserted by typing some sort of shortcut - double left bracket '{{' in my case.
What I have achieved so far
The div allows for a single HTML tag and only works in a single line of text.
The issues
1) When I break the line with the return key, the {{ no longer triggers the tag to show up. I assume that you have to somehow make the script to take line breaks (nodes?) into account when creating the range.
2) If you already have one HTML tag visible, you can't insert another one. Instead, you get the following error in browser's console.
Uncaught DOMException: Failed to execute 'setStart' on 'Range': The offset 56 is larger than the node's length (33).
I noticed that range offset goes to 0 (or starts with the end of HTML tag) which is probably at the culprit of the issue here.
Below is the code I have so far...
Everything is triggered on either keyup or mouseclick.
var tw_template_trigger = '{{';
var tw_template_tag = '<span class="tw-template-tag" contenteditable="false"><i class="tw-icon tw-icon-close"></i>Pick a tag</span>';
$('.tw-post-template-content').on( 'keyup mouseup', function() {
// Basically check if someone typed {{
// if yes, attempt to delete those two characters
// then paste tag HTML in that position
if( checkIfTagIsTriggered( this ) && deleteTagTrigger( this ) ) {
pasteTagAtCaret();
}
});
function pasteTagAtCaret(selectPastedContent) {
// Then add the tag
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// only relatively recently standardized and is not supported in
// some browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = tw_template_tag;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if ( (sel = document.selection) && sel.type != "Control") {
// IE < 9
var originalRange = sel.createRange();
originalRange.collapse(true);
sel.createRange().pasteHTML( tw_template_tag );
}
}
function checkIfTagIsTriggered(containerEl) {
var precedingChar = "", sel, range, precedingRange;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setStart(containerEl, 0);
precedingChar = range.toString().slice(-2);
}
} else if ( (sel = document.selection) && sel.type != "Control") {
range = sel.createRange();
precedingRange = range.duplicate();
precedingRange.moveToElementText(containerEl);
precedingRange.setEndPoint("EndToStart", range);
precedingChar = precedingRange.text.slice(-2);
}
if( tw_template_trigger == precedingChar )
return true;
return false;
}
function deleteTagTrigger(containerEl) {
var preceding = "",
sel,
range,
precedingRange;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setStart(containerEl, 0);
preceding = range.toString();
}
} else if ((sel = document.selection) && sel.type != "Control") {
range = sel.createRange();
precedingRange = range.duplicate();
precedingRange.moveToElementText(containerEl);
precedingRange.setEndPoint("EndToStart", range);
preceding = precedingRange.text;
}
// First Remove {{
var words = range.toString().trim().split(' '),
lastWord = words[words.length - 1];
if (lastWord && lastWord == tw_template_trigger ) {
/* Find word start and end */
var wordStart = range.toString().lastIndexOf(lastWord);
var wordEnd = wordStart + lastWord.length;
range.setStart(containerEl.firstChild, wordStart);
range.setEnd(containerEl.firstChild, wordEnd);
range.deleteContents();
range.insertNode(document.createTextNode(' '));
// delete That specific word and replace if with resultValue
return true;
}
return false;
}
I noticed that those two lines are causing the browser error in the second issue
range.setStart(containerEl.firstChild, wordStart);
range.setEnd(containerEl.firstChild, wordEnd);
Theoretically, I know what the issue is. I believe both issues could be solved by making the range-creating script to use parent node rather than children nodes and also to loop through text nodes which line breaks are. However, I don't have a clue how to implement it at this point.
Could you please point me into the right direction?
Edit
I've actually managed to upload a demo with the progress so far to make it more clear.
Demo
I solved the problem myself and merged all functions into one. Neat! Below is the final code. I removed the ability to press enter after further considering it.
Hope it helps someone
var tw_template_trigger = '{{';
var tw_template_tag = '<span class="tw-template-tag" contenteditable="false">Pick a tag</span>';
$(".tw-post-template-content").keypress(function(e){ return e.which != 13; });
$('.tw-post-template-content').on( 'keyup mouseup', function() {
triggerTag( this );
});
function triggerTag(containerEl) {
var sel,
range,
text;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange(); // clone current range into another variable for manipulation#
range.collapse(true);
range.setStart(containerEl, 0);
text = range.toString();
}
}
if( text && text.slice(-2) == tw_template_trigger ) {
range.setStart( range.endContainer, range.endOffset - tw_template_trigger.length);
range.setEnd( range.endContainer, range.endOffset );
range.deleteContents();
range.insertNode(document.createTextNode(' '));
//
var el = document.createElement("div");
el.innerHTML = tw_template_tag;
var frag = document.createDocumentFragment(), node, lastNode;
while ( (node = el.firstChild) ) {
lastNode = frag.appendChild(node);
}
var firstNode = frag.firstChild;
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
return true;
}
return false;
}
I have this contentedittable div
<div contenteditable="true" id="text">minubyv<img src="images/smiley/Emoji Smiley-01.png" class="emojiText" />iubyvt</div>
Here is an image description of the code output
so I want to get the caret position of the div and lets assume that the cursor is after the last character. And this is my code for getting the caret position
function getCaretPosition(editableDiv) {
var caretPos = 0,
sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
if (range.commonAncestorContainer.parentNode == editableDiv) {
caretPos = range.endOffset;
}
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
if (range.parentElement() == editableDiv) {
var tempEl = document.createElement("span");
editableDiv.insertBefore(tempEl, editableDiv.firstChild);
var tempRange = range.duplicate();
tempRange.moveToElementText(tempEl);
tempRange.setEndPoint("EndToEnd", range);
caretPos = tempRange.text.length;
}
}
return caretPos;
}
var update = function() {
console.log(getCaretPosition(this));
};
$('#text').on("mousedown mouseup keydown keyup", update);
But the problem is that it returns 6 instead of 14. The caret position goes back to 0 after the image. Please is there a way I can get the caret position to be 14 in this case.
EDIT
I want to also insert some element starting from the caret position. so this is my function to do that
selectStart = 0;
var update = function() {
selectStart = getCaretPosition(this);
};
function insertEmoji(svg){
input = $('div#text').html();
beforeCursor = input.substring(0, selectStart);
afterCursor = input.substring(selectStart, input.length);
emoji = '<img src="images/smiley/'+svg+'.png" class="emojiText" />';
$('div#text').html(beforeCursor+emoji+afterCursor);
}
See Tim Down's answer on Get a range's start and end offset's relative to its parent container.
Try to use the function he has to get the selection index with nested elements like this:
function getCaretCharacterOffsetWithin(element) {
var caretOffset = 0;
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var sel;
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
} else if ( (sel = doc.selection) && sel.type != "Control") {
var textRange = sel.createRange();
var preCaretTextRange = doc.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
var update = function() {
console.log(getCaretCharacterOffsetWithin(this));
};
$('#text').on("mousedown mouseup keydown keyup", update);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable="true" id="text">minubyv<img src="https://themeforest.net/images/smileys/happy.png" class="emojiText" />iubyvt</div>
I wrote my own function, based on Tim Down's, that works like you want it. I changed the treeWalker to filter NodeFilter.ELEMENT_NODE insted of NodeFilter.SHOW_TEXT, and now <img/> elements also get processed inside our loop. I start by storing the range.startOffset and then recurse through all the selection tree nodes. If it finds an img node, then it adds just 1 to the position; if the current node element is different than our range.startContainer, then it adds that node's length. The position is altered by a different variable lastNodeLength that is adds to the charCount at each loop. Finally, it adds whatever is left in the lastNodeLength to the charCount when it exists the loop and we have the correct final caret position, including image elements.
Final working code (it returns 14 at the end, exactly as it should and you want)
function getCharacterOffsetWithin_final(range, node) {
var treeWalker = document.createTreeWalker(
node,
NodeFilter.ELEMENT_NODE,
function(node) {
var nodeRange = document.createRange();
nodeRange.selectNodeContents(node);
return nodeRange.compareBoundaryPoints(Range.END_TO_END, range) < 1 ?
NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
},
false
);
var charCount = 0, lastNodeLength = 0;
if (range.startContainer.nodeType == 3) {
charCount += range.startOffset;
}
while (treeWalker.nextNode()) {
charCount += lastNodeLength;
lastNodeLength = 0;
if(range.startContainer != treeWalker.currentNode) {
if(treeWalker.currentNode instanceof Text) {
lastNodeLength += treeWalker.currentNode.length;
} else if(treeWalker.currentNode instanceof HTMLBRElement ||
treeWalker.currentNode instanceof HTMLImageElement /* ||
treeWalker.currentNode instanceof HTMLDivElement*/)
{
lastNodeLength++;
}
}
}
return charCount + lastNodeLength;
}
var update = function() {
var el = document.getElementById("text");
var range = window.getSelection().getRangeAt(0);
console.log("Caret pos: " + getCharacterOffsetWithin_final(range, el))
};
$('#text').on("mouseup keyup", update);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable="true" id="text">minubyv<img contenteditable="true" src="https://themeforest.net/images/smileys/happy.png" class="emojiText" />iubyvt</div>
This returns highlighted text:
function getSelection(elem) {
var selectedText;
if (document.selection != undefined) { // IE
elem.focus();
var sel = document.selection.createRange();
selectedText = sel.text;
} else if (elem.selectionStart != undefined) { // Firefox
var startPos = elem.selectionStart;
var endPos = elem.selectionEnd;
selectedText = elem.value.substring(startPos, endPos)
}
return selectedText;
}
$(document).on('mousedown', 'button', function(e) {
var selection = getSelection( $('#txtarea').get(0) );
alert(selection);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="txtarea">this is a test</textarea>
<button>highlighted text</button>
Now I need to select adjacent character from selected/highlighted text. for example if his is a t is selected, then I need to get both t(L) and e (R) characters. How can I do that?
Try this one
function GetSelection() {
var selection = "";
var textarea = document.getElementById("myArea");
if ('selectionStart' in textarea) {
// check whether some text is selected in the textarea
if (textarea.selectionStart != textarea.selectionEnd) {
selection = textarea.value.substring(textarea.selectionStart - 1, textarea.selectionEnd + 1);
}
} else { // Internet Explorer before version 9
// create a range from the current selection
var textRange = document.selection.createRange();
// check whether the selection is within the textarea
var rangeParent = textRange.parentElement();
if (rangeParent === textarea) {
selection = textRange.text;
}
}
if (selection == "") {
alert("No text is selected.");
} else {
alert("The current selection is: " + selection);
}
}
<body>
<textarea id="myArea" spellcheck="false">Select some text within this field.</textarea>
<button onclick="GetSelection ()">Get the current selection</button>
</body>
Get the value of the textarea using
var preview=document.getElementById("txtarea");
Get it's content using
var str=preview.value
The string to be matched
x="his is a t"
Using indexOf to get the character at which it starts
res=str.indexOf(x)
this returns 1
To find the character before it(check for res!=0)
str.charAt(res-1)
Returns "t"
For the last char
str.charAt(res+x.length)
Returns "e"