Skip to contentSkip to author details

icons

A 2-post collection

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.