Building a Chrome Extension: A Step-by-Step Guide with JavaScript

·

30 min read

Outline:

  1. Introduction

  2. Prerequisites

  3. Understanding the project

  4. Setting up the Project

    • 3.1 Step 1 - Let's Get the HTML Structure Sorted

    • 3.2 Step 2 - Let's Make It Look Good with Some CSS Magic

    • 3.3 Step 3 - Time to Add Some JavaScript Wizardry

    • 3.5 Step 3.1 - Displaying Leads in the DOM

    • 3.6 Step 3.2 -Saving and Retrieving Leads with Local Storage

    • 3.7 Step 3.3 - Deleting All Leads

    • 3.8 Step 3.4 - Deleting a Single Lead

    • 3.9 Step 3.5 - Capturing and Saving the Current Active Tab

    • 3.10 Step 3.6 - Filter and Find: Search Functionality

    • 3.11 (Optional) Creating a Favicon

    • 3.12 Step 3.7 - manifest.json Configuration

  5. Loading the Chrome Extension

  6. Testing & Using the Chrome Extension

  7. Conclusion: You're Now a Leads Capture App Maestro!

  8. Additional Resources

Introduction:

Welcome to this beginner-friendly tutorial on building your very own Leads Capture Chrome extension app! With this app, you'll be able to effortlessly save and track leads like a pro. Don't worry if you're new to JavaScript – I've got you covered. Let's dive in and go step by step, from setting up the HTML structure to adding some CSS flair, and finally, implementing JavaScript to make it all work seamlessly. Are you ready? Let's get started!

Prerequisites:

Before we dive into the project, there are a few prerequisites to ensure you have everything you need.

  • Basic Knowledge of HTML, CSS, and JavaScript: Familiarity with HTML, CSS, and JavaScript will be helpful in understanding and following along with the tutorial. However, if you don't have prior knowledge of these technologies, no worries! I'll be here to hold your hand through it all and guide you step by step.

  • Visual Studio Code (VSCode) Installed: We'll be using Visual Studio Code, a popular code editor, to write our code. If you haven't installed it yet, no worries! You can easily download and install it from the official website. Just search for "Visual Studio Code download" in your favorite search engine, find the official download page, and follow the instructions to install it on your computer.

  • Google Chrome Browser Installed: To develop and test Chrome extensions, you'll need the Google Chrome browser installed on your computer. If you haven't installed it yet, no problem! You can easily download and install it from the official Google Chrome website. Just search for "Google Chrome download," find the official download page, and follow the instructions to install it on your computer..

Once you have Visual Studio Code and Google Chrome installed, you're all set to start building your Chrome extension.

Understanding the Project:

What We're Building: Before we begin, let's take a quick look at what we'll be building. In this tutorial, we'll create a Chrome extension called "Leads Tracker" that allows you to save and track leads. The extension will provide a popup interface where you can enter leads, view saved leads, and delete unwanted leads. All the leads will be stored locally using the browser's local storage feature.

Here's what we're going to be building:

Now that we have a clear idea of what we're building, let's proceed with the step-by-step implementation.

Setting Up the Project

Before we start coding, let's set up our project structure and create the necessary files for our Chrome extension.

  1. Open Visual Studio Code: Launch Visual Studio Code on your computer. If you're using a Windows PC, you can find it in the Start menu or by searching for "Visual Studio Code" in the search bar. Mac users can locate it in the Applications folder or use Spotlight search.

  2. Create a New Folder: In Visual Studio Code, click on "File" in the top menu, then select "New Folder." Give your folder a name, such as "LeadsCaptureExtension," and choose a location to save it on your computer.

  3. Open the Folder: Once you've created the folder, select "File" again, then click on "Open Folder." Navigate to the location where you saved the folder in the previous step and select it. Visual Studio Code will now open the folder, and you'll see it listed in the Explorer sidebar.

Step 1: Let's Get the HTML Structure Sorted

  1. Create HTML File: Right-click on the folder in the Explorer sidebar and select "New File." Name the file "index.html" and press Enter. This file will serve as the main HTML file for our Chrome extension.

  2. Add HTML Boilerplate Code: Inside the index.html file, add the following HTML code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="style.css" />
    <link rel="icon" href="/favicon.png" type="image/x-icon">
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="index.js" defer></script>
    <title>Leads Capture Extension</title>
  </head>
  <body>
    <!-- Add your HTML code here -->
  </body>
</html>

Let's break down the code:

  • The <!DOCTYPE html> declaration specifies that this is an HTML5 document.

  • The <html> element is the root element of the HTML page.

  • The <head> element contains meta information about the document, such as character encoding and stylesheets.

  • The <meta> tags provide additional information about the document.

  • The <script> tag includes our JavaScript file (index.js) and uses the defer attribute to ensure that the script is executed after the HTML content is parsed.

  • The <title> element sets the title of the page, which will be displayed in the browser's title bar.

    NOTE: We will discuss the two link elements subsequently.

At this point, our project structure should look like this:

