Bootswatch Theme Preview Google Chrome Extension

Bootswatch offers many beautiful free themes for bootstrap. Recently I created a Google Chrome Extension that makes it easy to preview any existing bootstrap sites in one of these Bootswatch themes.

Install Extension From Chrome Web Store

Download Source From GitHub

How To Use

Install the extension from Chrome Web Store. Once the extension is installed, you can click on the icon icon to bring up the the following popup. From the popup, select the a theme and hit the Apply Theme button to apply theme on current page.

extension popup

How It Works

The extension has the following components:

  • A popup page and script for user interaction. It presents available themes and let user select desired theme.
  • A content script to apply a theme on current web page. This script is injected to the web page. It runs in a special context that has access to the page’s DOM. More on about content script later.
  • A background script for communication with the content script and organizing common functions and variables.

Content Script

Content script runs in a special environment of isolated world. It has access to the DOM of the page, but not any javascript functions or variables created by the page. It also does not have access to the functions and variables defined by the extensions’ pages. To communicate with the parent extension, we need to use message passing.

Message Passing

Since content scripts runs in isolated world and not part of the parent extension, it can only use message passing to communicate with the extension. For simple one time requests like the ones we have in this extension, we can use runtime.sendMessage or tabs.sendMessage to send messages, and runtime.onMessage to receive messages. For long-lived connections, chrome extension api offers other methods you can learn more about here

The Code

This is the extension’s manifest.json. Only the activeTab permission is specified as we will only inject content script to web page in the active tab.

{
  "manifest_version": 2,
  "name": "Bootswatch Theme Preview",
  "short_name": "BootswatchThemePreview",
  "description": "Preview a bootstrap site with a bootswatch theme.",
  "version": "1.0.1",
  "background": {
    "scripts": ["js/background.js"]
  },  
  "icons" : {
    "16" : "img/icon16.png",
    "48" : "img/icon48.png",
    "128": "img/icon128.png"
  },
  "browser_action": {
    "default_title": "Preview this page with a bootswatch theme.",
    "default_popup": "popup.html"
  },  
  "permissions" : [
    "activeTab"
  ]
}

This is the popup.js. When user clicks on the icon, the popup.html will be loaded and execute this script. This script serves two purposes. First, it creates the UI elements on the popup page. Second, it responds to the apply theme button’s click event and inject our content script to the current web page. When the button is clicked, it calls chrome.tabs.query to get the active tab. In the callback function (most of chrome extension api are asynchronous), it saves the selected theme to background script and calls chrome.tabs.executeScript to inject the content.js script to the web page.

document.addEventListener('DOMContentLoaded', function () {
    createBootswatchSelect();
    createBootswatchThumbnails();
    
    // When apply button is clicked
    document.getElementById("apply-button").addEventListener("click", function(){
        // Get the active tab
        chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
            // Save the selected them
            var bootswatchSelectList = document.getElementById("bootswatch-theme-select");
            var bootswatchThemeName = bootswatchSelectList.options[bootswatchSelectList.selectedIndex].value;
            chrome.extension.getBackgroundPage().saveUserPreference(bootswatchThemeName);
            
            // And inject content script to the active tab.
            chrome.tabs.executeScript({
                file: 'js/content.js'                
            });
        });        
    });
});

/**
 * Create the select list for bootstrap themes.
 */
function createBootswatchSelect() {
    var themes = chrome.extension.getBackgroundPage().bootswatchThemes.themes;
    var userThemeName = chrome.extension.getBackgroundPage().getUserBootswatchThemeName();
    var container = document.getElementById("bootswatch-theme-container");

    //Create and append select list
    var selectList = document.createElement("select");
    selectList.id = "bootswatch-theme-select";
    selectList.className = "form-control";
    container.appendChild(selectList);

    //Create and append the options
    for (var i = 0; i < themes.length; i++) {
        var theme = themes[i];
        var option = document.createElement("option");
        option.value = theme.name;
        option.text = theme.name + " | " + theme.description;   
        if (theme.name == userThemeName) {
            option.selected = true;
        }
        selectList.appendChild(option);        
    }
}


/**
 * Create the thumbnails
 */
function createBootswatchThumbnails() {
    var themes = chrome.extension.getBackgroundPage().bootswatchThemes.themes;
    var container = document.getElementById("bootswatch-thumbnail-container");
    
    //Create and append the thumbnail images
    for (var i = 0; i < themes.length; i++) {
        (function() {
            var theme, div, a, img;
            theme = themes[i];
            
            div = document.createElement("div");
            div.className = "col-xs-3";
            
            a = document.createElement("a");
            a.className="thumbnail";
            a.href = "#";    
            
            img = document.createElement("img");
            img.className = "img-responsive";
            img.src = "img/" + theme.name + ".png";
            
            container.appendChild(div);
            div.appendChild(a);
            a.appendChild(img);
            
            // change the select list selected item when a thumbnail is clicked on.
            a.addEventListener("click", function(){
                console.log(theme.name);
                document.querySelector('#bootswatch-theme-select [value="' + theme.name + '"]').selected = true;
            });             
        }());  
    }
}

This is the content.js. It uses chrome.runtime.sendMessage api to communicate with the background script. When it receives a reply in the callback function, it gets the selected bootswatch theme’s css cdn, and append a stylesheet element on the current web page.

/**
 * Send a message to background script to get the saved theme and apply it to current page.
 */
chrome.runtime.sendMessage({method: "getUserBootswatchTheme"}, function(response) {    
    var link = document.getElementById("injected-bootswatch-theme");
    if (link) {
        // We have already injected a bootswatch theme on this page, update the path instead of creating additional link
        link.href = response.theme.cssCdn;
    }
    else {
        var link = document.createElement( "link" );
        link.href = response.theme.cssCdn;
        link.type = "text/css";
        link.rel = "stylesheet";
        link.id = "injected-bootswatch -theme";
        document.body.appendChild(link);  
    }
});

This is the background script. It listens to messages from the content script and responds with the saved theme.

...
/**
 * Listen and respond to messages sent from content script
 */
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if (request.method == "getUserBootswatchTheme") {
    // respond with preferred theme.
    sendResponse({ theme: getUserBootswatchTheme() });
  }
});

/**
 * get user's preferred bootswatch theme.
 */
function getUserBootswatchTheme() {
  var themeName = getUserBootswatchThemeName();
  for (var i = 0; i < bootswatchThemes.themes.length; i++) {
      if (bootswatchThemes.themes[i].name == themeName) {
        return bootswatchThemes.themes[i];
      }
  }
  console.log("no user bootswatch theme is found");
}

/**
 * get the name of user's preferred bootswatch theme.
 */
function getUserBootswatchThemeName() {
  var name = localStorage["UserBootstrapThemeName"];
  if (name == undefined) {
    name = "Readable";
  }
  return name;  
}

/**
 * save user's preferred theme to local storage.
 */
function saveUserPreference(bootswatchThemeName) {
  localStorage["UserBootstrapThemeName"] = bootswatchThemeName;
}

Conclusion

That’s it. This is a relatively simple and yet useful extension that demonstrate how to use a content script to make changes to an existing page. Now you can preview your bootstrap sites in one of the bootswatch themes without making any changes. When you decide you like the theme, all you have to do is to download the css file from bootswatch.com and replace the one in your project to apply it permanently.

References

Chrome Message Passing
Chrome Content scripts

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s