First of, I'd like to use only native JavaScript to complete this task.
Let's say I am to make a custom dropdown, and the HTML code looks kind of like this.
<div class="dropdown">
<span class="dropdown-label" style="display:block">Select a thing</span>
<ul class="dropdownItemContainer">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
</ul>
</div>
In the CSS file I have something close to this:
ul.dropdownItemContainer li:hover {
background-color: #FF0000;
}
Yeah, there's really no dropdownish behavior, but it's not the point of discussion actually. The problem is that I couldn't think of a decent way to enable keyboard control for this dropdown. The desired outcome is the following: I press the down key, and the first option is highlighted; I press it again, and the second option is highlighted and so on.
The only option that I see at this point (just started studying JS) is to fetch all of the ul's children, stick'em into an array and assign the tags a background color through JS methods in a proper way whenever the down key is pressed.
On the other hand, I still have the :hover behavior described in the CSS for mouse countrol. Is there a smart way of simulating hovers?
I would go with a simple assignment of a class on your li-elements and steer it with a keydown handler. The following code is not meant to be complete but give you something you can work with.
var active = document.querySelector(".hover") || document.querySelector(".dropdownItemContainer li");
document.addEventListener("keydown",handler);
document.addEventListener("mouseover",handler);
function handler(e){
console.log(e.which);
active.classList.remove("hover");
if (e.which == 40){
active = active.nextElementSibling || active;
}else if (e.which == 38){
active = active.previousElementSibling || active;
}else{
active = e.target;
}
active.classList.add("hover");
}
You can see a working example here
You may want to use a library instead of coding this from scratch.
http://vebersol.net/demos/jquery-custom-forms/
http://www.dreamcss.com/2009/05/15-jquery-plugins-to-enhance-your-html.html
I would suggest removing the hover attribute from css.
And add only a hovered class which is applied on keypresses and on mouseover
This could look like this in Code
var dropDown = document.getElementsByClassName("dropdownItemContainer")[0]
document.addEventListener("keydown",function (e) {
if(e.keyCode == 38 || e.keyCode == 40 ) {
var key = e.keyCode
var hovered = dropDown.getElementsByClassName("hovered")
if(hovered.length != 0 ) {
cur = hovered[0]
cur.className = ""
cur = cur[(key==38?"previous":"next")+"ElementSibling"] || dropDown.children[key==38?dropDown.children.length-1:0]
} else {
cur = dropDown.children[key==38?dropDown.children.length-1:0]
}
cur.className="hovered"
}
});
dropDown.addEventListener("mouseover",function (e) {
for( var i = 0,j; j = dropDown.getElementsByClassName("hovered")[i];i++)
j.className = "";
e.srcElement.className = "hovered";
});
Heres an example on JSFiddle
Reality you didn't need any js for dropdown but You can use JavaScript Event for simulating it. You can use event like hover, focus, onclick
In JS You Can use This For Set Event
document.getElementById('id').addEventListener('focus',function(e){
//place code that want ran at event happened
}
In JQuery you can use bind, click ,...
$('#id')bind('focus',function(e){
//place code that want ran at event happened
}
List of Event
http://www.quirksmode.org/dom/events/index.html
Related
I'm working with jquery-sortable and I'm having some difficulty modifying the list container (ul) when it's been emptied or loaded empty. For example, If you have two containers:
A collection list to drag from that always contains a few items.
A destination list which loads empty (unless it is being edited and it will contain some list items but can be emptied by dragging them out of there
The empty container (ul) should display a message (i.e. nothing here) whenever it loads empty or it gets emptied on edit.
I tried several approaches with no avail.
SAMPLE HTML FOR EMPTY CONTAINER
<ul id="mediaItemsListDest" class="playlist-items connectedSortable">
<!-- force a message in html -->
<p>Drag and drop an item from the list</p>
</ul>
DIFFERENT JQUERY APPROACHES
if( $("#mediaItemsListDest li").length >= 1 ) {
//remove it when the ul contains an li
$("#mediaItemsListDest p").css('display','none');
}
or
if( $("#mediaItemsListDest li").length === 0 ) {
//no li is found, display a message via jquery but don't add it as a <p> element in the html
$(this).html("Sorry, this is empty");
}
or
if( !$("#mediaItemsListDest").has("li").length ) {
$(this).html("Sorry, this is empty");
}
None of them worked. How else can I hijack this empty or emptied list?
Here's a testing fiddle for you:
DEMO
Thanks in advance.
You need to handle on every list change the error message state so let's say we have the following HTML - example from your demo:
<ol id="mediaItemsListDest" class="simple_with_animation vertical">
<p>Drag and drop an item from the list</p>
<li>Item 1</li>
<li>Item 2</li>
</ol>
Additionally I have extended with a function which is handling the message state, code is placed on initialization part of the application:
function handleMessage() {
let liObjects = $('#mediaItemsListDest li');
let message = $('#mediaItemsListDest p');
console.log('length', liObjects.length);
console.log('message', message);
if (liObjects.length !== 0) {
message.css('display', 'none');
} else {
message.css('display', 'inline');
}
}
handleMessage();
This function needs to be called in onDrop event:
onDrop: function ($item, container, _super) {
// code part removed but you can find in your demo
handleMessage();
}
I did a quick test and it was working fine. I hope this one is helping, let me know if you need more information.
I'm trying to figure out a way to create an extremely basic autocomplete without 3rd parties dependencies. so far I've gotten to populate a list of results with an ajax call, and with mouse onclick events on each li the script completes the fields as supposed.
what I need to implement is an up/down/enter keys navigation system based on pure js, and after hours spent searching I gave up. this fiddle explains quite perfectly my situation, with the difference that it does require jQuery.
I'd rather not paste any of my own code here as the final aim is learning the process, but since I'm linking to jsfiddle I'm required to so here's the fiddle.
fiddle HTML:
<div id="MainMenu">
<ul>
<li class="active">PATIENT TEST</li>
<li>QC TEST</li>
<li>REVIEW RESULTS</li>
<li>OTHER</li>
</ul>
</div>
Up
Down
fiddle JS:
$(document).ready(function () {
$('#btnDown').click(function () {
var $current = $('#MainMenu ul li.active');
if ($current.next().length > 0) {
$('#MainMenu ul li').removeClass('active');
$current.next().addClass('active');
}
});
$('#btnUp').click(function () {
var $current = $('#MainMenu ul li.active');
if ($current.prev().length > 0) {
$('#MainMenu ul li').removeClass('active');
$current.prev().addClass('active');
}
});
$(window).keyup(function (e) {
var $current = $('#MainMenu ul li.active');
var $next;
if (e.keyCode == 38) {
$next = $current.prev();
} else if (e.keyCode == 40) {
$next = $current.next();
}
if ($next.length > 0) {
$('#MainMenu ul li').removeClass('active');
$next.addClass('active');
}
});
});
thanks a lot in advance to anyone willing to point me in the right direction.
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">li content</li>
<li class="listElement">li content</li>
<li class="listElement">li content</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.
you wrote
tabindex="1"
Why not just tabindex="0"?
Using a positive umber greater than 0 is only needed if you want to cahnge the default order of navigation.
When the bottom of my page (a.k.a. #main) comes into view jQuery toggles a class on my sidebar to make it scrollable using overflow-y: scroll — and overflow: hidden when the bottom of the page is out of view.
The desired effect here is to be at the bottom of the page (again in my example that's the #main div), but allow for the sidebar to keep scrolling provided there's more content.
So if you were to keep scrolling down on #main even after you'd reached the bottom, the sidebar would begin to scroll.
The problem right now is the desired scrolling effect only works when the cursor's over #sidebar. I'd like for it to be more natural andcapable of scrolling without the cursor needing to be over the #sidebar.
HTML:
<div id="container">
<div id="header"></div>
<div id="main">
<p>Lorem ipsum dolor sit amet, [...]</p>
</div>
<div id="sidebar">
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<li>Item 4</li>
<li>Item 5</li>
<li>Item 6</li>
<li>Item 7</li>
<li>Item 8</li>
<li>Item 9</li>
<li>Item 10</li>
[...]
</ul>
</div>
</div>
Javascript:
$('#sidebar').height( $('#main').height() );
$('#main').waypoint(function() {
$('#sidebar').toggleClass('scrollable');
}, { offset: 'bottom-in-view' });
I've setup a fiddle for my question here: http://jsfiddle.net/ZZqLr/
Follow-Up: By approaching the problem from another angle I managed to achieve the desired effect.
This time around, when the bottom of #main comes into view it becomes fixed to the bottom of the window, while the #sidebar continues to scroll freely. It's a bit of a hack, but is visually identical for my needs.
http://jsfiddle.net/ZZqLr/1/
You can catch the scroll event of a mouse with the following code:
var mouseWheelEvent = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; //FF doesn't recognize mousewheel as of FF3.x
if (document.attachEvent) { //if IE (and Opera depending on user setting)
document.attachEvent("on"+mouseWheelEvent, mouseWheelEventHandler);
} else if (document.addEventListener) { //WC3 browsers
document.addEventListener(mouseWheelEvent, mouseWheelEventHandler, false);
}
After this it is quite easy to see when someone scrolls, even though you have reached the end of the document.
The mouseWheelEventHandler is the function that I passed to handle your mouseWheelEvent for you, it looks like this:
function mouseWheelEventHandler(e)
{
if( !sidebar.hasClass('scrollable') && !sidebar.is(":hover") ) {
return true;
}
var event = window.event || e; //equalize event object
var delta = event.detail ? event.detail*(-120) : event.wheelDelta; //check for detail first so Opera uses that instead of wheelDelta
sidebar.scrollTop(sidebar.scrollTop()-delta);
}
It will only scroll the sidebar if the sidebar has the class scrollable and if you are not already with your mouse on the sidebar, as it would scroll that element anyway and you would and up double scrolling it.
This should work on most browsers according to the following link:
http://www.javascriptkit.com/javatutors/onmousewheel.shtml
And here is the fiddle to play with ofcourse:
http://jsfiddle.net/ZZqLr/5/
EDIT
To complete the answer, we'll add the behaviour of it only scrolling the sidebar up instead of the page when you scroll up again, for this we only have to prevent the scrolling event by event.preventDefault() and add the following code at the end of the mouseWheelEventHandler:
if( sidebar.scrollTop() == 0 ) {
sidebar.removeClass('scrollable');
}
the function then looks like this:
function mouseWheelEventHandler(e)
{
if( !sidebar.hasClass('scrollable') && !sidebar.is(":hover") ) {
return true;
}
var event = window.event || e; //equalize event object
event.preventDefault();
var delta = event.detail ? event.detail*(-120) : event.wheelDelta; //check for detail first so Opera uses that instead of wheelDelta
sidebar.scrollTop(sidebar.scrollTop()-delta);
if( sidebar.scrollTop() == 0 ) {
sidebar.removeClass('scrollable');
}
}
Again you can play with it right here: http://jsfiddle.net/ZZqLr/6/
By the way, the waypoints.js is not even necessary for this, just for fun, one without the waypoints.js by removing the waypoint function and just adding the following in the eventHandle function:
function mouseWheelEventHandler(e)
{
if( !sidebar.hasClass('scrollable') && !sidebar.is(":hover") ) {
if($(window).scrollTop() + $(window).height() == $(document).height()) {
sidebar.addClass('scrollable');
}
return true;
}
var event = window.event || e; //equalize event object
event.preventDefault();
var delta = event.detail ? event.detail*(-120) : event.wheelDelta; //check for detail first so Opera uses that instead of wheelDelta
sidebar.scrollTop(sidebar.scrollTop()-delta);
if( sidebar.scrollTop() == 0 ) {
sidebar.removeClass('scrollable');
}
return true;
}
And for playing with that as well, you might've guessed: http://jsfiddle.net/ZZqLr/7/
And that will be all, I guess :)
$('ul.mylist').on('click', 'li', function(){})
I want to transform this jQuery line into vanilla but how I can do this ? I looked at jQuery source but I'm confused. Is there simple way to do this in pure Javascript ?
Some features here are browser dependent, but this is basically how it goes...
// select all UL elements with the `mylist` class
var uls = document.querySelectorAll('ul.mylist');
// Iterate the collection, and bind the `click` handler
for (var i = 0; i < uls.length; i++)
uls[i].addEventListener('click', handler, false);
// The `click` handler iterates starting with `event.target` through its
// ancestors until the bound element is found. Each element matching the
// "LI" nodeName will run your code.
function handler(event) {
var node = event.target;
do {
if (node.nodeName === "LI") {
// RUN YOUR CODE HERE
}
} while (node !== this && (node = node.parentNode));
}
You can make the selector test more broad by using node.matchesSelector(".my.selector"), though that method is implemented using browser-specific property flags in some browsers, so you'd need to first expose it on the Element.prototype under the proper name.
You can bind click listener to any element as follows:
<ul id="mylist">
<li> item 1</li>
<li> item 2</li>
<li> item 3</li>
<li> item 4</li>
</ul>
<script type="text/javascript">
function click_handler(){
alert('you clicked a list');
}
var list=document.getElementById("mylist");
list.onclick=click_handler;
</script>
here is JSFiddle
I currently have a list of <li>'s. Each <li> will have a color class defined, example: .color-blue, .color-red, .color-green - like so:
<ul id="listings">
<li class="layer block color-blue" id="item-1"></li>
<li class="layer block color-red" id="item-2"></li>
<li class="layer block color-green" id="item-3"></li>
</ul>
How do I copy/get the color class of the specific <li> item that is clicked?
I have my click listener in place and also know how to get the <li "id"> however not sure on the specific class though.
/* Click listener */
document.getElementById("listings").addEventListener("click", function(e) {
//console.log(e.target.id + " was clicked");
});
Something like this:
document.getElementById("listings").addEventListener("click", function(e) {
var el = e.target;
if (el.tagName == "LI") { // Use only li tags
for (i=0; i < el.classList.length; i++) {
if (~el.classList[i].indexOf('color')) {
var color = el.classList[i];
console.log('color class found: '+color);
break;
}
}
}
});
http://jsfiddle.net/bHJ3n/
You can use (jQuery):
$('ul').find('li.layer block color-blue')
Or
$('ul#listings').find('li.layer block color-blue')
Or... you can not use jQuery as that wasn't in the original question and would be wasteful to include unnecessarily.
Here's a solution that works in vanilla JS:
jsFiddle Example
Essentially because you're lumping the colour among the other classes you have to split them into an array and iterate over them until you find the one that starts 'color-'. I would recommend you use a custom attribute instead, like data-color="blue" as that would mean you could just retrieve it with:
e.target.getAttribute('data-color');
Try
document.getElementById("listings").addEventListener("click", function(e) {
alert(e.srcElement.className);
});
DEMO
UPDATE(since it is not working in Firefox as pointed from Sai):
To work also in Firefox try this:
document.getElementById("listings").addEventListener("click", function(e) {
var target = e.target || e.srcElement;
alert(target.className);
});
DEMO2