Build Your Own Chrome Extension: a Google Documents Word Count Tool, Part 3

This entry is part 3 of 3 in the series Build a Google Documents Word Count Tool

Build a Google Documents Word Count Tool

Hello, and welcome to the third and final instalment of building a full Google Chrome extension from scratch. To recap, in the first part of the series, we built a Chrome extension that roughly counted the words in a Google Document that’s currently in focus, and did so via jQuery. It added a status bar at the bottom of the screen, and echoed out the count there.

In part two, we upgraded the extension significantly — we added precision to the word counter, added a page counter alongside the word counter, updated the manifest to the newest version as required by Chrome and, most importantly, removed jQuery in favor of vanilla JavaScript, to profit in terms of performance and resource consumption.

In this part we’ll finalize our extension by adding an Options page which can be accessed via the Extensions tab and can be used to configure the abilities we want our extension to have.

Adding the Options Page

An extension’s option page is basically just an HTML document which (usually) contains some form elements. Now, we could manually build an options page and throw in some elements for good measure, but I’ve never been a fan of reinventing the wheel and there is already an amazing package that does just what we want — it offers a default settings page that looks and feels like Chrome’s native settings page. Please download Fancy Setting and unzip it into your extension’s folder, giving it the name “fancy-settings”.

In order to make our extension load an options page, we need to add the following anyhwere in our manifest file:


"options_page": "fancy-settings/source/index.html"

This tells the extension the options page is to be loaded from fancy-settings/source/index.html, which is where we unzipped the downloaded Fancy Settings code. While we’re editing the manifest.json file, let’s bump up the version to 0.3.

To see this options page in action (although it won’t be of any use just now), reload the extension (as per instructions in part one) and click “Options” next to it (it should be next to “Allow in incognito”). This should open a new tab containing some quite beautiful form elements for us to edit and tweak.

Editing the Options Page

We’ll add the following simple options: we’ll let the user choose one of three visual themes, we’ll let the user turn off the word and page counters, and we’ll let the user remove the GDWC link from the statusbar.

For the first one, we need a group of radio buttons with three values. For the second, we need a group of checkboxes. For the third, we need a solitary checkbox. These can all be built via fancy-settings, so we’ll use their syntax. To find out more options you can use with Fancy Settings, read their documentation page.

In order to add the elements we need, we must edit the manifest of the options page itself. Replace the content of fancy-settings/source/manifest.js with the following:


window.manifest = {
"name": "GDWC",
"icon": "../../icon48.png",
"settings": [
{
"tab": "Main settings",
"group": "Visual Settings",
"name": "themepicker",
"type": "radioGroup",
"label": "Choose theme (default is light):",
"options": [
["light", "Light"],
["dark", "Dark"],
["terminal", "Terminal"],
],
"default": "light"
},
{
"tab": "Main settings",
"group": "Counter settings",
"name": "pagecounter",
"type": "checkbox",
"label": "Enable Page Counter",
"default": true
},
{
"tab": "Main settings",
"group": "Counter settings",
"name": "wordcounter",
"type": "checkbox",
"label": "Enable Word Counter",
"default": true
},
{
"tab": "Main settings",
"group": "Other options",
"name": "gdwclink",
"type": "checkbox",
"label": "Show GDWC Sitepoint link?",
"default": true
}
]
};

It’s as simple as that. The fancy-settings options page will now automatically build all the required elements. Please reload the extension and click options to see it in action.

Reading Default and Saved Settings

Fancy Settings comes with store.js, which helps you read the settings of an extension. To do this, we need to make several changes. The first change is our manifest file. Change


"background" : { "scripts": ["background.js"] }

to


"background" : { "page": "background.html" }

Next, create a background.html page in the extension’s folder, and paste in this content:


<script type="text/javascript" src="fancy-settings/source/lib/store.js"></script>
<script type="text/javascript" src="background.js"></script>

What we did there was change our background page from JS to HTML so that we can also load HTML content and other scripts in the background. In this HTML page, we simply include all the JS scripts we want to load when the extension starts up (in our case, store.js from fancy-settings and our own background.js from before). Now that store.js is active and loaded, we can use it in the background page itself.

Open background.js and at the very top of the file, paste the following:


var settings = new Store("settings", {
"themepicker": 'light',
"pagecounter": true,
"wordcounter": true,
"gdwclink": true
});

This fetches the settings from the client computer’s localStorage, and if they’re not set yet, sets the defaults. We need to be able to access these settings via our content script main.js, though. Remember that call to the background script from part one where we did this:


chrome.extension.sendRequest({}, function(response) {});

only to display the extension icon? Let’s put that to some better use.

In background.js, change:


sendResponse({});

to


sendResponse(settings.toObject());

This will send the retrieved settings back to the main.js script. Open main.js now, and change:


chrome.extension.sendRequest({}, function(response) {});

to


var options;
chrome.extension.sendRequest({}, function(response) {options = response;});

This means that the sendRequest call not only shows the icon, but now also fetches the settings and populates the newly added options variable with the received response. We now have options available in our main.js script. However, if we try to execute our main script outside the sendRequest callback and still set options = response inside the callback, the outside code will execute slightly faster than the one in the callback, thus complaining the “options” variable is not yet set. The asynchronous nature of JavaScript is something to carefuly consider here, so in order to avoid these problems we move this existing code:


var readyStateCheckInterval = setInterval(function() {
if (document.readyState === "complete") {
countWords();
clearInterval(readyStateCheckInterval);
}
}, 10);

after


options = response;

but still inside the callback, so that the whole sendRequest callback looks like this:


var options;
chrome.extension.sendRequest({}, function(response) {
options = response;
var readyStateCheckInterval = setInterval(function() {
if (document.readyState === "complete") {
countWords();
clearInterval(readyStateCheckInterval);
}
}, 10);
});

This change makes sure our extension calls the background scripts and fetches the options before anything else, thus making sure the options become available for all later parts of the extension. In a positive turn of events, all this also handled the saving of our options automatically through the Store.js library.

Using the Settings to Manipulate the Status Bar

Now that our settings can be saved and loaded and we have all the necessary elements, let’s actually add some of that functionality. At the end of our sendRequest callback in main.js, add the following code:


var gdwclink = document.getElementById('gdwclink');
if (options.gdwclink) {
gdwclink.style.display = 'inline-block';
} else {
gdwclink.style.display = 'none';
}

This code grabs the GDWC SitePoint link, checks options for whether or not it should be visible, and acts accordingly. We don’t really have a gdwclink ID in the statusbar, so let’s wrap the link and the separator after it in a span with such an ID. Open statusbar.html and change this:


<div id='GDWC_statusBar'>
<span class='GDWC_statusBarCounter'><a href='http://www.sitepoint.com'>GDWC</a></span>
<span class='GDWC_statusBarSeparator'></span>
<span class='GDWC_statusBarCounter' id='GDWC_wordsTotal'>Warming up...</span>
</div>

to this:


<div id='GDWC_statusBar'>
<span id="gdwclink" style='inline-block'>
<span class='GDWC_statusBarCounter'><a href='http://www.sitepoint.com'>GDWC</a></span>
<span class='GDWC_statusBarSeparator'></span>
</span>
<span class='GDWC_statusBarCounter' id='GDWC_wordsTotal'>Warming up...</span>
</div>

This should do. Let’s do the counter options now. Replace the entire body of the countwords() function in main.js with the following:


var wordCount = 0;
var pageCount = 0;

var message = '';

if (options.pagecounter) {
var divs = document.getElementsByTagName('div'), i;
for (i in divs) {
if((" " + divs[i].className + " ").indexOf(" kix-page ") > -1) {
pageCount++;
}
}
message += pageCount + ' pages';
}

if (options.wordcounter) {
var spans = document.getElementsByTagName('span'), i;
for (i in spans) {
if((" " + spans[i].className + " ").indexOf(" kix-lineview-text-block ") > -1) {
var words = spans[i].innerText.replace(/W+/g, ' ').match(/S+/g);
wordCount += words && words.length || 0;
}
}
if (message != '') {message += ', ';}
message += wordCount + ' total words';
}

