How to remove end space when firefox double click selected text - javascript

Firefox double click selected text with next space. How to remove end space on screen with javascript.
Thank you so much.

Here's solution that does remove trailing whitespace character selected on doubleclick in Windows browsers, somewhat emulating Firefox about:config setting layout.word_select.eat_space_to_next_word being set to "false".
Does it "On screen, not in variable".
Tested in Chrome 60.0.3112.113, Firefox 55.0.3, IE8 and IE Edge(14).
It's still sometimes visible as selection shrinks from that extra space but nothing can be done about that.
Works for both arbitrary text and input/textareas(those need completely different approaches).
Uses jQuery
(function() {
var lastSelEvent = null;
var lastSelInputEvent = null;
var lastDblClickEvent = null;
var selchangeModTs = null;
$(document).on('selectstart selectionchange', function (e) //not input/textarea case
{
fixEventTS(e);
lastSelEvent = e;
if (( selchangeModTs != null) && (new Date().getTime() - selchangeModTs < 50)) //without this we get infinite loop in IE11+ as changing selection programmatically apparently generates event itself...
return;
handleSelEvent(e);
});
$(document).on('select', function (e) //input/textarea
{
fixEventTS(e);
lastSelInputEvent = e;
handleSelEvent(e);
});
$(document).on('dblclick',function(e)
{
fixEventTS(e);
lastDblClickEvent = e;
handleSelEvent(e);
});
function fixEventTS(e)
{
if (typeof e.timeStamp == 'undefined') //IE 8 no timestamps for events...
{
e.timeStamp = new Date().getTime();
}
}
function handleSelEvent(e)
{
if (lastDblClickEvent===null)
return;
if ( ((e.type==='selectstart') || (e.type==='selectionchange') || (e.type==='dblclick')) && (lastSelEvent != null) && (Math.abs(lastDblClickEvent.timeStamp - lastSelEvent.timeStamp) < 1000) ) // different browsers have different event order so take abs to be safe...
{
switch (lastSelEvent.type)
{
case 'selectstart':
setTimeout(handleSelChange,50); //IE8 etc fix, selectionchange is actually only change, not selection "creation"
break;
case 'selectionchange':
handleSelChange();
break;
}
}
if ( ((e.type==='select') || (e.type==='dblclick')) && (lastSelInputEvent != null) && (Math.abs(lastDblClickEvent.timeStamp - lastSelInputEvent.timeStamp) < 1000) ){
handleSel(lastSelInputEvent);
}
}
function handleSel(e)
{
//right whitespace
while (/\s$/.test(e.target.value.substr(e.target.selectionEnd - 1, 1)) && e.target.selectionEnd > 0) {
e.target.selectionEnd -= 1;
}
//left whitespace
while (/\s$/.test(e.target.value.substr(e.target.selectionStart, 1)) && e.target.selectionStart > 0) {
e.target.selectionStart += 1;
}
}
function handleSelChange() {
var sel = null;
if (typeof window.getSelection == 'function') // modern browsers
sel = window.getSelection();
else if (typeof document.getSelection == 'function')
sel = document.getSelection();
if (sel && !sel.isCollapsed) {
var range = sel.getRangeAt(0); //have to use range instead of more direct selection.expand/selection.modify as otherwise have to consider selection's direction in non-doubleclick cases
if (range.endOffset > 0 && (range.endContainer.nodeType===3) && (range.endContainer.textContent != ''/*otherwise rightside pictures get deleted*/))
{
//right whitespaces
while ( /[\s\S]+\s$/.test(range.endContainer.textContent.substr(0,range.endOffset)) ) { //have to use instead of range.toString() for IE11+ as it auto-trims whitespaces there and in selection.toString()
selchangeModTs = new Date().getTime();
range.setEnd(range.endContainer, range.endOffset - 1);
}
}
if ((range.startContainer.nodeType===3) && (range.startContainer.textContent != '') && (range.startOffset < range.startContainer.textContent.length)) {
//left whitespaces
while (/^\s[\s\S]+/.test(range.startContainer.textContent.substr(range.startOffset))) {
selchangeModTs = new Date().getTime();
range.setStart(range.startContainer, range.startOffset + 1);
}
}
selchangeModTs = new Date().getTime();
sel.removeAllRanges(); //IE11+ fix, in Firefox/Chrome changes to range apply to selection automatically
sel.addRange(range);
}
else if (typeof document.selection != 'undefined') //IE 10 and lower case
{
var range = document.selection.createRange();
if (range && range.text && range.text.toString()) {
while ((range.text != '') && /[\s\S]+\s$/.test(range.text.toString())) {
selchangeModTs = new Date().getTime();
range.moveEnd('character', -1);
range.select();
}
while ((range.text != '') && /^\s[\s\S]+/.test(range.text.toString())) {
selchangeModTs = new Date().getTime();
range.moveStart('character', 1);
range.select();
}
}
}
}
})();
If you don't need old IE support & limitint to doubleclick-only removal it can be greatly simplified and only needs to handle selectchange and select events to respective functions.(but then you actually can't select stuff with keyboard)
Edit:
Using Rangy lib(https://github.com/timdown/rangy) Core + TextRange for non-input elements selection trimming is way better than my basic attempt that only works if endContainer happens to be a text node. Basic action of selection trimming is
rangy.getSelection().trim()
Minimal includes are
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js
https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-core.min.js
https://cdnjs.cloudflare.com/ajax/libs/rangy/1.3.0/rangy-textrange.min.js
Rangy implementation additionally supporting triple-click whole-line selection trimming
(function() {
var lastSelEvent = null;
var lastSelInputEvent = null;
var lastDblClickEvent = null;
var selchangeModTs = null;
var tripleclickFixBound = false;
var selStartTimout = null;
$(document).on('selectstart selectionchange', function (e) //non-inputs
{
fixEventTS(e);
lastSelEvent = e;
if ( ( selchangeModTs != null) && (new Date().getTime() - selchangeModTs < 50) ) //ie11+ fix otherwise get self-loop with our selection changes generating this event
return;
handleSelEvent(e);
});
if ('onselect' in document.documentElement) {
$(document).on('select', function (e) //input/textarea
{
fixEventTS(e);
lastSelInputEvent = e;
handleSelEvent(e);
});
}
$(document).on('click',function(e){
if (typeof e.originalEvent.detail !== 'undefined')
{
multiclickHandlerfunction(e);
}
else
{
fixEventTS(e);
if (!tripleclickFixBound) {
$(document).on('dblclick', function (e) {
fixEventTS(e);
selchangeModTs = null;
lastDblClickEvent = e;
handleSelEvent(e);
});
tripleclickFixBound=true;
}
if ( (lastDblClickEvent != null) && (e.timeStamp - lastDblClickEvent.timeStamp < 300))
{
lastDblClickEvent = e;
selchangeModTs = null;
handleSelEvent(e);
}
}
});
function multiclickHandlerfunction(e) {
if (e.originalEvent.detail === 2 || e.originalEvent.detail === 3) {
fixEventTS(e);
selchangeModTs = null;
lastDblClickEvent = e;
handleSelEvent(e);
}
}
function fixEventTS(e)
{
if (typeof e.timeStamp == 'undefined') //IE 8
{
e.timeStamp = new Date().getTime();
}
}
function handleSelEvent(e)
{
if (lastDblClickEvent===null)
return;
if ( ((e.type==='selectstart') || (e.type==='selectionchange') || (e.type==='dblclick') || (e.type==='click')) && (lastSelEvent != null) && (Math.abs(lastDblClickEvent.timeStamp - lastSelEvent.timeStamp) < 1000) ) // different order of events in different browsers...
{
switch (lastSelEvent.type)
{
case 'selectstart':
case 'selectionchange':
clearTimeout(selStartTimout);
selStartTimout = setTimeout(handleSelChange,(/msie\s|trident\/|edge\//i.test(window.navigator.userAgent)?150:0));
break;
}
}
if ( ((e.type==='select') || (e.type==='dblclick') || (e.type==='click')) && (lastSelInputEvent != null) && (Math.abs(lastDblClickEvent.timeStamp - lastSelInputEvent.timeStamp) < 1000) )
{
handleSel(lastSelInputEvent);
}
}
function handleSel(e)
{
if (typeof(e.target.selectionEnd) != 'undefined') {
//left whitespace
while (/\s$/.test(e.target.value.substr(e.target.selectionEnd - 1, 1)) && e.target.selectionEnd > 0) {
e.target.selectionEnd -= 1;
}
//right whitespace
while (/^s/.test(e.target.value.substr(e.target.selectionStart - 1, 1)) && e.target.selectionStart > 0) {
e.target.selectionStart += 1;
}
}
}
function handleSelChange() {
var sel = rangy.getSelection();
if (sel && !sel.isCollapsed) {
selchangeModTs = new Date().getTime();
sel.trim();
}
else if (typeof document.selection != 'undefined') //IE 10- input/textArea case
{
var range = document.selection.createRange();
if (range && range.text && range.text.toString()) {
while ((range.text != '') && /[\s\S]+\s$/.test(range.text.toString())) {
selchangeModTs = new Date().getTime();
range.moveEnd('character', -1);
range.select();
}
while ((range.text != '') && /^\s[\s\S]+/.test(range.text.toString())) {
selchangeModTs = new Date().getTime();
range.moveStart('character', 1);
range.select();
}
}
}
}
})($,window);
CodePen demo

One thing I tried was to use range.setEnd() using the selection focus and anchor node and offset but -1.
Please excuse the lack of checks for browser compatibility.
var selection = window.getSelection();
var range = selection.getRangeAt(0);
var selected = range.toString();
if (!selection.isCollapsed) {
if (/\s+$/.test(selected)) {
if (selection.focusOffset > selection.anchorOffset) {
range.setEnd(selection.focusNode, selection.focusOffset - 1);
} else {
range.setEnd(selection.anchorNode, selection.anchorOffset - 1);
}
}
}
I am unsure if it works in IE/Edge but Chrome seems happy with it.

You must be using a Windows machine. And so you are seeing this issue. It doesnt exists on ubuntu machine.
Also if you need to remove the trailing space with Js
Try something like this.
document.ondblclick = function () {
var sel = (document.selection && document.selection.createRange().text) ||
(window.getSelection && window.getSelection().toString());
var selection = window.getSelection();
$('#new_word').text(sel.trim());
};
Check this jsFiddle http://jsfiddle.net/shinde87sagar/FvkwV/

Thanks to #James Bellaby
It worked for me using Chrome 83, this is the code for removing leading and trailing spaces from the selected range.
function fix_selection(range) {
var selection = window.getSelection();
var selected = range.toString();
range = selection.getRangeAt(0);
let start = selection.anchorOffset;
let end = selection.focusOffset;
if (!selection.isCollapsed) {
if (/\s+$/.test(selected)) { // Removes leading spaces
if (start > end) {
range.setEnd(selection.focusNode, --start);
} else {
range.setEnd(selection.anchorNode, --end);
}
}
if (/^\s+/.test(selected)) { // Removes trailing spaces
if (start > end) {
range.setStart(selection.anchorNode, ++end);
} else {
range.setStart(selection.focusNode, ++start);
}
}
}
return range
}

just put the text inside a label and the problem will be solved

Javascript has a trim() method which trims tailing (end) and leading spaces.
e.g.
var str = " Hello World! ";
str = str.trim(); // returns "Hello World"
Ref: http://www.w3schools.com/jsref/jsref_trim_string.asp

Related

Javascript keydown event doesn´t give me the char # in EDGE

I'm working in a mentioning directive, basically when the user is typing in the input field ( a div with contentEditable=true in this case ), is gonna display a list of user for then insert the name of the user in a specific format, now the list is gonna displayed after the user press #, for chrome and firefox work just great but for EDGE and IE ( unfortunately i need to support ) doesn't work because in this case the # apparently doesn't exist.
now for the key press I'm using the #HostListener('keydown', ['$event'])
HostListener
#HostListener('keydown', ['$event']) keyHandler(event: any, nativeElement: HTMLInputElement = this._element.nativeElement) {
let val: string = getValue(nativeElement);
let pos = getCaretPosition(nativeElement, this.iframe);
let charPressed = this.keyCodeSpecified ? event.keyCode : event.key;
if (!charPressed) {
let charCode = event.which || event.keyCode;
if (!event.shiftKey && (charCode >= 65 && charCode <= 90)) {
charPressed = String.fromCharCode(charCode + 32);
} else if (event.shiftKey && charCode === KEY_2) {
charPressed = this.triggerChar;
} else {
charPressed = String.fromCharCode(event.which || event.keyCode);
}
}
if (event.keyCode == KEY_ENTER && event.wasClick && pos < this.startPos) {
// put caret back in position prior to contenteditable menu click
pos = this.startNode.length;
setCaretPosition(this.startNode, pos, this.iframe);
}
// console.log('=== keyHandler', this.startPos, pos, val, charPressed, event);
this.triggerList(event, charPressed, nativeElement, val, pos);
}
Now as you can see I'm using event.keycode and event.key to get the key from the event keydown, I pass does values to the method this.triggerList()
that basically is gonna display the list of mentions options if and only if the user press # that is the trigger char ( this.triggerChar ).
TriggerList Method
private triggerList(event, charPressed, nativeElement, val, pos): any {
if (charPressed == this.triggerChar) {
this.startPos = pos;
this.startNode = (this.iframe ? this.iframe.contentWindow.getSelection() : window.getSelection()).anchorNode;
// console.log('=== HERE CHAR', this.startNode, this.startPos);
// check if mentioning is allowed based on the text before the mention start char
if (!this.configService.appConfig.platform.EDGE) {
let position = this.getHtmlCaretPosition(nativeElement);
const charBefore = val[position - 1];
if (charBefore == undefined || charBefore.trim() == '' || charBefore == ':') {
this.log.trace('Start mentioning');
this.stopSearch = false;
this.searchString = null;
this.showSearchList(nativeElement);
this.updateSearchList();
}
} else {
this.stopSearch = false;
this.searchString = null;
this.showSearchList(nativeElement);
this.updateSearchList();
}
} else if (this.startPos >= 0 && !this.stopSearch) {
if (pos <= this.startPos) {
this.searchList.hidden = true;
}
// ignore shift when pressed alone, but not when used with another key
else if (event.keyCode !== KEY_SHIFT && !event.metaKey && !event.altKey && !event.ctrlKey && pos > this.startPos) {
if (event.keyCode === KEY_SPACE) {
this.startPos = -1;
} else if (event.keyCode === KEY_BACKSPACE && pos > 0) {
pos--;
if (pos == 0) {
this.stopSearch = true;
}
this.searchList.hidden = this.stopSearch;
} else if (!this.searchList.hidden) {
if (event.keyCode === KEY_TAB || event.keyCode === KEY_ENTER) {
this.stopEvent(event);
this.searchList.hidden = true;
// value is inserted without a trailing space for consistency
// between element types (div and iframe do not preserve the space)
let textValue = this.mentionSelect(this.searchList.activeItem);
insertValue(nativeElement, this.startPos, pos, textValue, this.iframe);
this.emitSelection(nativeElement);
if (this.htmlStyling) {
let quillElement = document.querySelector('.ql-editor');
let innerHtml = quillElement.innerHTML;
let strings = innerHtml.split(textValue);
if (strings.length === 2) {
innerHtml = `${strings[0]}<span id="mention${textValue.substring(1)}${strings.length - 1}" style="color: #0065FF; background: rgba(0,101,255,.2)">${textValue}</span> ${strings[1]}`;
} else {
let openSpan = false;
innerHtml = strings.reduce((total, current, currentIndex) => {
if (current.indexOf(`mention${textValue.substring(1)}`) > 0) {
return `${total}${openSpan ? '</span> ' : ''}${current}${textValue}`;
} else if (openSpan) {
return `${total}</span> ${currentIndex < strings.length - 1 ? current + textValue : current}`;
} else {
openSpan = true;
return `${total}${current}<span id="mention${textValue.substring(1)}${strings.length - 1}" style="color: #0065FF; background: rgba(0,101,255,.2)">${textValue}`;
}
}, '');
}
quillElement.innerHTML = innerHtml;
// tslint:disable-next-line:no-angle-bracket-type-assertion
let mentionElement: HTMLInputElement = document.getElementById(`mention${textValue.substring(1)}${strings.length - 1}`) as HTMLInputElement;
// tslint:disable-next-line:no-angle-bracket-type-assertion
setCaretPosition(mentionElement.nextSibling as HTMLInputElement, 1);
}
// fire input event so angular bindings are updated
if ('createEvent' in document) {
let evt = document.createEvent('HTMLEvents');
evt.initEvent('input', false, true);
nativeElement.dispatchEvent(evt);
}
this.startPos = -1;
return false;
} else if (event.keyCode === KEY_ESCAPE) {
this.stopEvent(event);
this.searchList.hidden = true;
this.stopSearch = true;
return false;
} else if (event.keyCode === KEY_DOWN) {
this.stopEvent(event);
this.searchList.activateNextItem();
return false;
} else if (event.keyCode === KEY_UP) {
this.stopEvent(event);
this.searchList.activatePreviousItem();
return false;
}
}
if (event.keyCode === KEY_LEFT || event.keyCode === KEY_RIGHT) {
this.stopEvent(event);
return false;
} else {
let mention = val.substring(this.startPos + 1, pos);
if (event.keyCode !== KEY_BACKSPACE) {
mention += charPressed;
}
this.searchString = mention;
this.searchTerm.emit(this.searchString);
this.updateSearchList();
}
}
}
}
now the issue here is that if the user to insert the char # need to use the combination of ALT + Q EDGE only detect ALT and then Q, compare to firefox and chrome that with the combination ALT + Q detect # for this reason the list is not display because the char never match.
First i replace the event keydown for keypress, then where i save the char in the variable charPress i create a condition to check if the browser is EDGE or IE, and get char code using event.charCode and convert it in to string using String.fromCharCode(event.charCode) at the end looks like this.
#HostListener('keypress', ['$event']) keyHandler(event: any, nativeElement: HTMLInputElement = this._element.nativeElement) {
let val: string = getValue(nativeElement);
let pos = getCaretPosition(nativeElement, this.iframe);
let charPressed = this.keyCodeSpecified ? event.keyCode : event.key;
if (this.configService.appConfig.platform.EDGE) {
charPressed = String.fromCharCode(event.charCode);
}
......

Can I change the HTML of the selected text?

Can we change the HTML or its attributes of the selected part of the web page using javascript?
For example,
There is a random web page:(A part of it is shown)
with HTML as
...<p>Sample paragraph</p>..
It is possible to get the HTML of the selected text which has already been answered here.
But, is it possible for me change the html of the selected text? Like, add a class or id attribute to the paragraph tag.
The jQuery wrapselection plugin sounds like what you're looking for http://archive.plugins.jquery.com/project/wrapSelection . What you're asking for is not directly possible though, the plugin works by adding in extra nodes around the surrounding text which may not be 100% reliable (particularly in IE6-8)
Here is a working example without a plugin.
http://jsfiddle.net/9HTPw/2/
function getSel() {
var sel = null;
if (
typeof document.selection != "undefined" && document.selection
&& document.selection.type == "Text") {
sel = document.selection;
} else if (
window.getSelection && window.getSelection().rangeCount > 0) {
sel = window.getSelection();
}
return sel;
}
var getSelectionStart = (function () {
function createRangeFromSel(sel) {
var rng = null;
if (sel.createRange) {
rng = sel.createRange();
} else if (sel.getRangeAt) {
rng = sel.getRangeAt(0);
if (rng.toString() == "") rng = null;
}
return rng;
}
return function (el) {
var sel = getSel(),
rng, r2, i = -1;
if (sel) {
rng = createRangeFromSel(sel);
if (rng) {
if (rng.parentElement) {
if (rng.parentElement() == el) {
r2 = document.body.createTextRange();
r2.moveToElementText(el);
r2.collapse(true);
r2.setEndPoint("EndToStart", rng);
i = r2.text.length;
}
} else {
if ( rng.startContainer && rng.endContainer
&& rng.startContainer == rng.endContainer
&& rng.startContainer == rng.commonAncestorContainer
&& rng.commonAncestorContainer.parentNode == el) {
//make sure your DIV does not have any inner element,
//otherwise more code is required in order to filter
//text nodes and count chars
i = rng.startOffset;
}
}
}
}
return i;
};
})();
$("#content").on('mousedown', function () {
$("#content").html(content)
});
var content = $("#content").html();
$("#content").on('mouseup', function () {
var start = getSelectionStart(this);
var len = getSel().toString().length;
$(this).html(
content.substr(0, start) +
'<span style=\"background-color: yellow;\">' +
content.substr(start, len) +
'</span>' +
content.substr(Number(start) + Number(len), content.toString().length));
});
References:
http://bytes.com/topic/javascript/answers/153164-return-selectionstart-div

Insert tags around selected text

I've had a look around but the other answers don't really help me out.
I want to create a small WYSIWYG editor, there only needs to be the options to add links and add lists. My question is how do I append tags around selected text in a textarea when one of the links/button (e.g. "Add Link") is clicked?
I've written a jQuery plug-in that does this (and which I really must document), which you can download from http://code.google.com/p/rangyinputs/downloads/list.
The following will work in all major browsers and surrounds the selected text, and restores the selection to contain the previously selected text:
var url = "https://stackoverflow.com/";
$("#yourTextAreaId").surroundSelectedText('', '');
For a solution without jQuery, you could use the getInputSelection() and setInputSelection() functions from this answer for compatibility with IE <= 8 as follows:
function getInputSelection(el) {
var start = 0, end = 0, normalizedValue, range,
textInputRange, len, endRange;
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
start = el.selectionStart;
end = el.selectionEnd;
} else {
range = document.selection.createRange();
if (range && range.parentElement() == el) {
len = el.value.length;
normalizedValue = el.value.replace(/\r\n/g, "\n");
// Create a working TextRange that lives only in the input
textInputRange = el.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());
// Check if the start and end of the selection are at the very end
// of the input, since moveStart/moveEnd doesn't return what we want
// in those cases
endRange = el.createTextRange();
endRange.collapse(false);
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split("\n").length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
}
}
}
return {
start: start,
end: end
};
}
function offsetToRangeCharacterMove(el, offset) {
return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
}
function setInputSelection(el, startOffset, endOffset) {
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
el.selectionStart = startOffset;
el.selectionEnd = endOffset;
} else {
var range = el.createTextRange();
var startCharMove = offsetToRangeCharacterMove(el, startOffset);
range.collapse(true);
if (startOffset == endOffset) {
range.move("character", startCharMove);
} else {
range.moveEnd("character", offsetToRangeCharacterMove(el, endOffset));
range.moveStart("character", startCharMove);
}
range.select();
}
}
function surroundSelectedText(el, before, after) {
var val = el.value;
var sel = getInputSelection(el);
el.value = val.slice(0, sel.start) +
before +
val.slice(sel.start, sel.end) +
after +
val.slice(sel.end);
var newCaretPosition = sel.end + before.length + after.length;
setInputSelection(el, newCaretPosition, newCaretPosition);
}
function surroundWithLink() {
surroundSelectedText(
document.getElementById("ta"),
'<a href="https://stackoverflow.com/">',
'</a>'
);
}
<input type="button" onmousedown="surroundWithLink(); return false" value="Surround">
<br>
<textarea id="ta" rows="5" cols="50">Select some text in here and press the button</textarea>
If you don't need support for IE <= 8, you can replace the getInputSelection() and setInputSelection() functions with the following:
function getInputSelection(el) {
return {
start: el.selectionStart,
end: el.selectionEnd
};
}
function setInputSelection(el, start, end) {
el.setSelectionRange(start, end);
}
You can use a function like the following:
function addUrl(url) {
var textArea = $('#myTextArea');
var start = textArea[0].selectionStart;
var end = textArea[0].selectionEnd;
var replacement = '' + textArea.val().substring(start, end) + '';
textArea.val(textArea.val().substring(0, start) + replacement + textArea.val().substring(end, textArea.val().length));
}

