Jump to content

Building a HideShow toggle


DarkxPunk
 Share

Recommended Posts

Updated Post:

 

Hello everyone, re tweaking this post to define a better goal. I want to build a cross platform, simple, backwards compatible HideShow toggle that mixes both modern methods and required outdated methods. This toggle will use both CSS tricks and JS as to give it the ability to be both modern and backwards compatible. If you are looking for the code so far please hit up the end of this post.

 

Original Post:

 

Hey there here is the code:

function hideShowToggle(a) {	var element = a.nextSibling;	element = element.nextSibling;	element.style.display = element.style.display == 'block' ? 'none' : 'block';	var input = a.childNodes;	input = input[1];	input.value = input.value == 'Hide' ? 'Show' : 'Hide';}

Works as a toggle in Safari, but when I try in IE6 it says I need to define an object. Please understand I am still in my infancy with JS, and I am trying to create a toggle that works from IE6 and up while still evolving using more modern methods (which I do not include in this post, but they are irrelevant to the JS)

 

Thanks for any pointers.

Michael

Edited by DarkxPunk
Link to comment
Share on other sites

Okay so I found a semi solution, but not a full solution…

 

Here is the new code:


function hideShowToggleAlt(l) {
var element = l.parentNode.getElementsByClassName('hideShowContent');
element = element[0];
element.style.display = element.style.display == 'block' ? 'none' : 'block';
var input = l.getElementsByTagName('input');
input = input[0];
input.value = input.value == 'Hide' ? 'Show' : 'Hide';
}

Why does it say object does not support this property or method? IE6 is a pain, but I guess this is what you gotta deal with to do the best backwards compatibility.

Link to comment
Share on other sites

So I resolved the issue unintentionally… This code works:

function hideShowToggle(l) {	var element = l.nextSibling.nextSibling;	element.style.display = element.style.display == 'block' ? 'none' : 'block';	var input = l.childNodes[1];	input.value = input.value == 'Hide' ? 'Show' : 'Hide';}

Now obviously this does have issue with the text nodes existing, but not only were the text nodes breaking the JS, but it was also breaking the styling. So the obvious solution, comment out the text nodes. It is not pretty, but it is what needed to be done. Now I am fine.

 

If you do figure out a way to improve this thought, let me know.

Edited by DarkxPunk
Link to comment
Share on other sites

Well that is the thing… I have attempted to test the elements, but every time I do that it results in two things. One it simply just says "undefined" or it goes into an infinite loop.

 

Just to make sure I am not crazy but should it not be:

while(element.tagName != 'div') { l.nextSibling }

 

Maybe I am crazy...

Link to comment
Share on other sites

See what this does in IE6...

