Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 8 of 8
  • Thread Tools
  • Rate This Thread
  1. #1
    New to the CF scene
    Join Date
    May 2016
    Posts
    4
    Thanks
    2
    Thanked 0 Times in 0 Posts

    show and hide javascript

    I am very new to javascript, but I have some elements that I want to be displayed on mouseover and hidden on mouseout events. I will also have to code this to work for touch events.


    var menu = document.getElementsByClassName("menu2");
    var subMenu = document.getElementsByClassName("submenu2");

    function showmenu(menu, subMenu) {
    if (menu = "mouseover") {
    subMenu.style.display = "block";
    } else if (menu = "mouseout") {
    subMenu.style.display = "none";
    }
    }

    window.addEventListener("mouseover", showmenu, false);

  2. #2
    Master Coder sunfighter's Avatar
    Join Date
    Jan 2011
    Location
    Washington
    Posts
    6,303
    Thanks
    30
    Thanked 864 Times in 862 Posts
    Things work better for use if you include the HTML.

    Are you working with a menu? It looks like it. Why not use CSS :hover for this?

    Not good code - just something to show you what I mean.
    Code:
    <!doctype html>
    <html>
    <head>
    <title></title>
    <style type="text/css">
    #submenu{
    	display: none;
    }
    #main:hover #submenu {
    	display: block;
    }
    </style>
    </head>
    
    <body>
    
    <nav>
    	<ul>
    		<li>Home</li>
    		<li id="main">Park
    			<ul id="submenu">
    				<li>Green</li>
    				<li>Pink</li>
    				<li>Brown</li>
    			</ul>
    		</li>
    		<li>Away</li>
    	</ul>
    </nav>
    
    </body>
    </html>
    Evolution - The non-random survival of random variants.
    Physics is actually atoms trying to understand themselves.

  3. #3
    New to the CF scene
    Join Date
    May 2016
    Posts
    4
    Thanks
    2
    Thanked 0 Times in 0 Posts
    I have, but I wanted to do it with javascript to expand my knowledge of the language. Also, because I have to make the code work for tablets as well. Hover will not work with tablets.

  4. #4
    Regular Coder
    Join Date
    Feb 2016
    Location
    Keene, NH
    Posts
    365
    Thanks
    1
    Thanked 50 Times in 47 Posts
    ... which is why I stopped making hover menus on websites nearly a decade ago. I saw the writing on the wall.

    Though IF I were to do it, I still wouldn't use JS, I'd use CSS with a media query below a certain width to do a show/hide menu using a <input type="checkbox"> and :checked... though you could also use an anchor, ID and :target.

    But really hover and touch events are COMPLETELY unrelated, so there is no correlation. You want touch you need to trap click events as that's all they really are... well, unless you dig into some bleeding edge stuff that's still not reliable enough to use in production code.

    That said, I have no clue what the scripting you presented is even trying to accomplish. You're trapping mouseover, but not mouseout, but checking some random value inside the function for both?!? I think you need to read up on how events work, as you're assuming values are passed to your callback function that simply don't exist. The only thing that would be passed to showmenu is an event object... is some browsers.

    I'm pulling a fast fade, but I'll leave this tab open and maybe tomorrow I'll do a quick example explaining events to you, as well as showing how to polyfill for older browsers like IE which don't pass events the same way.
    From time to time the accessibility of websites must be refreshed with the blood of designers and owners; it is its natural manure.
    http://www.cutcodedown.com

  5. #5
    Regular Coder
    Join Date
    Feb 2016
    Location
    Keene, NH
    Posts
    365
    Thanks
    1
    Thanked 50 Times in 47 Posts
    Ok, it's VERY hard to figure out what you are trying to do with your existing code, as I don't think you understand what/how values are passed to a function, much less an event handler... though I would ASSUME your markup goes something like this?

    Code:
    <ul>
    	<li id="menu2">
    		<a href="#">Menu item</a>
    		<ul id="submenu2" style="display:none;">
    			<li><a href="#">Submenu Item 1</a></li>
    			<li><a href="#">Submenu Item 2</a></li>
    			<li><a href="#">Submenu Item 3</a></li>
    			<li><a href="#">Submenu Item 4</a></li>
    		</ul>
    	</li>
    </ul>
    If so... well, your code still doesn't make sense. As I mentioned what should be sent to the function when it's assigned as an event handler is the EVENT object, containing the complete information about the triggered event including the object passed to it. As such this:

    Code:
    function showmenu(menu, subMenu)
    Is assigning the EVENT to menu (making the whole thing ignore your global menu variable) and probably setting submenu to "null" and/or undefined since event callbacks don't have two parameters.

    You also assign the event to "window" -- that means the ENTIRE window triggers when you mouse-over it, not just the elements you want to target. Instead of window.addEventListener -- assuming I'm following your intent -- you should be targeting menu.addEventListener.

    Likewise you should be listening for both mouseover and mouseout... that "menu" variable would NOT be telling you that.

    Oh, and if you're declaring multiple var in a row, you don't need to say var on each one, you can make a comma delimited list after a single var statement instead.

    Code:
    var
    	menu = document.getElementsByClassName("menu2"),
    	subMenu = document.getElementsByClassName("submenu2");
    	
    function showMenu(e) {
    	subMenu.style.display='block';
    }
    
    function hideMenu(e) {
    	subMenu.style.display='none';
    }
    
    menu.addEventListener('mouseover', showMenu, false);
    menu.addEventListener('mouseout', hideMenu, false);
    Is probably what you are trying to do. Now, that said... you have to take legacy IE compatibility into account as it doesn't handle events the same way as "proper" JavaScript. Thanks to fear of being sued by Netscape (or even Sun) back when IE went to war with Netscape 4, all versions of IE prior to 9 run JScript, not JavaScript, a similar and mostly compatible equivalent that has a few minor differences just to set it apart.

    One of the biggest differences is that the event is not passed as a parameter to the function and instead is stored in "window.event", and that there is no addEventListener method on anything... Thankfully some simple polyfills can fix that; I use two functions to be certain of compatibility on event handling... a lot of people will use massive frameworks that try to change how JS itself works (like jQuery) but really you should learn how to do it without the framework first; and frankly if you do so you may quickly find that these "frameworks" are a waste of time, effort, bandwidth, and on the whole end up a steaming pile of epic fail.

    The two function wrappers I use are as follows:

    Code:
    function eventAdd(e, eventName, callback) {
    	if (e.addEventListener) e.addEventListener(eventName, callback, false);
    		else e.attachEvent('on' + eventName, callback);
    }
    
    function eventProcess(e, prevent) {
    	e = e || window.event;
    	if (!e.target) e.target = e.srcElement;
    	if (prevent) {
    		// this MAY be overkill... Oh, who are we kidding, it is!
    		e.cancelBubble = true;
    		if (e.stopPropagation) e.stopPropagation();
    		if (e.preventDefault) e.preventDefault();
    		e.returnValue = false;
    	}
    	return e;
    }
    In the first function, "e" is the element you are adding the handler to, "eventName" is the name of the event you want to handle like "mouseover" or "mouseout", and "callback" is the function you want run when the event occurs.

    The second function (which you don't need for the above examples) properly makes sure you have the current "event" and that the source of the event, aka "target" is set to the proper value. "e" is the event object (if any) and the "prevent" value if true will stop any other handlers in the event chain from trying to do anything. We'll get to why we use that function shortly -- but for now using the above the code would change thusly:

    Code:
    // handy library functions
    
    function eventAdd(e, eventName, callback) {
    	if (e.addEventListener) e.addEventListener(eventName, callback, false);
    		else e.attachEvent('on' + eventName, callback);
    }
    
    function eventProcess(e, prevent) {
    	e = e || window.event;
    	if (!e.target) e.target = e.srcElement;
    	if (prevent) {
    		// this MAY be overkill... Oh, who are we kidding, it is!
    		e.cancelBubble = true;
    		if (e.stopPropagation) e.stopPropagation();
    		if (e.preventDefault) e.preventDefault();
    		e.returnValue = false;
    	}
    	return e;
    }
    
    // our actual functionality
    
    var
    	menu = document.getElementsByClassName("menu2"),
    	subMenu = document.getElementsByClassName("submenu2");
    	
    function showMenu(e) {
    	subMenu.style.display='block';
    }
    
    function hideMenu(e) {
    	subMenu.style.display='none';
    }
    
    eventAdd(menu, 'mouseover', showMenu);
    eventAdd(menu, 'mouseout', hideMenu);
    That would now work all the way back to IE 5.0

    Now, that's cute and all, but really directly manipulating style properties is sloppy coding, even when using JavaScript to handle these types of events. Style.display can lead to all sorts of woes and headaches like what if certain layouts you don't want that behavior? What if you want different behaviors for different layouts and media targets. Swapping classes instead of display behavior would be a far more versatile approach. Whilst the newest JavaScript has direct support for doing this through the new "classList" method on DOM elements, those methods are sadly really slow and you cannot rely on them existing in older browsers, so I use some special functions for handling that on elements as well. You could go directly to "className" to add/remove classes, but that doesn't preserve any existing ones so it's best to simply use a few more simple library functions.

    Code:
    function classExists(e, className) {
    	return RegExp('(\\s|^)' + className + '(\\s|$)').test(e.className);
    }
    
    function classAdd(e, className) {
    	if (!classExists(e, className)) e.className += (e.className ? ' ' : '') + n;
    }
    
    function classRemove(e, className) {
    	e.className = e.className.replace(
    		new RegExp('(\\s|^)' + className + '(\\s|$)'), ' '
    	) . replace(/^\s+|\s+$/g,'');
    }
    
    function classSwap(e, oldClass, newClass) {
    	classRemove(e, oldClass);
    	classAdd(e, newClass);
    }
    Likewise right now, that code would only work on one unique target since it needs to hit elements by ID... meaning you'd need to write unique handlers for each and every one of them... See how my eventProcess function goes after the "target" property of the event and/or window.event? That's the element that actually set off the event... so instead of targeting elements off a global value, we swap a class on the originator.

    So that would go something like this:

    Code:
    function showMenu(e) {
    	e = eventProcess(e, true);
    	classSwap(menu, 'hide', 'show');
    }
    
    function hideMenu(e) {
    	e = eventProcess(e, true);
    	classSwap(menu, 'show', 'hide');
    }
    That will add or remove the class from the LI, so for using display state (which I'd advise against for accessibility reasons) you'd need this CSS:

    Code:
    .hide ul { display:none; }
    .show ul { display:block; }
    Though I'd suggest this instead:
    Code:
    .hide { overflow:hidden; }
    .show { overflow:visible; }
    Since I'd assume your dropdown menu is absolute positioned. The Overflow:hidden will actually hide it without things like braille or screen readers accidentally failing to be able to even find your dropdown menu content.

    Now a more robust approach would be to make it so these simple handlers can apply quickly to all the LI inside something like your main menu. That would go something like this:

    Code:
    <ul id="mainMenu">
    	<li>
    		<a href="#">Menu item 1</a>
    		<ul>
    			<li><a href="#">Submenu Item 1-1</a></li>
    			<li><a href="#">Submenu Item 1-2</a></li>
    			<li><a href="#">Submenu Item 1-3</a></li>
    			<li><a href="#">Submenu Item 1-4</a></li>
    		</ul>
    	</li>
    	<li>
    		<a href="#">Menu item 2</a>
    		<ul>
    			<li><a href="#">Submenu Item 2-1</a></li>
    			<li><a href="#">Submenu Item 2-2</a></li>
    			<li><a href="#">Submenu Item 2-3</a></li>
    			<li><a href="#">Submenu Item 2-4</a></li>
    		</ul>
    	</li>
    </ul>
    To nab all those LI, I'd use a fairly advanced technique called "DOM walking" to get them. The complete code would then go something like this:

    Code:
    // handy library functions
    
    function eventAdd(e, eventName, callback) {
    	if (e.addEventListener) e.addEventListener(eventName, callback, false);
    		else e.attachEvent('on' + eventName, callback);
    }
    
    function eventProcess(e, prevent) {
    	e = e || window.event;
    	if (!e.target) e.target = e.srcElement;
    	if (prevent) {
    		// this MAY be overkill... Oh, who are we kidding, it is!
    		e.cancelBubble = true;
    		if (e.stopPropagation) e.stopPropagation();
    		if (e.preventDefault) e.preventDefault();
    		e.returnValue = false;
    	}
    	return e;
    }
    
    function classExists(e, className) {
    	return RegExp('(\\s|^)' + className + '(\\s|$)').test(e.className);
    }
    
    function classAdd(e, className) {
    	if (!classExists(e, className)) e.className += (e.className ? ' ' : '') + n;
    }
    
    function classRemove(e, className) {
    	e.className = e.className.replace(
    		new RegExp('(\\s|^)' + className + '(\\s|$)'), ' '
    	) . replace(/^\s+|\s+$/g,'');
    }
    
    function classSwap(e, oldClass, newClass) {
    	classRemove(e, oldClass);
    	classAdd(e, newClass);
    }
    
    // our actual functionality
    
    function showMenu(e) {
    	e = eventProcess(e, true);
    	classSwap(e.target, 'hide', 'show');
    }
    
    function hideMenu(e) {
    	e = eventProcess(e, true);
    	classSwap(e.target, 'show', 'hide');
    }
    
    var
    	mainMenu = document.getElementById('mainMenu'),
    	menuChild = mainMenu.firstChild;
    	
    while (menuChild) {
    	if (menuChild.nodeType == 1) {
    		// is a DOM element, only ones should be LI here
    		eventAdd(menuChild, 'mouseover', showMenu);
    		eventAdd(menuChild, 'mouseout', hideMenu);
    	}
    	menuChild = menuChild.nextSibling;
    }
    That might seem a bit complex, but basically I set the menuChild to the first child element of #mainMenu. This could be any type of node since some browsers store whitespace and comments as node types... but nodeType 1 is an actual DOM element, and in the case of a UL all the LI should be the only actual DOM nodes... so if it's nodetype 1 we add our events to it. We then look for the next sibling and keep looping if we find one processing them all.

    As such the only ID we needed to target was #mainMenu and it nabs all first level children of that UL. Now, if you wanted to target ALL the LI for this class swap -- which is viable as well, you'd be best off using getElementsByTagName.

    Code:
    var
    	mainMenu = document.getElementById('mainMenu'),
    	menuLI = mainMenu.getElementsByTagName('li');
    	
    for (var i = 0, iLen = menuLI.length; i < iLen; i++) {
    	eventAdd(menuLI[i], 'mouseover', showMenu);
    	eventAdd(menuLI[i], 'mouseout', hideMenu);
    }
    Which would add that class swap to EVERY LI inside #mainMenu whether it needs it or not -- but since we're targeting UL inside the LI, that would still function whether or not that UL exists or no... at least down to two depths, then specificity starts to be a pig. Mind you fancy CSS selectors like the ">" child selector could fix that, but those only work in modern browsers and the only reason to resort to scripting for this specific task is legacy browser support. (and then I'd still use CSS and then a .htc polyfill for ancient IE versions)

    But as you said, this is more a learning exercise than a proper way of doing things... I'd never do any of this on a modern site.

    Either way it's good to know how to do it manually first, so it's refreshing to see someone trying to do this stuff in Vanilla JavaScript without the frameworks first, since until you know how to do it without the framework you can't possibly be qualified to form a rational opinion on if said frameworks -- like jQuery -- are even worth using.

    SADLY a lot of people get frustrated before they have that comprehension which is why so many people sing jQuery's praises before they are actually competant enough to know just how badly it's shtupping them.

    In any case, you're kind of diving into the deep end of the pool and I know most of what I just presented is pretty advanced, but really that's the fast way to learn to swim. Read it through, try the code, take your time to digest it, and come back with questions.

    ... and remember, there are no stupid questions, only stupid answers.

    Examples of stupid answers:

    "Use jQuery"
    "Use Bootstrap"
    "Use Dreamweaver"
    "Use React"
    From time to time the accessibility of websites must be refreshed with the blood of designers and owners; it is its natural manure.
    http://www.cutcodedown.com

  6. The Following 2 Users Say Thank You to deathshadow For This Useful Post:

    Sousuke_Alexein (05-04-2016), webdevthad (05-03-2016)

  7. #6
    New to the CF scene
    Join Date
    May 2016
    Posts
    4
    Thanks
    2
    Thanked 0 Times in 0 Posts
    I really appreciate the feedback. After your first post I did go back and read through a lot of web pages and one book. If I were to have more than one submenu and i used the document.getElementsByClassName would I have to create a for statment to add eventlisteners to each menu with a submenu?

  8. #7
    Master Coder felgall's Avatar
    Join Date
    Sep 2005
    Location
    Sydney, Australia
    Posts
    8,131
    Thanks
    3
    Thanked 814 Times in 803 Posts
    Quote Originally Posted by Sousuke_Alexein View Post
    If I were to have more than one submenu and i used the document.getElementsByClassName would I have to create a for statment to add eventlisteners to each menu with a submenu?
    No. You can add event listeners to the body (or any element that is wrapped around the ones you want to process) and then have them check what element triggered the event in order to decide what to do.
    Stephen
    Learn Modern JavaScript - http://javascriptexample.net/
    Helping others to solve their computer problem at http://www.felgall.com/

    Don't forget to start your JavaScript code with "use strict"; which makes it easier to find errors in your code.

  9. Users who have thanked felgall for this post:

    Sousuke_Alexein (05-04-2016)

  10. #8
    New to the CF scene
    Join Date
    May 2016
    Posts
    4
    Thanks
    2
    Thanked 0 Times in 0 Posts
    Yes, I saw that in my research and tried to implement it. Unfortunately, I need to be able to select the child element of the currentTarget. But, I could not make that work.


 

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •