How do I insert a character at the caret with javascript? - 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;
}

Related

Split div content at cursor position

I need to split an element after user clicks on it and the attr 'contenteditable' becomes 'true'. This fiddle works for the first paragraph but not second because the latter is in a p tag. Similary in this fiddle you will see that when the element has html tags in it, the counter loses accuracy and hence the text before and after the cursor is not what you'd expect.
The assumption here is that the users will split the data in a way that the help tags will stay intact. As pointed out by dandavis here, e.g. the div has <i>Hello</i> <b>Wo*rld</b>, the user will only need to split the div into two divs, first will have <i>Hello</i> and the second div will have <b>Wo*rld</b> in it.
Html:
<div><mark>{DATE}</mark><i>via email: </i><mark><i>{EMAIL- BROKER OR TENANT}</i></mark></div>
JS:
var $splitbut = $('<p class="split-but">Split</p>');
$(this).attr('contenteditable', 'true').addClass('editing').append($splitbut);
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
var start = userSelection.anchorOffset;
var end = userSelection.focusOffset;
var before = $(this).html().substr(0, start);
var after = $(this).html().substr(start, $(this).html().length);
The "Split" button is not working as generating the html is not an issue once I get proper "after" and "before" text. Any ideas as to what I am doing wrong here?
Something like this could work for the specific case you describe
$('div, textarea').on('click', function(e) {
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
var start = userSelection.anchorOffset,
end = userSelection.focusOffset,
node = userSelection.anchorNode,
allText = $(this).text(),
nodeText = $(node).text();
// before and after inside node
var nodeBefore = nodeText.substr(0, start);
var nodeAfter = nodeText.substr(start, nodeText.length);
// before and after for whole of text
var allExceptNode = allText.split(nodeText),
before = allExceptNode[0] + nodeBefore,
after = nodeAfter + allExceptNode[1];
console.log('Before: ', before);
console.log('------');
console.log('After: ', after);
});
Updated demo at https://jsfiddle.net/gaby/vaLz55fv/10/
It might exhibit issues if there are tags whose content is repeated in the whole text. (problem due to splitting)

Selection in javascript

I want to get selection of block of text (including div's and formatting), I have function 'text_redesign' that should do it and I call it onlick to some button. But it select only the place in button where I clicked, i.e. on button is written "Restructure" and if I click close to 'R', I will get "R span class="someclass_tigran" /span estructure", and if I click close to 't', than I get "Restruc span class="someclass_tigran"/span ture". So it gets the place where I clicked, but not the selected text.
function text_redesign()
{
var text_comonent_area = $("#text_block_1").contents().find('iframe'); //.contents();
//var len = text_comonent_area.value.length;
//var start = text_comonent_area.selectionStart;
//var end = text_comonent_area.selectionEnd;
//var sel = text_comonent_area.value.substring(start, end);
//console.log("start " + start + " end " + end);
var selObj = window.getSelection()
console.log(selObj);
var selRange = selObj.getRangeAt(0);
console.log(selRange);
if(selObj.getRangeAt){ // thats for FF
console.log("sdfsdf33");
var range = selObj.getRangeAt(0);
var newNode = document.createElement("span");
newNode.setAttribute('class', 'someclass_tigran');
range.surroundContents(newNode);
}
}
P.S. I want apply it to iframe, but I tried as inside frame and well as to document and the result is same.
I am new to selection, so if you know where my mistake can be, please, tell and I will google it. Now I have no idea what to search for.
I think it may be better to check the selected text in text_redesign function.
Look at this example:
How to replace selected text with html in a contenteditable element?

Insert value into TEXTAREA where cursor was

I have a textarea and a div with values. When I click on a value I insert it into textarea. I need it to be inserted where my cursor was in textarea. Why do I say WAS? Because when I move it out and click on a value to insert, I assume it looses focus in the text area.
So, my question is, is there a way to "remember" the latest cursor position within textarea and then insert my values at that position?
Perhaps it could be a char number in a string?.. Currently I add it like this:
input.val( function( i, val ) { return val + " " + myInsert + " "; } );
Also I use jQuery, perhaps I could use it?
I've written a cross-browser jQuery plug-in for dealing with the caret/selection in textareas and text inputs called Rangy inputs (terrible name, I really should think of a better one). A combination of methods from this and the techniques in Edgar Villegas Alvarado's answer should do the trick, although in IE, the focusout event fires too late and you'll need to use the proprietary beforedeactivate event instead:
var $textBox = $("#foo");
function saveSelection(){
$textBox.data("lastSelection", $textBox.getSelection());
}
$textBox.focusout(saveSelection);
$textBox.bind("beforedeactivate", function() {
saveSelection();
$textBox.unbind("focusout");
});
When inserting text later, the following will insert text at the previous cursor position (or overwrite the previously selected text, if the user had selected any):
var selection = $textBox.data("lastSelection");
$textBox.focus();
$textBox.setSelection(selection.start, selection.end);
$textBox.replaceSelectedText("Some new text");
See jsFiddle example here: http://jsfiddle.net/rQXrJ/1/
Here is a working example of what you are trying to do check it out at:
http://jsfiddle.net/J5Z2n/1/
Hello there my good friend....
how do you do
the jQuery:
function insertAt (myField, myValue, startSel, endSel) {
if (startSel || startSel == '0') {
var startPos = startSel;
var endPos = endSel;
myField.val(myField.val().substring(0, startPos)+ myValue+ myField.val().substring(endPos, myField.val().length));
}
else {
myField.val() += myValue;
}
}
// calling the function
var targetBox = $('textarea#insert'),
startSel,
endSel;
targetBox.bind('focusout', function() {
//insertAtCursor(this, 'how do you do');
startSel = this.selectionStart;
endSel = this.selectionEnd;
});
$("#myvalue").click(function() {
var myValue = $(this).text();
insertAt(targetBox, myValue, startSel, endSel);
});
The original function was borrowed from this post
http://alexking.org/blog/2003/06/02/inserting-at-the-cursor-using-javascript
That should give you a solid head start I hope. Cheers
If the caret (the cursor) is somewhere in the text field, it registers in Javascript as an empty selection. That is, the selectionStart and selectionEnd attributes are the same. You can use those attributes to detect the position of the caret.
Apparently selectionStart and selectionEnd are quite useful here. Check this out:
http://www.scottklarr.com/topic/425/how-to-insert-text-into-a-textarea-where-the-cursor-is/
You can use the jQuery Caret plugin to get/set the cursor position .
Example usage:
var cursorPosition = $("#textbox").caret().start);
You could 'store' this position like this:
$("#textbox").focusout(function(){
var cursorPosition = $(this).caret().start);
$(this).data("lastCursorPos", cursorPosition);
});
To retrieve it (on your div click event), do this:
var lastCursorPosition = $("#textbox").data("lastCursorPos");
Hope this helps. Cheers

jQuery: scroll textarea to given position

