Tuesday 13 April 2021

Infinite Scrolling For Table with Search Operation in JavaScript and NodeJS

Now a days its a common functionality to provide a Infinite Scroll Bar for tables to display large amount of data. Here's the below code to implement it in Vanila JS or JQuery and NodeJS with Postgres SQL. Below code fetches the list of countries from NodeJS API and populates it on HTML table.

Prerequisites:

You should have Node JS & Postgres SQL Server installed on your system.

HTML

<!DOCTYPE html>
<html>

<head>
    <title>Infinite Scroll</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
        integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns"
        crossorigin="anonymous"></script>

    <link rel="stylesheet" href="style.css">
    <script src="countries.js"></script>

</head>

<body>

    <div class="container">
        <div class="alert alert-primary" role="alert">
            <h4>Implementation of Infinite Scroll for Table with Search Bar</h4>
        </div>

        <div class="input-group mb-3">
            <!-- <div class="input-group-prepend">
                <span class="input-group-text" id="basic-addon2">Search</span>
            </div> -->
            <input type="text" class="form-control search" placeholder="Enter the text to search (Min 2 characters)"
                aria-label="Enter the text to search (Min 2 characters)" aria-describedby="basic-addon2">
        </div>

        <div id="table-container">
            <table class="table table-striped table-bordered">
                <thead>
                    <tr>
                        <th scope="col">Id</th>
                        <th scope="col">Sort Name</th>
                        <th scope="col">Name</th>
                        <th scope="col">Phone Code</th>
                    </tr>
                </thead>
                <tbody id="rows-data">

                </tbody>
            </table>
            <div id="loading-msg"></div>
        </div>
    </div>
</body>

</html>

<script>

    $(document).ready(function () {

        let limit = 20;
        let offset = 0;
        let isDataAvailable = 0;
        let obj = new Countries(limit, offset, isDataAvailable, "");

        $("#table-container").scroll(function (e) {
            obj.onScrollEvent();
        });

        $(".search").on("keyup", function (e) {
            obj.onSearch(e.currentTarget.value);
        });

    });

    (function ($) {
        $.fn.hasScrollBar = function () {
            return this.get(0).scrollHeight > this.height();
        }
    })(jQuery);
</script>

JavaScript

class Countries {

    constructor(limit, offset, isNoDataAvailable, search) {
        this.limit = limit;
        this.offset = offset;
        this.isNoDataAvailable = isNoDataAvailable;
        this.search = search;
        this.getCountriesList();
    }

    getCountriesList() {

        this.search = this.search.length >= 2 ? this.search : "";
        let self = this;

        $.ajax({
            url: "http://localhost:8085/getData",
            method: "POST",
            data: {
                limit: self.limit,
                offset: self.offset,
                search: self.search
            },
            cache: false,
            success: function (response) {                

                if (!response.data.length) {
                    $('#loading-msg').html("");
                    self.isNoDataAvailable = 1;
                } else {
                    $('#loading-msg').html(`<div class="spinner-border text-primary" role="status"><span class="sr-only">Loading...</span></div>`);
                    self.isNoDataAvailable = 0;
                    self.generateData(response);

                    if(!$("#table-container").hasScrollBar()){
                        $('#loading-msg').html("");
                    }
                }
            }
        });
    }

    generateData(response) {
        let ele = "";

        response.data.forEach((value) => {
            ele = `<tr>                
                <td>${$.trim(value.id)}</td>
                <td>${$.trim(value.sortname)}</td>
                <td>${$.trim(value.name)}</td>                
                <td>${$.trim(value.phonecode)}</td>
                </tr>`;
            $("#rows-data").append(ele);
        });
    }

    onScrollEvent() {
        if (!this.isNoDataAvailable) {
            this.isNoDataAvailable = 1;
            this.offset = this.offset + this.limit;
            self = this;
            setTimeout(function () {
                self.getCountriesList();
            }, 1000);
        }
    }

    onSearch(value) {
        $("#rows-data").html("");
        this.limit = 20;
        this.offset = 0;
        this.isDataAvailable = 0;
        this.search = value;
        this.getCountriesList();
    }
}

NodeJS

'use strict';
const http = require('http');
const cors = require('cors')
const express = require('express');
const app = express();
const busboy = require('connect-busboy');
const bodyParser = require('body-parser');
const { Sequelize, DataTypes, Op } = require('sequelize');
const sequelize = new Sequelize('postgres://postgres:root@localhost:5432/postgres');

sequelize.authenticate().then(() => {
	console.log('Connection has been established successfully.');
}).catch(err => {
	console.error('Unable to connect to the database:', err);
});

let server = http.createServer(app);
const port = 8085;

app.use(busboy());
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(bodyParser.raw());

const Countries = sequelize.define('countries', {
	id: {
		type: DataTypes.NUMBER,
		allowNull: false,
		primaryKey: true
	},
	sortname: {
		type: DataTypes.STRING,
		allowNull: false
	},
	name: {
		type: DataTypes.STRING,
		allowNull: false
	},
	phonecode: {
		type: DataTypes.NUMBER,
		allowNull: false
	}
},
	{
		timestamps: false
	});

app.post('/getData', function (req, res) {

	let search = !req.body.search ? "" : req.body.search.toLowerCase();

	try {
		countRecords(search).then(result => {
			console.log("\n\nTotal Records: ", result.count + "\n\n");
			getRecords(req, search).then((data) => {
				return res.status(200).json({
					status: 200,
					count: result.count,
					data: JSON.parse(JSON.stringify(data)),
					message: "Success"
				});
			});
		});
	} catch (e) {
		return res.status(400).json({ status: 400, data: e, message: "No Data Found" });
	}

});

function getRecords(req, search) {
	return Countries.findAll({
		where: {
			[Op.or]: [
				{
					name: {
						[Op.iLike]: '%' + search + '%'
					}
				},
				
				{
					sortname: {
						[Op.like]: '%' + search + '%'
					}
				}
			]
		},
		limit: parseInt(req.body.limit),
		offset: parseInt(req.body.offset)
	})
}

function countRecords(search) {
	return Countries.findAndCountAll({
		where: {
			[Op.or]: [
				{
					name: {
						[Op.iLike]: '%' + search + '%'
					}
				},
				{
					sortname: {
						[Op.like]: '%' + search + '%'
					}
				}
			]
		}
	})
}

// Start server
function startServer() {
	server.listen(port, function () {
		console.log('Express server listening on ', port);
	});
}

setImmediate(startServer);

Download the code from below Repo:

https://github.com/jogkunal5/table_infinite_scroll/tree/master

No comments:

Post a Comment