Friday, May 2, 2025

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 widely adopted web application stack. No one wants to wait long to get the results they want from a web application. Ensuring optimal performance is crucial and should be integrated into every phase of planning and development. This article will present actionable practices to boost your application's performance and maintain fast response times. These strategies will cover optimizations at the database, Java, and JavaScript layers.


1. Database

One of the most common reasons for application slowness is access to external resources such as databases. That’s why you need to adopt certain behaviors and follow rules to make your application more efficient, such as:


Pooling: Database connection pooling is a technique that maintains a pool of open, ready-to-use database connections. Establishing a database connection is an expensive operation, involving network setup, authentication, and authorization checks, all of which consume significant time. Connection pooling minimizes the overhead of repeatedly creating new connections by allowing applications to reuse existing, active connections from the pool. This reuse significantly reduces the time required to execute database operations, leading to improved application performance and responsiveness.


Minimize Database Access: Optimize database interactions by reducing the frequency and granularity of requests. Instead of performing numerous small operations, consider batching related actions into a single database call. Furthermore, strategically caching frequently accessed data within your application's memory structures can significantly reduce the need to query the database repeatedly, leading to faster data retrieval and overall performance gains. While in-memory processing can be quicker for certain tasks, carefully evaluate data consistency requirements and potential memory constraints.


Minimize Expensive Database Operations: Reduce the use of inherently costly database operations. For example, avoid performing extensive string manipulations directly within database queries, as these can be less efficient than handling them within the application layer. Consider alternative data types and processing strategies to minimize the database's workload and improve query performance.


Optimize your SQL queries: To improve performance, reduce resource usage, and deliver faster response times. This is especially crucial when working with large datasets or complex joins. Let’s take an example of a correlated subquery and how to write it efficiently using Oracle SQL join.


Let's take the famous example of the 'customers' database. We want to find all customers who have made at least one purchase over 5,000. Using a correlated subquery, we get:

SELECT c.customer_id, c.customer_name

FROM customers c

WHERE EXISTS (

    SELECT 1

    FROM orders o

    WHERE o.customer_id = c.customer_id

    AND o.order_total > 5000

);


- This query is less performant because it runs once for each row in the customers table.

- On large datasets, this approach leads to numerous redundant executions, resulting in significant performance degradation.

- While indexing orders.customer_id or orders.order_total may offer some improvement, it remains a suboptimal solution.

You can make this query more efficient if you use join with DISTINCT as follows:

SELECT DISTINCT c.customer_id, c.customer_name

FROM customers c

JOIN orders o ON c.customer_id = o.customer_id

WHERE o.order_total > 5000;


The optimization advantages of this query over the first one are:

- Transforms the correlated subquery into a set-based join, allowing Oracle to leverage its optimized join algorithms for improved performance.

- The DISTINCT clause ensures that each customer is only returned once, even if they have multiple orders that meet the criteria.

- With indexes on orders.customer_id, and orders.order_total, Oracle can efficiently filter and join the tables, minimizing I/O operations and maximizing query speed.


Utilities to Boost Queries Performance

Database management systems like Oracle or MySQL offer utilities that help you understand the execution of your queries. So, you can analyze the actual performance of a query, debug performance issues, or tune queries. 

In Oracle, two tools, EXPLAIN PLAN and AUTOTRACE, are defined. The EXPLAIN PLAN reveals Oracle's execution plan for your SQL statement, including tables and indexes used, join methods, and operation order. It provides an estimation of how the Oracle optimizer will execute a SQL query.


AUTOTRACE is a utility integrated into tools like SQL*Plus or SQL Developer. It provides the execution plan and details about query execution, such as statistics like CPU time, row count, and other performance metrics.


In MySQL, there are tools like EXPLAIN and ANALYZE. EXPLAIN provides an estimated execution plan of an SQL statement without running it. On the other hand, ANALYZE EXPLAIN executes your query and provides a detailed execution plan.


These tools help to gain insight into query performance and optimization opportunities.


Tips for Query Optimization

Optimizing database queries is crucial for application performance. Here are several key strategies to consider:

    • Create indexes on frequently queried/joined columns, especially those in WHERE, JOIN, and ORDER BY clauses. 

    • Avoid applying functions to indexed columns in WHERE clauses (e.g., FUNCTION(column)) to preserve index usability. 

    • Avoid Full Table Scans. Only if they are necessary.

    • Improve query readability and performance by using WITH clauses (CTEs) for clarity and reuse and converting correlated subqueries to joins.

    • Use bind variables (parameters) to facilitate plan caching and mitigate hard parsing. 

    • Gather statistics to maintain current table and index statistics, enabling the optimizer to generate efficient execution plans based on accurate data.


2. Java Code

To enhance the performance of your Java code in a Java web application, consider following these best practices:

    • Avoid creating unnecessary objects. Reuse objects where possible to reduce memory usage and garbage collection overhead.

    • Use StringBuilder instead of string concatenation, especially within loops, to improve performance and reduce object creation.

    • Minimize session data storage. Avoid storing large volumes of data in the session to prevent excessive memory usage.

    • Set short session timeouts and invalidate sessions when they are no longer needed to free up resources promptly.

    • Use static objects sparingly. Static objects persist for the lifetime of the application and can lead to memory leaks if not managed carefully.

    • Always release closable resources, such as database connections, in finally blocks or use try-with-resources to ensure proper cleanup.

    • Implement concurrent threads or tasks to take advantage of multi-core processors and improve application responsiveness.

    • Activate caching to store frequently accessed data retrieved from the database. Subsequent requests can then be served directly from this cache, avoiding the overhead of querying the primary storage unless the data is not present or has expired. This significantly improves response times and reduces database load.

    • Use asynchronous processing for long-running requests, especially when the server delegates tasks to third-party services, to prevent blocking and improve scalability.

    • Avoid quadratic (O(N²)) or higher time complexity algorithms in performance-critical sections. Replace nested loops with more efficient strategies like indexing or divide-and-conquer approaches to improve scalability.

    • Use efficient data structures such as HashMap, Set, or TreeMap to optimize performance. Use Java Streams or parallel streams carefully—they can enhance code clarity and concurrency, but don’t reduce the underlying algorithmic complexity.

    • Reduce Server Load: To reduce server load, performance-intensive operations such as sorting and filtering should ideally be performed within the SQL query or client-side using JavaScript, depending on the specific use case and data volume. 


3. JavaScript Code

To improve the performance of your JavaScript code in a Java web application, consider following these best practices:


Load Scripts Efficiently

    • Break large scripts into smaller, manageable modules and load only what is needed for each page.

    • Avoid blocking the main thread to keep your application responsive. You can also use web workers that allow you to run JavaScript code in the background.

    • A page should be displayed quickly. Any slow data blocking the page display must be loaded asynchronously using mechanisms such as async or Ajax. 

    • Defer loading non-essential content like images and videos until they are about to be visible using the loading="lazy" HTML attribute on <img> elements and JavaScript Intersection Observers for more advanced control.


Use Efficient Data Structures and Algorithms

    • Select the right data structures (e.g. Set, Map, arrays) for your use case and avoid inefficient algorithms (e.g. O(N²) operations) on large datasets.

    • Cache expensive computations when possible.


Leverage Appropriate Caching Mechanisms

    • Use localStorage or sessionStorage to persist user preferences, filters, or data between page loads.

    • Use in-memory caching (e.g., variables or data-* HTML attributes) when data should be temporary and cleared on page refresh.

    • Refer to [my JavaScript caching tutorial] for detailed use cases and examples.


Debounce or Throttle High-Frequency Events

    • Apply debounce or throttle techniques to high-frequency events such as scroll, resize, or keyup. These techniques help prevent unnecessary function executions, reducing performance overhead and optimizing your application's responsiveness.


Minimize Direct DOM Manipulation. Access and update the DOM efficiently by batching changes or minimizing reflows/redraws. Also, avoid excessive use of innerHTML and appendChild, and modifying DOM elements in loops without optimization. Let's delve into details.

Manipulating the DOM (Document Object Model) involves directly changing a webpage's content using JavaScript, such as adding elements, modifying content, or applying styles. However, frequent and direct DOM manipulation can be slow and inefficient, particularly for complex or dynamic interfaces. To improve performance, consider the following best practices:

Batch Changes. Instead of making individual DOM changes in a loop, group them and apply the updates in a single batch. This technique reduces the number of times the browser needs to recalculate the page layout, resulting in improved performance. To further illustrate this, consider the following example:


// Before: individual updates in a loop

for (let i = 0; i < 100; i++) {

  const element = document.createElement('div');

  document.body.appendChild(element);

}

// After: batched updates

const fragment = document.createDocumentFragment();

for (let i = 0; i < 100; i++) {

  const element = document.createElement('div');

  fragment.appendChild(element);

}

document.body.appendChild(fragment);


Minimize Reflows/Repaints. Changing layout-related styles, such as size, position, or fonts, triggers a reflow or repaint, which can be costly. This process involves recalculating and redrawing parts of the page, impacting performance.

When accessing and modifying layout-related properties, such as offsetHeight or clientWidth, do so in separate blocks of code to avoid forced synchronous reflows. Avoid interleaving reads and writes to prevent unnecessary reflows. Here's an illustrative example of this topic:


Avoid Excessive Reparsing and Recalculations. Here are some points to consider to improve your application's performance:

    • Setting innerHTML replaces the entire content of an element, forcing a reparse of the HTML. Minimize the use of innerHTML and instead use targeted updates.

    • Repeatedly calling appendChild in a loop triggers multiple layout recalculations. Instead, use a DocumentFragment to batch updates.

    • Avoid setting styles in a loop. Instead, use classes or apply all styles at once to minimize recalculations.


The following example is for using cssText to update multiple styles at once instead of updating individual styles:

// Modify styles: bad practice 

element.style.height = '100px';

element.style.width = '200px';


// Modify styles: good practice 

element.style.cssText = "width: 100px; height: 50px;";


// Bad practice: interleaved reads and writes

const height = element.offsetHeight;

element.style.height = '100px';

const width = element.offsetWidth;


// Good practice: separate blocks

const height = element.offsetHeight;

const width = element.offsetWidth;


Minify and Bundle JavaScript Files

    • Minify JS code and bundle multiple scripts to reduce file size and HTTP requests. Use tools like Webpack to minify code, bundle scripts, and improve page load times.


Avoid Memory Leaks

    • To prevent memory leaks, especially during page transitions or single-page app (SPA) routing, it's crucial to clean up resources that are no longer needed by removing event listeners, clearing timeouts and intervals, and dereferencing DOM elements. Let's note that in the JavaScript environment in the SPA persists across these page transitions. 


Profile Using Browser DevTools

Unlock the potential of your browser's DevTools, such as Chrome DevTools or Firefox Developer Tools, to gain a deeper understanding of your web application's performance and resource utilization. With these built-in tools, you can profile your web application:

    • Memory: Analyze consumption over time to identify leaks and optimize usage.

    • Rendering: Inspect performance details to improve page rendering speed.

    • Script: Measure JavaScript execution to optimize code performance.

    • Network: Check load times of your files and requests for faster loading.

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...