Custom Tags¶
You can create custom Leaf tags using the LeafTag
protocol.
To demonstrate this, let's take a look at creating a custom tag #now
that prints the current timestamp. The tag will also support a single, optional parameter for specifying the date format.
Tip
If your custom tag renders HTML you should conform your custom tag to UnsafeUnescapedLeafTag
so the HTML is not escaped. Remember to check or sanitize any user input.
LeafTag
¶
First create a class called NowTag
and conform it to LeafTag
.
struct NowTag: LeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
...
}
}
Now let's implement the render(_:)
method. The LeafContext
context passed to this method has everything we should need.
enum NowTagError: Error {
case invalidFormatParameter
case tooManyParameters
}
struct NowTag: LeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
let formatter = DateFormatter()
switch ctx.parameters.count {
case 0: formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
case 1:
guard let string = ctx.parameters[0].string else {
throw NowTagError.invalidFormatParameter
}
formatter.dateFormat = string
default:
throw NowTagError.tooManyParameters
}
let dateAsString = formatter.string(from: Date())
return LeafData.string(dateAsString)
}
}
Configure Tag¶
Now that we've implemented NowTag
, we just need to tell Leaf about it. You can add any tag like this - even if they come from a separate package. You do this typically in configure.swift
:
app.leaf.tags["now"] = NowTag()
And that's it! We can now use our custom tag in Leaf.
The time is #now()
Context Properties¶
The LeafContext
contains two important properties. parameters
and data
that has everything we should need.
parameters
: An array that contains the parameters of the tag.data
: A dictionary that contains the data of the view passed torender(_:_:)
as the context.
Example Hello Tag¶
To do see how to use this, let's implement a simple hello tag using both properties.
Using Parameters¶
We can access the first parameter that would contain the name.
enum HelloTagError: Error {
case missingNameParameter
}
struct HelloTag: UnsafeUnescapedLeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
guard let name = ctx.parameters[0].string else {
throw HelloTagError.missingNameParameter
}
return LeafData.string("<p>Hello \(name)</p>")
}
}
#hello("John")
Using Data¶
We can access the name value by using the "name" key inside the data property.
enum HelloTagError: Error {
case nameNotFound
}
struct HelloTag: UnsafeUnescapedLeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
guard let name = ctx.data["name"]?.string else {
throw HelloTagError.nameNotFound
}
return LeafData.string("<p>Hello \(name)</p>")
}
}
#hello()
Controller:
return try await req.view.render("home", ["name": "John"])