diff --git a/README.md b/README.md index ce008bad..41da32c9 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ # HTMLKit -Render **lightning fast** HTML templates in a *typesafe* way! +Render dynamic HTML templates in a *typesafe* and **performant** way! By using Swift's powerful language features and a pre-rendering algorithm, HTMLKit will render insanely fast templates but also catch bugs that otherwise might occur with other templating options. ## Getting Started Add the following in your `Package.swift` file ```swift -.package(url: "https://github.com/vapor-community/HTMLKit.git", from: "2.0.0-alpha.1"), +.package(url: "https://github.com/vapor-community/HTMLKit.git", from: "2.0.0-alpha.9"), ``` And register the provider and the different templates with in `configure.swift` ```swift @@ -25,22 +25,44 @@ services.register(renderer) ## Usage -To create a HTML component, conform to the `HTMLComponent` protocol. Look at the example above for an Alert component. +To create a HTML template, conform to the `HTMLTemplate` protocol. ```swift -public struct Alert: HTMLComponent { +struct SimpleTemplate: HTMLTemplate { - let message: HTML - let isDisimissable: Conditionable + @TemplateValue(String?.self) + var context - public init(isDisimissable: Conditionable = true, @HTMLBuilder message: () -> HTML) { - self.isDisimissable = isDisimissable - self.message = message() + var body: HTML { + Document(type: .html5) { + Head { + Title { context } + } + Body { + Unwrap(context) { string in + P { string } + } + } + } } +} + +... +try SimpleTemplate().render(with: "Some string", for: req) +``` + +And to create a HTML component, just comform to the `HTMLComponent` protocol. + +```swift +public struct Alert: HTMLComponent { + + let isDisimissable: Conditionable // This is a protocol that makes it possible to optimize if's + let message: HTML public var body: HTML { Div { message + IF(isDisimissable) { Button { Span { "×" } @@ -48,18 +70,21 @@ public struct Alert: HTMLComponent { } .type(.button) .class("close") - .data(for: "dismiss", value: "alert") - .aria(for: "label", value: "Close") + .data("dismiss", value: "alert") + .aria("label", value: "Close") } } - .class("alert alert-danger bg-danger" + IF(isDisimissable) { " fade show" }) + .class("alert alert-danger bg-danger") + .modify(if: isDisimissable) { + $0.class("fade show") + } .role("alert") } } ``` -This can easily be used in another template or a static html page like: +This can then be used in another template or a static html page like: ```swift -struct SomePage: StaticView { +struct SomePage: HTMLPage { var body: HTML { Div { @@ -71,31 +96,6 @@ struct SomePage: StaticView { } ``` -If you need a page that changes based on runtime given values. Then a `TemplateView` could be more appropriate. Here you will need to pass the context when rendering. - -```swift - -struct DummyPage: TemplateView { - - struct Context { - let string: String - let int: Int? - } - - var body: HTML { - Div { - P { context.string } - IF(context.int.isDefined) { - Small { context.int } - } - } - } -} -... -let context = DummyPage.Context(string: "Some string", int: nil) -try DummyPage().render(with: context, for: req) -``` - ## Mixing HTMLKit with Leaf You can easily mix Leaf and HTMLKit in the same project. @@ -136,7 +136,7 @@ try renderer.registerLocalization(atPath: "workDir", defaultLocale: "en") And if the locale changes based on some user input, then you can change the used locale in the template. This also effects how dates are presentet to the user. ```swift -struct LocalizedDateView: TemplateView { +struct LocalizedDateView: HTMLTemplate { struct Context { let date: Date diff --git a/Sources/HTMLKit/AttributeNode.swift b/Sources/HTMLKit/AttributeNode.swift index d7566e2c..67ced776 100644 --- a/Sources/HTMLKit/AttributeNode.swift +++ b/Sources/HTMLKit/AttributeNode.swift @@ -33,6 +33,10 @@ public protocol GlobalAttributes { func aria(for key: String, value: HTML) -> Self + func data(_ key: String, value: HTML) -> Self + + func aria(_ key: String, value: HTML) -> Self + func style(css: HTML) -> Self /// The contenteditable attribute specifies whether the content of an element is editable or not. @@ -100,6 +104,10 @@ extension GlobalAttributes where Self: AttributeNode { add(HTMLAttribute(attribute: "aria-" + key, value: value)) } + public func aria(_ key: String, value: HTML) -> Self { + self.aria(for: key, value: value) + } + public func accessKey(_ value: HTML) -> Self { add(HTMLAttribute(attribute: "accessKey", value: value)) } @@ -112,6 +120,10 @@ extension GlobalAttributes where Self: AttributeNode { add(HTMLAttribute(attribute: "data-" + key, value: value)) } + public func data(_ key: String, value: HTML) -> Self { + self.data(for: key, value: value) + } + public func isEditable(_ value: Conditionable) -> Self { add(HTMLAttribute(attribute: "contenteditable", value: value)) } diff --git a/Sources/HTMLKit/ForEach.swift b/Sources/HTMLKit/ForEach.swift index 16e1bed2..0d99ec7e 100644 --- a/Sources/HTMLKit/ForEach.swift +++ b/Sources/HTMLKit/ForEach.swift @@ -125,7 +125,7 @@ extension TemplateValue where Value: Sequence { } extension Sequence { - public func forEachHTML(@HTMLBuilder content: (RootValue) -> HTML) -> HTML { + public func htmlForEach(@HTMLBuilder content: (RootValue) -> HTML) -> HTML { ForEach(in: .constant(self), content: content) } } diff --git a/Sources/HTMLKit/Markdown.swift b/Sources/HTMLKit/Markdown.swift deleted file mode 100644 index 8d82b24f..00000000 --- a/Sources/HTMLKit/Markdown.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Markdown.swift -// HTMLKit -// -// Created by Mats Mollestad on 16/04/2019. -// -// -//import SwiftMarkdown -// -///// A compiled template that converts markdown to html -//public struct Markdown: View { -// -// /// The markdown to convert -// let markdown: View -// -// /// The rendering options -// let options: MarkdownOptions -// -// public init(options: MarkdownOptions = [.safe, .normalize], @HTMLBuilder content: () -> View) { -// self.options = options -// self.markdown = content() -// } -// -// // View `Brewable` -// public func prerender(_ formula: HTMLRenderer.Formula) throws { -// formula.add(mappable: self) -// } -// -// // View `CompiledTemplate` -// public func render(with manager: HTMLRenderer.ContextManager) throws -> String { -// return try markdownToHTML(markdown.render(with: manager), options: options) -// } -//} - -//extension ContextualTemplate { -// -// /// Convert markdown to HTML -// /// -// /// - Parameters: -// /// - content: The content to convert to markdown -// /// - options: The options when rendering the markdown. Default = [.safe] -// /// - Returns: A compiled tamplate of the markdown -// public func markdown(_ content: View..., options: MarkdownOptions = [.safe, .normalize]) -> View { -// return Markdown(markdown: content, options: options) -// } -//} diff --git a/Sources/HTMLKit/TemplateVariable.swift b/Sources/HTMLKit/TemplateVariable.swift deleted file mode 100644 index 1e6bfcd0..00000000 --- a/Sources/HTMLKit/TemplateVariable.swift +++ /dev/null @@ -1,19 +0,0 @@ -//// -//// TemplateVariable.swift -//// HTMLKit -//// -//// Created by Mats Mollestad on 02/03/2019. -//// -// -// -///// A variable making it possible to lazily insert variables -///// -///// div(children: variable(\.name)) // May leed to "
Mats
", deepending in the context given -//public struct TemplateVariable where Root: ContextualTemplate, Value: View { -// -// /// The key-path to the variable to render -// let reference: ContextReference -// -// /// The escaping option -// public let escaping: EscapingOption -//} diff --git a/Tests/HTMLKitTests/HTMLKitTests.swift b/Tests/HTMLKitTests/HTMLKitTests.swift index 39222094..8f49ae88 100644 --- a/Tests/HTMLKitTests/HTMLKitTests.swift +++ b/Tests/HTMLKitTests/HTMLKitTests.swift @@ -221,11 +221,11 @@ final class HTMLKitTests: XCTestCase { let chaindDataRender = try renderer.render(raw: ChainedEqualAttributesDataNode.self) //// let inputRender = try renderer.render(FormInput.self) - XCTAssertEqual(multipleEmbedRender, "WelcomeSome text

