Martin Paul Eve bio photo

Martin Paul Eve

Professor of Literature, Technology and Publishing at Birkbeck, University of London

Email Books Twitter Github Stackoverflow MLA CORE Institutional Repo Hypothes.is ORCID ID  ORCID iD Wikipedia Pictures for Re-Use

As part of the translation platform we’re building, I needed to implement the following workflow:

  • If the DOM has been modified previously, then restore the DOM and run the substitution function.
  • If the DOM hasn’t been modified, then just run the substitution function.

The problem was that whenever I ran $(“body”).html(this.original_document); in the first of these cases, the javascript would stop executing. I think this is because the object that triggered the event was destroyed, even though the script was in the head tag.

In other words, whenever I replace the body tag of the HTML, my javascript immediately stops and I can’t call the next function. I also tried using setTimeout to somehow defer this into a space that wouldn’t be picked up by garbage collection or anything else.

I eventually worked out that I can solve this problem by using DOM Mutation Observers. The first thing I do, now, when the page loads is setup a DOM Mutation Observer thus (I’m using a mixture of coffeescript and javascript here):

this.continue_action = ""
`
var target = document.body;
var observer = new MutationObserver(this.handle_state_continuity);
var config = { attributes: true, childList: true, characterData: true };

observer.observe(target, config);
`

This basically says call the handle_state_continuity function whenever the contents of the body changes at any depth, in any way. Inside the event-firing method, I then have:

  startSubstitution: (event = {}) =>
    if this.original_document == ""
      this.original_document = `$("body").html()`

    if this.original_document != `$("body").html()`
      this.continue_action = "substitute"
      console.log("Annotran: Restoring DOM to original state.")
      `
      $("body").html(this.original_document);
      `
      return null
    else
      console.log("Annotran: Starting substitution directly.")
      this.makeSubstitution(event)

The important part to note here is that, in the case where it changes the body html, it first preserves the next action: this.continue_action = “substitute”.

The final part needed to glue this all together is the handle_state_continuity function, which for me looks like this:

  handle_state_continuity: (mutation = null) =>
    if this.continue_action != ""
      this.continue_action = ""
      console.log("Annotran: Starting substitution via state machine.")
      this.makeSubstitution(null)

And, tada! Now, when startSubstitution is called twice, the console output is:

Annotran: Starting substitution directly. [first time] Annotran: Restoring DOM to original state. [second time] Annotran: Starting substitution via state machine.