Skip to content

Commit

Permalink
Fix boolean checkboxes values in form
Browse files Browse the repository at this point in the history
  • Loading branch information
Telroshan committed Apr 28, 2024
1 parent 9d65265 commit 36a6181
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 49 deletions.
35 changes: 31 additions & 4 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -3250,6 +3250,14 @@ var htmx = (function() {
}
}

/**
* @param {Element} elt
* @returns {boolean}
*/
function isBooleanCheckbox(elt) {
return elt instanceof HTMLInputElement && elt.getAttribute('type') === 'checkbox' && typeof elt.getAttribute('value') !== 'string'
}

/**
* @param {Element[]} processed
* @param {FormData} formData
Expand All @@ -3274,30 +3282,49 @@ var htmx = (function() {
if (elt instanceof HTMLInputElement && elt.files) {
value = toArray(elt.files)
}
if (elt instanceof HTMLInputElement && elt.getAttribute('type') === 'checkbox' && typeof elt.getAttribute('value') !== 'string') {
value = elt.checked
if (isBooleanCheckbox(elt)) {
value = (/** @type HTMLInputElement */ (elt)).checked
}
addValueToFormData(name, value, formData)
if (validate) {
validateElement(elt, errors)
}
}
if (elt instanceof HTMLFormElement) {
const namesWithBooleanCheckboxes = {}
forEach(elt.elements, function(input) {
if (processed.indexOf(input) >= 0) {
// The input has already been processed and added to the values, but the FormData that will be
// constructed right after on the form, will include it once again. So remove that input's value
// now to avoid duplicates
removeValueFromFormData(input.name, input.value, formData)
if (!isBooleanCheckbox(input)) {
removeValueFromFormData(input.name, input.value, formData)
} else {
removeValueFromFormData(input.name, input.checked.toString(), formData)
}
} else {
processed.push(input)
}
if (validate) {
validateElement(input, errors)
}
if (isBooleanCheckbox(input)) {
namesWithBooleanCheckboxes[input.name] = true
}
})
new FormData(elt).forEach(function(value, name) {
addValueToFormData(name, value, formData)
if (!namesWithBooleanCheckboxes[name]) {
addValueToFormData(name, value, formData)
}
})
forEach(elt.elements, function(input) {
if (namesWithBooleanCheckboxes[input.name]) {
if (isBooleanCheckbox(input)) {
addValueToFormData(input.name, input.checked.toString(), formData)
} else if (input.type !== 'checkbox' || input.checked) {
addValueToFormData(input.name, input.value, formData)
}
}
})
}
}
Expand Down
43 changes: 43 additions & 0 deletions test/core/parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,4 +339,47 @@ describe('Core htmx Parameter Handling', function() {
var vals = htmx._('getInputValues')(input, 'get').values
vals.foo.should.equal('true')
})

it('Unticked checkbox with value (in form) does not include value', function() {
var form = make('<form><input type="checkbox" name="foo" value="bar"/></form>')
var vals = htmx._('getInputValues')(form, 'get').values
should.equal(vals.foo, undefined)
})

it('Value-less unticked checkbox (in form) includes false value', function() {
var form = make('<form><input type="checkbox" name="foo"/></form>')
var vals = htmx._('getInputValues')(form, 'get').values
vals.foo.should.equal('false')
})

it('Value-less ticked checkbox (in form) includes true value', function() {
var form = make('<form><input type="checkbox" name="foo" checked/></form>')
var vals = htmx._('getInputValues')(form, 'get').values
vals.foo.should.equal('true')
})

it('Multiple checkboxes (in form) work', function() {
var form = make('<form>' +
'<input type="checkbox" name="foo" checked/>' +
'<input type="checkbox" name="foo"/>' +
'<input type="checkbox" name="foo" checked/>' +

'<input type="checkbox" name="test" checked/>' +
'<input type="checkbox" name="test" value="test1" checked/>' +
'<input type="checkbox" name="test" value="test2"/>' +
'<input type="checkbox" name="test"/>' +

'<input type="checkbox" name="test2" value="test1" checked/>' +
'<input type="checkbox" name="test2" checked/>' +
'<input type="checkbox" name="test2" value="test2"/>' +
'<input type="checkbox" name="test2"/>' +

'<input type="checkbox" name="bar" value="test" checked/>' +
'</form>')
var vals = htmx._('getInputValues')(form, 'get').values
vals.foo.should.deep.equal(['true', 'false', 'true'])
vals.test.should.deep.equal(['true', 'test1', 'false'])
vals.test2.should.deep.equal(['test1', 'true', 'false'])
vals.bar.should.equal('test')
})
})
90 changes: 45 additions & 45 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,56 +55,56 @@ <h2>Mocha Test Suite</h2>
<script src="util/util.js"></script>

