My editor area:
<div id="myeditor" contenteditable="true"><div>
I could send and display keycodes using
$("#myeditor").text($("#myeditor").text() + String.fromCharCode(65))
which it displays "A" in my div of id 'myeditor'. But I am stuck sending Arrow Cursor Keycode to it. (left : 37; up : 38; right: 39; down : 40)
$("#myeditor").text($("#myeditor").text() + String.fromCharCode(39))
Ahh it doesnt work.. it is showing other ascii character (like ampersand or quote sign) which arent intended. Can you tell me a right way to do it?
I guess the reason is, all other characters are from keys that have some character associated with it. But arrow keys do not have ←,→ such characters associated with them. They just have some functions. Have you ever used arrow keys to input an arrow in a text box?
If you really want to use them to input arrows, you can do the following in your jQuery..
var element = $('#myeditor'),
elementText = element.text();
store = {
37: "←",
38: "↑",
39: "→",
40: "↓"
};
element.keydown(function( event ) {
var selection = window.getSelection().getRangeAt(0);
//getSelection gets the selected range of text, when nothing is selected,selection is empty.getRangeAt(0)makes selection from the start of selection.
var keyCode = event.which;
if( store[ keyCode ]) {
var n = document.createElement('span');
//Adds a new html element.
n.innerHTML = store[keyCode];
//Adds the arrow to the newly created html element.
selection.insertNode( n );
//insert the newly created html element.
}
});
Demo Fiddle by Connor →
or http://jsfiddle.net/ybPzP/10/
I used String.fromCharCode(0x2190) to compare left arrow character in javascript. If you alert this then you can see left arrow character is alerted.
Related
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;
I have a web page with a textarea, and I need to capture the keys typed by the user (so that I can substitute different unicode characters for the keys typed). My current code is as follows:
$("#myTextArea").bind('keypress', function(event) {
var keyInput = event.which;
// call other functions
});
This above code works on PCs, and iPhone/Safari. However, it fails when using Chrome on an android (samsung) tablet. For some reason when I type on the android virtual (soft) keyboard, the "keypress" event is not triggered. The android version is 5.0.2.
If I try using "keyUp" or "keyDown", it always returns 229 for all characters (except for return key, space, backspace, etc).
Even though the keyCode is always 229, the textarea displays the correct characters typed by the user. Which means the device knows which key was entered, but somehow I'm unable to get a handle on this event (and the key code) using javascript.
Here are the alternatives that I have tried so far, and their outcomes:
$("#mainTextArea").on("keydown keyup", function(event) {
// event.which and event.keyCode both return 229
$(document).on('keypress', function(event) {
// function is not triggered
$('#myTextArea').bind('input keypress', function(event) {
// comes inside function, but keyCode and which are undefined
Any help regarding this issue is appreciated..
Unfortunately it seems you cannot do much here.
Keypress event is deprecated, thus not fired.
229 on keyup and keydown indicates the keyboard buffer is busy. The reason - when you press a key - the input is still not guaranteed to be what the user pressed, because of auto suggest and other events that may follow immediately and invalidate the event.
Although in my opinion it would have been better to send the key first, then fire another event perhaps on auto suggest so you can act upon it separately...
The only thing that I currently know of is to attach to both - keydown and keyup, store the value on keydown, get the value on keyup and find the delta, which is what user pressed. Unfortunately this will not work for non-input controls (e.g. - the body or something like that).
Maybe not what you want to hear as answer but still.
I came across this discussion while doing research for a project I was working on. I had to create input masks for a mobile app, and Pavel Donchev's answer got me thinking about what could work to capture keys in Android. In my specific project, keydown and keyup would not be enough because keyup event is only triggered after a key is released, so it would imply in a late validation, so I did some more research (and lots of trial and error) with input events and got it working.
var input = document.getElementById('credit-card-mask'),
oldValue,
newValue,
difference = function(value1, value2) {
var output = [];
for(i = 0; i < value2.length; i++) {
if(value1[i] !== value2[i]) {
output.push(value2[i]);
}
}
return output.join("");
},
keyDownHandler = function(e) {
oldValue = input.value;
document.getElementById("onkeydown-result").innerHTML = input.value;
},
inputHandler = function(e) {
newValue = input.value;
document.getElementById("oninput-result").innerHTML = input.value;
document.getElementById("typedvalue-result").innerHTML = difference(oldValue, newValue);
}
;
input.addEventListener('keydown', keyDownHandler);
input.addEventListener('input', inputHandler);
<input type="text" id="credit-card-mask" />
<div id="result">
<h4>on keydown value</h4>
<div id="onkeydown-result"></div>
<h4>on input value</h4>
<div id="oninput-result"></div>
<h4>typed value</h4>
<div id="typedvalue-result"></div>
</div>
The oninput event is triggered right after the keydown event, which is the perfect timing for my validations.
I compiled the whole thing in an article. If you're curious, you can read about it here.
I ran into the same issue. Several explanations are out there but anyhow it seems strange that no solution is offered.
For the moment I solved it by capturing the oninput event.
"This event is similar to the onchange event. The difference is that the oninput event occurs immediately after the value of an element has changed, while onchange occurs when the element loses focus, after the content has been changed"
This event support inserted text too, from pasting text or from corrections & suggestions.
it doesn't give me the perfect solution cause I can only manipulate the text AFTER it has been entered, but for the moment it is the best I have.
If anyone has a better solution I will be glad to hear about it.
just check your input characters keyCode, if it is 0 or 229 then here is the function getKeyCode() which uses charCodeAt of JS to return the KeyCode which takes input string a parameter and returns keycode of last character.
<script>
var getKeyCode = function (str) {
return str.charCodeAt(str.length);
}
$('#myTextfield').on('keyup',function(e){
//for android chrome keycode fix
if (navigator.userAgent.match(/Android/i)) {
var inputValue = this.value;
var charKeyCode = e.keyCode || e.which;
if (charKeyCode == 0 || charKeyCode == 229) {
charKeyCode = getKeyCode(inputValue);
alert(charKeyCode+' key Pressed');
}else{
alert(charKeyCode+' key Pressed');
}
}
});
</script>
There is a textInput event that gives you the entered character
const inputField = document.getElementById('wanted-input-field');
inputField.addEventListener('textInput', function(e) {
// e.data will be the 1:1 input you done
const char = e.data; // In our example = "a"
// If you want the keyCode..
const keyCode = char.charCodeAt(0); // a = 97
// Stop processing if "a" is pressed
if (keyCode === 97) {
e.preventDefault();
return false;
}
return true;
});
I recently implemented a "mentions" feature in the latest version of our Astro AI assisted Email app. Basically you type "#" in our compose web view and you get a list of autocomplete suggestions. We, like most other people, had problems trying to solve this in the Javascript. What eventually worked was a native solution. If you #Override the WebView's onCreateInputConnection() method, you can wrap the super.onCreateInputConnection() result (which is just an InputConnection interface) with a custom class. Then in your wrapper implementation, you can trap input via commitText() or setComposingText() or maybe some other method specific to what you are looking for...like deletes. I don't know if you would get any callbacks on control characters like up and down arrows but maybe this can be a place to start to solve your specific problem.
You can approach it from a different perspective by using the selectionstart property:
Returns / Sets the beginning index of the selected text. When nothing
is selected, this returns the position of the text input cursor
(caret) inside of the <input> element [also applies to <textarea>].
Source: MDN
I do something like this in my own app and it has worked reliably...
document.querySelector('textarea').addEventListener('input', (e) => {
const elInput = e.target;
// Get start of selection (caret offset when typing)
const nSelStart = elInput.selectionStart;
// Get last typed character (modify for your own needs)
const sLastTyped = elInput.value.substr(nSelStart-1, 1);
console.log('Last typed character:', sLastTyped);
});
textarea {
width: 99%;
height: 4rem;
}
<textarea placeholder="Enter something using Android soft keyboard"></textarea>
Codepen: https://codepen.io/thdoan/full/dymPwVY
I FIGURED IT OUT!
Here's a 100% working solution that works EVERYWHERE with EVERY feature, including even emoji suggestions on iOS and any pasted content. I'm using substring comparison to find actual stuff that changed from onInput to onInput.
Points from which to which text is deleted and from which to which it's inserted are pointed out.
Rating and selecting as an answer is appreciated.
var x = document.getElementById("area"),
text = x.value,
event_count = 0
function find_Entered_And_Removed_Substrings(
previous_string, current_string, pos
) {
let
right_pos = pos,
right_boundary_of_removed =
previous_string.length -
(
current_string.length -
pos
),
left_max_pos = Math.min(
pos,
right_boundary_of_removed
),
left_pos = left_max_pos
for (
let x = 0; x < left_max_pos; x++
) {
if (
previous_string[x] !==
current_string[x]
) {
left_pos = x
break
}
}
return {
left: left_pos,
right: pos,
removed_left: left_pos,
removed_right: right_boundary_of_removed
}
}
x.oninput =
(e) => {
// debugger;
let
cur_text = x.value,
positions =
find_Entered_And_Removed_Substrings(
text, cur_text, Math.max(
x.selectionStart, x.selectionEnd
)
)
console.log(positions)
let
entered =
cur_text.substring(
positions.left, positions.right
),
removed =
text.substring(
positions.removed_left, positions.removed_right
)
if (
entered.length >
0 ||
removed.length >
0
) {
document.getElementById("entered")
.innerHTML +=
entered
document.getElementById("removed")
.innerHTML +=
removed
document.getElementById("events")
.innerHTML =
event_count++
}
text = cur_text
}
<textarea id="area"></textarea>
<br/>
<pre id="entered"></pre>
<br/>
<div id="events"></div>
<pre id="removed"></pre>
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 ! ;)
In html form we need to move up or down using arrow keys to shift focus in the fields. following code works fine for all input elements. but in case of textarea there could be multiple lines so, if we move up or down cursor moves up or down in the textarea itself.
Now, I'm not getting how to move up the focus, if the cursor reaches in the first line of the textarea while pressing up arrow OR move down the focus if the cursor reaches at the end or in the last line of the textarea while pressing down arrow key.
Here is my partial code:
var INPUT_SELECTOR = ':input:enabled:visible';
var inputs = $(INPUT_SELECTOR)
$("form").on("keypress", "input", function(event){
// i is current input
if(event.keyCode === 38){ //action == 'up'
inp = inputs[i > 0 ? parseInt(i) - 1 : inputs.length - 1];
}
if(event.keyCode === 40){ //action == 'down'
inp = inputs[i < inputs.length - 1 ? parseInt(i) + 1 : 0];
}
// focus on the input
moveFocusTo(inp);
});
Our requirement for the up and down arrow keys is same as tab and shift+tab behaviour. everything is done, we have just stuck at textarea.
Sounds like you need to use
https://developer.mozilla.org/en-US/docs/Web/API/event.relatedTarget#1003983
event.relatedTarget
Then you can check to see during the event what element the user is focused on and act accordingly.
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).