Select Li Element With Arrow Keys (Up and Down) Using JavaScript (Jquery)

Select li element with arrow keys (up and down) using (jQuery) from li tag in ajax response

If i understood you correctly, if you add this code to your ajax call, it should do the trick.

From here on, you do what you need to do with the elements.

$(window).on('keydown', function(){
switch(event.keyCode)
{
case 40:
if($("li[active]").length == 0)
$("li:first()")
.attr('active','1')
.css('color','red');
else
$("li[active]")
.removeAttr('active')
.css('color','green')
.next()
.attr('active','1')
.css('color','red');

break;
case 38:
if($("li[active]").length == 0)
$("li:last()")
.attr('active','1')
.css('color','red');
else
$("li[active]")
.removeAttr('active')
.css('color','green')
.prev()
.attr('active','1')
.css('color','red');

break;
}
$("#hidden_id").get(0).value = $("li[active]").data('id');
});

Found out that there is a modern way to do this, so here you go.

$(window).on('keydown', function(e){
switch(e.key)
{
case 'ArrowDown':
if($("li[active]").length == 0)
$("li:first()")
.attr('active','1')
.css('color','red');
else
$("li[active]")
.removeAttr('active')
.css('color','green')
.next()
.attr('active','1')
.css('color','red');

break;
case 'ArrowUp':
if($("li[active]").length == 0)
$("li:last()")
.attr('active','1')
.css('color','red');
else
$("li[active]")
.removeAttr('active')
.css('color','green')
.prev()
.attr('active','1')
.css('color','red');

break;
}
$("#hidden_id").get(0).value = $("li[active]").data('id');
});

You can break down that chain of commands using variables, i didnt because i don't know what you will do with the elements, but it's fairly easy to adapt from here.

I painted the LI text in red or green, cause i didn't know what you want to happen when selected.

Navigate into layer using arrow key

Here's a workaround to get your goal achieved :

Q1 : "When you arrow down the window also scrolls down with it even though I added prevent default"

A : a long answer that can be applied (parts of it) to the other question. That's the logic behavior to occur when pressing arrow keys and you have attached a keyup listener but that event can't be undone as the key is already pressed :

  • keyup can catch arrow keys but can't be undone ==> not an option.

  • keypress can be undone but can't catch arrow keys ==> not an option.

  • keydown can catch arrow keys and can be undone ==> what we need, perfect.

    So keydown is the qualified event but so let's assume you changed the events to keydown :

  • imagine you have focused the textarea#searchTerm and pressed the down arrow to start navigating in the autocomplete box, the event won't trigger (due to preventDefault call).

  • we can't even start navigating with arrow keys after calling preventDefault.

A solution is to think a bit wisely :

  • attach a keydown handler to the window that checks if the current focused element in the page ($(document.activeElement)) is either the form#searchForm or one of its children (like the autocomplete items) and the key that being pressed is the up/down arrow then we prevent that event thus no scrolling down when scrolling into the autocomplete list.
  • attach a keydown handler to the form#searchForm that sees if the key pressed isn't up/down arrow then stop the event propagation to prevent getting bubbled to the window thus allowing us to write and navigate in the autocomplete list (remember we have preventDefault called in the handler for the window that can prevent us from writing).
  • a final handler for keydown attached to textarea#searchTerm (even though an input[type="text"] seems to be more suitable preventing line breaks, that may appear in a textarea, at least) that has a simple and important task which is while focusing (writing in) the field and the up arrow gets pressed we don't allow the autocomplete list last item to be selected.

the ordering when adding these handlers is very important : first attch form#searchForm handler, textarea#searchTerm's and then the handler for the window.

Q2 : "When you get to the end of the list it loops back round. It needs to stop at the end"

A : in the Navigate function we see if the displayBoxIndex variable (that tracks which item to be selected) has reached the last item and the down arrow was pressed then we just return to quit the function.

Q3 : "When you go back to the first element in the list - then the next UP action - should take you back into the input box"

