I recently added some predictive text input fields to the web-app I am supporting.
Big deal, right? Not really, seems like if your web-app doesn't do this -- you are already behind the times and your end-users are complaining. (At least that's how it is over here).
So, my question has to do with the "up" arrow key.
The predictive textbox has a onkeyup listener.
The handler segregates the key strokes and does something depending on the character the user entered.
The up arrow key allows the user to navigate in a div I created loaded with "suggestions."
I have several variables tracking indexes, etc...
Basically, when the user hits the up arrow I will change the id of the div to an id that has some css associated with it that will make the div appear as though it is selected. Additionally I will grab the value in that div and assign it to the textbox where the user is able to type.
The problem is an aesthetic one. Inherently with all text boxes I am learning, the up arrow key will reset the cursor position. This is happening just before I am writing the new value to the text field.
So, on each up arrow stroke, the user is seeing a jumping cursor in the textbox (it will jump to the beginning and immediately it will appear at the end).
Here's the code -
if (event.keyCode === 38 && currentUserInput.length > 0) {
// user has toggled out of the text input field, save their typing thus far
if (currentToggledIndex == -1) {
currentToggledIndex = autoFillKeywordsList.length-1;
savedKeywordUserInput = currentUserInput;
}
else {
// revert currently selected index back to its original id
document.getElementById("kw_selected").id = "kw_" + currentToggledIndex ;
// user has toggled back into user input field
if (currentToggledIndex == 0) {
currentToggledIndex = -1;
}
// user has toggled to the next suggestion
else {
currentToggledIndex--;
}
}
// 2. Determine next action based on the updated currentToggledIndex position
// revert the user input field back to what the user had typed prior to
// toggling out of the field
if (currentToggledIndex == -1) {
element.value = savedKeywordUserInput;
}
// mark the toggled index/keyword suggestion as "selected" and copy
// its value into the text field
else {
document.getElementById("kw_"+currentToggledIndex).id = "kw_selected";
element.value = autoFillKeywordsList[currentToggledIndex];
}
// 3. Determine what the user can do based on the current value currently
// selected/displayed
displayAppropriateButtonActions(element.value);
}
The funny thing is - the "down" arrow works perfectly since by default the down arrow key will place the cursor at the end of the string currently located in the textbox.
Ok, so things that I have already tried -
event.preventDefault();
event.stopPropogation();
I also tried to set the cursor position PRIOR to setting the new value to no avail using a setCursorPosition function I found on another post here. (Yeah, I was reaching with this one)
I tagged this as JavaScript and Jquery. I prefer to use JavaScript, but open to suggestions in Jquery too!
As Ryan suggested. how I achieved this in angular 4.x is
.html
<.. (keydown)="keyDownEvent($event)" >
.ts
keyDownEvent(event: any){
if (event.keyCode === 38 && event.key == "ArrowUp")
{
event.preventDefault();
//logic..
}
I think what you can do is when they move the cursor, grab that and find out what element it is ... then store it in a variable and focus() it and erase it and then put the value you stored back into it.
var holdme = $("#myelement").val();
$("#myelement").focus().val('').val(holdme);
This works for me when having weird cursor issues in jquery/javascript most of the time. Give it a try and if it doesn't work, let me know and I'll see what else might be wrong.
I found that it worked well to capture the caret position, blur, restore the caret position, then focus again.
myTextInput.onkeydown = function(e){
//some other code
if(e.key == "ArrowDown" || e.key == 40 || e.key == "ArrowUp" || e.key == 38){
var caretPos = this.selectionStart;
//do your stuff with up and down arrows
e.preventDefault();
this.blur();
this.selectionStart = caretPos;
this.selectionEnd = caretPos;
this.focus();
}
}
The caret will very briefly disappear, but I think you have to be incredibly observant to notice.
Related
Is there a way to enforce the onscreen keyboard on Android/iOS browsers to become visible when a no page element has focus, or the active input is disabled, or is set to be readOnly?
I use a Bluetooth barcode scanner. If this device is connected it represents a hardware keyboard. I already have a Javascript listening to KeyDown events. On desktop computers this works fine, so only if no input or textarea is active or the active element is readOnly, it begins capturing the key input in a hidden field to be processed later.
If however an input or textarea is active the user should be allowed to enter, append or edit the scanned input manually. But when the barcode scanner is connected in some cases the onscreen keyboard will not show up, because the browser expects the hardware keyboard to be in charge or is designed to ignore a readOnly field.
On a desktop computer one can do this with the main hardware keybaord. But to give the users the option to choose between both ways of entering data on a tablet or smartphone, I need to be able to show/hide the onscreen keyboard with a toggle while the bluetooth barcode scanner is connected and the active element might be readOnly.
The script so far is:
document.addEventListener('DOMContentLoaded', () => {
'use strict';
document.addEventListener('keydown', event => {
const charFilter = "abcdefghijklmnopqrstuvwxyzäöü1234567890_-+=%$##&!?.ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜß";
const key = event.key;
const ael = document.activeElement;
const qrf = document.getElementById('qr_scanner_form');
if ((ael.nodeName=='INPUT' && ael.readOnly==false) || (ael.nodeName=='TEXTAREA' && ael.readOnly==false)){
if(ael != qrf.qr_scanner_input){
// clear the hidden element from any previous input
qrf.qr_scanner_input.value='';
// abort further processing
return;
}
}
if(key === 'Enter'){ // pre capture an enter key
var clean = qrf.qr_scanner_input.value.split("´");
qrf.qr_scanner_input.value = clean.pop(); // only process the last item
if(qrf.qr_scanner_input.value != 'stop' && qrf.qr_scanner_input.value != ''){
pWaitForServer(1); // starts hourglass animation
document.body.style.cursor = "wait";
setTimeout(function(){ pWaitForServer(0);qrf.submit(); }, 1357); // stops hourglass animation and proceeds
}
}else{
// check for unwanted characters, abort if nececary
if (charFilter.indexOf(key) === -1) return;
// append hidden element with last key
if(ael!=qrf.qr_scanner_input) qrf.qr_scanner_input.value+=key;
}
});
});
document.body.style.cursor = "auto";
I found many suggestions to hide or disable the onscreen keyboard, but I basically need to do the reverse and found nothing about it. My thoughts are going toward using a clipboard, copy the scanner input, setting the readOnly of the clicked element to false and then allow the last hardware input to be pasted there. I Hope somebody can give me a nudge toward a nicer solution.
I was hoping I could get some help with a problem I've been having. I would like to set a small delay when the user presses the TAB button before the next input field found on the webpage is selected. The reason for this is that I am using AJAX to build an order form field, with expandable and collapsible tables that will be taking input from the user.
I have it set up using onfocusout to call a function that expands the table below the current table the user is in after they leave the last field, and would like the first input field to be auto selected right after. Everything works as should when the user exits the last field of a table by clicking out of it, but when tabbing out of it I believe there is a problem because there is not a field to be tabbed to until after it retrieves the next table to display using AJAX.
I can provide code if needed, but I think I'm missing something, rather than having a mistake somewhere. Help would be appreciated, and I'd be more than happy to clarify anything that is unclear. Thank you!
So, I've created a very basic example of how this can work. My example only accounts for inputs, but you can play with the code to do what you need.
Warning: Preventing natural browser behavior can be tricky so pay attention to other issues this could cause.
Here is a Fiddle showing how you can do this: https://jsfiddle.net/iamjpg/wme339h9/
document.onkeydown = TabExample;
// Basic function
function TabExample(evt) {
// Capture event
var evt = (evt) ? evt : ((event) ? event : null);
// Tab keycode constant
var tabKey = 9;
// If key is tab...
if (evt.keyCode == tabKey) {
// Prevent the next focus.
evt.preventDefault()
// Grab current focused element
var focusedElement = document.activeElement;
// Get array of inputs
var inputs = document.getElementsByTagName('input');
// Set delay of 2 seconds.
setTimeout(function() {
// Loop through inputs
for (i = 0; i < inputs.length; i++) {
// If current evaluated input is one with focus...
if (inputs[i] === document.activeElement) {
// Grab the next index...
var focus_input = i + 1;
// Assure it isn't undefined.
if (inputs[focus_input]) {
// Give new input focus.
inputs[focus_input].focus();
}
}
}
}, 2000)
}
}
Good luck!
As you tab between input fields in a browser, the browser will automatically scroll the nearest parent container to place the next focused field within the view.
Simple JSFiddle: http://jsfiddle.net/TrueBlueAussie/pxyXZ/1/
$('.section').eq(6).find('input').focus();
For example if you open the above fiddle it selects "Sample item 7" at the bottom of the yellow window. If you press tab the "Sample text 8" field jumps up towards the middle of the parent window.
Obviously this is a great thing for normal websites, but I have a custom scrolling container in which I position & scroll everything manually. I am tracking focus changes and will use a momentum scroller to bring it into view, but how do I disable the default scrolling behavior of web-browsers? Happy to accept CSS, Javascript or JQuery solutions.
This is just winging it based on my comment above:
$('input').on('keyup',function(e){
if(e.keyCode === 9) {
var $this = $(this);
// (do your scroll thing here
// ..., function(){
$this.parent().next().find('input').focus();
// });
}
});
Long as the callback timing is correct, this will only change focus after you have already scrolled. You'll need to do your own magic to determine what to scroll to, but this should give you the focus behavior you want.
Turns out you can't smooth scroll for focus changes as the events happen in the wrong order. You get an awful delay while it scrolls the field into view, before focus is set. A better move of the item onscreen, or superfast scroll, is all we can hope for.
As suggested by PlantTheIdea (+1'ed), you need to catch the TAB key and find the next focusable item, bring it into view, then set focus to it.
In practice there are a number of issues to resolve:
Change of focus occurs on TAB keydown (not keyup).
Only match non-hidden inputs (lots of web apps have hidden fields that will go bang if you try to focus them).
Allow for the selection to tab off the first or last item on the page (otherwise the browser loses the ability to tab to its address bar)
use e.keyCode || e.which to allow for older browsers
catch event at document level to allow for cases of other inputs, outside of the scrolling area, causing it to enter the scrolling area (first or last input).
The final code looks like this:
$(document).on('keydown', ':focus', function (event)
{
if ((event.keyCode || event.which) == 9)
{
var $inputs = $(":input:not(hidden)")
var index = $inputs.index(this);
// Index previous or next input based on the shift key
index += event.shiftKey ? -1 : 1;
// If we are in the range of valid inputs (else browser takes focus)
if (index >= 0 && index < $inputs.length)
{
var $next = $inputs.eq(index);
event.preventDefault();
// Move, not scroll, to the next/prev item....
MoveIntoView($next);
$next.focus();
return false;
}
}
});
I have implemented the AJAX search which is similar to the example here. In this example, you might notice that you can switch between the search results using TAB key. In my search results, there is a table in the following format:
*Client* *Status* *Hostname*
<client1> value value
<client2> value value
<client3> value value
Client1, client2, client3 are actually hyperlinks and are in the class search_result_entry. So, when down arrow key is pressed, I would like the focus to go to the client1 link. The TAB key works here, but the arrow key would be more intuitive. The values in status and hostname are not clickable. Also, note that I am using overflow: auto so if there are too many search results, the scrollbar shows up. In this case, hitting the TAB key twice gets me to the first search result.
I was doing trial and error and tried the following code, but it did not work:
if (e.which == 40){ // 40 is the ASCII for down arrow key
$("#keyword").focusout();
$("#results").focus(function(){
$(this).next("td").focus();
});
}
How do I get the focus to move to the search results using the down arrow key and navigate in it using the down/up arrow keys?
//Keep track of the focused element
var focusedElement = null;
// update it on focus
$("#results").focus(function(){
focusedElement = this;
});
And somewhere in your handler:
//... code
if (e.which == 40){ // 40 is the ASCII for down arrow key
if(focusedElement) $(focusedElement).next().focus();
else $('#results').somethingToGetYourFirstElementDependingOnYourCode().focus();
}
//... more code
The first part will keep track of the currently focused element (if any) and the second part will update the focused element (which will trigger the first part and update the currently focused element)
We're having problems trying to implement a word counter similar to the one on twitter which decrements when the user types in the text field. Unfortunately it's quite glitchy as once you've deleted all the characters via backspace it displays that you only have 84 characters left (unless you hit backspace again). If you it the delete button the counter goes down even when it has removed nothing from the screen at the end of the text(I'm guessing it removes the 'invisible' character that is causing it to say 84 instead of 85 in the example before). All I want if for it to operate like the one on twitter
/* Limit textarea for cancelation policy to 85 characters*/
$('.counter').text(85-($('#txtCpolicy').val().length));
$('#txtCpolicy').keydown(function(e) {
var current = $(this).val().length;
$('.counter').text(85 - ($('#txtCpolicy').val().length));
if(current >= 85) {
if(e.which != 0 && e.which != 8) {
e.preventDefault();
}
}
});
Hope you can help
Change your code to keyup instead of keydown. Keydown triggers before the text is added to the textarea. Seems to work once you change it.