Thanks to visit codestin.com
Credit goes to Github.com

Skip to content

Ideas for attributes #16

@kevinrenskers

Description

@kevinrenskers

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

Screen Shot 2021-02-10 at 11 15 07

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

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions