Skip to content

History back - page restoration fails for nested ionic lists #3466

@Gleek

Description

@Gleek

Hello,
I'm currently experimenting with ionic and htmx and facing an issue with history restore for ionic lists that are nested in some other ionic webcomponent.
I'm not sure if it's an issue with WebComponents in general or something special that ionic is doing.
I'm attaching steps to reproduce and a possible fix.

Replication

  1. Create two files:
  • page1.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>HTMX Ionic History Issue</title>
    <!-- Ionic CSS and JS -->
    <link href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" rel="stylesheet" />
    <script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
    <script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>

    <!-- HTMX -->
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/htmx.min.js"></script>

    <style>
      body {padding: 20px;}
      #main-content {
        border: 1px dashed #aaa;
        padding: 20px;
        min-height: 200px;
        margin-top: 20px;
      }
      ion-card {margin-top: 20px;}
      nav {margin-top: 30px;}
    </style>
  </head>
  <body>
    <h1>Home Page (with Ionic List)</h1>

    <div id="main-content" hx-preserve-attrs="class">
      <h2>Ionic Section (Expected: Content missing after history back)</h2>
      <ion-card>
        <ion-card-content>
          <ion-list>
            <ion-item button href="#">
              <ion-label>Item 1</ion-label>
            </ion-item>
            <ion-item button href="#">
              <ion-label>Item 2</ion-label>
            </ion-item>
            <ion-item button href="#">
              <ion-label>Item 3</ion-label>
            </ion-item>
          </ion-list>
        </ion-card-content>
      </ion-card>
    </div>
    <nav hx-boost="true">
      <a href="page2.html">Go to Another Page</a>
    </nav>
  </body>
</html>
  • page2.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Another Page</title>
</head>
<body>
    <h1>Another Page Content</h1>
    <p>This content was loaded via HTMX boosting from index.html.</p>
    <p>Now, press your browser's **BACK button** to return to the previous page and observe its restored state.</p>
</body>
</html>
  1. Run a http server in the same directory (Eg: python -m http.server 6060
  2. Open localhost:6060
  3. Notice the 3 items in the list
  4. Click the "Go to Another Page" link and press back
  5. Notice the list items are gone

Possible fix

On debugging this, I realised that htmx uses cloneNode to save the document in history:

htmx/src/htmx.js

Line 3235 in 0da136f

const clone = /** @type Element */ (elt.cloneNode(true))

Replacing this line with:

const clone = /** @type Element */ getDocument().importNode(elt, true)

seems to fix the issue.

Further considerations

I'm unsure on the cause for the original issue and the internal differences of importNode and cloneNode that is fixing it.

A few things that I noticed though:
Doing:

document.body.children[1].children[1].children[0].children.length

Should give 1 as output (ion-list element) but gives 0 instead.
though doing:

document.body.children[1].children[1].children[0].querySelector(':scope > *') 

Does return me the ion-list element

They querySelector line fails on the cloned element (with cloneNode) but works on the element returned by importNode
Console screenshot:
Image

So there is definitely some implementation difference between both these functions that is the cause.
While MDN suggests (here):

To clone a node to insert into a different document, use Document.importNode() instead.

I couldn't find a suitable explanation for this behaviour.

Someone, with deeper insights into these APIs would be able to better comment

Regards.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions