1. Advertising
    y u no do it?

    Advertising (learn more)

    Advertise virtually anything here, with CPM banner ads, CPM email ads and CPC contextual links. You can target relevant areas of the site and show ads based on geographical location of the user if you wish.

    Starts at just $1 per CPM or $0.10 per CPC.

How to effectively show/hide parts of table cells (and replace js onclick)?

Discussion in 'HTML & Website Design' started by tomasz86, Jun 23, 2016.

  1. #1
    I have the following code.

    HTML:
    
    <table border=1>
       <tr id=row1>
         <td><label><input type=checkbox onclick=toggle('row1')></label></td>
         <td>cell 2<div>cell 2 details<div></td>
         <td>cell 3<div>cell 3 details</div></td>
       </tr>
       <tr id=row2>
         <td><label><input type=checkbox onclick=toggle('row2')></label></td>
         <td>cell 2<div>cell 2 details<div></td>
         <td>cell 3<div>cell 3 details</div></td>
       </tr>
       <tr id=row3>
         <td><label><input type=checkbox onclick=toggle('row3')></label></td>
         <td>cell 2<div>cell 2 details<div></td>
         <td>cell 3<div>cell 3 details</div></td>
       </tr>
    </table>
    
    Code (markup):
    CSS:
    
    div {
       display: none;
    }
    
    .visible div {
       display: block !important;
    }
    
    Code (markup):
    JS:
    
    function toggle(n) {
      n = document.getElementById(n), n.className.match(/\bvisible\b/) ? n.className = n.className.replace(/(?:^|\s)visible(?!\S)/g, "") : n.className += " visible"
    }
    
    Code (markup):
    Codepen:
    Full: http://codepen.io/tomasz86/pen/KMadGj
    Debug: http://s.codepen.io/tomasz86/debug/KMadGj

    I am looking for a better way to show / hide parts of table cells. This is just a sample code but the real site is constructed in the same way.

    Could anyone suggest any improvements to deal with this situation? I have tried to use pure CSS solutions such as the checkbox hack or :target. The former does not work as the hidden elements are not child or siblings of the input element. The latter does work but it does not seem possible to show elements from different rows at the same time.

    I would prefer not to add empty elements such as labels for each checkbox, etc. before the table.

    On a side note, my JavaScript skills are very basic so please understand :) I know that using onclick is not recommended so if there is any better way to do this I am open to all suggestions.

    PS Just in case someone is worried about accessibility - all of the hidden content is made visible when JS is disabled.
     
    tomasz86, Jun 23, 2016 IP
  2. sarahk

    sarahk iTamer Staff

    Messages:
    28,494
    Likes Received:
    4,457
    Best Answers:
    123
    Trophy Points:
    665
    #2
    Sticking to pure javascript I came up with http://codepen.io/anon/pen/wWgKLO

    I use a classname to identify the rows - it's a handy corruption of the class attribute
    I also changed the class .visible div to .visible
    Yours was looking for a div within the element that had the class. The other alternate notation would be div.visible
    I changed the name of the function to toggleVisibility which was a little OCD but I like names to be a) clear and b) unlikely to clash with another function that might be added now or in the future.
     
    sarahk, Jun 23, 2016 IP
  3. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #3
    Not sure why you're wasting a label around that... good scripting should hook the markup not the other way around (aka don't use onevent), and you would probably be better off node-walking to the parent you want to change instead of targeting by ID... and quotes are NOT optional around attribute values unless you're still writing HTML 3.2 like it's 1997! Likewise, the border attribute has ZERO business on ANY HTML written after that time either.

    Basically, use less markup and more scripting, if nothing else scripting is cached when markup may not.
     
    deathshadow, Jun 24, 2016 IP
  4. tomasz86

    tomasz86 Greenhorn

    Messages:
    24
    Likes Received:
    2
    Best Answers:
    0
    Trophy Points:
    18
    #4
    Thank you Sarah.

    Could you tell me though what specific advantages your code has over the current one? Is there any reason not to target the rows as I am doing right now?

    This was just a quickly written sample so I did not bother to put the quotes there, etc. as this was not the point. The labels are also normally not empty as there is a text for screen readers there. I have updated the Codepen now and added the quotes and the text. I do not agree on the border attribute though as adding it (with the value set to "1") basically tells browsers that this table is not used for layout.

    After W3C:
    Source: https://www.w3.org/TR/html-markup/table.html

    It should be the other way round but the reality is different.

    This is exactly what I would like to do. At best I would like to get rid of the onclick attribute all together but really do not know how to do it with JS (and the CSS only methods do not seem to be applicable here). Do you have any specific ideas how to do it?
     
    tomasz86, Jun 24, 2016 IP
  5. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #5
    Are you ***ing ***ing me? I mean, I've been saying it for seven years that the whatWG's steaming pile of manure "specification" is there to drag development back to the WORST of late 1990's pre-strict development -- but now they are just bound and determined to show they give a flying purple fish about semantic markup or anything 4 Strict was trying to accomplish, aren't they?

    If it's not tabular data, DON'T USE A TABLE -- even having that attribute be the trigger means they're saying it's ok to use it for layout, at which point you know what? **** THAT ****!

    Sweet hey-Zeus H. Mammary plowing Christmas, that's it, the kid gloves are coming off. I'm ****ing DONE letting the W3C piss all over the internet with all the ignorant halfwit dumbass BULLSHIT that they've been pissing on the HTML specification with since 5 dropped. This is even more mind-numbingly dumbass than HGROUP was!!!

    I swear, the ignorant ****'s who are behind HTML 5 won't be happy until FONT and CENTER are valid again. That seems to be their final plan given the outright nonsensical bullshit they've been crapping into it willy-nilly. I'm tempted to figure out where they meet and make Orlando look like an accident. If they put BORDER back in, they are too stupid to be allowed to LIVE, much less continue being the ones in charge of the HTML specification.

    ... and I thought Aria roles, MAIN, SECTION, NAV, FOOTER, ARTICLE, and ASIDE were idiotic pointless redundancies. Nope... why don't they just admit it, they want HTML 3.2 back!

    -------------

    HTML 5 re-re bleeding edge of 1997 development bullshit aside, hooking the elements is pretty easy. I'd probably either give them each a class and get them with document.getElementByClassName, or give the parent table a class and target it via getElementsByTagName inside each of the tables with that class.

    either way, once you have the list of elements, you just addEventListener (or attachEvent if you need legacy IE support) the event, use event.target (or window.event.srcElement) to figure out which input got clicked, then do element.parentNode.parentNode to target the TR.
     
    deathshadow, Jun 24, 2016 IP
  6. tomasz86

    tomasz86 Greenhorn

    Messages:
    24
    Likes Received:
    2
    Best Answers:
    0
    Trophy Points:
    18
    #6
    Tables are used for layout, hence there is no border by default. If the table is not used for layout, add "border=1" and display the border.

    This is what seem to be saying... :/

    Anyway, I will try to modify the code and come back if I manage to remove the onclick and replace it with a class.
     
    tomasz86, Jun 24, 2016 IP
  7. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #7
    Tables are for tabular data, that's why we have SEMANTICS and why choosing tags should be based on the MEANING, and NOT the default appearance or behavior. That's why tables for layout is idiotic halfwit bullshit from the 1990's, and anyone using a table just for layout clearly never embraced ANY of the improvements that 4 Strict and nearly 18 years of progress have given us. (Either that or is in fact writing HTML 3.2 for e-mail since mail clients HTML support is stuck at 1997)

    It is so mind-numbingly dumbass that said section is even in the specification, there are no polite words for it. ANYONE who thinks that is correct or has any place in there is such an absolute ignoramus that they make creationists, anti-vaxxers, holistic healers, and foodies look grounded in reality!

    If you are choosing your tags based one what they look like and not what they mean, you are choosing all the wrong tags for all the wrong reasons. That was the message of 4 Strict, the entire reason HTML came into being in the first place, the entire concept behind semantic markup and separation of presentation from content...

    There are NO polite words to respond to that being in the specification. It's as idiotic as an "assault weapons" ban that doesn't ban based on round fired, barrel length, mechanical action, round capacity, accuracy, rate of fire, or any other meaningful data, but instead just based on the furniture hung off the weapon. Mossberg 500 legal, Mossberg 500 with pistol grip and stock that collapses a whole whopping three inches illegal.

    It's THAT stupid. Time to update my What's wrong with HTML 5 article.
     
    deathshadow, Jun 24, 2016 IP
  8. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #8
    Oh yes, because adding that fat bloated steaming pile of halfwit ineptitude would be SO much more efficient.

    jQuery: bloat more, work harder, think less...
     
    deathshadow, Jun 24, 2016 IP
  9. kk5st

    kk5st Prominent Member

    Messages:
    3,497
    Likes Received:
    376
    Best Answers:
    29
    Trophy Points:
    335
    #9
    It is never beside the point to write correct syntax.

    That is plain and simple BS. The browser – no browser for that matter – does not listen or care about whether the table is a table or a layout element. Whoever fed you that was presenting you with an unfiltered fart.
     
    kk5st, Jun 24, 2016 IP
  10. sarahk

    sarahk iTamer Staff

    Messages:
    28,494
    Likes Received:
    4,457
    Best Answers:
    123
    Trophy Points:
    665
    #10
    oh lordy, you walked into that one didn't you :)

    I tend to agree that you should only use tables for tabular data but my code wasn't relying on the table.

    Which is better? I guess I prefer to play it safe and act on the items I'm directly wanting to change. I guess if the div's still had a class and you had
    .visibility .detailcell {}
    it would be safer.
     
    sarahk, Jun 24, 2016 IP
  11. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #11
    Sadly, who appears to be feeding people that is the W3C with idiotic changes to the HTML 5 specification... or if you prefer the versionless whatWG BS "HTML" -- as it's in both now... Hence the fact that I'm taking a day off to sit and stew about it before I rip the W3C a new hole in their backside with a new update on my site.

    ...and true proof that the "future" of HTML in the WhatWG and W3C's eyes is what was once called HTML 3.2
     
    deathshadow, Jun 24, 2016 IP
  12. tomasz86

    tomasz86 Greenhorn

    Messages:
    24
    Likes Received:
    2
    Best Answers:
    0
    Trophy Points:
    18
    #12
    The problem with the border attribute is that this is the current HTML5 specification and I am pretty sure that most people will just take it for granted. The border=1 was used in the code following that specification (i.e. to say that the table is not for layout). I have removed it for now just not to provoke any more discussion (because this really has nothing to do with the original question). If it was for me personally I would remove the border attribute all together and also make browsers display table borders by default (this would however break all those sites that rely on them for layout).
     
    tomasz86, Jun 24, 2016 IP
  13. kk5st

    kk5st Prominent Member

    Messages:
    3,497
    Likes Received:
    376
    Best Answers:
    29
    Trophy Points:
    335
    #13
    The role attribute makes at least some sense for layout tables. The value should be re-specified to read, "theAuthorIsAnIdiotWhoDoesNotKnowEnoughToBeAllowedNearAWebPage".

    I confess to not having read the table section; I mean, how could you eff up the table syntax?

    gary
     
    kk5st, Jun 24, 2016 IP
  14. PoPSiCLe

    PoPSiCLe Illustrious Member

    Messages:
    4,623
    Likes Received:
    725
    Best Answers:
    152
    Trophy Points:
    470
    #14
    That would be a good thing. If that was the case, breaking all those sites would be VERY GOOD, since it might force them to rethink their crappy code. There is NO REASON WHATSOEVER to use tables for layout. Not one. There haven't been for nearly 20 years.
     
    PoPSiCLe, Jun 25, 2016 IP
  15. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #15
    Ok, setting aside the W3C being run by fools who I have zero confidence in building a specification that makes sense anymore... and focusing on the actual question of this thread...

    One of the big things I think would help is to review some basic rules of JavaScript and accessible design, and apply them.

    The biggest of these rules being "If you can't make a website that functions without scripting first, you have zero business adding scripting to it" and "if an element only functions when scripting is enabled, it shouldn't be in the markup."

    These two combined means that by default scripting off, all your sub-DIV should in fact be showing their content -- and that the TD and input column that controls the show/hide has no business even existing in the HTML!

    Another rule is that you should avoid (when possible) abusing form elements outside of forms for non-form behaviors. Whilst I violate that rule for CSS because the :checked behavior is too good to give up, in this case since we can't make the checkboxes be kin to TR why use a input? Between the fact that on IE your click toggle could in fact end up out of sync with the check state, that you would HAVE to trap both onclick and onchange to be sure of full cross browser support... I'd switch to using a span and then style it as desired in the CSS.

    So first step, I'd gut the markup down to:
    
    <table class="showHideDetails">
    	<caption>
    		Sample table
    	</caption>
    	<thead>
    		<tr>
    			<th scope="col">Column 1</th>
    			<th scope="col">Column 2</th>
    		</tr>
    	</thead><tbody>
    		<tr>
    			<td>cell 2<div>cell 2 details</div></td>
    			<td>cell 3<div>cell 3 details</div></td>
    		</tr><tr>
    			<td>cell 2<div>cell 2 details</div></td>
    			<td>cell 3<div>cell 3 details</div></td>
    		</tr><tr>
    			<td>cell 2<div>cell 2 details</div></td>
    			<td>cell 3<div>cell 3 details</div></td>
    		</tr>
    	</tbody>
    </table>
    
    Code (markup):
    For the scripting side, I would have something like this. It's a bit of code, but it gracefully degrades and has a lot of functionality you wouldn't otherwise have.

    
    /*
    	Show/hide table rows by adding TD and span to table
    	Jason M. Knight, June 2016
    */
    
    // put in self-instancing function to isolate scope
    (function(d) {
    
    	// start with some handy helper functions
    	
    	function classAdd(e, newClass) {
    		if (!classExists(e, newClass)) {
    			e.className += (e.className ? ' ' : '') + newClass;
    			return true;
    		}
    		return false;
    	}
    
    	function classExists(e, searchClass) {
    		return RegExp('(\\s|^)' + searchClass + '(\\s|$)').test(e.className);
    	}
    
    	function classRemove(e, removeClass) {
    		if (classExists(e, removeClass)) {
    			e.className = e.className.replace(
    				new RegExp('(\\s|^)' + removeClass + '(\\s|$)'), ' '
    			) . replace(/^\s+|\s+$/g,'');
    			return true;
    		}
    		return false;
    	}
    
    	function classToggle(e, toggleClass) {
    		if (classRemove(e, toggleClass)) return false;
    		classAdd(e, toggleClass);
    		return true;
    	}
    	
    	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 make(tagName, content, parent, attr) {
    		var e = d.createElement(tagName);
    		if (content) nodeAdd(e, content);
    		if (attr) nodeAttr(e, attr);
    		// ALWAYS do parent last, or IE will ignore new attribs
    		if (parent) parent.appendChild(e);
    		return e;
    	} 
    	
    	function nodeAdd(e, newNode) {
    		e.appendChild(
    			typeof newNode == 'object' ? newNode : d.createTextNode(newNode)
    		);
    	}
    	
    	function nodeAttr(e, attr) {
    		for (var i in attr) {
    			if (typeof attr[i] == 'object') {
    				if (typeof e[i] !== 'object') e[i] = {};
    				nodeAttr(e[i], attr[i]);
    			} else e[i] = attr[i];
    		}
    	}
    	
    	// then our actual functionality
    	
    	function showHideClick(e) {
    		e = eventProcess(e, true);
    		classToggle(e.target.parentNode.parentNode, 'show');
    	}
    	
    	var tables = d.getElementsByTagName('table');
    	for (var i = 0, iLen = tables.length; i < iLen; i++) {
    		if (classExists(tables[i], 'showHideDetails')) {
    			classAdd(tables[i], 'showHideActive');
    			for (var j = 0, jLen = tables[i].rows.length; j < jLen; j++) {
    				var tr = tables[i].rows[j], cell;
    				switch (tr.parentNode.tagName) {
    					case 'THEAD':
    						tr.insertBefore(make('th', 'Detail'), tr.firstChild);
    					break;
    					case 'TBODY':
    						tr.insertBefore(make(
    							'td',
    							make('span', false, false, { onclick : showHideClick }),
    							false,
    							{ className : 'showHide'}
    						), tr.firstChild);
    				} // switch
    			} // for rows
    		} // if showHideDetails
    	} // for tables
    	
    })(document);
    
    Code (markup):
    I start out wrapping the whole thing in a self instancing functions so that all our variables and functions are isolated in scope from any other scripting on the page. I then have a small library of helper functions just to make doing things easier cross-browser without resorting to a massive bloated wreck like jQuery. Some of these functions are redundant to some of the newer ECMAScript stuff -- but they work in older browsers and laughably seem to run faster than the new routines.

    In our code that actually provides the functionality we first make the function to show/hide click. For this I just toggle a single class on and off. The target for this is derived from the element that submits the event, looking for the parent TR of its parent TD. My handy little eventProcess function normalizes the "event" object cross-browser compatible, and prevents the even from further propagating.

    We then need to attach this function by creating the scripting only elements on the DOM. I use the DOM rather than innerHTML as the former can get quite tricky when inserting elements, and it trips the parser which can spike CPU use, introduce unwanted behaviors, and is otherwise just sloppy coding. When possible, just say no to innerHTML and anything that works like it.

    First I get all the tables that have our .showHideDetails class. I 'brute force' this via getElementsByTagName for legacy browser compatibility as I still often have to support IE8/earlier. Really no sane page should have enough tables for the "disadvantage" of not using the AGONIZINGLY SLOW "querySelector" or the somewhat more efficient "getElementsByClassName" functions for there to be any legitimate reason not to just use the most compatible method.

    To trigger that yes, scripting is working I then add "showHideActive" to the table. We then iterate each row adding a leading cell to them. If the row is inside THEAD, we add a TH with the word "Detail" inside it, if it's in TBODY we make a TD with the class "showHide" that has a empty span inside it. Said span is where our click handler is added.

    NORMALLY in JavaScript you'd "never use the onclick attribute" since other scripts could be trying to hook elements, but we have the ONE exception to that rule in that we JUST made the element with document.createElement, so until this script ends no other scripts will even know it exists.

    Writing it this way gives you the potential for this code to actually work all the way back to IE 5.x, for little if any extra effort.

    Finally we just need a bit of CSS to make things "work"

    
    .showHideDetails .showHide span {
    	display:inline-block;
    	width:1em;
    	height:1em;
    	border:1px solid #000;
      -webkit-touch-callout:none;
      -webkit-user-select:none;
      -khtml-user-select:none;
      -moz-user-select:none;
      -ms-user-select:none;
      user-select:none;
    }
    
    .showHideDetails .show .showHide span {
    	background:#AAA;
    }
    
    .showHideDetails .show .showHide span:before {
    	content:'x';
    	position:relative;
    	top:-0.33em;
    }
    
    .showHideActive tbody div {
    	display:none;
    }
    
    .showHideActive tbody .show div {
    	display:block;
    }
    
    Code (markup):
    Not exactly rocket science. We style the span to behave like a checkbox, we use the (painfully prefixed that we STILL have to prefix in everything) user-select option to prevent fast clicking on the span trying to select text, and we add the actual show/hide of the nested DIV.

    I put a demo of this up live here:
    http://www.cutcodedown.com/for_others/tomasz86/template.html

    As with all my examples the directory:
    http://www.cutcodedown.com/for_others/tomasz86/

    Is wide open for easy access to the gooey bits and pieces.

    I threw some cute styling on it, and of course since we're NOT using a checkbox you now have the freedom to style than span however you like... images, CSS3 tricks, SVG... go wild with it!

    ... and again, while it's more code, all you need to do is include the code before </body> and ANY table with the "showHideDetails" class on it will have the elements automatically added to it for you, with all the scripting hooks attached automatically. This also means that once this script is loaded/cached you're sending less markup, so it can actually use far less markup if you use this effect across multiple pages... and of course most importantly, scripting off you don't have form input elements that do dick and the content will be shown fully expanded.

    Hope this helps.
     
    deathshadow, Jun 26, 2016 IP
    malky66, sarahk and PoPSiCLe like this.
  16. tomasz86

    tomasz86 Greenhorn

    Messages:
    24
    Likes Received:
    2
    Best Answers:
    0
    Trophy Points:
    18
    #16
    Thank you very much, deathshadow.

    As you have said, the script will let me remove all the toggle related elements from the HTML which will reduce the code significantly (the tables in question have hundreds of rows). You have also mentioned IE having problems with syncing the check state - actually the same thing happens in Firefox too so I am very happy to get rid of the checkbox all together.

    I am not using jQuery or any other libraries. This is in fact the only script I am interested in using on this particular page. There is a lot of content in each cell and without hiding the details the table becomes extremely long and difficult to navigate. I do not mind the script code size if it comes with better cross-browser support, especially for older versions of IE.

    I have just one question regarding accessibility. When CSS is disabled and JS enabled the "Details" column is still displayed, yet its functionality is not needed (since the details are visible anyway).

    See http://codepen.io/tomasz86/full/dXNEOL/

    Is there any preferred method to deal with this? The two methods that come to my mind are either 1) using CSS pseudo elements to add the text instead of JS (which is bad practice) or 2) using the HTML5's "hidden" attribute (which is kind of controversial). I was also thinking about adding visually hidden text for screen readers but this faces the same problem.
     
    tomasz86, Jun 27, 2016 IP
  17. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #17
    I'd go with method #1 if that matters to you. The thing that makes "generated content" bad practice is when it doesn't degrade... the thing is in this case, it's the PERFECT usage scenario... I'll modify the script in question to reflect that change as it's actually a pretty good idea. The only "problem" with that approach is IE7/earlier won't get that text... OH WELL.

    Relevant changes:
    
    					case 'THEAD':
    						tr.insertBefore(
    							make('th', false, false, {
    								className : 'showHide',
    								scope : 'row'
    							}),
    							tr.firstChild
    						);
    					break;
    
    Code (markup):
    ... and in the CSS:
    
    .showHideActive thead .showHide:before {
    	content:"Details";
    }
    Code (markup):
    As the TH it's applied to will only exist if scripting is active, having the text then applied only when both are working is fine and dandy, and NOT what I'd consider "bad practice" -- well, unless you care about IE7/earlier actually getting that text. In theory you could probably use :first-child instead of the class since that's IE8+ as well, but since we're generating it from the script and not the markup I'm a bit less gun-shy about that approach.

    I wish there was a "clean" way to tell if CSS is active from JS, sadly most methods are hit-or-miss and highly unreliable and sloppy.

    Side note -- it's DAMNED refreshing to see someone who cares about ALL the combinations of scripting and CSS disabled, and not just blindly assuming they're all present all the time.
     
    deathshadow, Jun 27, 2016 IP
  18. tomasz86

    tomasz86 Greenhorn

    Messages:
    24
    Likes Received:
    2
    Best Answers:
    0
    Trophy Points:
    18
    #18
    It is partly because I believe that the Internet should be accessible to everyone, especially considering the fact that adding at least some accessibility to a website is really nothing compared to something like adding a wheelchair ramp to a building entrance (just a real life example), and partly because I have experienced many problems with inaccessible websites myself which would not have existed if the site designers had paid just a little bit of attention to users (all users, not just the majority) needs at the beginning.

    I would consider at least seven different cases when it comes to JS, CSS, images, and fonts.

    1) CSS ON / JS ON. The most common environment - no need to talk more about it.
    2) CSS ON / JS OFF. The latter can be turned off for various reasons - let them be company policies, privacy, performance or just user preferences. Unfortunately a huge number of websites simply do not work at all with JS turned off, and many others are only partly accessible. I personally turn off JS for privacy and performance reasons in some of my configurations.
    3) CSS OFF / JS ON. This is probably not a common case but still possible, e.g. when the CSS simply could not be loaded, a particular kind of screen reader is used, or simply a user has his or her own CSS which overwrites the original one.
    4) CSS OFF / JS OFF. I think this one is actually more common than many realise. Text only browsers are the obvious but also services like RSS feeds rely on basic HTML for content. Many sites have a markup which simply does not degrade gracefully at all, resulting in a complete mess which is impossible to decipher. I myself have experienced some issues when saving websites to Evernote which in some cases stripped the CSS from them, and the results were very, very bad.
    5) Images OFF. I turn off images to save data on mobile and have seen many sites that rely on images way too much, and those images often have no alt text so the content is simply unavailable.
    6) Custom fonts ON / Web fonts OFF. Web fonts can be disabled for various reasons, e.g. for privacy and security (like in NoScript), better legibility, special needs (dyslexia, etc.) or simply due to user preferences. I personally was forced to disable Web fonts when I had a CRT monitor (just a few years ago) and the majority of Web fonts were unreadable with gray anti-aliasing used instead of ClearType (in Windows). Only the Web fonts that have been delta hinted may work but they are scarce (PT Sans?) and even then there still are browser related issues. The core problem with disabling or overriding Web fonts is that a lot of sites rely on icon fonts to display their (often critical) content and all of that becomes overridden, resulting in strange characters displayed instead of the intended icons. Firefox was supposed to fix this recently by allowing certain icon fonts to load but there are still sites where the fonts are not working properly. I do not think other browsers have a solution to this issue at all.
    7) Windows high-contrast mode. This is tricky because it turns off all CSS inserted graphics while leaving the proper images, i.e. the ones inserted through img. The problem is that many websites use CSS background images, sprites, etc. for proper content and not just for decoration. Thus, such sites are very problematic to use in high-contrast mode.

    There are probably many more use cases but these are just a few I would always test a website against in order to eliminate at least some of the most obvious problems. You also do not need any advanced tools to test as just a proper web browser with developer tools should be more than enough.

    Coming back to the script, thank you again deathshadow for the fix. I have modified the script a little bit so that there is a checkbox in the thead th too which can be used to show details of all table cells. I have also added a text for screen readers although I am not sure whether is should be in all cells, or rather only in the thead.

    HTML
    
    <!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
    <meta
      name="viewport"
      content="width=device-width,height=device-height,initial-scale=1"
    >
    <link
      rel="stylesheet"
      href="screen.css"
      media="screen,projection,tv"
    >
    <title>
      Table Detail Show/Hide Demo
    </title>
    
    </head><body>
    
    <h1>
      Table Detail Show/Hide Demo
    </h1>
    
    <table class="showHideDetails">
      <caption>
        Sample table
      </caption>
      <thead>
        <tr>
          <th scope="col">Column 1</th>
          <th scope="col">Column 2</th>
        </tr>
      </thead><tbody>
        <tr>
          <td>cell 2<div class="details">cell 2 details</div></td>
          <td>cell 3<div class="details">cell 3 details</div></td>
        </tr><tr>
          <td>cell 2<div class="details">cell 2 details</div></td>
          <td>cell 3<div class="details">cell 3 details</div></td>
        </tr><tr>
          <td>cell 2<div class="details">cell 2 details</div></td>
          <td>cell 3<div class="details">cell 3 details</div></td>
        </tr>
      </tbody>
    </table>
    
    <script src="showHideTableDetails.js"></script>
    
    </body></html>
    
    Code (markup):
    CSS
    
    /* null margins and padding to give good cross-browser baseline */
    html,body,address,blockquote,div,
    form,fieldset,caption,
    h1,h2,h3,h4,h5,h6,
    hr,ul,li,ol,ul,
    table,tr,td,th,p,img {
        margin:0;
        padding:0;
    }
    
    img, fieldset {
        border:none;
    }
    
    hr {
        display:none;
        /*
            HR in my code are for semantic breaks in topic/section, NOT
            style/presenation, so hide them from screen.css users
        */
    }
    
    /* fix for legacy iOS and windows Mobile devices */
    @media (max-width:512px) {
        * {
            -webkit-text-size-adjust:none;
            -ms-text-size-adjust:none;
        }
    }
    
    /* fix for HDX displays like the Kindle Fire HDX */
    @media
        (-webkit-min-device-pixel-ratio:2) and (min-width:1600px),
        (min-resolution:172dpi) and (min-width:1600px)
    {
        html { font-size:200%; }
    }
    
    body {
        min-width:192px;
        font:normal 85%/150% arial,helvetica,sans-serif;
    }
    
    h1 {
        padding:0.5em;
        text-align:center;
        font:bold 200%/120% arial,helvetica,sans-serif;
    }
    
    .showHideDetails {
        width:100%;
        max-width:32em;
        margin:0 auto 1em;
        border-spacing:0.25em;
        border:solid #AAA;
        border-width:0 2px 2px;
    }
    
    * html .showHideDetails {
        /* legacy IE can't do max width, shove fixed width at them, OH WELL! */
        width:24em;
    }
    
    .showHideDetails caption {
        padding:0.33em;
        font:bold 150%/120% arial,helvetica,sans-serif;
        background:#AAA;
    }
    
    .showHideDetails th {
        text-align:left;
        padding:0.5em;
    }
    
    .showHideDetails td {
        padding:0.5em;
        vertical-align:top;
        border:2px solid #AAA;
    }
    
    .showHideActive .showHide {
        width:1%;
        border:0;
        text-align:center;
    }
    
    .showHideActive .showHideToggle {
        display:inline-block;
        width:1em;
        height:1em;
        border:1px solid #000;
        -webkit-touch-callout:none;
        -webkit-user-select:none;
        -khtml-user-select:none;
        -moz-user-select:none;
        -ms-user-select:none;
        user-select:none;
      cursor:pointer;
    }
    
    .show .showHideToggle {
        background:#AAA;
    }
    
    .show .showHideToggle:before {
        content:'✓';
        position:relative;
        top:-.175em;
    }
    
    .showHideDetails .details {
        border-top:2px solid #DDD;
        padding-top:0.5em;
        margin-top:0.5em;
    }
    
    .showHideActive .details {
        display:none;
    }
    
    .show .details {
        display:block;
    }
    
    .showHideActive .showHide:before {
      clip:rect(1px 1px 1px 1px);
        content:"Show details";
      position:absolute;
    }
    
    .show tbody .showHide {
      visibility:hidden;
    }
    
    Code (markup):
    JS
    
    /*
      Show/hide table rows by adding TD and span to table
      Jason M. Knight, June 2016
    */
    
    // put in self-instancing function to isolate scope
    (function(d) {
    
      // start with some handy helper functions
      function classAdd(e, newClass) {
        if (!classExists(e, newClass)) {
          e.className += (e.className ? ' ' : '') + newClass;
          return true;
        }
        return false;
      }
    
      function classExists(e, searchClass) {
        return RegExp('(\\s|^)' + searchClass + '(\\s|$)').test(e.className);
      }
    
      function classRemove(e, removeClass) {
        if (classExists(e, removeClass)) {
          e.className = e.className.replace(
            new RegExp('(\\s|^)' + removeClass + '(\\s|$)'), ' '
          ) . replace(/^\s+|\s+$/g,'');
          return true;
        }
        return false;
      }
    
      function classToggle(e, toggleClass) {
        if (classRemove(e, toggleClass)) return false;
        classAdd(e, toggleClass);
        return true;
      }
      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 make(tagName, content, parent, attr) {
        var e = d.createElement(tagName);
        if (content) nodeAdd(e, content);
        if (attr) nodeAttr(e, attr);
        // ALWAYS do parent last, or IE will ignore new attribs
        if (parent) parent.appendChild(e);
        return e;
      }
      function nodeAdd(e, newNode) {
        e.appendChild(
          typeof newNode == 'object' ? newNode : d.createTextNode(newNode)
        );
      }
      function nodeAttr(e, attr) {
        for (var i in attr) {
          if (typeof attr[i] == 'object') {
            if (typeof e[i] !== 'object') e[i] = {};
            nodeAttr(e[i], attr[i]);
          } else e[i] = attr[i];
        }
      }
      // then our actual functionality
      function showHideClick(e) {
        e = eventProcess(e, true);
        classToggle(e.target.parentNode.parentNode, 'show');
      }
    
      function showHideAllClick(e) {
        e = eventProcess(e, true);
        classToggle(e.target.parentNode.parentNode.parentNode.parentNode, 'show');
      }
      var tables = d.getElementsByTagName('table');
      for (var i = 0, iLen = tables.length; i < iLen; i++) {
        if (classExists(tables[i], 'showHideDetails')) {
          classAdd(tables[i], 'showHideActive');
          for (var j = 0, jLen = tables[i].rows.length; j < jLen; j++) {
            var tr = tables[i].rows[j];
            switch (tr.parentNode.tagName) {
              case 'THEAD':
                tr.insertBefore(
                  make('th',
                    make('span', false, false, { className: 'showHideToggle', onclick : showHideAllClick }),
                    false, {
                    className : 'showHide',
                    scope : 'col'
                  }),
                  tr.firstChild
                );
              break;
              case 'TBODY':
                tr.insertBefore(make(
                  'td',
                  make('span', false, false, { className: 'showHideToggle', onclick : showHideClick }),
                  false,
                  { className : 'showHide' }
                ), tr.firstChild);
            } // switch
          } // for rows
        } // if showHideDetails
      } // for tables
    })(document);
    Code (markup):
    Codepen: http://codepen.io/tomasz86/pen/OXpjoG

    Please let me know if you think something can be done better or if there are any issues.

    PS Should not these inserted tds actually be ths with scope="row"?
     
    Last edited: Jun 28, 2016
    tomasz86, Jun 28, 2016 IP
  19. tomasz86

    tomasz86 Greenhorn

    Messages:
    24
    Likes Received:
    2
    Best Answers:
    0
    Trophy Points:
    18
    #19
    I have found another accessibility related issue, and this one is rather important. At the moment the checkboxes are not focusable and cannot be accessed with keyboard at all. Just adding tabindex makes them focusable but it is still not possible to interact with them. I have searched for possible solutions and found http://jakub-g.github.io/accessibility/onclick/ where they basically recommend using either inputs / buttons or anchors for maximum keyboard compatibility.

    What do you think?
     
    tomasz86, Jun 29, 2016 IP
  20. deathshadow

    deathshadow Acclaimed Member

    Messages:
    9,732
    Likes Received:
    1,998
    Best Answers:
    253
    Trophy Points:
    515
    #20
    oops, complete and total screwup on my part. Use button instead of span. duh. Still avoids the headache of checkbox's oddball click/change behavior, but you can focus it normally.

    I just uploaded a revised copy:
    http://www.cutcodedown.com/for_others/tomasz86/

    Relevant changes:

    JS:
    
    						tr.insertBefore(make(
    							'td',
    							make('button', false, false, { onclick : showHideClick }),
    							false,
    							{ className : 'showHide' }
    						), tr.firstChild);
    
    Code (markup):
    CSS:
    
    .showHideDetails .showHide button {
    	display:block;
    	margin:0.4em auto 0;
    	width:1.5em;
    	height:1.5em;
    	text-align:center;
    	border:1px solid #000;
    	-webkit-touch-callout:none;
    	-webkit-user-select:none;
    	-khtml-user-select:none;
    	-moz-user-select:none;
    	-ms-user-select:none;
    	user-select:none;
    }
    
    .showHideDetails .showHide button:active,
    .showHideDetails .showHide button:focus,
    .showHideDetails .showHide button:hover {
    	background:#8AC;
    }
    
    .showHideDetails .show .showHide button {
    	background:#AAA;
    }
    
    .showHideDetails .show .showHide button:before {
    	content:'x';
    	position:relative;
    }
    
    Code (markup):
    Gah, rookie mistake on my part -- I KNEW better than that.

    I also targeted :Active and :focus for full modern and legacy key support (some older UA's incorrectly use :active instead of :focus) and gave it a bit more of a visual style aid.

    I swear senility is setting in, I literally already knew this, and then went and herped it out anyways.
     
    deathshadow, Jun 29, 2016 IP