<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><title>click to show/hide</title><style>p.click{cursor:pointer;color:red;}div.inner{width:300px;border:1px solid #aaa;}p.green{color:green;}</style><script>window.onerror = function(m, u, l){alert('Javascript Error: '+m+'nURL: '+u+'nLine Number: '+l);return true;}</script><script>window.onload = init;function init() {var list = document.getElementsByTagName('p');for(i=0,len=list.length ; i<len ; i++){if (list[i].tagName == 'P' && list[i].className == 'click'){list[i].onclick = showhide;//alert(i);}}}//end of functionfunction showhide(e){  //alert('process ['+ this.innerHTML +']');  var cnt = 0;  var par = this.parentNode;  var elist = par.childNodes;  for(var i=0,len=elist.length ; i<len ; i++){    //alert(i +' = '+ elist[i].tagName);    if (elist[i].tagName=='P'){      cnt++;      if (cnt==2){        //alert('showhide ['+ elist[i].style.display +']');        elist[i].style.display = (elist[i].style.display != 'none') ? 'none':'block';      }    }  }}//end of function</script></head><body><img src="#" alt="alt image"/><hr/><br/><div class="outer"><div class="inner">text text text text<p>para</p><h3>group 1</h3>text text text text<p class="green">hide me 1</p><p>para</p><input type="text" id="in1"/><p class="click">click to hide 1</p></div><div class="inner">text text text text<p>para</p><h3>group 2</h3>text text text text<p class="green">hide me 2</p><p>para</p><input type="text" id="in2"/><p class="click">click to hide 2</p></div><div class="inner">text text text text<p>para</p><h3>group 3</h3>text text text text<p class="green">hide me 3</p><p>para</p><input type="text" id="in3"/><p class="click">click to hide 3</p></div></div><div id="out1"></div></body>    </html>
Link to comment
Share on other sites

Well the reason in my head that is should work (guess I should have included this) is element = l.nextSibling...

 

Saying that I have tried while(element.tagName != 'div') { element.nextSibling } but no real luck...

 

I still can't get basic javascript logic wrapped around my head :facepalm::fool:

Link to comment
Share on other sites

remember. the '=' sign is an assignment operator. you need to use it if you want to assign a value or actually change something. Saying... element.nextSibling; ...only tells you about the next sibling of the element (or returns null if the element WAS the last sibling). You need to write element = element.nextSibling; to assign the current element to it's next sibling. Also. You should consider another possible terminator if there is no div at all in the current sibling collection. If that loop reaches the last sibling and doesn't find a 'div', it's just gonna throw an error since element will become null, and won't understand how to get the property "nextSibling" from null. In any case, the way I would write a toggle script would be in one of 2 ways, assuming I'm writing without jQuery. Depending on how involved I want it to be. and how related they would be. A: write a stand-alone function that has a id reference to the element to be hidden. This approach is simple, straightforward, and pretty easy to follow. and no need to traverse the DOM

function toggle(tEle){  tEle = document.getElementById(tEle);  if(tEle.style.display == "none"){      tEle.style.display == "block";      this.innerHTML = "hide"  }else{       tEle.style.display == "none";      this.innerHTML= "show"   } } <!--html example--> <button onclick="toggle(this,'divToggle1')">hide</button> <div id="divToggle1"><p>this is the a section of hidable content</p> </div>

B: create an object on initialization that will 1st identify involved elements and then delegate control of the toggled objects like in this jsfiddle. I prefer this approach more as the javascript isn't written inline inside the html, at all. Also I like to avoid recalling document functions like getElementById inside event handlers. Personal preference of mine that when an event is running the function already knows what it needs to deal with before hand. I prefer that a piece of code only needs to do it once if it can (I haven't tested this cross-browser, so might not work in IE 6, but I would likely only have to work on the for loop at the bottom) both options benefit from the fact that the linked elements do not need to know their relationship to each other. so they don't even have to be siblings (as shown for example in the jsfiddle). Also I would look into some posted polyfills for addeventslisteners on the web. if you really want cross browser support in your scripts, polyfills are simple written scripts that provide support for browsers that would in one case or another wouldn't have the function. For example addEventListener is whats used in IE 9+ while earlier versions used attachEvent. As well as other browsers prefering other methods to connect event handlers to their elements. You can grab a polyfill for addEventListener so that script that you write will work with using that function regardless of the browser. Even better I would recommend looking into using javascript libraries like jQuery or dojo. This way you can have the library worry about making sure your script will work in all viable browsers, while you worry about the site itself.

Link to comment
Share on other sites

Everyone is guessing what the actual html code looks like heres mine :)

 

EDIT: mark2

<!DOCTYPE html><html>    <head>        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">        <meta name="viewport" id="viewport" content="target-densitydpi=high-dpi,initial-scale=1.0,user-scalable=no" />        <title>Document Title</title>        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>        <script  type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>        <script>            function ignoretextnodes(cur_element, tag, trans_Type)            {                if (trans_Type === 'sib')                {                    while (cur_element.nodeName.toLowerCase() !== tag.toLowerCase()) {                        cur_element = cur_element.nextSibling;                    }                }                else                {                    for (i = 0; i < cur_element.length; i++)                    {                        if (cur_element[i].nodeName.toLowerCase() !== tag.toLowerCase())                        {                            i++;                        }                        else                        {                            break;                        }                    }                    cur_element = cur_element[i];                }                return cur_element;            }            function hideShowToggle(a) {                var element = a.nextSibling;                element = ignoretextnodes(element, 'DIV', 'sib');                element.style.display = element.style.display === 'block' ? 'none' : 'block';                var input = a.childNodes;                input = ignoretextnodes(input, 'input', 'child');                input.value = input.value === 'Hide' ? 'Show' : 'Hide';            }        </script>        <style type="text/css">        </style>    </head>    <body>        <div onclick="hideShowToggle(this)"><input type="button" value="Show"></div><div style="display:none;">div 1</div><div onclick="hideShowToggle(this)"><input type="button" value="Show"></div><div style="display:none;">div 2</div>        <div onclick="hideShowToggle(this)"><input type="button" value="Show"></div>        <div style="display:none;">div 3</div>        <div onclick="hideShowToggle(this)"><input type="button" value="Show"></div>        <div style="display:none;">div 4</div>    </body></html>
Edited by dsonesuk
Link to comment
Share on other sites

