I use ReactJS in order to make an input box which automatically takes care letter-to-letter substitutions (so that users do not need to add a specific language to their OS in order to type in that language).
The input uses JS to transform all letters from their English to their Greek equivalent using a simple substitution matrix.
The problem is that preventDefault() (which I use in order to prevent the original letter from appearing) is also preventing my input from automatically scrolling to current-cursor-position.
Is there a way to programmatically scroll my input to the current cursor position? Any alternatives?
Here is a JSFiddle which illustrates the problem:
https://jsfiddle.net/sgouros/mnzw56s9/
substituteKey = event => {
let letterToAdd = this.ÎșeySubstitutions[event.keyCode];
event.target.value += letterToAdd;
this.handleOnChange(event);
// the following line prevents input from scrolling
event.preventDefault();
};
To reproduce:
Type letters until input fills up. No scrolling takes place
Press 'a' (deliberately left out of the substitutions). The input scrolls as expected (preventDefault is deliberately not being called for letter 'a' for illustration purposes)
It seems that I have to focus on my input and then scroll to the left for as long as the scroll width is. For ex:
handleKeyDown = event => {
if (this.ÎșeySubstitutions[event.keyCode]) {
// this calls preventDefault()
// but we will fix in a bit it by manually scrolling the input
this.substituteKey(event);
}
// SOLUTION: these 2 lines manually scroll the input
// be sure to add ref="myInput" when rendering your input
this.refs.myInput.focus();
this.refs.myInput.scrollLeft = this.refs.myInput.scrollWidth;
};
Many thanks to https://stackoverflow.com/users/2533679/stephen-kaiser
who answered a very similar question here:
"Scroll" to the very right of a long text input
Related
How would you go about updating a form template (handlebars) each time the data updates and keep the input element focused? In my code sample you will find a form field to which is attached a keyup event listener that updates the data.
But on each update I am re-rendering the form which causes the input to lose focus.
JavaScript
import "./styles.css";
import $ from "jquery";
import Handlebars from 'handlebars';
const updateHtml = data => {
const app = $('#app');
const html = template(data);
app.html(html);
}
const app = $('#app');
const data = {
foo: 'default value',
}
const template = Handlebars.compile(`<form><input type="text" id="foo" value="{{foo}}"></form>`);
updateHtml(data);
app.on('change keyup', function(e){
const val = $(e.target).val();
data.foo = val;
updateHtml(data);
});
I have created a codesandbox for you to test https://codesandbox.io/s/magical-leftpad-ry2lx?file=/src/index.js
Try typing in the input and you will find it loses focus after a keyup. So, how to solve this problem so that I can write in the input and the data object updates itself according to the inbox?
Update
I would like to provide more context on the actual application I am developing. I cam creating a complex form that includes, checkboxes, radio buttons, repeater fields which changes inputs depending on another input (conditional fields).
I do not want to update dom elements (adding/inserting/removing) by event listeners. Dom should update when data updates. That is why I am using handlebars templating system.
I am not sure that this is the best implementation for what you want to achieve. As the complexities of the form have been left out of the example, it is difficult to properly assess this solution and to propose an alternative.
The problem with this implementation is that you are removing your form element and creating a new one upon each change and keyup event. This gets to the heart of your question about your input losing focus. The input is not losing focus; the input is being removed and replaced with a new element. The only way for the new input element to have focus is for you to directly add it via code.
As you are using jQuery and have given an ID of "foo" to your input element, you can assign it focus using:
$("#foo").focus();
However, setting focus to an element will place the cursor at the beginning of the input text and this will be painful to your user as the standard behaviour is for the cursor to remain at the end of the text; allowing the user to continue typing without first moving the cursor.
Setting the cursor position is possible via code as well. Here is a link to where the question has been asked and answered before: Use JavaScript to place cursor at end of text in text input element
The jQuery code to set the focus and set the cursor would be:
$("#foo").focus().get(0).setSelectionRange(-1, -1);
I have forked your codesandbox and added this line.
This code may work for the simplified example you have provided, but I fear it won't scale.
For example, if your form was to include even just a second text input, you would need to track which input was being changed in order to set the focus and cursor on the correct input, rather than jumping focus to the other.
I think a better solution would be to show/hide dependent parts of the form on change events rather than to replace the entire form on every change and keyup event.
Is there a way to detect where in an input element's content a user has clicked? Specifically in Firefox?
I need to know not where the caret is but where the caret would be when the user clicks into an input element.
I am trying to fix a bug in firefox where the user cannot click to place the caret into an input element which has had '.select()' called on it -- the caret fails to appear in firefox, so I want to place it manually if possible.
Thanks!
You can get the pixel position of the user's click (relative to the input field) by reading the click event's offsetX and offsetY:
// get the click position:
document.getElementById('test').onclick = function(e) {
console.log(e.offsetX, e.offsetY)
};
// for testing the 'select' issue:
document.getElementById('btn').onclick = function() {
document.getElementById('test').select();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="test">xxxxxx</textarea>
<button id="btn">Select</button>
Converting that to the desired caret location is not easy, though, because it will depend on font sizes and the text content of the input field. The best I can think of would be to do something like the technique used in textarea-caret-position, except iterating through every possible caret position in the textarea to find the one closest to where the user clicked. Which is almost certainly overkill for the task you have in mind.
(For what it's worth, the current version of firefox (v57) does not seem to have any trouble placing the caret correctly whether the input field is selected or not. I'm not certain whether this was the case in previous versions.)
Found the root of the problem, some bad css had set text-select to auto on input elements. Changing it to text-select:text allowed the fix I used for Safari to work in Firefox as well.
Let me start off saying that I am new to javascript.
I am trying to change a div's content to whatever letter I type in. So if I press "p" anywhere on the page, the div text will add that letter.
For example:
<html>
<head>
<script>
document.getElementById("change").onkeyup = function() {myFunction()};
function myfunction(){
}
</script>
</head>
<body>
<div id=change>p</div>
</body>
</html>
The function is where I am stuck.
I have seen a lot of examples involving typing in a textbox but none without one. If someone could guide me in the right direction, I would be very grateful.
Why does your code not work?
You can't use the keyup event on the <div> element as you have tried to do, because JavaScript will never fire it as the div is never focused. Elements have to be focused to receive keypress events, otherwise how would the user know where their keyboard was going to type to.
What can we do about that?
One element that always "has focus" is the <body> of the webpage (unless of course, you are using a different area of your web browser, such as bookmarking a site, navigating with the URL bar, etc).
So, we can detect the event on the body of the page, and then change the content of the div accordingly. We can detect the event using document.body.onkeyup = function(event), and then get change the div content using document.getElementById("change").innerHTML, which targets that div by it's ID, and then sets it's HTML value to something new.
However, JavaScript will only send back the code of the key that was pressed (it's internal representation of the key), not the character which the key represents - (this is actually useful if you are trying to detect if a key like backspace or ctrl has been pressed). We can get this value from the event using event.keyCode.
Thus, we will have to transform that into a string, which is the final piece of our code : String.fromCharCode(event.keyCode). This transforms a character code into a string.
All together, we can update the value of the div in response to a key press.
Working Example
document.body.onkeyup = function(event) {
document.getElementById("change").innerHTML = String.fromCharCode(event.keyCode);
}
<div id="change">p</div>
Notes
You can use onkeypress rather that onkeyup if you want to detect the case of letters;
You can use += rather than = if you want to append what you have typed now to what is already in the div;
If you want to just use a div as a place where you can type, check out contenteditable;
If you want a list of keycodes that do not map to a string value, check out this list.
If you want to add the letters as you press you can add use +=. Also onkeypress is case sensitive while onkeyup will give you capital letters.
document.body.onkeypress = function(event) {
document.getElementById("change").innerHTML += String.fromCharCode(event.keyCode);
}
<div id="change">p</div>
I've been struggling with getting a field working properly. This field displays a lot of data, and the user wants to select and copy a large portion of it. The data is basically a big list and the user wants to select all entries below a certain point. The way that they achieve the selection is by highlighting a word or two in the first entry they want then pressing ctrl+shft+end to select everything to the bottom. This was working until a new feature on the page was added below the contents of the list. Now the hot key select also selects the contents of the rest of the page.
The current implementation is simply :
<div id='diff-contents'>[content here]</div>
<div id='trailing-content'>blah blah blah...</div>
I have tried a read-only input field:
<input id='diff-contents' value='[content here]' readonly/>
This works in Firefox to some extent however the contents contains HTML, and the input field show html literally, not rendered. In addition to that Chrome doesn't show a blinking caret and the hot keys do nothing, so the input field is sadly not viable for me in this situation.
How can I make a selectable field that maintains focus for the cursor and shows a blinking caret but is not editable using javascript, CSS, HTML, or JQuery?
Edit: jsfiddle example that should clarify a bit.
Look at these questions how to determine the current selection: Getting selected text in a browser, cross-platform
The next step is to create a new range which starts at the end tag of #diff-contents. With this information, you should be able to extend/modify the existing selection.
I suggest to either add a button to the UI or use JavaScript with a key-press handler to trigger this code.
With that, the correct amount of HTML should be selected. Users can then copy that into the clipboard with Ctrl+C.
#Aaron Digulla mentioned key listeners, and that got me thinking about simply stopping the events.
The diff-content element is still a div but it is set to editable. This gives both HTML rendering and a blinking caret.
$(this).keydown(function (event) {
if (document.activeElement.id == 'diff-content') {
if (!allowedKeys(event.keyCode)) {
//The only other key presses that should be processed are ctrl+c (keycode 67) and ctrl+a (65)
if (!event.ctrlKey || !(event.keyCode == 67 || event.keyCode == 65)) {
event.preventDefault();
}
}
}
});
The javascript adds a keydown event listener to the entire page. This is necessary since if you just add it to the element, the event has already propagated through the rest of the page and will still be processed, and this was causing funny issues for me. Next we check if it's the diff-content that is active since we want other input elements to still operate normally. Then we check if the key event is an allowed key (tab, home, end, arrows). And finally, check for ctrl+c and ctrl+a and allow those too. I tried event.stopPropogation() and event.stopImmediatePropogation(), and neither of those worked, but preventDefault did.
Lastly, I added style="outline-style:none" to the element so that the blue border would not appear when the element has focus.
The only issue that I have yet to resolve is that since it is editable, the browser still allows you to select and then right click to either cut or paste, which will allow you to alter the text.
Here is the final jsfiddle for what I am using: http://jsfiddle.net/wh3nzmj8/12/
I'm developing a virtual keyboard in jQuery and my problem is:
When I click on a key of the keyboard the input loses the focus during the click, and if the number of letters in the input is longer than the input size, the input shows the beginning of the string. And then when the click is released the input gets back the focus and the caret comes to the end of the string. So it's quite ugly because we have the impression that the input contents blink.
theButtonDiv.click(function() {
attachedInput.value = idOfAttachedInput.value + theActualKey;
attachedInput.focus();
});
So I would like to prevent the input from losing the focus when we clicked on a button of the keyboard.
How can I do this?
Thanks.
One way is to listen for the "mousedown" event on the top level keyboard node and call preventDefault on the event object.
I think you're looking for a CSS property called outline.
#yourinput {
outline: none;
}
That should take care of the box being highlighted when it has focus.