I was trying to create a system for indent the text (code).
I have a div with attribute contenteditable set to true where your code can be written.
This is my code:
function moveCaret(win, charCount) {
var sel, range;
if (win.getSelection) {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var textNode = sel.focusNode;
var newOffset = sel.focusOffset + charCount;
sel.collapse(textNode, Math.min(textNode.length, newOffset));
}
} else if ( (sel = win.document.selection) ) {
if (sel.type != "Control") {
range = sel.createRange();
range.move("character", charCount);
range.select();
}
}
}
$(document).ready(function(e) {
var edit = $('.edit');
edit.on('keydown',function(e) {
var e = e || window.event;
charCode = e.which || e.keyCode;
if(charCode == 9) {
moveCaret(window, 5);
return false;
}
});
});
If I click in a text and press the TAB button, the cursor moves forward five positions.
The problem is that this does not work if inside the div there is no text, I would that would shift the cursor without text.
How can I fix?
I tried in this way but the code is not interpreted, then put in the div exactly five times:
This is the code:
function insertTextAtCursor(text) {
var sel, range, html;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode( document.createTextNode(text) );
}
} else if (document.selection && document.selection.createRange) {
document.selection.createRange().text = text;
}
}
var edit = $('.edit');
edit.on('keydown',function(e) {
var e = e || window.event;
charCode = e.which || e.keyCode;
if(charCode == 9) {
var indent = ' ';
insertTextAtCursor(indent);
return false;
}
});
Related
I've a div with contenteditable and I'm trying to manipulate the array of this div to check if each letter written inside it, is the same as the letter on another text, based on the index.
The validation is done, I can check it, my problem is that, I need to change the letter color to red, if the letter don't validate. And I'm having trouble inserting a span with the class that I want.
Anyone have a sugestion?
Code follows:
timer.addEventListener('secondsUpdated', function (e) {
$('#countdown .values').html(timer.getTimeValues().toString(['minutes', 'seconds']));
if ($('.true-textarea').contents().get(0)) {
var textResult = $(".true-textarea").contents().get(0).nodeValue;
var textSize = textResult().length - 1;
}
$(".true-textarea").on("keypress", function(e) {
var c = String.fromCharCode(e.which);
e.preventDefault();
$(".true-textarea").html("");
if (c != textFake[textSize]) {
$(".true-textarea").addClass("red-border");
cls = "color-red";
} else {
cls = "color-purple";
$(".true-textarea").removeClass("red-border");
}
console.log('result', textResult);
console.log('size', textSize)
res += "<span class='"+cls+"'>"+c+"</span>";
cls = "";
$(".true-textarea").html(res);
});
});
So after hours trying to figure this out I came up if this code.
Used some examples that I saw in around the internet, fixed some problems, and it's working with the following code:
function focusAtEnd(el) {
el.focus();
if (typeof window.getSelection != "undefined"
&& typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange != "undefined") {
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
$('.true-textarea').unbind();
$('.true-textarea').focus();
timer.start({countdown: true, startValues: {seconds: 60}});
$('#countdown .values').html(timer.getTimeValues().toString(['minutes', 'seconds']));
var res = "", cls = "";
textResult.on("keypress", function(e){
textResult.find('br').remove();
var c = String.fromCharCode(e.which);
e.preventDefault();
res = textResult.html();
textSize = textResult.text().length;
if (c === textFake[textSize]) {
cls = "color-purple";
} else {
cls = "color-red";
}
res += "<span class='"+cls+"'>"+c+"</span>";
var del = false;
var lastFocused;
cls = "";
textResult.html(res);
focusAtEnd($(this).get(0));
})
When we double click on single word then browser select that word, when we triple click on single word then browser select that whole single line or whole paragraph.
Here, I want to prevent selecting all words on triple click and change to select only single word
I tried with this code
function clearSelection() {
if(document.selection && document.selection.empty) {
document.selection.empty();
} else if(window.getSelection) {
var sel = window.getSelection();
sel.removeAllRanges();
}
}
This works for you?jsfiddle
I edit your fiddle.
document.querySelector('div').addEventListener('click', function (evt) {
var o = this,
ot = this.textContent;
if (evt.detail >= 3) {
clearSelection();
}
});
function clearSelection() {
if(document.selection && document.selection.empty) {
document.selection.empty();
} else if(window.getSelection) {
var sel = window.getSelection();
sel.removeAllRanges();
}
}
Here's my fork: http://jsfiddle.net/kr2t0bpw/1/
It captures the selected word on the second click and saves the offsets. On third click it clears the selection and creates a new one with the previous saved offset.
var throttle = false;
var sel = window.getSelection();
var selStart = 0;
var selEnd = 0;
document.querySelector('div').addEventListener('click', function (evt) {
if (!throttle && evt.detail === 2) {
selStart = sel.anchorOffset;
selEnd = sel.focusOffset;
}
if (!throttle && evt.detail === 3) {
var node = this.firstChild;
sel.removeAllRanges();
throttle = true;
var range = document.createRange();
range.setStart(node, selStart);
range.setEnd(node, selEnd)
sel.removeAllRanges();
sel.addRange(range);
}
});
this is old and i cant add a comment, but for the answer of #kbariotis, you need to set throttle to false at the end or it will just work once.
var throttle = false;
var sel = window.getSelection();
var selStart = 0;
var selEnd = 0;
document.querySelector('div').addEventListener('click', function (evt) {
if (!throttle && evt.detail === 2) {
selStart = sel.anchorOffset;
selEnd = sel.focusOffset;
}
if (!throttle && evt.detail === 3) {
var node = this.firstChild;
sel.removeAllRanges();
throttle = true;
var range = document.createRange();
range.setStart(node, selStart);
range.setEnd(node, selEnd)
sel.removeAllRanges();
sel.addRange(range);
}
throttle = false;
});
window.addEventListener("keydown", function(e){
/*
keyCode: 8
keyIdentifier: "U+0008"
*/
if(e.keyCode === 16 && getSelectionText() != "") {
e.preventDefault();
replaceSelectedText(strcon(getSelectionText()));
}
});
function getSelectionText() {
var text = "";
if (window.getSelection) {
text = window.getSelection().toString();
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
return text;
}
function strcon(givenString) {
var b = '';
var a = givenString;
for (i = 0; i < a.length; i++) {
if (a.charCodeAt(i) >= 65 && a.charCodeAt(i) <= 90) {
b = b + a.charAt(i).toLowerCase();
}
else
b = b + a.charAt(i).toUpperCase();
}
return b;
}
function replaceSelectedText(replacementText) {
var sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(replacementText));
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
range.text = replacementText;
}
}
The code I have right now seems to change the appearance of the actual text instead of actually changing it. For example, when I'm on Facebook and I press the certain key, the text seems to have changed but then when I press enter, the text goes back to what it was before.
I believe the issue is with the function replaceSelectedText but I'm not sure how to fix it.
Any ideas?
No JQuery please.
https://jsfiddle.net/1rvz3696/
You have to get your textarea element to replace the value in it. This is how your replaceSelectedText function should look like,
function replaceSelectedText(text) {
var txtArea = document.getElementById('myTextArea');
if (txtArea.selectionStart != undefined) {
var startPos = txtArea.selectionStart;
var endPos = txtArea.selectionEnd;
selectedText = txtArea.value.substring(startPos, endPos);
txtArea.value = txtArea.value.slice(0, startPos) + text + txtArea.value.slice(endPos);
}
}
And here's the Fiddle.
Without specific id, you can replace txtArea to this.
var txtArea = document.activeElement;
And another Fiddle
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
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