document.getElementById('GDWC_wordsTotal').innerText = message;

timeout = setTimeout('countWords()', 5000);

As you can see, we added some if checks before every counter, and we created a variable to store the message that is displayed in the status bar. The content of this message depends on the options we’ve chosen — that is, whether or not we wish to see page count, word count, none, or both.

Finally, let’s focus on the themes. We’ve chosen “light”, “dark” and “terminal” as our options. Let’s say that, in CSS form, they mean the following:


div#GDWC_statusBar.light {
background-color: #ebebeb;
border-top: 1px solid silver;
color: black;
}

div#GDWC_statusBar.dark {
background-color: darkgrey;
border-top: 1px solid #2d2d2d;
color: orange;
}

div#GDWC_statusBar.terminal {
background-color: #000;
border-top: 1px solid #2d2d2d;
color: green;
}

You can already see where we’re going with this — we’ll force the chosen option to become the class of our status bar. Replace the entire style block of statusbar.html with the following code:


<style type="text/css">
div#GDWC_statusBar.light {
background-color: #ebebeb;
border-top: 1px solid silver;
color: black;
}
div#GDWC_statusBar.light span.GDWC_statusBarCounter a {
color: black;
}

div#GDWC_statusBar.dark {
background-color: #333;
border-top: 1px solid #2d2d2d;
color: orange;
}
div#GDWC_statusBar.dark span.GDWC_statusBarCounter a {
color: orange;
}

div#GDWC_statusBar.terminal {
background-color: #000;
border-top: 1px solid #2d2d2d;
color: green;
}
div#GDWC_statusBar.terminal span.GDWC_statusBarCounter a {
color: green;
}

div#GDWC_statusBar {
width: 100%;
height: 18px;
position: fixed;
bottom: 0;
z-index: 25000;
text-align: right;
}
span.GDWC_statusBarCounter {
padding: 0 10px 0 10px;
line-height: 18px;
font-family: verdana;
font-size: 10pt;
}
span.GDWC_statusBarCounter a {
text-decoration: none;
}
span.GDWC_statusBarSeparator {
border-left: 1px solid silver;
border-right: 1px solid silver;
margin: 0 1px;
display: inline-block;
width: 1px;
height: 10px;
}
</style>

If you reload now, the status bar will be transparent and awkward-looking. We need to force the default theme on it first. At the end of the sendRequest callback, immediately after the gdwc show/hide logic, add this:


var statusbar = document.getElementById('GDWC_statusBar');
statusbar.className = options.themepicker;

That’s it. Now reload the extension, and the Google document you were testing it on. No change, right? Now go into the options of our extension and try selecting an option, then refresh the document you were testing it on to see the change go live. Play around and add some more options of your own if you wish.

Unfortunately, we need to reload the extension for the options we’ve chosen to take effect. While it’s not overly complicated to make the extension update in regards to new settings dynamically, it falls outside the scope of this article due to its length and and level. Regardless, allow me to point you in the right direction should you desire to do so yourself — all Fancy Settings elements can have event listeners, so in order to do this, you must listen for a change event on the elements we use and access all instances of the extension to edit the content of the status bar. Further information can be found at the Fancy Settings wiki page linked above, and in the Google Developers Guide.

Conclusion

In this final part of the series we’ve further upgraded our extension with an options page which resembles Chrome’s native settings pages, and we’ve added the ability to choose themes, deactivate certain features, and hide some elements on the status bar. I hope this introduction into option pages was enough to whet your appetite, and that it will encourage you to experiment on your own. The world of Chrome extensions is a big one and there’s a niche for high-quality, innovative extensions that enhance workflow. Who knows? You might even end up making a commercially viable one with some practice.

You can download the source code for this Chrome extension tutorial from github. Best of luck in your Chrome experiments!

Build a Google Documents Word Count Tool

<< Build Your Own Chrome Extension: a Google Documents Word Count Tool, Part 2

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.