Disable tab key blur in focused contentEditable - javascript

I'm fully aware that this question looks like a duplicate, but it isn't. Please read.
I have a div tag with the attribute contentEditable="true". I'm trying to make it so I can use the tab key inside the div without it moving focuses. Here's some code I have that doesn't fully fix the problem:
var tab = function(id){
if(event.keyCode === 9){
event.preventDefault();
document.getElementById(id).innerHTML += " ";
setCaretPos(id);
}
}
var setCaretPos = function(id){
var node = document.getElementById(id);
node.focus();
var textNode = node.firstChild;
var caret = textNode.length;
var range = document.createRange();
range.setStart(textNode, caret);
range.setEnd(textNode, caret);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
That is basically just adding four spaces to the end of the value of the text inside the div, then moving the caret to the end. That becomes a problem when I use tabs on other lines, because it will just add the tab to the end (If I'm not already on the last line).
So, I want to insert the four spaces where the caret is. This is where the trouble comes. I'll show the code, but I'm not sure what I'm doing wrong. I want it to add the four spaces to the text before the caret, then add all the text after the caret. Here's the code:
var insertTabAtCaret = function(id){
if(event.keyCode === 9){
event.preventDefault();
var box = document.getElementById(id);
if(box.selectionStart || box.selectionStart == "0"){
var startPos = box.selectionStart;
var endPos = box.selectionEnd;
box.innerHTML.substring(0, startPos) + " " + box.innerHTML.substring(endPos, box.innerHTML.length);
}
}
}
Please help! Let me know how I can achieve tabbing in a contentEditable div using the method described above! (I would prefer it in normal JavaScript, but jQuery is permissible. Please, don't tell me about libraries, plugins, extensions, etc. I don't want to use any of those.)

document.querySelector("div").addEventListener("keydown",insertTabAtCaret);
function insertTabAtCaret(event){
if(event.keyCode === 9){
event.preventDefault();
var range = window.getSelection().getRangeAt(0);
var tabNode = document.createTextNode("\u00a0\u00a0\u00a0\u00a0");
range.insertNode(tabNode);
range.setStartAfter(tabNode);
range.setEndAfter(tabNode);
}
}
div#editor{
height: 200px;
width:80%;
border: solid
}
<div contenteditable id="editor">
some text
</div>

Related

JS - focus to a b tag

I want to set the focus to a b tag (<b>[focus should be here]</b>).
My expected result was that the b tag into the div has the focus and if I would write, that the characters are bold.
Is this impossible? How can I do this?
Idea was from here:
focus an element created on the fly
HTML:
<div id="editor" class="editor" contentEditable="true">Hallo</div>
JS onDomready:
var input = document.createElement("b"); //create it
document.getElementById('editor').appendChild(input); //append it
input.focus(); //focus it
My Solution thanks to A1rPun:
add: 'input.tabIndex = 1;' and listen for the follow keys.
HTML:
<h1>You can start typing</h1>
<div id="editor" class="editor" contentEditable="true">Hallo</div>
JS
window.onload = function() {
var input = document.createElement("b"); //create it
document.getElementById('editor').appendChild(input); //append it
input.tabIndex = 1;
input.focus();
var addKeyEvent = function(e) {
//console.log('add Key');
var key = e.which || e.keyCode;
this.innerHTML += String.fromCharCode(key);
};
var addLeaveEvent = function(e) {
//console.log('blur');
// remove the 'addKeyEvent' handler
e.target.removeEventListener('keydown', addKeyEvent);
// remove this handler
e.target.removeEventListener(e.type, arguments.callee);
};
input.addEventListener('keypress', addKeyEvent);
input.addEventListener('blur', addLeaveEvent);
};
You can add a tabIndex property to allow the element to be focused.
input.tabIndex = 1;
input.focus();//now you can set the focus
jsfiddle
Edit:
I think the best way to solve your problem is to style an input tag with font-weight: bold.
I had to cheat a little by adding an empty space inside the bold area because I couldn't get it to work on the empty element.
This works by moving the selector inside the last element in the contentEditable since the bold element is the last one added.
It can be edited to work on putting the focus on any element.
http://jsfiddle.net/dnzajx21/3/
function appendB(){
var bold = document.createElement("b");
bold.innerHTML = " ";
//create it
document.getElementById('editor').appendChild(bold); //append it
setFocus();
}
function setFocus() {
var el = document.getElementById("editor");
var range = document.createRange();
var sel = window.getSelection();
range.setStartAfter(el.lastChild);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
el.focus();
}
The SetFocus function I took was from this question: How to set caret(cursor) position in contenteditable element (div)?

How do you keep the tab level in a textarea when pressing enter?

When the user presses enter I want the cursor to move to a new line, but if they are currently indented by two tabs, then the cursor should stay indented two tabs.
I have already implemented the ignore tab event to stop the focus moving within the page, so I'm now just looking for the logic to keep the tab level on new line.
if(e.keyCode === 13){
//Logic here
}
http://jsfiddle.net/DVKbn/
$("textarea").keydown(function(e){
if(e.keyCode == 13){
// assuming 'this' is textarea
var cursorPos = this.selectionStart;
var curentLine = this.value.substr(0, this.selectionStart).split("\n").pop();
var indent = curentLine.match(/^\s*/)[0];
var value = this.value;
var textBefore = value.substring(0, cursorPos );
var textAfter = value.substring( cursorPos, value.length );
e.preventDefault(); // avoid creating a new line since we do it ourself
this.value = textBefore + "\n" + indent + textAfter;
setCaretPosition(this, cursorPos + indent.length + 1); // +1 is for the \n
}
});
function setCaretPosition(ctrl, pos)
{
if(ctrl.setSelectionRange)
{
ctrl.focus();
ctrl.setSelectionRange(pos,pos);
}
else if (ctrl.createTextRange) {
var range = ctrl.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();
}
}
I improved the answer by Endless by using execCommand 'insertText' instead of modifying textarea.value.
Advantages:
Maintains undo-redo history of the <textarea>.
Maintains native behavior where any selected text is deleted.
Does not lag when value is 4000+ characters.
Shorter, simpler code.
Disadvantages:
Currently not supported by Firefox. (Use solution by Endless as fallback.)
$('textarea').on('keydown', function(e) {
if (e.which == 13) { // [ENTER] key
event.preventDefault() // We will add newline ourselves.
var start = this.selectionStart;
var currentLine = this.value.slice(0, start).split('\n').pop();
var newlineIndent = '\n' + currentLine.match(/^\s*/)[0];
if (!document.execCommand('insertText', false, newlineIndent)) {
// Add fallback for Firefox browser:
// Modify this.value and update cursor position as per solution by Endless.
}
}
});
<textarea style="width:99%;height:99px;"> I am indented by 8 spaces.
I am indented by a tab.</textarea>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Must say solutions based on one key press are obscure because people also like pasting text. Use input event instead. You can make it in jQuery like so:
$('textarea').on('input', function(e) {
var el = $(this);
var cur = $(this).prop('selectionStart'); // retrieve current caret position before setting value
var text = $(this).val();
var newText = text.replace(/^(.+)\t+/mg, '$1'); // remove intermediate tabs
newText = newText.replace(/^([^\t]*)$/mg, '\t\t$1'); // add two tabs in the beginning of each line
if (newText != text) { // If text changed...
$(this).val(newText); // finally set value
// and reset caret position shifted right by one symbol
$(this).prop('selectionStart', cur + 1);
$(this).prop('selectionEnd', cur + 1);
}
});
<textarea></textarea>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
By the way I'm too lazy to explain how to watch tab count needed for user, this one just inserts two tabs on every line.

Wrap certain word with <span> using jquery

I have the following div:
<div id="query" style="width:500px; height:200px;border:1px solid black"
spellcheck="false" contenteditable="true"></div>​
where Clients can write their SQL queries. What I was trying to do is wrap words the client enters right after hitting Space with a span and give this span a certain class according to the word typed:
example
If the client types select i need to wrap this select word like this in the div:
<span class='select'> SELECT </span> <span> emp_name </span>
CSS
.select{color:blue ;text-transform:uppercase;}
It is something very similar to what jsFiddle does. How can i achieve this?
Here is what i have tried so far : jsFiddle
$(function(){
$('div').focus() ;
$('div').keyup(function(e){
//console.log(e.keyCode) ;
if(e.keyCode == 32){
var txt = $('div').text() ;
var x = 'SELECT' ;
$('div:contains("'+x+'")').wrap("<span style='color:blue ;
text-transform:uppercase;'>") ;
if(txt == 'SELECT'){
console.log('found') ; // why This Doesn't do any thing ?
}
}
});
});
I did a proof of concept with some modifications from what you originally had. See below,
DEMO: http://jsfiddle.net/cgy69/
$(function() {
$('div').focus();
var x = ['SELECT', 'WHERE', 'FROM'];
$('div').keyup(function(e) {
//console.log(e.keyCode) ;
if (e.keyCode == 32) {
//using .text() remove prev span inserts
var text = $.trim($(this).text()).split(' ');
$.each(text, function(i, v) {
$.each(x, function(j, xv) {
if (v.toUpperCase() === xv) {
text[i] = '<span style="color: blue; text-transform: uppercase;">' + v + '</span>';
}
});
});
$(this).html(text.join(' ') + ' ');
setEndOfContenteditable(this);
}
});
function setEndOfContenteditable(contentEditableElement) {
var range, selection;
if (document.createRange) //Firefox, Chrome, Opera, Safari, IE 9+
{
range = document.createRange(); //Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement); //Select the entire contents of the element with the range
range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection(); //get the selection object (allows you to change selection)
selection.removeAllRanges(); //remove any selections already made
selection.addRange(range); //make the range you have just created the visible selection
}
else if (document.selection) //IE 8 and lower
{
range = document.body.createTextRange(); //Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement); //Select the entire contents of the element with the range
range.collapse(false); //collapse the range to the end point. false means collapse to end rather than the start
range.select(); //Select the range (make it the visible selection
}
}
});
You going to extend this further to handle
Backspace
HTML contents from previous inserts
Cursor position Partially done, editing in the middle would still mess up the caret.
and more..
Starting with a contenteditable element we can replace the markup as we need by operating directly on its innerHtml:
$('#query-container').on('keyup', function(e){
var $this = $(this);
//(?!\<\/b\>) negative lookahead is used so that anything already wrapped
//into a markup tag would not get wrapped again
$this.html($this.html().replace(/(SELECT|UPDATE|DELETE)(?!\<\/b\>)/gi, '<b>$1</b>'));
setEndOfContenteditable(this);
});
IMO this is a more readable option. Add the rangeselect method from the previous answer and we have a working fiddle

How to mimic JS ranges/selections with divs as backgrounds?

I need to mimic ranges/selections (those that highlight content on a website, and when you press for ex CTRL + C you copy the content) with divs as backgrounds. Chances are that the "highlighting divs" will be position:absolute;
<div id="highlight">
<!-- The highlightor divs would go here -->
</div>
<div id="edit">
<!-- The divs to be highlighted (that have text) would go here -->
</div>
Edit: Functionalities such as copying are essential.
PS: If you're curious about "why", refer to this question.
I created a new question because I felt the old one was pretty much answered, and this one differed to much from that one.
Here's the concept, with some code to get you started. Every time text is selected in the page, append that text to a hidden textarea on the page and then select the textarea. Then, wrap the original selection in a span to make it look selected. This way, you have the appearance of a selection, and since the hidden textarea is actually selected, when the user hits "ctrl+c" they are copying the text from the textarea.
To get the full functionality you are looking for, you'll probably want to extend this. Sniff keys for "ctrl+a" (for select all). I don't think you'll be able to override right-click behavior... at least not easily or elegantly. But this much is at least a proof of concept for you to run with.
window.onload = init;
function init()
{
document.getElementById("hidden").value = "";
document.body.ondblclick = interceptSelection;
document.body.onmouseup = interceptSelection;
}
function interceptSelection(e)
{
e = e || window.event;
var target = e.target || e.srcElement;
var hidden = document.getElementById("hidden");
if (target == hidden) return;
var range, text;
if (window.getSelection)
{
var sel = getSelection();
if (sel.rangeCount == 0) return;
range = getSelection().getRangeAt(0);
}
else if (document.selection && document.selection.createRange)
{
range = document.selection.createRange();
}
text = "text" in range ? range.text : range.toString();
if (text)
{
if (range.surroundContents)
{
var span = document.createElement("span");
span.className = "selection";
range.surroundContents(span);
}
else if (range.pasteHTML)
{
range.pasteHTML("<span class=\"selection\">" + text + "</span>")
}
hidden.value += text;
}
hidden.select();
}
Here's the css I used in my test to hide the textarea and style the selected text:
#hidden
{
position: fixed;
top: -100%;
}
.selection
{
background-color: Highlight;
color: HighlightText;
}

How do I insert a character at the caret with javascript?

I want to insert some special characters at the caret inside textboxes using javascript on a button. How can this be done?
The script needs to find the active textbox and insert the character at the caret in that textbox. The script also needs to work in IE and Firefox.
EDIT: It is also ok to insert the character "last" in the previously active textbox.
I think Jason Cohen is incorrect. The caret position is preserved when focus is lost.
[Edit: Added code for FireFox that I didn't have originally.]
[Edit: Added code to determine the most recent active text box.]
First, you can use each text box's onBlur event to set a variable to "this" so you always know the most recent active text box.
Then, there's an IE way to get the cursor position that also works in Opera, and an easier way in Firefox.
In IE the basic concept is to use the document.selection object and put some text into the selection. Then, using indexOf, you can get the position of the text you added.
In FireFox, there's a method called selectionStart that will give you the cursor position.
Once you have the cursor position, you overwrite the whole text.value with
text before the cursor position + the text you want to insert + the text after the cursor position
Here is an example with separate links for IE and FireFox. You can use you favorite browser detection method to figure out which code to run.
<html><head></head><body>
<script language="JavaScript">
<!--
var lasttext;
function doinsert_ie() {
var oldtext = lasttext.value;
var marker = "##MARKER##";
lasttext.focus();
var sel = document.selection.createRange();
sel.text = marker;
var tmptext = lasttext.value;
var curpos = tmptext.indexOf(marker);
pretext = oldtext.substring(0,curpos);
posttest = oldtext.substring(curpos,oldtext.length);
lasttext.value = pretext + "|" + posttest;
}
function doinsert_ff() {
var oldtext = lasttext.value;
var curpos = lasttext.selectionStart;
pretext = oldtext.substring(0,curpos);
posttest = oldtext.substring(curpos,oldtext.length);
lasttext.value = pretext + "|" + posttest;
}
-->
</script>
<form name="testform">
<input type="text" name="testtext1" onBlur="lasttext=this;">
<input type="text" name="testtext2" onBlur="lasttext=this;">
<input type="text" name="testtext3" onBlur="lasttext=this;">
</form>
Insert IE
<br>
Insert FF
</body></html>
This will also work with textareas. I don't know how to reposition the cursor so it stays at the insertion point.
In light of your update:
var inputs = document.getElementsByTagName('input');
var lastTextBox = null;
for(var i = 0; i < inputs.length; i++)
{
if(inputs[i].getAttribute('type') == 'text')
{
inputs[i].onfocus = function() {
lastTextBox = this;
}
}
}
var button = document.getElementById("YOURBUTTONID");
button.onclick = function() {
lastTextBox.value += 'PUTYOURTEXTHERE';
}
Note that if the user pushes a button, focus on the textbox will be lost and there will be no caret position!
loop over all you input fields...
finding the one that has focus..
then once you have your text area...
you should be able to do something like...
myTextArea.value = 'text to insert in the text area goes here';
I'm not sure if you can capture the caret position, but if you can, you can avoid Jason Cohen's concern by capturing the location (in relation to the string) using the text box's onblur event.
A butchered version of #bmb code in previous answer works well to reposition the cursor at end of inserted characters too:
var lasttext;
function doinsert_ie() {
var ttInsert = "bla";
lasttext.focus();
var sel = document.selection.createRange();
sel.text = ttInsert;
sel.select();
}
function doinsert_ff() {
var oldtext = lasttext.value;
var curposS = lasttext.selectionStart;
var curposF = lasttext.selectionEnd;
pretext = oldtext.substring(0,curposS);
posttest = oldtext.substring(curposF,oldtext.length);
var ttInsert='bla';
lasttext.value = pretext + ttInsert + posttest;
lasttext.selectionStart=curposS+ttInsert.length;
lasttext.selectionEnd=curposS+ttInsert.length;
}

Categories

Resources