Click here to Skip to main content
Click here to Skip to main content

Responsive Web Design Using Twitter Bootstrap

, 13 Dec 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
A look at how to create a responsive web site using ASP MVC / Twitter Bootstrap / WebAPI and Knockout.js.

Table of Contents

You can download the demo code here: TwitterBootstrapDemo_ASPMVC4.zip.

Introduction

At work I do a mixture of ASP MVC / WCF and WPF. We have pretty much embraced MVxx frameworks for JavaScript such as Knockout.js and Angular.js, but we recently had to do some truly responsive design web work, which needed to target loads of different form factors. We have good software guys where I work, for sure, but none of us are exceptional with CSS, as we prefer to code I guess. So we set about hiring some very UI/Ux people. The first thing that the new UI/Ux guys did was introduce the use of Bootstrap which is an HTML framework made by some of the guys that work for Twitter.

For those that have not heard of a responsive framework, it is typically one for working with HTML, that has lots of helpers, CSS, JavaScript, and well tried and tested patterns for developing websites that behave in a well known manner on different form factors. These responsive frameworks typically have these sorts of things:

  • A responsive grid
  • Navigation menus
  • Pop-ups / Dialogs
  • Standard set of CSS controls / styles

They may have more or less, but these are the basic building blocks of these frameworks.

I had obviously heard of Bootstrap and other big responsive frameworks like Foundation.js. However the new guys went down the Bootstrap route. Now, not having played with Bootstrap so much, I decided to have a play around with it, and came up with some code I decided to share. The code in this article is not clever at all, but if you are new to Bootstrap (or even responsive HTML design) it may have some bits and pieces in it which you will find useful. For seasoned web developers that may have already used Bootstrap or another responsive framework, I don't know if you will get that much out of this article. 

Just before we get into the guts of the demo code, I just wanted to point out that the demo app I decided to do was something that I may end up using as a companion (linked from if you like) site to my blog. As such it contains a lot of information that is kind of only relevant to me, such as links to articles that I have written, etc.

Like I say, sorry about the shameless self promotion thing there, I just figured if I was going to write something I might as well make it something that I could actually make use of, so that is what I decided to do, I hope people are OK with that.

That said, I think the demo code still shows you some nice introductory material on using Bootstrap and also Knockout.js if you have not used them before.

Pre-Requisites

The demo app uses SQL Server, and expects you to change the connection string in the Web.Config to your own SQL Server connection instance.

  • SQL Server

The Demo App

As I have stated, the demo app is very focused around the needs I personally had, to make a kind of companion site to my blog. So what does that mean exactly? Well, for me, that meant that I wanted a small micro site that fulfilled the following requirements:

  • Site would make use of ASP MVC / Entity Framework / Web API / Bootstrap / Knockout.js
  • I wanted it to be a small site, nothing too fancy
  • I wanted to be able to add content to it pretty quickly
  • I wanted it show a list of categories, such as C# / WPF etc., which would each show some images of articles I have written in these areas
  • Each article image should show a tooltip
  • Each article image should be clickable to show a more detailed description about the article, and should provide a mechanism to view the full article
  • Wanted it to work across different form factors (desktop / mobile / tablet)

Loading

