Code Refactor and understanding confusions I have

Hello,

I have a script on my site that isn’t working correctly in one particular area. I feel like this script is getting way too messy and spaghettified therefore it needs some refactoring and tidying up. I will post the full original script below.

I have done a bit of reading online about using import and export to split my code and try and make it a little simpler. I have started this and am hitting some areas where I feel I need a bit of verification about whether what I have done is the right road to go down and also other areas where I am not sure what path to take and need some answers.

Here’s my original messy script:

import URI from 'urijs'
import Handlebars from 'handlebars'
import InfiniteScroll from 'infinite-scroll'
import Flickity from 'flickity'
import { getOffsetTop } from './get-offset-top.js'
import MicroModal from 'micromodal'
import SlimSelect from 'slim-select'

// Define global variables
let infScroll = false
let infiniteScrollEnabled = false
let object = false
let selector = false
let url = false

function debounce(func, wait, immediate) {
    var timeout
    return function () {
        var context = this, args = arguments
        var later = function () {
            timeout = null
            if (!immediate) func.apply(context, args)
        }
        var callNow = immediate && !timeout
        clearTimeout(timeout)
        timeout = setTimeout(later, wait)
        if (callNow) func.apply(context, args)
    }
}



function toggleViewMoreButton() {
    if (document.querySelector('.pagination')) {
        document.querySelector('.view-more-button').style.display = 'inline-flex'
    } else {
        document.querySelector('.view-more-button').style.display = 'none'
    }
}



function refreshModals() {
    // Use whatever selector you're using for MicroModal triggers.
    const modalTrigger = 'data-micromodal-trigger'

    // Get all triggers.
    const modalTriggers = document.querySelectorAll(`[${modalTrigger}]`)

    modalTriggers.forEach(trigger => {
        // Get the attribute to save.
        const triggerElement = trigger.getAttribute(modalTrigger)

        // Remove the attribute briefly to clear memory/existing modals.
        trigger.removeAttribute(modalTrigger)

        // Immediately add it back.
        trigger.setAttribute(modalTrigger, triggerElement)

        // Re-initialize.
        MicroModal.init()
    })
}



function doInfiniteScroll() {
    if (document.querySelector(`.paginator--${object}s .next_page a`)) {
        let gridElement = document.querySelector(`.${object}-grid`)
        let requestPath = `.paginator--${object}s .next_page a`

        infScroll = new InfiniteScroll(gridElement, {
            path: requestPath,
            append: `.${object}-top-level`,
            scrollThreshold: false,
            status: '.page-load-status',
            button: '.view-more-button',
            history: false
        })

        infScroll.on('append', (response, path, items) => {
            // Refresh Modals
            refreshModals()

            // Manually push URL to the Turbolinks history
            Turbo.navigator.history.push(new URL(path));

            console.log(path)
        })

        infScroll.on('request', function (path) {
            console.log(path)
        })

        infiniteScrollEnabled = true
    }
}



function endInfiniteScroll() {
    if (infiniteScrollEnabled) {
        infScroll.destroy()

        infiniteScrollEnabled = false
    }
}



function updateGrid(url) {
    fetch(url)
        .then(response => response.json())
        .then(data => {
            let template = document.getElementById('park-and-coaster-grid-template').innerHTML
            let templateScript = Handlebars.compile(template)
            let html = templateScript(data)

            document.querySelector('.wacky-worm').innerHTML = html

            // Re enable InfiniteScroll
            doInfiniteScroll()

            // Refresh Modals
            refreshModals()

            // Show or Hide View More button
            toggleViewMoreButton()
        })

    // Add current filter settings to URL history (Remove page query if on first page)
    let queries = url.search(true)
    if (queries.page == 1) {
        url.removeSearch('page')
    }

    url.suffix('')

    Turbo.navigator.history.push(new URL(url));
}