I have a textarea with lots and lots of text:
<textarea cols="50" rows="10" id="txt">lots and lots of text goes here</textarea>
I want to scroll the textarea down, so the user can see 2000-th character. How can I do this using javasctipt/jQuery?
$('#txt').scrollToCharNo(2000); // something like this would be great
EDIT (my solution)
Well, I managed to make it work myself. The only way I found, is to create a DIV with the same font and width as the textarea, put a SPAN near the needed character and find the position of that span.
I am sure, that somebody might find my solution useful, so i'll paste it here:
jQuery.fn.scrollToText = function(search) {
// getting given textarea contents
var text = $(this).text();
// number of charecter we want to show
var charNo = text.indexOf(search);
// this SPAN will allow up to determine given charecter position
var anch = '<span id="anch"></span>';
// inserting it after the character into the text
text = text.substring(0, charNo) + anch + text.substring(charNo);
// creating a DIV that is an exact copy of textarea
var copyDiv = $('<div></div>')
.append(text.replace(/\n/g, '<br />')) // making newlines look the same
.css('width', $(this).attr('clientWidth')) // width without scrollbar
.css('font-size', $(this).css('font-size'))
.css('font-family', $(this).css('font-family'))
.css('padding', $(this).css('padding'));
// inserting new div after textarea - this is needed beacuse .position() wont work on invisible elements
copyDiv.insertAfter($(this));
// what is the position on SPAN relative to parent DIV?
var pos = copyDiv.find('SPAN#anch').offset().top - copyDiv.find('SPAN#anch').closest('DIV').offset().top;
// the text we are interested in should be at the middle of textearea when scrolling is done
pos = pos - Math.round($(this).attr('clientHeight') / 2);
// now, we know where to scroll!
$(this).scrollTop(pos);
// no need for DIV anymore
copyDiv.remove();
};
$(function (){
$('#scroll_button').click(
function() {
// scrolling to "FIND ME" using function written above
$('#txt').scrollToText('FIND ME');
return false;
}
);
});
Here is a demo (it works!): http://jsfiddle.net/KrVJP/
Since none of the answers actually solved the problem, I'll accept experimentX's one: thank you for putting an effort to help me, I appreciate it!
I am not sure if it will work. Please also check it here. It's seems working for 2000th, 1500th, and 1000th position.
EDIT confused font-size or line-height ???
$(function (){
var height = 2000/$('#txt').attr('cols');
$('#txt').scrollTop(height*13);
$('#txt').selectRange(2000,2000); //this is just to check
});
$.fn.selectRange = function(start, end) { //this is just to check
return this.each(function() {
if (this.setSelectionRange) {
this.focus();
this.setSelectionRange(start, end);
} else if (this.createTextRange) {
var range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', start);
range.select();
}
});
};
UPDATE How about this one
var height = 1200/$('#txt').attr('cols');
var line_ht = $('#txt').css('line-height');
line_ht = line_ht.replace('px','');
height = height*line_ht;
You can use this jquery plugin

Tag-like autocompletion and caret/cursor movement in contenteditable elements

I'm working on a jQuery plugin that will allow you to do #username style tags, like Facebook does in their status update input box.
My problem is, that even after hours of researching and experimenting, it seems REALLY hard to simply move the caret. I've managed to inject the <a> tag with someone's name, but placing the caret after it seems like rocket science, specially if it's supposed work in all browsers.
And I haven't even looked into replacing the typed #username text with the tag yet, rather than just injecting it as I'm doing right now... lol
There's a ton of questions about working with contenteditable here on Stack Overflow, and I think I've read all of them, but they don't really cover properly what I need. So any more information anyone can provide would be great :)
You could use my Rangy library, which attempts with some success to normalize browser range and selection implementations. If you've managed to insert the <a> as you say and you've got it in a variable called aElement, you can do the following:
var range = rangy.createRange();
range.setStartAfter(aElement);
range.collapse(true);
var sel = rangy.getSelection();
sel.removeAllRanges();
sel.addRange(range);
I got interested in this, so I've written the starting point for a full solution. The following uses my Rangy library with its selection save/restore module to save and restore the selection and normalize cross browser issues. It surrounds all matching text (#whatever in this case) with a link element and positions the selection where it had been previously. This is triggered after there has been no keyboard activity for one second. It should be quite reusable.
function createLink(matchedTextNode) {
var el = document.createElement("a");
el.style.backgroundColor = "yellow";
el.style.padding = "2px";
el.contentEditable = false;
var matchedName = matchedTextNode.data.slice(1); // Remove the leading #
el.href = "http://www.example.com/?name=" + matchedName;
matchedTextNode.data = matchedName;
el.appendChild(matchedTextNode);
return el;
}
function shouldLinkifyContents(el) {
return el.tagName != "A";
}
function surroundInElement(el, regex, surrounderCreateFunc, shouldSurroundFunc) {
var child = el.lastChild;
while (child) {
if (child.nodeType == 1 && shouldSurroundFunc(el)) {
surroundInElement(child, regex, surrounderCreateFunc, shouldSurroundFunc);
} else if (child.nodeType == 3) {
surroundMatchingText(child, regex, surrounderCreateFunc);
}
child = child.previousSibling;
}
}
function surroundMatchingText(textNode, regex, surrounderCreateFunc) {
var parent = textNode.parentNode;
var result, surroundingNode, matchedTextNode, matchLength, matchedText;
while ( textNode && (result = regex.exec(textNode.data)) ) {
matchedTextNode = textNode.splitText(result.index);
matchedText = result[0];
matchLength = matchedText.length;
textNode = (matchedTextNode.length > matchLength) ?
matchedTextNode.splitText(matchLength) : null;
surroundingNode = surrounderCreateFunc(matchedTextNode.cloneNode(true));
parent.insertBefore(surroundingNode, matchedTextNode);
parent.removeChild(matchedTextNode);
}
}
function updateLinks() {
var el = document.getElementById("editable");
var savedSelection = rangy.saveSelection();
surroundInElement(el, /#\w+/, createLink, shouldLinkifyContents);
rangy.restoreSelection(savedSelection);
}
var keyTimer = null, keyDelay = 1000;
function keyUpLinkifyHandler() {
if (keyTimer) {
window.clearTimeout(keyTimer);
}
keyTimer = window.setTimeout(function() {
updateLinks();
keyTimer = null;
}, keyDelay);
}
HTML:
<p contenteditable="true" id="editable" onkeyup="keyUpLinkifyHandler()">
Some editable content for #someone or other
</p>
As you say you can already insert an tag at the caret, I'm going to start from there. The first thing to do is to give your tag an id when you insert it. You should then have something like this:
<div contenteditable='true' id='status'>I went shopping with <a href='#' id='atagid'>Jane</a></div>
Here is a function that should place the cursor just after the tag.
function setCursorAfterA()
{
var atag = document.getElementById("atagid");
var parentdiv = document.getElementById("status");
var range,selection;
if(window.getSelection) //FF,Chrome,Opera,Safari,IE9+
{
parentdiv.appendChild(document.createTextNode(""));//FF wont allow cursor to be placed directly between <a> tag and the end of the div, so a space is added at the end (this can be trimmed later)
range = document.createRange();//create range object (like an invisible selection)
range.setEndAfter(atag);//set end of range selection to just after the <a> tag
range.setStartAfter(atag);//set start of range selection to just after the <a> tag
selection = window.getSelection();//get selection object (list of current selections/ranges)
selection.removeAllRanges();//remove any current selections (FF can have more than one)
parentdiv.focus();//Focuses contenteditable div (necessary for opera)
selection.addRange(range);//add our range object to the selection list (make our range visible)
}
else if(document.selection)//IE 8 and lower
{
range = document.body.createRange();//create a "Text Range" object (like an invisible selection)
range.moveToElementText(atag);//select the contents of the a tag (i.e. "Jane")
range.collapse(false);//collapse selection to end of range (between "e" and "</a>").
while(range.parentElement() == atag)//while ranges cursor is still inside <a> tag
{
range.move("character",1);//move cursor 1 character to the right
}
range.move("character",-1);//move cursor 1 character to the left
range.select()//move the actual cursor to the position of the ranges cursor
}
/*OPTIONAL:
atag.id = ""; //remove id from a tag
*/
}
EDIT:
Tested and fixed script. It definitely works in IE6, chrome 8, firefox 4, and opera 11. Don't have other browsers on hand to test, but it doesn't use any functions that have changed recently so it should work in anything that supports contenteditable.
This button is handy for testing:
<input type='button' onclick='setCursorAfterA()' value='Place Cursor After <a/> tag' >
Nico

Categories

Resources