Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building Custom jQuery and Developing a Knockout.js Table Application

Tech 1

Building a Custom jQuery Library

jQuery introduced a feature allowing developers to construct custom builds of the library, containing only the required components. This minimizes the file size, which is particularly beneficial for mobile and performance-critical applications. The process involves using the official jQuery build tool, Grunt.js, which relies on Node.js.

Environment Setup

To build a custom jQuery version, you must first set up a development environment with the following tools:

  • Git: For cloning the jQuery source code repository.
  • Node.js: Required to run the build tool.
  • Grunt.js: The build tool itself, installed via Node Package Manager (NPM).

Cloning and Building jQuery

Clone the jQuery repository from GitHub:

git clone https://github.com/jquery/jquery.git

Navigate in to the cloned directory and install Grunt and its dependencies:

npm install -g grunt-cli
npm install

To build a complete jQuery version, execute:

grunt

This generates the standard jQuery files in the dist directory.

Creating a Custom Build

You can exclude specific modules to reduce the library size. For example, to build a core-only version, run:

grunt custom:-ajax,-css,-deprecated,-dimensions,-effects,-offset

This command produces a smaller jQuery file containing only essential functionalities.

Running Unit Tests with QUnit

jQuery uses QUnit for unit testing. To run the test suite, ensure you have a web server (like Apache with PHP) configured to serve the jQuery source directory. Access the test suite via:

http://localhost/test

Tests can be run for any jQuery version. To switch to a sttable branch (e.g., 1.8.3), use:

git checkout 1.8.3
npm install
grunt

Then, reload the test page to verify all tests pass.

Implementing an Infinite Scroller with jQuery

Infinite scrolling loads content dynamically as the user scrolls, enhancing user experience by progressively disclosing data. This technique is ideal for sequentially ordered content like news feeds or video lists.

Basic Page Setup

Start with a simple HTML structure and include necessary scripts: jQuery, JsRender for templating, and the imagesLoaded plugin.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Infinite Scroller</title>
    <link rel="stylesheet" href="css/infinite-scroller.css">
</head>
<body>
    <div id="videoContainer"></div>

    <script src="img/jquery-1.9.0.min.js"></script>
    <script src="img/jsrender.js"></script>
    <script src="img/jquery.imagesloaded.min.js"></script>
    <script src="img/infinite-scroller.js"></script>
</body>
</html>

Data Retrieval and Display

Use jQuery's $.getJSON() to fetch data from an API (e.g., YouTube). Define functions to retrieve user profile and video data.

$(function() {
    let appData = {};
    let startIndex = 1;

    const fetchUser = () => {
        return $.getJSON('https://gdata.youtube.com/feeds/api/users/tedtalksdirector?callback=?', {
            v: 2,
            alt: 'json'
        }, (response) => {
            appData.user = response.entry;
        });
    };

    const fetchVideos = () => {
        return $.getJSON('https://gdata.youtube.com/feeds/api/videos?callback=?', {
            author: 'tedtalksdirector',
            v: 2,
            alt: 'jsonc',
            'start-index': startIndex
        }, (response) => {
            appData.videos = response.data.items;
        });
    };

    $.when(fetchUser(), fetchVideos()).done(() => {
        startIndex += 25;
        renderContent(true);
    });
});

Templating with JsRender

Define templates for rendering user information and video lists.

<script id="userTemplate" type="text/x-jsrender">
    <section>
        <header>
            <img src="{{>avatar}}" alt="{{>name}}">
            <h1>{{>name}}</h1>
            <p>{{>summary.substring(0,200)}}...</p>
        </header>
        <ul id="videoList"></ul>
    </section>
</script>

<script id="videoTemplate" type="text/x-jsrender">
    <li>
        <article>
            <a href="{{>link}}">
                <img src="{{>thumbnail}}" alt="{{>title}}">
                <h3>{{>title}}</h3>
            </a>
            <p>{{>description.substring(0,100)}}...</p>
        </article>
    </li>
</script>

Scroll Event Handling

Attach a scroll event listener to load more content when the user reaches the bottom.

$(window).on('scroll', function() {
    if ($(window).scrollTop() + $(window).height() >= $(document).height()) {
        $('<li class="loading">Loading more videos...</li>').appendTo('#videoList');
        $.when(fetchVideos()).done(() => {
            startIndex += 25;
            renderContent(false);
            $('.loading').remove();
        });
    }
});

Developing a Heatmap with jQuery

A heatmap visualizes user interaction data, such as click locations, on a webpage. This can be particularly useful for analyzing user behavior on responsive designs.

Client-Side Click Tracking

Capture click coordinates and viewport information, then send data to a server for storage.

$(function() {
    const clickData = {
        url: window.location.href,
        clicks: []
    };
    const layouts = [];

    // Parse CSS media queries to determine layout breakpoints
    $('link[rel="stylesheet"]').each(function() {
        const sheet = this.sheet;
        if (sheet && sheet.cssRules) {
            Array.from(sheet.cssRules).forEach(rule => {
                if (rule.media && rule.media.length) {
                    const mediaText = rule.media[0];
                    layouts.push({
                        min: mediaText.includes('min-width') ? parseInt(mediaText.split('min-width:')[1]) : 0,
                        max: mediaText.includes('max-width') ? parseInt(mediaText.split('max-width:')[1]) : Infinity
                    });
                }
            });
        }
    });

    $(document).on('click', function(e) {
        const clickInfo = {
            x: Math.round((e.pageX / $(document).width()) * 100),
            y: Math.round((e.pageY / $(document).height()) * 100),
            layout: determineLayout($(document).width())
        };
        clickData.clicks.push(clickInfo);
    });

    function determineLayout(width) {
        for (let i = 0; i < layouts.length; i++) {
            if (width >= layouts[i].min && width <= layouts[i].max) {
                return i + 1;
            }
        }
        return layouts.length + 1;
    }

    // Send data before page unload
    $(window).on('beforeunload', function() {
        $.ajax({
            url: '/save-clicks',
            method: 'POST',
            data: JSON.stringify(clickData),
            contentType: 'application/json',
            async: false
        });
    });
});

Admin Console for Heatmap Visualization

Create an admin interface to display collected click data as an overlay on the target page.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Heatmap Console</title>
    <link rel="stylesheet" href="css/console.css">
</head>
<body class="heatmap-console">
    <header>
        <h1>Heatmap Dashboard</h1>
        <input type="text" id="pageUrl" placeholder="Enter page URL">
        <button id="loadPage">Load</button>
        <select id="layoutSelect"></select>
    </header>
    <section>
        <iframe id="targetPage" scrolling="no"></iframe>
        <canvas id="heatmapCanvas"></canvas>
    </section>

    <script src="img/jquery-1.9.0.min.js"></script>
    <script src="img/console.js"></script>
</body>
</html>

Rendering the Heatmap on Canvas

Fetch stored click data and draw points on a canvas overlay.

$(function() {
    const $iframe = $('#targetPage');
    const $canvas = $('#heatmapCanvas');
    const ctx = $canvas[0].getContext('2d');

    $('#loadPage').on('click', function() {
        const url = $('#pageUrl').val();
        $iframe.attr('src', url).on('load', function() {
            $.getJSON('/get-clicks', { url: url }, function(clicks) {
                renderHeatmap(clicks);
            });
        });
    });

    function renderHeatmap(clicks) {
        const width = $iframe.width();
        const height = $iframe.height();
        $canvas.attr({ width, height }).css({ width, height });
        ctx.clearRect(0, 0, width, height);
        ctx.fillStyle = 'rgba(255,0,0,0.5)';
        clicks.forEach(click => {
            const x = (click.x / 100) * width;
            const y = (click.y / 100) * height;
            ctx.beginPath();
            ctx.arc(x, y, 8, 0, Math.PI * 2);
            ctx.fill();
        });
    }
});

Creating a Sortable, Paginated Table with Knockout.js

Knockout.js facilitates building dynamic user interfaces with a clean separation of data (Model), presentation (View), and logic (ViewModel). Combined with jQuery, it enables efficient development of interactive applications.

Basic Table with Knockout Bindings

Define a ViewModel with an observable array and bind it to a table in the HTML.

<table>
    <thead>
        <tr>
            <th data-bind="click: sortByColumn, css: sortOrder('name')">Name</th>
            <th data-bind="click: sortByColumn, css: sortOrder('number')">Atomic Number</th>
            <th data-bind="click: sortByColumn, css: sortOrder('symbol')">Symbol</th>
            <th data-bind="click: sortByColumn, css: sortOrder('weight')">Atomic Weight</th>
            <th data-bind="click: sortByColumn, css: sortOrder('discovered')">Discovered</th>
        </tr>
    </thead>
    <tbody data-bind="foreach: displayedItems">
        <tr>
            <td data-bind="text: name"></td>
            <td data-bind="text: number"></td>
            <td data-bind="text: symbol"></td>
            <td data-bind="text: weight"></td>
            <td data-bind="text: discovered"></td>
        </tr>
    </tbody>
