bootstrap

A 8-post collection

Sidebar Mechanics of a Custom Bootstrap Site

Written by Michael Earls
 bootstrap  ghost  jquery  html

Ghost development makes it easy to define a custom sidebar by using partials. However, the partial runs in the context of the post.hbs or page.hbs files. This limits the functionality that you can provide.

The sidebar partial hierarchy

For my custom Bootstrap theme, I created two separate partial files for the sidebar; one for the author page, and one for the page, post, and tag templates. I did this so that I could display information about myself on those pages. This, of course, assumes that you are running a single author blog. It would be a bit more complicated for multiple authors (if you even wanted to do such a thing at all).

Here is the code for the post page page which shows the location of the sidebar as a bootstrap <div /> with the id of "sidebar-container" and a css class of col-md-4.

<div class="row">
    <div id="sidebar-container" class="col-md-4">
        {{> sidebar author}}
    </div> <!-- .col-md-4 -->
    <div class="col-md-8">
        <!-- content... -->
    </div> <!-- .col-md-8 -->
</div>

Notice that I sent in the author context so that the sidebar would have access to the author data.

Here is the author.hbs partial that is used in the above code:

<!-- sidebar -->
{{!include the various components}}
{{> "search-form"}}
<div id="sidebar-component-container"></div>
{{> "bio-panel"}}
{{> "sidebar-theme-picker"}}
<!-- /sidebar -->

