Sunday, December 25, 2022
HomeiOS DevelopmentCustomized Leaf tags in Vapor 4

Customized Leaf tags in Vapor 4


On this article I will present you the best way to create some helpful customized tags for the Leaf template engine, written in Swift.

Vapor



Easy methods to lengthen Leaf?

With the rebirth of Leaf we are able to really lengthen the template engine and customized tags are only a factor of the previous. You understand in earlier variations of Leaf all the things was referred to as a tag and there was no differentiation between these little bastards beginning with the # image. Now issues have modified. There are numerous completely different entities in Leaf Tau.

  • Blocks (e.g. #for, #whereas, #if, #elseif, #else)
  • Features (e.g. #Date, #Timestamp, and many others.)
  • Strategies (e.g. .depend(), .isEmpty, and many others.)

It is a great point and on prime of this you’ll be able to create your very personal capabilities, strategies and even blocks. This brings us to a completely extensible template engine that may render all the things in a non-blocking asynchronous approach. How cool is that? 😎

Did I point out that Leaf you’ll be able to lengthen the context with customized LeafDataGenerators? Sure, that is a factor now, up to now you possibly can use the userInfo object to set a “world” accessible variable in Leaf, that was effectively accessible in each single template file.

Now there are some particular variables accessible that you may lengthen:


The present context in fact is what you move to your template utilizing the render technique written in Swift. It’s value to say that self is simply an alias to the present $context, so it does not issues which one you utilize. The $app and $req scopes are empty by design, however you’ll be able to lengthen them. You possibly can even register your individual scope for instance $api and set all the things you want globally below that variable. I will present you the way to do that in a while.

As you’ll be able to see there are many choices accessible to increase Leaf. It’s a must to assume twice which path you’re taking, however it’s nice that we have now this many alternatives. Now we’ll stroll via of every of these items and I will present you the best way to write customized extensions for Leaf Tau. 🥳




Easy methods to lengthen Leaf contexts?

Some of the straightforward approach of extending Leaf is to offer customized context variables. We are able to simply write an extension for the Utility and the Request object and return LeafDataGenerator values with particular keys and in a while we are able to register these as further context variables.


import Vapor
import Leaf

extension Utility {
    var customLeafVars: [String: LeafDataGenerator] {
        [
            "isDebug": .lazy(LeafData.bool(!self.environment.isRelease && self.environment != .production))
        ]
    }
}

extension Request {
    var customLeafVars: [String: LeafDataGenerator] {
        [
            "url": .lazy([
                        "isSecure": LeafData.bool(self.url.scheme?.contains("https")),
                        "host": LeafData.string(self.url.host),
                        "port": LeafData.int(self.url.port),
                        "path": LeafData.string(self.url.path),
                        "query": LeafData.string(self.url.query)
                    ]),
        ]
    }
}


A LeafDataGenerator object could be lazy or speedy. Speedy values will likely be saved immediately, then again lazy values will produce generator blocks which might be going to be referred to as solely when the renderer wants them. Nothing particular, this works just like the lazy key phrase in Swift.


struct ScopeExtensionMiddleware: Middleware {

    func reply(to req: Request, chainingTo subsequent: Responder) -> EventLoopFuture<Response> {
        do {
            strive req.leaf.context.register(turbines: req.software.customLeafVars, toScope: "app")
            strive req.leaf.context.register(turbines: req.customLeafVars, toScope: "req")
        }
        catch {
            return req.eventLoop.future(error: error)
        }
        return subsequent.reply(to: req)
    }
}


We’d like an extension middleware that registers our generator variables to the given scope.


public func configure(_ app: Utility) throws {

    app.middleware.use(ScopeExtensionMiddleware())

    
}

Attempt to print these values in a template file, you’ll be able to entry child-values utilizing the dot notation.

#(self)
#($context)

#($app)
#($app.isDebug)

#($req)
#($req.url)
#($req.url.host)
#($req.url.isSecure)
#($req.url.path)
#($req.url.port)
#($req.url.question)

Now we’re going to create a customized context to get some details about the host machine.

remaining class ServerLeafContextGenerator: LeafContextPublisher {

    var osName: String {
        #if os(macOS)
        return "macOS"
        #elseif os(Linux)
        return "Linux"
        #elseif os(Home windows)
        return "Home windows"
        #else
        return "Unknown"
        #endif
        
    }

    lazy var leafVariables: [String: LeafDataGenerator] = [
        "os": .lazy([
            "name": LeafData.string(self.osName),
            "version": LeafData.string(ProcessInfo.processInfo.operatingSystemVersionString),
        ]),
        "cpu-cores": .speedy(ProcessInfo.processInfo.processorCount),
        "reminiscence": .speedy(ProcessInfo.processInfo.physicalMemory),
    ]
}

We are able to merely put this line subsequent to the opposite two within the scope extension middleware.

strive req.leaf.context.register(turbines: ServerLeafContextGenerator().leafVariables, toScope: "server")

This fashion we are able to get some additional information in regards to the server in our Leaf templates through the use of the $server scope. One other approach is to increase a scope domestically with a generator.

app.get("server-info") { req -> EventLoopFuture<View> in
    var context: LeafRenderer.Context = [
        "title": "Server info",
    ]
    strive context.register(object: ServerLeafContextGenerator(), toScope: "server")
    return req.leaf.render(template: "server-info", context: context)
}

The distinction is that within the second case the server scope is simply accessible for a single endpoint, but when we register it via the middleware then it may be reached globally in each single Leaf file.

I feel scopes are very helpful, particularly Request associated ones. Up to now we needed to create a customized Leaf tag to get the trail, however now we are able to use a scope extension and this information will likely be accessible in every single place. With the lazy load we additionally get some free efficiency enhancements.



Customized Leaf capabilities and strategies

You possibly can create customized capabilities and strategies for Leaf, I might say that this new API is the replacemenet of the outdated tag system. There are some variations and at first sight you may assume that it is tougher to create a operate with the brand new instruments, however in time you will get used to it.


public struct Hey: LeafFunction, StringReturn, Invariant {
    public static var callSignature: [LeafCallParameter] { [.string] }

    public func consider(_ params: LeafCallValues) -> LeafData {
        guard let title = params[0].string else {
            return .error("`Hey` have to be referred to as with a string parameter.")
        }
        return .string("Hey (title)!")
    }
}

It is a very primary operate. Each single operate has a name signature, which is only a checklist of type-safe arguments. Features can have return varieties, fortuitously there are pre-made protocols for these, so you do not have to implement the required stuff, however you’ll be able to say that this capabilities is e.g. a StringReturn operate. Invariant signifies that the operate will all the time return the identical output for a similar enter. That is what you need more often than not, it additionally lets you keep away from side-effects.

Within the consider operate you will get entry to all of the enter parameters and you need to return with a LeafData kind. If a parameter is lacking or it could actually’t be casted to the right kind you’ll be able to all the time return with an error. Consider is wish to the outdated render technique, however it’s far more superior.

LeafConfiguration.entities.use(Hey(), asFunction: "Hey")

You additionally must register this newly created operate below a give title.

#Hey("Leaf Tau")

Oh by the way in which strategies are simply particular capabilities so you’ll be able to construct them the identical approach and register them by way of the asMethod: property. If you wish to see extra examples, you need to check out my different put up about what’s new in Leaf Tau or scroll right down to the final part of this text.




Easy methods to construct customized Leaf blocks?

It is a very attention-grabbing and sophisticated subject. Blocks are particular sort of LeafFunctions, similar to strategies, however issues are just a bit bit extra difficult on this case. Instance time:

import Vapor
import Leaf


struct MaybeBlock: LeafBlock, VoidReturn, Invariant {
    
    static var parseSignatures: ParseSignatures? = nil
    static var evaluable: Bool = false
    var scopeVariables: [String]? = nil

    static var callSignature: [LeafCallParameter] { [.double(labeled: "chance")] }
         
    static func instantiate(_ signature: String?, _ params: [String]) throws -> MaybeBlock { .init() }

    mutating func evaluateScope(_ params: LeafCallValues, _ variables: inout [String: LeafData]) -> EvalCount {
        params[0].double! > Double.random(in: 0..<1) ? .as soon as : .discard
    }
    
    mutating func reEvaluateScope(_ variables: inout [String : LeafData]) -> EvalCount {
        fatalError("Error: `Possibly` blocks cannot be re-evaluated.")
    }
}


This block has a name signature with a labeled argument referred to as likelihood. It has an instantiate technique which is utilized by the Leaf engine to create this block. It will not have any parseSignatures or scope variables, we’ll go away that for the for block (go and verify the supply in LeafKit in case you are curious & courageous sufficient). We set evaluable to false since we do not wish to make it callable by way of the #consider operate. Now let’s discuss scope analysis actual fast.

The evaluateScope technique will likely be referred to as first when the block inside your template will get evaluated. It’s a must to return an EvalCount on this technique, which can resolve what number of instances ought to we print out the contents in between your block (#[name]:THIS PART#finish[name]).

Principally when a LeafBlock is evaluated the primary time, it is by way of evaluateScope. If that returns a end result moderately than nil, any additional calls will use reEvaluateScope as an alternative. – tdotclare

If EvalCount is about to discard then the contents will likely be discarded, in any other case it will be evaluated as many instances as you come back. If the depend is .as soon as which means the top of the story, but when it get’s evaluated a number of instances and you do not want further params for additional analysis, then the reEvaluateScope will likely be referred to as for all the opposite cycles.


LeafConfiguration.entities.use(MaybeBlock.self, asBlock: "possibly")


Remember that we have now to register this block with a given title earlier than we might use it.


#possibly(likelihood: 0.5):
    <p>Is that this going to occur? 50-50.</p>
#endmaybe


That is it, we have simply prolonged Leaf with a primary block, you’ll be able to attempt to construct your individual A/B testing Chained block if you wish to dig deeper, however that is fairly a sophisticated subject and there aren’t any docs accessible simply but so you have got to try the LeafKit supply recordsdata in many of the circumstances.





Helpful Leaf extensions.

I’ve made a bunch of helpful Leaf extensions accessible below the LeafFoundation repository. It is a work-in-progress challenge, however hopefully it will comprise lot extra attention-grabbing extensions by the point Leaf 4 will likely be formally launched. PR’s are welcomed. 😬











Supply hyperlink

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments