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
66 changes: 41 additions & 25 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,26 +458,41 @@ var htmx = (function() {
}

/**
* Checks whether an attribute name appears in the space separated list of an
* hx- attribute ('*' matches all attributes)
*
* @param {string} attributeName the attribute name to check for
* @param {string|null} attributeList the space separated list of attribute names, or '*'
* @returns {boolean}
*/
function isAttributeInList(attributeName, attributeList) {
return !!attributeList && (attributeList === '*' || attributeList.split(' ').indexOf(attributeName) >= 0)
}

/**
* Check for and respect disinheritance in getAttributeValue
*
* @param {Element} initialElement
* @param {Element} ancestor
* @param {string} attributeName
* @returns {string|null}
*/
function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName) {
const attributeValue = getAttributeValue(ancestor, attributeName)
// the element's own attribute is always applied; disinheritance only affects inherited values
if (initialElement === ancestor) {
return attributeValue
}
const inherit = getAttributeValue(ancestor, 'hx-inherit')
const disinherit = getAttributeValue(ancestor, 'hx-disinherit')
var inherit = getAttributeValue(ancestor, 'hx-inherit')
if (initialElement !== ancestor) {
if (htmx.config.disableInheritance) {
if (inherit && (inherit === '*' || inherit.split(' ').indexOf(attributeName) >= 0)) {
return attributeValue
} else {
return null
}
}
if (disinherit && (disinherit === '*' || disinherit.split(' ').indexOf(attributeName) >= 0)) {
return 'unset'
}

// when inheritance is globally disabled, only attributes re-enabled via hx-inherit are inherited
if (htmx.config.disableInheritance) {
return isAttributeInList(attributeName, inherit) ? attributeValue : null
}
// hx-disinherit stops inheritance of this attribute from this element upwards
if (isAttributeInList(attributeName, disinherit)) {
return 'unset'
}
return attributeValue
}
Expand Down Expand Up @@ -3907,27 +3922,28 @@ var htmx = (function() {
}

/**
* @param {Element} elt
* @param {string} attr
* @param {boolean=} evalAsDefault
* @param {Object=} values
* @param {Event=} event
* @returns {Object}
*/
function getValuesForElement(elt, attr, evalAsDefault, values, event) {
* @param {Element} elt
* @param {string} attr
* @param {boolean=} evalAsDefault
* @param {Object=} values
* @param {Event=} event
* @returns {Object}
*/
function getValuesForElement(elt, attr, evalAsDefault, values, event, initialElement) {
if (values == null) {
values = {}
}
if (elt == null) {
return values
}
const attributeValue = getAttributeValue(elt, attr)
const initialElt = initialElement || elt
const attributeValue = getAttributeValueWithDisinheritance(initialElt, elt, attr)
if (attributeValue && attributeValue.trim() === 'unset') {
return values
}
if (attributeValue) {
let str = attributeValue.trim()
let evaluateValue = evalAsDefault
if (str === 'unset') {
return null
}
if (str.indexOf('javascript:') === 0) {
str = str.slice(11)
evaluateValue = true
Expand Down Expand Up @@ -3958,7 +3974,7 @@ var htmx = (function() {
}
}
}
return getValuesForElement(asElement(parentElt(elt)), attr, evalAsDefault, values, event)
return getValuesForElement(asElement(parentElt(elt)), attr, evalAsDefault, values, event, initialElt)
}

/**
Expand Down
127 changes: 127 additions & 0 deletions test/attributes/hx-vals.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,131 @@ describe('hx-vals attribute', function() {
this.server.respond()
div.innerHTML.should.equal('Clicked!')
})
it('hx-vals can be disinherited from parent', function() {
this.server.respondWith('POST', '/vars', function(xhr) {
var params = getParameters(xhr)
params.should.be.empty
xhr.respond(200, {}, 'Clicked!')
})
make("<div hx-vals='\"i1\":\"test\"' hx-disinherit='hx-vals'><div id='d1' hx-post='/vars'></div></div>")
var div = byId('d1')
div.click()
this.server.respond()
div.innerHTML.should.equal('Clicked!')
})

it('hx-vals can be disinherited by an intermediate ancestor', function() {
this.server.respondWith('POST', '/vars', function(xhr) {
var params = getParameters(xhr)
params.should.be.empty
xhr.respond(200, {}, 'Clicked!')
})
make(
"<div hx-vals='\"i1\":\"test\"'>\
<div hx-disinherit='hx-vals'>\
<div id='d1' hx-post='/vars'></div>\
</div>\
</div>"
)
var div = byId('d1')
div.click()
this.server.respond()
div.innerHTML.should.equal('Clicked!')
})

it('hx-vals can be disinherited with asterisk', function() {
this.server.respondWith('POST', '/vars', function(xhr) {
var params = getParameters(xhr)
params.should.be.empty
xhr.respond(200, {}, 'Clicked!')
})
make("<div hx-vals='\"i1\":\"test\"' hx-disinherit='*'><div id='d1' hx-post='/vars'></div></div>")
var div = byId('d1')
div.click()
this.server.respond()
div.innerHTML.should.equal('Clicked!')
})

it('hx-vals can be disinherited but own value is used', function() {
this.server.respondWith('POST', '/vars', function(xhr) {
var params = getParameters(xhr)
params.size.should.equal('small')
should.not.exist(params.color)
xhr.respond(200, {}, 'Clicked!')
})
make("<form hx-vals='\"size\":\"large\",\"color\":\"blue\"' hx-disinherit='hx-vals'>\
<button id='d1' hx-vals='\"size\":\"small\"' hx-post='/vars'>Click Me!</button>\
</form>")
var btn = byId('d1')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Clicked!')
})

it('hx-vals disinheritance keeps values of intermediate ancestors', function() {
this.server.respondWith('POST', '/vars', function(xhr) {
var params = getParameters(xhr)
params.inner.should.equal('2')
should.not.exist(params.outer)
xhr.respond(200, {}, 'Clicked!')
})
make("<div hx-vals='\"outer\":\"1\"' hx-disinherit='hx-vals'>\
<div hx-vals='\"inner\":\"2\"'>\
<button id='d1' hx-post='/vars'>Click Me!</button>\
</div>\
</div>")
var btn = byId('d1')
btn.click()
this.server.respond()
btn.innerHTML.should.equal('Clicked!')
})

it('hx-vals can be disinherited from parent with javascript', function() {
this.server.respondWith('POST', '/vars', function(xhr) {
var params = getParameters(xhr)
params.should.be.empty
xhr.respond(200, {}, 'Clicked!')
})
make("<div hx-vals=\"javascript:i1:'test'\" hx-disinherit='hx-vals'><div id='d1' hx-post='/vars'></div></div>")
var div = byId('d1')
div.click()
this.server.respond()
div.innerHTML.should.equal('Clicked!')
})

it('hx-vals is not inherited when disableInheritance is set', function() {
try {
htmx.config.disableInheritance = true
this.server.respondWith('POST', '/vars', function(xhr) {
var params = getParameters(xhr)
params.should.be.empty
xhr.respond(200, {}, 'Clicked!')
})
make("<div hx-vals='\"i1\":\"test\"'><div id='d1' hx-post='/vars'></div></div>")
var div = byId('d1')
div.click()
this.server.respond()
div.innerHTML.should.equal('Clicked!')
} finally {
htmx.config.disableInheritance = false
}
})

it('hx-vals can be inherited when disableInheritance is set and hx-inherit is used', function() {
try {
htmx.config.disableInheritance = true
this.server.respondWith('POST', '/vars', function(xhr) {
var params = getParameters(xhr)
params.i1.should.equal('test')
xhr.respond(200, {}, 'Clicked!')
})
make("<div hx-vals='\"i1\":\"test\"' hx-inherit='hx-vals'><div id='d1' hx-post='/vars'></div></div>")
var div = byId('d1')
div.click()
this.server.respond()
div.innerHTML.should.equal('Clicked!')
} finally {
htmx.config.disableInheritance = false
}
})
})