document.addEventListener("turbo:load", function (e) {
    // Figure out whether we are dealing with Parks or Coasters
    if (document.body.classList.contains('parks-index') || document.body.classList.contains('coasters-index') || document.body.classList.contains('rides-index')) {
        object = document.querySelector('.wacky-worm').getAttribute('data-object')

        if (document.querySelector('.item-coaster_attributes')) {
            const slim_select = new SlimSelect({
                select: '.item-coaster_attributes',
                placeholder: 'All attributes',
                allowDeselectOption: true
            });
        }

        doInfiniteScroll()

        toggleViewMoreButton()

        // Pagination
        document.querySelector('.wacky-worm').addEventListener('click', (e) => {
            if (e.target.matches('.pagination li a')) {
                e.preventDefault()

                endInfiniteScroll()

                url = new URI(e.target.getAttribute('href'))
                // params = url.search(true)
                url.suffix('json')

                updateGrid(url)
            }
        })

        // If a year is selected then show the "New in <year>" checkbox automatically
        var url = new URI();
        let queries = url.search(true)
        if (queries.page == 1) {
            url.removeSearch('page')
        }

        if (queries.ridden_in) {
            let year = queries.ridden_in
            let new_in_ridden_in_year_element = document.querySelector('.item-new-in-ridden-in-year')

            new_in_ridden_in_year_element.querySelector('.update-year-here').innerHTML = 'New in ' + year
            new_in_ridden_in_year_element.style.visibility = 'visible'
        }





        // Order
        document.querySelector('select.item-sort').addEventListener('change', (e) => {
            let selected = e.target.options[e.target.selectedIndex].value
            url = new URI()

            endInfiniteScroll()

            url.removeQuery('sort')
            url.addQuery('sort', selected)
            url.removeSearch('page')
            url.addQuery('page', 1)
            url.suffix('json')

            updateGrid(url)
        })




        // Material
        selector = 'select.item-material'
        if (document.querySelector(selector)) {
            document.querySelectorAll(selector).forEach(radio => {
                radio.addEventListener('change', (e) => {
                    let selected = e.target.value
                    url = new URI()

                    endInfiniteScroll()

                    if (selected == 'all') {
                        url.removeSearch('material')
                        url.suffix('json')

                        updateGrid(url)
                    } else {
                        url.removeSearch('material')
                        url.addQuery('material', selected)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')

                        updateGrid(url)
                    }
                })
            })
        }



        // Milestones
        selector = '[type="checkbox"][name="milestones"]'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                url = new URI()

                endInfiniteScroll()

                if (e.target.checked) {
                    url.removeSearch('milestones')
                    url.addQuery('milestones', 1)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeSearch('milestones')
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }



        // Soundtracks
        selector = '[type="checkbox"][name="soundtracks"]'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                url = new URI()

                endInfiniteScroll()

                if (e.target.checked) {
                    url.removeSearch('soundtracks')
                    url.addQuery('soundtracks', 1)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeSearch('soundtracks')
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }



        // Reverse
        selector = '[type="checkbox"][name="reverse"]'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                url = new URI()

                endInfiniteScroll()

                if (e.target.checked) {
                    url.removeSearch('reverse')
                    url.addQuery('reverse', 1)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeSearch('reverse')
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }



        // Countries
        selector = 'select.item-countries'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                let selected = e.target.options[e.target.selectedIndex].value
                url = new URI()

                endInfiniteScroll()

                if (selected == 'all') {
                    url.removeSearch('country')
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeSearch('country')
                    url.addQuery('country', selected)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }



        // Parks
        selector = 'select.item-parks'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                let selected = e.target.options[e.target.selectedIndex].value
                url = new URI()

                endInfiniteScroll()

                if (selected == 'all') {
                    url.removeSearch('park')
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeSearch('park')
                    url.addQuery('park', selected)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }



        // Manufacturers
        selector = 'select.item-manufacturers'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                let selected = e.target.options[e.target.selectedIndex].value
                url = new URI()

                endInfiniteScroll()

                if (selected == 'all') {
                    url.removeSearch('manufacturer')
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeSearch('manufacturer')
                    url.addQuery('manufacturer', selected)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }



        // Park Chains
        selector = 'select.item-parkchains'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                let selected = e.target.options[e.target.selectedIndex].value
                url = new URI()

                endInfiniteScroll()

                if (selected == 'all') {
                    url.removeSearch('parkchain')
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeSearch('parkchain')
                    url.addQuery('parkchain', selected)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }



        // Letter
        selector = '[type="radio"][name="letter"]'
        if (document.querySelector(selector)) {
            document.querySelectorAll(selector).forEach(radio => {
                radio.addEventListener('change', (e) => {
                    let selected = e.target.value
                    url = new URI()

                    endInfiniteScroll()

                    if (selected == 'all') {
                        url.removeSearch('letter')
                        url.suffix('json')

                        updateGrid(url)
                    } else {
                        url.removeSearch('letter')
                        url.addQuery('letter', selected)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')

                        updateGrid(url)
                    }
                })
            })
        }



        // Ridden in Year
        selector = 'select.item-ridden-in'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                let selected = e.target.options[e.target.selectedIndex].value
                url = new URI()

                let new_in_ridden_in_year_element = document.querySelector('.item-new-in-ridden-in-year')

                // Set New in RIY Checkbox back to unchecked
                new_in_ridden_in_year_element.querySelector('[type="checkbox"]').checked = false

                endInfiniteScroll()

                url.removeQuery('new_in_riy')

                if (selected == 'all') {
                    new_in_ridden_in_year_element.style.visibility = 'hidden'

                    url.removeQuery('ridden_in')
                    url.suffix('json')

                    updateGrid(url)
                    console.log(url)
                } else {
                    new_in_ridden_in_year_element.style.visibility = 'visible'
                    new_in_ridden_in_year_element.querySelector('.update-year-here').innerHTML = 'New in ' + selected

                    url.removeQuery('ridden_in')
                    url.addQuery('ridden_in', selected)
                    url.removeQuery('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                    console.log(url)
                }
            })
        }



        // New in Ridden in Year
        selector = '[type="checkbox"][name="new_in_riy"]'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                url = new URI()

                endInfiniteScroll()

                if (e.target.checked) {
                    url.removeQuery('new_in_riy')
                    url.addQuery('new_in_riy', 1)
                    url.removeQuery('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeQuery('new_in_riy')
                    url.removeQuery('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }

        // Coaster Styles
        selector = 'select.item-coaster_styles'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                let selected = e.target.options[e.target.selectedIndex].value
                url = new URI()

                endInfiniteScroll()

                if (selected == 'all') {
                    url.removeSearch('coaster_styles')
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeSearch('coaster_styles')
                    url.addQuery('coaster_styles', selected)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }

        // Coaster Attributes
        selector = 'select.item-coaster_attributes'
        if (document.querySelector(selector)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                // let selected = e.target.options[e.target.selectedIndex].value

                // Convert all of the options to an array
                // Then, get an array of only the options that are selected
                // Then, get an array of the selected option values
                let selected = Array.from(document.querySelector(selector).options).filter(function (option) {
                    return option.selected;
                }).map(function (option) {
                    return option.value;
                })

                selected = selected.join('|');

                url = new URI()

                endInfiniteScroll()

                if (selected == '') {
                    url.removeSearch('coaster_attributes')
                    url.suffix('json')

                    updateGrid(url)
                } else {
                    url.removeSearch('coaster_attributes')
                    url.addQuery('coaster_attributes', selected)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')

                    updateGrid(url)
                }
            })
        }
    }
})

