diff --git a/src/htmx.js b/src/htmx.js index e574f31f4..9ce1b23a3 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -458,6 +458,20 @@ 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 @@ -465,19 +479,20 @@ var htmx = (function() { */ 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 } @@ -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 @@ -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) } /** diff --git a/test/attributes/hx-vals.js b/test/attributes/hx-vals.js index 50a7e0716..326f6b535 100644 --- a/test/attributes/hx-vals.js +++ b/test/attributes/hx-vals.js @@ -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("
") + 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( + "
\ +
\ +
\ +
\ +
" + ) + 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("
") + 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("
\ + \ +
") + 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("
\ +
\ + \ +
\ +
") + 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("
") + 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("
") + 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("
") + var div = byId('d1') + div.click() + this.server.respond() + div.innerHTML.should.equal('Clicked!') + } finally { + htmx.config.disableInheritance = false + } + }) })