Vanilla JavaScript Gallery

I wrote Vanilla JavaScript gallery using a class Gallery using manageable methods (functions) for the most part. Here is the JavaScript code -

'use strict';
/*
 * Photo Gallery 2.0 Created by John Pepp
 * on October 27, 2023 
 */
class Gallery {
    constructor() {
        // DOM Elements
        this.category = document.querySelector('#category');
        this.container = document.querySelector('.container');
        this.sidebar = document.querySelector('.sidebar_pages');
        this.lightbox = document.querySelector('.lightbox');

        // Pagination Configurations
        this.current_page = 1;
        this.per_page = 4;
        this.offset = 0;
        this.total_pages = 0;

        // Data fetched from the database
        this.database_data = {
            'category': 'wildlife',
            'current_page': this.current_page,
            'per_page': this.per_page,
            'total_count': 0,
            'offset': this.offset
        };
        this.pages = [{}];

        // Binding `this` context to methods
        this.categoryUISuccess = this.categoryUISuccess.bind(this);
        this.paginationUISuccess = this.paginationUISuccess.bind(this);
        this.resetLinks = this.resetLinks.bind(this);
    }

    // Handle fetch errors
    handleErrors(response) {
        if (!response.ok) {
            throw (response.status + ' : ' + response.statusText);
        }
        return response;
    }

    // Update the UI upon successfully fetching gallery category data
    async categoryUISuccess(parsedData) {
        this.clearContainer();

        parsedData.forEach((slide, index) => {
            this.createSlide(slide, index);
        });

        this.addImageListeners();
        this.addLightboxListener();
    }

    // Clear all child elements of the container
    clearContainer() {
        while (this.container.firstChild) {
            this.container.removeChild(this.container.firstChild);
        }
    }

    // Create individual gallery slides
    createSlide(slide, count) {
        const displayFormat = [
            "gallery-container w-3 h-2",
            'gallery-container w-3 h-2',
            'gallery-container w-3 h-2',
            'gallery-container w-3 h-2',
            'gallery-container w-3 h-2',
            'gallery-container w-3 h-2'
        ];

        const displayDiv = this.createElementWithClass('div', displayFormat[count]);
        this.container.appendChild(displayDiv);

        const galleryItem = this.createElementWithClass('div', 'gallery-item');
        displayDiv.appendChild(galleryItem);

        const images = this.createElementWithClass('div', 'images');
        galleryItem.appendChild(images);

        const galleryImage = this.createElement('img', {
            src: slide.image_path,
            alt: slide.content,
            loading: 'lazy',
            'data-exif': `${slide.Model} ${slide.ExposureTime} ${slide.Aperture} ${slide.ISO} ${slide.FocalLength}`
        });
        images.appendChild(galleryImage);

        const paragraph = this.createElementWithClassAndContent('p', 'hideContent', slide.content);
        images.appendChild(paragraph);

        const title = this.createElementWithClass('div', 'title');
        galleryItem.appendChild(title);

        const heading1 = this.createElementWithClassAndContent('h1', 'pictureHeading', `${slide.heading[0].toUpperCase()}${slide.heading.slice(1)}`);
        title.appendChild(heading1);

        const titleSpan = this.createElementWithClassAndContent('span', 'exifInfo', slide.Model);
        title.appendChild(titleSpan);
    }

    // Utility function to create an HTML element with attributes
    createElement(tag, attributes = {}) {
        const element = document.createElement(tag);
        Object.entries(attributes).forEach(([key, value]) => {
            element.setAttribute(key, value);
        });
        return element;
    }

    createElementWithClass(tag, className) {
        const element = this.createElement(tag);
        element.className = className;
        return element;
    }

    createElementWithClassAndContent(tag, className, content) {
        const element = this.createElementWithClass(tag, className);
        element.textContent = content;
        return element;
    }

    // Add click event listeners to images to open in lightbox
    addImageListeners() {
        const images = document.querySelectorAll('img');
        images.forEach(image => {
            image.addEventListener('click', () => this.handleImageClick(image)); // Corrected this line
        });
    }

    // Handle the click event when an image is clicked
    handleImageClick(image) {
        this.lightbox.classList.add('active');
        this.container.style.display = 'none';

        // Create the common container for galleryImage and galleryExif
        let imageExifContainer = document.createElement('div');
        imageExifContainer.classList.add('image-exif-container');
        /*
         * Create Image portion of LightBox
         */
        let galleryImage = document.createElement('img');
        galleryImage.classList.add('galleryImage');
        galleryImage.width = 800;
        galleryImage.height = 534;
        console.log('image', image);
        galleryImage.src = image.src // image path

        /*
         * Create EXIF portion of LightBox
         */
        let galleryExif = document.createElement('p');

        if  (image.getAttribute('data-exif') === 'null null null null null') {
            galleryExif.classList.add('galleryExif');
            galleryExif.textContent = 'No EXIF data available';
        } else {
            galleryExif.classList.add('galleryExif');
            galleryExif.textContent = image.getAttribute('data-exif');
        }

        // Add both elements to the common container
        imageExifContainer.appendChild(galleryImage);
        imageExifContainer.appendChild(galleryExif);

        /*
         * Create Text portion of Lightbox
         */

        let nextSibling = image.nextElementSibling; // Grab the next sibling:
        let galleryText = document.createElement('p');
        galleryText.classList.add('galleryText');

        galleryText.textContent = nextSibling.textContent;

        /* Remove large Image For Screen (cleanup) */
        while (this.lightbox.firstChild) {
            this.lightbox.removeChild(this.lightbox.firstChild)
        }

        // Add the container to the lightbox
        this.lightbox.appendChild(imageExifContainer);

        /* Add Content to Screen */
       this.lightbox.appendChild(galleryText);
    }

