keyboard friendly AJAX search - javascript

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)

Related

Ace Editor (javascript): Triggering a tab press event for Ace Editors event handlers (not just inserting '/t' or spaces)

I am using Ace Editor to build a code replay program. I store all the keys pressed when you type code, and then I replay them in Ace Editor. I have finished storing and replaying all keyboard/mouse input, but am having issues replaying tab presses.
Ace Editor handles tabs within a textarea DOM. The default behavior for a textarea when tab is pressed is to move to the next DOM, so I know they are using preventDefault() and using their own handler in order to allow softTab (insertion of 1,2,3, or 4 spaces before all highlighted text).
My goal is to cause Ace editor to trigger the tab event - such that whatever is currently highlighted in the Ace editor is tabbed over the correct number of spaces. Does anyone know how to do this?
Here are a list of options I've tried and why they don't work:
Store tab presses on keydown and then calculate the column value and insert the spaces in that location. BUT - this fails when you have some text half highlighted. The correct functionality should shift the entire word over, but this would just insert spaces in the middle of the word.
Store the location and keys pressed whenever editor.on('change', some_event_handler) fires, which gives me exactly what was input and the location (perfect for replay) except it doesnt tell me whether tab or spacebar was pressed (it will fire for both and spacebar is already handled). Plus this still inserts spaces at the location (potentially in middle of a word instead of shifting word over) as in number 1.
For example:
editor.getSession().on('change', function(e) {
if (handlers) {
var text = e.data.text;
if (text == ' ' || text == ' ' || text == ' ' || text == ' ') {
//FAILS because it doesn't know if its space or a single space tab.
Try to trick Ace Editor to trigger a tab by storing '/t' and inserting it into the ace Editor.
For example (storage code):
function keypress_handler(e) {
var key = e.which;
var text = String.fromCharCode(key);
switch(key) {
case 9: //Tab
text = '\t'; // manually add tab
//Code to store this event for replay later
break;
}
For example (replay code):
// Assuming the cursor/selection is in the correct position
editor.insert(log.text);
At this point, I was beginning to think about building tab from scratch (when to shift multiple things if multiple lines are selected, how far to shift, how to handle if a word is half highlighted when tab is pressed), but Ace clearly already does this when tab is pressed, so I would like to just trigger the tab press. Normally to trigger a tab press, I'd simply do:
// trigger an artificial Tab Keydown event for Ace Editor using jQuery
var tab_press= $.Event('keydown');
tab_press = 9; // Tab keycode
$('.editor').trigger(tab_press);
But this causes results in no behavior. Any suggestions?
I read through the source code here:
https://github.com/ajaxorg/ace/blob/master/lib/ace/commands/default_commands.js
And found the following snippet of code:
{
name: "indent",
bindKey: bindKey("Tab", "Tab"),
exec: function(editor) { editor.indent(); },
multiSelectAction: "forEach",
scrollIntoView: "selectionPart"
}
Thus, to trigger a tab (that works in all cases), simply call:
editor.indent();
How incredibly simple - wish there was some documentation out there for this so that many hours could have been spared.
In Ace all of the user input from keyboard is processed via commands. This is used in Ace to record and replay macros see https://github.com/ajaxorg/ace/blob/v1.1.4/lib/ace/commands/command_manager.js#L52-L96.
If you want to record user input and then to replay it you can use
// record
commands=[]
editor.commands.on("afterExec", function(e) {
commands.push({name: e.command.name, args: e.args})
});
// replay
commands.forEach(function(e) {editor.execCommand(e.name, e.args)})
Capturing mouse input is a bit tricker, but from question it seems you already know how to do it.
This pull request is somewhat related to your question. It allows to emulate user input by calling simulateKeys("a", "b", "ctrl-Left", "Tab")

prevent "up arrow" key reseting cursor position within textbox

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.

How to stop browser auto scrolling containers on focus changes

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;
}
}
});

Detecting when an element is about to get focus

I have an ASP.NET page with a Telerik RadEditor (rich text box). When tabbing through a page, when a user gets to the text box, focus gets set to the various toolbar icons before it goes to the textarea. I added some jQuery to one page to set the focus on the text area when tabbing out of the last cell on a form:
$('input[type=text][id*=tbCost]').keydown(function (e) {
var keyCode = e.keyCode || e.which;
if (keyCode == 9) { //If TAB key was pressed
e.preventDefault();
var editor = $('body').find("<%=RadEditor1.ClientID%>"); //get a reference to RadEditor client object
editor.setFocus(); //set the focus on the the editor
}
});
I am looking for a way to implement this functionality in the control so that it will work regardless of the page it is on. For example, in the above code, focus is only set if the user is tabbing out of the tbCost cell. I would like to be able to set the focus to the text area when a user tabs into the toolbar items.
Is there any way to detect when an element is about to get focus? I know I can see if an element has focus, but I can't think of a way to implement this functionality.
Thanks
Solution:
If anybody has this same question in the future and wants an example, here is the code I used:
$(document).ready(function () {
$('.reToolCell').focusin(function () {
var editor = $('body').find("<%=RadEditor1.ClientID%>");
editor.setFocus();
});
});
You might consider binding to a focus on the toolbar icons and redirecting focus to the text area. Although this might have unintended side effects if users are trying to tab-focus these tools in order to use them.
//on focus eventHandler for all your icons that calls a function
#('.elementtype, class or a generic way of identifying the icons'.onfocus(myFunction(this))
//the function take a parameter of your element, moves to the next sibling element and sets the focus
myFunction = (element) {
element.next().focus();
}

Capture delete key if not focused in a form

I have a web app which plots points with SVG, I want to add the ability to delete a selected point by pressing the delete key. I can capture delete keydown (or up) for the entire document and prevent the default event (Chrome's default behavior is to go back a page), however this obviously blocks all delete events so the delete button no longer works in forms.
Is there a way to set it up so that the delete key works as intended in forms/inputs but when anywhere else in the app it can be used as a custom delete function?
The first thing that came into my mind, is to stopPropagation on the input and textarea fields, then the preventDefault should not be triggered on the document.
JQuery pseudo code:
$('input, textarea').keypress(e, function(e){e.stopPropagation();});
$(document).keypress(e, function(e){if(delete) e.preventDefault();});
Another possiblity is the check the orignal target on the key event of the document.
Event callback:
var originalElement = e.srcElement || e.originalTarget;
if(orignalElement.tagName === 'INPUT' or orignalElement.tagName === 'TEXTAREA'){ return; }
// else do your delete key stuff
The first line should be obsolete, if you are using jQuery, because it normalized the event for you, and you can use e.target to get the originalTarget
My prefered approach would be something like this:
$(window).keyup(function(e) {
if(e.keyCode == 46 && $("input:focus, textarea:focus").length == 0) {
e.preventDefault();
alert("delete key pressed!");
}
});
However, I'm not sure if you'll be able to override the back button behaviour - it seems unlikely that Chrome would allow it, given the potential for abuse.

Categories

Resources