What’s the point of HTML5 Boilerplate?

This isn’t a rhetorical question, I’m genuinely trying to understand what the point of this package is. I’m sure a lot of people have invested a lot of time and energy to it, but maybe I’m just missing something.

And I did read the “Why it is good” section on the project site, but still couldn’t figure out why all of this shouldn’t be known to a designer to begin with as it seems to me that this stuff is a collection of Googlings or stuff that can be resolved by browsing the spec page for a bit. And browser quirks aren’t something that will trouble you if you keep to sane uses of CSS and HTML; a lot of problems can be avoided when following the rule of Less is more.

Cross-browser compatible (IE6+, yeah we got that.)

IE6 is dead. Supporting a dead or dying browser makes no sense at all and, realistically speaking, any “advanced” feature you’re trying to push via HTML5 on your site will be thoroughly broken on it anyway.

Browsers that matter (I.E. ones you want to push your flashy new site on) will likely have other issues besides how well it renders every pixel on screen. Besides that, “cross-browser compatible” is usually a byword term for “not accessible” so in essence there’s a lot of mutually exclusive stuff pushed with the package with regard to accessibility.

HTML5 ready. Use the new tags with certainty.

Modernizr allows you to do this already and most browsers (even the old ones) simply treat a lot of the new architecture tags like <article>, <section> or <nav> as just a div by default.

Video and audio are the sticking points for most HTML5 sites with media and while you can do embedding fairly easily, the hard part is the codec standard (which none of the major vendors are agreeing on) so that leaves the embed tag you choose a non-issue compared to the codec headaches.

Optimal caching and compression rules for grade-A performance

jQuery caches dynamically loaded scripts as does Modernizr and most web hosts already have mod_deflate enabled for HTML, CSS and JavaScript.

Best practice site configuration defaults

Less is more. See above.

Often times, if you’re struggling to make a layout look exactly the same as a mockup, you’re writing more markup and not effective markup. CSS and HTML only get complicated when you try to do things that are best left doing with images rather than pure markup.

Mobile browser optimizations

Less is more (2). See above.

Also, modern mobile browsers are fully capable of rendering a page meant for a full screen; Mobile browsers have a zoom feature. The issue isn’t the rendering, it’s the data plan. Worry more about “how much” you’re sending to the mobile device rather than “how” it’s rendered.

Progressive enhancement graceful degradation … yeah yeah we got that

Less is more (3). See above.

Browsers that will render HTML4 will render HTML5 without issue for the most part. CSS 3 with conditional 2.1 or older may help with older browsers, but what will really help a visitor is a helpful reminder (if they’re on IE 6) that they’re using a dangerously insecure browser and should upgrade right away. Let’s be more concerned with our civic duty to protect the web and its users than how pretty our sites look for a change.

IE specific classes for maximum cross-browser control

Less is more (4). See above.

There’s a line in Star Wars Episode IV: A New Hope by Princess Leia to governor Tarkin

The more you tighten your grip, Tarkin, the more star systems will slip through your fingers

Sensible control is a better option than “maximum” control. You want your site to look good (“perfect” is a dream until we all start using the same browser; at which point I’d rather not be developing for the web) so think about this… what is it that you’re trying to accomplish with your site? And does your preoccupation with perfection keeping you from accomplishing it?

Handy .no-js and .js classes to style based on capability

Less is more (5). See above.

Sensible use of CSS 2.1 and 3  will get a great deal of the same functionality as JS. I’d much rather see designers become familiar with 2.1 first as there are a lot of basics that get skipped that lay a good foundation. You would be amazed at how little markup you actually need to get a good result.

Also, JS gets ignored by three kind of visitors :

  • JS Disabled, but capable – Aren’t interested in being bothered with ads or other flashy nonsense and will likely be interested in the content rather than presentation. Be concerned with the quality of the content and just make it available without hurdles.
  • JS Incapable – Usually screen readers, text only or other such specialty browser. CSS is only applicable for accessibility.
  • Bots – Don’t need CSS anyway.

Again, here is a good case for using sensible CSS rather than clever CSS.

Console.log nerfing so you won’t break anyone by mistake.

This is vendor specific stuff that shouldn’t be encouraged too much. Firebug is fine and all, but if something “breaks” something else, it’s usually a result of poor encapsulation.

Also… Less is more (6). See above.

Never go wrong with your doctype or markup!

Never underestimate a novice developer’s ability to break the unbreakable. I remember Adam Savage of the Mythbusters once giving a presentation and mentioned how surprised he was that actors were able to break welded steel on certain stage props (back in his prop making days) by merely handling them. If there’s a way to break it, they will find it. Also of note, Less is more (7) and the less markup you have, the less you will likely break it.

An optimal print stylesheet, performance optimized.

If your HTML5 layout can’t be made printer friendly with the following :

body { color:#000 !important; background:#fff !important;}
aside, footer { display:none !important; }

You’re doing it wrong or doing it too complicated.

Also… Less is more (8). See above.

iOS, Android, Opera Mobile-adaptable markup and CSS skeleton.

Less is more (9). See above.

As mentioned before, mobile devices today aren’t the text only readers of yesteryear. When multi-touch is becoming commonplace, your chief concern should still be bulk, not pixel perfection.

Fun fact: A pixel is not really a pixel on a mobile screen.

.clearfix, .visuallyhidden classes to style things wisely and accessibly.

Never been a fan of hacks. You can bash IE all you want, but there are ways to get around it without resorting to too much CSS witchcraft. And since Less is more (10), you automatically improve accesibility by simply not piling too much on the browser to begin with and taking a browse at the above linked spec page for HTML5 and CSS3 at w3schools.

.htaccess file that allows proper use of HTML5 features and faster page load

Also guarantees to break any CMS. There are some things in the default .htaccess that do make sense like denying access to hidden folders or log and other such special files, but then there are other things that are completely pedantic or just plain asinine. Like forcibly rewriting http://www.example.com into example.com. Honestly what’s the big deal?

And measures like setting session.cookie_httponly makes sense if you don’t use JavaScript to manipulate cookies on your own web application. This is a classic case of more security not necessarily being better than just better security. You should be vetting your application for SQL injection and XSS vulnerabilities and not handicapping yourself. Security is a process, not a destination, and the .htaccess isn’t something to be casually played with. You’re better off not having a .htaccess file there at all and instead a link to the OWASP page so anyone who downloads the package can familiarlize themselves and understand what it is they’re doing rather than copy > pasting.

And then there’s this :

# Force the latest IE version, in various cases when it may fall back to IE7 mode
#  github.com/rails/rails/commit/123eb25#commitcomment-118920
# Use ChromeFrame if it’s installed for a better experience for the poor IE folk

This may be a secret to a lot of people and you may not know this about IE users, but please believe me when I say this, it’s completely true. Most IE users DON’T GIVE A SH!# ABOUT WHICH JS ENGINE THEY HAVE!!

If your site needs a JS engine swap to make it work better on IE7, then it’s more poorly designed than IE7.

Also, did I mention Less is more (11)? See above.

CDN hosted jQuery with local fallback failsafe.

I’m fairly certain that the Google library or the one hosted by Microsoft are unlikely to be unavailable unless you’re hosting your site in Iran or China (do they block ajax.googleapis.com?) Other than that, this is a 3 second fix for anyone using Modernizr or jQuery.

Think there’s too much? The HTML5 Boilerplate is delete-key friendly. :)

Enter-key friendly will always leave less cruft in your code than delete-key friendly.

Here’s all you need to start with HTML 5 (add more as necessary) using just the bare essentials to get going.

A basic index.html file

<!DOCTYPE html>
<html lang="en-us">
<head>
	<meta charset="UTF-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1" />

	<title>Site title</title>
	<script type="text/javascript" src="lib/modernizr.min.js"></script>
	<script type="text/javascript">
		var spath = "lib/";
	</script>
	<script type="text/javascript" src="lib/loader.js"></script>
	<link rel="stylesheet" href="style.css" type="text/css" />
</head>
<body>
	<div class="page">
		<header>
			<div class="title">
				<h1><a href=".">This is a site</a></h1>
				<p>Some sorta description</p>
			</div>
		</header>
		<article>
			<section class="column two-thirds">
				<h2>Section header</h2>
				<p>This is where your main content goes.</p>
			</section>
			<aside class="column one-third">
				<h3>This can be the sidebar</h3>
				<p>Put your links and stuff here.</p>
			</aside>
			<footer>
				<p>Copyright and stuff here</p>
			</footer>
		</article>
	</div>
</body>
</html>

You can download Modernizr here. Using modernizr, here is the loader.js file (gets the basics downloaded including jQuery, validation and jQuery UI).

if(!window.jQuery)
Modernizr.load([{
	load: "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"
}, {
	load: "https://ajax.aspnetcdn.com/ajax/jquery.validate/1.9/jquery.validate.min.js"
}, {
	load: "https://ajax.aspnetcdn.com/ajax/jquery.validate/1.9/additional-methods.min.js"
}, {
	load: "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js"
}, {
	load: spath + "yourlib.js"
}]);

You can add more of your own libraries as necessary, but yourlib.js can be just a jQuery specific code file.

$(function () {
// More of your stuff here
});

And here’s a usable style.css that you can run with.

/* Default */
body
{
	font-family: "Segoe UI" , Tahoma, Sans-Serif;
	font-weight: normal;
	font-size: medium;
}

