First of all, thanks for this library! I've only started to dig in recently, but am enjoying the syntax and the fact that I can just use map, compactMap, if statements etc, without having to worry about wrapping things in .group
or .fragment
types (as Plot does), or the need for a custom forEach
element (like Vaux).
My biggest issue with this library however is that working with tag attributes is rather painful. The autocomplete is useless since there are so many parameters, of which the order actually matters.
![Screen Shot 2021-02-10 at 11 15 22](https://user-images.githubusercontent.com/229384/107497254-a4403280-6b92-11eb-82c2-7ab7fe9da64d.png)
![Screen Shot 2021-02-10 at 11 15 07](https://user-images.githubusercontent.com/229384/107497260-a609f600-6b92-11eb-8e81-7347ba177261.png)
Here's my initial idea for an improvement:
public enum Attribute {
case `class`(String)
case id(String)
case custom(String, String)
var name: String {
switch self {
case .class:
return "class"
case .id:
return "id"
case .custom(let name, _):
return name
}
}
var value: String {
switch self {
case .class(let value):
return value
case .id(let value):
return value
case .custom(_, let value):
return value
}
}
}
public func p(_ attributes: Attribute...,
@NodeBuilder children: () -> NodeConvertible = { Node.fragment([]) }
) -> Node {
let attributesDict = attributes.reduce([:]) { (result, next) -> [String: String] in
var result = result
result[next.name] = next.value
return result
}
return .element("p", attributesDict, children().asNode())
}
The result:
p(.id("id"), .class("class"), .custom("data-whatever", "foobar")) {
"Hello"
}
This way you have simple autocompletion (just type .
and you get a list of attributes), and the order doesn't matter. Of course in this simple naive way, all attributes would be available to be used on all tags, which is not ideal. But hey, this was my first idea which doesn't require a big architecture change of Swim.
Plot solves the same problem with protocols:
![Screen Shot 2021-02-10 at 11 30 32](https://user-images.githubusercontent.com/229384/107497910-6b548d80-6b93-11eb-908c-df9699df55ab.png)
Vaux on the other hand goes a different route: you add attributes onto a tag:
div {
paragraph { "Hello" }
}.class("article")
To be honest I'm not a big fan of that approach, it's much less readable in a long(ish) tree of tags. But the idea is the same: you limit what kind of attributes you can use per "type of tag", without having all those repeated function parameters in every function.
Finally, swift-html also uses an array of enum cases:
.div(
attributes: [.class("article")],
.p("Hello there")
)
If you look at the function definition of div
, you can see their solution to limit which attributes can be used:
div(attributes: [Html.Attribute<Html.Tag.Div>] = [], _ content: Html.Node...) -> Html.Node
And that seems to be a winner to me? So the basic idea is this:
struct Attribute<Node> {
let key: String
let value: String?
init(_ key: String, _ value: String?) {
self.key = key
self.value = value
}
}
extension Attribute {
static func id(_ value: String) -> Attribute {
return .init("id", value)
}
}
extension Attribute where Node: Linkable {
static func href(_ value: String) -> Attribute {
return .init("href", value)
}
}
I'm curious to see what you think. Have the very long function signatures bothered you as well?