Below is where I am currently up to in regards to splitting and simplifying this code:

import SlimSelect from 'slim-select'
import startInfiniteScroll from './coaster_and_park_filters/startInfiniteScroll'
import initialise_new_in_ridden_in_year_checkbox from './coaster_and_park_filters/initialise_new_in_ridden_in_year_checkbox'
import filterChange from './coaster_and_park_filters/filterChange'

export let infScroll = false
export let infiniteScrollEnabled = false
export let object = false

document.addEventListener("turbo:load", function (e) {
    let url = false

    if (document.body.classList.contains('parks-index') || document.body.classList.contains('coasters-index') || document.body.classList.contains('rides-index')) {
        // Figure out what type of object we have
        object = document.querySelector('.wacky-worm').getAttribute('data-object')

        // Initialise Coaster Attributes select box with SlimSelect for tag selection
        if (document.querySelector('.item-coaster_attributes')) {
            const slimSelect = new SlimSelect({
                select: '.item-coaster_attributes',
                placeholder: 'All attributes',
                allowDeselectOption: true
            })
        }

        // Enable Infinite Scroll on grid items
        if (!(infiniteScrollEnabled)) {
            startInfiniteScroll()
        }

        // If a year is selected then show the "New in <year>" checkbox automatically
        initialise_new_in_ridden_in_year_checkbox()

        filterChange('select.item-sort', 'sort', 'select-no-all-option')
        filterChange('[type="checkbox"][name="reverse"]', 'reverse', 'checkbox')
        filterChange('select.item-material', 'material', 'select')
        filterChange('select.item-countries', 'country', 'select')
        filterChange('[type="checkbox"][name="milestones"]', 'milestones', 'checkbox')
        filterChange('[type="checkbox"][name="soundtracks"]', 'soundtracks', 'checkbox')
        filterChange('select.item-parks', 'park', 'select')
        filterChange('select.item-manufacturers', 'manufacturer', 'select')
        filterChange('select.item-parkchains', 'parkchain', 'select')
        filterChange('[type="radio"][name="letter"]', 'letter', 'radio')
        filterChange('select.item-ridden-in', 'ridden_in', 'select')
        filterChange('[type="checkbox"][name="new_in_riy"]', 'new_in_riy', 'checkbox')
        filterChange('select.item-coaster_styles', 'coaster_styles', 'select')
        filterChange('select.item-coaster_attributes', 'coaster_attributes', 'select')
    }
})

