Persisting elements between page changes

A third-party script I want to use places an element in the body of the page and relies on that element in order to work. However, the element gets toasted on page changes after the initial load.

I tried adding data-turbo-permanent to it, but as I understand it, that only works for elements of the same ID that exist between two pages. Since the widget was added async, subsequent page requests will not have the widget in the body, so there is no matching element to persist.

Is there any way to tell Turbo to leave certain elements in the document if they exist?

I’d use this overall approach:

  1. before the page changes, capture the objects you want persisted
  2. after the page changes, put them back manually where you’d like them

There’s probably a combination of a Stimulus controller and Turbo’s lifecycle events in there that will work.

<!-- on the body, wrap a stimulus controller -->
<div data-controller="name-of-third-party"
     data-action="turbo:before-visit->name-of-third-party#persist">
</div>
// name_of_third_party_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "instance" ]
  
  connect() {
    this.restoreInstances()
  }

  persist() {
    // save to window.nameOfThirdPartyInstances
  }

  restoreInstances() {
    // read from window.nameOfThirdPartyInstances to add back in place
    // cleanup 
  }
}

There’s a caveat. If the third-party widget adds its own interactivity (ie. it creates its own event listeners), the copy of that element will lose all those event listeners and will need to be re-initialized. It gets hairy from there, depending on how the third-party widget was coded and whether it can export/import its state.