Skip to content

Commit

Permalink
Boolean attributes (#68)
Browse files Browse the repository at this point in the history
* Change `required` and `autofocus` to be boolean attributes.
* Added `readonly`, `disabled`, and `multiple` boolean attributes to `input` elements.
* Added `readonly ` and `disabled ` attributes to the `textarea` element.
* Added a `placeholder` attribute to the `textarea` element.
* Changed the `allowfullscreen` attribute on the `iframe` element to be a boolean attribute.
* Added an `open` boolean attribute to the `details` element.
* Added the `hidden` boolean attribute as a global attribute.
* Added `checked` boolean attribute to the `input` element.
* Added a `name` to the `file` input element in the form test.
  • Loading branch information
daveverwer authored Jun 1, 2021
1 parent 0320a8d commit c27f685
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 18 deletions.
70 changes: 64 additions & 6 deletions Sources/Plot/API/HTMLAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ public extension Node where Context: HTMLContext {
static func title(_ title: String) -> Node {
.attribute(named: "title", value: title)
}

/// Assign whether the element should be hidden.
/// - parameter isHidden: Whether the element should be hidden or not.
static func hidden(_ isHidden: Bool) -> Node {
isHidden ? .attribute(named: "hidden") : .empty
}
}

public extension Attribute where Context: HTMLNamableContext {
Expand Down Expand Up @@ -185,6 +191,16 @@ public extension Node where Context == HTML.AnchorContext {
}
}

// MARK: - Interactive elements

public extension Node where Context == HTML.DetailsContext {
/// Assign whether the details element is opened/expanded.
/// - parameter isOpen: Whether the element should be displayed as open.
static func open(_ isOpen: Bool) -> Node {
isOpen ? .attribute(named: "open") : .empty
}
}

// MARK: - Sources and media

public extension Attribute where Context: HTMLSourceContext {
Expand Down Expand Up @@ -286,7 +302,7 @@ public extension Attribute where Context == HTML.InputContext {
Attribute(name: "type", value: type.rawValue)
}

/// Assigns a placeholder to the input field.
/// Assign a placeholder to the input field.
/// - parameter placeholder: The placeholder to assign.
static func placeholder(_ placeholder: String) -> Attribute {
Attribute(name: "placeholder", value: placeholder)
Expand All @@ -301,13 +317,37 @@ public extension Attribute where Context == HTML.InputContext {
/// Assign whether the element is required before submitting the form.
/// - parameter isRequired: Whether the element is required.
static func required(_ isRequired: Bool) -> Attribute {
isRequired ? Attribute(name: "required", value: "true") : .empty
isRequired ? Attribute(name: "required", value: nil, ignoreIfValueIsEmpty: false) : .empty
}

/// Assign whether the element should be autofocused when the page loads.
/// - parameter isOn: Whether autofocus should be turned on.
static func autofocus(_ isOn: Bool) -> Attribute {
isOn ? Attribute(name: "autofocus", value: "true") : .empty
isOn ? Attribute(name: "autofocus", value: nil, ignoreIfValueIsEmpty: false) : .empty
}

/// Assign whether the element should be read-only.
/// - parameter isReadonly: Whether the input is read-only.
static func readonly(_ isReadonly: Bool) -> Attribute {
isReadonly ? Attribute(name: "readonly", value: nil, ignoreIfValueIsEmpty: false) : .empty
}

/// Assign whether the element should be disabled.
/// - parameter isDisabled: Whether the input is disabled.
static func disabled(_ isDisabled: Bool) -> Attribute {
isDisabled ? Attribute(name: "disabled", value: nil, ignoreIfValueIsEmpty: false) : .empty
}

/// Assign whether the element should allow the selection of multiple values.
/// - parameter isMultiple: Whether multiple values are allowed.
static func multiple(_ isEnabled: Bool) -> Attribute {
isEnabled ? Attribute(name: "multiple", value: nil, ignoreIfValueIsEmpty: false) : .empty
}

/// Assign whether a checkbox or radio input element has an active state.
/// - parameter isChecked: Whether the element has an active state.
static func checked(_ isChecked: Bool) -> Attribute {
isChecked ? Attribute(name: "checked", value: nil, ignoreIfValueIsEmpty: false) : .empty
}
}

Expand All @@ -332,16 +372,34 @@ public extension Node where Context == HTML.TextAreaContext {
.attribute(named: "rows", value: String(rows))
}

/// Assign a placeholder to the text area.
/// - parameter placeholder: The placeholder to assign.
static func placeholder(_ placeholder: String) -> Node {
.attribute(named: "placeholder", value: placeholder)
}

/// Assign whether the element is required before submitting the form.
/// - parameter isRequired: Whether the element is required.
static func required(_ isRequired: Bool) -> Node {
isRequired ? .attribute(named: "required", value: "true") : .empty
isRequired ? .attribute(named: "required") : .empty
}

/// Assign whether the element should be autofocused when the page loads.
/// - parameter isOn: Whether autofocus should be turned on.
static func autofocus(_ isOn: Bool) -> Node {
isOn ? .attribute(named: "autofocus", value: "true") : .empty
isOn ? .attribute(named: "autofocus") : .empty
}

/// Assign whether the element should be read-only.
/// - parameter isReadonly: Whether the input is read-only.
static func readonly(_ isReadonly: Bool) -> Node {
isReadonly ? .attribute(named: "readonly") : .empty
}

/// Assign whether the element should be disabled.
/// - parameter isDisabled: Whether the input is disabled.
static func disabled(_ isDisabled: Bool) -> Node {
isDisabled ? .attribute(named: "disabled") : .empty
}
}

Expand Down Expand Up @@ -423,7 +481,7 @@ public extension Attribute where Context == HTML.IFrameContext {
/// Assign whether to grant the iframe full screen capabilities.
/// - parameter allow: Whether the iframe should be allowed to go full screen.
static func allowfullscreen(_ allow: Bool) -> Attribute {
Attribute(name: "allowfullscreen", value: String(allow))
allow ? Attribute(name: "allowfullscreen", value: nil, ignoreIfValueIsEmpty: false) : .empty
}
}

Expand Down
4 changes: 2 additions & 2 deletions Tests/PlotTests/HTMLComponentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ final class HTMLComponentTests: XCTestCase {
<form action="url.com" method="post">\
<fieldset>\
<label>Username\
<input type="text" name="username" required="true" autofocus="true" autocomplete="off"/>\
<input type="text" name="username" required autofocus autocomplete="off"/>\
</label>\
<label class="password-label">Password\
<input type="password" name="password" class="password-input"/>\
Expand All @@ -386,7 +386,7 @@ final class HTMLComponentTests: XCTestCase {
.render()

XCTAssertEqual(html, """
<iframe src="url.com" frameborder="0" allowfullscreen="true" allow="gyroscope"></iframe>
<iframe src="url.com" frameborder="0" allowfullscreen allow="gyroscope"></iframe>
""")
}

Expand Down
44 changes: 34 additions & 10 deletions Tests/PlotTests/HTMLTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ final class HTMLTests: XCTestCase {
assertEqualHTMLContent(html, #"<body class="b"></body>"#)
}

func testHiddenElements() {
let html = HTML(.body(
.div(.hidden(false)),
.div(.hidden(true))
))
assertEqualHTMLContent(html, "<body><div></div><div hidden></div></body>")
}

func testTitleAttribute() {
let html = HTML(
.head(
Expand Down Expand Up @@ -347,10 +355,14 @@ final class HTMLTests: XCTestCase {
.input(.name("a"), .type(.text))
),
.input(.name("b"), .type(.search), .autocomplete(false), .autofocus(true)),
.input(.name("c"), .type(.text), .autofocus(false)),
.input(.name("c"), .type(.text), .autofocus(false), .readonly(false), .disabled(false)),
.input(.name("d"), .type(.email), .placeholder("email address"), .autocomplete(true), .required(true)),
.textarea(.name("e"), .cols(50), .rows(10), .required(true), .text("Test")),
.textarea(.name("f"), .autofocus(true)),
.input(.name("e"), .type(.text), .readonly(true), .disabled(true)),
.textarea(.name("f"), .cols(50), .rows(10), .required(true), .text("Test")),
.textarea(.name("g"), .autofocus(true), .placeholder("Placeholder"), .readonly(false), .disabled(false)),
.textarea(.name("h"), .readonly(true), .disabled(true), .text("Test")),
.input(.name("i"), .type(.checkbox), .checked(true)),
.input(.name("j"), .type(.file), .multiple(true)),
.input(.type(.submit), .value("Send"))
)
))
Expand All @@ -361,11 +373,15 @@ final class HTMLTests: XCTestCase {
<label for="a">A label</label>\
<input name="a" type="text"/>\
</fieldset>\
<input name="b" type="search" autocomplete="off" autofocus="true"/>\
<input name="b" type="search" autocomplete="off" autofocus/>\
<input name="c" type="text"/>\
<input name="d" type="email" placeholder="email address" autocomplete="on" required="true"/>\
<textarea name="e" cols="50" rows="10" required="true">Test</textarea>\
<textarea name="f" autofocus="true"></textarea>\
<input name="d" type="email" placeholder="email address" autocomplete="on" required/>\
<input name="e" type="text" readonly disabled/>\
<textarea name="f" cols="50" rows="10" required>Test</textarea>\
<textarea name="g" autofocus placeholder="Placeholder"></textarea>\
<textarea name="h" readonly disabled>Test</textarea>\
<input name="i" type="checkbox" checked/>\
<input name="j" type="file" multiple/>\
<input type="submit" value="Send"/>\
</form></body>
""")
Expand Down Expand Up @@ -577,13 +593,17 @@ final class HTMLTests: XCTestCase {
.src("url.com"),
.frameborder(false),
.allow("gyroscope"),
.allowfullscreen(false)
),
.iframe(
.allowfullscreen(true)
)
))

assertEqualHTMLContent(html, """
<body>\
<iframe src="url.com" frameborder="0" allow="gyroscope" allowfullscreen="true"></iframe>\
<iframe src="url.com" frameborder="0" allow="gyroscope"></iframe>\
<iframe allowfullscreen></iframe>\
</body>
""")
}
Expand Down Expand Up @@ -657,11 +677,15 @@ final class HTMLTests: XCTestCase {

func testDetails() {
let html = HTML(.body(
.details(.summary("Summary"), .p("Text"))
.details(.open(true), .summary("Open Summary"), .p("Text")),
.details(.open(false), .summary("Closed Summary"), .p("Text"))
))

assertEqualHTMLContent(html, """
<body><details><summary>Summary</summary><p>Text</p></details></body>
<body>\
<details open><summary>Open Summary</summary><p>Text</p></details>\
<details><summary>Closed Summary</summary><p>Text</p></details>\
</body>
""")
}

Expand Down

0 comments on commit c27f685

Please sign in to comment.