filterChange.js:

import URI from 'urijs'
import endInfiniteScroll from './endInfiniteScroll'
import startInfiniteScroll from './startInfiniteScroll'
import updateGrid from './updateGrid'

export default function filterChange(selector, parameter, type) {
    let selected = false
    let url = false
    let new_in_ridden_in_year_element = false

    if (document.querySelector(selector)) {

        if (['checkbox', 'select', 'select-no-all-option'].includes(type)) {
            document.querySelector(selector).addEventListener('change', (e) => {
                url = new URI()

                // endInfiniteScroll()

                if (type == 'checkbox') {
                    if (e.target.checked) {
                        url.removeSearch(parameter)
                        url.addQuery(parameter, 1)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')
                    } else {
                        url.removeSearch(parameter)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')
                    }
                }

                if (type == 'select') {
                    if (selector == 'select.item-coaster_attributes') {
                        // Convert all of the options to an array
                        // Then, get an array of only the options that are selected
                        // Then, get an array of the selected option values
                        selected = Array.from(document.querySelector(selector).options).filter(function (option) {
                            return option.selected;
                        }).map(function (option) {
                            return option.value;
                        })

                        selected = selected.join('|');
                    } else {
                        selected = e.target.options[e.target.selectedIndex].value
                    }

                    if (parameter == 'ridden_in') {
                        new_in_ridden_in_year_element = document.querySelector('.item-new-in-ridden-in-year')

                        // Set New in RIY Checkbox back to unchecked
                        new_in_ridden_in_year_element.querySelector('[type="checkbox"]').checked = false

                        url.removeQuery('new_in_riy')
                    }

                    if (selected == 'all' || selected == '') {
                        if (parameter == 'ridden_in') {
                            new_in_ridden_in_year_element.style.visibility = 'hidden'
                        }

                        url.removeSearch(parameter)
                        url.suffix('json')
                    } else {
                        if (parameter == 'ridden_in') {
                            new_in_ridden_in_year_element.style.visibility = 'visible'
                            new_in_ridden_in_year_element.querySelector('.update-year-here').innerHTML = 'New in ' + selected
                        }

                        url.removeSearch(parameter)
                        url.addQuery(parameter, selected)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')
                    }
                }

                if (type == 'select-no-all-option') {
                    url.removeQuery('sort')
                    url.addQuery('sort', selected)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')
                }

                // Re-starting Infinite Scroll will be part of the updateGrid function
                // startInfiniteScroll()
                updateGrid(url)
            })
        } else if (['radio'].includes(type)) {
            document.querySelectorAll(selector).forEach(radio => {
                radio.addEventListener('change', (e) => {
                    selected = e.target.value
                    url = new URI()

                    console.log('something happened 2!')

                    // endInfiniteScroll()

                    if (selected == 'all') {
                        url.removeSearch(parameter)
                        url.suffix('json')
                    } else {
                        url.removeSearch(parameter)
                        url.addQuery(parameter, selected)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')
                    }

                    updateGrid(url)
                })
            })
        }
    }
}

startInfiniteScroll.js:

import InfiniteScroll from 'infinite-scroll'
import { object } from '../park-and-coaster-filters'
import { infScroll } from '../park-and-coaster-filters'
import { infiniteScrollEnabled } from '../park-and-coaster-filters'

export default function startInfiniteScroll() {
    console.log('start startInfiniteScroll')

    console.log(object)

    infScroll = new InfiniteScroll(`.${object}-grid`, {
        path: '.next_page a',
        append: `.${object}-top-level`,
        scrollThreshold: false,
        status: '.page-load-status',
        button: '.view-more-button',
        history: 'push'
    })

    console.log('end startInfiniteScroll')

    infiniteScrollEnabled = true
}

endInfiniteScroll.js:

import { infScroll } from '../park-and-coaster-filters'
import { infiniteScrollEnabled } from '../park-and-coaster-filters'

export default function endInfiniteScroll() {
    console.log('start endInfiniteScroll')

    if (infiniteScrollEnabled) {
        infScroll.destroy()

        infiniteScrollEnabled = false
    }

    console.log('end endInfiniteScroll')
}

I do have another file for my updateGrid function but not progressed far with it so I won’t post it.

Overall, I basically wish. to know if I am going down the right path with how I am splitting this up and one problem I can’t understand is, in my main file I have some variables infScroll, infiniteScrollEnabled, object etc. What is the best way to get access to these in the other functions? Do I export them, like I have above and then import them into my other files, or do I pass them as parameters into the function calls each time?

I can’t seem to figure out which way is best and the upsides and downsides of each.

Also, when running my script I am getting this error in my console:

Uncaught TypeError: Cannot set property infScroll of # which has only a getter at startInfiniteScroll

I’m really not sure as to what’s causing this but I “think” it may be the import of infScroll but I am not sure why and how I could resolve it.

I really hope someone can take a look through my scripts and questions and give me some pointers and advice. I have really struggled to figure out exactly what is confusing me and what questions to ask so I hope the above makes at least a bit of sense!

Many thanks in advance.

Neil

It looks like you’ve done a fairly decent job with refactoring the code.

I find that creating a suite of tests for each file has several benefits. The code ends up being more modular because testing helps to ensure that you don’t rely on global variables, and refactoring things further becomes really easy because the tests give you immediate feedback for if something goes wrong.

With infiniteScrollEnabled for example, how would you test the different behaviour? You would need to either pass that information into the function as a function parameter, or include it in an object that you can already access.

As you seem to already have an infScroll object, inside of that object seems to be a good place to store the infiniteScrollEnabled information.

Hi @Paul_Wilkins

Many thanks for your comment. What would you suggest for tests? I actually spoke with a friend yesterday who actually took a quick look at my code and answered a few questions I had. Mainly around the whole passing data between the new modules I’ve made. It turns out, where I was exporting and importing a variable. That won’t work. I ended up making a config object and exporting and importing that. That made everything much easier and I’ve at least now managed to get everything to work.

It’s not perfect, but I’m happy it’s tidier and at least working. I have some issues with history.pushstate but I have a GitHub issue opened where I am hoping to get some answers. See here

I will post all my new code for you to take a look where I am currently at. I like your idea of using the infScroll object to store the enabled/disabled state which would remove one extra bit I guess. I think the reason I created the extra var was just so it was distinctly different to the object that infiniteScroll creates.

Here’s all my updated code:

park-and-coaster-filters.js:

import config from './coaster_and_park_filters/config'
import URI from 'urijs'
import SlimSelect from 'slim-select'
import startInfiniteScroll from './coaster_and_park_filters/startInfiniteScroll'
import endInfiniteScroll from './coaster_and_park_filters/endInfiniteScroll'
import initialise_new_in_ridden_in_year_checkbox from './coaster_and_park_filters/initialise_new_in_ridden_in_year_checkbox'
import filterChange from './coaster_and_park_filters/filterChange'
import updateGrid from './coaster_and_park_filters/updateGrid'

document.addEventListener("turbo:load", function (e) {
    if (document.body.classList.contains('parks-index') || document.body.classList.contains('coasters-index') || document.body.classList.contains('rides-index')) {
        // Figure out what type of object we have
        config.object = document.querySelector('.wacky-worm').getAttribute('data-object')

        config.url = new URI()

        // Initialise Coaster Attributes select box with SlimSelect for tag selection
        if (document.querySelector('.item-coaster_attributes')) {
            const slimSelect = new SlimSelect({
                select: '.item-coaster_attributes',
                placeholder: 'All attributes',
                allowDeselectOption: true
            })
        }

        // Enable Infinite Scroll on grid items
        if (!(config.infiniteScrollEnabled)) {
            startInfiniteScroll()
        }

        // If a year is selected then show the "New in <year>" checkbox automatically
        initialise_new_in_ridden_in_year_checkbox()

        filterChange('select.item-sort', 'sort', 'select-no-all-option')
        filterChange('[type="checkbox"][name="reverse"]', 'reverse', 'checkbox')
        filterChange('select.item-material', 'material', 'select')
        filterChange('select.item-countries', 'country', 'select')
        filterChange('[type="checkbox"][name="milestones"]', 'milestones', 'checkbox')
        filterChange('[type="checkbox"][name="soundtracks"]', 'soundtracks', 'checkbox')
        filterChange('select.item-parks', 'park', 'select')
        filterChange('select.item-manufacturers', 'manufacturer', 'select')
        filterChange('select.item-parkchains', 'parkchain', 'select')
        filterChange('[type="radio"][name="letter"]', 'letter', 'radio')
        filterChange('select.item-ridden-in', 'ridden_in', 'select')
        filterChange('[type="checkbox"][name="new_in_riy"]', 'new_in_riy', 'checkbox')
        filterChange('select.item-coaster_styles', 'coaster_styles', 'select')
        filterChange('select.item-coaster_attributes', 'coaster_attributes', 'select')

        document.querySelector('.wacky-worm').addEventListener('click', (e) => {
            if (e.target.matches('.pagination li a')) {
                e.preventDefault()

                endInfiniteScroll()

                config.url = new URI(e.target.getAttribute('href'))
                config.url.suffix('json')

                updateGrid()
            }
        })
    }
})

initialise_new_in_ridden_in_year_checkbox.js:

import URI from 'urijs'
import config from './config'

export default function initialise_new_in_ridden_in_year_checkbox() {
    let url = config.url
    let queries = url.search(true)

    if (queries.page == 1) {
        url.removeSearch('page')
    }

    if (queries.ridden_in) {
        let year = queries.ridden_in
        let new_in_ridden_in_year_element = document.querySelector('.item-new-in-ridden-in-year')

        new_in_ridden_in_year_element.querySelector('.update-year-here').innerHTML = 'New in ' + year
        new_in_ridden_in_year_element.style.visibility = 'visible'
    }
}

config.js:

export default {
    infScroll: false,
    infiniteScrollEnabled:  false,
    url: false,
    object: false
}

filterChange.js:

import URI from 'urijs'
import endInfiniteScroll from './endInfiniteScroll'
import updateGrid from './updateGrid'
import config from './config'

export default function filterChange(selector, parameter, type) {
    let selected = false
    let new_in_ridden_in_year_element = false

    let url = config.url

    if (document.querySelector(selector)) {

        if (['checkbox', 'select', 'select-no-all-option'].includes(type)) {
            document.querySelector(selector).addEventListener('change', (e) => {

                if (config.infiniteScrollEnabled) {
                    endInfiniteScroll()
                }

                if (type == 'checkbox') {
                    if (e.target.checked) {
                        url.removeSearch(parameter)
                        url.addQuery(parameter, 1)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')
                    } else {
                        url.removeSearch(parameter)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')
                    }
                }

                if (type == 'select') {
                    if (selector == 'select.item-coaster_attributes') {
                        // Convert all of the options to an array
                        // Then, get an array of only the options that are selected
                        // Then, get an array of the selected option values
                        selected = Array.from(document.querySelector(selector).options).filter(function (option) {
                            return option.selected;
                        }).map(function (option) {
                            return option.value;
                        })

                        selected = selected.join('|');
                    } else {
                        selected = e.target.options[e.target.selectedIndex].value
                    }

                    if (parameter == 'ridden_in') {
                        new_in_ridden_in_year_element = document.querySelector('.item-new-in-ridden-in-year')

                        // Set New in RIY Checkbox back to unchecked
                        new_in_ridden_in_year_element.querySelector('[type="checkbox"]').checked = false

                        url.removeQuery('new_in_riy')
                    }

                    if (selected == 'all' || selected == '') {
                        if (parameter == 'ridden_in') {
                            new_in_ridden_in_year_element.style.visibility = 'hidden'
                        }

                        url.removeSearch(parameter)
                        url.suffix('json')
                    } else {
                        if (parameter == 'ridden_in') {
                            new_in_ridden_in_year_element.style.visibility = 'visible'
                            new_in_ridden_in_year_element.querySelector('.update-year-here').innerHTML = 'New in ' + selected
                        }

                        url.removeSearch(parameter)
                        url.addQuery(parameter, selected)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')
                    }
                }

                if (type == 'select-no-all-option') {
                    selected = e.target.options[e.target.selectedIndex].value

                    url.removeQuery(parameter)
                    url.addQuery(parameter, selected)
                    url.removeSearch('page')
                    url.addQuery('page', 1)
                    url.suffix('json')
                }

                updateGrid()
            })
        } else if (['radio'].includes(type)) {
            document.querySelectorAll(selector).forEach(radio => {
                radio.addEventListener('change', (e) => {
                    selected = e.target.value

                    if (config.infiniteScrollEnabled) {
                        endInfiniteScroll()
                    }

                    if (selected == 'all') {
                        url.removeSearch(parameter)
                        url.suffix('json')
                    } else {
                        url.removeSearch(parameter)
                        url.addQuery(parameter, selected)
                        url.removeSearch('page')
                        url.addQuery('page', 1)
                        url.suffix('json')
                    }

                    updateGrid()
                })
            })
        }
    }
}

startInfiniteScroll.js:

import InfiniteScroll from 'infinite-scroll'
import config from './config'

export default function startInfiniteScroll() {
    // console.log('start startInfiniteScroll')

    // console.log(config.infiniteScrollEnabled)

    if (document.querySelector('.next_page a')) {
        config.infScroll = new InfiniteScroll(`.${config.object}-grid`, {
            path: '.next_page a',
            append: `.${config.object}-top-level`,
            scrollThreshold: false,
            status: '.page-load-status',
            button: '.view-more-button',
            history: 'push'
        })

        config.infiniteScrollEnabled = true
    }

    // console.log(config.infiniteScrollEnabled)

    // console.log('end startInfiniteScroll')

    // infScroll.on('append', (body, path, items, response) => {
    //     console.log('append')
    //     // Refresh Modals
    //     // refreshModals()

    //     //Output navigator history
    //     console.log(navigator)

    //     console.log(navigator.history)

    //     console.log(Turbo)

    //     console.log(Turbo.navigator.history)

    //     // Manually push URL to the Turbolinks history
    //     // Turbo.navigator.history.push(path)

    //     console.log(path)
    // })
}

endInfiniteScroll.js:

import config from './config'

export default function endInfiniteScroll() {
    // console.log('start endInfiniteScroll')

    // console.log(config.infiniteScrollEnabled)

    if (config.infiniteScrollEnabled) {
        config.infScroll.destroy()

        config.infiniteScrollEnabled = false
    }

    // console.log(config.infiniteScrollEnabled)

    // console.log('end endInfiniteScroll')
}

toggleViewMoreButton.js:

export default function toggleViewMoreButton() {
    if (document.querySelector('.pagination')) {
        document.querySelector('.view-more-button').style.display = 'inline-flex'
    } else {
        document.querySelector('.view-more-button').style.display = 'none'
    }
}

updateGrid.js:

import Handlebars from 'handlebars'
import startInfiniteScroll from './startInfiniteScroll'
import toggleViewMoreButton from './toggleViewMoreButton'
import config from './config'

export default function updateGrid() {
    // console.log('start updateGrid')

    // console.log(config.url)

    document.querySelector(`.${config.object}-grid`).classList.add('is-loading')

    fetch(config.url)
        .then(response => response.json())
        .then(data => {
            let template = document.getElementById('park-and-coaster-grid-template').innerHTML
            let templateScript = Handlebars.compile(template)
            let html = templateScript(data)

            document.querySelector('.wacky-worm').innerHTML = html

            document.querySelector(`.${config.object}-grid`).classList.remove('is-loading')

            // Re enable InfiniteScroll
            if (!(config.infiniteScrollEnabled)) {
                startInfiniteScroll()
            }

            // Refresh Modals
            // refreshModals()

            // Show or Hide View More button
            toggleViewMoreButton()
        })

    // Add current filter settings to URL history (Remove page query if on first page)
    let queries = config.url.search(true)
    if (queries.page == 1) {
        config.url.removeSearch('page')
    }

    config.url.suffix('')

    history.pushState({}, '', config.url)

    // console.log('end updateGrid')
}

Thanks Again Paul.

Neil

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.