Skip to content
27 changes: 17 additions & 10 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -4053,7 +4053,10 @@ var htmx = (function() {
targetOverride: resolvedTarget,
swapOverride: context.swap,
select: context.select,
returnPromise: true
returnPromise: true,
push: context.push,
replace: context.replace,
selectOOB: context.selectOOB
})
}
} else {
Expand Down Expand Up @@ -4668,8 +4671,8 @@ var htmx = (function() {
const requestPath = responseInfo.pathInfo.finalRequestPath
const responsePath = responseInfo.pathInfo.responsePath

const pushUrl = getClosestAttributeValue(elt, 'hx-push-url')
const replaceUrl = getClosestAttributeValue(elt, 'hx-replace-url')
const pushUrl = responseInfo.etc.push || getClosestAttributeValue(elt, 'hx-push-url')
const replaceUrl = responseInfo.etc.replace || getClosestAttributeValue(elt, 'hx-replace-url')
const elementIsBoosted = getInternalData(elt).boosted

let saveType = null
Expand Down Expand Up @@ -4788,19 +4791,17 @@ var htmx = (function() {
}

if (hasHeader(xhr, /HX-Location:/i)) {
saveCurrentPageToHistory()
let redirectPath = xhr.getResponseHeader('HX-Location')
/** @type {HtmxAjaxHelperContext&{path:string}} */
var redirectSwapSpec
/** @type {HtmxAjaxHelperContext&{path?:string}} */
var redirectSwapSpec = {}
if (redirectPath.indexOf('{') === 0) {
redirectSwapSpec = parseJSON(redirectPath)
// what's the best way to throw an error if the user didn't include this
redirectPath = redirectSwapSpec.path
delete redirectSwapSpec.path
}
ajaxHelper('get', redirectPath, redirectSwapSpec).then(function() {
pushUrlIntoHistory(redirectPath)
})
redirectSwapSpec.push = redirectSwapSpec.push || 'true'
ajaxHelper('get', redirectPath, redirectSwapSpec)
return
}

Expand Down Expand Up @@ -4899,7 +4900,7 @@ var htmx = (function() {
selectOverride = xhr.getResponseHeader('HX-Reselect')
}

const selectOOB = getClosestAttributeValue(elt, 'hx-select-oob')
const selectOOB = etc.selectOOB || getClosestAttributeValue(elt, 'hx-select-oob')
const select = getClosestAttributeValue(elt, 'hx-select')

swap(target, serverResponse, swapSpec, {
Expand Down Expand Up @@ -5216,6 +5217,9 @@ var htmx = (function() {
* @property {Object|FormData} [values]
* @property {Record<string,string>} [headers]
* @property {string} [select]
* @property {string} [push]
* @property {string} [replace]
* @property {string} [selectOOB]
*/

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

/**
Expand Down
70 changes: 70 additions & 0 deletions test/core/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,16 @@ describe('Core htmx API test', function() {
div.innerHTML.should.equal('<div id="d2">bar</div>')
})

it('ajax api works with selectOOB', function() {
this.server.respondWith('GET', '/test', "<div id='oob'>OOB Content</div><div>Main Content</div>")
var target = make("<div id='target'></div>")
var oobDiv = make("<div id='oob'></div>")
htmx.ajax('GET', '/test', { target: '#target', selectOOB: '#oob:innerHTML' })
this.server.respond()
target.innerHTML.should.equal('<div>Main Content</div>')
oobDiv.innerHTML.should.equal('OOB Content')
})

it('ajax api works with Hx-Select overrides select', function() {
this.server.respondWith('GET', '/test', [200, { 'HX-Reselect': '#d2' }, "<div id='d1'>foo</div><div id='d2'>bar</div>"])
var div = make("<div id='target'></div>")
Expand Down Expand Up @@ -656,4 +666,64 @@ describe('Core htmx API test', function() {
var div = make('<div>textNode</div>')
htmx.process(div.firstChild)
})

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

var div = make("<div id='d1'></div>")
htmx.ajax('POST', '/test123', {
target: '#d1',
swap: 'innerHTML',
push: 'true'
})
this.server.respond()
div.innerHTML.should.equal('Clicked!')
var path = sessionStorage.getItem('htmx-current-path-for-history')
path.should.equal('/test123')
})

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

var div = make("<div id='d1'></div>")
htmx.ajax('POST', '/test', {
target: '#d1',
swap: 'innerHTML',
push: '/abc123'
})
this.server.respond()
div.innerHTML.should.equal('Clicked!')
var path = sessionStorage.getItem('htmx-current-path-for-history')
path.should.equal('/abc123')
})

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

var div = make("<div id='d1'></div>")
htmx.ajax('POST', '/test123', {
target: '#d1',
swap: 'innerHTML',
replace: 'true'
})
this.server.respond()
div.innerHTML.should.equal('Clicked!')
var path = sessionStorage.getItem('htmx-current-path-for-history')
path.should.equal('/test123')
})

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

var div = make("<div id='d1'></div>")
htmx.ajax('POST', '/test', {
target: '#d1',
swap: 'innerHTML',
replace: '/abc123'
})
this.server.respond()
div.innerHTML.should.equal('Clicked!')
var path = sessionStorage.getItem('htmx-current-path-for-history')
path.should.equal('/abc123')
})
})
50 changes: 50 additions & 0 deletions test/core/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,56 @@ describe('Core htmx AJAX headers', function() {
}, 30)
})

it('should push new Url on HX-Location', function(done) {
sessionStorage.removeItem('htmx-current-path-for-history')
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"path":"/test2", "target":"#work-area"}' }, ''])
this.server.respondWith('GET', '/test2', [200, {}, '<div>Yay! Welcome</div>'])
var div = make('<div id="testdiv" hx-trigger="click" hx-get="/test"></div>')
div.click()
this.server.respond()
this.server.respond()
setTimeout(function() {
getWorkArea().innerHTML.should.equal('<div>Yay! Welcome</div>')
var path = sessionStorage.getItem('htmx-current-path-for-history')
path.should.equal('/test2')
done()
}, 30)
})

it('should not push new Url on HX-Location if push Url false', function(done) {
sessionStorage.setItem('htmx-current-path-for-history', '/old')
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"push":"false", "path":"/test2", "target":"#work-area"}' }, ''])
this.server.respondWith('GET', '/test2', [200, {}, '<div>Yay! Welcome</div>'])
var div = make('<div id="testdiv" hx-trigger="click" hx-get="/test"></div>')
div.click()
this.server.respond()
this.server.respond()
setTimeout(function() {
getWorkArea().innerHTML.should.equal('<div>Yay! Welcome</div>')
var path = sessionStorage.getItem('htmx-current-path-for-history')
path.should.equal('/old')
done()
}, 30)
})

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'
sessionStorage.removeItem(HTMX_HISTORY_CACHE_NAME)
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"push":"/abc123", "path":"/test2", "target":"#work-area"}' }, ''])
this.server.respondWith('GET', '/test2', [200, {}, '<div>Yay! Welcome</div>'])
var div = make('<div id="testdiv" hx-trigger="click" hx-get="/test"></div>')
div.click()
this.server.respond()
this.server.respond()
setTimeout(function() {
getWorkArea().innerHTML.should.equal('<div>Yay! Welcome</div>')
var path = sessionStorage.getItem('htmx-current-path-for-history')
path.should.equal('/abc123')
done()
}, 30)
})

it('should refresh page on HX-Refresh', function() {
var refresh = false
htmx.location = { reload: function() { refresh = true } }
Expand Down
3 changes: 3 additions & 0 deletions www/content/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ or
* `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
* `selectOOB` - allows you to select content for out-of-band swaps from a response
* `push` - can be `'true'` or a path to push a URL into browser location history
* `replace` - can be `'true'` or a path to replace the URL in the browser location history

##### Example

Expand Down
2 changes: 2 additions & 0 deletions www/content/headers/hx-location.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ 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

## Notes

Expand Down