Dynamically adding icons to a Bootstrap nav menu in Ghost

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.

Dynamically adding icons to a Bootstrap nav menu in Ghost

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.