<!-- core tests -->
<!--<script src="core/internals.js"></script>-->
<!--<script src="core/api.js"></script>-->
<!--<script src="core/ajax.js"></script>-->
<!--<script src="core/config.js"></script>-->
<!--<script src="core/verbs.js"></script>-->
<script src="core/internals.js"></script>
<script src="core/api.js"></script>
<script src="core/ajax.js"></script>
<script src="core/config.js"></script>
<script src="core/verbs.js"></script>
<script src="core/parameters.js"></script>
<!--<script src="core/headers.js"></script>-->
<!--<script src="core/regressions.js"></script>-->
<!--<script src="core/security.js"></script>-->
<!--<script src="core/shadowdom.js"></script>-->
<!--<script src="core/perf.js"></script>-->
<!--<script src="core/validation.js"></script>-->
<!--<script src="core/tokenizer.js"></script>-->
<!--<script src="core/extensions.js"></script>-->
<!--<script src="core/extension-swap.js"></script>-->
<script src="core/headers.js"></script>
<script src="core/regressions.js"></script>
<script src="core/security.js"></script>
<script src="core/shadowdom.js"></script>
<script src="core/perf.js"></script>
<script src="core/validation.js"></script>
<script src="core/tokenizer.js"></script>
<script src="core/extensions.js"></script>
<script src="core/extension-swap.js"></script>

<!--&lt;!&ndash;&lt;!&ndash; attribute tests &ndash;&gt;&ndash;&gt;-->
<!--<script src="attributes/hx-confirm.js"></script>-->
<!--<script src="attributes/hx-delete.js"></script>-->
<!--<script src="attributes/hx-ext.js"></script>-->
<!--<script src="attributes/hx-get.js"></script>-->
<!--<script src="attributes/hx-headers.js"></script>-->
<!--<script src="attributes/hx-history.js"></script>-->
<!--<script src="attributes/hx-include.js"></script>-->
<!--<script src="attributes/hx-indicator.js"></script>-->
<!--<script src="attributes/hx-disabled-elt.js"></script>-->
<!--<script src="attributes/hx-inherit.js"></script>-->
<!--<script src="attributes/hx-disinherit.js"></script>-->
<!--<script src="attributes/hx-params.js"></script>-->
<!--<script src="attributes/hx-patch.js"></script>-->
<!--<script src="attributes/hx-post.js"></script>-->
<!--<script src="attributes/hx-preserve.js"></script>-->
<!--<script src="attributes/hx-push-url.js"></script>-->
<!--<script src="attributes/hx-put.js"></script>-->
<!--<script src="attributes/hx-request.js"></script>-->
<!--<script src="attributes/hx-swap-oob.js"></script>-->
<!--<script src="attributes/hx-swap.js"></script>-->
<!--<script src="attributes/hx-error-swap.js"></script>-->
<!--<script src="attributes/hx-sync.js"></script>-->
<!--<script src="attributes/hx-target.js"></script>-->
<!--<script src="attributes/hx-error-target.js"></script>-->
<!--<script src="attributes/hx-trigger.js"></script>-->
<!--<script src="attributes/hx-vals.js"></script>-->
<!--<script src="attributes/hx-vars.js"></script>-->
<!--&lt;!&ndash; attribute tests &ndash;&gt;-->
<script src="attributes/hx-confirm.js"></script>
<script src="attributes/hx-delete.js"></script>
<script src="attributes/hx-ext.js"></script>
<script src="attributes/hx-get.js"></script>
<script src="attributes/hx-headers.js"></script>
<script src="attributes/hx-history.js"></script>
<script src="attributes/hx-include.js"></script>
<script src="attributes/hx-indicator.js"></script>
<script src="attributes/hx-disabled-elt.js"></script>
<script src="attributes/hx-inherit.js"></script>
<script src="attributes/hx-disinherit.js"></script>
<script src="attributes/hx-params.js"></script>
<script src="attributes/hx-patch.js"></script>
<script src="attributes/hx-post.js"></script>
<script src="attributes/hx-preserve.js"></script>
<script src="attributes/hx-push-url.js"></script>
<script src="attributes/hx-put.js"></script>
<script src="attributes/hx-request.js"></script>
<script src="attributes/hx-swap-oob.js"></script>
<script src="attributes/hx-swap.js"></script>
<script src="attributes/hx-error-swap.js"></script>
<script src="attributes/hx-sync.js"></script>
<script src="attributes/hx-target.js"></script>
<script src="attributes/hx-error-target.js"></script>
<script src="attributes/hx-trigger.js"></script>
<script src="attributes/hx-vals.js"></script>
<script src="attributes/hx-vars.js"></script>

<!--&lt;!&ndash; this hyperscript integration should be removed once its removed from the tests &ndash;&gt;-->
<!-- this hyperscript integration should be removed once its removed from the tests -->
<script src="lib/_hyperscript.js"></script>

<!--&lt;!&ndash; events last so they don't screw up other tests &ndash;&gt;-->
<!--<script src="core/events.js"></script>-->
<!-- events last so they don't screw up other tests -->
<script src="core/events.js"></script>

<div id="mocha"></div>

Expand Down

0 comments on commit 36a6181

Please sign in to comment.