A : also in the Navigate function we check if displayBoxIndex variable is at the first item and the up arrow was pressed then we trigger focus for the text field, remove the selected class from the autocomplete items, reset displayBoxIndex variable to -1 (ensuring we start from the first item when the down arrow gets pressed while typing) and finally return to halt the function.

this line displayBoxIndex += diff must appear after the above two condition in order to get correct calculation for which element we're selecting and the next one to be selected.

So, here's a demo to illustrate, it contains a wealth of helpful comments that may assist you while reading, also some covers some changes made into the code that I didn't cover above :

const form = $('#searchForm'),
searchTerm = $('#searchTerm'),
autoc = $('#autoComplete'),
jqWin = jQuery(window),
oBoxCollection = jQuery('.ac-list'),
cssClass = 'selected',
Navigate = diff => {
/** focus the text field when in the first item and the up arrow was pressed **/
if (displayBoxIndex === 0 && diff === -1) {
searchTerm.trigger('focus');
oBoxCollection.removeClass(cssClass);
displayBoxIndex = -1;
return;
}
/** prevent looping back when reached the last item in the autocomplete box **/
if (displayBoxIndex === oBoxCollection.length - 1 && diff == 1) return;

/** make the navigation **/
displayBoxIndex += diff;
oBoxCollection.removeClass(cssClass)
.eq(displayBoxIndex)
.addClass(cssClass)
.children('a')
.focus();
};

let displayBoxIndex = -1;

/** attaching events with respect of ordering **/

/** 1: keydown listener on the form **/
form.on('keydown', e => {
const upOrDown = [38, 40].indexOf(e.which) != -1;
/** is it the up/down key **/
!upOrDown && (e.stopPropagation());
/** if no (another key) just stop the event propagation so the one attached to the window won't be fired **/
upOrDown && (Navigate(e.which == 40 ? 1 : -1));
/** if yes we call we call Navigate (if we gets here to the ternary operator the e.which is either 40 or 38) **/
e.which == 27 && (autoc.slideUp(400, () => {
searchTerm.blur();
displayBoxIndex = -1;
oBoxCollection.removeClass(cssClass);
}));
});

/** 2: keydown listener on the text field **/

/** prevent going to the list box bottom when pressing up arrow **/
searchTerm.on({
keydown: e => {
e.which == 38 && (e.stopPropagation());
},
focus: e => {
/** show the autocomplete box if is hidden **/
autoc.is(':hidden') && (autoc.slideDown(400));
displayBoxIndex = -1;
} /** reset navigation position (resets to the first item in the autocomplete box) when the text field is focus **/
});

/** 3: keydown listener on the window **/
jqWin.on('keydown', e => {
const focus = $(document.activeElement);
/** get the current focused element in the page **/
[38, 40].indexOf(e.which) != -1 && (focus.is('#searchForm') ||
form.has(focus).length) && (e.preventDefault());
/** prevent scroll when navigating in the autocomplete box (the scrolling you asked about in your first question) **/
});
/** basic styling to appear like a real autocomplete box. Doesn't affect the main functionality required **/

* {
box-sizing: border-box;
margin: 0;
padding: 0;
}

body {
height: 300vh;
}

.wrapper {
position: relative;
display: flex;
width: 250px;
justify-content: center;
align-items: flex-start;
margin: 15px auto;
}

.wrapper textarea {
display: block;
width: 100%;
height: 35px;
padding: 8px 4px;
border: 2px solid #181818;
resize: none;
transition: all .4s 0s ease;
}

.wrapper textarea:focus {
background-color: #ccc;
}

.wrapper #autoComplete {
position: absolute;
width: 100%;
max-height: 150px;
overflow-y: auto;
top: 100%;
left: 0;
box-shadow: 0 8px 25px -8px rgba(24, 24, 24, .6);
}

.wrapper ul {
list-style-type: none;
background-color: #181818;
}

.wrapper ul li {
display: block;
margin-bottom: 4px;
transition: all .4s 0s ease;
}

.wrapper ul li:last-child {
margin-bottom: 0;
}

.wrapper ul li:hover,
.selected {
background-color: #f00;
}

