Tuesday, March 11, 2025

Virtualization

 <!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Virtualized Table Demo</title>

    <style>

        body {

            font-family: Arial, sans-serif;

            margin: 20px;

        }

        

        h1 {

            color: #333;

        }

        

        .stats {

            background-color: #f4f4f4;

            padding: 10px;

            margin: 10px 0;

            border-radius: 4px;

        }

        

        #table-container {

            border: 1px solid #ccc;

            border-radius: 4px;

            width: 100%;

            max-width: 800px;

            box-shadow: 0 2px 5px rgba(0,0,0,0.1);

        }

        

        .row-content {

            display: flex;

            border-bottom: 1px solid #eee;

            background-color: #fff;

        }

        

        .row-content:hover {

            background-color: #f9f9f9;

        }

        

        .cell {

            padding: 10px;

            flex: 1;

            overflow: hidden;

            text-overflow: ellipsis;

            white-space: nowrap;

        }

        

        .header {

            display: flex;

            background-color: #f0f0f0;

            font-weight: bold;

            border-bottom: 2px solid #ddd;

        }

        

        .header .cell {

            padding: 10px;

        }

        

        /* Different background colors for even rows */

        .virtual-row:nth-child(even) .row-content {

            background-color: #f7f7f7;

        }

        

        .controls {

            margin: 20px 0;

        }

        

        button {

            padding: 8px 15px;

            background-color: #4CAF50;

            color: white;

            border: none;

            border-radius: 4px;

            cursor: pointer;

            margin-right: 10px;

        }

        

        button:hover {

            background-color: #45a049;

        }

    </style>

</head>

<body>

    <h1>Virtualized Table Demo</h1>

    

    <div class="stats">

        <div>Total Rows: <span id="total-rows">10,000</span></div>

        <div>DOM Elements: <span id="dom-elements">0</span></div>

        <div>Current Scroll Index: <span id="current-index">0</span></div>

    </div>

    

    <div class="controls">

        <button id="scroll-to-middle">Scroll to Middle</button>

        <button id="scroll-to-end">Scroll to End</button>

        <button id="scroll-to-start">Scroll to Start</button>

    </div>

    

    <div class="header">

        <div class="cell">ID</div>

        <div class="cell">Name</div>

        <div class="cell">Email</div>

        <div class="cell">City</div>

    </div>

    

    <div id="table-container"></div>

    

    <script>

        // Generate a large amount of mock data

        function generateMockData(count) {

            const names = ['John', 'Jane', 'Michael', 'Emily', 'David', 'Sarah', 'Robert', 'Olivia', 'William', 'Sophia'];

            const lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'Garcia', 'Wilson', 'Martinez'];

            const cities = ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix', 'Philadelphia', 'San Antonio', 'San Diego', 'Dallas', 'San Jose'];

            

            return Array.from({ length: count }, (_, i) => ({

                id: i + 1,

                name: `${names[Math.floor(Math.random() * names.length)]} ${lastNames[Math.floor(Math.random() * lastNames.length)]}`,

                email: `user${i + 1}@example.com`,

                city: cities[Math.floor(Math.random() * cities.length)]

            }));

        }

        

        class VirtualizedTable {

            constructor(options) {

                this.container = options.container;

                this.data = options.data || [];

                this.rowHeight = options.rowHeight || 40;

                this.visibleRows = options.visibleRows || 10;

                this.bufferRows = options.bufferRows || 5;

                

                this.totalRows = this.data.length;

                this.totalHeight = this.totalRows * this.rowHeight;

                this.renderedRows = [];

                

                this.setupContainer();

                this.renderInitialView();

                this.attachScrollHandler();

                

                // Update stats

                document.getElementById('total-rows').textContent = this.totalRows.toLocaleString();

            }

            

            setupContainer() {

                this.container.style.position = 'relative';

                this.container.style.overflow = 'auto';

                this.container.style.height = (this.visibleRows * this.rowHeight) + 'px';

                

                this.spacer = document.createElement('div');

                this.spacer.style.height = this.totalHeight + 'px';

                this.spacer.style.width = '100%';

                this.spacer.style.position = 'relative';

                this.container.appendChild(this.spacer);

            }

            

            renderInitialView() {

                const totalRowsToRender = this.visibleRows + (this.bufferRows * 2);

                

                for (let i = 0; i < Math.min(totalRowsToRender, this.totalRows); i++) {

                    this.createRowElement(i);

                }

                

                // Update DOM elements count in stats

                document.getElementById('dom-elements').textContent = this.renderedRows.length;

            }

            

            createRowElement(dataIndex) {

                const rowData = this.data[dataIndex];

                

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

                rowElement.className = 'virtual-row';

                rowElement.style.position = 'absolute';

                rowElement.style.top = (dataIndex * this.rowHeight) + 'px';

                rowElement.style.height = this.rowHeight + 'px';

                rowElement.style.width = '100%';

                

                rowElement.innerHTML = `

                    <div class="row-content">

                        <div class="cell">${rowData.id}</div>

                        <div class="cell">${rowData.name}</div>

                        <div class="cell">${rowData.email}</div>

                        <div class="cell">${rowData.city}</div>

                    </div>

                `;

                

                rowElement.dataset.virtualIndex = dataIndex;

                

                this.spacer.appendChild(rowElement);

                this.renderedRows.push({

                    element: rowElement,

                    dataIndex: dataIndex

                });

                

                return rowElement;

            }

            

            updateRowElement(rowObj, newDataIndex) {

                const { element } = rowObj;

                const rowData = this.data[newDataIndex];

                

                element.style.top = (newDataIndex * this.rowHeight) + 'px';

                

                element.innerHTML = `

                    <div class="row-content">

                        <div class="cell">${rowData.id}</div>

                        <div class="cell">${rowData.name}</div>

                        <div class="cell">${rowData.email}</div>

                        <div class="cell">${rowData.city}</div>

                    </div>

                `;

                

                element.dataset.virtualIndex = newDataIndex;

                rowObj.dataIndex = newDataIndex;

            }

            

            attachScrollHandler() {

                this.container.addEventListener('scroll', () => {

                    this.updateVisibleRows();

                    

                    // Update current index in stats

                    const firstVisibleIndex = Math.floor(this.container.scrollTop / this.rowHeight);

                    document.getElementById('current-index').textContent = firstVisibleIndex;

                });

            }

            

            updateVisibleRows() {

                const scrollTop = this.container.scrollTop;

                

                const firstVisibleIndex = Math.floor(scrollTop / this.rowHeight);

                

                const startIndex = Math.max(0, firstVisibleIndex - this.bufferRows);

                const endIndex = Math.min(

                    this.totalRows - 1,

                    firstVisibleIndex + this.visibleRows + this.bufferRows

                );

                

                const rowsToUpdate = this.renderedRows.filter(row => {

                    return row.dataIndex < startIndex || row.dataIndex > endIndex;

                });

                

                const indicesToShow = [];

                for (let i = startIndex; i <= endIndex; i++) {

                    const isRendered = this.renderedRows.some(row => row.dataIndex === i);

                    if (!isRendered) {

                        indicesToShow.push(i);

                    }

                }

                

                for (let i = 0; i < Math.min(rowsToUpdate.length, indicesToShow.length); i++) {

                    this.updateRowElement(rowsToUpdate[i], indicesToShow[i]);

                }

            }

            

            scrollToIndex(index) {

                const targetIndex = Math.min(Math.max(0, index), this.totalRows - 1);

                this.container.scrollTop = targetIndex * this.rowHeight;

            }

        }

        

        // Initialize the table when the page loads

        document.addEventListener('DOMContentLoaded', () => {

            const container = document.getElementById('table-container');

            const mockData = generateMockData(10000); // 10,000 rows

            

            const virtualTable = new VirtualizedTable({

                container: container,

                data: mockData,

                rowHeight: 40,

                visibleRows: 15,

                bufferRows: 5

            });

            

            // Setup scroll buttons

            document.getElementById('scroll-to-middle').addEventListener('click', () => {

                virtualTable.scrollToIndex(5000);

            });

            

            document.getElementById('scroll-to-end').addEventListener('click', () => {

                virtualTable.scrollToIndex(9999);

            });

            

            document.getElementById('scroll-to-start').addEventListener('click', () => {

                virtualTable.scrollToIndex(0);

            });

        });

    </script>

</body>

</html>

Tuesday, March 4, 2025

jquery plugin

 // Simple stateful toggle plugin

(function($) {

    'use strict';

    

    $.fn.toggleState = function(options) {

        // Default options

        var settings = $.extend({

            activeClass: 'active',

            inactiveClass: 'inactive',

            onToggle: function() {}

        }, options);

        

        return this.each(function() {

            var $element = $(this);

            

            // Get the existing state from the element or initialize it

            var state = $element.data('plugin_toggleState');

            

            // If the plugin hasn't been initialized on this element yet

            if (!state) {

                // Initialize the state and store it using $.data()

                state = {

                    isActive: false,

                    toggleCount: 0

                };

                

                // Store the state object on the DOM element

                $element.data('plugin_toggleState', state);

                

                // Initialize the element appearance

                $element.addClass(settings.inactiveClass);

                

                // Set up click handler

                $element.on('click.toggleState', function() {

                    $element.toggleState('toggle');

                });

            }

            

            // Method invocation handling

            if (typeof options === 'string') {

                if (options === 'toggle') {

                    // Toggle the state

                    state.isActive = !state.isActive;

                    state.toggleCount++;

                    

                    // Update the element

                    if (state.isActive) {

                        $element.removeClass(settings.inactiveClass).addClass(settings.activeClass);

                    } else {

                        $element.removeClass(settings.activeClass).addClass(settings.inactiveClass);

                    }

                    

                    // Update the stored state

                    $element.data('plugin_toggleState', state);

                    

                    // Call the callback

                    settings.onToggle.call($element, state.isActive, state.toggleCount);

                }

                else if (options === 'status') {

                    // Return the current state

                    return state;

                }

                else if (options === 'destroy') {

                    // Clean up

                    $element.removeData('plugin_toggleState');

                    $element.off('.toggleState');

                    $element.removeClass(settings.activeClass + ' ' + settings.inactiveClass);

                }

            }

        });

    };

})(jQuery);


// Usage:

$(document).ready(function() {

    // Initialize the plugin

    $('.toggle-button').toggleState({

        activeClass: 'btn-success',

        inactiveClass: 'btn-secondary',

        onToggle: function(isActive, count) {

            console.log('Button toggled to: ' + (isActive ? 'active' : 'inactive'));

            console.log('Button has been toggled ' + count + ' times');

        }

    });

    

    // Get the state

    $('#statusButton').on('click', function() {

        var state = $('.toggle-button').first().toggleState('status');

        alert('First button state: ' + (state.isActive ? 'active' : 'inactive') + 

              '\nToggle count: ' + state.toggleCount);

    });

    

    // Programmatically toggle

    $('#toggleAllButton').on('click', function() {

        $('.toggle-button').toggleState('toggle');

    });

    

    // Destroy the plugin

    $('#resetButton').on('click', function() {

        $('.toggle-button').toggleState('destroy');

    });

});