Call action in another controller

Hi,

Here is one of my controllers:

import { Controller } from "@hotwired/stimulus"
import InfiniteScroll from 'infinite-scroll'

export default class extends Controller {
    static get targets() {
        return ["next", "grid", "footer", "infinitescrollelement"]
    }

    connect() {
        let infScroll;

        if (this.hasNextTarget) {
            infScroll = new InfiniteScroll(this.gridTarget, {
                path: '.next_page a',
                append: '[data-infinitescroll-target="infinitescrollelement"]',
                // append: `.${this.data.get("object")}-top-level`,
                scrollThreshold: false,
                status: '.page-load-status',
                button: '.view-more-button'
            })

            this.footerTarget.querySelector('.view-more-button').style.display = 'inline-flex'
        } else {
            this.footerTarget.querySelector('.view-more-button').style.display = 'none'
        }

        // When new content is appended, re-layout the gallery to ensure new photos position correctly
        ***infScroll.on('append', (event, response, path, items) => {
            ***layoutGallery(galleryElement)
        ***})
    }
}

The three lines that start with *** are where my problem is.

Basically, when new content is appended by Infinite Scroll, I need an action in my Gallery Controller to be run. How can I do this? It can’t be run at the same time, it must only be run when that infinite scroll event is called.

Any ideas?

You’ll want to dispatch a custom event in this controller. You’ll want to listen for that custom event elsewhere (it will be the “trigger” (for the Stimulus action) that will call the other controller).

So (1) dispatch event; (2) listen for event in HTML element and have that call the action you want to call.

@lordofthedanse How can I listen for a InfiniteScroll event though?

I saw the example of dispatch in the docs but I really didn’t understand it.

yourCustomEventName@window->yourController#yourAction

@lordofthedanse

I’m really sorry but i’m just not getting this.

Ok, my gallery controller is:

import { Controller } from "@hotwired/stimulus"
import InfiniteScroll from 'infinite-scroll'

export default class extends Controller {
    static get targets() {
        return ["next", "grid", "footer"]
    }

    connect() {
        let infScroll;
        let el = this;

        console.log(`.${this.gridTarget.className} .infinite-scroll-element`);

        if (this.hasNextTarget) {
            infScroll = new InfiniteScroll(this.gridTarget, {
                path: '.next_page a',
                append: `.${this.gridTarget.className} .infinite-scroll-element`,
                scrollThreshold: false,
                status: '.page-load-status',
                button: '.view-more-button'
            })

            this.footerTarget.querySelector('.view-more-button').style.display = 'inline-flex'

            // When new content is appended, re-layout the gallery to ensure new photos position correctly
            infScroll.on('append', (event, response, path, items) => {
                console.log('append happened');
                // I NEED TO RUN GALLERY#LAYOUT WHEN THIS APPEND EVENT HAPPENS
            })
        } else {
            this.footerTarget.querySelector('.view-more-button').style.display = 'none'
        }
    }

    append() {
        this.dispatch("append");
    }
}

My Infinitescroll controller is:

import { Controller } from "@hotwired/stimulus"
import InfiniteScroll from 'infinite-scroll'

export default class extends Controller {
    static get targets() {
        return ["next", "grid", "footer"]
    }

    connect() {
        let infScroll;
        let el = this;

        console.log(`.${this.gridTarget.className} .infinite-scroll-element`);

        if (this.hasNextTarget) {
            infScroll = new InfiniteScroll(this.gridTarget, {
                path: '.next_page a',
                append: `.${this.gridTarget.className} .infinite-scroll-element`,
                scrollThreshold: false,
                status: '.page-load-status',
                button: '.view-more-button'
            })

            this.footerTarget.querySelector('.view-more-button').style.display = 'inline-flex'

            // When new content is appended, re-layout the gallery to ensure new photos position correctly
            infScroll.on('append', (event, response, path, items) => {
                console.log('append happened');
            })
        } else {
            this.footerTarget.querySelector('.view-more-button').style.display = 'none'
        }
    }

    append() {
        this.dispatch("append");
    }
}

I also added an action to my html like so:

data-infinitescroll-action="append@window->gallery#layout"

Please can you, or someone else, take a look and help me out.

That should just be data-action= "append@window->gallery#layout" and it needs to be placed on (or within the scope of) your gallery controller’s element. The @window part of the declaration ensures it will be called when the event bubbles up to the window, regardless of the location of the event’s target element.

Yeah, I’m pretty sure you also copied dispatch() from somewhere (stimulus-use, perhaps?) — the vanilla JS function is dispatchEvent(), so … dispatch() probably isn’t working either unless you pulled in the relevant dependency (which there’s no need for just to dispatch an event).

Stimulus controllers actually do have a dispatch function for delivering custom events. It takes the name of the custom event you want created and an optional detail object as arguments.

Not sure when that feature was added, but I just discovered it myself a few months ago…

This is what makes forums so lovely; thanks for the tip @OutlawAndy — I’ll have to take a peek.

1 Like

Instead of using the InfiniteScroll append event, you might also explore the [name]TargetConnected lifecycle callback method on the Gallery controller.

By doing this, you uncouple your Gallery controller from the source of the DOM modifications. If you introduce new gallery elements via another system or if you switch to another library for infinite scroll, you won’t have to change that part. Your Gallery will just react to DOM changes, whatever the source of those changes.