Just to preface a few goals. No jquery, as simple as possible, no ids. I want something that can be implemented anywhere quickly like a little module that has no pre-req but works in all browsers.

 

So simple I want a hide/show toggle on an adjacent div. He links to one css file, and one js file and puts the one line of html which is the hide show button. I have not seen it done anywhere and I wanna make this work. I will keep playing myself and posting code, but please keep throwing back ideas.

Link to comment
Share on other sites

I need to get some sleep so maybe someone has an idea what is going on while I sleep...

window.onload = init;function init(){	var inputs = document.getElementsByClassName('hideShowLabel');	for(i=0 ; i<inputs.length ; i++){		if (inputs[i].className == 'hideShowLabel') {			var nS = inputs[i].nextSibling;			while(nS.nodeType != '1') { nS = nS.nextSibling; break; }			nS.className = 'hideShowContent';		}	}}function hideShowToggle(e) {	var btn = e.childNodes[1];	var aftClk = e.childNodes[3].value;	var bfrClk = e.childNodes[5].value;	var nS = e.nextSibling;	btn.value = btn.value == aftClk ? bfrClk : aftClk;	while(nS.nodeType != '1') { nS = nS.nextSibling; break; }	nS.style.display = nS.style.display == 'block' ? 'none' : 'block';}

Saying I have two hide show buttons, when I click the first one the first time it shows both, but if I click to hide the 2nd and click the first twice it has no effect on the 2nd. Why does it effect both on first click? If I click the 2nd it has no effect on the first no matter what. Also note this was not happening until after I added the init function. Thanks for any input...

Link to comment
Share on other sites

As long as you have layout of trigger div directly follow by opening show/hide div and outer container with specific class, this seems to work IE6 + and safari, FF

<!DOCTYPE html><html>    <head>        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">        <meta name="viewport" id="viewport" content="target-densitydpi=high-dpi,initial-scale=1.0,user-scalable=no" />        <title>Document Title</title>        <script>            window.onload = init;            function init() {                var div_elem = document.getElementsByTagName('DIV');                var div_count = 0;                for (i = 0; i < div_elem.length; i++)                {                    if (div_elem[i].className === 'hide_show') // identify parent container                    {                        var children = div_elem[i].children; // listing of children elements ('div') (.children list elements only not text nodes)                        for (div_children = 0; div_children < children.length; div_children++)                        {                            if (div_children % 2 === 0) //apply onclick to 1st and every second div after that                            {                                children[div_children].onclick = function() {                                    hideShowToggle(this);                                };                            }                            else                            {                                children[div_children].style.display = "none"; //hide the rest for show/hide                            }                        }                    }                }            }            ;            function ignoretextnodes(cur_element, tag) //filter out text node to select required element only            {                if (tag === 'DIV') //for div                {                    while (cur_element.nodeName.toLowerCase() !== tag.toLowerCase()) {                        cur_element = cur_element.nextSibling;                    }                }                else // will list #text as well as input so set it as 'INPUT'                {                    tag = 'INPUT';                     for (i = 0; i < cur_element.length; i++)                    {                        if (cur_element[i].nodeName.toLowerCase() === tag.toLowerCase())                        {                            cur_element = cur_element[i];                            break;                        }                    }                }                return cur_element;            }            function hideShowToggle(a) {                var element = a.nextSibling;                element = ignoretextnodes(element, a.nodeName); // nodeName identifys it as DIV                element.style.display = element.style.display === 'block' ? 'none' : 'block';                var input = a.childNodes;                input = ignoretextnodes(input, a.childNodes[0].nodeName); // .childNodes[0].nodeName will identify it a '#text' or 'INPUT'                input.value = input.value === 'Hide' ? 'Show' : 'Hide';            }        </script>        <style type="text/css">            .hide_show{font-size: 1em;}        </style>    </head>    <body>        <div class="hide_show">            <div><input type="button" value="Show">            </div>            <div>div 1 NOTE: it does not matter if elements stacked, butted together or have space placed between them</div>            <div>                <input type="button" value="Show">            </div>            <div>div 2</div>            <div>                <input type="button" value="Show">            </div>            <div>div 3</div>            <div>                <input type="button" value="Show">            </div>            <div>div 4</div>        </div>        <hr />        <div class="hide_show">            <div><input type="button" value="Show">            </div>            <div>div 5</div>            <div>                <input type="button" value="Show">            </div>            <div>div 6</div>            <div>                <input type="button" value="Show">            </div>            <div>div 7</div>            <div>                <input type="button" value="Show">            </div>            <div>div 8</div>        </div>    </body></html>
Link to comment
Share on other sites

Your whole system makes sense but way to much code than is needed and it focuses on divs only. If you read my previous post that does everything I am expecting, but the whole hide show cascading from the start for what reason, I don't know...

Link to comment
Share on other sites

The reason there is more code is because its unobtrusive, All the onclick, events, hiding the content divs by default, is taken care off by this code, it also depending what you want to target loops through chilldNodes filtering out #text nodes created by spaces and line break/stacking of html coding, which you still seem to be ignoring by targeting with e.childNodes[3] and e.childNodes[5], this will work for some browsers but not for others, and the statement about 'focusing on divs' I thought that what you want when i see original code

 

if this is a input button

var element = a.nextSibling;

 

it won't have childnodes var input = a.childNodes;

if is button element, it can't have childnode of inputs.

if it is anchor you have to disable its default action, and depending on doctype maybe invalid to use.

 

So i choose div, and if it bothers you so much its not difficult to change, but all this could be avoided if you just supplied html code that gave us a better idea on what you a trying to achieve and with what! instead of us guessing all the time.

Link to comment
Share on other sites

Sorry for the miscommunication about DIVs, I should have been more clear. Also not to sound rude or anything but my goal (and reason for not showing html) is because it should be nearly irrelevant. To make it easier though I will attach it here:

<style>	* {		font-family: Helvetica,Arial;	}	html,body {		margin: 0;		padding: 0;	}	.hideShowInput {		width: 0;		height: 0;		display: none;		position: absolute;	}	.hideShowLabel,.hideShowButton {		margin: 0;		border: 0;		width: 200px;		padding: 8px 0;		font-size: 100%;		position: relative;		cursor: pointer;	}	.hideShowButton {		background: red;		z-index: -1;	}	.hideShowLabel:hover .hideShowButton {		background: blue;		color: #ffffff;	}	.show {		display: inline-block;	}	.hide,.hideShowContent {		display: none;	}</style><script type="text/javascript">//<!--	window.onload = init;	function init(){		var inputs = document.getElementsByClassName('hideShowLabel');		for(i=0 ; i<inputs.length ; i++){			if (inputs[i].className == 'hideShowLabel') {				var nS = inputs[i].nextSibling;				while(nS.nodeType != '1') { nS = nS.nextSibling; break; }				nS.className = 'hideShowContent';			}		}	}	function hideShowToggle(e) {		var btn = e.childNodes[1];		var aftClk = e.childNodes[3].value;		var bfrClk = e.childNodes[5].value;		var nS = e.nextSibling;		btn.value = btn.value == aftClk ? bfrClk : aftClk;		while(nS.nodeType != '1') { nS = nS.nextSibling; break; }		nS.style.display = nS.style.display == 'block' ? 'none' : 'block';	}//--></script>		<!--[if lte IE 9]>			<noscript>				<style>					.hideShowLabel {						display: none;					}					.hideShowContent {						display: block;					}				</style>			</noscript>				<style>					.hideShowButton {						z-index: 1;					}				</style>		<![endif]-->		<input type="checkbox" id="hideShowInput1" class="hideShowInput"/>		<label for="hideShowInput1" class="hideShowLabel" onclick="hideShowToggle(this)"><!--			--><input type="button" class="hideShowButton show" value="Show"/><!--			--><input type="button" class="hideShowButton hide" value="Hide"/><!--			--><input type="hidden" class="hideShowButton show" value="Show"/><!--		--></label>		<div>Stuff</div>		<br/>		<input type="checkbox" id="hideShowInput2" class="hideShowInput"/>		<label for="hideShowInput2" class="hideShowLabel" onclick="hideShowToggle(this)"><!--			--><input type="button" class="hideShowButton show" value="Show"/><!--			--><input type="button" class="hideShowButton hide" value="Hide"/><!--			--><input type="hidden" class="hideShowButton show" value="Show"/><!--		--></label>		<a href="#">RANDOM LINK</a>		<br/>		<input type="checkbox" id="hideShowInput3" class="hideShowInput"/>		<label for="hideShowInput3" class="hideShowLabel" onclick="hideShowToggle(this)"><!--			--><input type="button" class="hideShowButton show" value="Show"/><!--			--><input type="button" class="hideShowButton hide" value="Hide"/><!--			--><input type="hidden" class="hideShowButton show" value="Show"/><!--		--></label>		<p>BIG GIANT TEXT IN A PARAGRAPH AREA</p>

I have tested this working in all browsers (other than aforementioned adding of the init which causes them to cascade on when first clicking.

 

Thanks for any input.

Link to comment
Share on other sites

document.getElementsByClassName('hideShowLabel'); is not supported in IE6 to IE8 so this is ignored, and the class to hide elements is not applied.

multiple inputs within label, and input id ref that is within label that does not match label for 'for' attribute value is not valid.

Edited by dsonesuk
Link to comment
Share on other sites

The label acts as click event for input its enclosing, so you have to prevent this default action, something weird? having the input button at z-index:-1; prevented clicking of label being registered in IE11... what am i saying!, as expected IE came up with something weird nutty idea? that having the input button at z-index:-1; now prevented clicking of label being registered.

<!DOCTYPE html><html>    <head>        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">        <meta name="viewport" id="viewport" content="target-densitydpi=high-dpi,initial-scale=1.0,user-scalable=no" />        <title>Document Title</title>        <script>            window.onload = init;            function init() {                var all_elem = document.getElementsByTagName('*');                for (i = 0; i < all_elem.length; i++)                {                    if (all_elem[i].className === 'hide_show_wrap')                    {                        var children = all_elem[i].children;                        for (elem_idx = 0; elem_idx < children.length; elem_idx++)                        {                            if (children[elem_idx].className === "hide_show")                            {                                // children[elem_idx].className = "hide_show";                                children[elem_idx].onclick = function(event) {                                    var e = event;// better browsers                                     if (!e)                                        e = window.event; // and then IE method                                    hideShowToggle(this, e);                                };                                var hidethese = children[elem_idx].nextSibling;                                hidethese = ignoretextnodes(hidethese, children[elem_idx].className);                                hidethese.style.display = 'none';                            }                        }                    }                }            }            function hideShowToggle(a, e) {                var class_name = a.className;                e.preventDefault ? e.preventDefault() : e.returnValue = false; // better browsers and then IE method                var element = a.nextSibling;                element = ignoretextnodes(element, class_name);                element.style.display = element.style.display === 'block' ? 'none' : 'block';                var child_elem = a.childNodes;                class_name = "n/a";                child_elem = ignoretextnodes(child_elem, class_name);                child_elem.value = child_elem.value === 'Hide' ? 'Show' : 'Hide';            }            function ignoretextnodes(cur_element, class_name)            {                if (class_name === 'hide_show')                {                    while (cur_element.nodeType != '1') {                        cur_element = cur_element.nextSibling;                        break;                    }                }                else                {                    for (i = 0; i < cur_element.length; i++)                    {                        if (cur_element[i].nodeType == 1)                        {                            cur_element = cur_element[i];                            break;                        }                    }                }                return cur_element;            }        </script>        <style type="text/css">            .hide_show{font-size: 1em;}            * {                font-family: Helvetica,Arial;            }            html,body {                margin: 0;                padding: 0;            }            .hideShowInput {                width: 0;                height: 0;                display: none;                position: absolute;            }            .hideShowLabel,.hideShowButton {                margin: 0;                border: 0;                width: 200px;                padding: 8px 0;                font-size: 100%;                position: relative;                cursor: pointer;            }            .hideShowButton {                background: red;                /* z-index: -1; DO NOT use -1 if you still want be able to select the label element IE 11*/                z-index: 0;            }            .hideShowLabel:hover .hideShowButton {                background: blue;                color: #ffffff;            }            .show {                display: inline-block;            }            .hide,.hideShowContent {                display: none;            }        </style>    </head>    <body>        <div class="hide_show_wrap">            <!--[if lte IE 9]>            <noscript>                    <style>                            .hideShowLabel {                                    display: none;                            }                            .hideShowContent {                                    display: block;                            }                    </style>            </noscript>                    <style>                            .hideShowButton {                                    z-index: 1;                            }                    </style>    <![endif]-->            <input type="checkbox" id="hideShowInput1" class="hideShowInput"/>            <label class="hide_show" >                <input type="button" class="hideShowButton show" value="Show"/></label>            <div>Stuff</div>            <br/>            <input type="checkbox" id="hideShowInput2" class="hideShowInput"/>            <label class="hide_show" >                <input type="button" class="hideShowButton show" value="Show"/></label>            <a href="#">RANDOM LINK</a>            <br/>            <input type="checkbox" id="hideShowInput3" class="hideShowInput"/>            <label  class="hide_show" ><input type="button" class="hideShowButton show" value="Show"/></label>            <p>BIG GIANT TEXT IN A PARAGRAPH AREA</p>            <p></p>        </div>        <p></p>    </body></html>
Edited by dsonesuk
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...