Here is what it looks like when it is first run, where the loading icon is shown while the database is created and seeded with data (that's an Entity Framework thing, more on this later).

Click the image for a larger view

After Data Is Loaded

Here is what it looks like when it has loaded everything:

Click the image for a larger view

And here is what it looks like when we have clicked on one of the article images:

Click the image for a larger view

Responsive Design

And this is what it would look like on a Smartphone or small browser window.

There are several things to note here, such as:

  1. The navigation bar (NavBar is Bootstrap lingo) has changed (more on this later)
  2. The images are no longer several per row, they are in a single column (more on this later)
  3. The content fits still just fine

And with an article image clicked, see how the popup dialog is still OK and doesn't overflow the screen bounds, and that the popup dialog content is also fitting nicely to its new size.

As I say, I am fully aware that this demo app is of a very personal nature (i.e., personal to me), but I still feel it is a useful vehicle to teach some of the workings of Knockout.js and Bootstrap even though it is obviously a demo that is very focused towards my own needs.

How Does It Work

The following sections will outline the inner workings of the demo app. 

The Database

One of the things I wanted to do was to use SQL Server and Entity Framework (Code First), where I wanted to seed the database with some initial seed data. This is what the specific DbContext for the demo application looks like:

public class DatabaseContext : DbContext
{
    public DatabaseContext() : base("DefaultConnection")
    {
        this.Configuration.LazyLoadingEnabled = true;
        this.Configuration.ProxyCreationEnabled = true;
    }

    protected override void OnModelCreating(DbModelBuilder mb)
    {
        // Code here
    }

    public DbSet<Article> Articles { get; set; }
    public DbSet<Category> Categories { get; set; }
}

It can be seen that it is pretty simple and just has two DbSets, one for categories and one for articles, where the Category and Article classes look like this:

Category

[Table("Categories")]
public class Category
{
    [Key]
    public int Id { get; set; }

    [Required(ErrorMessage = "SlideOrder is a required field.")]
    public int SlideOrder { get; set; }

    [Required(ErrorMessage = "Title is a required field.")]
    public string Title { get; set; }

    [Required(ErrorMessage = "Description is a required field.")]
    public string Description { get; set; }

    public virtual ICollection<Article> Articles { get; set; }
}

Article

[Table("Articles")]
public class Article
{
    [Key]
    public int Id { get; set; }

    [Required(ErrorMessage = "Title is a required field.")]
    public string Title { get; set; }

    [Required(ErrorMessage = "ShortDescription is a required field.")]
    public string ShortDescription { get; set; }

    [Required(ErrorMessage = "LongDescription is a required field.")]
    public string LongDescription { get; set; }

    [Required(ErrorMessage = "ImageUrl is a required field.")]
    public string ImageUrl { get; set; }

    [Required(ErrorMessage = "ArticleUrl is a required field.")]
    public string ArticleUrl { get; set; }

    [ForeignKey("Category")]
    public int CategoryId { get; set; }

    public virtual Category Category { get; set; }

}

The use of the DataAnnotation attributes here is a bit of an overkill for the demo application, since it never allows the entry of new Category / Article objects, but as this demo app was something for me, I thought I may expand upon it in the future to allow new Category / Article objects to be created by the user, and as such I thought the use of these attributes may prove useful further down the line.

Database Creation

For the seed data, I could obviously have run some SQL scripts, but this time I decided to use the built-in Entity Framework database initializer stuff, so I wrote this simple initializer that will drop and recreate the database from scratch each time, and will also seed the database using the seed data that was passed in via the constructor:

public class DatabaseInitializer : DropCreateDatabaseAlways<DatabaseContext> 
{
    private List<Category> categoriesSeedData;

    public DatabaseInitializer(List<Category> categoriesSeedData)
    {
        this.categoriesSeedData = categoriesSeedData;
    }

    protected override void Seed(DatabaseContext context)
    {
        foreach (var category in categoriesSeedData)
        {
            context.Categories.Add(category);
        }
        context.SaveChanges();
    }
}

And we need to make sure this database initializer is used, so to do that we need to set it up in global.asax.cs, which is done as follows:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        var categoriesSeedData = XmlParser.ObtainSeedData();

        Database.SetInitializer(new DatabaseInitializer(categoriesSeedData));

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

We will see how the seed data is generated next.

XML Seed Data

Now we know that we are expecting to seed the database with some initial data, where does this data come from?

Well, the answer to that lies in a small XML file which looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<seedData>
  <!-- CATEGORIES -->
  <categories>
    <category>
      <slideOrder>1</slideOrder>  
      <title>About</title>  
      <description>
        <![CDATA[Category text here..........]]>
      </description>
    </category>
    <category>
      <slideOrder>2</slideOrder>  
      <title>C#</title>  
      <description>
        <![CDATA[Category text here..........]]>
      </description>
    </category>
    ....
    ....
    ....
    ....
    ....
    ....
    ....
  </categories>

  <!-- C# ARTICLES -->
  <articles>
    <article>
      <title>Threading 1 of 5</title>
      <shortDescription>Beginners guide to threading in .NET (Part 1)</shortDescription>
      <longDescription><![CDATA[Introduction into threading in .NET]]>
      </longDescription>
      <imageUrl>Content/images/Thread1.png</imageUrl>
      <articleUrl>http://www.codeproject.com/Articles/.......</articleUrl>
    </article>
    ....
    ....
    ....
    ....
    ....
    ....
    ....
  </articles>
</seedData>

We then parse this seed data using this tiny little helper class, where the return value of the ObtainSeedData() method is used to initialize the database (see the global.asax.cs code we saw before):

public static class XmlParser
{
    public static List<Category> ObtainSeedData()
    {
        var appPath = HttpRuntime.AppDomainAppPath;
        var doc = XElement.Load(string.Format(@"{0}\App_Data\SeedData.xml",appPath));
        var serverUtility = HttpContext.Current.Server;

        var categories = doc.Descendants("category").Select(x =>
        {
            Category category = new Category
            {
                SlideOrder = int.Parse(x.Element("slideOrder").Value),
                Title = x.Element("title").Value.Trim(),
                Description = x.Element("description").Value.Trim()
            };


            var articles = x.Descendants("articles").Descendants("article").Select(y =>
                {
                    return new Article
                        {
                            Title = y.Element("title").Value.Trim(),
                            ShortDescription = y.Element("shortDescription").Value.Trim(),
                            LongDescription = y.Element("longDescription").Value.Trim(),
                            ArticleUrl = y.Element("articleUrl").Value,
                            ImageUrl = y.Element("imageUrl").Value,
                            Category = category
                        };
                });

            category.Articles = articles.ToList();
            return category;

        }).ToList();

        return categories;
    }
}

Web API

To expose the Category and Article objects, I chose to use the WebAPI. There were a couple of decisions that came into play here, namely:

  • Should I allow auto content negotiation
    • I chose not to, and forced the results to be JSON serialized as I knew I wanted to use the results with Knockout.js which would just work much better with JSON. And also, if I allowed XML, I had to worry about Lazy/Greedy loading.
  • Should I try and stick to pure REST verbs such as PUT / POST / GET etc.
    • I opted for creating a custom route for the articles, as I essentially wanted lazy loading of the articles collection, but only when I wanted them from JavaScript. So for that requirement, in my eyes it made sense to create a new custom route for the ArticleController.

CategoryController

Here is what the CategoryController looks like:

public class CategoryController : ApiController
{
    public HttpResponseMessage Get()
    {
        IEnumerable<Category> categories;
        using (var context = new DatabaseContext())
        {
            context.Categories.Include("Articles");
            categories = context.Categories.OrderBy(x => x.SlideOrder).ToList();
            if (categories.Any())
            {

                var slimCats = categories.Select(x => new
                    {
                        Id = x.Id,
                        Title = x.Title,
                        Description = x.Description,
                        ArticleIds = x.Articles.Select(y => y.Id).ToList()
                    }).ToList();
                    
                return Request.CreateResponse(HttpStatusCode.OK, slimCats,
                    Configuration.Formatters.JsonFormatter);
            }

            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
    }
}

ArticleController

Here is what the ArticleController looks like:

public class ArticleController : ApiController
{
    // GETALL api/article/GetAll/5
    public HttpResponseMessage GetAll(int categoryId)
    {
        IEnumerable<Article> articles;
        using (var context = new DatabaseContext())
        {
            articles = context.Articles.Where(x => x.CategoryId == categoryId).ToList();
            var slimArticles = articles.Select(x => new
                {
                    x.CategoryId,
                    x.Id,
                    x.Title, 
                    x.ImageUrl,
                    x.ShortDescription,
                    x.LongDescription,
                    x.ArticleUrl
                }).ToList();

            return Request.CreateResponse(HttpStatusCode.OK, slimArticles,
                    Configuration.Formatters.JsonFormatter);
        }
    }
}

WebAPI Routing

And here is the routing setup for both of these Web API controllers, where it can be seen that there is the standard route, and also a specific one that I created for the ArticleController:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
        name: "Articles",
        routeTemplate: "api/Article/GetAll/{categoryId}",
            defaults: new
            {
                controller = "Article",
                action = "GetAll",
                categoryId = 1
            }
        );

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

Knockout JavaScript ViewModels

As has previously been stated, I decided to use Knockout.js for the client side MVVM bindings. Before we get into the code for the ViewModel(s), I just want to go through what the ViewModel(s) should be trying to do. I decided to create the following Knockout.js ViewModel(s).

DemoMainPageViewModel

This view model is the one that is controlled by the Bootstrap NavBar and Bootstrap Carousel, which essentially means it has to load the Articles for the currently selected Bootstrap Carousel or clicked Bootstrap NavBar item. The idea being that a single Category is represented either as a Bootstrap NavBar menu item or a single Bootstrap Carousel slide. Each time of one of those two things is clicked, we fetch the Articles for the current Category.

//=================================================================
// SUMMARY 
//=================================================================
//
// A simple Knockout.js overall page viewmodel, which simply acts as
// a host for a number of CategoryViewModel items, which are displayed
// within an embedded Twitter Boostrap Carousel. The first slide
// will have its articles fetched on initial creation of the 
// DemoMainPageViewModel. Subsequent slides will have their articles
// loaded when the slide is requested. The loading of a slides articles
// will only happen once. Essentially once the articles for a given slide
// (category) are loaded the first time they are never asked to load again
var DemoMainPageViewModel = function () {

    this.Categories = ko.observableArray();
    this.Loading = ko.observable(true);
    this.Loaded = ko.observable(false);
    this.HasArticle = ko.observable(false);
    this.CurrentArticle = ko.observable();

    this.SetArticle = function (article) {
            
        this.HasArticle(true);
        this.CurrentArticle(article);

        $('#articleModal').modal({
            keyboard: true
        }).modal('show');

    };

    this.Initialise = function(jsonCats) {

        this.Loading(false);
        this.Loaded(true);
        for (var i = 0; i < jsonCats.length; i++) {
            this.Categories.push(new CategoryViewModel(this, jsonCats[i]));
        }
            
        // Load the articles for the 1st slide (remember KO observableArray, 
        // are functions, so we need to call a function)
        this.Categories()[0].loadArticles();
    };

    // When the Category slide becomes active (requested to be shown via Nav menu)
    // load the Article(s) for the Category slide
    this.mainActionClicked = function (data, event) {
        var sourceElement = event.srcElement;
        var slideNumber = $(sourceElement).data('maincarouselslide');
        $('#mainCarousel').carousel(slideNumber);
        this.Categories()[slideNumber].loadArticles();
    };
};

CategoryViewModel

The CategoryViewModel is a simple one that is capable of holding a number of Articles, and is also responsible for showing the data related to a clicked Article. The basic idea is that the Articles are lazy loaded the first time, and only the first time, were the call to fetch them will hit the WebAPI ArticleController. Where we make use of the lovely jQuery when/the syntax. I love that, coming from C# and Task Parallel Library and its use of continuations, it makes a lot of sense to me. Anyway, here is the CategoryViewModel code:

//=================================================================
// SUMMARY 
//=================================================================
//
// A simple Knockout.js Category viewmodel. Each Category is 1 slide
// of an embedded Twitter Boostrap Carousel. Each Category also lazy
// loads an ObservableArray of Article items, when the Category slide
// becomes active in the Twitter Boostrap Carousel. The lazy loading
// of the Articles is only done once per slide.
var CategoryViewModel = function (parent, jsonCat) {

    this.Id = jsonCat.Id;
    this.Parent = parent;
    this.LoadedArticles = ko.observable(false);
    this.Title = ko.observable(jsonCat.Title);
    this.Description = ko.observable(jsonCat.Description);
    this.Articles = ko.observableArray();
    var context = this;

    this.hasArticles = ko.computed(function() {
        return context.Articles().length > 0;
    }, this);

        
    // Shows a modal dialog of the clicked Article
    this.articleClicked = function (article) {
        context.Parent.SetArticle(article);
    };

    // Do the lazy load of the article. Basically only do it
    // when this fuction is called externally
    this.loadArticles = function () {

            
        if (this.LoadedArticles() == true) {
            return;
        }

        this.LoadedArticles(true);

        // Push stuff to articles
        $.when($.ajax('/api/article/GetAll/' + this.Id))
            .then(function (data, textStatus, jqXHR) {
                for (var i = 0; i < data.length; i++) {
                    context.Articles.push(data[i]);

                    $('#img' + data[i].Id).tooltip({
                        title: data[i].ShortDescription,
                        trigger: 'hover focus'
                    });
                }
        });
    };
};

Kicking It All Off

In order to kick off the DemoMainPageViewModel initialization (i.e., loading it with the Category data from CategoryController) we use the following code, which is a simple JavaScript self executing function and an initial jQuery AJAX call to fetch the Category data. This code essentially shows a busy indicator while the Category data is being fetched, and only when the Category data is available will the busy indicator be hidden and the main user interface shown.

(function ($) {
    
    $(document).ready(function () {
        createViewModel();
    });


    //=================================================================
    // SUMMARY 
    //=================================================================
    //
    // A simple Knockout.js overall page viewmodel, which simply acts as
    // a host for a number of CategoryViewModel items, which are displayed
    // within an embedded Twitter Boostrap Carousel. The first slide
    // will have its articles fetched on initial creation of the 
    // DemoMainPageViewModel. Subsequent slides will have their articles
    // loaded when the slide is requested. The loading of a slides articles
    // will only happen once. Essentially once the articles for a given slide
    // (category) are loaded the first time they are never asked to load again
    var DemoMainPageViewModel = function () {

        ....
        ....
        ....

    };


    //=================================================================
    // SUMMARY 
    //=================================================================
    //
    // Initialise Carousel, and listen to the carousel controls, that should
    // load the current category slides Article(s) when the slide becomes active.
    // The Article(s) are loaded using a REST based WebApi call
    function hookUpCarouselControls(demoVM) {

        $('#mainCarousel').carousel({
            interval: false,
            pause:true
        }).on('slid.bs.carousel', function (event) {

            var active = $(event.target)
                .find('.carousel-inner > .item.active');
            var from = active.index();
            demoVM.Categories()[from].loadArticles();
        });
    }



    //=================================================================
    // SUMMARY 
    //=================================================================
    //
    // Shows a busy indicator, and then does an initial AJAX call to get
    // the initial Categories using REST based WebApi call. When the initial
    // categories are fetched, a Twitter Boostrap Carousel is created, which
    // is done using a simple Knockout.js ViewModel
    function createViewModel() {     

        var demoVM = new DemoMainPageViewModel();
        ko.applyBindings(demoVM);

        $.when($.ajax("/api/category"))
            .then(function (data, textStatus, jqXHR) {
                demoVM.Initialise(data);
                hookUpCarouselControls(demoVM);
        });
    }
    
})(jQuery);

_Layout.cshtml

This is the main ASP MVC layout page that makes use of the DemoMainPageViewModel. Though, you will see some more usage of the DemoMainPageViewModel when we discuss the carousel below. It can be seen below that there is a Bootstrap NavBar, which makes use of some of the Knockout.js bindings within the DemoMainPageViewModel. We will be covering this in more detail in a while.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>


    <div id="wrap">


        <div class="container mainContainer">
            <div class="row">
                <div class="col-xs-8 col-md-8 col-xs-offset-2 col-md-offset-2">

                    <div class="navbar navbar-default navbar-inverse navbar-static-top" 
                            data-bind="visible: Loaded">
                        <div class="container">
                            <div class="navbar-header">
                                <button type="button" 
                                    class="navbar-toggle" 
                                    data-toggle="collapse" 
                                    data-target=".navbar-collapse">
                                    <span class="icon-bar"></span>
                                    <span class="icon-bar"></span>
                                    <span class="icon-bar"></span>
                                </button>
                                <a class="navbar-brand" href="#">MY PORTFOLIO</a>
                            </div>
                            <div class="navbar-collapse collapse">
                                <ul class="nav navbar-nav">
                                    <li class="active">
                                        <a data-bind="click: mainActionClicked" 
                                            href="#" 
                                            data-maincarouselslide="0">About</a>
                                    </li>
                                    <li>
                                        <a data-bind="click: mainActionClicked" 
                                           href="#" 
                                           data-maincarouselslide="1">C#</a>
                                    </li>
                                    <li><a data-bind="click: mainActionClicked" 
                                        href="#" data-maincarouselslide="2">Web</a></li>
                                    <li class="dropdown">
                                        <a href="#" 
                                           class="dropdown-toggle" 
                                            data-toggle="dropdown">XAML <b class="caret"></b></a>
                                        <ul class="dropdown-menu">
                                            <li><a data-bind="click: mainActionClicked" 
                                                href="#" data-maincarouselslide="3">WPF</a></li>
                                            <li><a data-bind="click: mainActionClicked" 
                                                href="#" data-maincarouselslide="4">Silverlight</a></li>
                                            <li><a data-bind="click: mainActionClicked" 
                                                href="#" data-maincarouselslide="5">WinRT</a></li>
                                        </ul>
                                    </li>
                                </ul>
                            </div>
                            <!--/.nav-collapse -->
                        </div>
                    </div>


                </div>
            </div>
        </div>
        <div id="seperator"></div>

        @RenderBody()


    </div>
    <div id="footer" data-bind="visible: Loaded">
        <div class="container">
            <div class="row">
                <div class="col-xs-8 col-md-8 col-xs-offset-2 col-md-offset-2">
                    <span>
                        <img src="~/Content/images/Info.png" 
                             width="30px" 
                            height="30px"></img><span>Sacha Barbers portfolio</span>
                    </span>
                </div>
            </div>
            <div class="row">
                <div class="col-xs-8 col-md-8 col-xs-offset-2 col-md-offset-2">
                    <span>
                        <img src="~/Content/images/Link.png" 
                            width="30px" height="30px">
                            <a class="footerAnchor" 
                               href="http://www.codeproject.com/script/Articles/MemberArticles.aspx?amid=569009" 
                                target="_blank">View my articles here</a>
                        </img>
                    </span>
                </div>
            </div>
        </div>
    </div>
    @Scripts.Render("~/bundles/Js")
    @RenderSection("scripts", required: false)
</body>
</html>

Boostrap Bits

As I stated at the start of this article, Bootstrap is one of a breed of emerging (emerged) HTML frameworks that provide a core set of CSS / JavaScript libraries and helpers that allow a developer to get up and running quite quickly. There are generally lots of different controls included in these libraries. I will certainly not be covering all of the functionality within Bootstrap, but we will visit a couple of the core items.

The first steps to starting out with Bootstrap is to grab the code. Then we need to do the following things:

Include the JavaScript/CSS Stuff

As I am working with ASP MVC, for me this literally meant including the correct files in a bundle like this:

public class BundleConfig
{
    // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/Js").Include(
                    "~/Scripts/jquery-{version}.js",
                    "~/Scripts/bootstrap.js",
                        "~/Scripts/knockout-2.1.0.js",
                        "~/Scripts/TwitterBoostrapDemo.js"));

        // Use the development version of Modernizr to develop with and learn from. Then, when you're
        // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
        bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                    "~/Scripts/modernizr-*"));


        bundles.Add(new StyleBundle("~/Content/css").Include(
            "~/Content/bootstrap-3.0.1/css/bootstrap.css",
                "~/Content/bootstrap-3.0.1/css/bootstrap-theme.css",
                "~/Content/site.css"));
    }
}

Navbar

Bootstrap comes with a very cool (at least in my opinion) feature (OK, it has a few but this one is my favorite). So what is it you ask?

Well conceptually, it's a menu, but what is cool about it is, it is completely responsive.

For example, here is what is looks like on a big screen:

And this is what we get when we might view it on a tablet / smart phone, see how it is all compact and in a single column:

And the code that makes this all happen could not be easier. Here it is:

<div class="navbar navbar-default navbar-inverse navbar-static-top" 
        data-bind="visible: Loaded">
    <div class="container">
        <div class="navbar-header">
            <button type="button" 
                class="navbar-toggle" 
                data-toggle="collapse" 
                data-target=".navbar-collapse">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">MY PORTFOLIO</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li class="active">
                    <a data-bind="click: mainActionClicked" 
                        href="#" 
                        data-maincarouselslide="0">About</a>
                </li>
                <li>
                    <a data-bind="click: mainActionClicked" 
                        href="#" 
                        data-maincarouselslide="1">C#</a>
                </li>
                <li><a data-bind="click: mainActionClicked" 
                    href="#" data-maincarouselslide="2">Web</a></li>
                <li class="dropdown">
                    <a href="#" 
                        class="dropdown-toggle" 
                        data-toggle="dropdown">XAML <b class="caret"></b></a>
                    <ul class="dropdown-menu">
                        <li><a data-bind="click: mainActionClicked" 
                            href="#" data-maincarouselslide="3">WPF</a></li>
                        <li><a data-bind="click: mainActionClicked" 
                            href="#" data-maincarouselslide="4">Silverlight</a></li>
                        <li><a data-bind="click: mainActionClicked" 
                            href="#" data-maincarouselslide="5">WinRT</a></li>
                    </ul>
                </li>
            </ul>
        </div>
        <!--/.nav-collapse -->
    </div>
</div>

It can be seen that it is simply a matter of using a couple of Bootstrap classes, such as:

  • navbar
  • navbar-default
  • navbar-inverse
  • navbar-static-top
  • navbar-header
  • navbar-brand
  • navbar-collapse
  • nav navbar-nav

Full details of which can be found at the Bootstrap site.

Responsive Layout AKA "The Grid"

The other killer feature of Bootstrap is its responsive layout feature, otherwise known as "The Grid". The grid is one clever piece of kit for sure.

The key to using "The Grid" correctly boils down to the usage of a couple of different container DIVs and a couple of different CSS classes.

I think the best way to learn about the grid, is for me to just include this little description from the Bootstrap site:

Bootstrap includes a responsive, mobile first fluid grid system that appropriately scales up to 12 columns as the device or viewport size increases. It includes predefined classes for easy layout options, as well as powerful mix-ins for generating more semantic layouts.

Introduction

  • Grid systems are used for creating page layouts through a series of rows and columns that house your content. Here's how the Bootstrap grid system works:
  • Rows must be placed within a .container for proper alignment and padding.
  • Use rows to create horizontal groups of columns.
  • Content should be placed within columns, and only columns may be immediate children of rows.
  • Predefined grid classes like .row and .col-xs-4 are available for quickly making grid layouts. LESS mixins can also be used for more semantic layouts.
  • Columns create gutters (gaps between column content) via padding. That padding is offset in rows for the first and last column via negative margin on .rows.
  • Grid columns are created by specifying the number of twelve available columns you wish to span. For example, three equal columns would use three .col-xs-4.