How do I add text to a textarea at the cursor location using javascript [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Inserting a text where cursor is using Javascript/jquery
I'm building a simple templating system here. Basically I've set up buttons that add preset variables into a textarea.
The thing is that I want to be able to add text in my textarea at the position where my cursor is. Just like when you click on the URL link in the editor window for posts here at stackoverflow the link is added at the position where your cursor is.
How can I do that? And can it be done using prototype JS?
It's a bit tricky in IE, which does an exceptionally bad job of this kind of thing. The following will insert text at the end of the current selection in a textarea or text input (or at the caret if the no text is selected) and then position the cursor after the text:
Demo: http://jsfiddle.net/RqTvT/1/
Code:
function getInputSelection(el) {
var start = 0, end = 0, normalizedValue, range,
textInputRange, len, endRange;
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
start = el.selectionStart;
end = el.selectionEnd;
} else {
range = document.selection.createRange();
if (range && range.parentElement() == el) {
len = el.value.length;
normalizedValue = el.value.replace(/\r\n/g, "\n");
// Create a working TextRange that lives only in the input
textInputRange = el.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());
// Check if the start and end of the selection are at the very end
// of the input, since moveStart/moveEnd doesn't return what we want
// in those cases
endRange = el.createTextRange();
endRange.collapse(false);
if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split("\n").length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
}
}
}
return {
start: start,
end: end
};
}
function offsetToRangeCharacterMove(el, offset) {
return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
}
function setSelection(el, start, end) {
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
el.selectionStart = start;
el.selectionEnd = end;
} else if (typeof el.createTextRange != "undefined") {
var range = el.createTextRange();
var startCharMove = offsetToRangeCharacterMove(el, start);
range.collapse(true);
if (start == end) {
range.move("character", startCharMove);
} else {
range.moveEnd("character", offsetToRangeCharacterMove(el, end));
range.moveStart("character", startCharMove);
}
range.select();
}
}
function insertTextAtCaret(el, text) {
var pos = getInputSelection(el).end;
var newPos = pos + text.length;
var val = el.value;
el.value = val.slice(0, pos) + text + val.slice(pos);
setSelection(el, newPos, newPos);
}
var textarea = document.getElementById("your_textarea");
insertTextAtCaret(textarea, "[INSERTED]");

How to delete an HTML element inside a div with attribute contentEditable?

have this html:
<div id="editable" contentEditable="true" >
<span contentEditable="false" >Text to delete</span>
</div>
need that the span (and all text inside) is removed with a single backspace, is it possible?
This turned out to be more complicated than I thought. Or I've made it more complicated than it needs to be. Anyway, this should work in all the big browsers:
function getLastTextNodeIn(node) {
while (node) {
if (node.nodeType == 3) {
return node;
} else {
node = node.lastChild;
}
}
}
function isRangeAfterNode(range, node) {
var nodeRange, lastTextNode;
if (range.compareBoundaryPoints) {
nodeRange = document.createRange();
lastTextNode = getLastTextNodeIn(node);
nodeRange.selectNodeContents(lastTextNode);
nodeRange.collapse(false);
return range.compareBoundaryPoints(range.START_TO_END, nodeRange) > -1;
} else if (range.compareEndPoints) {
if (node.nodeType == 1) {
nodeRange = document.body.createTextRange();
nodeRange.moveToElementText(node);
nodeRange.collapse(false);
return range.compareEndPoints("StartToEnd", nodeRange) > -1;
} else {
return false;
}
}
}
document.getElementById("editable").onkeydown = function(evt) {
var sel, range, node, nodeToDelete, nextNode, nodeRange;
evt = evt || window.event;
if (evt.keyCode == 8) {
// Get the DOM node containing the start of the selection
if (window.getSelection && window.getSelection().getRangeAt) {
range = window.getSelection().getRangeAt(0);
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
}
if (range) {
node = this.lastChild;
while (node) {
if ( isRangeAfterNode(range, node) ) {
nodeToDelete = node;
break;
} else {
node = node.previousSibling;
}
}
if (nodeToDelete) {
this.removeChild(nodeToDelete);
}
}
return false;
}
};
Because you want to delete the whole element, it's better to make it contenteditable="false" so that browser won't let the contents of an element to be deleted.
Then you can use this attribute for tests in event handler as follows:
$('#editable').on('keydown', function (event) {
if (window.getSelection && event.which == 8) { // backspace
// fix backspace bug in FF
// https://bugzilla.mozilla.org/show_bug.cgi?id=685445
var selection = window.getSelection();
if (!selection.isCollapsed || !selection.rangeCount) {
return;
}
var curRange = selection.getRangeAt(selection.rangeCount - 1);
if (curRange.commonAncestorContainer.nodeType == 3 && curRange.startOffset > 0) {
// we are in child selection. The characters of the text node is being deleted
return;
}
var range = document.createRange();
if (selection.anchorNode != this) {
// selection is in character mode. expand it to the whole editable field
range.selectNodeContents(this);
range.setEndBefore(selection.anchorNode);
} else if (selection.anchorOffset > 0) {
range.setEnd(this, selection.anchorOffset);
} else {
// reached the beginning of editable field
return;
}
range.setStart(this, range.endOffset - 1);
var previousNode = range.cloneContents().lastChild;
if (previousNode && previousNode.contentEditable == 'false') {
// this is some rich content, e.g. smile. We should help the user to delete it
range.deleteContents();
event.preventDefault();
}
}
});
demo on jsfiddle

Categories

Resources