String

String

String

Welcome

") + XCTAssertEqual(multipleEmbedRender, "WelcomeSome text

String

String

String

Welcome

") // XCTAssertEqual(varialbeRender, "

<script>"'&</script>

") XCTAssertEqual(staticEmbedRender, "

Text

Hello

2
") - XCTAssertEqual(someViewRender, "Welcome

Hello Mats!

") - XCTAssertEqual(someViewRenderTitle, "Test

Hello Mats!

") + XCTAssertEqual(someViewRender, "Welcome

Hello Mats!

") + XCTAssertEqual(someViewRenderTitle, "Test

Hello Mats!

") XCTAssertEqual(forEachRender, "

1

2

3

") XCTAssertEqual(firstIfRender, "
I am a child
") XCTAssertEqual(secondIfRender, "

My name is: Mats!

I am growing

Simple bool

") diff --git a/Tests/HTMLKitTests/HTMLTestDocuments.swift b/Tests/HTMLKitTests/HTMLTestDocuments.swift index 17f4cba1..b11217f5 100644 --- a/Tests/HTMLKitTests/HTMLTestDocuments.swift +++ b/Tests/HTMLKitTests/HTMLTestDocuments.swift @@ -44,6 +44,23 @@ struct StaticEmbedView: HTMLTemplate { } } +struct SimplePage: HTMLTemplate { + + @TemplateValue(String.self) + var context + + var body: HTML { + Document(type: .html5) { + Head { + Title { context } + } + Body { + P { context } + } + } + } +} + struct BaseView: HTMLComponent { let context: TemplateValue @@ -55,18 +72,20 @@ struct BaseView: HTMLComponent { } var body: HTML { - HTMLNode { - Head { - Title { context } - Link() - .href("some url") - .relationship(.stylesheet) - Meta() - .name(.viewport) - .content("width=device-width, initial-scale=1.0") - } - Body { - content + Document(type: .html5) { + HTMLNode { + Head { + Title { context } + Link() + .href("some url") + .relationship(.stylesheet) + Meta() + .name(.viewport) + .content("width=device-width, initial-scale=1.0") + } + Body { + content + } } } }