LeadsCaptureExtension/
  └── index.html

In the next step, we'll create the CSS file and add some styling to our extension.

Step 2 - Let's Make It Look Good with Some CSS Magic

Now that we have our HTML file set up, let's add some styling to our Chrome extension using CSS.

  1. Create CSS File: Right-click on the "LeadsCaptureExtension" folder in the Explorer sidebar and select "New File." Name the file "style.css" and press Enter. This file will contain our CSS styles.

  2. Add Stylesheet Link in the HTML File: To include the stylesheet (style.css) in our HTML file, we need to add a link to it inside the <head> element as seen above. Like this:

     <head>
       <link rel="stylesheet" href="style.css">
     </head>
    
  3. Add CSS Styling: Inside the style.css file, add the following CSS code:

body {
  box-sizing: border-box;
  margin: .6rem auto;
  font-family: Arial, Helvetica, sans-serif;
  max-width: 80%;
  min-width: 400px;
}

.input-area {
  display: flex;
  flex-direction: row;
  gap: .6rem;
}

input {
  outline: none;
  width: 92%;
  padding: .9rem .6rem;
  border: none;
  background-color: rgb(190, 143, 21);
  color: aliceblue;
}

#search-input {
  background-color: whitesmoke;
  color: rgb(134, 97, 6);
  border: 1px solid rgb(134, 97, 6);
}

button {
  background-color: rgb(134, 97, 6);
  text-transform: uppercase;
  color: aliceblue;
  text-align: center;
  border: none;
  margin-top: .3rem;
  padding: .9em 1.9rem;
  font-weight: bold;
  cursor: pointer;
}

button:hover {
  background-color: rgb(82, 60, 3);
}

#delete-btn {
  background-color: whitesmoke;
  color: rgb(134, 97, 6);
  border: 1px solid rgb(134, 97, 6);
}

#delete-btn:hover {
  background-color: rgb(134, 97, 6);
  color: whitesmoke;
}

.li-btn {
  padding: 0.2rem 0.4rem;
  font-size: 0.5rem;
  margin-left: 0.3rem;
  font-weight: bold;
}

ul {
  list-style: none;
  padding-left: 0;
  margin-top: 0.9rem;
}

li {
  display: block;
  padding: .2rem 0;
  margin-left: 0;
}

li a {
  color: rgb(190, 143, 21);
  text-decoration: none;
}

Let's go through the CSS code:

  • We target the body element to set some general styles, such as the box model, margin, padding, and font family.

  • The .input-area class styles the container for our input elements.

  • The input element styles the input fields with a specific width, padding, background color, and text color.

  • The #search-input styles the search input field with a different background color and border.

  • The button styles our buttons with a specific background color, text color, padding, and cursor style. The hover pseudo-class is used to define the button's appearance when hovered over.

  • The #delete-btn styles the delete button with a different background color and border.

  • The .li-btn class styles the buttons within each list item.

  • The ul styles the unordered list by removing the default list style and setting the left padding to 0.

  • The li styles each list item by removing the default margin.

  • The li a styles the anchor links within each list item with a specific text color and removes the default underline.

Now that we have added the CSS styles, our project structure should look like this:

LeadsCaptureExtension/
  ├── index.html
  └── style.css

In the next step, we'll add the JavaScript functionality to our Chrome extension.

Step 3 - Time to Add Some JavaScript Wizardry

In this step, we will add JavaScript code to handle the functionality of our Chrome extension. We'll be using JavaScript to interact with the HTML elements, handle button clicks, save and delete leads, and more.

  1. Create JavaScript File: Right-click on the "LeadsCaptureExtension" folder in the Explorer sidebar and select "New File." Name the file "index.js" and press Enter. This file will contain our JavaScript code.

  2. Add the index.js file to the head of your HTML file like this:

      <head>
         <script src="index.js" defer></script>
       </head>
    

    3.1 Displaying Leads in the DOM:

    Inside the index.js file, add the following JavaScript code:

// DOM Elements
const inputEl = document.getElementById("input-el");
const saveBtn = document.getElementById("save-btn");
const ulEl = document.getElementById("ul-el");

let myLeads = [];

// Render elements to the DOM
function render(leads) {
  let listItems = "";
  for (let i = 0; i < leads.length; i++) {
    listItems += `<li><a target="_blank" href="${leads[i]}">${leads[i]}</a><button class="li-btn">x</button></li>`;
  }
  ulEl.innerHTML = listItems;
}

// Add element to UI when "Save Input" button is clicked
saveBtn.addEventListener("click", () => {
  if (!inputEl.value || myLeads.includes(inputEl.value)) {
    return;
  }
  const newLead = inputEl.value;
  myLeads.push(newLead);
  inputEl.value = "";
  render(myLeads);
});

Let's go through the JavaScript code:

// DOM Elements
const inputEl = document.getElementById("input-el");
const saveBtn = document.getElementById("save-btn");
const ulEl = document.getElementById("ul-el");

let myLeads = [];

DOM Elements: The code uses getElementById() to select specific elements from the HTML document and assign them to variables.

    • inputEl represents the input element with the id "input-el".

      • saveBtn represents the save button element with the id "save-btn".

      • ulEl represents the unordered list element with the id "ul-el".

// Render elements to the DOM
function render(leads) {
  let listItems = "";
  for (let i = 0; i < leads.length; i++) {
    listItems += `<li><a target="_blank" href="${leads[i]}">${leads[i]}</a><button class="li-btn">x</button></li>`;
  }
  ulEl.innerHTML = listItems;
}
  1. Render elements to the DOM: The code defines a function named render that takes an array of leads as an argument. This function is responsible for generating the HTML structure for the leads and updating the UI.

    • Inside the render function, a for loop is used to iterate over each lead in the leads array.

    • For each lead, a list item is generated using template literals (``) to create an HTML structure.

    • The lead is added as the href attribute of an anchor (<a>) tag, creating a clickable link.

    • A button with a class of "li-btn" is also added for future functionality.

    • The generated list items are concatenated to the listItems string.

    • Finally, the listItems string is assigned to the innerHTML property of the ulEl element, replacing its existing content with the generated list items.

// Add element to UI when "Save Input" button is clicked
saveBtn.addEventListener("click", () => {
  if (!inputEl.value || myLeads.includes(inputEl.value)) {
    return;
  }
  const newLead = inputEl.value;
  myLeads.push(newLead);
  inputEl.value = "";
  render(myLeads);
});
  1. Add element to UI when "Save Input" button is clicked: The code adds an event listener to the saveBtn element that listens for a "click" event. When the button is clicked, the code inside the arrow function is executed.

    • The function first checks if the input field (inputEl) is empty or if the entered lead already exists in the myLeads array using the includes() method.

    • If either of the conditions is true, the function returns early and no further action is taken.

    • If the conditions are false, the value of the input field (inputEl.value) is stored in the newLead variable.

    • The newLead is then added to the myLeads array using the push() method.

    • The value of the input field is cleared by assigning an empty string to inputEl.value.

    • Finally, the render function is called with the myLeads array as an argument to update the UI and display the updated list of leads.

3.2 Saving and Retrieving Leads with Local Storage:

If you notice, anytime you reload your page your lead dissapears from the UI.
To ensure that our leads are saved and persist in the UI even after page reload, we need to store them in local storage. You can add the following code to achieve that:

// DOM Elements
const inputEl = document.getElementById("input-el");
const saveBtn = document.getElementById("save-btn");
const ulEl = document.getElementById("ul-el");

let myLeads = [];

// Display items from Local Storage
const leadsFromLocalStorage = JSON.parse(localStorage.getItem("myLeads"));

if (leadsFromLocalStorage) {
  myLeads = leadsFromLocalStorage;
  render(myLeads);
}

// Render elements to the DOM
function render(leads) {
  let listItems = "";
  for (let i = 0; i < leads.length; i++) {
    listItems += `<li><a target="_blank" href="${leads[i]}">${leads[i]}</a><button class="li-btn">x</button></li>`;
  }
  ulEl.innerHTML = listItems;
}

// Add element to UI when "Save Input" button is clicked
saveBtn.addEventListener("click", () => {
  if (!inputEl.value || myLeads.includes(inputEl.value)) {
    return;
  }
  const newLead = inputEl.value;
  myLeads.push(newLead);
  localStorage.setItem("myLeads", JSON.stringify(myLeads));
  inputEl.value = "";
  render(myLeads);
});

Let's walk through this code:

  1. const leadsFromLocalStorage = JSON.parse(localStorage.getItem("myLeads")): This line retrieves data from the local storage using the getItem() method of the localStorage object. It takes the key as a parameter, which in this case is "myLeads". The retrieved data is stored in the leadsFromLocalStorage variable.

  2. localStorage.getItem("myLeads"): This retrieves the value associated with the key "myLeads" from the local storage. Since data stored in local storage is in string format, the retrieved value needs to be parsed into its original JavaScript object form using JSON.parse().

Checking and rendering retrieved data:

3. if (leadsFromLocalStorage) {...}: This line checks if leadsFromLocalStorage is not null or undefined. It ensures that there is existing data stored in local storage under the "myLeads" key.

  1. myLeads = leadsFromLocalStorage;: This line assigns the retrieved leads from local storage to the myLeads array. This ensures that the array contains the saved leads when the page is loaded.

  2. render(myLeads);: This line calls the render function, passing the myLeads array as an argument. It triggers the display of the leads on the web page when the page is loaded and leads are retrieved from local storage.

By including these additional steps, the code first checks if there is existing data in local storage before retrieving it and then renders the retrieved data using the render function to display it on the web page.

3.3 Deleting All Leads Functionality:

To enable the deletion of all leads from the UI, we can implement a feature that clears the leads stored in both local storage and the myLeads array. This can be achieved by adding a "Delete All" button to the user interface and attaching a click event listener to it.

JavaScript code:

// DOM Elements
const inputEl = document.getElementById("input-el");
const saveBtn = document.getElementById("save-btn");
const ulEl = document.getElementById("ul-el");
const deleteAllBtn = document.getElementById("delete-btn");

let myLeads = [];

// Display items from Local Storage
const leadsFromLocalStorage = JSON.parse(localStorage.getItem("myLeads"));

if (leadsFromLocalStorage) {
  myLeads = leadsFromLocalStorage;
  render(myLeads);
}

// Render elements to the DOM
function render(leads) {
  let listItems = "";
  for (let i = 0; i < leads.length; i++) {
    listItems += `<li><a target="_blank" href="${leads[i]}">${leads[i]}</a><button class="li-btn">x</button></li>`;
  }
  ulEl.innerHTML = listItems;
}

// Add element to UI when "Save Input" button is clicked
saveBtn.addEventListener("click", () => {
  if (!inputEl.value || myLeads.includes(inputEl.value)) {
    return;
  }
  const newLead = inputEl.value;
  myLeads.push(newLead);

  localStorage.setItem("myLeads", JSON.stringify(myLeads));
  inputEl.value = "";
  render(myLeads);
});

// Delete all leads
deleteAllBtn.addEventListener("click", () => {
  const confirmation = confirm("Are you sure you want to delete all leads?");

  if (confirmation) {
    localStorage.clear();
    myLeads = [];
    render(myLeads);
  }
});

Let's walk through the code:

Selecting the Delete All button:

const deleteAllBtn = document.getElementById("delete-btn");

The above code selects the delete button element from the DOM using its id "delete-btn" and assigns it to the deleteAllBtn variable.

Adding a click event listener:

deleteAllBtn.addEventListener("click", () => {
  // Delete All functionality
});

The code above adds a click event listener to the delete button. When the button is clicked, the function inside the arrow function will be executed.

Confirmation prompt:

const confirmation = confirm("Are you sure you want to delete all leads?");

The confirmation prompt displays a dialog box with the specified message. The confirm() function returns true if the user clicks "OK" and false if the user clicks "Cancel". The user's response is stored in the confirmation variable.

Handling the confirmation response:

if (confirmation) {
  // Delete All functionality
}

The code above checks if the user confirmed the deletion by evaluating the value of the confirmation variable.

Clearing local storage:

localStorage.clear();

This line clears all the data stored in the local storage. It removes all the key-value pairs.

Clearing the myLeads array:

myLeads = [];

By assigning an empty array to the myLeads variable, all leads stored in the array are effectively cleared.

Rendering the updated leads:

render(myLeads);

This line calls the render function, passing the updated myLeads array as an argument. It updates the UI by displaying an empty list of leads after all the leads have been deleted.

3.4 Deleting a Single Lead:

In addition to deleting all leads, it is also helpful to provide the functionality to delete individual leads from the list. By implementing the "Delete Single" feature, you can easily remove specific leads from the UI and update the leads stored in local storage accordingly. The updated code should look like this :

// DOM Elements
const inputEl = document.getElementById("input-el");
const saveBtn = document.getElementById("save-btn");
const ulEl = document.getElementById("ul-el");
const deleteAllBtn = document.getElementById("delete-btn");
const liBtns = document.querySelectorAll(".li-btn");

let myLeads = [];

// Display items from Local Storage
const leadsFromLocalStorage = JSON.parse(localStorage.getItem("myLeads"));

if (leadsFromLocalStorage) {
  myLeads = leadsFromLocalStorage;
  render(myLeads);
}

// Render elements to the DOM
function render(leads) {
  let listItems = "";
  for (let i = 0; i < leads.length; i++) {
    listItems += `<li><a target="_blank" href="${leads[i]}">${leads[i]}</a><button class="li-btn">x</button></li>`;
  }
  ulEl.innerHTML = listItems;
}

// Add element to UI when "Save Input" button is clicked
saveBtn.addEventListener("click", () => {
  if (!inputEl.value || myLeads.includes(inputEl.value)) {
    return;
  }
  const newLead = inputEl.value;
  myLeads.push(newLead);

  localStorage.setItem("myLeads", JSON.stringify(myLeads));
  inputEl.value = "";
  render(myLeads);
});

// Delete all leads
deleteAllBtn.addEventListener("click", () => {
  const confirmation = confirm("Are you sure you want to delete all leads?");

  if (confirmation) {
    localStorage.clear();
    myLeads = [];
    render(myLeads);
  }
});

// Delete single list element
ulEl.addEventListener("click", (e) => {
  if (e.target.classList.contains("li-btn")) {
    const listItem = e.target.parentElement;
    const index = Array.from(listItem.parentNode.children).indexOf(listItem);

    myLeads.splice(index, 1);
    localStorage.setItem("myLeads", JSON.stringify(myLeads));
    render(myLeads);
  }
});

Let's understand what is happening with our updated code:

Using querySelectorAll:

const liBtns = document.querySelectorAll(".li-btn");

To enable the deletion of individual leads, the code selects all elements with the class name "li-btn" using the querySelectorAll() method. This method returns a NodeList containing all elements that match the specified selector, in this case, all buttons with the class "li-btn". The NodeList is stored in the liBtns variable.

Deleting a Single Lead:

ulEl.addEventListener("click", (e) => {
  if (e.target.classList.contains("li-btn")) {
    const listItem = e.target.parentElement;
    const index = Array.from(listItem.parentNode.children).indexOf(listItem);

    myLeads.splice(index, 1);
    localStorage.setItem("myLeads", JSON.stringify(myLeads));
    render(myLeads);
  }
});
  1. Event listener:

    • The code adds a click event listener to the ulEl element, which represents the unordered list that contains the leads.

    • When a click event occurs within the ulEl element, the provided callback function is executed.

  2. Checking if the clicked element is a delete button:

    • Within the callback function, the code checks if the clicked element has the class name "li-btn" by using the classList.contains() method.

    • If the clicked element is a delete button, the subsequent code block is executed.

  3. Determining the index of the lead to delete:

    • The code retrieves the parent element of the delete button using the parentElement property. In this case, the parent element is the <li> element containing the lead.

    • The code uses Array.from() to convert the collection of <li> elements (children of the parent <ul> element) into an array.

    • The indexOf() method is then used to determine the index of the <li> element within the array of siblings. This index represents the position of the lead in the myLeads array.

  4. Deleting the lead:

    • The code uses the splice() method to remove the lead from the myLeads array. It takes the determined index and removes one element at that position.

    • The updated myLeads array is then stored back into local storage using localStorage.setItem(), ensuring the changes are persisted.

    • Finally, the render() function is called to update the UI and display the remaining leads.

By implementing the code explained above, users will be able to delete individual leads from the list by clicking the corresponding delete buttons. The leads array in local storage is updated accordingly, and the UI reflects the changes by rendering the updated list of leads.

3.5 Capturing and Saving the Current Active Tab:

In order to capture the URL of the currently active tab and save it as a lead, we can implement a functionality that retrieves the active tab using the Chrome extension API. This section focuses on the code that handles capturing and saving the active tab when the "Save Tab" button is clicked. The updated JavaScript code will look like this :

// DOM Elements
const inputEl = document.getElementById("input-el");
const saveBtn = document.getElementById("save-btn");
const ulEl = document.getElementById("ul-el");
const deleteAllBtn = document.getElementById("delete-btn");
const liBtns = document.querySelectorAll(".li-btn");
const tabBtn = document.getElementById("tab-btn");

let myLeads = [];

// Display items from Local Storage
const leadsFromLocalStorage = JSON.parse(localStorage.getItem("myLeads"));

if (leadsFromLocalStorage) {
  myLeads = leadsFromLocalStorage;
  render(myLeads);
}

// Render elements to the DOM
function render(leads) {
  let listItems = "";
  for (let i = 0; i < leads.length; i++) {
    listItems += `<li><a target="_blank" href="${leads[i]}">${leads[i]}</a><button class="li-btn">x</button></li>`;
  }
  ulEl.innerHTML = listItems;
}

// Add element to UI when "Save Input" button is clicked
saveBtn.addEventListener("click", () => {
  if (!inputEl.value || myLeads.includes(inputEl.value)) {
    return;
  }
  const newLead = inputEl.value;
  myLeads.push(newLead);

  localStorage.setItem("myLeads", JSON.stringify(myLeads));
  inputEl.value = "";
  render(myLeads);
});

// Delete all leads
deleteAllBtn.addEventListener("click", () => {
  const confirmation = confirm("Are you sure you want to delete all leads?");

  if (confirmation) {
    localStorage.clear();
    myLeads = [];
    render(myLeads);
  }
});

// Delete single list element
ulEl.addEventListener("click", (e) => {
  if (e.target.classList.contains("li-btn")) {
    const listItem = e.target.parentElement;
    const index = Array.from(listItem.parentNode.children).indexOf(listItem);

    myLeads.splice(index, 1);
    localStorage.setItem("myLeads", JSON.stringify(myLeads));
    render(myLeads);
  }
});

// Save the current active tab
tabBtn.addEventListener("click", () => {
  chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    if (!tabs[0].url || myLeads.includes(tabs[0].url)) {
      return;
    }
    myLeads.push(tabs[0].url);
    localStorage.setItem("myLeads", JSON.stringify(myLeads));
    render(myLeads);
  });
});

Code Explanation:

// DOM Element
const tabBtn = document.getElementById("tab-btn");

tabBtn.addEventListener("click", () => {
  chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    if (!tabs[0].url || myLeads.includes(tabs[0].url)) {
      return;
    }
    myLeads.push(tabs[0].url);
    localStorage.setItem("myLeads", JSON.stringify(myLeads));
    render(myLeads);
  });
});

This code adds a click event listener to the "Save Tab" button (tabBtn). When the button is clicked, the function inside the arrow function is executed.

  1. The document.getElementById("tab-btn") selects the "Save Tab" button element from the DOM and assigns it to the tabBtn variable.

  2. Inside the event listener, we utilize the chrome.tabs.query() method provided by the Chrome extension API. This method is used to query information about tabs. We pass an object { active: true, currentWindow: true } as the parameter to specify that we want to query the currently active tab in the current window.

  3. The chrome.tabs.query() method retrieves an array of tabs that match the query parameters. In this case, since we are querying for the active tab, we receive an array of tabs. We access the first tab in the array using tabs[0].

  4. Inside the callback function of chrome.tabs.query(), we check if tabs[0].url exists and is not already included in the myLeads array. If either condition is true, we return and do not proceed further.

  5. If the tab's URL is valid and not already present in myLeads, we push it to the myLeads array.

  6. Next, we save the updated myLeads array to the local storage using localStorage.setItem() and convert the array to a JSON string using JSON.stringify().

  7. Finally, we call the render() function to update the UI with the updated list of leads.

This functionality leverages the Chrome extension API's chrome.tabs.query() method to capture and save the URL of the currently active tab when the "Save Tab" button is clicked. By accessing tabs[0].url, we retrieve the URL of the active tab and add it to the myLeads array for further processing.

Note: The "Save Tab" function requires the Chrome extension environment to work properly. It utilizes Chrome APIs and specific queries that are only available within the Chrome extension context. The manifest.json file plays a crucial role in configuring the extension's behavior and we will be working on that soon.

3.6 Filter and Find: Search Functionality:

Implementing a search filter allows users to easily search for specific leads by entering a search term in the search input field. The leads will then be filtered in real-time to display only the ones that match the search term. This section focuses on the code responsible for handling the search filter functionality.

With this functionality added, your index.js file should look like this now:

// DOM Elements
const inputEl = document.getElementById("input-el");
const saveBtn = document.getElementById("save-btn");
const ulEl = document.getElementById("ul-el");
const deleteAllBtn = document.getElementById("delete-btn");
const liBtns = document.querySelectorAll(".li-btn");
const tabBtn = document.getElementById("tab-btn");
const searchInput = document.getElementById("search-input");

let myLeads = [];
let filteredLeads = [];

// Display items from Local Storage
const leadsFromLocalStorage = JSON.parse(localStorage.getItem("myLeads"));

if (leadsFromLocalStorage) {
  myLeads = leadsFromLocalStorage;
  render(myLeads);
}

// Render elements to the DOM
function render(leads) {
  let listItems = "";
  for (let i = 0; i < leads.length; i++) {
    listItems += `<li><a target="_blank" href="${leads[i]}">${leads[i]}</a><button class="li-btn">x</button></li>`;
  }
  ulEl.innerHTML = listItems;
}

// Add element to UI when "Save Input" button is clicked
saveBtn.addEventListener("click", () => {
  if (!inputEl.value || myLeads.includes(inputEl.value)) {
    return;
  }
  const newLead = inputEl.value;
  myLeads.push(newLead);

  localStorage.setItem("myLeads", JSON.stringify(myLeads));
  inputEl.value = "";
  render(myLeads);
});

// Delete all leads
deleteAllBtn.addEventListener("click", () => {
  const confirmation = confirm("Are you sure you want to delete all leads?");

  if (confirmation) {
    localStorage.clear();
    myLeads = [];
    render(myLeads);
  }
});

// Delete single list element
ulEl.addEventListener("click", (e) => {
  if (e.target.classList.contains("li-btn")) {
    const listItem = e.target.parentElement;
    const index = Array.from(listItem.parentNode.children).indexOf(listItem);

    myLeads.splice(index, 1);
    localStorage.setItem("myLeads", JSON.stringify(myLeads));
    render(myLeads);
  }
});

// Save the current active tab
tabBtn.addEventListener("click", () => {
  chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    if (!tabs[0].url || myLeads.includes(tabs[0].url)) {
      return;
    }
    myLeads.push(tabs[0].url);
    localStorage.setItem("myLeads", JSON.stringify(myLeads));
    render(myLeads);
  });
});

// Search filter
searchInput.addEventListener("input", () => {
  const searchTerm = searchInput.value.toLowerCase();
  filteredLeads = myLeads.filter((lead) =>
    lead.toLowerCase().includes(searchTerm)
  );
  render(filteredLeads);
});

Let's walk through it:

DOM Elements:

const searchInput = document.getElementById("search-input");
  • const searchInput = document.getElementById("search-input");: This line selects the search input element from the DOM by using its id "search-input" and assigns it to the searchInput variable. It allows us to access the user's input for the search term.
let filteredLeads = [];

searchInput.addEventListener("input", () => {
  const searchTerm = searchInput.value.toLowerCase();
  filteredLeads = myLeads.filter((lead) =>
    lead.toLowerCase().includes(searchTerm)
  );
  render(filteredLeads);
});
  • let filteredLeads = [];: This line declares an empty array called filteredLeads to store the filtered leads that match the search term.

  • searchInput.addEventListener("input", () => {...}): This code adds an event listener to the search input field, which triggers whenever the user enters or modifies the search term.

  • const searchTerm = searchInput.value.toLowerCase();: This line retrieves the current value entered in the search input field and converts it to lowercase for case-insensitive comparison.

  • filteredLeads = myLeads.filter((lead) => lead.toLowerCase().includes(searchTerm));: This line uses the filter method on the myLeads array to create a new array filteredLeads. It checks each lead against the search term by converting both to lowercase and using includes to determine if the lead contains the search term.

  • render(filteredLeads);: This line calls the render function, passing the filteredLeads array as an argument. It updates the UI to display only the leads that match the search term.

This functionality allows users to input a search term in the search input field, and as they type, the leads are dynamically filtered and rendered based on the matching search term. The code uses the input event to capture user input and filters the leads using the filter() method. The rendered UI is then updated to display the filtered leads.

With all the JavaScript code added, our project structure should look like this:

LeadsCaptureExtension/
  ├── index.html
  ├── style.css
  └── index.js

In the next step, we'll see how to load our Chrome extension in Google Chrome for testing and use.

(Optional) Creating a Favicon:

A favicon is a small icon that appears in the browser tab or next to the page title in bookmarks. Although optional, adding a favicon to your Chrome extension can provide a more polished and professional look.

Here's a step-by-step guide on how to create a favicon for your project:

  1. Choose or design an icon: Start by selecting or creating an icon that represents your Chrome extension. Favicon icons are typically small and square, usually around 16x16 pixels or 32x32 pixels in size. You can use an image editing tool like Photoshop, GIMP, or online favicon generators to create your favicon.

  2. Save the icon image: Save the icon image in a suitable format such as PNG, GIF, or ICO. Make sure the file is named appropriately, such as "favicon.png" or "favicon.ico".

  3. Place the favicon in your project directory: Move the favicon image file to the root directory of your Chrome extension project.

To make the favicon appear on websites, you need to add a <link> tag to the <head> section of your HTML files. Here's an example of the <link> tag you can include:

<link rel="icon" type="image/png" href="favicon.png">

Make sure to replace "favicon.png" with the appropriate path and filename of your favicon image file.

This <link> tag specifies the "icon" relationship and sets the type attribute to "image/png" to indicate that the favicon is in PNG format. The href attribute specifies the path to the favicon image file.

Place this <link> tag within the <head> section of your HTML files, and the specified favicon will be displayed on the website when accessed in a browser that supports favicons.

Here's an example of how the complete <head> section with the favicon <link> tag might look:

<head>
  <meta charset="UTF-8">
  <title>Your Page Title</title>
  <link rel="icon" type="image/png" href="favicon.png">
  <!-- Other meta tags, stylesheets, and scripts -->
</head>

By adding this <link> tag, the favicon will be associated with your website, and it will be displayed in the browser tab or next to the page title in bookmarks when users visit your site.

Test the favicon: Load your Chrome extension in the browser and verify if the favicon is displaying correctly in the browser tab or bookmarks. If the favicon doesn't appear immediately, try clearing the browser cache and reloading the extension.

4. Adding the Extension Manifest (manifest.json):

The manifest.json file is a crucial component of a Chrome extension. It provides essential information about the extension to the browser, such as its name, version, permissions, and more. In this section, we will create and configure the manifest.json file for our leads capture app.

Step 1: Open a text editor and create a new file named manifest.json.

Step 2: Copy and paste the following code into the manifest.json file:

{
  "manifest_version": 3,
  "name": "Leads Tracker",
  "version": "1.0",
  "action": {
    "default_popup": "index.html",
    "default_icon": {
      "16": "favicon.png",
      "48": "favicon.png",
      "128": "favicon.png"
    }
  },
  "permissions": ["tabs"],
  "host_permissions": ["<all_urls>"]
}

Let's go through each key-value pair in the manifest.json code:

  • "manifest_version": 3 indicates that we are using manifest version 3.

  • "name": "Leads Tracker" specifies the name of our extension. You can change this to suit your app's name.

  • "version": "1.0" denotes the version number of the extension. Modify it accordingly as you update your app.

  • "action" is an object that defines the behavior of the extension's browser action (e.g., toolbar button). Here, we specify that the default popup for the browser action is index.html.

  • "default_icon" provides the path to the icon files used for the extension in different sizes (16x16, 48x48, 128x128). Make sure you have an icon.png file in the same directory as the manifest.json file. We are using the same image we used for our favicon.

  • "permissions": ["tabs"] grants the extension permission to access tabs in the browser.

  • "host_permissions": ["<all_urls>"] gives the extension permission to access all URLs. Modify this as per your app's requirements.

Step 3: Save the manifest.json file in the root directory of your leads capture app.

Congratulations! You have successfully created and configured the manifest.json file for your leads capture app. This file provides the necessary information and permissions for your Chrome extension to function properly.

Make sure to include all the required icon files (icon.png) in the same directory as the manifest.json file. These icons will be used to represent your extension in different UI elements of the browser.

Note: The manifest_version value is set to 3 in the example above. Manifest version 3 introduced changes in the structure and behavior of the manifest file. If you intend to target older versions of Chrome, you may need to use a different manifest version or adapt the code accordingly.

Step 4: Loading the Chrome Extension

Now that we have implemented the necessary HTML, CSS, and JavaScript code for our Chrome extension, it's time to load it in the Google Chrome browser for testing and use. Follow these steps:

  1. Open Google Chrome: Ensure that you have the Google Chrome browser installed on your computer. If not, refer back to the prerequisites section for instructions on how to download and install it.

  2. Open Extensions Settings: In the Google Chrome browser, click on the three vertical dots at the top right corner to open the Chrome menu. From the menu, navigate to "More Tools" and select "Extensions." This will open the Extensions settings page.

  3. Enable Developer Mode: In the Extensions settings page, you'll find a toggle switch labeled "Developer mode" at the top right corner. Enable it by toggling the switch to the right. This will enable developer features for Chrome extensions.

  4. Load Unpacked Extension: Once developer mode is enabled, you'll see a new button labeled "Load unpacked" appear on the left side of the screen. Click on it to load your Chrome extension.

  5. Select Extension Folder: A file browser dialog will open, allowing you to select the folder containing your Chrome extension. Navigate to the location where you saved the "LeadsCaptureExtension" folder, select it, and click "Open." Chrome will then load the extension.

  6. Verify the Extension: After loading the extension, you should see a new card labeled "Leads Capture" appear in the Extensions settings page. Make sure the toggle switch next to the extension is enabled, indicating that it's active.

That's it! You have successfully loaded your Chrome extension in the Google Chrome browser. Now let's test it out and see our lead capture functionality in action.

Step 5: Testing & Using the Chrome Extension

Now that our Chrome extension is loaded and active in Google Chrome, let's explore how to use it to capture leads from websites. Follow these steps:

  1. Open a New Tab: In Google Chrome, open a new tab by clicking the "+" button next to the existing tabs or by using the keyboard shortcut "Ctrl/Cmd + T."

  2. Access a Website: In the newly opened tab, enter the URL of the website from which you want to capture a lead. For example, you can enter "example.com". Press Enter to load the website.

  3. Capture the Lead: Once the website is loaded, you'll notice the input area at the top of the page in our extension. It has two text input fields: one for entering a custom lead URL and another for searching through the captured leads.

  4. Save a Custom Lead: In the first text input field, enter the URL of a lead you want to capture. For instance, you can enter "example.com/lead". After entering the lead URL, click on the "Save Input" button. The lead URL will be added to the list below, and it will be displayed as a clickable link.

  5. Save the Current Tab as a Lead: Click the "Save Tab" button to capture the URL of the current tab and add it to your list of leads. You can also capture leads from any opened tab by clicking the Leads Capture icon in the extension tray. It should work seamlessly on all tabs with full functionality.

  6. Delete a Lead: If you want to remove a specific lead from the list, click on the "x" button next to the corresponding lead. The lead will be deleted from the list, and the updated list will be displayed.

  7. Delete All Leads: If you want to remove all the captured leads and start fresh, click on the "Delete All" button. A confirmation dialog will appear asking for your confirmation. If you choose to proceed, all the leads will be deleted from the list.

  8. Search for Leads: If you have a long list of captured leads and want to find a specific lead, use the second text input field labeled "Search leads..." to enter a search term. As you type, the list will be filtered, displaying only the leads that match the search term.

  9. Test the Extension: Test the extension's behavior with different websites and verify that it captures leads and saves tabs correctly.

    If any issues or bugs are encountered during testing, go back to your code and make the necessary fixes. Repeat the process of loading the unpacked extension to see the changes in action.

That's it! You have successfully used our Chrome extension to capture leads from websites. Experiment with capturing leads from different websites and explore the functionality of the extension.

Congratulations on building your own Chrome extension! You can now customize and enhance it further based on your needs and preferences. Feel free to modify the HTML, CSS, and JavaScript code to add new features or improve existing ones.

In the final step, we'll wrap up the tutorial and provide some additional resources for further learning and exploration.

Step 6: Wrapping Up and Further Learning

Congratulations on successfully building your own Chrome extension! You've learned how to create a simple lead capture extension using HTML, CSS, and JavaScript. Now, you can explore and customize the extension based on your requirements.

If you want to review or access the code for this project, you can find the complete source code on the GitHub repository here. Feel free to star the repository if you found this tutorial helpful.

Feel free to experiment with the code, add new features, or make improvements. Building Chrome extensions opens up a world of possibilities for enhancing your browsing experience and productivity.

To continue your learning journey and explore more about Chrome extension development, here are some recommended resources:

Remember, the more you practice and build real projects, the better you'll become at Chrome extension development. So keep experimenting, exploring, and building!

Thank you for following along with this tutorial! If you have any questions or need further assistance, don't hesitate to reach out to me. You can find me on Twitter (@codingLuchi). I would love to hear your thoughts, so feel free to leave a comment. Happy coding!