From 4529645a754ae4625fec70d958172af8346cbd82 Mon Sep 17 00:00:00 2001 From: Shagen Ogandzhanian Date: Tue, 15 Jun 2021 13:24:43 +0200 Subject: [PATCH] Don't allow to assign null value in attr (#780) * Remove atrributes completely and reapply everything Rather than keeping map with attributes applied * Don't allow nullable signatures while calling attr --- .../com/sample/content/CodeSamplesSwitcher.kt | 2 +- .../com/sample/content/CodeSamplesSwitcher.kt | 4 +- .../kotlin/androidx/compose/web/DomApplier.kt | 14 +-- .../androidx/compose/web/attributes/Attrs.kt | 106 +++++++++--------- .../compose/web/attributes/AttrsBuilder.kt | 9 +- .../androidx/compose/web/elements/Elements.kt | 8 +- .../jsTest/kotlin/elements/AttributesTests.kt | 4 +- .../compose/web/sample/tests/InputsTests.kt | 4 +- 8 files changed, 76 insertions(+), 75 deletions(-) diff --git a/examples/web_landing/src/jsMain/kotlin/com/sample/content/CodeSamplesSwitcher.kt b/examples/web_landing/src/jsMain/kotlin/com/sample/content/CodeSamplesSwitcher.kt index 05152b8496..c9e3c8ea01 100644 --- a/examples/web_landing/src/jsMain/kotlin/com/sample/content/CodeSamplesSwitcher.kt +++ b/examples/web_landing/src/jsMain/kotlin/com/sample/content/CodeSamplesSwitcher.kt @@ -80,7 +80,7 @@ fun CodeSampleSwitcher(count: Int, current: Int, onSelect: (Int) -> Unit) { Input(type = InputType.Radio, value = "snippet$ix", attrs = { name("code-snippet") id("snippet$ix") - if (current == ix) checked(true) + if (current == ix) checked() onRadioInput { onSelect(ix) } }) Label(forId = "snippet$ix") { Text("${ix + 1}") } diff --git a/web/benchmark-core/src/jsMain/kotlin/com/sample/content/CodeSamplesSwitcher.kt b/web/benchmark-core/src/jsMain/kotlin/com/sample/content/CodeSamplesSwitcher.kt index 59e9595be3..b5f8a51897 100644 --- a/web/benchmark-core/src/jsMain/kotlin/com/sample/content/CodeSamplesSwitcher.kt +++ b/web/benchmark-core/src/jsMain/kotlin/com/sample/content/CodeSamplesSwitcher.kt @@ -74,10 +74,10 @@ fun CodeSampleSwitcher(count: Int, current: Int, onSelect: (Int) -> Unit) { Input(type = InputType.Radio, value = "snippet$ix", attrs = { name("code-snippet") id("snippet$ix") - if (current == ix) checked(true) + if (current == ix) checked() onRadioInput { onSelect(ix) } }) Label(forId = "snippet$ix") { Text("${ix + 1}") } } } -} \ No newline at end of file +} diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt index 1ff40f5964..29591f8679 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/DomApplier.kt @@ -89,15 +89,13 @@ open class DomNodeWrapper(open val node: Node) { class DomElementWrapper(override val node: HTMLElement): DomNodeWrapper(node) { - private var currentAttrs = emptyMap() - - fun updateAttrs(attrs: Map) { - currentAttrs.forEach { - node.removeAttribute(it.key) + fun updateAttrs(attrs: Map) { + while (node.attributes.length > 0) { + node.removeAttributeNode(node.attributes[0]!!) } - currentAttrs = attrs - currentAttrs.forEach { - if (it.value != null) node.setAttribute(it.key, it.value ?: "") + + attrs.forEach { + node.setAttribute(it.key, it.value) } } diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt index 7c7c425e15..a0b982dfaa 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/Attrs.kt @@ -13,7 +13,7 @@ import org.w3c.dom.HTMLTableCellElement import org.w3c.dom.HTMLTableColElement import org.w3c.dom.HTMLTextAreaElement -fun AttrsBuilder.href(value: String?) = +fun AttrsBuilder.href(value: String) = attr("href", value) fun AttrsBuilder.target(value: ATarget = ATarget.Self) = @@ -36,11 +36,11 @@ fun AttrsBuilder.download(value: String = "") = /* Button attributes */ -fun AttrsBuilder.autoFocus(value: Boolean = true) = - attr("autofocus", if (value) "" else null) +fun AttrsBuilder.autoFocus() = + attr("autofocus", "") -fun AttrsBuilder.disabled(value: Boolean = true) = - attr("disabled", if (value) "" else null) +fun AttrsBuilder.disabled() = + attr("disabled", "") fun AttrsBuilder.form(formId: String) = attr("form", formId) @@ -54,8 +54,8 @@ fun AttrsBuilder.formEncType(value: ButtonFormEncType) = fun AttrsBuilder.formMethod(value: ButtonFormMethod) = attr("formmethod", value.methodStr) -fun AttrsBuilder.formNoValidate(value: Boolean = true) = - attr("formnovalidate", if (value) "" else null) +fun AttrsBuilder.formNoValidate() = + attr("formnovalidate", "") fun AttrsBuilder.formTarget(value: ButtonFormTarget) = attr("formtarget", value.targetStr) @@ -77,8 +77,8 @@ fun AttrsBuilder.action(value: String) = fun AttrsBuilder.acceptCharset(value: String) = attr("accept-charset", value) -fun AttrsBuilder.autoComplete(value: Boolean) = - attr("autocomplete", if (value) "" else null) +fun AttrsBuilder.autoComplete() = + attr("autocomplete", "") fun AttrsBuilder.encType(value: FormEncType) = attr("enctype", value.typeStr) @@ -86,8 +86,8 @@ fun AttrsBuilder.encType(value: FormEncType) = fun AttrsBuilder.method(value: FormMethod) = attr("method", value.methodStr) -fun AttrsBuilder.noValidate(value: Boolean = true) = - attr("novalidate", if (value) "" else null) +fun AttrsBuilder.noValidate() = + attr("novalidate", "") fun AttrsBuilder.target(value: FormTarget) = attr("target", value.targetStr) @@ -103,23 +103,23 @@ fun AttrsBuilder.accept(value: String) = fun AttrsBuilder.alt(value: String) = attr("alt", value) // type: image only -fun AttrsBuilder.autoComplete(value: Boolean = true) = - attr("autocomplete", if (value) "" else null) +fun AttrsBuilder.autoComplete() = + attr("autocomplete", "") -fun AttrsBuilder.autoFocus(value: Boolean = true) = - attr("autofocus", if (value) "" else null) +fun AttrsBuilder.autoFocus() = + attr("autofocus", "") fun AttrsBuilder.capture(value: String) = attr("capture", value) // type: file only -fun AttrsBuilder.checked(value: Boolean = true) = - attr("checked", if (value) "" else null) // radio, checkbox +fun AttrsBuilder.checked() = + attr("checked", "") // radio, checkbox fun AttrsBuilder.dirName(value: String) = attr("dirname", value) // text, search -fun AttrsBuilder.disabled(value: Boolean = true) = - attr("disabled", if (value) "" else null) +fun AttrsBuilder.disabled() = + attr("disabled", "") fun AttrsBuilder.form(id: String) = attr("form", id) @@ -133,8 +133,8 @@ fun AttrsBuilder.formEncType(value: InputFormEncType) = fun AttrsBuilder.formMethod(value: InputFormMethod) = attr("formmethod", value.methodStr) -fun AttrsBuilder.formNoValidate(value: Boolean = true) = - attr("formnovalidate", if (value) "" else null) +fun AttrsBuilder.formNoValidate() = + attr("formnovalidate", "") fun AttrsBuilder.formTarget(value: InputFormTarget) = attr("formtarget", value.targetStr) @@ -160,8 +160,8 @@ fun AttrsBuilder.min(value: String) = fun AttrsBuilder.minLength(value: Int) = attr("minlength", value.toString()) -fun AttrsBuilder.multiple(value: Boolean = true) = - attr("multiple", if (value) "" else null) +fun AttrsBuilder.multiple() = + attr("multiple", "") fun AttrsBuilder.name(value: String) = attr("name", value) @@ -172,8 +172,8 @@ fun AttrsBuilder.pattern(value: String) = fun AttrsBuilder.placeholder(value: String) = attr("placeholder", value) -fun AttrsBuilder.readOnly(value: Boolean = true) = - attr("readonly", if (value) "" else null) +fun AttrsBuilder.readOnly() = + attr("readonly", "") fun AttrsBuilder.required(value: Boolean = true) = attr("required", value.toString()) @@ -182,7 +182,7 @@ fun AttrsBuilder.size(value: Int) = attr("size", value.toString()) fun AttrsBuilder.src(value: String) = - attr("src", value.toString()) // image only + attr("src", value) // image only fun AttrsBuilder.step(value: Int) = attr("step", value.toString()) // numeric types only @@ -200,11 +200,11 @@ fun AttrsBuilder.value(value: String): AttrsBuilder.value(value: String) = attr("value", value) -fun AttrsBuilder.disabled(value: Boolean = true) = - attr("disabled", if (value) "" else null) +fun AttrsBuilder.disabled() = + attr("disabled", "") -fun AttrsBuilder.selected(value: Boolean = true) = - attr("selected", if (value) "" else null) +fun AttrsBuilder.selected() = + attr("selected", "") fun AttrsBuilder.label(value: String) = attr("label", value) @@ -214,23 +214,23 @@ fun AttrsBuilder.label(value: String) = fun AttrsBuilder.autocomplete(value: String) = attr("autocomplete", value) -fun AttrsBuilder.autofocus(value: Boolean = true) = - attr("autofocus", if (value) "" else null) +fun AttrsBuilder.autofocus() = + attr("autofocus", "") -fun AttrsBuilder.disabled(value: Boolean = true) = - attr("disabled", if (value) "" else null) +fun AttrsBuilder.disabled() = + attr("disabled", "") fun AttrsBuilder.form(formId: String) = attr("form", formId) -fun AttrsBuilder.multiple(value: Boolean = true) = - attr("multiple", if (value) "" else null) +fun AttrsBuilder.multiple() = + attr("multiple", "") fun AttrsBuilder.name(value: String) = attr("name", value) -fun AttrsBuilder.required(value: Boolean = true) = - attr("required", if (value) "" else null) +fun AttrsBuilder.required() = + attr("required", "") fun AttrsBuilder.size(numberOfRows: Int) = attr("size", numberOfRows.toString()) @@ -240,22 +240,22 @@ fun AttrsBuilder.size(numberOfRows: Int) = fun AttrsBuilder.label(value: String) = attr("label", value) -fun AttrsBuilder.disabled(value: Boolean = true) = - attr("disabled", if (value) "" else null) +fun AttrsBuilder.disabled() = + attr("disabled", "") /* TextArea attributes */ fun AttrsBuilder.autoComplete(value: Boolean = true) = attr("autocomplete", if (value) "on" else "off") -fun AttrsBuilder.autoFocus(value: Boolean = true) = - attr("autofocus", if (value) "" else null) +fun AttrsBuilder.autoFocus() = + attr("autofocus", "") fun AttrsBuilder.cols(value: Int) = attr("cols", value.toString()) -fun AttrsBuilder.disabled(value: Boolean = true) = - attr("disabled", if (value) "" else null) +fun AttrsBuilder.disabled() = + attr("disabled", "") fun AttrsBuilder.form(formId: String) = attr("form", formId) @@ -272,11 +272,11 @@ fun AttrsBuilder.name(value: String) = fun AttrsBuilder.placeholder(value: String) = attr("placeholder", value) -fun AttrsBuilder.readOnly(value: Boolean = true) = - attr("readonly", if (value) "" else null) +fun AttrsBuilder.readOnly() = + attr("readonly", "") -fun AttrsBuilder.required(value: Boolean = true) = - attr("required", if (value) "" else null) +fun AttrsBuilder.required() = + attr("required", "") fun AttrsBuilder.rows(value: Int) = attr("rows", value.toString()) @@ -291,10 +291,10 @@ fun AttrsBuilder.value(value: String): AttrsBuilder.src(value: String?): AttrsBuilder = +fun AttrsBuilder.src(value: String): AttrsBuilder = attr("src", value) -fun AttrsBuilder.alt(value: String?): AttrsBuilder = +fun AttrsBuilder.alt(value: String): AttrsBuilder = attr("alt", value) private val setInputValue: (HTMLInputElement, String) -> Unit = { e, v -> @@ -302,15 +302,15 @@ private val setInputValue: (HTMLInputElement, String) -> Unit = { e, v -> } /* Img attributes */ -fun AttrsBuilder.forId(value: String?): AttrsBuilder = +fun AttrsBuilder.forId(value: String): AttrsBuilder = attr("for", value) /* Table attributes */ fun AttrsBuilder.span(value: Int): AttrsBuilder = attr("span", value.toString()) -fun AttrsBuilder.scope(value: Scope?): AttrsBuilder = - attr("scope", value?.str) +fun AttrsBuilder.scope(value: Scope): AttrsBuilder = + attr("scope", value.str) fun AttrsBuilder.colspan(value: Int): AttrsBuilder = attr("colspan", value.toString()) diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt index 4fd6d190ae..2fc7a78283 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/attributes/AttrsBuilder.kt @@ -34,13 +34,8 @@ class AttrsBuilder : EventsListenerBuilder() { this.refEffect = effect } - fun attr(attr: String, value: String?): AttrsBuilder { - if (value == null) { - attributesMap.remove(attr) - } else { - attributesMap[attr] = value - } - + fun attr(attr: String, value: String): AttrsBuilder { + attributesMap[attr] = value return this } diff --git a/web/core/src/jsMain/kotlin/androidx/compose/web/elements/Elements.kt b/web/core/src/jsMain/kotlin/androidx/compose/web/elements/Elements.kt index 7f781367cb..c3f5635842 100644 --- a/web/core/src/jsMain/kotlin/androidx/compose/web/elements/Elements.kt +++ b/web/core/src/jsMain/kotlin/androidx/compose/web/elements/Elements.kt @@ -347,7 +347,9 @@ fun A( TagElement( elementBuilder = ElementBuilder.A, applyAttrs = { - href(href) + if (href != null) { + this.href(href) + } attrs() }, content = content @@ -645,7 +647,9 @@ fun Label( TagElement( elementBuilder = ElementBuilder.Label, applyAttrs = { - forId(forId) + if (forId != null) { + forId(forId) + } attrs() }, content = content diff --git a/web/core/src/jsTest/kotlin/elements/AttributesTests.kt b/web/core/src/jsTest/kotlin/elements/AttributesTests.kt index 587b04dd63..c8a6d9aae8 100644 --- a/web/core/src/jsTest/kotlin/elements/AttributesTests.kt +++ b/web/core/src/jsTest/kotlin/elements/AttributesTests.kt @@ -68,7 +68,9 @@ class AttributesTests { composition { Button( { - disabled(disabled) + if (disabled) { + disabled() + } } ) {} } diff --git a/web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/tests/InputsTests.kt b/web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/tests/InputsTests.kt index 5a1c41d98e..53a88f11c4 100644 --- a/web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/tests/InputsTests.kt +++ b/web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/tests/InputsTests.kt @@ -49,7 +49,9 @@ class InputsTests { type = InputType.Checkbox, attrs = { id("checkbox") - checked(checked) + if (checked) { + checked() + } onCheckboxInput { checked = !checked }