hotwired / stimulus

A modest JavaScript framework for the HTML you already have
https://stimulus.hotwired.dev/
MIT License
12.67k stars 420 forks source link

How to handle portals? #73

Closed aar0nr closed 6 years ago

aar0nr commented 6 years ago

How can you render a child element of a controller into a different part of the DOM but delegate the events back to the original controller. This is the portal concept from React.

This is a common pattern with dropdowns, popovers, modals, etc. I'm not sure how to go about handling this scenario in stimulus.

For example, let's say your building a Popover, but you want to render the popover content in the <body> element so it's displayed over all other elements.

<body>
  <div data-controller="popover">
    <button data-action="click->popover#open">Open</button>
    <div data-target="popover.content" hidden>
      <div data-action="click->popover#handleClick">
        click me!
      </div>
    </div>
  </div>

  <!-- 
  when the popover button is clicked, you would copy the popover content
  and insert it into the DOM here, but you would still want the `#handleClick` action
  to trigger on the original controller.
  -->
  <div data="click->popover#handleClick">
     click me!
   </div>
</body>

Maybe there is some scope/context trickery that can be done to create some portal-type instance of a controller element, but delegate actions to a different controller? For example:

<body>
  <div data-controller="portal">
    <div data-controller="popover">
      <button data-action="click->popover#open">Open</button>
      <div data-target="portal.content" hidden>
        <div data-action="click->portal#delegate" data-to="popover#handleClick">
          click me!
        </div>
      </div>
    </div>

    <!--
    the portal controller could create a copy element for each portal.content
    it finds, then delegate to the proper controller somehow?
    -->
    <div data-action="click->portal#delegate" data-to="popover#handleClick">
      click me!
    </div>
  </div>
</body>

Have you guys used this pattern for anything on Basecamp? Any tips/ideas?

javan commented 6 years ago

We don't have direct support for that, but see https://github.com/stimulusjs/stimulus/issues/35 for a similar discussion and possible solutions.

For example, let's say your building a Popover, but you want to render the popover content in the <body> element so it's displayed over all other elements.

In your case, you could try using <body> as your controller element:

<body data-controller="popover">
  <div>
    <p>
      <button data-action="click->popover#open">Open</button>
    </p>
  </div>

  …

  <div data-target="popover.content" hidden>
    …
  </div>
</body>

Feel free to continue the discussing in https://github.com/stimulusjs/stimulus/issues/35.