Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@storybook/html 5.0.1 can't run scripts on story load, aka 'DOMContentLoaded' doesn't help anymore #6113

Closed
BennyZ28 opened this issue Mar 15, 2019 · 10 comments

Comments

@BennyZ28
Copy link

Hi folks! So, here's my situation: some of my components require javascript. I can successfully call the javascript file in preview-head.html but, initialising it is a problem.

Before v5.0.1 I used to have:

document.addEventListener("DOMContentLoaded", function(event) {
        /* The stuff I needed to initialise */
    }, false);

and it worked, but now it doesn't.

I've been looking all over your docs and Stackoverflow itself but I can't find a solution to this. It feels like it should be something Storybook should be keeping in consideration.

Any help would be appreciated.

@BennyZ28
Copy link
Author

Nevermind, solved

@shilman
Copy link
Member

shilman commented Mar 15, 2019

@BennyZ28 what was the solution?

@shilman shilman added this to the 5.0.x milestone Mar 15, 2019
@BennyZ28 BennyZ28 reopened this Mar 15, 2019
@BennyZ28
Copy link
Author

BennyZ28 commented Mar 15, 2019

@shilman Actually I haven’t solved it, I throught I had after too many hours in front of the screen but I was wrong.

So, any help still appreciated!

@d4nyll
Copy link

d4nyll commented Mar 18, 2019

Whenever the 'page' changes, the old div#root element inside the iframe is removed and a new one is added. You can use this feature and combine it with MutationObserver to run a function each time the page changes.

Inside your preview-head.html, you can add:

function runOnInit() {
  console.log('Init')
}
function runOnPageChange() {
  console.log('Page has changed!')
}
document.addEventListener('DOMContentLoaded', function() {
  runOnInit();
  const callback = function(mutationsList) {
    for (let i = 0, len = mutationsList.length; i < len; i++) {
      if (mutationsList[i].type == 'childList') {
        runOnPageChange();
        break;
      }
    }
  };

  const observer = new MutationObserver(callback);
  const config = { childList: true, subtree: false };
  observer.observe(document.getElementById('root'), config);
}, false);

To understand how MutationObserver works, please refer to the MDN documentation.

@BennyZ28
Copy link
Author

🙏

@vipcxj
Copy link

vipcxj commented Apr 4, 2020

@BennyZ28 what was the solution?

The workaround is too complex, why not storybook provide a callback just called after the story is added to the dom tree?

@MartinDawson
Copy link

MartinDawson commented Aug 4, 2021

Still not fixed properly: #15753

@JoshuwayMorris
Copy link

JoshuwayMorris commented Jul 13, 2022

Hey there!
I encountered this issue earlier today and thought I would share the fix that worked for me.

I include a separate JavaScript file into Storybook for my HTML components. This JavaScript file listens for DOMContentLoaded events before executing particular code.

I did not want to have to rewrite my frontend JavaScript code to be able to use it with Storybook, so I decided to adapt the code from d4nyll to dispatch a DOMContentLoaded event every time Storybook changes.

Essentially, the snippet does the following:

  1. Adds an event listener for DOMContentLoaded to detect first-page load
  2. Once the page has loaded, create a mutation observer to watch for Storybook changes
  3. Once the mutation observer has been created, I remove the event listener for DOMContentLoaded
  4. When a mutation is "detected", I then fire a DOMContentLoaded event manually which re-runs my separate frontend JavaScript code.

Hopefully, it might help someone else. Thanks!

Place the following script inside of preview-head.html in your .storybook directory.

<script>

  // On page change, fire DOMContentLoaded event
  function pageChange() {

    // Dispatch DOMContentLoaded event
    window.document.dispatchEvent(new Event('DOMContentLoaded', {
      bubbles: true,
      cancelable: true
    }));
  }

  function watchForChanges() {
    const mutationObserver = function(mutationsList) {
      for (let i = 0, len = mutationsList.length; i < len; i++) {
        if (mutationsList[i].type == 'childList') {
          pageChange();
          break;
        }
      }
    };

    const observer = new MutationObserver(mutationObserver);
    const config = { childList: true, subtree: true };
    observer.observe(document.getElementById('docs-root'), config);

    // Remove event listener, otherwise we will re-run this function when we fire our manual event
    document.removeEventListener('DOMContentLoaded', watchForChanges);
  }

  // Initially create mutation observer
  document.addEventListener('DOMContentLoaded', watchForChanges);
</script>

Edit

I discovered after posting this solution, that when changing "Stories" within a "Component" that I was accidentally adding an event listener every time to my button (as part of the DOMContentLoaded event in my separate JavaScript file). I solved this by implementing the following in my separate JavaScript file that I want to include in Storybook (required by my components):

// On button click
function onButtonClick(event) {
  console.log('Button Clicked!', event)
}

// Wait for document load
document.addEventListener('DOMContentLoaded', () => {
  
  // Retrieve all buttons
  const buttons = document.querySelectorAll('button');

  // Loop all button elements
  for (let i = 0; i < buttons.length; i += 1) {

    // Remove any existing `onButtonClick` event listeners
    buttons[i].removeEventListener('click', onButtonClick);

    // Add click event listener
    buttons[i].addEventListener('click', onButtonClick);
  }
});

The key here is the .removeEventListener that removes any existing listeners for our function onButtonClick.

A slight rewrite to my frontend assets, but I can live with that!

@Bilge
Copy link

Bilge commented Jan 27, 2023

Is this really still state of the art? WTF?

@James-Wilkinson-git
Copy link

Trying your approach @JoshuwayMorris but on an AutoDocs page it causes chrome to hang, also in Storybook 8 there is no docs-root, is this suppose to be the storybook frame, or the previews frame?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants