Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/htmx.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,13 @@ export interface Htmx {
* Equivalent to listening for `htmx:after:process`.
*/
onLoad(callback: (elt: Element) => void): void;
/**
* Sets up history handling and processes `document.body`.
* Called automatically on `DOMContentLoaded` (or next tick if the document is already loaded).
* Safe to call multiple times — history listeners are only registered once.
* Call manually when loading htmx asynchronously or after a streaming response delivers the full page.
*/
initialize(): void;
/**
* Initialize htmx attributes on `root` and all its descendants.
* When `force` is `true`, tears down and re-initializes already-processed elements —
Expand Down
38 changes: 18 additions & 20 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ var htmx = (() => {
#hxOnQuery
#transitionQueue
#historyAbort
#historyInitialized
#processingTransition

constructor() {
Expand Down Expand Up @@ -185,14 +186,10 @@ var htmx = (() => {
triggerHtmxEvent: this.__trigger.bind(this),
executeJavaScript: this.__executeJavaScript.bind(this)
};
let init = () => {
this.__initHistoryHandling()
this.process(document.body)
};
let init = () => this.initialize();
if (document.readyState === 'loading') {
document.addEventListener("DOMContentLoaded", init)
} else {
// wait a tick so extensions can register
setTimeout(init)
}
}
Expand Down Expand Up @@ -1256,6 +1253,22 @@ var htmx = (() => {
// Public JS API
//============================================================================================

initialize() {
if (this.config.history && !this.#historyInitialized) {
this.#historyInitialized = true;
if (!history.state) history.replaceState({htmx: true}, '', location.href);
if (window.navigation && !/firefox/i.test(navigator.userAgent)) {
navigation.addEventListener('navigate', (event) => {
if (event.navigationType === 'traverse' && event.canIntercept && !event.hashChange)
event.intercept({handler: () => this.__restoreHistory()});
});
} else {
window.addEventListener('popstate', (event) => this.__restoreHistory(event.state));
}
}
this.process(document.body);
}

async swap(ctx) {
try {
this.__handleHistoryUpdate(ctx);
Expand Down Expand Up @@ -1617,21 +1630,6 @@ var htmx = (() => {
// History Support
//============================================================================================

__initHistoryHandling() {
if (!this.config.history) return;
if (!history.state) {
history.replaceState({htmx: true}, '', location.href);
}
if (window.navigation && !/firefox/i.test(navigator.userAgent)) {
navigation.addEventListener('navigate', (event) => {
if (event.navigationType === 'traverse' && event.canIntercept && !event.hashChange)
event.intercept({handler: () => this.__restoreHistory()});
});
} else {
window.addEventListener('popstate', (event) => this.__restoreHistory(event.state));
}
}

__pushUrlIntoHistory(path) {
if (!this.config.history) return;
if (!history.state) history.replaceState({htmx: true}, '', location.href);
Expand Down
1 change: 1 addition & 0 deletions test/tests/unit/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ describe('bootstrap unit tests', function() {
'ajax',
'find',
'findAll',
'initialize',
'on',
'onLoad',
'parseInterval',
Expand Down
33 changes: 33 additions & 0 deletions test/tests/unit/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,37 @@ describe('process() unit tests', function() {
assert.equal(counts['c'], 1, 'child cleaned once')
})

});

describe('initialize() unit tests', function() {

beforeEach(function() {
setupTest();
});

afterEach(function() {
cleanupTest();
});

it('initialize() processes document.body', function() {
let btn = createProcessedHTML('<button hx-get="/test"></button>')
btn.removeAttribute('data-htmx-powered')
delete btn._htmx
htmx.initialize()
assert.isTrue(btn.hasAttribute('data-htmx-powered'))
})

it('initialize() is idempotent - calling twice does not double-register history listeners', function() {
let count = 0
let orig = window.addEventListener
window.addEventListener = function(type, ...args) {
if (type === 'popstate') count++
return orig.call(this, type, ...args)
}
htmx.initialize()
htmx.initialize()
window.addEventListener = orig
assert.isAtMost(count, 1, 'popstate listener registered more than once')
})

});
38 changes: 38 additions & 0 deletions www/src/content/reference/05-methods/10-htmx-initialize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: "htmx.initialize()"
description: "Manually initializes htmx"
---

The `htmx.initialize()` function sets up htmx history handling and processes `document.body`. It is called automatically on `DOMContentLoaded` (or on the next tick if the document is already loaded), but can be called manually when htmx is loaded asynchronously or after a streaming response has delivered the full page.

## Syntax

```javascript
htmx.initialize()
```

## Parameters

None.

## Usage

Call after loading htmx asynchronously to ensure history handling and element initialization run at the right time:

```javascript
import('https://cdn.jsdelivr.net/npm/htmx.org/dist/htmx.min.js').then(() => {
htmx.initialize();
});
```

Call from a streaming extension after the full page has arrived:

```javascript
// inside an hx-streaming extension handler
htmx.initialize();
```

## Notes

* Safe to call multiple times — history listeners are only registered once
* Equivalent to setting up history handling then calling [`htmx.process(document.body)`](/reference/methods/htmx-process)
Loading