Semantic Bootstrap

Want a video version of this article?  Webucator has a collection of mobile web application videos, including a video inspired by this post.

I’ve been reading and thinking about the semantic web recently. This is mostly because of the mess of classes that Bootstrap brings to a typical application. I’m thinking there has to be another way. Let’s take the header of my application:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="~/jspm_packages/npm/font-awesome@4.3.0/css/font-awesome.min.css">
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Architects+Daughter">
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Sigmar+One">

<link rel="stylesheet" href="~/style/main.css">
<link rel="stylesheet" href="~/jspm_packages/github/twbs/bootstrap@3.3.4/css/bootstrap.min.css">

<!-- Required Polyfills -->
<script src="~/jspm_packages/npm/webcomponents.js@0.6.0/webcomponents-lite.min.js"></script>

<!-- Components used -->
@RenderSection("htmlelements", required: false)

<title>@ViewBag.Title</title>
</head>
<body>
<header class="navbar navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="@Url.Action("Index", "Home", new { area = "Main" })">Grumpy<br>Wizards</a>
</div>
<div id="navbar" class="collapse navbar-collapse navbar-right">
<ul class="nav navbar-nav">
@if (User.Identity.IsAuthenticated) {
<li><a href="@Url.Action("Index", "Profile", new { area = "Account" })"><i class="glyphicon glyphicon-user"></i> Administrator</a></li>
<li><a href="@Url.Action("Logout", "Login", new { area = "Account" })"><i class="glyphicon glyphicon-log-out"></i> Sign Out</a></li>
} else {
<li><a href="@Url.Action("Index", "RegisterAccount", new { area = "Account" })"><i class="glyphicon glyphicon-user"></i> Register</a></li>
<li><a href="@Url.Action("Index", "Login", new { area = "Account" })"><i class="glyphicon glyphicon-log-in"></i> Sign In</a></li>
}
</ul>
</div>
</div>
</header>

<div class="flex-container container-fluid">
@RenderBody()
</div>
</body>
</html>

It’s a mess of CSS classes. This is the layout file I want to get to:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="~/js/polyfills.js"></script>

<link rel="stylesheet" href="~/style/main.css">
<link rel="import" href="~/elements/common.html">

@RenderSection("webcomponents", required: false)
</head>
<body>
<header>
<s-logo>Grumpy Wizards</s-logo>
<s-navigation-menu authenticated="@User.Identity.IsAuthenticated">
<s-userprofile></s-userprofile>
<s-signout></s-signout>
<s-signin></s-signin>
</s-navigation-menu>
</header>
<section id="mainContent">
@RenderBody()
</section>

@if (IsSectionDefined("scripts")) {
<script src="~/jspm_packages/system.js"></script>
<script src="~/config.js"></script>
@RenderSection("scripts", required: false)
}
</body>
</html>

I could, of course, go all in on the web components and just do something like <s-header> but that is a little too compressed. This version hides the details of the implementation, allowing me to easily swap out what I need to, yet remains very readable so I know what is going on. Styling is done through CSS within the component and there is not one shred of bootstrap classes. When I am finished (probably in a few days), I’ll have HTML that I can read and understand and a set of reusable components.

To assist with this effort, I’ve created yet another project within blog-code. You can see where I am starting from by looking at tag testweb-1.

Todays exercise is to set up the header semantically. This means that it must be a fixed header with the content scrolling behind it. Of course, this means that I need a whole bunch of text so that it scrolls and I can see what happens when it does. To generate a whole bunch of text, I turned to my favorite lorem ipsum generator. Just fill in the form, hit submit, bring up the source and copy the text paragraphs into the Index.cshtml file. I chose 50 paragraphs, which should be plenty.

Semantic Bootstrap

There are two ways of using Bootstrap. The first is what I call “as-is”. You load it from a CDN or download it and serve the CSS files yourself. You add classes to your HTML that are built-in and standard. This is what I have been using so far in my projects.

There is another way to use Bootstrap – as a library. In this method, you download the LESS or SASS files to your development environment and then import the library as a reference. This is the mechanism I am going to use today.

Let’s start by looking at the page “pre-styling”:

blog-code-0503-1

In short – there is no styling here. This is just the HTML being rendered with the default stylesheet.

The first thing I need to do is add bootstrap-less to my list of devDependencies. The package is available on npm, so all I need to do is add it into my package.json file.

Now that I have the library in my build system, I need to add a LESS stylesheet – src/style/main.less – that imports the library. Let’s take a look at the header definition as well:

@import (reference) "../../node_modules/bootstrap-less/bootstrap/index.less";

header {
.navbar();
.navbar-fixed-top();
}

The Bootstrap Less library defines a mixin with the same name as each class. So, if you would normally add a class “btn-danger”, then add the mixin .btn-danger() to the button that you are styling. I can also augment the styles with my own. For example, I had a file with the site colors and font stacks in it. I can create a file called src/style/site.less with the following contents:

/*
Color Stack
*/

@color-1: #558C89;
@color-2: #74AFAD;
@color-3: #D9853B;
@color-4: #ECECEA;
@color-5: #444444;

/*
Font Stack
*/
@import url(http://fonts.googleapis.com/css?family=Sigmar+One|Architects+Daughter);
@logo-font: 'Sigmar One', serif;
@header-font: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;

/*
Specific Header settings
*/
@header-h: 65px;

I imported the fonts from Google Fonts. If you click on the link, you can see the @import directive to use – it’s a simple cut-and-paste after that. Now I can update my main.less file to include my site settings:

@import (reference) "../../node_modules/bootstrap-less/bootstrap/index.less";
@import (less) "site.less";

header {
.navbar();
.navbar-fixed-top();
height: @header-h;
background-color: @color-1;
}

section#mainContent {
.container-fluid();
margin-top: @header-h;
}

Once I have built the CSS from this, I can run the project and I see this:

blog-code-0503-2

The text scrolls behind my header as well. Obviously, I have a whole bunch of custom controls to make up now. Some, like <s-logo> are simple enough that I can just write them. After all, I’ve already created the code for a simple HTML replacement component (Part 1, Part 2).

One of the things I do want to do is create the appropriate flex box for the header. Flexbox allows me to generate a responsive design, particularly with the new web component for the navbar collapse that I have in mind. I started using flexbox but switched over to using the bootstrap semantics because of peculiarities with bootstrap itself. I can now do switch back to doing the flexbox method. This entails:

  1. Making the header display:flex with some settings
  2. Configuring the two containers inside with display:block and a flex parameter

You can read more about flexbox in the best guide I could find on the subject.

Once I implemented the flexbox, here is what my main.less file looked like:

@import (reference) "../../node_modules/bootstrap-less/bootstrap/index.less";
@import (less) "site.less";

header {
/* Bootstrap Requirements */
.navbar();
.navbar-fixed-top();
/* Flexbox Setup */
display: flex;
flex-flow: row nowrap;
align-items: stretch;
/* Additional styling */
height: @header-h;
background-color: @color-1;

&gt; s-logo, &gt; s-navigation-menu {
/* Flexbox setup */
display: block;
flex-grow: 1;
height: 100%;
/* Additional Styling */
border: 1px solid red;
}
}

section#mainContent {
/* Bootstrap Requirements */
.container-fluid();
/* Make sure the content flows behind the header */
margin-top: @header-h;
}

Note the use of > prior to the s-logo and s-navigation-menu elements. This means that it has to be a direct descendent in the HTML DOM. If I put the s-logo in something else and then inside header, this rule would not match. This is a great way of targeting the specific DOM elements you want.

Finally, you may be wondering how I generate the polyfills.js and common.html files that are imported at the top of the page. This is a task within the Gulpfile. Here is the simpler one that generates the polyfills.js file:

var paths = {
// Some other stuff
commonlibs: [
"wwwroot/jspm_packages/github/components/jquery@2.1.3/jquery.min.js",
"wwwroot/jspm_packages/github/twbs/bootstrap@3.3.4/bootstrap.min.js",
"wwwroot/jspm_packages/npm/webcomponents.js@0.6.0/webcomponents-lite.min.js"
],
// Some other stuff
}

// ... later on, in one of the tasks...
// Concatenate all the JS Library Polyfills we need
var polyfills = gulp.src(paths.commonlibs)
.pipe(concat("polyfills.js"))
.pipe(gulp.dest(paths.dest.js));&lt;/pre&gt;

The concat function is just the result of requiring gulp-concat, which you should add to your devDependencies in package.json. The common.html file generation is almost the same but has a twist:

var paths = {
// Some other stuff
commonhtml: [
"s-logo.html"
],
// Some other stuff
}

// ... later on, in one of the tasks ...
// Create the common.html vulcanized set of components
var commonfiles = paths.commonhtml.map(function (p) {
return path.join(paths.dest.elements, p);
});
// Add the Polymer library to the front of the common elements
commonfiles.unshift(path.join(paths.src.polymer, "dist/polymer.html"));
var v5 = gulp.src(commonfiles)
.pipe(concat("common.html"))
.pipe(gulp.dest(paths.dest.elements));

In this case, we compute the path of the commonhtml, getting a list of specific paths using the javascript Array.map method. The Array.map method takes an array and returns an array after calling a function on each element. I use this to take the list of components that I want to include in common.html and compute their location, using the post-vulcanized file paths.

To use the web components, I need to first include the polymer.html file. The Array.unshift method puts it’s argument(s) on the front of the array. In this case, I add on the location of the vulcanized polymer.html file.

I did need to do some re-organization of my Gulpfile.js. I can’t vulcanize the components until all the temporary files are created. This is done by the build:t_components task. I can’t build the common.html file until the vulcanized files are created.

That’s it for the basics. If you look at tag testweb-2, you will note several changes:

  1. Adjustments to the Gulpfile.js for building combined files
  2. Addition of the src/style/main.css and other less files
  3. Addition of the s-logo web component

I’ve done very few changes to the Layout.cshtml during this process. The only change I made was to put the title of the page in the <s-logo> content. Tomorrow I’m intending on doing a menu icon web component that can be wrapped inside a drop-down menu or on the navigation bar. This will set me up nicely to provide the navigation bar later on.