    // Add click event listener to lightbox to close it
    addLightboxListener() {
        this.lightbox.addEventListener('click', () => {
            if (this.lightbox.hasChildNodes()) {
                this.exitLightbox();
            }
        });
    }

    // Close the active lightbox
    exitLightbox() {
        document.getElementById('gallery_category').style.display = 'block';
        document.querySelector('.sidebar_pages').style.display = 'flex';

        this.lightbox.classList.remove('active');
        this.lightbox.classList.add('lightbox');

        document.querySelector('.container').style.display = 'grid';
        // document.querySelector('.pagination').style.display = 'flex';
    }

    // Handle errors when fetching gallery category data fails
    categoryUIError(error) {
        console.log("Database Table did not load", error);
    }

    // Send a request to the server to fetch images
    async createImageRequest(url, succeed, fail) {
        try {
            const response = await fetch(url, {
                method: 'POST', // or 'PUT'
                body: JSON.stringify(this.database_data),
            });

            this.handleErrors(response);

            const data = await response.json();
            succeed(data);
        } catch (error) {
            fail(error);
        }
    }

    // Clear all pagination links
    resetLinks() {
        /* Remove Links For Screen (cleanup) */
        while (this.sidebar.firstChild) {
            this.sidebar.removeChild(this.sidebar.firstChild)
        }
    }

    // Update the UI with the received pagination data
    async paginationUISuccess(parsedData) {
        this.resetLinks();

        this.database_data.offset = await parsedData.offset;
        this.total_pages = Math.ceil(this.database_data.total_count / this.database_data.per_page);

        /* Create the Display Links and add an event listener */
        this.pages = [{}];
        /*
         * Creating the array of page object(s)
         */
        for (let x = 0; x < this.total_pages; x++) {
            this.pages[x] = {page: x + 1};
        }

        this.pages.forEach(link_page => {
            const links = document.createElement('div');
            links.className = 'links';
            this.sidebar.appendChild(links);
            /*
             * Add event listener for the links
             */
            links.addEventListener('click', () => {
                this.database_data.current_page = link_page.page;

                // Close the lightbox if it's currently active
                if (this.lightbox.classList.contains('active')) {
                    this.lightbox.classList.remove('active');
                    document.getElementById('gallery_category').style.display = 'block';
                    document.querySelector('.sidebar_pages').style.display = 'flex';
                    document.querySelector('.container').style.display = 'grid';
                }

                this.createRequest('galleryPagination.php', this.paginationUISuccess, this.paginationUIError);
            });

            const pageText = document.createElement('p');
            pageText.className = 'linkStyle';
            pageText.id = 'page_' + link_page.page;
            pageText.textContent = link_page.page;
            links.appendChild(pageText);
            if (this.database_data.current_page === link_page.page) {
                links.style.backgroundColor = "#00b28d";
            }
        })

        await this.createImageRequest('galleryImagesGet.php', this.categoryUISuccess, this.categoryUIError);
    }


    // Handle errors when fetching pagination data fails
    paginationUIError(error) {
        console.log("Database Table did not load", error);
    }

    // Send a request to the server
    async createRequest(url, succeed, fail) {
        try {
            const response = await fetch(url, {
                method: 'POST', // or 'PUT'
                body: JSON.stringify(this.database_data),
            });

            this.handleErrors(response);

            const data = await response.json();
            console.log('count', data);
            succeed(data);
        } catch (error) {
            fail(error);
        }
    }

    // Send a request to get the total number of images in a category
    async updateTotalCountAndPagination() {
        await this.createRequest('getTotalCount.php', this.totalCountUISuccess.bind(this), this.totalCountUIError.bind(this));
    }

    // Update the UI upon successfully fetching the total count
    totalCountUISuccess(parsedData) {
        this.database_data.total_count = parsedData.total_count;
        this.createRequest('galleryPagination.php', this.paginationUISuccess.bind(this), this.paginationUIError.bind(this));
    }

    // Handle errors when fetching the total count fails
    totalCountUIError(error) {
        console.log("Database Table did not load", error);
    }

    // Add event listeners to DOM elements
    bindEvents() {
        this.category.addEventListener('change', () => {
            this.database_data.current_page = 1;
            this.database_data.category = this.category.value;

            if (this.lightbox.classList.contains('active')) {
                this.lightbox.classList.remove('active');
                document.getElementById('gallery_category').style.display = 'block';
                document.querySelector('.sidebar_pages').style.display = 'flex';
                document.querySelector('.container').style.display = 'grid';
            }

            this.updateTotalCountAndPagination();
        });

        document.addEventListener('DOMContentLoaded', () => {
            this.createRequest('galleryPagination.php', this.paginationUISuccess.bind(this), this.paginationUIError.bind(this));
        });
    }

    // Initialization function
    init() {
        this.updateTotalCountAndPagination();
        this.bindEvents();
    }
}

const gallery = new Gallery();
gallery.init();

the PHP can be found at my GitHub Repository here: https://github.com/Strider64/brain-wave-blitz along with an older gallery js script called images.js.

This might help someone who wants to develop a photo gallery that is more in-depth like a slideshow or something along that line.

1 Like
Sponsor our Newsletter | Privacy Policy | Terms of Service