This sidebar adds a sidebar component container (which I'll discuss below) as well as calls to bio-panel and sidebar-theme-picker partials.

Here's the bio-panel.hbs which takes advantage of the fact that we passed in the author context from the page:

<a name="author" class="sr-only"></a>
<div class="panel panel-primary">
    <div class="panel-heading">
        About {{name}}
    </div>
    <div class="panel-body">
        {{#if image}}
        <img class="img-thumbnail center-block" src="{{image}}" alt="{{name}}'s Picture" />
        {{/if}}
        <h1 class="author-title center-block">{{name}}</h1>
        {{#if bio}}
            <div class="bio-container">
                <h2 class="author-bio text-justify">{{bio}}</h2>
            </div>
        {{/if}}
        <div class="author-meta center-block sidebar-meta">
            {{#if location}}<p><i class="fa fa-map-marker"></i> <small>{{location}} <a href="https://www.google.com/maps/place/{{ encode location }}" aria-label="{{location}} map link" target="locationMap">(map)</a></small></p>{{/if}}
            {{#if website}}<p><i class="fa fa-link"></i> <small><a href="{{website}}">{{website}}</a></small></p>{{/if}}
        </div>
    </div>
</div>

The <a /> with the sr-only class is simply a bookmark for screen readers. It is not displayed to the user.

All of the data that is displayed is coming from the author context that we passed into the original partial.

This works well when we only need to use the information from a single context like page or post where we have author information, but what happens when we want to display the author information from a context that has multiple possible authors? In this case, we pass in the author context like so (this is from the tag.hbs page):

<div id="sidebar-container" class="col-md-4">
    {{#posts.[0].author}}
    {{> "sidebar"}}
    {{/posts.[0].author}}
</div> <!-- .col-md-4 -->

In this case, I am simply pulling the author context off of the first post. Obviously, if the author you want to display in the sidebar is not the author of the first post, you'll need to pick a different post.

Note: there are (as of this writing) experimental API calls you can make to retrieve some of this information, but it outputs some code in the html header that I am not comfortable with. I just get a weird feeling doing it that way.

Sleight-of-hand tricks for sidebar content out of context

If you are reading this post and the Bootstrap theme is enabled, you should see a panel with Social Media share buttons in it. As you can see from the code above, those links are not included in any of the sidebar code.

For the post social media links to work, I have included a hidden <div /> in the post.hbs template that contains another <div /> decorated with the css class of sidebar-component.

<div class="panel panel-primary share sidebar-component">
    <div class="panel-heading">
        <p class="panel-title">Share this post</p>
    </div> <!-- .panel-heading (share) -->
    <div class="panel-body">
        <a class="text-primary share-link" href="https://twitter.com/intent/tweet?text={{encode title}}&amp;url={{url absolute="true"}}"
           onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;"
           title="Share on Twitter" aria-label="Share on Twitter"><i class="fa fa-3x fa-twitter-square"></i><span class="sr-only">Twitter</span></a>
        <a class="text-primary share-link" href="https://www.facebook.com/sharer/sharer.php?u={{url absolute="true"}}"
           onclick="window.open(this.href, 'facebook-share','width=580,height=296');return false;"
           title="Share on Facebook" aria-label="Share on Facebook"><i class="fa fa-3x fa-facebook-square"></i><span class="sr-only">Facebook</span></a>
        <a class="text-primary share-link" href="https://plus.google.com/share?url={{url absolute="true"}}"
           onclick="window.open(this.href, 'google-plus-share', 'width=490,height=530');return false;"
           title="Share on Google+" aria-label="Share on Google+"><i class="fa fa-3x fa-google-plus-square"></i><span class="sr-only">Google+</span></a>
    </div> <!-- .panel-body (share) -->
</div> <!-- .panel (share) -->

Then, there is code in the page initialization that moves any of those divs to the <div /> in the sidebar with the id of sidebar-component-container.

Here is that code:

function moveSidebarItems() {
    // move items to the sidebar
    $('.sidebar-component').each(function () {
        $(this).detach().appendTo($('#sidebar-component-container'));
    });
}

$(moveSidebarItems);

This is pretty self-explanatory. It uses jQuery to move the intended sidebar content to the sidebar after the page has loaded. That means that the server has already processed it and used the context that the div is defined under. In simple terms, it means that you can hide a div in any part of a page or in any partial under any context and display data about that context, then simply make sure that the div has the class sidebar-component attached to it and it will get moved into place automatically. It should be noted that there is no defined order as that is out of the scope of this discussion. It could be done with custom html attributes (data-sidebar-order or similar attribute and ordered during the move at page ready).

Another approach to adding icons to dynamically generated links

Written by Michael Earls
 bootstrap  programming  icons  jquery  javascript

In my previous post, I outlined the code necessary to add icons to navigation links. I have since updated my implementation to use a function to add the link-to-icon mapping.

I have refactored my theme code to use the new approach.

The GitHub repository is ghost-cerkit-theme.

Instead of using JSON to define the link-to-icon mapping (as outlined in the previous post), we use an object on the window object:

window.linkIconMap = {};

Then, we define a function that we can use to add a new mapping:

window.addLinkIcon = function (target, icon, size) {  
    // check to see if we have maps defined already
    if (!window.linkIconMap.maps) {
        // if not, define it
        window.linkIconMap.maps = [];
    }

    // if we have a size passed in, use it, otherwise use the default icon size on our icon map. If that's missing, use nothing (Font Awesome default size)
    var iconSize = size ? size : 'defaultIconSize' in window.linkIconMap ? window.linkIconMap.defaultIconSize : '';
    window.linkIconMap.maps.push({ "target": target, "icon": icon, "size": iconSize });
};

First, we see if our maps array exists. If not, we create it. This prevents us from doing anything if the theme user never added any mappings.

After we check on the maps, we figure out what size to use. Basically, the same rules apply as before: if the map does not define a size, then we use the default size stored in defaultIconSize on our window.linkIconMap (but only if it's defined).

// sample definition for icon size
window.linkIconMap.defaultIconSize = 'fa-lg';

As a last resort, we fall back on an empty string, which has the effect of using the default size from the Font Awesome font.

In order for the theme user to create the map, they simply call the window.addLinkIcon() function:

// Navbar Icon Map  
window.addLinkIcon('nav-home', 'fa-home');  
window.addLinkIcon('nav-about', 'fa-user');  
window.addLinkIcon('nav-my-public-key', 'fa-key');  
window.addLinkIcon('nav-test', 'fa-cogs', 'fa-2x' /* optional */);

Note: you may notice that it's possible to bind an icon to any class on your page, not just navigation links. For example, I just set up my navbar expansion button (displayed when the screen is too narrow to show all the buttons) so that it does not have anything in it. I then add a call to window.addLinkIcon('navbar-toggle', 'fa-sitemap', 'fa-2x'); for it. This will then apply to all navbar toggles.

Here is where the toggle button is defined.

When the page loads, it calls the following code:

function bindLinkIcons() {  
    if (window.linkIconMap.maps) {
        var curIconMap;
        var curSize;

        for (var i = 0; i < window.linkIconMap.maps.length; i++) {
            // get a handle on the current icon map
            curIconMap = linkIconMap.maps[i];

            // set the icon on the navbar item
            createIcon(curIconMap.target, curIconMap.icon, curIconMap.size);
        }
    } else {
        console.warn('cerkit-bootstrap theme supports navbar link icons. Add the following to your footer in code injection: \<script\>window.addLinkIcon(/* target = */ "nav-home", /* icon = */ "fa-home", /* (optional) size = */ "fa-3x");\</script\>');
    }
}

$(bindLinkIcons);

We're only going to bind the icons if we have a map array to work with. If we do, then we simply loop through each entry and call the createIcon() function, passing in the relevant information.

Here is the createIcon() function definition:

function createIcon(target, icon, size) {  
    var iconElement = $(document.createElement('i')).attr('class', 'link-icon fa fa-fw ' + icon + ' ' + size).append('&nbsp;');
    var targetNavbarItem = $('.' + target);
    var targetItemFirstChild = $(targetNavbarItem).children()[0];

    // figure out if the nav item has any links in it. If so, use that as the icon parent.
    // Otherwise, use the navbarIconItem.
    var iconParentElement = targetItemFirstChild == null ? targetNavbarItem : targetItemFirstChild;

    // insert the icon element at the beginning of the parent
    $(iconParentElement).prepend(iconElement);
}

The first thing we do is create a new i tag to use as the icon container element. We then add the appropriate class attributes based on the icon and size values passed into the function. After the icon is defined, we append a non breaking space so that the icon isn't too close to the link. We then add the link-icon class to each of the icons that gets bound to a link. Then, in the site's css file, we simply add the following code:

.link-icon {  
    margin-right: 3px;
}

The next thing that happens is the code gets a handle on the target by selecting it based on the class name passed into the function (as the target argument).

Then, the first child element is selected (it is assumed that this element will either be a a tag or plain text. Whatever it is, we'll prepend our new i element representing our icon.

I believe that this approach is superior to the original approach as it is friendlier and more expressive to call a function to add a mapping rather than some arbitrary (and potentially confusing) JSON code.

I have created a Gist file that contains the core code to make this work.

Dynamically adding icons to a Bootstrap nav menu in Ghost

Written by Michael Earls
 blog  bootstrap  programming  icons  navbar

Update: I have written another post about an alternate approach to defining the link-to-icon mapping.

Note: I wrote this post to describe how I added icons to my Ghost navbar, but this process can work with any website that uses unique classes for links. Technically, it can bind an icon to any element decorated with a css class.

I am currently very pleased with how Bootstrap is working on my custom Ghost blog theme.

However, I wanted to use FontAwesome icons on my navbar. Since I couldn't control the CSS classes coming from dynamically generated content on my hosted server, I had to add some client-side script to add them after the page loads.

In my theme, each navbar link is tagged with a nav-{{slug}} class, so I have something to work with. Here is what my navbar looks like as presented to the visitor's browser:

<div class="collapse navbar-collapse" id="ghost-menu-navbar-collapse-1">  
    <ul class="nav navbar-nav">
        <li class="nav-home active" role="presentation"><a href="https://cerkit.com/" aria-label="Home">Home</a></li>
        <li class="nav-about" role="presentation"><a href="https://cerkit.com/author/michael/" aria-label="About">About</a></li>
        <li class="nav-my-public-key" role="presentation"><a href="https://cerkit.com/my-public-key/" aria-label="My Public Key">My Public Key</a></li>
    </ul>
    <ul class="nav navbar-nav navbar-right">
        <li class="nav-test navbar-text" role="presentation">No Link icon test</li>
        <li><a href="https://cerkit.com/rss/" role="presentation" id="subscribe-button"><i class="fa fa-fw fa-lg fa-rss"></i>&nbsp;&nbsp;Subscribe</a></li>
    </ul>
</div><!-- /.navbar-collapse -->

Link-to-Icon Mapping

Since each navigation link has its own tag, we can create a mapping to the desired icon. I am using the following four icons for my navbar:

Home - <i class="fa fa-fw fa-lg fa-home"></i>
User - <i class="fa fa-fw fa-lg fa-user"></i>
Key - <i class="fa fa-fw fa-lg fa-key"></i>
Cogs - <i class="fa fa-fw fa-lg fa-cogs"></i>

Now, we simply need to map them to the nav links. We do this by using a JSON-formatted Icon Map and jQuery code that runs on the page ready handler.

Here is what the link-to-icon map looks like for this site (at the time of writing):

var navbarIconMap = {  
    'defaultIconSize' : 'fa-lg', 
    'iconMaps' : [
        { 'target' : 'nav-home', 'icon' : 'fa-home' },
        { 'target' : 'nav-about', 'icon' : 'fa-user' },
        { 'target' : 'nav-my-public-key', 'icon' : 'fa-key' },
        { 'target' : 'nav-test', 'icon' : 'fa-cogs' }
    ]
};

Note: This object is defined in the Footer of the Code Injection settings screen on your Ghost blog. For non-Ghost sites, you will need to ensure that this variable is defined in a <script /> tag outside of the $(document).ready(); block.

Note: you may notice that it's possible to bind an icon to any class on your page, not just navigation links.

As you can see, the object has two properties:

  • defaultIconSize - The size of the icon to use when rendering (if this is an empty string, then the default FontAwesome font size will be used).
  • iconMaps an array of map objects containing the information required to map your nav links to icons.

iconMaps contains objects that have three properties:

  • target - the name of the css class of the nav element to bind the icon to (ex: nav-home)
  • icon - the FontAwesome name of the icon (Ex: fa-home)
  • size - (optional) - The size of the icon to be displayed (ex: fa-lg, fa-3x, etc.). If this value is not provided, it will use the value stored in defaultIconSize.

What happens after the page loads?

Once the page is ready, the following code will execute (within the $(document).ready(); handler):

if (navbarIconMap != null) {  
    for (var i = 0; i < navbarIconMap.iconMaps.length; i++) {
        curIconMap = navbarIconMap.iconMaps[i];
        // see if the current map provided a size. If not, use the default
        curSize = 'size' in curIconMap ? curIconMap.size : navbarIconMap.defaultIconSize;

        // set the icon on the navbar item
        console.log('curIconMap.target = ' + curIconMap.target);

        targetNavbarItem = $('.' + curIconMap.target);

        console.log('$(targetNavbarItem).html() = ' + $(targetNavbarItem).html());

        targetItemFirstChild = $(targetNavbarItem).children()[0];

        // figure out if the nav item has any links in it. If so, use that as the icon parent.
        // Otherwise, use the navbarIconItem.
        iconParentElement = targetItemFirstChild == null ? targetNavbarItem : targetItemFirstChild;

        console.log($(iconParentElement).html());

        // insert the icon element at the beginning of the parent
        var iconElement = createIcon(curIconMap.icon, curSize);
        $(iconParentElement).prepend(iconElement);
    }
}

First, we make sure that the navbarIconMap is not null. This is how we determine whether or not to add icons to the navbar.

Then, we loop through each of the maps in the iconMaps array in our navbarIconMap, setting a few variables in the process. In particular, we set the size of the icon based on the size property on the current iconMap. If that's not set, then we set it to the navbarIconMap.defaultIconSize value. If that is blank, then we use the default size as set by FontAwesome.

We then get a handle on the target navbar item by using its class name provided by the curIconMap.target property. Once we have it, we then look to see if there are any child elements. This is done so that we can put the icon within the <a> tag so it is part of the link. If there are no children, then the code will simply add the new icon element to the beginning of the element (this happens when there is just text in the navbar nav item).

The system creates the icon in the createIcon(icon, size) function:

function createIcon(icon, size) {  
    var iconElement = $(document.createElement('i')).attr('class', 'fa fa-fw ' + icon + ' ' + size).append('&nbsp;');
    console.log('iconElement class attribute value: "' + iconElement.attr('class') + '"');
    return iconElement;
}

This is mostly just inline jQuery that embeds a call to the document object to create an <i> tag that we can use to display our icon. It then adds the class attribute as defined by the function arguments. After that, it appends a space so the icon doesn't sit too close to the other elements/text in the nav element.

I sprinkled in a few console.log() calls so I could check my sanity along the way. I left them in this post for completeness.

Conclusion

The benefits of using Bootstrap on my blog are enormous. I have been able to rapidly extend the Ghost theme and quickly develop code to extend the functionality and appearance of my blog.

This dynamic navbar icon project is one of many projects I've worked on using Bootstrap and Ghost. I have also created a sidebar architecture that allows me to add items from various Ghost contexts to the sidebar after the page loads. I plan to write about that next. As of this writing, there is a "Share" panel that contains social media buttons that allow you to share each post. That panel was actually defined in the post.hbs file and then moved to the sidebar after the page loaded. It does not load for other pages. I will provide more details in the near future.

I hope this overview of my work has been beneficial to you. Please leave a comment if you have any questions or wish to contribute ideas on how this could be done more efficiently.

As always, you can contribute to the theme project or even fork it on GitHub.

Creating a Bootstrap Theme Selector for your Site

Written by Michael Earls
 blog  bootstrap  themes  development  bootswatch

I have recently converted my Ghost blog to use Bootstrap. As part of that, I decided to add a little toy to the sidebar that would allow users to change the Bootstrap theme that the site uses to display its content. All code for my custom theme can be found on GitHub in the ghost-cerkit-theme repository.

This post will outline the design and code for the theme selection "control".

Themes

I am using the Bootswatch themes for my website. This will give us quite a few decent looking themes to choose from. They're also free.

Source Files

For this control to work, we will need to add an id to the link tag that we use for our initial Bootstrap theme (bootstrap-theme):

<link id="bootstrap-theme" href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.6/superhero/bootstrap.min.css" rel="stylesheet"  crossorigin="anonymous" />

This will allow us to use this link element in jQuery to actually change the CSS source file location.

You will also need to add a link to the JavaScript code in your page. For my Ghost theme, I used the following code below the link to jQuery:

<script type="text/javascript" src="{{asset "js/bootstrap-theme-selector.js"}}"></script>

In order to display this and other controls on the side, I have added a sidebar partial to my Ghost theme.

<!-- sidebar -->  
{{!include the various components}}
{{> "search-form"}}
{{> "sidebar-bio"}}
{{> "sidebar-theme-picker"}}

Let's take a look at the code for the sidebar-theme-picker.hbs partial.

<div class="panel panel-primary">  
    <div class="panel-heading">
        Alternate Theme
    </div>
    <div class="panel-body">
        <form class="form-inline" id="theme-selection-form">
            <div class="form-group">
                <div class="input-group">
                    <select id="theme-selector" class="form-control">
                        <option value="cerulean">Cerulean</option>
                        <option value="cosmo">Cosmo</option>
                        <option value="cyborg">Cyborg</option>
                        <option value="darkly">Darkly</option>
                        <option value="flatly">Flatly</option>
                        <option value="journal">Journal</option>
                        <option value="lumen">Lumen</option>
                        <option value="paper">Paper</option>
                        <option value="readable">Readable</option>
                        <option value="sandstone">Sandstone</option>
                        <option value="simplex">Simplex</option>
                        <option value="slate">Slate</option>
                        <option value="spacelab">Spacelab</option>
                        <option value="superhero">Superhero</option>
                        <option value="united">United</option>
                        <option value="yeti">Yeti</option>
                    </select>
                </div> <!-- .input-group -->
            </div> <!-- .form-group -->
            <button class="btn btn-success form-control" onclick="clearTheme()">Reset</button>
        </form>
    </div>
</div>

This will give us the drop down control that we need to choose the theme.

The JavaScript code that wires this control up to the change event can be found in the bootstrap-theme-selector.js file.

In addition, all initialization of the variables for the theme selector can be found in the site-init.js file. Here is the required code for initialization of the theme selector:

var linkToBootstrapCDN = "//maxcdn.bootstrapcdn.com/bootswatch/3.3.6/";  
var themeStyleCss = "/bootstrap.min.css";  
var defaultTheme = 'superhero';  
var showThemeSelector = true;

Here is the code that runs when the document is ready:

$(document).ready(function() {

    var selectedTheme = $.cookie('user-theme');

    if(selectedTheme != null) {     
        changeTheme(selectedTheme);
    }
    else {
        setSelectedOption(defaultTheme);
    }

    $(document).on('change', '#theme-selector', function(e) {
        var selectedTheme = e.target.options[e.target.selectedIndex].value;
        setTheme(selectedTheme);
    });

    if(!showThemeSelector) {
        $('#theme-selector').css('display','none');
    }
});

We start by pulling a cookie out that stores the value of the user selected theme. If this value is not set, then we use the default theme as defined by our defaultTheme variable. Otherwise, we use the theme name as set in the cookie. We do this by calling the changeTheme() function, passing the name of the theme we wish to change to.

Here is the code for the changeTheme() function:

function changeTheme(selectedTheme) {  
    setSelectedOption(selectedTheme);

    var completeCssLink = linkToBootstrapCDN + selectedTheme + themeStyleCss;
    $('link#bootstrap-theme').attr('href', completeCssLink);
}

The first thing we do is call the setSelectedOption() function to loop through all of the options in the theme selection control and find the currently selected theme. When we find it, we set the selected attribute to 'true' (this has the same effect as adding an empty selected attribute). For options that aren't the currently selected theme, we simply remove the selected attribute altogether (it may or may not be on that option, but it doesn't hurt to attempt to remove a non-existent attribute).

After that, we build the entire link to the selected CSS file by combining the Bootstrap CDN address with the selected theme name and the filename to link to.

Then, we change the href attribute of the link element with the id of bootstrap-theme to point to the newly created CSS address. This is what triggers the browser to change the CSS theme that it uses to format the content.

Note: If you've made any CSS modifications that need to override the Bootstrap themes, you will need to also load those in the changeTheme() function after you have loaded the Bootstrap theme.

Here is the code for setSelectedOption():

function setSelectedOption(selectedTheme) {  
    $("#theme-selector option").each(function() {
        if ($(this).val() === selectedTheme) {
            $(this).attr('selected','true');
        }
        else {
            $(this).removeAttr('selected');
        }
    });
}

Selection control change event handler

In order to change the theme when the user selects a new theme from the drop-down list, we need to respond to the change event on the control. We wire up the change event by using the jQuery on() function like so (all of this happens during the $(document).ready() handler):

$(document).on('change', '#theme-selector', function(e) {  
    var selectedTheme = e.target.options[e.target.selectedIndex].value;
    setTheme(selectedTheme);
});

Note: #theme-selector is the id of the select control containing the list of themes.

After pulling the value of the theme out of the currently selected option, we call the setTheme() function, passing the name of the selected theme. Here is the code for setTheme():

function setTheme(theme) {  
    $.cookie('user-theme', theme, { expires: 170, path: '/' });
    changeTheme(theme);
}

As you can see, all this function does is set the user's cookie to hold the value of the selected theme and then call the changeTheme() function to trigger the browser to actually load the theme.

Resetting the theme

The sidebar form also contains a button that allows the user to reset the theme back to the default (as chosen by the blog theme author, or as defined in the header of the Settings->Code Injection section of the blog dashboard). The button itself is defined to call the clearTheme() function when clicked:

<button class="btn btn-success form-control" onclick="clearTheme()">Reset</button>

Here is the code for the clearTheme() function:

function clearTheme() {  
    $.removeCookie('user-theme', { path: '/' });
    changeTheme(defaultTheme);
}

All we do here is remove the cookie and change the theme to the default theme.

Hiding the control

The ghost-cerkit-theme allows you to hide this control by adding the following code to your header in Settings->Code Injection:

<script>  
    showThemeSelector = false;
</script>

Here is the code to check to see if the theme selector should display:

if(!showThemeSelector) {  
    $('#theme-selector').css('display','none');
}

This code exists in the $(document).ready() handler below everything else.

Summary

While not necessary, this control allows the user to change your blog's theme to something other than the theme you set during your design. This allows them to set it to a theme that is easier for them to read, or just change it to something different to keep from getting bored.

Integrating Bootstrap Pagination with Ghost

Written by Michael Earls
 blog  bootstrap  programming  ghost  development  casper  pagination

In my previous post, I discussed how to integrate Bootstrap into the default Ghost theme. This post will cover the way that I modified pagination to use Bootstrap's pagination control.

To start, you will need to create a new pagination.hbs file in the partials folder in the root of your theme.

Because of the way that Bootstrap pagination works, I had to implement pagination using client-side JavaScript. The implementation uses a combination of server-side handlebars markup and client-side script.

The variables that we'll use in the pagination are defined in the site-init.js file in /assets/js.

var prev;  
var pages;  
var page;  
var next;  
var pageUrl;  
var pageUrlPrev;  
var pageUrlNext;  
var numbersSurroundingEllipses = 3;  
var useSimplePagination = false;

Here is the pagination.hbs for the customized pagination that I am using on my site:

<script type="text/javascript">  
    // set the values that we'll use in the bootstrap-pagination.js file
    {{!if there is no value for the variable, display a 0}}
    prev = {{#if prev}}{{prev}}{{else}}0{{/if}};
    pages = {{#if pages}}{{pages}}{{else}}0{{/if}};
    page = {{#if page}}{{page}}{{else}}0{{/if}};
    next = {{#if next}}{{next}}{{else}}0{{/if}};
    pageUrl = '{{page_url}}';
    pageUrlPrev = '{{pageUrl prev}}';
    pageUrlNext = '{{pageUrl next}}';
    pageUrlFirst = '{{pageUrl 1}}';
    pageUrlLast = '{{pageUrl pages}}';
</script>
<nav>
  <ul class="pagination bootstrap-pagination">
  </ul>
</nav>

This particular bit of code is a little confusing at first glance because it combines client- and server-side script. Let's break it down.

Since this code will appear each time the pagination control is displayed, we don't want to define the variables here. That's why they were defined in the init script file. So, each time the code runs on the page, it will set the value of the variables needed to create the pagination control.

Notice the if statements. Since these values may be empty, we can't just use them "raw" to define the values of our JavaScript variables. This will lead to a JavaScript error because essentially, an empty value would render the following code:

prev = ;

This is obviously incorrect. The {{#if prev}}{{prev}}{{else}}0{{/if}}; code will check to see if we have the prev variable defined. If so, we'll simply print it out. If not, we'll provide the number 0. It then repeats this process for the remaining initialization variables.

After the initialization code, we just create a nav control with the ul defined. It is empty when it is defined. In order to fill it with page navigation controls, we will use jQuery code when the document is ready.

The main default.hbs file includes a link to the pagination script. Here is the code that runs when the page starts up:

$(document).ready(function() {  
    // see if there are any previous pages
    // if so, append them to the pagination ul
    if (prev > 0) {
        $('ul.bootstrap-pagination').append('<li class="prev"><a href="' + pageUrlFirst + '" title="Go to first page" aria-label="First"><span aria-hidden="true"><i class="fa fa-angle-double-left"></i></i></span></a></li>');
        $('ul.bootstrap-pagination').append('<li class="prev"><a href="' + pageUrlPrev + '" title="Go to previous page" aria-label="Previous"><span aria-hidden="true"><i class="fa fa-angle-left"></i></span></a></li>');
    }

    if (useSimplePagination) {
        doSimplePagination();
    }
    else {
        doComplexPagination();
    }

    // if we have pages after this one, display the 'next' buttons
    if (next > 0) {
        $('.bootstrap-pagination').append('<li class="nxt"><a href="' + pageUrlNext + '" title="Go to next page" aria-label="Next"><span aria-hidden="true"><i class="fa fa-angle-right"></i></span></a></li>');
        $('ul.bootstrap-pagination').append('<li class="nxt"><a href="' + pageUrlLast + '" title="Go to last page" aria-label="Last"><span aria-hidden="true"><i class="fa fa-angle-double-right"></i></i></span></a></li>');
    }
});

As you can see, this code will add the navigate to first and navigate to previous buttons if we are not on the first page. It does this by using jQuery selectors to find any ul's on the page that use the .bootstrap-pagination class and appends the li controls that have the proper links.

Then, it checks to see if it should perform simple or complex pagination. Simple pagination merely shows "Page X of Y". Complex pagination shows the page numbers with an ellipses in the middle.

Simple pagination is performed by the following function:

function doSimplePagination() {  
    $('.bootstrap-pagination').append('<li><a href="' + pageUrl + 'page/"' + page + '" aria-label="Page ' + page + ' of ' + pages + '">Page ' + page + ' of ' + pages + '</a></li>');
}

This shows the user what page they're on.

Complex pagination shows a set number of page numbers on either side of an ellipses. The number of page numbers to show is determined by the variable numbersSurroundingEllipses. This value is set in the init script to a default value of 3.

It is possible to override this number on the Settings->Code Injection screen on your site's dashboard. If the value is set to -1, then the pagination control will display all page numbers.

The code to determine which pages to display on the control is a bit complicated. In short, it will show the number of pages as defined by the numbersSurroundingEllipses variables and the page directly before and after the current page so that you can navigate between pages. When you get within a certain number of pages from the end (calculated based on the pages - (numbersSurroundingEllipses * 2) calculation), the ellipses is not shown and a link to the page in the "middle" is displayed.

Refer to the doComplexPagination() function in the bootstrap-pagination.js file.

The very last thing we do when creating the pagination control is set the current page as active:

$('li.page' + page).addClass('active');

This provides you with Bootstrap pagination on your Ghost blog.

Here is a screenshot of the pagination control:

Simple Pagination:
Simple Pagination

Complex Pagination:
Complex Pagination

Note: This implementation requires a link to FontAwesome fonts (used in the left and right buttons).

To make this work, make sure you remove (or alter) any CSS in your theme's screen.css file that might define properties on the nav element or the pagination class. I had a terrible time getting this working at first because the default theme heavily customizes these elements and has class names that clash with Bootstrap.

Integrating Bootstrap with the Casper Ghost Theme

Written by Michael Earls
 blog  bootstrap  css  programming  ghost  theme  casper

I have been working on my blog lately to customize the theme and provide some features that I wanted. I started with the default Ghost theme (Casper) and modified it to suit my needs.

I have shared my theme (I named it "Ghost Cerkit Theme") on GitHub in a new repository:

Ghost Cerkit Theme

I added a few features, like Google custom Search and Disqus comments. I also added a sidebar that shows Author information for the author of the first post on the site. This is for blogs that have a single author that want to show their information on all pages (except the Author page, where it is suppressed).

To use the theme, there are a few customization options that you'll have to change. The hardest one is probably the Google custom search. Instructions for how to create a custom search engine can be found on the Ghost website. In order for your custom search to work, you will need to put your search engine id in the hidden cx form field on the search form.

I had to make significant changes to the screen.css file in order for Bootstrap to work properly. There are a lot of settings in the default theme that interfere with Bootstrap. I believe the best way to see what changed would be to view the diffs for the css file (and some of the core .hbs templates).

In addition to the custom search form, I also added the ability for the user to change the current site theme. Since the custom theme utilizes Bootswatch themes, I was able to add a selection box containing all of the Bootswatch themes. When the user picks a new theme, the site changes to that theme and stores its name in a cookie on the user's browser. Whenever the user visits a page on the site in the future, the chosen theme is used instead of the default theme. There is also a reset button to return the site to the original theme as defined by the site owner. It is possible to hide the theme selector. Just add the following code to the header:

showThemeSelector = false;

I also replaced the Casper pagination control with a Bootstrap pagination control that shows more detail. However, in order for this to work, I had to utilize JavaScript. This might affect SEO a bit, but with the theme retaining the meta tags for navigation, I don't think there will be a major impact.

I will be writing up additional blog entries about how I implemented the Bootstrap pagination for Ghost as well as an entry about the theme selector.

The theme still needs some polish to remove all of the CSS that interferes with Bootstrap. I worked as hard as I could to remove any customizations that are specific to my personal site, but there are still some things that need to be done in order for it to work completely.

I retained the MIT license on the original Casper theme so that there are no constraints on usage. I thought about licensing it under GPL 2, but adding a more restrictive sublicense seemed inappropriate in this case. If you use the code and feel inclined to give me attribution, that would be great. Just link to my website (https://cerkit.com).

Merging two menus in a custom WordPress theme

Written by Michael Earls
 bootstrap  menu  theme programming  wordpress

Update: I have switched to using Ghost as my blogging engine, but this code still works on WordPress.

I have been working on a custom theme for my WordPress website. I had the need to combine the Primary menu with the Social menu to create a Bootstrap navbar (I am creating a Bootstrap theme for WordPress). Instead of creating two separate menus, I need to merge them because I don’t want two expand buttons and I’m not sure how that would work on a navbar.

I am using the default wp_nav_menu() (link) in my theme.

Here is the code (I can’t get my blog editor to format it correctly. You can see the properly formatted code here).

<nav id="top-navbar" class="navbar navbar-default navbar-fixed-top" role="navigation">  
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header"> 
    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="navbar-collapse-1">
        <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="/" rel="home"> My Website </a> 
</div> 
<div class="collapse navbar-collapse" id="navbar-collapse-1"> 
    <ul class="nav navbar-nav"> 
        <?php 
        /* Show the primary menu */ 
        wp_nav_menu( 
            array( 
                'menu' => 'primary', 
                'theme_location' => 'primary', 
                'container' => false, 
                'depth' => 2, 
                'items_wrap' => '%3$s', 
                'fallback_cb' => 'wp_bootstrap_navwalker::fallback', 
                'walker' => new wp_bootstrap_navwalker()
             ) 
        ); 
        /* Create a social menu that displays icons for social networks 
         * it is special because it needs to integrate with the existing links                
         */ 
        wp_nav_menu( 
            array( 
                'menu' => 'social', 
                'theme_location' => 'social', 
                'container' => false, 
                'depth' => 1, 
                'items_wrap' => '%3$s', 
                'fallback_cb' => 'wp_bootstrap_navwalker::fallback', 
                'walker' => new wp_bootstrap_navwalker()
            )
        ); 
        ?> 
        </ul> 
    </div>
</nav>

Notice that I had to manually add the code for the div that has collapse navbar-collapse and the <ul> that contains the menu items since we won’t be using the built-in container from the call to wp_nav_menu().

Here is the php specific menu code:

<?php  
/* Show the primary menu */ 
wp\_nav\_menu(  
    array( 
        'menu' => 'primary', 
        'theme_location' => 'primary', 
        'container' => false, 
        'depth' => 2, 
        'items_wrap' => '%3$s', 
        'fallback_cb' => 'wp_bootstrap_navwalker::fallback', 
        'walker' => new wp_bootstrap_navwalker()
    ) 
); 
/* Create a social menu that displays icons for social networks 
 * it is special because it needs to integrate with the existing links 
 */ 
wp\_nav\_menu(  
    array( 
        'menu' => 'social', 
        'theme_location' => 'social', 
        'container' => false, 
        'depth' => 1, 
        'items_wrap' => '%3$s', 
        'fallback_cb' => 'wp_bootstrap_navwalker::fallback', 
        'walker' => new wp_bootstrap_navwalker()
    ) 
); 
?>

Here are the main settings that give us what we want:

  • 'container' => false – prevents WordPress from wrapping your menu
  • 'items_wrap' => '%3$s' – simply returns the <li> element containing the link to your item.

Note: You can leave the ‘fallback_cb’ and ‘walker’ blank to use the default walker. However, I am using the custom wp_bootstrap_navwalker  from twittemHere is my fork that adds support for FontAwesome fonts.

Dynamic Bootstrap themes with ASP.NET using server-side bundling

Written by Michael Earls
 bootstrap  bundling  css  themes  VB.NET

I am setting up a system that needs to load the bootstrap theme that is being used from the web.config file (eventually, this might move to a database field for runtime changes).

Initially, I had my theme defined in the bundle.config file like so:

<?xml version="1.0" encoding="utf-8" ?> 
<bundles version="1.0"> 
    <styleBundle path="~/Content/css"> 
        <include path="~/Content/bootstrap.css" /> 
        <include path="~/Content/Site.css" /> 
        <include path="~/Content/bootstrap-theme.superhero.min.css" /> 
    </styleBundle>
</bundles>

This worked well for statically defined theme, but I need to make it dynamic. It turns out that this was quite easy. Simply remove the theme from bundle.config and add it to BundleConfig.vb (or BundleConfig.cs for C# projects).

Before doing that, let’s go ahead and add it to the web.config file:

<appSettings>  
    <add key="theme" value="bootstrap-theme.superhero.min.css"/>
</appSettings>

Then, we just add this one line to the bottom of the BundleConfig.vb file:

bundles.GetBundleFor("~/Content/css").Include("~/Content/" & ConfigurationManager.AppSettings("theme"))

It is important to notice that the virtual path for the bundle matches the same name as the path defined in your bundle.config file.

Now, whenever pages on your site are served, the theme css will be served along with the other bundled css. After testing, I’m pretty sure that the newly included css is last in line.

To change your theme, simply change it in web.config and reload. Or, replace the call to ConfigurationManager.AppSettings to a database call to retrieve the theme.

Also, make sure you’ve downloaded and saved the css theme to the Content folder before trying to access it.

You can get some nice free themes from Bootswatch.