Grids and full-width layouts

Folks looking to create fully fluid layouts (meaning your site stretches the entire width of the viewport) must wrap their grid content in a containing element with padding: 0 15px; to offset the margin: 0 -15px; used on .rows.

Media Queries

We use the following media queries in our LESS files to create the key breakpoints in our grid system.

/* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */

/* Small devices (tablets, 768px and up) */
@media (min-width: @screen-sm-min) { ... }

/* Medium devices (desktops, 992px and up) */
@media (min-width: @screen-md-min) { ... }

/* Large devices (large desktops, 1200px and up) */
@media (min-width: @screen-lg-min) { ... }

We occasionally expand on these media queries to include a max-width to limit the CSS to a narrower set of devices.

@media (max-width: @screen-xs-max) { ... }
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { ... }
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) { ... }
@media (min-width: @screen-lg-min) { ... }

Grid Options

See how aspects of the Bootstrap grid system work across multiple devices with a handy table

CLICK THE IMAGE FOR A LARGER VIEW

So that is how to use the grid system in Bootstrap's own words. Let's see an example of working with the grid system. Here is a small excerpt of the the Carousel code that we will be looking at in just a minute:

<div class="container">
    <div class="row">
        <div class="col-xs-8 col-md-8 col-xs-offset-2 col-md-offset-2">
            <p class="lead" data-bind="text: Title" />
            <p data-bind="text: Description" />
        </div>

        <div data-bind="visible: hasArticles" >
            <div data-bind="foreach:  $data.Articles" 
			class="row col-xs-8 col-md-8 col-xs-offset-2 col-md-offset-2 articleRow">
                <div class="col-xs-12 col-md-3">
                    <img data-bind="attr: {src: ImageUrl, id: 'img' + Id}, 
				click: $parent.articleClicked" 
			class="img-responsive articleImage" 
			alt="Responsive image" />
                </div>
            </div>
        </div>
    </div>