</table>

<select data-bind="value: pageSize, event: { change: resetPage }">
    <option value="10">10</option>
    <option value="30">30</option>
    <option value="all">All</option>
</select>

<nav>
    <a href="#" data-bind="click: previousPage, css: { disabled: currentPage() === 0 }">Previous</a>
    <span data-bind="foreach: pageNumbers">
        <a href="#" data-bind="text: $data, click: $parent.goToPage, css: { active: $data === $parent.currentPage() + 1 }"></a>
    </span>
    <a href="#" data-bind="click: nextPage, css: { disabled: isLastPage() }">Next</a>
</nav>

ViewModel Implementation

function TableViewModel(data) {
    const self = this;
    self.allItems = ko.observableArray(data);
    self.displayedItems = ko.observableArray([]);
    self.pageSize = ko.observable(10);
    self.currentPage = ko.observable(0);
    self.sortKey = ko.observable('name');
    self.sortDirection = ko.observable('asc');

    self.sortedItems = ko.computed(() => {
        const key = self.sortKey();
        const dir = self.sortDirection();
        return self.allItems().slice().sort((a, b) => {
            const valA = a[key];
            const valB = b[key];
            if (valA < valB) return dir === 'asc' ? -1 : 1;
            if (valA > valB) return dir === 'asc' ? 1 : -1;
            return 0;
        });
    });

    self.totalPages = ko.computed(() => {
        if (self.pageSize() === 'all') return 1;
        return Math.ceil(self.sortedItems().length / self.pageSize());
    });

    self.pageNumbers = ko.computed(() => {
        const pages = [];
        for (let i = 1; i <= self.totalPages(); i++) pages.push(i);
        return pages;
    });

    self.isLastPage = ko.computed(() => {
        return self.currentPage() === self.totalPages() - 1;
    });

    ko.computed(() => {
        const start = self.pageSize() === 'all' ? 0 : self.currentPage() * self.pageSize();
        const end = self.pageSize() === 'all' ? self.sortedItems().length : start + self.pageSize();
        self.displayedItems(self.sortedItems().slice(start, end));
    });

    self.sortByColumn = function(column) {
        if (self.sortKey() === column) {
            self.sortDirection(self.sortDirection() === 'asc' ? 'desc' : 'asc');
        } else {
            self.sortKey(column);
            self.sortDirection('asc');
        }
        self.currentPage(0);
    };

    self.sortOrder = function(column) {
        return ko.computed(() => {
            return self.sortKey() === column ? self.sortDirection() : '';
        });
    };

    self.previousPage = function() {
        if (self.currentPage() > 0) self.currentPage(self.currentPage() - 1);
    };

    self.nextPage = function() {
        if (!self.isLastPage()) self.currentPage(self.currentPage() + 1);
    };

    self.goToPage = function(pageNum) {
        self.currentPage(pageNum - 1);
    };

    self.resetPage = function() {
        self.currentPage(0);
    };
}

const initialData = [
    { name: "Hydrogen", number: 1, symbol: "H", weight: 1.00794, discovered: 1766 },
    // ... more data
];

ko.applyBindings(new TableViewModel(initialData));

Adding Filtering Capability

Extend the ViewModel to include filtering by element state.

self.filterStates = ko.observableArray(['All', 'Solid', 'Liquid', 'Gas', 'Unknown']);
self.selectedState = ko.observable('All');

self.filteredItems = ko.computed(() => {
    const state = self.selectedState();
    if (state === 'All') return self.allItems();
    return self.allItems().filter(item => item.state === state);
});

// Update sortedItems to use filteredItems
self.sortedItems = ko.computed(() => {
    const key = self.sortKey();
    const dir = self.sortDirection();
    return self.filteredItems().slice().sort((a, b) => {
        const valA = a[key];
        const valB = b[key];
        if (valA < valB) return dir === 'asc' ? -1 : 1;
        if (valA > valB) return dir === 'asc' ? 1 : -1;
        return 0;
    });
});

Add a select element for filtering in the HTML:

<select data-bind="options: filterStates, value: selectedState"></select>

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.