.wrapper ul li a {
display: block;
padding: 4px 15px;
color: #fff;
text-decoration: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- added a wrapper div in the form just to simplify the styling it has nothing related to the functionality -->
<form id="searchForm">
<div class="wrapper">
<textarea placeholder="Enter search terms..." id="searchTerm" name="searchTerm"></textarea>
<div id="autoComplete">
<ul>
<li class="ac-list"><a href="#">autocomplete item 1</a></li>
<li class="ac-list"><a href="#">autocomplete item 2</a></li>
<li class="ac-list"><a href="#">autocomplete item 3</a></li>
<li class="ac-list"><a href="#">autocomplete item 4</a></li>
<li class="ac-list"><a href="#">autocomplete item 5</a></li>
<li class="ac-list"><a href="#">autocomplete item 6</a></li>
<li class="ac-list"><a href="#">autocomplete item 7</a></li>
<li class="ac-list"><a href="#">autocomplete item 8</a></li>
<li class="ac-list"><a href="#">autocomplete item 9</a></li>
<li class="ac-list"><a href="#">autocomplete item 10</a></li>
</ul>
</div>
</div>
</form>

issue with choosing li elements using the arrow keys in javascript or jquery

So I have figured it out using javascript and the steps as follows:

1- you must get the ul and get its first child and add an id to it:

var list = document.getElementById('searchResults'); // targets the<ul>
var first = list.firstChild; // targets the first <li>
first.setAttribute('id', 'selected');

2- Then you can use Document.boy on key up and implement the below switch case:

 switch (e.keyCode) {
case 38: // if the UP key is pressed
var ulSelected = document.getElementById('searchResults');

var selected = document.getElementById('selected');
if (selected.previousSibling == null) {
break;
}
var newSelected = selected.previousSibling;
selected.removeAttribute('id');
newSelected.setAttribute('id', 'selected');
var searchBar = document.getElementById('menuSearch');
searchBar.value = selected.firstChild.innerText;
MickyAutoScroll(selected, ulSelected)
break;
case 40: // if the DOWN key is pressed
var ulSelected = document.getElementById('searchResults');
var selected = document.getElementById('selected');
if (selected.nextSibling == null) {
break;
}
var newSelected = selected.nextSibling;
selected.removeAttribute('id');
newSelected.setAttribute('id', 'selected');
var searchBar = document.getElementById('menuSearch');
searchBar.value = selected.firstChild.innerText;
MickyAutoScroll(selected, ulSelected)
break;
case 13:
var link = document.getElementById("selected");
link.firstChild.click();
break;
case 27:
var searchBar = document.getElementById("menuSearch");
searchBar.value = null;
break;
}

this basically will move up, down and access the link in the li using the enter button

Arrow keys navigation doesn't work on li elements

See below. You had to assign li in the window function and add a check on the typing input box keyup function to make sure it wasn't an up/down/esc key being pressed.

if ($("#suggest-results").length) {
var li = $('li'); var liSelected; $(window).keydown(function(e) { li = $('li'); if (e.which === 40) { console.log("Succeed on down arrow"); if (liSelected) { liSelected.removeClass('selected'); next = liSelected.next(); if (next.length > 0) { liSelected = next.addClass('selected'); } else { liSelected = li.eq(0).addClass('selected'); } } else { liSelected = li.eq(0).addClass('selected'); } } else if (e.which === 38) { if (liSelected) { liSelected.removeClass('selected'); next = liSelected.prev(); if (next.length > 0) { liSelected = next.addClass('selected'); } else { liSelected = li.last().addClass('selected'); } } else { liSelected = li.last().addClass('selected'); } } else if (e.which === 27) { $("#syrInputForm").val(''); $("#suggest-results").html(' '); return false; } }); $(".header input[name=tag]").keyup(function(e) { if (e.which !== 27 && e.which !== 38 && e.which !== 40) { var searched = $(this).val(); var gghref = 'https://suggestqueries.google.com/complete/search?hl=en&ds=yt&client=youtube&hjson=t&cp=1&q=' + searched; var result; $.ajax({ url: gghref, type: "POST", dataType: 'jsonp', success: function(data) { for (var i = 1; i < data[1].length; i++) { if (data[1][i][0].length && data[1][i]) { result += '<li class="gsuggested"><a href="#">' + data[1][i][0] + '</a></li>'; } } $("#suggest-results").html("<ul>" + result.replace("undefined", "") + "</ul>"); $('.gsuggested > a').click(function() { var valoare = $(this).text(); $(".header input[name=tag]").val(valoare).focus(); $("#suggest-results").html(' '); return false; }); } }); } });}
#suggest-results .selected {background: #CCD5DB;} #suggest-results li.selected {background: #CCD5DB;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"><div class="header"><div class="searchWidget"><form action="" method="get" id="searchform" autocomplete="off" novalidate="true">   <div class="search-holder">      <span class="search-button">        <button type="submit">           <i class="fa fa-search"></i>        </button>     </span>   <div class="form-control-wrap">      <input id="syrInputForm" type="text" class="form-control input-lg empty" name="tag" value="" placeholder="Search">                   </div></div></form><div id="suggest-results"></div></div></div>

Navigate through list using arrow keys? (JavaScript/JQ)

Since you didn't really explain what you're having trouble with, I just created a general solution. Hopefully this helps:

var li = $('li');
var liSelected;
$(window).keydown(function(e) {
if(e.which === 40) {
if(liSelected) {
liSelected.removeClass('selected');
next = liSelected.next();
if(next.length > 0) {
liSelected = next.addClass('selected');
} else {
liSelected = li.eq(0).addClass('selected');
}
} else {
liSelected = li.eq(0).addClass('selected');
}
} else if(e.which === 38) {
if(liSelected) {
liSelected.removeClass('selected');
next = liSelected.prev();
if(next.length > 0) {
liSelected = next.addClass('selected');
} else {
liSelected = li.last().addClass('selected');
}
} else {
liSelected = li.last().addClass('selected');
}
}
});

JSFiddle: http://jsfiddle.net/Vtn5Y/

arrow keys navigation through li (no jquery)

This turned out to be simpler than I expected, and I've came up with the following code which appearently does the job quite well.

Things to take in account are:

  • the HTML attribute 'tabindex' must be specified on each element for the .focus() to be applied
  • to have a ENTER->submit feeling, you MUST target a link element within the li (still, I'm achieving this with onclick events not included here)
  • this works with an extremely simple list structure, so far I haven't tested it with nested dropdown menus
  • Note: this is most likely not suitable for a copy/paste situation, but as far as I can tell this method is procedurally currect, and can get you started developing more complex solutions

This is the basic HTML:

<input type="text" name="main_input" id="input" />
<ul id="list">
<li class="listElement"><a href="#" tabindex="1">li content</a></li>
<li class="listElement"><a href="#" tabindex="1">li content</a></li>
<li class="listElement"><a href="#" tabindex="1">li content</a></li>
</ul>

And here's the JS function, triggered when the list above is populated and shown:

    function scrollList() {
var list = document.getElementById('list'); // targets the <ul>
var first = list.firstChild; // targets the first <li>
var maininput = document.getElementById('input'); // targets the input, which triggers the functions populating the list
document.onkeydown = function(e) { // listen to keyboard events
switch (e.keyCode) {
case 38: // if the UP key is pressed
if (document.activeElement == (maininput || first)) { break; } // stop the script if the focus is on the input or first element
else { document.activeElement.parentNode.previousSibling.firstChild.focus(); } // select the element before the current, and focus it
break;
case 40: // if the DOWN key is pressed
if (document.activeElement == maininput) { first.firstChild.focus(); } // if the currently focused element is the main input --> focus the first <li>
else { document.activeElement.parentNode.nextSibling.firstChild.focus(); } // target the currently focused element -> <a>, go up a node -> <li>, select the next node, go down a node and focus it
break;
}
}
}

Apologies in advance for the kinda chaotic layout of the code, the function I came up with is a bit more complex and I've stripped out most of it for explaination purposes.

Needless to say, I'm looking forward any comment about the solution above, in regard of errors, improvements or known compatibility issues.



Related Topics



Leave a reply



Submit