Skip to content

Instantly share code, notes, and snippets.

@zGrav
Created June 17, 2020 09:10
Show Gist options
  • Select an option

  • Save zGrav/883a4ff3613580da6beb204d3e0f1a4b to your computer and use it in GitHub Desktop.

Select an option

Save zGrav/883a4ff3613580da6beb204d3e0f1a4b to your computer and use it in GitHub Desktop.

Revisions

  1. zGrav created this gist Jun 17, 2020.
    122 changes: 122 additions & 0 deletions eventlistener.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,122 @@
    useEffect(() => {
    if (!isSidebarOpen) {
    removeAllListeners(document.body, "click");
    } else {
    addListener(document.body, "click", (event: MouseEvent) =>
    handleBodyClick(event, "sidebar", setIsSidebarOpen)
    );
    }

    if (localStorage) {
    localStorage.setItem("sidebar-open", isSidebarOpen.toString());
    }
    }, [isSidebarOpen]);

    const eventHandlers = {};

    export const addListener = (
    node: HTMLElement | Node,
    event: string,
    handler,
    capture: boolean = false
    ) => {
    // checks if specific event is stored in our obj
    // otherwise create it
    if (!(event in eventHandlers)) {
    eventHandlers[event] = [];
    }

    // update eventHandlers
    // and in here we track the events
    // and their nodes.
    // we cannot use the node itself as Object keys,
    // as they'd get coerced into a string
    eventHandlers[event].push({
    node,
    handler,
    capture
    });

    node.addEventListener(event, handler, capture);

    return true;
    };

    export const removeAllListeners = (
    targetNode: HTMLElement | Node,
    event: string
    ) => {
    // if we don't have this event stored, we can skip
    if (!(event in eventHandlers)) {
    return false;
    }

    // remove listeners from the matching nodes
    eventHandlers[event]
    .filter(({ node }) => node === targetNode)
    .forEach(({ node, handler, capture }) =>
    node.removeEventListener(event, handler, capture)
    );

    // update eventHandlers
    eventHandlers[event] = eventHandlers[event].filter(
    ({ node }) => node !== targetNode
    );

    return true;
    };

    // handles outside of elements clicks (closes sidebar/dropdowns)
    export const handleBodyClick = (
    event: MouseEvent,
    type: string,
    action: (visibility: boolean) => void
    ) => {
    if (!event) {
    return false;
    }

    // since it's event-based and all...
    event.preventDefault();

    // these id's can also include buttons
    // so be careful:)
    const playerProfileId = "player-profile-";
    const questsId = "quests-";
    const sidebarId = "sidebar";

    // gets objects on which event listeners will be invoked
    const paths = event.composedPath();

    // let's trickle down the array
    const found = paths.filter(path => {
    // this is actually a HTMLElement so map it as so
    const p = path as HTMLElement;

    // if we got one of the HTMLElement
    // with a certain id we return true
    // since we are filtering, falses get dropped.
    if (p.id && typeof p.id === "string") {
    if (
    p.id.indexOf(playerProfileId) > -1 ||
    p.id.indexOf(questsId) > -1 ||
    p.id.indexOf(sidebarId) > -1
    ) {
    return true;
    }
    }

    return false;
    });

    // if we found no reference to our IDs
    // (aka clicked outside of it or it's children)
    // we trigger the hide action
    if (found.length === 0) {
    if (type === "player" || type === "quest" || type === "sidebar") {
    action(false);
    }
    }

    return true;
    };