Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Tag easier to subclass #35

Open
wants to merge 159 commits into
base: main
Choose a base branch
from
Open

Make Tag easier to subclass #35

wants to merge 159 commits into from

Conversation

bgisme
Copy link
Contributor

@bgisme bgisme commented Jul 11, 2023

This pull request attempts to make Tag more straightforward to subclass.

Right now, if you take a basic html tag like Div and customize it, the rendered tag name will be the same as your custom subclass.

Example...
class MyClass: Div { }

Renders as...
<myclass></myclass>

So if you want to subclass an html tag like Div and keep that name, you have to add extra code.

class MyClass: Div {
     override open class func createNode() -> Node {
         Node(type: .standard, name: "div")
    }
}

This seems like overkill. And prone to mistakes. Like changing the superclass from 'Div' to 'P' and then forgetting to change the node name to match.

The proposed code changes try to fix this. And in the process, make customizing a tag name simple—either take the name of your subclass or override one class variable.

The HTML tags still use their own class name as the node name, to prevent typos. And all subclasses get it for free.

The code changes also try to make Tag more accommodating to complicated setups.

Here are the different ways to use Tag from simple to complex.

  1. Subclass Tag and it will render with your class name lowercased.
class MyTag: Tag { }

// <mytag></mytag>
  1. Subclass Tag and override the name property.
class MyTag: Tag { 

    override open class var name: String { "myTag" }
}

// <myTag></myTag>
  1. Subclass Tag and override the node property.
class MyTag: Tag {
    
    override open class var node: Node { .init(type: .empty, name: "myTag") }
}

// <myTag>
  1. Subclass Tag, create your own custom initializer and then call the Tag designated initializer.
class MyTag: Tag {

    init(myAttributeValue: String, @TagBuilder _ builder: () -> Tag) {
        let attribute = .init(key: "myKey", value: myAttributeValue)
        let node = Node(type: .empty, name: "myTag", attributes: [attribute])
        super.init(node: node, [builder()])
    }
}

A few other subtle but important changes...

Node names are no longer optional. There were some forced unwraps in DocumentRenderer that seemed like a bad place to crash. And since Tag automatically generates a name, leaving it optional seemed unnecessary.

• An extension was added to String for init() with a class name. This is used in all the html tags to convert their class names into node names. And since the class names are type checked, it helps prevent typos if the class name changes.

• A new intermediate class TypedTag was created for subclasses with a specific node type: .standard, .empty, .comment, .group. It seemed appropriate to keep all their initializers in one file instead of many. So now there's a subclass for each node type: GroupTag, EmptyTag, StandardTag, CommentTag. And these make reading the html tags a little more clear. You can immediately tell how the tag will render...

class Div: StandardTag { }

class A: EmptyTag { }

bgisme added 30 commits July 27, 2023 12:32
Other Global Attributes... add nil values and conditions
Conflicts:
	Sources/SwiftHtml/Attributes/Events.swift
	Sources/SwiftHtml/Tags/Body.swift
	Sources/SwiftHtml/Tags/Head.swift
	Sources/SwiftHtml/Tags/Html.swift
	Sources/SwiftSgml/Tag.swift
	Tests/SwiftHtmlTests/SwiftHtmlTests.swift
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants