Friday, January 10, 2025

Custom JavaScript Plugin

 

1. Introduction

Plugins are a powerful mechanism for avoiding repetitive tasks. Once you develop a plugin, you can reuse it in multiple projects. That’s why learning how to build custom plugins is a valuable skill for developers. It enables you to extend the functionality of existing software, tailor solutions to specific requirements, and optimize your workflow.

In this article, we’ll walk you through the fundamental steps of developing and using a custom plugin in JavaScript. We will explain an example of a custom plugin that creates a tooltip that displays the data of the attached element. The tooltip opens and closes if you click on the corresponding element. The full example of this custom tooltip and a demo of its usage is on GitHub:

https://github.com/noura-github/custom-plugin-tooltip-blog


For more examples, including another custom tooltip implementation, you can explore our repository on GitHub:

https://github.com/noura-github/custom-plugin-translate-blog


This custom plugin enables a tooltip that displays the translation of a word when hovered over. The translation is rendered in the language selected from a dropdown menu.


2. JavaScript Plugin

A jQuery plugin is a reusable code which adds new functionalities to jQuery. Plugins are a kind of pre-built solution for common tasks. They help reduce repetitive codes. They utilize existing libraries and frameworks to streamline development and reduce redundant code. 


Technically, a plugin is code written in a JavaScript file and consists of one or multiple functions that extend the jQuery object. To use the plugin’s functionality, you can call a custom method on a jQuery object.


2.2. Use Cases for jQuery Plugins

jQuery plugins can be used for a variety of purposes, including:

UI Components and Widgets: Modals, tables, date pickers, etc.

Form Enhancements: Form validation, processing, auto-complete, etc.

DOM manipulation and traversal

Effects and Animations: Fade effects, etc.

Ajax Enhancements: Simplified data loading and displaying.


2.3. Plugin Definition

We usually start by creating a new JavaScript file to create a new plugin. This file defines the plugin code, including its properties and functions. Then, we attach these functions to the jQuery object using the $.fn property.


Here’s a simple example of a jQuery plugin that adds a ‘myPlugin()’ method:


(function($) {

  $.fn.myPlugin = function(options) {

    this.css('background-color', options.bgColor);

    this.css('border', options.border);

    this.css('font-size', options.fontSize);

    return this;

  };

})(jQuery);


Now we can attach the plugin ‘myPlugin()’ to an element (paragraph) like this:


$("p").myPlugin({

     bgColor: "red",

     fontSize: "18px",

     border: "2px solid blue"

});


3. Example of Custom Plugin

The plugin we want to suggest to you in this section is useful for creating visually rich and interactive tooltips. The tooltip opens and closes when you click on the corresponding element.


Note. Here, we focus exclusively on the key aspects of defining and using a custom plugin. For the complete example, visit the GitHub repository:

https://github.com/noura-github/custom-plugin-tooltip-blog


3.1. Tooltip Plugin Code

The tooltip plugin code is developed in jQuery. But for the demonstration, we need a back-end and front-end code. The back-end code is written in Python to send dummy data from a database to the front-end code. We also need HTML and CSS code to display and style the data respectively. Finally, the JavaScript code is used to create the tooltip plugin and demonstrate how to use it.


Python Code

For this example, we need some data. So, we created a Python SQLite database named ‘custom_plugin.db’ with two tables: ‘employee’ and ‘files’. The table ‘employee’ stores the data about an employee such as their first name, last name, email, and so on. 

The ‘files’ table stores a file with a file name and description. A file can be associated with an employee using the foreign key ‘file_id’ which corresponds to a primary key ‘id’ in the ‘files’ table. Here is the code for the ‘create_database()’ function:


# Function to create the database and tables

def create_database():

    # Connect to SQLite database (or create it if it doesn't exist)

    conn = sqlite3.connect('custom_plugin.db')

    cursor = conn.cursor()


    # Create the files table

    cursor.execute('''

    CREATE TABLE IF NOT EXISTS files (

        id INTEGER PRIMARY KEY AUTOINCREMENT,

        filename TEXT NOT NULL,

        description TEXT,

        file BLOB

    )

    ''')

    print("Table 'files' created successfully.")


    # Create the employee table

    cursor.execute('''

    CREATE TABLE IF NOT EXISTS employee (

        id INTEGER PRIMARY KEY,

        firstname TEXT NOT NULL,

        lastname TEXT NOT NULL,

        email TEXT NOT NULL,

        phone TEXT NOT NULL,

        departmentId INTEGER NOT NULL,

        departmentName TEXT NOT NULL,

        companyName TEXT NOT NULL,

        file_id INTEGER,

        FOREIGN KEY (file_id) REFERENCES files (id)

    )

    ''')

    print("Table 'employee' created successfully.")


    # Commit changes and close the connection

    conn.commit()

    conn.close()


We need then to insert some dummy data. Thus, to add some data to the ‘files’ table, we suggest the following functions:


# Function to insert a file into the database

def insert_file(filename, description, filepath):

    # Connect to SQLite database

    conn = sqlite3.connect('custom_plugin.db')

    cursor = conn.cursor()


    # Read the file as binary data

    with open(filepath, 'rb') as file:

        file_data = file.read()


    # Insert the file into the table

    cursor.execute('''

        INSERT INTO files (filename, description, file)

        VALUES (?, ?, ?)

    ''', (filename, description, file_data))


    print(f"File '{filename}' inserted successfully.")


    # Commit changes and close the connection

    conn.commit()

    conn.close()


# Function to populate the files table

def populate_files():

    # Example: Insert a file into the table

    insert_file('Dock.jpg', 'A dock over a lake at night.', 'static/images/Dock.jpg')

    insert_file('Fields.jpg', 'A road in the fields of flowers leading to the mountains.', 'static/images/Fields.jpg')

    insert_file('Waterfall.jpg', 'A Cascading Waterfall under pink trees.', 'static/images/Waterfall.jpg')

    insert_file('Lake.jpg', 'A a blue water lake.', 'static/images/Lake.jpg')


The ‘insert_file()’ Function: This function inserts a file into the SQLite database. It reads the file as binary data, then executes an SQL INSERT statement to store the file’s name, description, and contents in the ‘files’ table.


The ‘populate_files()’ Function: This function inserts four image files into the database using the ‘insert_file()’ function, with their respective names, descriptions, and file paths.


In this Python code, we need a ‘get_data()’ function as a Flask route that handles GET requests to the ‘/data’ endpoint by returning the employee data dictionary as a JSON object.


@app.route('/data', methods=['GET'])

def get_data():

    # Sample data to send to the frontend

    return jsonify(get_employee_data())


Moreover, we need another Flask API endpoint that handles a POST request to the ‘/imagedata’ route. The endpoint expects a JSON payload with an ‘id’ field, which is used to retrieve a file’s binary data.


@app.route('/imagedata', methods=['POST'])

def get_image_data():

    # Ensure request contains JSON data

    data = request.get_json()


    if not data or 'id' not in data:

        return Response("Missing 'id' in request body", status=400)


    file_id = data.get('id')


    # Your logic to retrieve the file data

    file_row = find_image_file(file_id)

    if not file_row or not file_row[0]:

        return Response("File not found", status=404)


    blob_data = file_row[0]


    # Return binary data with appropriate headers

    response = Response(blob_data, mimetype='application/octet-stream')

    response.headers['Content-Disposition'] = 'attachment; filename="output_file.bin"'

    return response


The ‘get_image_data()’ Function: This function handles POST requests to the ‘/imagedata’ route, validates the request for a file ‘id’, retrieves the corresponding binary file data from the database, and returns it as a downloadable response.


The ‘find_image_file()’ Function: This function connects to the ‘custom_plugin.db’ SQLite database and retrieves the file associated with the given ‘file_id’ from the files table.


def find_image_file(file_id):

    # Connect to SQLite database

    conn = sqlite3.connect('custom_plugin.db')

    cursor = conn.cursor()


    # Query the table for the file

    cursor.execute('''

        SELECT file FROM files WHERE id = ?

    ''', (file_id,))


    # Fetch the file data

    file_data = cursor.fetchone()


    # Close the connection

    conn.close()


    return file_data


HTML Code

In HTML code, we need a table to display the data:

<table id="dataTable">

    <thead>

        <tr>

            <th>First Name</th>

            <th>Last Name</th>

            <th>Department Name</th>

            <th>Company Name</th>

        </tr>

    </thead>

    <tbody class="containment">

    </tbody>

</table>


JavaScript Code

We have crafted two files in JavaScript. The first one, tooltipdata.js, houses our custom plugin, while the other file, main.js, is designed to populate the data and activate the ‘tooltipdata’ plugin. Let’s dive into the details of the custom plugin and how to utilize it.


Custom Tooltip Plugin. An overview of the code for this plugin is as follows:


(function($) {


    function __tooltipdata(elem, options) {

        var that = this;

        this.id = options.id;

        this.title = options.title;

        this.customClass = options.customClass;

        this.containerClass = options.containerClass;

        this.tooltipImgClass = options.tooltipImgClass;

        this.headerClass = options.headerClass;

        this.menuClass = options.menuClass;

        this.contentClass = options.contentClass;

        this.footerClass = options.footerClass;


// Rest of the code (properties, methods, or events handler)

...

    }

    // Plugin function

    $.fn.tooltipData = function(data) {

        return this.each(function() {

            // Check if the element already has tooltip data initialized

            if (!$.data(this, "tooltipdata")) {

                // Initialize the tooltip data and store it in the element's data

                $.data(this, "tooltipdata", new __tooltipdata($(this), data));

            }

        });

    };

}(jQuery));


