Skip to content

Commit 83a1449

Browse files
Add pushUrl option to ajax api and improve hx-location push url handling (#3404)
* add pushUrl option * Remove duplicate save to history * Improve pushUrl and hx-location url handling * Add replace option to api as well * minor wording change * push headers support true * roll back anchor support for header base paths except for true case * add selectOOB and simplify ajax helper * Remove refactor * reverse order of push/replace --------- Co-authored-by: MichaelWest22 <michael.west@docuvera.com>
1 parent cd045c3 commit 83a1449

5 files changed

Lines changed: 142 additions & 10 deletions

File tree

src/htmx.js

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4075,7 +4075,10 @@ var htmx = (function() {
40754075
targetOverride: resolvedTarget,
40764076
swapOverride: context.swap,
40774077
select: context.select,
4078-
returnPromise: true
4078+
returnPromise: true,
4079+
push: context.push,
4080+
replace: context.replace,
4081+
selectOOB: context.selectOOB
40794082
})
40804083
}
40814084
} else {
@@ -4690,8 +4693,8 @@ var htmx = (function() {
46904693
const requestPath = responseInfo.pathInfo.finalRequestPath
46914694
const responsePath = responseInfo.pathInfo.responsePath
46924695

4693-
const pushUrl = getClosestAttributeValue(elt, 'hx-push-url')
4694-
const replaceUrl = getClosestAttributeValue(elt, 'hx-replace-url')
4696+
const pushUrl = responseInfo.etc.push || getClosestAttributeValue(elt, 'hx-push-url')
4697+
const replaceUrl = responseInfo.etc.replace || getClosestAttributeValue(elt, 'hx-replace-url')
46954698
const elementIsBoosted = getInternalData(elt).boosted
46964699

46974700
let saveType = null
@@ -4810,19 +4813,17 @@ var htmx = (function() {
48104813
}
48114814

48124815
if (hasHeader(xhr, /HX-Location:/i)) {
4813-
saveCurrentPageToHistory()
48144816
let redirectPath = xhr.getResponseHeader('HX-Location')
4815-
/** @type {HtmxAjaxHelperContext&{path:string}} */
4816-
var redirectSwapSpec
4817+
/** @type {HtmxAjaxHelperContext&{path?:string}} */
4818+
var redirectSwapSpec = {}
48174819
if (redirectPath.indexOf('{') === 0) {
48184820
redirectSwapSpec = parseJSON(redirectPath)
48194821
// what's the best way to throw an error if the user didn't include this
48204822
redirectPath = redirectSwapSpec.path
48214823
delete redirectSwapSpec.path
48224824
}
4823-
ajaxHelper('get', redirectPath, redirectSwapSpec).then(function() {
4824-
pushUrlIntoHistory(redirectPath)
4825-
})
4825+
redirectSwapSpec.push = redirectSwapSpec.push || 'true'
4826+
ajaxHelper('get', redirectPath, redirectSwapSpec)
48264827
return
48274828
}
48284829

@@ -4921,7 +4922,7 @@ var htmx = (function() {
49214922
selectOverride = xhr.getResponseHeader('HX-Reselect')
49224923
}
49234924

4924-
const selectOOB = getClosestAttributeValue(elt, 'hx-select-oob')
4925+
const selectOOB = etc.selectOOB || getClosestAttributeValue(elt, 'hx-select-oob')
49254926
const select = getClosestAttributeValue(elt, 'hx-select')
49264927

49274928
swap(target, serverResponse, swapSpec, {
@@ -5240,6 +5241,9 @@ var htmx = (function() {
52405241
* @property {Object|FormData} [values]
52415242
* @property {Record<string,string>} [headers]
52425243
* @property {string} [select]
5244+
* @property {string} [push]
5245+
* @property {string} [replace]
5246+
* @property {string} [selectOOB]
52435247
*/
52445248

52455249
/**
@@ -5286,6 +5290,9 @@ var htmx = (function() {
52865290
* @property {Object|FormData} [values]
52875291
* @property {boolean} [credentials]
52885292
* @property {number} [timeout]
5293+
* @property {string} [push]
5294+
* @property {string} [replace]
5295+
* @property {string} [selectOOB]
52895296
*/
52905297

52915298
/**

test/core/api.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,16 @@ describe('Core htmx API test', function() {
321321
div.innerHTML.should.equal('<div id="d2">bar</div>')
322322
})
323323

324+
it('ajax api works with selectOOB', function() {
325+
this.server.respondWith('GET', '/test', "<div id='oob'>OOB Content</div><div>Main Content</div>")
326+
var target = make("<div id='target'></div>")
327+
var oobDiv = make("<div id='oob'></div>")
328+
htmx.ajax('GET', '/test', { target: '#target', selectOOB: '#oob:innerHTML' })
329+
this.server.respond()
330+
target.innerHTML.should.equal('<div>Main Content</div>')
331+
oobDiv.innerHTML.should.equal('OOB Content')
332+
})
333+
324334
it('ajax api works with Hx-Select overrides select', function() {
325335
this.server.respondWith('GET', '/test', [200, { 'HX-Reselect': '#d2' }, "<div id='d1'>foo</div><div id='d2'>bar</div>"])
326336
var div = make("<div id='target'></div>")
@@ -658,4 +668,64 @@ describe('Core htmx API test', function() {
658668
var div = make('<div>textNode</div>')
659669
htmx.process(div.firstChild)
660670
})
671+
672+
it('ajax api push Url should push an element into the cache when true', function() {
673+
this.server.respondWith('POST', '/test123', 'Clicked!')
674+
675+
var div = make("<div id='d1'></div>")
676+
htmx.ajax('POST', '/test123', {
677+
target: '#d1',
678+
swap: 'innerHTML',
679+
push: 'true'
680+
})
681+
this.server.respond()
682+
div.innerHTML.should.equal('Clicked!')
683+
var path = sessionStorage.getItem('htmx-current-path-for-history')
684+
path.should.equal('/test123')
685+
})
686+
687+
it('ajax api push Url should push an element into the cache when string', function() {
688+
this.server.respondWith('POST', '/test', 'Clicked!')
689+
690+
var div = make("<div id='d1'></div>")
691+
htmx.ajax('POST', '/test', {
692+
target: '#d1',
693+
swap: 'innerHTML',
694+
push: '/abc123'
695+
})
696+
this.server.respond()
697+
div.innerHTML.should.equal('Clicked!')
698+
var path = sessionStorage.getItem('htmx-current-path-for-history')
699+
path.should.equal('/abc123')
700+
})
701+
702+
it('ajax api replace Url should replace an element into the cache when true', function() {
703+
this.server.respondWith('POST', '/test123', 'Clicked!')
704+
705+
var div = make("<div id='d1'></div>")
706+
htmx.ajax('POST', '/test123', {
707+
target: '#d1',
708+
swap: 'innerHTML',
709+
replace: 'true'
710+
})
711+
this.server.respond()
712+
div.innerHTML.should.equal('Clicked!')
713+
var path = sessionStorage.getItem('htmx-current-path-for-history')
714+
path.should.equal('/test123')
715+
})
716+
717+
it('ajax api replace Url should replace an element into the cache when string', function() {
718+
this.server.respondWith('POST', '/test', 'Clicked!')
719+
720+
var div = make("<div id='d1'></div>")
721+
htmx.ajax('POST', '/test', {
722+
target: '#d1',
723+
swap: 'innerHTML',
724+
replace: '/abc123'
725+
})
726+
this.server.respond()
727+
div.innerHTML.should.equal('Clicked!')
728+
var path = sessionStorage.getItem('htmx-current-path-for-history')
729+
path.should.equal('/abc123')
730+
})
661731
})

test/core/headers.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,56 @@ describe('Core htmx AJAX headers', function() {
410410
}, 30)
411411
})
412412

413+
it('should push new Url on HX-Location', function(done) {
414+
sessionStorage.removeItem('htmx-current-path-for-history')
415+
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"path":"/test2", "target":"#work-area"}' }, ''])
416+
this.server.respondWith('GET', '/test2', [200, {}, '<div>Yay! Welcome</div>'])
417+
var div = make('<div id="testdiv" hx-trigger="click" hx-get="/test"></div>')
418+
div.click()
419+
this.server.respond()
420+
this.server.respond()
421+
setTimeout(function() {
422+
getWorkArea().innerHTML.should.equal('<div>Yay! Welcome</div>')
423+
var path = sessionStorage.getItem('htmx-current-path-for-history')
424+
path.should.equal('/test2')
425+
done()
426+
}, 30)
427+
})
428+
429+
it('should not push new Url on HX-Location if push Url false', function(done) {
430+
sessionStorage.setItem('htmx-current-path-for-history', '/old')
431+
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"push":"false", "path":"/test2", "target":"#work-area"}' }, ''])
432+
this.server.respondWith('GET', '/test2', [200, {}, '<div>Yay! Welcome</div>'])
433+
var div = make('<div id="testdiv" hx-trigger="click" hx-get="/test"></div>')
434+
div.click()
435+
this.server.respond()
436+
this.server.respond()
437+
setTimeout(function() {
438+
getWorkArea().innerHTML.should.equal('<div>Yay! Welcome</div>')
439+
var path = sessionStorage.getItem('htmx-current-path-for-history')
440+
path.should.equal('/old')
441+
done()
442+
}, 30)
443+
})
444+
445+
it('should push different Url on HX-Location if push Url is string', function(done) {
446+
sessionStorage.removeItem('htmx-current-path-for-history')
447+
var HTMX_HISTORY_CACHE_NAME = 'htmx-history-cache'
448+
sessionStorage.removeItem(HTMX_HISTORY_CACHE_NAME)
449+
this.server.respondWith('GET', '/test', [200, { 'HX-Location': '{"push":"/abc123", "path":"/test2", "target":"#work-area"}' }, ''])
450+
this.server.respondWith('GET', '/test2', [200, {}, '<div>Yay! Welcome</div>'])
451+
var div = make('<div id="testdiv" hx-trigger="click" hx-get="/test"></div>')
452+
div.click()
453+
this.server.respond()
454+
this.server.respond()
455+
setTimeout(function() {
456+
getWorkArea().innerHTML.should.equal('<div>Yay! Welcome</div>')
457+
var path = sessionStorage.getItem('htmx-current-path-for-history')
458+
path.should.equal('/abc123')
459+
done()
460+
}, 30)
461+
})
462+
413463
it('should refresh page on HX-Refresh', function() {
414464
var refresh = false
415465
htmx.location = { reload: function() { refresh = true } }

www/content/api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ or
6565
* `values` - values to submit with the request
6666
* `headers` - headers to submit with the request
6767
* `select` - allows you to select the content you want swapped from a response
68+
* `selectOOB` - allows you to select content for out-of-band swaps from a response
69+
* `push` - can be `'true'` or a path to push a URL into browser location history
70+
* `replace` - can be `'true'` or a path to replace the URL in the browser location history
6871

6972
##### Example
7073

www/content/headers/hx-location.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Path is required and is url to load the response from. The rest of the data mirr
3030
* `values` - values to submit with the request
3131
* `headers` - headers to submit with the request
3232
* `select` - allows you to select the content you want swapped from a response
33+
* `push` - set to `'false'` or a path string to prevent or override the URL pushed to browser location history
34+
* `replace` - a path string to replace the URL in the browser location history
3335

3436
## Notes
3537

0 commit comments

Comments
 (0)