leaf-kit
leaf-kit copied to clipboard
Custom tags with body
I tried to define a custom tag that accepts a body, but the body is provided to the tag as [Syntax]
, which is not something that converts to the required return type of LeafData
.
How can I capture the body, and return if necessary?
For context, I'm trying to create requireRole(role)
tag, that returns the body if the role matches the role of the current user;
#requireRole("ADMIN"):
<a href="/projects/#(project.id)/delete">Delete</a>
#endrequireRole
In the tag class I retrieve the body using ctx.requireBody()
, which, as mentioned, returns an array of Syntax
elements.
final class RequireRoleTag<A>: LeafTag where A: RoleAuthenticatable {
init(userType: A.Type) {}
func render(_ ctx: LeafContext) throws -> LeafData {
try ctx.requireParameterCount(1)
let body = try ctx.requireBody()
guard let requiredRole = ctx.parameters[0].string else {
throw "role is not a string"
}
guard let req = ctx.request,
let role = getRole(req: req)
else {
return .trueNil
}
if role == requiredRole {
// This doesn't work
return body
}
return .trueNil
}
private func getRole(req: Request) -> String? {
let a = req.auth.get(A.self)
return a?.role.description
}
}
+1, this feels like it should be straightforward as #requireRole
is essentially syntactic sugar over #if
.
A slightly more complex case that I was hoping to solve with a custom tag is injecting common variables without needing to set them in the view model. For example...
#withUser():
<img src="#(user.avatarURL)" title="#(user.name) />
#endwithUser
When building re-usable templates such as navigation, there are many use-cases for wanting common data available without needing to add all that common context to the view model/controller. I guess this could all be bundled up into some common view model type, but that's still extra code that's required.
How to return LeafData
from [Syntax]
(only works correctly if there are no nested Leaf tags, so doesn't solve your issue):
let body = try ctx.requireBody()
guard case let .raw(byteBuffer) = body.first else {
throw "Body not found"
}
return .data(Data(buffer: byteBuffer))
Make sure your custom tag conforms to UnsafeUnescapedLeafTag
, because the body may contain HTML tags.