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
25 changes: 12 additions & 13 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -4744,9 +4744,13 @@ var htmx = (function() {
const requestPath = responseInfo.pathInfo.finalRequestPath
const responsePath = responseInfo.pathInfo.responsePath

const pushUrl = responseInfo.etc.push || getClosestAttributeValue(elt, 'hx-push-url')
let replaceUrl = responseInfo.etc.replace || getClosestAttributeValue(elt, 'hx-replace-url')
if (replaceUrl === 'false') replaceUrl = null
let pushUrl = responseInfo.etc.push ?? getClosestAttributeValue(elt, 'hx-push-url')
let replaceUrl = responseInfo.etc.replace ?? getClosestAttributeValue(elt, 'hx-replace-url')

const pushDisabled = pushUrl === 'false' || pushUrl === false
if (pushDisabled) pushUrl = null

if (replaceUrl === 'false' || replaceUrl === false) replaceUrl = null
const elementIsBoosted = getInternalData(elt).boosted

let saveType = null
Expand All @@ -4758,17 +4762,12 @@ var htmx = (function() {
} else if (replaceUrl) {
saveType = 'replace'
path = replaceUrl
} else if (elementIsBoosted) {
} else if (elementIsBoosted && !pushDisabled) {
saveType = 'push'
path = responsePath || requestPath // if there is no response path, go with the original request path
}

if (path) {
// false indicates no push, return empty object
if (path === 'false') {
return {}
}

// true indicates we want to follow wherever the server ended up sending us
if (path === 'true') {
path = responsePath || requestPath // if there is no response path, go with the original request path
Expand Down Expand Up @@ -5293,8 +5292,8 @@ var htmx = (function() {
* @property {Object|FormData} [values]
* @property {Record<string,string>} [headers]
* @property {string} [select]
* @property {string} [push]
* @property {string} [replace]
* @property {string|false} [push]
* @property {string|false} [replace]
* @property {string} [selectOOB]
*/

Expand Down Expand Up @@ -5342,8 +5341,8 @@ var htmx = (function() {
* @property {Object|FormData} [values]
* @property {boolean} [credentials]
* @property {number} [timeout]
* @property {string} [push]
* @property {string} [replace]
* @property {string|false} [push]
* @property {string|false} [replace]
* @property {string} [selectOOB]
*/

Expand Down
42 changes: 42 additions & 0 deletions test/attributes/hx-replace-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,48 @@ describe('hx-replace-url attribute', function() {
cache[cache.length - 1].url.should.equal(normalizePath('/test'))
})

it('should replace URL when hx-push-url is false', function() {
var history = recordHistoryUpdates()
try {
this.server.respondWith('GET', '/test', 'Replaced!')
var div = make('<div hx-push-url="false" hx-replace-url="true" hx-target="this"><button id="b1" hx-get="/test">Go</button></div>')
byId('b1').click()
this.server.respond()
div.innerHTML.should.equal('Replaced!')
history.updates.should.deep.equal([['htmx:replacedInHistory', '/test']])
} finally {
history.stop()
}
})

it('boosted anchor should replace URL when hx-push-url is false', function() {
var history = recordHistoryUpdates()
try {
this.server.respondWith('GET', '/test', 'Replaced!')
var div = make('<div hx-boost="true" hx-push-url="false" hx-replace-url="/custom" hx-target="this"><a id="a1" href="/test">Go</a></div>')
byId('a1').click()
this.server.respond()
div.innerHTML.should.equal('Replaced!')
history.updates.should.deep.equal([['htmx:replacedInHistory', '/custom']])
} finally {
history.stop()
}
})

it('boosted anchor should still push URL when hx-replace-url is false', function() {
var history = recordHistoryUpdates()
try {
this.server.respondWith('GET', '/test', 'Pushed!')
var div = make('<div hx-boost="true" hx-replace-url="false" hx-target="this"><a id="a1" href="/test">Go</a></div>')
byId('a1').click()
this.server.respond()
div.innerHTML.should.equal('Pushed!')
history.updates.should.deep.equal([['htmx:pushedIntoHistory', '/test']])
} finally {
history.stop()
}
})

it('should handle HX-Replace-Url response header', function() {
var path
var handler = htmx.on('htmx:replacedInHistory', function(event) {
Expand Down
30 changes: 30 additions & 0 deletions test/core/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,36 @@ describe('Core htmx API test', function() {
path.should.equal('/abc123')
})

it('ajax api push Url should not push an inherited Url when false', function() {
var history = recordHistoryUpdates()
try {
this.server.respondWith('GET', '/test', 'Loaded!')
make('<div hx-push-url="/inherited"><div id="d1"></div></div>')
var div = byId('d1')
htmx.ajax('GET', '/test', { source: div, target: div, push: false })
this.server.respond()
div.innerHTML.should.equal('Loaded!')
history.updates.should.deep.equal([])
} finally {
history.stop()
}
})

it('ajax api replace Url should not replace an inherited Url when false', function() {
var history = recordHistoryUpdates()
try {
this.server.respondWith('GET', '/test', 'Loaded!')
make('<div hx-replace-url="/inherited"><div id="d1"></div></div>')
var div = byId('d1')
htmx.ajax('GET', '/test', { source: div, target: div, replace: false })
this.server.respond()
div.innerHTML.should.equal('Loaded!')
history.updates.should.deep.equal([])
} finally {
history.stop()
}
})

it('ajax api replace Url should replace an element into the cache when true', function() {
this.server.respondWith('POST', '/test123', 'Clicked!')

Expand Down
66 changes: 66 additions & 0 deletions test/core/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,72 @@ describe('Core htmx AJAX headers', function() {
}, 30)
})

it('should replace Url on HX-Location if push is string false', function() {
var history = recordHistoryUpdates()
try {
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"path":"/test2", "target":"#testdiv", "push":"false", "replace":"true"}' }, ''])
this.server.respondWith('GET', '/test2', 'Redirected!')
var div = make('<div id="testdiv" hx-get="/test"></div>')
div.click()
this.server.respond()
this.server.respond()
div.innerHTML.should.equal('Redirected!')
history.updates.should.deep.equal([['htmx:replacedInHistory', '/test2']])
} finally {
history.stop()
}
})

it('should replace Url on HX-Location if push is false', function() {
var history = recordHistoryUpdates()
try {
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"path":"/test2", "target":"#testdiv", "push":false, "replace":"/custom"}' }, ''])
this.server.respondWith('GET', '/test2', 'Redirected!')
var div = make('<div id="testdiv" hx-get="/test"></div>')
div.click()
this.server.respond()
this.server.respond()
div.innerHTML.should.equal('Redirected!')
history.updates.should.deep.equal([['htmx:replacedInHistory', '/custom']])
} finally {
history.stop()
}
})

it('should not update history on HX-Location from a boosted source if push and replace are string false', function() {
var history = recordHistoryUpdates()
try {
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"path":"/test2", "source":"#a1", "target":"#output", "push":"false", "replace":"false"}' }, ''])
this.server.respondWith('GET', '/test2', 'Redirected!')
var output = make('<div id="output"></div>')
make('<div hx-boost="true" hx-push-url="/inherited" hx-replace-url="/inherited"><a id="a1" href="/test">Go</a></div>')
byId('a1').click()
this.server.respond()
this.server.respond()
output.innerHTML.should.equal('Redirected!')
history.updates.should.deep.equal([])
} finally {
history.stop()
}
})

it('should not update history on HX-Location from a boosted source if push and replace are false', function() {
var history = recordHistoryUpdates()
try {
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"path":"/test2", "source":"#a1", "target":"#output", "push":false, "replace":false}' }, ''])
this.server.respondWith('GET', '/test2', 'Redirected!')
var output = make('<div id="output"></div>')
make('<div hx-boost="true" hx-push-url="/inherited" hx-replace-url="/inherited"><a id="a1" href="/test">Go</a></div>')
byId('a1').click()
this.server.respond()
this.server.respond()
output.innerHTML.should.equal('Redirected!')
history.updates.should.deep.equal([])
} finally {
history.stop()
}
})

it('should push different Url on HX-Location if push Url is string', function(done) {
sessionStorage.removeItem('htmx-current-path-for-history')
var HTMX_HISTORY_CACHE_NAME = 'htmx-history-cache'
Expand Down
16 changes: 16 additions & 0 deletions test/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,22 @@ function getParameters(xhr) {
}
}

function recordHistoryUpdates() {
var updates = []
var handler = function(event) {
updates.push([event.type, event.detail.path])
}
htmx.on('htmx:pushedIntoHistory', handler)
htmx.on('htmx:replacedInHistory', handler)
return {
updates,
stop: function() {
htmx.off('htmx:pushedIntoHistory', handler)
htmx.off('htmx:replacedInHistory', handler)
}
}
}

function log(val) {
console.log(val)
return val
Expand Down
10 changes: 8 additions & 2 deletions www/content/headers/hx-location.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ Path is required and is url to load the response from. The rest of the data mirr
* `values` - values to submit with the request
* `headers` - headers to submit with the request
* `select` - allows you to select the content you want swapped from a response
* `push` - set to `'false'` or a path string to prevent or override the URL pushed to browser location history
* `replace` - a path string to replace the URL in the browser location history
* `push` - set to `'false'`, `false`, or a path string to prevent or override the URL pushed to browser location history
* `replace` - set to `'true'` or a path string to replace the URL in the browser location history

To replace the current history entry, disable the default push and specify `replace`:

```html
HX-Location: {"path":"/test2", "push":"false", "replace":"true"}
```

## Notes

Expand Down