Input not scrolling to the far right like it should - javascript

I have an input field where i append data at the cursor position.
after that, i set the selectionStart to the end of the field.
BUT, whenever i add something to the input (by button clicks), i only see the left part of it (until it reaches the right edge). everything more is there (i can select it with the mouse and scroll), but it doesn't automatically show the right edge.
how can i do that?
i want to add something to the input and jump right to the end of the string.
// add 2 digit number
$('button#2digit').on('click', function add2digit() {
addNumberToInput(10, 99);
});
function addNumberToInput(min, max) {
var problemInput = $('input#testProblem');
if (lastCharIsOperation() || problemInput.val().trim() < 1) { // if last char is an operation or first in string, just append the number
addAtCursor(randomNonPrime(min, max));
} else {
addAtCursor('+' + randomNonPrime(min, max));
}
}
function addAtCursor(toAdd) {
var problemInput = $('input#testProblem');
var oldText = problemInput.val();
var cursor = problemInput[0].selectionStart;
var pre = oldText.substring(0,cursor);
var post = oldText.substring(cursor, oldText.length);
//insert at cursor
problemInput.val(pre + toAdd + post);
//put cursor to end
problemInput[0].selectionStart = problemInput.val().length;
}
(it even skips back to the left on blur, i couldn't make a picture with the windows snipping tool, because i had to click it first)

From Set mouse focus and move cursor to end of input using jQuery.
var problemInput = $('input#testProblem');
problemInput.focus();
var t=problemInput.val();
problemInput.val('');
problemInput.val(t);
Here is the start of a full solution: https://jsfiddle.net/michaelgentry/vwm159pt/

This will still cause the scroll to jump back to the left on blur, but does what you are asking:
var elem = document.getElementById('myInput');
elem.focus();
elem.scrollLeft = elem.scrollWidth;

Related

Set input cursor to proper position after javascript regex

After regex is called cursor goes to end. So I tried that fix, that also did not work properly. How can that be fixed? My target: if field empty and digits is typed > cursor at end, if back to add or delete any digit > cursor at proper position(not at end)
document.getElementById('target').addEventListener('input', function (e) {
var target = e.target,
position = target.selectionStart; // Capture initial position
target.value = target.value.replace(/\B(?=(\d{3})+(?!\d))/g, "-");// This triggers the cursor to move.
target.selectionEnd = position; // Set the cursor back to the initial position.
});
Fiddle
Set the selection range to "position" that you created, and if you include a "-" you increment the position, like that:
document.getElementById('target').addEventListener('input', function (e) {
var target = e.target,
position = target.selectionStart; // Capture initial position
var old = target.value;
target.value = target.value.replace(/\B(?=(\d{3})+(?!\d))/g, "-");
if(old != target.value)
position++;
target.setSelectionRange(position, position);
});
Try setSelectionRange().
Your fiddle: **http://jsfiddle.net/ku8bg0c9/2/
I put 10000 for length as I don't think 10000 characters will ever be inputted. But you can set the length based on length of the input string.
document.getElementById('target').addEventListener('input', function (e) {
var target = e.target,
position = target.selectionStart; // Capture initial position
target.value = target.value.replace(/\B(?=(\d{3})+(?!\d))/g, "-"); // This triggers the cursor to move.
target.selectionEnd = position; // Set the cursor back to the initial position.
target.setSelectionRange(10000, 10000);
});

How to know if Ckeditor anchor is encapsulated within span?

This is specifically for the developers of CKEditor.
The goal is to know when the cursor is either inside or outside a span of custom atttributes.
When using the Ckeditor, if inserted a custom span like below from a plug-in, occassionally when you stop typing, un focus the textarea and replace your cusor to the end of the line - as though you are continuing typing where you had left off. The text can either be inside the span or outside with no encapsulation.
Example before unfocus:
<span style="color:blue;line-height:12px;font-size:10px;font-family:arial;" class="master-span">
<span style="color:red;line-height:20px;font-size:18px;font-family:museo;" class="child-span">Text is here</span>
</span>
Re-Focus and positioning cusor on screen at the end of "here" and start typing.
<span style="color:blue;line-height:12px;font-size:10px;font-family:arial;" class="master-span">
<span style="color:red;line-height:20px;font-size:18px;font-family:museo;" class="child-span">Text is here</span> Text is now outside!!!
</span>
What I'm asking is that where would I target my troubleshoot to know when the cursor/anchor is within the span or outside of it i.e. Is there a calculation being made in the code to determine this?
The reason for this enquiry is that the span formmating is extremely important and we can't use a master span in the surrounding textarea as this can be altered by the user. We find also, that using bulletpoints are troublesome due to the browsers inability to set line-height so ensuring text is always within the span would be a major help.
P.S I've downloaded the source code to see where I could find it.
Many Thanks for any help.
Upon clicking of the text area of any of my CKeditor instances I looked for the parent of the cursor to see if it was an LI node. This indicating that the cursor had landed outside of my span within a bullet point. I proceeded to find the most outside Text Node I could find as my assumption is that if the cursor was outside the span thus the user wanted to continue typing from the last character.
In my setup, within a span there can be a number of multiple sub spans with no limitation of number. So I had to find the most outside span that had a text node at the end (or close to the end).
I did by getting the Node List and reversing the order to find the first text node within the reverse order. I created a function that could loop the number of span and break upon finding the first text node.
Once I had my text node I then set up ranges and calculated the length of the text in order to place it at the end. Below is my final solution which works on latest Chrome/FF/IE.
$("#preview-text-3827").on("click", function(){
var CKEDITOR = window.parent.CKEDITOR;
var ck_instance_name = false;
for ( var ck_instance in CKEDITOR.instances ){
if (CKEDITOR.instances[ck_instance].focusManager.hasFocus){
ck_instance_name = ck_instance;
break;
}
}
var editor = CKEDITOR.instances[ck_instance_name];
var selection = editor.getSelection();
var parent_attrs = "";
if (selection.getType() == CKEDITOR.SELECTION_TEXT) {
parent_attrs = selection.getStartElement();
if(parent_attrs.getName() == "li"){
var nodeList = parent_attrs.getChildren();
console.log( "Now Reverse" );
for ( var i = nodeList.count() - 1; i > -1; --i ) {
//console.log( nodeList.getItem( i).nodeName );
var el = nodeList.getItem( i);
if(el.$.nodeName == "#text"){
// This is the last text but it's inside the li which is WRONG
}
if(el.$.nodeName == "SPAN"){
// This should be the last span
// Jump into this span and find the children.
var result = retractLiPosition(el);
if(result.text == true){
var inner = result.result;
var text = inner.$.textContent || inner.$.innerText;
var length = text.length;
var range = editor.createRange();
range.setStart( inner , length);
range.setEnd( inner, length);
editor.getSelection().selectRanges( [ range ] );
break;
}
}
}
}
}
});
function retractLiPosition(element){
var returning = new Object();
returning.result = element;
returning.text = false;
var result = element;
var nodeList = element.getChildren();
for ( var i = nodeList.count() - 1; i > -1; --i ) {
var el = nodeList.getItem( i);
if(el.$.nodeName == "#text"){
returning.result = el;
returning.text = true;
break;
}
if(el.$.nodeName == "SPAN"){
// This should be the last span
// Jump into this span and find the children.
returning = retractLiPosition(el);
}
}
return returning;
}
});

Firing an event when the caret gets within a particular div/span/a tag and also, when the caret leaves the tag

The idea is this -
There is a contenteditable element with some text in it. Am trying to build out a tagging mechanism (kind of like twitter's people tagging when you type '#'). Whenever a user types '#', it shows up a popover with suggestions and filters when they continue typing. Until here it's easy and I have got it figured out. The problem comes when I need to show the popover if/only if the caret is over the element containing the tag.
<div contenteditable="">
<p>Some random text before
<a href="javascript:;"
class="name-suggest"
style="color:inherit !important;text-decoration:inherit !important">#samadams</a>
Some random text after</p>
</div>
Now, whenever the user moves the caret over the a tag / clicks on it, I want to trigger an event that shows the popover, and remove it whenever the caret leaves the a tag. (kind of like focus / blur but they don't seem to work). onmousedown works but there is no way to tell if the cursor has been moved into the anchor tag with the keyboard.
Also, am doing this in angularjs, so, any solution targeted towards that would be preferable but not necessary.
Have been trying to get this to work for a day and any help is greatly appreciated.
This will let you know when your caret position is in an anchor node containing an #
$('#content').on('mouseup keydown keyup', function (event) {
var sel = getSelection();
if (sel.type === "Caret") {
var anchorNodeVal = sel.anchorNode.nodeValue;
if ( anchorNodeVal.indexOf('#') >= 0) {
$('#pop').show()
} else {
$('#pop').hide()
}
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="content" contenteditable="">
<p>Some random text before
<a href="javascript:;"
class="name-suggest"
style="color:inherit !important;text-decoration:inherit !important">#samadams</a>
Some random text after</p>
</div>
<div id="pop" style="display:none">Twitter node found</div>
You could add some regex to further validate the selection.
There is a weird move with RegExps and offset calculation in the code below, but let me explain why it's a better solution.
I've been building a complicated editor using contenteditable about a year ago. It wasn't just a disaster. It was a fucking disaster. There is no cover-all-the-cases spec. Browsers behave differently in every possible detail and it changes frequently. Put a caret before # char and you will get this is Gecko:
<a href="#">|#name
And this in WebKit:
|<a href="#">#name
Well, unless <a> is paragraph's first child. Then result would be the same as in Gecko. Try to put caret after the nickname and both will tell it's inside the link. Start typing, and caret will pop out the element - a year ago Gecko wasn't doing it.
I've used native Selection & Range APIs in this example, they are IE9+. You may want to use Rangy instead.
$el = $('#content');
var showTip = function (nickname) {
// ...
console.log('Show: ' + nickname);
};
var dismissTip = function () {
// ...
console.log('Hide');
};
// I'm sure there is a better RegExp for this :)
var nicknameRegexp = /(^|\b|\s)\#(\w+)(\s|\b|$)/g;
var trackSelection = function () {
var selection = window.getSelection(),
range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
if (range == null || $el[0].contains(range.commonAncestorContainer) == false) {
return dismissTip();
}
var comparer = range.cloneRange();
comparer.setStart($el[0], 0);
var offset = comparer.toString().length;
var match, from, to;
while (match = nicknameRegexp.exec($el[0].textContent)) {
from = match.index + match[1].length;
to = match.index + match[1].length + match[2].length + 1;
if (offset >= from && offset <= to) {
// Force rewind, otherwise next time result might be incorrect
nicknameRegexp.lastIndex = 0;
return showTip(match[2]);
}
}
return dismissTip();
};
$el.on({
// `mousedown` can happen outside #content
'mousedown': function (e) {
$(document).one('mouseup', function (e) {
// Calling function without a tiny delay will lead to a wrong selection info
setTimeout(trackSelection, 5);
});
},
'keyup': trackSelection
});
Just looked at Fire event when caret enters span element which led me here, pretending your case was quite similar except finding if current word is specifically beginning with # for the modal to show...
The thing you need is a way to get the word we're on at the moment we move or type, then check the first character and hide/show the modal pane accordingly will be pretty easy.
function getSelectedWord(grab=document.getSelection()) {
var i = grab.focusOffset, node = grab.focusNode, // find cursor
text = node.data || node.innerText, // get focus-node text
a = text.substr(0, i), p = text.substr(i); // split on caret
return a.split(/\s/).pop() + p.split(/\s/)[0]} // cut-out at spaces
Now you can listen for keydown or selectionchange events and show your pane knowning what have already been written of the current/selected word.
editor.addEventListener('keydown', ev => {
if (ev.key.substr(0, 5) != 'Arrow') // react when we move caret or
if (ev.key != '#') return; // react when we type an '#' or quit
var word = getSelectedWord(); // <-- checking value
if (word[0] == '#') showModal(word.substr(1)); // pass without '#'
});
Note that social networks and code completion usually stops at caret position while I did check for word tail... You can go usual by removing p off of getSelectedWord function definition if desired.
Hope this still helps; Happy coding ! ;)

Trigger extra mouse click event in same location as first

I have some html, and want to create an input field when clicking a paragraph. That is no problem, but then I'd also like the text cursor to move where the click occured. This can be done simply by clicking again of course, but I'd like this to happen in a single click.
It would be nice to use the first click event to trigger another right after the input field has been created, which would (hopefully) use the mouse coordinates of the first event to move the text cursor to the correct location. Is this possible?
I'm using jquery, so if that makes it easier, please use it for your answer. Thanks for your help!
Here's an approximate solution. It work by calculating the approximate character offset from the number of characters in the text, the width of the text and the relative mouse-offset.
The function for setting the cursor position (once we calculated it) comes from https://stackoverflow.com/a/841121/1961666
http://jsfiddle.net/4LmZ9/
$.fn.selectRange = function(start, end) {
if(!end) end = start;
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();
}
});
};
$('span').on('click', function(evt)
{
var left = $(this).offset().left;
var width = $(this).width();
var clen = $(this).text().length;
var offset = parseInt((evt.pageX - left)/width * clen);
$(this).replaceWith($('<input />').val($(this).html()));
$('input').trigger('focus').selectRange(offset);
});
(You can get better precision if you calculate the width of each character and taking that into account, if it's important.)

JavaScript 'contenteditable' -- Getting/Setting Caret Position

I have read a few posts on positioning the caret, but none seem to answer my particular issue.
I have 2 divs (div1 and div2)
div1 = noneditable div
div2 = contenteditable div
both divs contain exact same contents
when user clicks on div1, it gets hidden, and div2 appears in exact location and user can edit
The problem: I want the caret to appear in exact location on div2 as div1
So, I need some way to READ the location where the user clicks on div1, and then when div2 appears place the cursor/caret in that same location, so a getCaretLocation(in_div_id) and setCaretLocation(in_div_id) set of functions.
Any way to do that?
Thanks -
Short answer : You can't
Long answer : The problem you'll face is that you'll be able to get (x,y) coordinates for the click event on div1, but any implementation of the caret position while require you knowing the position of the caret in the content (which is the number of characters preceding the caret).
To convert the (x,y) coordinates to a character position you actually need to know how many characters were before (ie. left on the current line and above, if the text is ltr).
If you use a fixed width font, you can simplify the problem : mapping an (x,y) coordinate to a (line, column) coordinate on a character grid.
However, you still face the problem of not knowing how the text is wrapped. For example :
------------------
|Lorem ipsum |
|dolor sit amet |
|consectetur |
|adipiscing elit |
------------------
If the user clicks on the d in dolor, you know that the character is the 1st on the 2nd line, but without knowing the wrapping algorithm there is no way you'll know that it is the 13th character in "Lorem ipsum dolor sit…". And there is no guarantee that such a wrapping algorithm is identical across browsers and platform.
Now, what I'm wondering is why would you use 2 different synced div in the first place ? Wouldn't it be easier to use only one div and set its content to editable when the user clicks (or hovers) ?
You could insert a tiny span-element at the caret, get its position, and remove it. For a cross-browser range and selection library, see rangy.
you can, basically you need set temporary content editable on your first div to catch caret pos
$('div1').hover(function()
{ $(this).attr('contenteditable','true');
},function()
{ $(this).removeAttr('contenteditable');
}).mouseup(function()
{ var t = $(this);
// get caret position and remove content editable
var caret = t.getCaret();
t.removeAttr('contenteditable');
// do your div switch stuff
...
// and apply saved caret position
$('div2').setCaret(caret);
});
now just need get/set caret method :)
edit > here is my own, (live demo)
getSelection:function($e)
{ if(undefined === window.getSelection) return false;
var range = window.getSelection().getRangeAt(0);
function getTreeOffset($root,$node)
{ if($node.parents($root).length === 0) return false; // is node child of root ?
var tree = [], treesize = 0;
while(1)
{ if($node.is($root)) break;
var index, $parent = $node.parent();
index = $parent.contents().index($node);
if(index !== -1) { tree[treesize++] = index; } $node = $parent;
}; return tree.reverse();
}
var start = getTreeOffset($e,$(range.startContainer));
var end = getTreeOffset($e,$(range.endContainer));
if(start & end === false) return false;
return {start:start,end:end,startOffset:range.startOffset,endOffset:range.endOffset};
}, setSelection:function($e,s,win)
{ $e.focus(); if(s === false) return; var sel = win.getSelection(); sel.removeAllRanges();
function getNode($e,s)
{ var node = $e;
for( var n=0;n<s.length;n++ )
{ var index = s[n]; if(index < 0) break;
node = node.contents(':eq('+index+')');
} return node.get(0);
}
var start = getNode($e,s.start), end = getNode($e,s.end), range = win.document.createRange();
range.setStart(start,s.startOffset); range.setEnd(end,s.endOffset); sel.addRange(range);
}
It sounds like you are trying to do an inline edit... have you looked at the jeditable plugin?
When you click on an element, a Selection object with zero length is created (get it from element.getSelection() , where element is the div in question). The focusOffset of that object will let you know that you clicked on, for example, the 74th character in that div (this is the thing that Adrien said was impossible in a different answer).

Categories