@media screen
{
	/* Reset */
	body, div, header, article, section, aside, footer,
	p, h1, h2, h3, h4, h5, h6, ul, li, blockquote,
	form, fieldset, legend, input, textarea, select,
	table, td, th, hr
	{
		margin: 0;
		padding: 0;
		border: 0;
		line-height: normal;
	}

	body
	{
		font: normal 86% "Segoe UI" , "Myriad" , Tahoma, Sans-serif;
		color: #333;
		background: #fff;
		margin: 0;
		padding: .7em;
	}

	div.page
	{
		width: 80%;
		max-width: 1200px;
		min-width: 800px;
		text-align: left;
		margin: 0 auto 1em auto;
	}

	/* Headings */
	h1, h2, h3, h4
	{
		font-weight: normal;
		padding: .4em 0 .1em 0;
	}
	h1
	{
		font-size: 230%;
		color: #a33;
	}
	h2
	{
		font-size: 150%;
		padding: .4em 0;
	}
	h3
	{
		font-size: 140%;

	}
	aside h3
	{
		border:1px dotted #aaa;
		border-width:0 0 1px 0;
	}
	h4
	{
		font-size: 130%;
	}
	h5
	{
		font-size: 120%;
	}
	h6
	{
		font-size: 110%;
	}

	/* Page segments */
	article, header, footer, hr
	{
		clear: both;
	}
	header
	{
	}
	article
	{
		width: 100%;
	}

	header:first-child
	{
		margin: 0 0 1em 0;
		border: 1px dotted #aaa;
		border-width: 0 0 1px 0;
	}
	footer
	{
		border: 1px dotted #aaa;
		border-width: 1px 0 0 0;
	}

	aside
	{
		background: #f8f8ff;
		box-shadow:3px 3px 3px #ddd;
		border-radius:3px;
	}

	hr
	{
		background: #a33;
		height: 1px;
		margin: .5em 0;
	}

	/* Paragraphs */
	p
	{
		line-height: 140%;
		padding: 1em;
	}

	.column p
	{
		padding:1em 0;
	}

	section p
	{
		padding: .2em 0 1em 0;
	}
	aside p
	{
		padding: 1em !important;
	}

	form p
	{
		line-height:normal;
		padding:.2em .5em .5em .5em !important;
	}
	header p
	{
		padding: 0 0 1em 0;
	}

	footer p
	{
		font-size: 90%;
		color: #000;
	}
	blockquote p
	{
		padding: .4em 0;
	}

	/* Block content */
	blockquote
	{
		background: #f5f5ff;
		border: 1px dashed #aaa;
		padding: .4em 1em;
		margin: .5em 0;
		border-radius:.5em;
	}

	/* Columns */
	.column
	{
		float: left;
		margin: 0 2% 0 0;
		padding: .2em 0;
	}

	.half
	{
		width: 47.4%;
	}

	.two-thirds
	{
		width: 64.7%;
	}

	.one-third
	{
		width: 31.3%;
	}

	.one-fourth
	{
		width: 23%;
	}

	.one-fifth
	{
		width: 18%;
	}

	/* Images */
	img
	{
		vertical-align: middle;
	}
	a img
	{
		border: 0;
	}

	/* Links */
	a
	{
		color: #a33;
	}
	nav a
	{
		color: #a33 !important;
	}
	h1 a, h2 a, h3 a, h4 a
	{
		text-decoration: none;
	}
	h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover
	{
		text-decoration: underline;
	}

	/* Pager */
	nav.page
	{
		font-size:120%;
		padding:1em 0;
		clear:both;
	}

	nav.page strong, nav.page a
	{
		font-weight:bold;
	}
	nav.page a
	{
		text-decoration:none;
		font-size:90%;
	}
	nav.page a:hover
	{
		box-shadow:2px 2px 2px #ddd;
	}
	nav.page strong
	{
		font-size:120%;
	}

	/* Tags */
	aside p.tags
	{
		line-height: 200% !important;
	}
	a.tag
	{
		padding: 7px 3px !important;
		text-decoration: none;
	}
	a.tag:hover
	{
		text-decoration: underline;
	}
	a.x1
	{
		font-size: 11px;
	}
	a.x2
	{
		font-size: 12px;
	}
	a.x3
	{
		font-size: 13px;
	}
	a.x4
	{
		font-size: 14px;
	}
	a.x5
	{
		font-size: 15px;
	}
	a.x6
	{
		font-size: 16px;
	}
	a.x7
	{
		font-size: 17px;
	}
	a.x7
	{
		font-size: 18px;
	}
	a.x8
	{
		font-size: 18px;
	}
	a.x9
	{
		font-size: 19px;
	}
	a.x10
	{
		font-size: 20px;
	}
	a.x11
	{
		font-size: 21px;
	}
	a.x12
	{
		font-size: 22px;
	}
	a.x13
	{
		font-size: 23px;
	}
	a.x14
	{
		font-size: 24px;
	}
	a.x15
	{
		font-size: 25px;
	}

	/* Lists */
	ul, ol
	{
		margin: 1em 2em;
	}
	ul li, ol li
	{
		margin: 0 0 .3em 0;
	}

	/* Form elements */
	header form
	{
		float: right;
		margin:3.5em 0 0 0;
	}
	header form legend
	{
		display: none;
	}

	fieldset
	{
		border: 1px solid #a33;
		border-color:#a33 #ddd #ddd #ddd;
		box-shadow:3px 3px 3px #ddd;
		margin: 0 0 1em 0;
		padding:.3em;
	}

	.column fieldset
	{
		min-height:15em;
	}

	legend
	{
		font-size: 130%;
		padding: .2em .3em;
		margin: .2em 1em 0 1em;
	}

	input[type^='button'], input[type^='text'], input[type^='password'], textarea
	{
		border: 1px solid #999;
		border-radius:.3em;
		background: #fff;
		color: #575757;
		padding: .4em;
	}
	.column input[type^='text'], .column input[type^='password'], textarea
	{
		width: 90%;
	}
	input[type^='text']:focus, input[type^='password']:focus, textarea:focus
	{
		border-color:#a33;
	}
	textearea {
		font: normal 100% "Segoe UI" , "Myriad" , Tahoma, Sans-serif;
	}

	input[type^='submit'], input[type^='reset'], input[type^='button']
	{
		cursor: pointer;
		background: transparent;
		font-size: 120%;
		color: #a33;
	}

	label input:not([type^='checkbox']), label textarea
	{
		display: block;
	}
	label span
	{
		font-size: 75%;
		font-weight: bold;
		color: #a33;
	}

	input.error
	{
		color: #000;
		background: #fcc;
		border: 1px solid #a33;
	}

	label span
	{
		font: bold 75% sans-serif;
		color: #a33;
	}

	label.error
	{
		padding: 2px;
		color: #fff;
		background: #f33;
	}
}

@media print
{
	body { color:#000 !important; background:#fff !important; }
	aside, footer { display:none !important; }
}

/*
Chocolate? This is doo doo baby!
- Dave Chappelle
*/

I do know that HTML5 Boilerplate is doing a very good job of promoting HTML5 Boilerplate.

All in all, it seems to be a solution to a problem that doesn’t really exist. Or maybe I’m wrong.

Advertisement

Basic: The community version

A little while ago, I created a theme called “Basic” for corporate-ish websites and I’ve been getting requests for a community version; basically a version that would accomodate a blog/forum. I decided to reuse 90% – 95% of the original backend JavaScript to the discussion forum mockup (which I’ve renamed “Road”) including a lot of similar functionality. Except this time, I’m going for a more “component” oriented layout that I can easily turn into tabs. I created a simple tab plugin in jQuery to turn page elements into tabs with minimal overhead and I’m reusing a slightly modified version of the code formatting plugin in the topic page example .

Community index

 

Community tag browsing

 

Most of the changes really went to the topic view page. I made some changes to the code format snippet and moved the user info and avatar away to the side for clarity. The darker colors were partly inspired by the Sublime text editor (thanks to Shannon for mentioning it) as I found it easier to read large blocks of code on a darker background although I’m still not doing any syntax highlighting.

Forum topic view

 

Haven’t change much of the backend code of the add topic form, but I have changed the styling and streamlined things a bit.

Reworded instructions

Code formatting (snippet)

I had a question sent to me asking about formatting code for viewing on a web page purely using HTML and CSS and without using JavaScript or any syntax highlighter of some sort. My initial answer would be that this isn’t usually a good idea since the <pre> and <code> tags are still probaby the best option for displaying code, however it isn’t really that hard to do.

To show code with line numbers, the best way would be to put it into an <ol> (ordered list) and each line into <li> tags.

The CSS portion is fairly straightforward…

/* Code Formatting */
ol.code
{
	margin: 1em 0;
	padding:0 0 0 3em;
	background:#f5f5ff;
	border:1px solid #eee;
}

ol.code li
{
	margin: 0;
	padding: .1em 0 .1em .3em;
	background: #fff;
	white-space: pre-wrap;
	border: 1px solid #eee;
	border-width: 0 0 0 1px;
}

ol.code li:nth-child(even)
{
	background: #f8f8ff;
}

And the HTML (used to display the same code above)…

<ol class="code">
	<li>/* Code Formatting */</li>
	<li>ol.code</li>
	<li>{</li>
	<li>	margin: 1em 0;</li>
	<li>	padding: 0 0 0 2.2em;</li>
	<li>	background: #f5f5ff;</li>
	<li>	border: 1px solid #eee;</li>
	<li>}</li>
	<li></li>
	<li>ol.code li</li>
	<li>{</li>
	<li>	margin: 0;</li>
	<li>	padding: .1em 0 .1em .3em;</li>
	<li>	background:#fff;</li>
	<li>	white-space:pre-wrap;</li>
	<li>	border:1px solid #eee;</li>
	<li>	border-width:0 0 0 1px;</li>
	<li>}</li>
	<li></li>
	<li>ol.code li:nth-child(even)</li>
	<li>{</li>
	<li>	background: #f8f8ff;</li>
	<li>}</li>
</ol>

You can see a running example added to the Basic theme.

The down side to this is that this means if you copy the code off the formatted view, all your line breaks and even some spacing would be lost. There’s still a need to provide an alternative like a textarea view or side <pre> tag. One idea would be to use jQuery UI tabs to switch between formatted and raw views since this way at least you’re reusing existing code as much as possible.

Update

After a couple of back and forth emails, I wrote a very simple jQuery plugin that will turn any text block into a formatted and raw view via a simple tab interface. This is meant to be as fast and as lightweight as possible so it doesn’t have any syntax highlighting.

/*
jQuery fast code format (non-syntax highlighting) plugin
*/

(function($) {
	$.fn.codeformat = function(options) {
		var settings = $.extend({
			rawText : "Raw",
			formattedText: "Formatted"
		}, options);
		
		return this.each(function(i) {
			var c = $(this);
			
			var oc = c.text().split("\n"); // Get each line of code
			var ol = '<ol class="code">'; // <ol> wrapper
			
			// This is faster than "append()";
			$.each(oc, function(j, v) {
				ol += '<li>'+ v +'</li>';
			});
			ol += '</ol>'; // Finish <ol>
			
			// Container
			var w = $('<div id="codeview'+ i +'" class="codeformat" />');
			
			// Tab controls
			var u = $('<ul><li><a href="#codeview'+ i +'f">'+ settings.formattedText +'</a></li>'+
				'<li><a href="#codeview'+ i +'r">'+ settings.rawText +'</a></li></ul>');

			// Formatted and Raw view containers
			var fv = $('<div id="codeview'+ i +'f" class="codeview" />');
			var rv = $('<div id="codeview'+ i +'r" class="codeview" />');
			
			w.insertBefore(c)	// Put the container before the current code view
			u.appendTo(w)	// Put the tabs into the container
			fv.insertAfter(u);	// Put formatted view right after tabs
			rv.insertAfter(fv);	// Put raw view after formatted view
			
			fv.append(ol);	// Insert code lines into formatted view
			c.appendTo(rv)	// Put the current code block into raw view
			
			// Tab controls
			u.find('a:[href="#codeview'+ i +'f"]').bind('click', function(e) {
				if(rv.is(':visible')) {
					fv.toggle();
					rv.toggle();
					$(this).addClass("active");
					u.find('a:[href="#codeview'+ i +'r"]').removeClass("active");
				}
				e.preventDefault();
			});
			u.find('a:[href="#codeview'+ i +'r"]').bind('click', function(e) {
				if(fv.is(':visible')) {
					fv.toggle();
					rv.toggle();
					$(this).addClass("active");
					u.find('a:[href="#codeview'+ i +'f"]').removeClass("active");
				}
				e.preventDefault();
			});

			// Set initial view (show formatted)
			$('#codeview'+ i +'r').toggle();
			$('a:[href="#codeview'+ i +'f"]').addClass("active");
		});
	};
})(jQuery);

 

And the updated CSS

/* Code Formatting */
div.codeformat
{
	padding: .3em;
}

div.codeview
{
	background:#fff;
	color:#444;
	border:1px solid #ddd;
}

/* Code format tabs */
div.codeformat ul:first-child
{
	margin:.4em .2em .3em .2em;
	padding:0;
	list-style:none;
	min-height:1.8em;
}

div.codeformat ul:first-child li 
{
	margin:.3em .3em 0 .5em;
	padding:0;
	float:left;
}

div.codeformat ul:first-child li a
{
	padding:.4em .6em;
	text-decoration:none;
	font-weight:bold;
	color: #999;
	background: #f5f5ff;
	border-radius: 5px 5px 0 0;
	border:1px solid #ddd;
	border-width: 1px 1px 0 1px;
}

div.codeformat ul:first-child li a.active
{
	color: #444;
	background:#fff;
	padding:.4em .6em .5em .6em;
}

code, pre
{
	margin: 1em 0;
	font-family: Monospace;
	font-size:100%;
	overflow-x: scroll;
}
code
{
	white-space: pre;
}
div.codeformat code, div.codeformat pre
{
	margin:.2em .5em;
}

ol.code
{
	margin: 0 !important;
	padding: 0 0 0 3em;
	background: #f5f5ff;
	font-family: Monospace;
}

ol.code li
{
	margin: 0;
	padding: .2em 0 .2em .3em;
	background: #fff;
	white-space: pre-wrap;
	border: 1px solid #ddd;
	border-width: 0 0 0 1px;
}

ol.code li:nth-child(even)
{
	background: #f8f8ff;
}

 

To use this, just add jQuery and the plugin to your page and call the following on any <pre> or <code> block

$('code').codeformat();

You can checkout the updated running example.

Basic: A simple theme for corporate-ish sites

When I say corporate-ish, I mean totally serious and devoid of any shenanigans that may offend your boss… or you, if you’re that type of person. Deadline: 1 hour.

I was going to make this post before, but I just didn’t have the time. This was another design partly inspired by need and part by reader request. I got an email this evening by somone with a very similar need so I figured I’d post it here for anyone else to pickup.

“Basic” would fall into the simple-but-not-simplistic category of designs in that it’s got the bare essentials only, similar to the Simply theme, but with even fewer bells and whistles and yet is still clear, functional and usable. While it can be used as-is, it’s really a learning tool ment to be taken apart and reassembled with any additional hacks by the user. I also left out a lot of older cruft from pre HTML 5 designs as much as possible and the CSS also reflects more v.3 usage.

Basic: Front page

 

While creating this theme, I realised how similar the header is to the asp.net tutorial pages, especially the article header below the top links. This was purely coincidence since this was meant to be a bare minimum theme and Microsoft has a reputation for being terribly unexciting.

Header uses the same font and sizes

 

But since it looked similar at this point anyway, I decided to add a matching breadcrumb navigation…

Since we're this close. Why not go all the way?

 

The difference of course is that the HTML and CSS in my version are completely different. I.E. Simpler and straightforward. In the MS example, they’re using a paragraph with actual backlashes and s to denote seperators. This is way overkill and pointless so in my example, I’m using the <nav> tag and a <ul> list.

The backslashes are added via CSS “after” and “content” :

nav.crumbs ul li:after
{
	content: "/";
}
nav.crumbs ul li:last-child:after
{
	content: "";
}

Since most modern browsers support this anyway, and the fact that these are not inline links, but list items, it pretty well does exactly what a navigation list is meant to do.

The breadcrumbs are shown on the corresponding “topics” example page.

Basic with breadcrumbs

Borrowing from the Discussion Forum mockup where I had placed the new topic, login and registration forms on the same page, I included them on the bottom here and used an expandable “component” function. Basically takes any container with a “component” class and turns the elements inside into widgets with the first <h2> element inside as the handler instead of using jQuery UI tabs or accordion.

The JavaScript is otherwise the same as the forum mockup. Here’s that “component” function :

function initComps() {
	$('.components').children().each(function(i) {
		var b = $(this);

		// First element is usually the title/handler
		var hd = b.children('h2');

		// All other elements need to be wrapped up
b.children(':not(:first-child)').wrapAll('</pre>
<div class="blockct"></div>
<pre>');
		var ct = b.children('.blockct');
		var lnk = hd.append(' <a href="#" rel="showhide">(show)</a>');

		// Find the toggle link in the header and assign the control
		hd.children('a[rel="showhide"]').click(function (e) {
			ct.slideToggle();	// Toggle contents

			// Change link text
			$(this).text(($(this).text() == "(show)")? "(hide)" : "(show)");
			return false;	// Don't return
		});

		ct.hide(); // Initially hide everything
	});
}

If you think the opener to this post is odd : Not that I’m calling my boss boring…

…but I can’t think of a way to finish that sentence.

Bart Simpson

Building a jQuery UI Dashboard

Sometimes you have an existing website or template that needs to function as a dashboard. The only solution then is to create drop-in widget functionality without necessarily modifying the HTML. This is a bit of an update to a much older post

The good news is that if the layout is by and large uncluttered and follows a predictable set of elements (key issue), it’s fairly straightforward to give a layout dashboard functionality without actually touching the HTML structure. jQuery and jQuery UI are perfect for this.

The tricky bit is adding any additional elements for control while handling each segment of content as a widget. In this case, every “widget” can be any element that has the CSS class column applied to it which means we can use the existing <h5> title element as the header for it.

Take as an example, my Simply theme which has no “widget-like” functionality whatsoever and the only JavaScript is Modernizr for backwards compatibility since it was recently rewritten in HTML5. But because all the content elements are predictable (there are sections and one level of nested sections that act as columns), it can be turned into a dashboard without touching the rest of the HTML.

You can take a look at a running demo of it and see below for an explanation of what’s going on.

A running demo of "Simply" turned into a dashboard

For this example, I needed the dashboard widgets to be able to be moved around on each row, have the title, content and the article icon changed and of course be able to close them. Naturally, this is only a client-side example. The final version would need to post these changes server-side for saving.

The Procedure

We can first start by adding the jQuery UI theme CSS file right above the original stylesheet. I’m using a customized version of the Smoothness theme. Just a few tweaks to make it match mine.

<link rel="stylesheet" type="text/css" href="themes/jQueryUI/ui-style.css" id="ui-theme" />
<link rel="stylesheet" type="text/css" href="themes/Simply/style.css" id="theme" />

After that we can add the jQuery and jQuery UI libraries to the template. In my project, there was also a need to add a wysiwyg and I had to use their propietary one, but for this example, I can simply use TinyMCE.

For this example, I’m going to put all the scripts into a folder called /lib. This is also where I’ll extract TinyMCE (the jQuery plugin version).

Modernizr, very usefully, can load other scripts by URL, so we can simply do the following right after adding its JS own file :

<script type="text/javascript" src="lib/modernizr.custom.js"></script>
<script type="text/javascript">
	Modernizr.load([{
		load: "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"
	}, {
		load: "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"
	}, {
		load: "lib/tiny_mce/jquery.tinymce.js"
	}, {
		load: "lib/dash.js"
	}]);
</script>

And that’s all we need to do on the template itself. That last file being called, “dash.js” in the /lib folder is where we’ll put everything else.

dash.js

I’m going to start by adding some custom CSS to this file rather than cluttering up the head section of the template. These are just a few added classes to make the content more presentable.

All this will be added inside the jQuery $(fuction() { }); section.

$('head').append('<style type="text/css">' +
	'.icon { width:75px; height:75px; }' +
	'.ticon { float:left; margin: .3em .5em; width:25px; height:25px; box-shadow:1px 1px 1px #aaa; }' +
	'.ui-dialog { text-align:left; }'+
	'.ui-dialog input[type="text"], .ui-dialog textarea { display:block; width:90%; }' +
	'</style>');

Next, since we’re working with TinyMCE, we’ll store those settings in a variable.

var tinySettings = {
	script_url : 'lib/tiny_mce/tiny_mce.js',
	plugins: "inlinepopups",
	theme: "advanced",
	theme_advanced_buttons1 : "bold,italic,underline,strikethrough," +
		"separator,cut,copy,paste,separator,bullist,numlist," +
		"separator,undo,redo,separator,link,unlink,image",
	theme_advanced_buttons2 : "",
	theme_advanced_buttons3 : "",
	theme_advanced_toolbar_location : "top",
	theme_advanced_toolbar_align : "left",
	theme_advanced_statusbar_location : "bottom",
	width: "100%",
	height: 250,
	content_css : "content.css",
};

You’ll notice the last line refers to a “content.css” file. This basically styles the wysiwyg content as it’s being edited. In my case, this is all it contained…

body {
	font: normal 90% Sans-serif,tahoma,verdana;
	color: #575757;
}

p {
	line-height:170%;
}

Dialogs

Dashboards frequently use dialogs when making changes, presenting confirmations etc… In this case, I just created two dialogs for editing content and to confirm when removing the widget…

function initDialogs() {
	// Close widget dialog
	$('article').append('<div id="dialog-confirm-close-widget" title="Close widget" style="display:none;">' +
		'<span class="ui-icon ui-icon-alert" style="float:left;"></span>' +
		'You are about to delete this widget. Are you sure?</div>');

	// Config widget dialog
	$('article').append('<div id="dialog-config-widget" title="Modify" style="display:none;">' +
		'<form><fieldset><legend>Change widget content</legend>' +
		'<p><label>Title <input type="text" id="widget-title-text" /></label></p>'+
		'<p><label>Content <textarea rows="5" cols="50" id="widget-content-text">Some test content</textarea></label></p>'+
		'<p id="icon-field"><label>Icon URL <span>Will be resized to 75x75 pixels</span><input type="text" id="widget-icon-text" /></label></p>'+
		'</fieldset></form></div>');

	// Create and destroy these dialogs to hide them
	$('#dialog-confirm-close-widget').dialog("destroy");
	$('#dialog-config-widget').dialog("destroy");
}

That textarea will be replaced with TinyMCE later.

3 Little Helpers

Often overlooked, helpers can save a lot of time and help unclutter your JS quite a bit.

The following three check for null or empty strings, initializes and applies TinyMCE to an element with the given settings and removes the wysiwyg after sending the changes back to the textarea… in that order.

function notEmpty(t) {
	if(t) {
		if($.trim(t) != "")
			return true;
	}
	return false
}

function initTiny(t, s) {
	t.tinymce(s);
}

function closeTiny(t) {
	var e = t.attr('id');
	t.val(tinyMCE.get(e).getContent());
	tinyMCE.execCommand('mceRemoveControl', false, e);
}

Widget controls

In jQuery UI, there are ui-icons that we can use here without having to invest extra time to create our own. This also means we can apply our controls to these icons by using something like the “title” or “alt” attribute as the command string. In this case, I opted to use the “title” attribute since I’ll be using these icons in <span> tags.

function setControls(ui) {
	ui = (ui)? ui : $('.ui-icon');
	ui.click(function() {
		var b = $(this);
		var p = b.parentsUntil('.column').parent();
		var i = p.children('img[alt="icon"]:first').eq(0);

		var h = p.children('.ui-widget-header h5:first').eq(0);

		// Control functionality
		switch(b.attr('title').toLowerCase()) {
			case 'config':
				widgetConfig(b, p);
				break;

			case 'toggle':
				widgetToggle(b, p, i);
				break;

			case 'close':
				widgetClose(b, p);
				break;
		}
	});
}

Note, the three widget functions that will “config” (as in edit the content), “toggle” (collapse/expand) and “close” the widgets.

The toggle function is pretty simple. I wanted to minimize the .widget-content class (which we’ll add later), change the minus into a plus on the side of the widget after collapsing and turn the big image icon seen on most of those segments into a smaller one that fits on the header. The .ticon class is what we added to the head earlier.

// Toggle widget
function widgetToggle(b, p, i) {
	// Change the + into - and visa versa
	b.toggleClass('ui-icon-minus').toggleClass('ui-icon-plus');

	// Turn the big icon into a small one or visa versa
	if(i.hasClass('icon'))
		i.switchClass('icon', 'ticon', '300');
	else
		i.switchClass('ticon', 'icon', '300');

	// Show/Hide widget content
	p.children('.widget-content').eq(0).toggle(300);
};

Closing a widget is also pretty straightforward. We’ll be using the closing dialog we added earlier.

// Close widget with dialog
function widgetClose(w, p) {
	$("#dialog-confirm-close-widget").dialog({
		resizable: false,
		modal: true,
		buttons: {
			"Close widget": function() {
				p.toggle('slide', {}, 500, function() {
					p.remove();
				});
				$(this).dialog("close");
			},
			Cancel: function() {
				$(this).dialog("close");
			}
		}
	});
}

The last of the widgets is the config. This is the trickiest part, since we’ll need to create and destroy TinyMCE instances and get input from the dialog and push to the content.

// Modify widget
function widgetConfig(w, p) {

	// Input elements in the dialog
	var dt = $('#widget-title-text');
	var dc = $('#widget-content-text');
	var du = $('#widget-icon-text');

	// Widget elements to change
	var wt = p.children('h5:first').eq(0);
	var wc = p.children('.widget-content').eq(0);

	// If there is no icon on the widget, there's nothing to change
	var wi = p.children('img[alt="icon"]:first');
	if(wi.length > 0) {
		wi = p.children('img[alt="icon"]:first').eq(0);
			$('#icon-field').show();
	}
	else {
		$('#icon-field').hide();
	}

	$("#dialog-config-widget").dialog({
		resizable: false,
		modal: true,
		width: 500,
		open: function() {
			if(wi != null)
				du.val(wi.attr('src'));

			dt.val(wt.text());
			dc.val(wc.html());

			// Initialize TinyMCE
			initTiny(dc, tinySettings);
		},
		buttons: {
			"Save changes": function(e, ui) {

				// Some widgets don't have an icon
				if(wi.length > 0) {
					if(notEmpty(du.val()))
						wi.attr('src', du.val());
				}

				// Remove editor (also gets content from TinyMCE back to textarea)
				closeTiny(dc);

				// Update
				if(notEmpty(dc.val()))
					wc.html(dc.val());

				// Careful here, don't wanna lose the control icons
				if(notEmpty(dt.val())) {
					var ci = wt.children('span.ui-icon');
					wt.html(dt.val());

					// Reset controls
					wt.prepend(ci);
					setControls(ci);
				}

				$(this).dialog("close");
			},
			Cancel: function() {

				// Destroy TinyMCE
				closeTiny(dc);

				$(this).dialog("close");
			}
		}
	});
}

Basically, the above function grabs the text fields from the dialog and puts the existing content on page into them. The user makes some edits and clicks on “Save changes” which causes the script to take the changed content and apply it back to the page. In a real-world example, this content will actually be posted back to the server.

Note: Since the control icons are also located in the title, when we change the title text, we have to make sure that the control icons are spared from any edits.

The Initialization.

This basically takes every section with a “column” class that has a <h5> element inside into a sortable widget. It adds the control icons as <span> tags and wraps the content in a widget-content <div> (which we can minimize) and adds the ui-widget classes from jQuery UI to aid with sorting.

function Init() {

	// Initialize dialogs
	initDialogs();

	// Portlet and sort related CSS classes
	var sortClasses = "ui-widget ui-widget-content ui-helper-clearfix";

	// Set every column segment with h5 element as a sortable widget
	$('.column:has(h5)').each(function() {

		var s = $(this);
		var p = s.parentsUntil('section').parent();
		var h = s.children('h5:first').eq(0);

		if(!p.hasClass('ui-widget'))
			p.addClass(sortClasses);

		// Function icons
		h.addClass('ui-widget-header')
			.prepend('<span class="ui-icon ui-icon-gear" title="Config"></span>')
			.prepend('<span class="ui-icon ui-icon-minus" title="Toggle"></span>')
			.prepend('<span class="ui-icon ui-icon-close" title="Close"></span>');

		// Need this to drag not highlight
		h.disableSelection();

		// Interaction cues
		h.css('cursor', 'move');
		$('.ui-icon').css('cursor', 'pointer');

		// Wrap control stuff (like icons and headers) in a widget-header div
		// and the rest in a widget-content div
		s.children().not('img[alt="icon"], .ui-widget-header, .ui-icon')
			.wrapAll('<div class="widget-content" />');

		s.children().not('widget-content').wrapAll('<div class="widget-header" />');
	});

	// Group sortable widgets in each section to one sort-area
	$('section').has('.column').each(function() {
		$(this).children().not('header,hr')
			.wrapAll('<div class="sort-area" />');
	});

	// Trigger control initialization
	setControls();
}

And lastly, if we don’t have sorting ability, we can’t really call this a dashboard. Ironically the most defining characteristic in dashboards is the most simple to implement once all the wrapper divs are in place.

$(".sort-area").sortable({
	connectWith: ".sort-area",
	opacity: 0.6,
	helper: 'clone',
	dropOnEmpty: true
});

This post was prompted by yet another example of why I have a dim view of project managers.

Can you put together a dashboard for us?

OK, no problem.

We already have the backend completed and a template in place although it doesn’t have any widget markup. Except for the head tag, we need you to not touch the rest of it because we do have other stuff on the page. BTW… We need it in an hour.

@$#& me!

I wish the above was an exaggeration, but sadly, it happens more often than I care to tolerate.

The problem is easy enough to come by. Someone first builds a backend thinking they’ll have all sorts of widget like functionality in the future, but doesn’t develop the front end for any of it. Time goes by… The next developer comes along and decides that since the template is already in place, they’ll finagle the output to be more “widget-like” and create a separate handler to manage user changes. This also explains why so many “legacy” dashboards are notoriously slow.

So now they bring in someone else to drop in a dashboard where there previously existed no frontend for it… And they want it yesterday.

Oh, right New Year resolution…
Less grumpy. Less grumpy. Less grumpy.

Update

Changed the notEmpty(t) function line if(t != “”) into if($.trim(t) != “”).