How to add Alpine directives to markup that you can't control

by philw_

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/2.8.0/alpine.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.2/tailwind.min.css">
<p>What if you had a block of HTML that you wanted to use Alpine with, but you can't edit the source of the HTML?</p>
 <p>Maybe it's coming from a CMS or external service that you don't control, but you still want to avoid the boilerplate of writing event listeners and reactive state handling yourself?</p>
<div class="cms-nav" id="cms-nav">
  <ul>
    <li><a href="/program-hub/">Program Hub</a><button>▼</button>
      <ul class="multilevel-linkul-0">
        <li><a href="/program-hub/undergraduate-programs/">Undergraduate Programs</a></li>
        <li><a href="/program-hub/postgraduate-programs/">Postgraduate Programs</a></li>
        <li><a href="/program-hub/program-search/">Program Search</a></li>
        <li><a href="/program-hub/program-search/program-compare/">Program Compare</a></li>
        <li><a href="/program-hub/program-catalog-builder/">Program Catalog Builder</a></li>

      </ul>

    </li>
    <li><a href="/life-at-t4u/">Life at TXU</a><button>▼</button>
      <ul class="multilevel-linkul-0">
        <li><a href="/life-at-txu/campuses/">Campuses</a>
        </li>
        <li><a href="/life-at-txu/faq/">FAQ</a></li>
        <li><a href="/life-at-txu/student-support/">Student Support</a></li>
        <li><a href="/life-at-txu/blog/">Blog</a></li>

      </ul>

    </li>
    <li><a href="/research/">Research</a><button>▼</button>
      <ul class="multilevel-linkul-0">
        <li><a href="/research/staff-directory/">Staff Directory</a></li>
      </ul>

    </li>
    <li><a href="/alumni/">Alumni</a><button>▼</button>
      <ul class="multilevel-linkul-0">
        <li><a href="/alumni/events/">Events</a>


        </li>
        <li><a href="/alumni/newsroom/">Newsroom</a>


        </li>

      </ul>

    </li>
    <li><a...

SCSS

.cms-nav {
  a {
    text-decoration: underline;
  }
  
  li {
    display: block;
  }
  
  &> ul {
    display: flex;
    
    button {
      margin-left: 1em;
    }
  }
  
  &> ul > li {
    padding: 1em;
    background-color: #ebebeb;
    margin-bottom: 0.5em;
  }
  
  ul ul {
    margin-top: 1em;
  }
}

JavaScript

// 1) This is where we use JavaScript to make an Alpine component dynamically.
const nav = document.querySelector('#cms-nav > ul');
const topLevelLi = document.querySelectorAll('#cms-nav > ul > li');
const topLevelButton = document.querySelectorAll('#cms-nav > ul > li > button');
const topLevelLinks = document.querySelectorAll('#cms-nav > ul > li > a');
const secondLevelUl = document.querySelectorAll('#cms-nav > ul > li > ul');

topLevelLi.forEach((elem, index) => {
  elem.setAttribute('x-on:mouseenter', `toggleMenu(${index})`);
  elem.setAttribute('x-on:mouseleave', `closeMenu()`);
});

topLevelButton.forEach((elem, index) => {
  elem.setAttribute('x-on:click', `toggleMenu(${index})`);
});


topLevelLinks.forEach((elem, index) => {
  elem.setAttribute('x-bind:aria-expanded', `expanded(${index})`);
});

secondLevelUl.forEach((elem, index) => {
  elem.setAttribute('x-show', `menuOpen === ${index}`);
  elem.style.display = 'none';
});

nav.setAttribute('x-on:click.away', 'escape()');
nav.setAttribute('x-on:keydown.escape.window', 'escape()')
// Adding x-data makes it into an Alpine app again thanks to the Alpine.js
// MutationObserver that detects any DOM changes on the page.
nav.setAttribute('x-data', 'subNavigation()');

// 2) This is the previous Alpine code.
function subNavigation() {
  return {
    timer: false,
    menuOpen: false,
    dropdownButton: false,

    escape(key = null) {
      if (key && key !== 'Escape') return;
      this.changeMenuState(false);
      this.dropdownButton = false;
      this.timer = false;
    },

    focus(index) {
      this.dropdownButton = index;
      this.changeMenuState(false);
    },

    toggleMenu(index) {
      const state = this.menuOpen === index ? false : index;
      this.changeMenuState(state);
    },

    openMenu(index) {
      clearTimeout(this.timer);
      this.changeMenuState(index);
    },

    closeMenu() {
      this.changeMenuState(false);
    },

    expanded(index) {
      return this.menuOpen ===...