Plugin Properties. The code defines a function named ‘__tooltipdata’ that takes two arguments: ‘elem’ and ‘options’. This function creates and manages tooltip data for a given element. The ‘__tooltipdata’ function sets up several properties for the tooltip data, including:

id: an identifier for image file of the tooltip.

title: the title of the tooltip

customClass: a custom CSS class for the tooltip.

containerClass: a CSS class for the tooltip container.

tooltipImgClass: a CSS class for the tooltip image.

headerClass: a CSS class for the tooltip header.

menuClass: a CSS class for the tooltip menu.

contentClass: a CSS class for the tooltip content.

footerClass: a CSS class for the tooltip footer.

image: the URL of the tooltip image.

info_head, info_foot, and info_menu: HTML content for the tooltip header, footer, and menu, respectively.


Tooltip Creation. The code creates a tooltip element using jQuery and appends it to the elem element. It also sets up event listeners for the tooltip, including a click event handler that toggles the tooltip’s visibility.


elem.tooltip({

  content: this.content,

  position: this.position,

  show: this.show,

  hide: this.hide,

  track: this.track,

  classes: this.classes,

  open: function(event, ui) {

    // code here

  }

});


In the event handler of the click event, if the title property is set, the code checks the ‘isOpened’ property of that object. This property is used to keep track of whether the tooltip is currently open or not. If the ‘isOpened’ property is true, the code sets it to false and hides the tooltip using the tooltip("disable") method. This method is part of the tooltip plugin and is used to disable the tooltip.


$(elem).click(function () {

   if (that.title) {

       if (that.isOpened) {

           that.isOpened = false;

           // Hide the tooltip using the API

           $(elem).tooltip("disable");

       } else {

           that.isOpened = true;

           // Temporarily update the title and enable the tooltip

           $(elem).attr("title", that.title).tooltip("enable");

           // Show the tooltip programmatically

           $(elem).tooltip("open");

       }

   }

});


The function ‘onOpen()’: If the image is not cached, the code makes an AJAX request to the URL specified in the ‘imageurl’ property of the ‘tooltipdata’ plugin. The request is made using the $.ajax method and has the following properties:

type: The request method is set to POST.

url: The request URL is set to the value of the ‘imageurl’ property.

dataType: The expected data type of the response is set to binary.

processData: This property is set to false. This means that the data sent in the request body will not be processed by jQuery.

contentType: This property is set to ‘application/json’, which means that the request body will be sent as JSON data.

xhrFields: This property is set to an object with a single property ‘responseType’ set to blob. This means that the response will be treated as a binary blob.

data: The request body is set to a JSON object with a single property ‘id’ set to the value of the ‘id’ property of the plugin.

When the response is received, the code checks if the response was successful. If it was, the code creates a URL for the response blob using the ‘URL.createObjectURL’ method and sets the image property to the created URL. The code also caches the image URL in the ‘imageCache’ object using the ‘id’ property as the key. Finally, the code updates the ‘src’ attribute of the ‘tooltipImg’ element with the created image URL.


this.onOpen = function() {

   if (that.image) return;

   if (that.imageCache[that.id]) {

       that.image = that.imageCache[that.id];

       that.tooltipImg.attr("src", that.image);

   } else {

       $.ajax({

          type: 'POST',

          url: that.imageurl,

          dataType: 'binary',

          processData: false,

          contentType: 'application/json',

          xhrFields: { responseType: 'blob' },

          data: JSON.stringify({ 'id': that.id }),

          success: function(blob) {

              // Create a URL for the blob

              var imageUrl = URL.createObjectURL(blob);

              that.image = imageUrl;

             // Cache the image

             that.imageCache[that.id] = imageUrl;

             // Set the src attribute of the img element

             that.tooltipImg.attr("src", imageUrl);

           },

           error: function(xhr, status, error) {

              console.error("Error: ", error, "Status: ", status);

           }

        });

    }

}


Plugin Function. The code defines a plugin function named ‘tooltipData’ that can be used to create and manage tooltips for a given element.


To work with this plugin, we first need to load some data. For this, we create a function called ‘getEmployeeData()’ that we call once the document is ready as follows:


$(document).ready(function () {

    getEmployeeData();

});


The ‘getEmployeeData()’ Function: This function makes a GET request to the ‘/data’ endpoint to retrieve employee data in JSON format. Upon successful retrieval, it calls two other functions: ‘displayEmployeeData()’ to display the received data and ‘initializeTooltipData()’ to set up tooltip data.


function getEmployeeData() {

    $.ajax({

        type: 'GET',

        url: '/data',

        dataType: 'json',

        success: function(data) {

            displayEmployeeData(data);

            initializeTooltipData();

        },

        error: function(xhr, status, error) {

            console.error("Error: ", error, "Status: ", status);

        }

    });

}


The ‘displayEmployeeData()’ Function: This function takes an array of employee data and populates the table with the data. It loops through each employee object in the data array. For each employee, it appends a new table row (tr) to the table body. Within the table row, it creates four table cells (td) containing the employee’s: First name (with a tooltip trigger), Last name (with a tooltip trigger), Department name, and Company name. The tooltip triggers (span elements with class ‘tooltip_trigger’) contain additional data attributes (e.g. data-firstname, data-lastname, etc.) that will be used to display more information about the employee when the tooltip is triggered.

function displayEmployeeData(data) {

    const $tableBody = $("#dataTable tbody");

    $tableBody.empty();


    $.each(data, function(index, employee) {

        $tableBody.append(`

            <tr>

                <td><span class="tooltip_trigger" data-firstname="${employee.firstname}" data-lastname="${employee.lastname}" data-id="${employee.file_id}" data-filename="${employee.filename}" data-description="${employee.description}" data-email="${employee.email}" data-phone="${employee.phone}">${employee.firstname}</span></td>

                <td><span class="tooltip_trigger" data-firstname="${employee.firstname}" data-lastname="${employee.lastname}" data-id="${employee.file_id}" data-filename="${employee.filename}" data-description="${employee.description}" data-email="${employee.email}" data-phone="${employee.phone}">${employee.lastname}</span></td>

                <td><span>${employee.departmentName}</span></td>

                <td><span>${employee.companyName}</span></td>

            </tr>

        `);

    });

}


The ‘initializeTooltipData()’ Function: This function initializes tooltips for each employee data row in the table. It selects all span elements within the table body that contain employee data. For each span element, it extracts various data attributes (e.g. firstname, lastname, email, phone, etc.). If the firstname or lastname attribute is present, it creates a tooltip with the following content:

- Header with employee name.

- Footer with file title and description.

- Menu with email and phone number.

The function then initializes a tooltip for each span element using the ‘tooltipData’ method, passing in various options for the tooltip’s behavior, appearance, and content.

function initializeTooltipData() {

    const $tableBody = $("#dataTable tbody");

    let $spanElem = $tableBody

                .children("tr")

                .find("td")

                .find("span");


    $spanElem.each(function () {

        const $span = $(this);

        let firstname = $span.data("firstname");

        let lastname = $span.data("lastname");

        if (firstname || lastname) {

            let email = $span.data("email");

            let phone = $span.data("phone");

            let fileName = $span.data("filename");

            let description = $span.data("description");

            let file_id = $span.data("id");

            let info_head = "<span class='menu-head'>" + firstname + " " + lastname + "</span>";

            let info_foot = "<span class='menu-head'>" + "Title: " + "</span>"

                            + "<span class='menu-body'>" + getFileBaseName(fileName) + "</span>"

                            + "<br>"

                            + "<span class='menu-head'>" +  "Description: " + "</span>"

                            + "<span class='menu-body'>" + description + "</span>";

            let info_menu = "<span class='menu-head'>" + "Email: " + "</span>"

                            + "<span class='menu-body'>" + email + "</span>"

                            + "<br>"

                            + "<span class='menu-head'>" + "Phone: " + "</span>"

                            + "<span class='menu-body'>" + phone + "</span>";

            $span.tooltipData({

                id: file_id,

                title: $span.text(),

                contentClass: "content-tooltip",

                containerClass: "tooltip-container",

                tooltipImgClass: "tooltip_img",

                headerClass: "header",

                menuClass: "menu",

                contentClass: "content",

                footerClass: "footer",

                image: "",

                info_head: info_head,

                info_foot: info_foot,

                info_menu: info_menu,

                imageurl: "/imagedata",

                position: { my: "left+10 center", at: "right center" },

                show: {

                    effect: "fadeIn", 

                    duration: 400   

                },

                hide: {

                    effect: "fadeOut",

                    duration: 400   

                },

                track: true,

                classes: {

                    "ui-tooltip": "highlight"

                },

            });

        }

    });

}


3.2. Example Running

When you run the example, and you click on the last name (‘Smith’), you get its tooltip as shown in the following figure:










Blog Posts

Enhancing Performance of Java-Web Applications

Applications built with a Java back-end, a relational database (such as Oracle or MySQL), and a JavaScript-based front-end form a common and...