JS ArrowDown addEventListener for multiple items in a loop (demo)

You have the following keyboard listener for the ArrowDown event (it key code is 40 ):

 window.onload = function() { var itemsContainer = document.getElementById('cities-drop'); document.addEventListener('keyup',function(event){ if (event.keyCode == 40 && itemsContainer.style.display=='block') { event.preventDefault(); for (var i=0;i<itemsContainer.children.length-1;i++){ if (itemsContainer.getAttribute('class').substr('hovered')!=-1){ itemsContainer.children[i].setAttribute('class',''); itemsContainer.children[i].nextSibling.setAttribute('class','hovered'); //break; } } } }); 

in this case, hovering goes to the last item in the list after pressing ArrowDown .

If break uncommented, it goes to the second element and no longer jumps.

It is impossible to get a principle on how to do this, that the listener is always listening ...

EDIT live demo
it may be a closing issue, but I'm not sure

+7
source share
3 answers

By looking at your code and understanding what you are trying to do, I think your error is using substr , where you should use indexOf . Here is the updated line:

 if (itemsContainer.getAttribute('class').indexOf('hovered') != -1) 



More: In fact, you used substr with a string value for the start index. I'm not sure what the result of this would be, but apparently it is not -1, since the condition returned true every time, making the next element depend EVERY time, down to the bottom of the list. Using the break statement, the if statement was executed on the first element (causing the second element to hang), and then completed.

I would leave the break statement there after correcting your code, so that the loop will stop after it finds a match, and will not overly go through the rest of the elements.


EDIT:

I found a couple more problems in the code. Here is an example that works for me in IE and FF, at least (not tested in Safari, Opera or Chrome):

 <html> <head> <style type="text/css"> .hovered { color:red; } </style> <script type="text/JavaScript"> function move(event) { var itemsContainer = document.getElementById('cities-drop'); if (event.keyCode == 40 && itemsContainer.style.display == 'block') { if (event.preventDefault) event.preventDefault(); if (event.cancelBubble) event.cancelBubble(); if (event.stopImmediatePropagation) event.stopImmediatePropagation(); for (var i=0; i<itemsContainer.children.length-1; i++) { if (itemsContainer.children[i].className.indexOf('hovered') != -1) { itemsContainer.children[i].className = ""; itemsContainer.children[i+1].className = "hovered"; break; } } } }; </script> </head> <body onkeydown="move(event)"> <div id="cities-drop" style="display:block;"> <p class="hovered">Item 1</p> <p>Item 2</p> <p>Item 3</p> <p>Item 4</p> <p>Item 5</p> <p>Item 6</p> <p>Item 7</p> </div> </body> </html> 


Details: For me, in IE9, the function was never called. Instead, I just made it a regular function and added the onkeydown event to the body tag.

Next, for compatibility with multiple browsers, you must ensure that event.preventDefault exists before using it. I was getting JS error in IE.

In your if-statement you have itemsContainer.getAttribute('class') . First, you need to use itemsContainer.children[i] . Secondly, .getAttribute('class') did not work for me in IE, so I switched it only to .className .

Finally, itemsContainer.children[i].nextSibling did not work for me, but it is simple enough to just change it to itemsContainer.children[i+1] to get the next brother.

+4
source

There are a few things that I see that can be a problem. First of all, you update itemsContainer.children[i].nextSibling , which is equal to itemsContainer.children[i+1] . Therefore, the last item is always selected if you miss a break. itemsComtainer [i + 1] will always hang if there is an element corresponding to the class.

The second problem is that Travesty3 points to his answer.

I also changed the if condition to check if the hanging class is on one of the child elements, and not on the container itself.

 if (itemsContainer.children[i].getAttribute('class').match('hovered')) 

I changed the event handler with the following lines of code, and this seems to work fine:

 document.addEventListener('keyup',function(event){ if (event.keyCode === 40 && itemsContainer.style.display==='block') { event.preventDefault(); for (var i=0,l=itemsContainer.children.length;i<l;++i){ if (itemsContainer.children[i].getAttribute('class').match('hovered')){ itemsContainer.children[i].setAttribute('class',''); itemsContainer.children[i+1].setAttribute('class','hovered'); break; } } } }); 

Keep in mind that creating such a down control is a lot of work. The user expects to navigate it using the keyboard. To create a great user interface, you need to handle several keys, such as arrow keys, a tab to focus control, space to open and close it, alphanumeric input to focus on the first matching element, etc.

If user experience is important, I would recommend using a framework and plugin for this. I personally prefer jquery and jquery ui, and there are several plugins to select drop downs. Another advantage is that if javascript is disabled by the client or your javascript is erroneous for some reason, most plugins will return to their usual native select element, which will still work fine.

I myself used this selectbox plugin for a simple drop down list: http://www.abeautifulsite.net/blog/2011/01/jquery-selectbox-plugin/

Edit: I cancel this recommendation as it does not work if multiple elements have the same name. If this is important, you should check out the Filament Group selectmenu plugin: http://filamentgroup.com/lab/jquery_ui_selectmenu_an_aria_accessible_plugin_for_styling_a_html_select/ // Change

... and a jquery autocomplete plugin for combobox that also supports written input: http://jqueryui.com/demos/autocomplete/

+3
source

Instead of using a loop, you can try a simpler approach:

 window.onload = function() { var itemsContainer = document.getElementById('cities-drop'); document.addEventListener('keyup',function(event) { if (event.keyCode == 40 && itemsContainer.style.display=='block') { event.preventDefault(); var previousHoveredChoice = itemsContainer.querySelector('.hovered'); previousHoveredChoice.className = ''; var currentHoveredChoice = previousHoveredChoice.nextSibling; if (currentHoveredChoice) { currentHoveredChoice.className = 'hovered'; } } }); //following code is copy-pasted from the live example //just to close the onload function handler in this solution document.addEventListener('keyup',function(event){ if (event.keyCode == 27) { if (document.getElementById('cities-drop').style.display=='block'){ document.getElementById('cities-drop').style.display='none'; } } }); //end of copy-pasted code }; 
+3
source

All Articles