In this tutorial, we build a Chrome extension to download gists from GitHub Gist and explore the different parts that make up a Chrome extension, effectively harnessing the power of jQuery and JavaScript to build an extension.
Chrome extensions are browser programs made to customize functionality and modify behavior in the Google Chrome browser. They are written in HTML, JavaScript and CSS. With Chrome extensions, you can do more than just customize web pages—you can also add custom behaviors and functionality to suit your needs by harnessing the power of JavaScript. In short, any JavaScript program that can be created in the browser can be written as a Chrome Extension.
GitHub Gist is a simple way to share code snippets and pastes with others. It is a platform where you can share single files, parts of files or full applications with other people. Gists are driven by git version control, so they also have complete revision histories.
In this tutorial, we will create a Chrome extension to download code snippets from GitHub gists.
To follow along, a basic understanding of JavaScript / ES6 and jQuery is required. Knowledge of HTML and CSS is recommended but not mandatory. You should also have at least Node version 6+ installed on your system. For a guide on how to install Node, check out the official docs.
First we need to create a project directory to work from. Create a folder called gistdownloader and open it in your favorite text editor. Create an index.html
file in the root directory and create a js directory with a main.js
file.
Let’s start by creating the manifest.json
file. The manifest file simply describes the extension’s content. Every Chrome extension is required to have a manifest``.json
file. In the root of your project, create a manifest.json
file and add the following code to it.
// manifest.json
{
"manifest_version": 2,
"name": "GitHub Gist downloader",
"description": "An extension used for downloading github gists",
"version": "1.0",
"browser_action": {
"default_icon": "icon.png"
},
"permissions": [
"activeTab",
"download"
],
"content_scripts": [
{
"matches": ["https://gist.github.com/*"],
"js": ["js/main.js"],
"run_at": "document_end"
}
]
}
Chrome manifests are JSON Objects with various properties. Some are required, while some are optional, and which to use depends on the function of your extension. Our manifest file expects to have a manifest_version
of 2.
Every Chrome extension needs an identifier. This is usually the extension’s icon. The browser_action
object tells the extension which icon to use. It also has other values that can be found here.
The permissions
property is an array of permissions our extension needs. Since we need access to the current tab and the ability to download when the gist is opened, we set the value to activeTab and download.
Finally, the content_scripts
array contains an object with three properties.
More info on this can be found here.
At this point, our folder structure should look like this:
gistdownloader/
js/
main.js
icon.png
index.html
manifest.json
The main.js
file will be the main entry file for the project and will update the relevant DOM elements in the html file when needed.
Copy the following code into the index.html
file:
<!-- ./index.html -->
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>Gist Downloader</title>
</head>
<body>
<div class="file">
<div class="file-header">
<div class="file-actions">
</div>
</div>
</div>
<script src="./js/main.js"></script>
</body>
</html>
In the index.html
file, we do three things:
main.js
file.We haven’t created our main.js
file yet, so let’s go ahead and do that. Copy the following code into the main.js
file:
// js/main.js
$(window).on('load', function() {
const fileActions = document.body.querySelectorAll(
'.file .file-header .file-actions '
);
$.each(fileActions, function(i, val) {
const containerEl = document.createElement('span');
$(containerEl).append(
"<button type='submit' onclick='prepareDownload()' class='customName btn btn-sm copy-pretty tooltipped tooltipped-n BtnGroup-item' aria-label='Download the file'>Download file</button>"
);
$(this).prepend(containerEl);
});
addEventListenerForRawLinks()
});
function download(filename, text) {
const element = document.createElement('a');
element.setAttribute(
'href',
'data:text/plain;charset=utf-8,' + encodeURIComponent(text)
);
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
// when button is clicked
function prepareDownload() {
const gistText = document.getElementsByClassName('highlight tab-size js-file-line-container')[0].innerHTML;
download("gist", gistText )
}
function addEventListenerForRawLinks() {
var links = document.getElementsByClassName("customName");
if (links !== null) {
links[0].addEventListener('click', prepareDownload);
}
}
Let’s go over the file. First we use the window.load
function to make sure the scripts start running when the page is fully loaded.
Next, we select all the elements matching the classes '.file .file-header .file-actions '
. That is where we intend to put our download button.
After we loop through all the matching div’s, we then create a span tag and append a button to it. Finally, we prepend the button to the outer div.
Notice the onclick=``"``prepareDownload``"
function inside the button, as that is the method that will trigger the download when clicked.
We finally end that function by adding an event listener to grab raw links from the page. (Raw links are the absolute links to the gist files.) We do this so as to comply with GitHub’s content script policy.
Outside that block, we define three functions:
download : The actual function for downloading the file. It accepts two parameters: a file name and the contents to download.
prepareDownload : This is the function that is called when the button is clicked. It grabs the text of the gists by getElementsByClassName
method. It then calls the download function and passes in the required parameters.
addEventListenerForRawLinks : This is the function called when the download button is clicked. Its purpose is to call the prepareDownload function.
And that’s all! We have our extension. However, one thing is left. We have been using jQuery without having it in our file, which will cause some errors if we try to load the file. So we need to include jQuery in our main.js
file.
To do that, first grab the jQuery code from this page and save it into a file called jquery-3.1.1.js
in our js directory.
Next we need to concatenate the two JavaScript files together and build our final project files. For that we will use gulp, a lightweight JavaScript task runner.
Under your project directory in your terminal type in the following:
npm install --save dev gulp gulp-concat
That command will create a package.json
file. The file should look like this:
// ./package.json
{
"name": "gistdownloader",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"gulp": "^4.0.0",
"gulp-concat": "^2.6.1"
}
}
Finally we need to create a gulpfile.js
to run the functions. Create a gulpfile.js
in the root of your project and add the following to it:
// ./gulpfile.js
var concat = require('gulp-concat');
var gulp = require('gulp');
gulp.task('addJs', function() {
return gulp.src(['./js/jquery-3.3.1.js', './js/main.js'])
.pipe(concat('main.js'))
.pipe(gulp.dest('./build/js'));
});
gulp.task('copyFiles', function () {
return gulp.src(['*.html', '*.png', 'manifest.json'])
.pipe(gulp.dest('./build/'));
});
In this file we create two tasks—one to add our Js files together (with the help of the gulp-concat plugin), and one to copy our files to a **build**
directory.
In your terminal under the root folder, run the following commands:
****
gulp addJs
gulp copyFiles
Once that is successful, you will see a build directory created with our files in it.
Now it’s time to test what we have done so far. Open your Chrome browser and go to Settings > Extensions. Next, turn on developer mode by toggling the button. You should now see a button on the left side that says load unpacked. Click it to upload your build folder.
After uploading the build folder, you should now see your extension loaded.
To test our extension, go to gists.github.com and select a gist. You should see a download button next to the raw button. Click it and it will download the gist!
Note: All the files for this extension can be found here.
In this tutorial, you have learned how to create Chrome extensions that can manipulate page data. There’s a whole lot you can do with Chrome extensions! Be sure to check out the official documentation. Happy coding.
Want to learn more about creating great web apps? It all starts out with Kendo UI, a complete UI component library that allows you to quickly build high-quality, responsive apps. It includes everything you need, from grids and charts to dropdowns and gauges.
Chris Nwamba is a Senior Developer Advocate at AWS focusing on AWS Amplify. He is also a teacher with years of experience building products and communities.