Web Design/Layout toggle

This is a demonstration for toggling between two layouts using a script that toggles between the CSS class names "grid" and "list".

The HTML document used in the demo has the style sheet and script on board, embedded using <style> and <script> tags respectively. The appearance of the elements is determined through classes in the style sheet. The items' descriptions are hidden in grid view. The source code is annotated with comments.

This demonstration also makes use of tooltips using the title attribute, showing additional details when the pointer is hovered on some of the elements.

When the user toggles between the list and grid views, the text in the button is changed accordingly. The script memorizes the last used view by storing it into a browser cookie, and loads it when opened the next time. The Firefox browser stores cookies for local HTML files (file:///), as has been successfully tested. A localhost server might be necessary to test this feature on a different browser.

At the end of the script, the one list item is multiplied between 10 to 50 times and sets a random background colour for variety. This would not be done on an actual website, and serves for illustration in this demo.

Source code edit

<!DOCTYPE html>
<html lang="en">
<head>
	<title>Grid and list layout toggle demo</title>
	<meta name="author" content="Elominius from Wikiversity">

	<!-- necessary to render the interpuncts correctly in older browser versions -->
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">

	<!-- compatibility -->
	<meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>

<button class="toggle_grid_list_button" id="toggle_grid_list_button" onclick="toggleView_demo();">Toggle to grid view</button>
<!-- changed to "Toggle to list view" and back by the script when pressed -->

<ul class="item_container grid">
	<li>
		<div class="thumbnail_wrapper">
			<div class="thumbnail_container">
				<!-- a picture would go here -->
				<span class="thumbnail_duration">6:38</span>
			</div>
		</div>
		<div class="info_wrapper">
			<div class="title_container" title="This is a title.">
				<!-- fake hyperlink colour for illustration purposes -->
				<span class=fake_URL>This is a title.</span>
			</div>
		    <div class="status_container">
				<!-- non-breaking spaces after numbers -->
				by <span class="channel_name fake_URL" title="312,147&nbsp;subscribers">VideoCreator</span> <span class="subscriber_count list_show">(312,147&nbsp;subscribers)</span><time class="upload_date" datetime="2022-06-12T10:11:16" title="June 12, 2022 &#10;10:11:16 (UTC)">2&nbsp;weeks ago</time><span class="view_count" title="56,887 views since 24 hours">1,013,237&nbsp;views</span><span class="rating" title="106,228 likes, &#10;3187 dislikes">97% liked</span>
			</div>
			<div class="tag_container list_show">
				<span class="video_tag">This is a tag.</span>
				<span class="video_tag">example</span>
				<span class="video_tag">demo</span>
			</div>
			<div class="description_container list_show">
				This is the description. It can only be seen in list view. It does not matter if it contains too much text for the space, since excess text can be hidden or extend downwards.
			</div>
		</div>
	</li>
</ul>

<!-- This "type" attribute serves as a label for the source code, and is not necessary in modern browsers. Same with "text/javascript". -->
<style type="text/css">
/* font pack */
body { font-family: 'noto sans', ubuntu, 'segoe ui', futura, arial, helvetica, 'trebuchet ms', tahoma, verdana, sans-serif; }

/* shared style */
.item_container { 
	list-style: none;
	padding-right: 1em;

}
.thumbnail_container { 
	width:256px; height:144px;
	position: relative; /* necessary to keep .thumbnail_duration inside thumbnail */
    border: 2px solid grey;
    margin-right: 1em;
    margin-bottom: 0.5em;
    background-color: lightblue; /* placeholder */
    border-radius: 5px;
}

	/* title and status */
.title_container { font-size:20pt; }
.fake_URL { color:#48C; } /* for illustration */
.status_container { font-size:10pt; color:#555; }

	/* tags */
.tag_container { color:grey; font-size:small;  }
.video_tag { border:1px solid #38F; border-radius:10px; padding:0 0.5em; }
.video_tag:hover { color: #ddd; }

	/* description */
.description_container { 
	display:block;
	/* description height limit */
	max-height:5em; /* 5 lines */
	overflow-y: hidden; /* hide excess text */
	/* end line with ellipsis for newer browsers */
	/* display: -webkit-box;
	text-overflow:ellipsis;
	-webkit-box-orient: vertical;
	-webkit-line-clamp: 5; */
}

.thumbnail_duration {
	display: inline-block;
	position: absolute;
	background-color: rgba(0,0,0,0.5);
	padding:0 4px;
	bottom:0; right:0;
	border-radius: 5px; /* fallback */
	border-radius: 5px 0 3px 0;
}

	/* only visible in specific modes */
.grid_show { display: none; }
.list_show { display: none; }

/* grid view */
.item_container.grid .grid_show { display: inline; }
	/* moves title closer to video above than below to clarify that it belongs to the former */
.item_container.grid .status_container { padding-bottom: 15pt; }
.item_container.grid .thumbnail_container { margin-bottom: 0; }

.item_container.grid li { 
	/* limit width per item */
	display: inline-block;
    width: 256px;
    margin-right:1em;

}

	/* hide description – obsolete due to .list_show */
/* .item_container.grid .description_container { display:none; } */


/* list view */
.item_container.list .list_show { display: inline; }
.item_container.list .description_container {
	display:block;
	width:calc(100% - 300px); /* limit width to avoid breaking underneath */
}
.item_container.list li { display:block; clear:both; } /* extend over entire row */
.item_container.list .thumbnail_wrapper { 
	display: inline-block;
    width: 256px;
	float:left;
    margin-right: 1em;
}

.item_container.list .info_wrapper { 
	/* Deactivated due to possibility of it getting below the thumbnail. Might be necessary on older browsers, but would require fixed width. */
	/* display: inline-block; */
    /* width: 256px; */
}

/* responsive width - puts information below thumbnail on narrow displays (optional) */
@media (max-width: 720px) {
  .item_container.list .thumbnail_wrapper { float:none; }
  .item_container.list .description_container { width: 100%; }
}

/* dark theme (optional) */
body { background-color:#222; color:#eee; }
.status_container { color:#aaa; }
</style>

<script type="text/javascript">
// put item container into shortcut variable
var item_container = document.getElementsByClassName("item_container")[0];
var first_item = item_container.getElementsByTagName("li")[0];
var toggle_grid_list_button = document.querySelectorAll(".toggle_grid_list_button")[0];

// set view
function setView(mode) {
	// if none set, default to grid
	if (item_container.className.search("grid")+item_container.className.search("list") == -2) {
		item_container.className="item_container grid";
		document.cookie = "view_mode=grid";
	}

	switch(mode) {
		case "list":
			// replaces the "grid" class with "list"
			item_container.className = item_container.className.replace('grid','list');

			// changes button label
			toggle_grid_list_button.innerHTML="Toggle to grid view";

			// stores view mode into cookie
			document.cookie = "view_mode=list";
			break;
		case "grid":
			// replaces the "list" class with "grid"
			item_container.className = item_container.className.replace('list','grid');

			// changes button label
			toggle_grid_list_button.innerHTML="Toggle to list view";

			// stores view mode into cookie
			document.cookie = "view_mode=grid";
			break;
	}
}

// toggle view
function toggleView_demo(mode) {
	if (
		// checks if grid mode is activated by looking for the word "grid" in the class
		item_container.className.search("grid") > -1
	) {
		setView("list");
	} else if (
		// checks for list mode
		item_container.className.search("list") > -1
	) {
		setView("grid");
	} else { 
		// add "grid" class by default
		item_container.className+=" grid";
		document.cookie = "view_mode=grid";
	}
}

// Cookie function dependencies
function setCookie(cname, cvalue, exdays) {
  var d = new Date();
  d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
  var expires = "expires="+d.toUTCString();
  document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}

function getCookie(cname) {
  var name = cname + "=";
  var ca = document.cookie.split(';');
  for(var i = 0; i < ca.length; i++) {
    var c = ca[i];
    while (c.charAt(0) == ' ') {
      c = c.substring(1);
    }
    if (c.indexOf(name) == 0) {
      return c.substring(name.length, c.length);
    }
  }
  return "";
}


// check configuration (note: only Firefox stores cookies for locally opened HTML files.)
if ( getCookie("view_mode") == "grid" ) setView("grid");
if ( getCookie("view_mode") == "list" ) setView("list");


// Description width fallback for older browsers – uncomment and optionally convert to "onresize" if necessary.
/*
document.body.appendChild( document.createElement("style") );
var description_width_fallback = document.body.lastChild;
document.body.lastChild.className="description_width_fallback"; // label for page inspector

window.addEventListener('resize', function(event) {
	if ( item_container.className.search("list") > -1 ) {
		description_width_fallback.innerHTML = ".item_container.list .description_container { width: " + (first_item.offsetWidth-400) +"px; }";
	}
}, true);
*/

// Multiply list items for illustrative purposes – the following code would not be implemented on an actual web site.
var li_1_content = first_item.innerHTML;
var color_list = ['darkred', 'green', 'blue', 'yellow', 'orange', 'darkorange', 'darkgreen', 'darkseagreen', 'lightyellow', 'lightblue', 'lightskyblue', 'lightgreen', 'teal', 'turquoise', 'darkturquoise', 'mediumturquoise', 'lightcoral', 'antiquewhite', 'aqua', 'aquamarine', 'purple', 'violet', 'darkviolet', 'indigo', '#38F', 'lightseagreen', 'deepskyblue', 'steelblue', 'royalblue', 'beige', 'ivory', 'gray', 'slategray', 'darkslategray', 'wheat', 'gold', 'silver', 'brown', 'olive', 'lime', 'limegreen', 'greenyellow', 'yellowgreen', 'seagreen', 'crimson'];
var count = 0;

for (
	count = 0; // start counter
	count < Math.floor(10+Math.random()*40); // repeat 10 to 50 times
	count++ // count up
	) {
	item_container.appendChild(	document.createElement("li") );
	item_container.lastChild.innerHTML=li_1_content;
	// random color
	var random_number = Math.floor(Math.random()*(color_list.length));
	item_container.lastChild.querySelector(".thumbnail_container").style.backgroundColor=color_list[random_number];
}
</script>

</body>
</html>