</div>

In the example code above, what we can see is that we have a main Container, which in turn has two Rows.

Each Row has some content that will take up eight columns on "Extra Small" and "Medium" devices, and in each case will have a two column offset on the left and right sides. This layout does work very well (at least in my testing) across all the different form factors.

Carousel

At the heart of the demo app is the usage of a Bootstrap Carousel (see http://getbootstrap.com/javascript/#carousel), which is used to host a slide per Category obtained via the Web API CategoryController. This is then bound to the the Knockout.js DemoMainPageViewModel which hosts a number of Knockout.js CategoryViewModel(s). Each CategoryViewModel also hosts a number of Knockout.js ArticleViewModel(s).

The inner workings of the different viewmodels was covered earlier in this article, so I won't repeat that. Instead we will just look at the markup for the Bootstrap Carousel, where you should pay special attention to the Knockout.js bindings in there.

Essentially, it is pretty simple: one Carousel slide = one CategoryViewModel. One image within the slide = one ArticleViewModel. Obviously as the slide changes, so does the CategoryViewModel it represents and the CategoryViewModel Articles that belong to the newly shown CategoryViewModel.

Here is the HTML code and Knockout.js bindings for the Bootstrap Carousel:

<div id="mainCarousel" data-wrap="false" class="slide"  data-bind="visible: Loaded">
    <div class="carousel-inner" data-bind="foreach: Categories">
        <div class="item" data-bind="css: {'active': $index()==0}">
            <div class="container">
                <div class="row">
                    <div class="col-xs-8 col-md-8 col-xs-offset-2 col-md-offset-2">
                        <p class="lead" data-bind="text: Title" />
                        <p data-bind="text: Description" />
                    </div>

                    <div data-bind="visible: hasArticles" >
                        <div data-bind="foreach:  $data.Articles" 
				class="row col-xs-8 col-md-8 col-xs-offset-2 col-md-offset-2 articleRow">
                            <div class="col-xs-12 col-md-3">
                                <img data-bind="attr: {src: ImageUrl, id: 'img' + Id}, 
						click: $parent.articleClicked" 
					class="img-responsive articleImage" alt="Responsive image" />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <a id="leftCaroselControl" class="left carousel-control" href="#mainCarousel" 
		data-slide="prev"><span 
		class="glyphicon glyphicon-chevron-left"></span></a>
    <a id="rightCaroselControl" class="right carousel-control" href="#mainCarousel" 
		data-slide="next"><span 
		class="glyphicon glyphicon-chevron-right"></span></a>
</div>

Tooltips

Tooltips are very easy once you have included all the stuff you need to for Bootstrap, it is simply a case of some JavaScript like this:

$('#img' + data[i].Id).tooltip({
    title: data[i].ShortDescription,
    trigger: 'hover focus'
});

Which results in a nice shiny tooltip like this being shown on element hover:

Popup Dialogs

Bootstrap makes it very easy to show popup dialogs. Shown below is how it works when an Article is clicked (where the parent of the CategoryViewModel will be the singleton instance of the DemoMainPageViewModel):

var CategoryViewModel = function (parent, jsonCat) {

    // Shows a modal dialog of the clicked Article
    this.articleClicked = function (article) {
        context.Parent.SetArticle(article);
    };
}


var DemoMainPageViewModel = function () {

    this.SetArticle = function (article) {
            
        this.HasArticle(true);
        this.CurrentArticle(article);

        $('#articleModal').modal({
            keyboard: true
        }).modal('show');

    };
}

That's It

Anyway that is all I wanted to say this time. If you like the article, a vote comment is always welcome.

By the way, I am going to be taking a break from writing articles for a while, as I have decided to learn F#, as such I will not be writing many articles, you can however expect a few more blog posts about my journey into a new language, which I hope may prove useful for other folk that are new to F#, like me. These blog posts will be at a very basic level, as that is where I will be coming from with F#. Still you got to start somewhere, right?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralA 10 Pingroupjohnmcpherson1023-Jul-14 11:31 
GeneralRe: A 10 PinmvpSacha Barber23-Jul-14 23:14 
GeneralMy vote of 5 Pinmemberkentclark51916-Jul-14 8:27 
GeneralRe: My vote of 5 PinmvpSacha Barber16-Jul-14 21:04 
General非常感谢,这个正是我需要学习的信息 Pingroupdanmodanqing22-Jun-14 23:08 
QuestionMy Vote of 1...... + 4 = 5 Pinprofessionaldb7uk11-Feb-14 22:16 
AnswerRe: My Vote of 1...... + 4 = 5 PinmvpSacha Barber11-Feb-14 23:03 
GeneralMy vote of 5 PinmemberGerardo Vasquez Pineda31-Jan-14 3:01 
GeneralMy vote of 5 PinmemberMember 410546831-Jan-14 2:59 
GeneralRe: My vote of 5 PinmvpSacha Barber31-Jan-14 5:04 
GeneralAn F# Idea Pingroupcamassey27-Jan-14 6:02 
GeneralRe: An F# Idea PinmvpSacha Barber27-Jan-14 22:17 
GeneralMy vote of 5 Pinmembernospam196120-Jan-14 13:21 
GeneralRe: My vote of 5 PinmvpSacha Barber21-Jan-14 7:00 
GeneralMy vote of 1 PinmemberDaniel Fisher (lennybacon)20-Jan-14 6:31 
GeneralRe: My vote of 1 PinmvpSacha Barber20-Jan-14 7:16 
SuggestionAngular does everything that knockout does but better PinmemberDaniel Fisher (lennybacon)20-Jan-14 6:30 
GeneralRe: Angular does everything that knockout does but better [modified] PinmvpSacha Barber20-Jan-14 7:15 
GeneralMy vote of 5 PinprofessionalRenju Vinod15-Jan-14 23:58 
GeneralRe: My vote of 5 PinmvpSacha Barber16-Jan-14 0:21 
Question[My vote of 1] Late to the party... PinmemberMember 45654334-Jan-14 2:18 
AnswerRe: [My vote of 1] Late to the party... [modified] PinmvpSacha Barber5-Jan-14 4:46 
AnswerRe: [My vote of 1] Late to the party... PinprotectorPete O'Hanlon9-Jan-14 6:30 
QuestionWhere is the code sample PinmemberSakshi Smriti17-Dec-13 19:10 
AnswerRe: Where is the code sample PinmvpSacha Barber17-Dec-13 21:50 
GeneralRe: Where is the code sample PinmemberSakshi Smriti17-Dec-13 22:16 
GeneralRe: Where is the code sample PinmemberSakshi Smriti17-Dec-13 22:16 
QuestionExcelente articulo PinmemberTavoAlzate9-Dec-13 8:20 
AnswerRe: Excelente articulo PinmvpSacha Barber9-Dec-13 10:03 
QuestionAs always PinprofessionalBadassAlien8-Dec-13 11:15 
AnswerRe: As always PinmvpSacha Barber8-Dec-13 19:30 
GeneralMy vote of 5 PinmemberNandaKumer8-Dec-13 9:25 
GeneralRe: My vote of 5 PinmvpSacha Barber8-Dec-13 10:11 
QuestionNice once again PinmemberSteve Solomon5-Dec-13 23:27 
AnswerRe: Nice once again PinmvpSacha Barber6-Dec-13 0:16 
GeneralGreat Info Pinmemberdbw3dev4-Dec-13 12:56 
GeneralRe: Great Info PinmvpSacha Barber4-Dec-13 18:07 
GeneralVery nice! PinmemberRemi Lebrun3-Dec-13 4:14 
GeneralRe: Very nice! PinmvpSacha Barber3-Dec-13 4:18 
GeneralMy vote of 5 Pinmembermirfan003-Dec-13 2:57 
GeneralRe: My vote of 5 PinmvpSacha Barber3-Dec-13 4:08 
Thank you
Sacha Barber
  • Microsoft Visual C# MVP 2008-2012
  • Codeproject MVP 2008-2012
Open Source Projects
Cinch SL/WPF MVVM

Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralEr... Pinmembermark merrens2-Dec-13 13:24 
GeneralRe: Er... PinmvpSacha Barber2-Dec-13 19:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.141022.1 | Last Updated 13 Dec 2013
Article Copyright 2013 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid