I'm making a file edit interface in my web-app, I have a textarea with file contents.
When textarea is focused, I want to output the position of the cursor, i.e. line number and column: this is useful because error messages usually yield a line number, for example.
The question is: how do I figure out the position of the cursor in textarea?
I'm using prototype library. Maybe there's a solution already?
I'm not really interested in fancy toolbars for the textarea, which are offered by those advanced widgets.
When I want the current line number of textarea and current column of textarea, I solved like this:
<textarea onkeyup="getLineNumberAndColumnIndex(this);" onmouseup="this.onkeyup();" >
</textarea>
function getLineNumberAndColumnIndex(textarea){
var textLines = textarea.value.substr(0, textarea.selectionStart).split("\n");
var currentLineNumber = textLines.length;
var currentColumnIndex = textLines[textLines.length-1].length;
console.log("Current Line Number "+ currentLineNumber+" Current Column Index "+currentColumnIndex );
}
You may want to check out these 2 links:
http://www.dedestruct.com/2008/03/22/howto-cross-browser-cursor-position-in-textareas/[The orginal source does not exist any more, the modified link points to the latest version of the Web Archive]
https://developer.mozilla.org/En/DOM:Selection
..once you have a selection (cursor index in text), you can split your text by newlines, thus getting line number. you can get column by determining index from beginning of a line
Related
I am currently trying to create a syntax highlighter for Javascript and I currently facing the issue which I have found out is common with creating something like this which is setting the caret position to the end while the user types or edit contentEditable text.
I researched and found this and many other solutions here on SO but none works. It gets the position of the caret but never resets it so I am trying to find a workaround for this problem.
Below is the code I came up with.
html
<div id="editor" contentEditable="true" onkeyup="resetPosition(this)"></div>
<input type="text" onkeyup="resetPosition(this)" />
js
function getPos(e) {
// for contentedit field
if (e.isContentEditable) {
e.focus()
let _range = document.getSelection().getRangeAt(0)
let range = _range.cloneRange()
range.selectNodeContents(e)
range.setEnd(_range.endContainer, _range.endOffset)
return range.toString().length;
}
// for texterea/input element
return e.target.selectionStart
}
function setPos(pos, e) {
// for contentedit field
if (e.isContentEditable) {
e.focus()
document.getSelection().collapse(e, pos);
return
}
e.setSelectionRange(pos, pos)
}
function resetPosition(e) {
if(e.isContentEditable) {
let currentPosition = getPos(e);
e.innerHTML=e.innerHTML.replace(/[0-9]/g, "a");
setPos(currentPosition, e);
return;
}
e.value = e.value.replace(/[0-9]/g, "a");
setPos(currentPosition, e);
}
This works fine for text input but not for contentEditable divs.
When I type something like function, I get otincfun.
UPDATE: I was able to fix the setPos function by changing this line from document.getSelection().collapse(e, pos); to document.getSelection().collapse(e.firstChild, pos); but a new bug arose.
When I press ENTER Key, the caret goes back to the first line and first character. Please how do I fix?
Below is the fiddle link
https://jsfiddle.net/oketega/bfeh9nm5/35/
Thanks.
The Problems
document.getSelection().collapse(element, index) collapses the cursor to the child node that index points to, not the character index.
I was able to fix the setPos function by changing this line from document.getSelection().collapse(e, pos); to document.getSelection().collapse(e.firstChild, pos);
That will work if you are only replacing characters, but if you are creating a syntax highlighter, you will want to encase characters in span elements to style them. e.firstChild would then only set the position to an index within e's first child, excluding latter span's
Another thing to consider is that you may want to autocomplete the certain chars. The caret position before you manipulate the text may not be the same as after you do so.
The Solution
I recommend creating a <span id="caret-position"></span> element to track where the caret is.
It would work like this:
function textChanged(element) {
// 1
const text = setCursorMarker(element.innerText, element);
// 2
const html = manipulate(text);
element.innerHTML = html;
// 3
const index = findCursorIndex(element);
document.getSelection().collapse(element, index)
}
Every time the user types, you can get the current caret position and slip in the #caret-position element in there.
Overwrite the existing html with the syntax highlighted text
Find out where #caret-position is and put the caret there.
Note: The recommended way to listen for when the user types in the content-editable element is with the oninput listener, not onkeyup. It is possible to insert many characters by holding down a key.
Example
There is a working js fiddle here: https://jsfiddle.net/Vehmloewff/0j8hzevm/132/
Known Issue: After you hit Enter twice, it looses track of where the caret is supposed to be. I am not quite sure why it does that.
I need advice on how I can create dynamic line breaks ideally with an <li> inside TINY. I have tried using an each loop and also a for loop, and the values just do not appear in the text area. BUT, if I just add them to the text area with a val() they go in fine, BUT as just one long string.
The text area has an id of wo_materials. I'm successfully getting my text into Tiny like this:
$('#wo_materials').val(materials);
tinymce.init({
selector:'textarea'
});
And I get a nice row of text values:
The materials value is an array. If I look at it in the console it looks like this:
0: BP #15 Plain Felt 36"
1: Duraflo Weatherpro 50
2: 1 1/4 Coil Nails - box
Thanks !
If you're only modifying the value of the text area before tinymce is initialized then this might work for you:
$('#wo_materials).val(materials.join('<br/>'));
This works. The secret is adding the incrementing var "text" to the for loop and then wrapping the object / array in an <li>
var materials= JSON.parse(localStorage.getItem('materials'));
var text=" ";
materials.length;
function workorders(){
for (i = 0; i < materials.length; i++) {
text += "<li>"+materials[i]+"</li>";
}
$('#wo_materials').val("<li>"+text+"</li>");
}
workorders();
There is a custom method to insert HTML(html fragment not just plain text) into an editor (Rich Text Editor), but for some reason I have to use e.preventDefault to prevent browser default paste action and insert the copy data later. My code looks like below:
editor.addEventListener('paste', function(e) {
var data = e.clipboardData.getData('text/html'),
newData;
e.preventDefault();
newData = custom.handle(data);
custom.insert(newData);
}, false);
After custom.insert(newData), cursor is still blinking at the origin position. I expected it to have moved the end of newData.
Can anybody help me fix that?
Your question may already have an answer here:
Use JavaScript to place cursor at end of text in text input element
Set focus and cursor to end of text input field / string w. Jquery
With Mike Berrow's example, you can replace the input value with itself to set the carret to the end of the input. This would seem to be the most reliable way to do it, event if it is slightly hackish.
myInput.value = myInput.value;
With browsers that support it, you can rather use the setSelectionRange method. Since you already use clipboardData, this shouldn't be a problem.
myInput.setSelectionRange(myInput.value.length, myInput.value.length);
Pay attention to the fact that the value length may be harder to get if you are working with a textarea.
https://developer.mozilla.org/fr/docs/Web/API/HTMLInputElement/setSelectionRange
I don't know what properties of functions your custom has, but you can use this the code to move the cursor to the end of the text in an input and textarea:
custom.selectionStart = custom.selectionEnd;
If newData is pasted in the middle or beginning of the text, then you will have to calculate the length of newData and move the cursor by that many characters. Something like:
custom.selectionStart = custom.selectionStart + newData.length;
custom.selectionEnd = custom.selectionStart;
Edit to answer your question: How to calculate the length of text with HTML tags?
Well, this will be a bit tricky. You can create a temporary HTML element in memory, add the newData in it, and calculate the length of its innerText property. Something like this:
var temp = document.createElement("div");
temp.innerHTML = newData;
custom.selectionStart = custom.selectionStart + temp.innerText.length;
custom.selectionEnd = custom.selectionStart;
Note: innerText is was introduced by Microsoft and is not a W3C standard. The implementation varies across browsers although most replicate IE's behavior. innerText is style-aware and will only return the visible text.
I am able to grab the text that a user has selected on a web page,
using this code:
function getSelected() {
var userSelection;
if (window.getSelection) {
selection = window.getSelection();
} else if (document.selection) {
selection = document.selection.createRange();
}
}
is it posible for me to get the words around the
selected word.
Take these sentences for example: "If you need to
framglubble the zartbox, then you should buy the red widget.
Otherwise you can buy the blue widget and save some money."
My code will tell me if the person has selected the word "widget".
But I'd like to know if the selection is after "red" or "blue". Is
this possible? I've been scouring the Internet for some advice, and
I'm having trouble finding an answer.
thank you for your help
I have written quick script that can identify the part before selection and after selection inside the same DIV element.
However if the same DIV contains the same word more than one time and you select only that word, the current code I wrote can't identify if it's the first or second selected word so bottom line it will not answer your needs.
Anyway, you can see/copy/test the code here: http://jsfiddle.net/kvHxJ/ just select something and see the alert that appears.
If it's enough for your needs after all then great, accept this answer and move on... otherwise I need to know: can we assume the user will select whole words only, one word only? If the answer is yes I do have idea how to go around this.
The way to do this in non-IE browsers is to obtain a Range object from the selection. The range has a start and end boundary, and each boundary of the range is expressed as an offset within a node; if the boundary is within a text node, this offset will be a character offset.
For example, if the following was a text node and the selection is delimited by pipes:
"red |widget| blue widget"
... then the range you'd get from the selection would have a start offset of 4 within the text node.
The following will get you a Range representing the selection and alert the start boundary:
var sel = window.getSelection();
var selectedRange = sel.rangeCount ? sel.getRangeAt(0) : null;
if (range) {
alert("Offset " + selectedRange.startOffset
+ " in node " + selectedRange.startContainer.nodeName);
}
Ranges may be compared to other Ranges, so if you wanted to know, for example, if the current selection came after the word "blue" in the above text node, you could create a Range encompassing the word "blue" and compare it with the selected Range:
// Assume the text node is stored in a variable called textNode
var blueRange = document.createRange();
blueRange.setStart(textNode, 11);
blueRange.setEnd(textNode, 15);
var selectionIsAfterBlue =
(selectedRange.compareBoundaryPoints(Range.END_TO_START, blueRange) == 1);
In IE, none of this works and everything is done differently, generally with much more difficulty. To normalize this to single consistent interface, you could use my Rangy library.
IE has the move set of methods, which reduces this problem to just a couple of lines to expand the selection forward or backward any number of words (see http://www.webreference.com/js/column12/trmethods.html). From there, it's just a matter of comparing text against any arbitrary list of values. Other browsers don't have this feature AFAIK. Fate of the browser wars: one develops an awesome feature ignored or barred by patent from any other, so the feature is forever lost and avoided as burden of cross-browser support for all these innovations inevitably falls squarely on the website designers.
So, below is a generalized function to only get the ID of the parent element of the selected text. And, to work with this cross-browser solution, you have to wrap each word in it's own element complete with unique ID or other attribute. With this setup, it should then be a relatively painless jump to looking ahead and back at sibling or sequentially ID'd/named elements.
The catch here is that the client has to click/drag from the start of the word or phrase to the end, and absolutely no bordering spaces. Even double-clicking on a word will cause it to reference the next element (or in the case of IE, the parent DIV). Additionally, you should add code to restrict the selection boundary to a single parent DIV, as the below code may also expand the selection to surrounding elements. But hopefully you can take fixing that up from here. Otherwise, it's up to using vectors to pinpoint the coordinates of a text compared to all surrounding text.
<script type="text/javascript">
function get_selected_element_id() {
if (window.getSelection) {
// FF
var range = window.getSelection();
}
else if (document.selection) {
// IE
var range = document.selection.createRange();
}
if (range.focusNode) {
// FF
var test_value = range.focusNode.parentNode.id;
}
else {
// IE
var test_value = range.parentElement().id;
}
return test_value;
}
</script>
</head>
<body>
<div id="test_div">
<span id="test1">test</span> <span id="test2">asdf</span> <span id="test3">test2</span> <span id="test4">bla</span>
</div>
<button onclick="alert(get_selected_element_id());">go</button>
How do you prevent formatted user input past a max width? (e.g., 800 pixels) The user input is formatted because it is entered into a WYSIWYG text editor (CK Editor).
This solution doesn't work:
// Replicate user input in hidden div
// Check width
// If width > 800px, remove characters
...because you'd be removing characters from a formatted entry - e.g., from <p>Hello World</p> you'd end up with <p>Hello World</p
I can already find the width of the
formatted string. The problem is in
the actually shortening of it.
I think given your problem it is possible to remove the last char from a formatted entry. You'd just have to recursively dig through your HTML structure till you find it. Have a look at this neat little function I've written:
function findLastElement(element){
var content = $.trim(element.html());
if(content.charAt(content.length - 1) === '>'){
var lastChild = element.children(':last');
if(lastChild.length > 0){
return findLastElement(lastChild);
}
}
return element;
}
The name is slightly misleading, but this function will dig through the jQuery element you pass to it to find the element containing the last character, so I think this will solve your problem.
PS. I'd readily accept any suggestion on how to optimize/adopt best practice with this piece of code, if any of the gurus here would kindly drop one in the comments.
Can we assume only a certain font will be used? This one might help:
Calculate text width with JavaScript