vapor / leaf Goto Github PK
View Code? Open in Web Editor NEW๐ An expressive, performant, and extensible templating language built for Swift.
Home Page: https://docs.vapor.codes/4.0/leaf/getting-started
License: MIT License
๐ An expressive, performant, and extensible templating language built for Swift.
Home Page: https://docs.vapor.codes/4.0/leaf/getting-started
License: MIT License
Would make debugging faster.
I am not understanding how this works or not sure it's a bug. Please advise.
<a href="#(link)#()features">Features</a>
<a href="#(link)#raw() {#features}">Features</a>
No error
Below throws error when no space between raw()
and {
<a href="#(link)#raw(){#features}">Features</a>
Uncaught Error: ParseError.expectedOpenParenthesis. Use middleware to catch this error and provide a better response. Otherwise, a 500 error page will be returned in the production environment.
#if(name)
should run if name is present at all or true at all. Currently it only appears to be running if name is a truthy value.
Allow .html
, .blah
, .leaf
etc to be passed. All should be rendered as leaf templates. If no extension, assume .leaf
How can I generate HTML string from a leaf after rendering so i can send as an email?
support for:
#for user in users {
#(user.name)
}
https://github.com/isagalaev/highlight.js/blob/master/src/languages/leaf.js
When I want to use the #empty(variable)
syntax, I got this error :
ParseError.tagTemplateNotFound("empty")
In the documentation, the empty tag is used to test is a variable is empty, but it doesn't work. When I go into the Constants.swift of Leaf, there is no empty...
thanks for help !
When embedding content that is common across some pages, inside a #set
it doesn't work. Scenario is:
There are no errors, it just doesn't display the content, like the nested #get()
doesn't return anything
Example repo is https://github.com/0xTim/LeafNestedEmbed
Referencing vapor/vapor#1491 here given I am experiencing the same issue when using LeafProvider
and LeafRenderer
to render views.
After asking for help on Slack I was told to use:
#equal(user.name, "rugaard") {
}
Instead of:
#if(user.name == "rugaard") {
}
But I keep getting the error about the tag equal
doesn't exist.
Testcase is #("this, should, be, one, arg")
Just trying to create a new url with pagination like so
href="/list?page=#(currentPage - 1)&size=#(pageSize)"
My Struct context declares these as Int
. But when I add or subtract from the variable the result is a Double
. Just spoke to Tim about it and he said to raise it.
#if(index + 1 == 1)
does not render when index
is 0
Take the following code:
return try drop.view.make("welcome", [
"name": "Leaf ๐"
])
Right now we can include leafs into a leaf using #include
or #embed
welcome.leaf
#embed("header")
<h1>Hello, #(name)</h1>
#embed("footer")
We need a way to invert this dependency with layouts by adding three new functions.
extend(Constant)
export(Constant) { Body }
import(Constant)
welcome.leaf
#extend("app")
#export("main") {
<h1>Hello, #(name)!</h1>
}
app.leaf
<head>
<title>my site</title>
</head>
#import("main")
<footer>some copyright crap</footer>
or even
app.leaf
#embed("header")
#import("main")
#embed("footer")
Should leaf search for .leaf
, and then .html
as well? Or check for .html
, and don't append suffix.
These would be rendered as 100% raw.
Workaround:
.leaf
instead of .html
I cannot find a good documentation for how to use leaf templating language. Any good resources?
it is very common to use nested tags in child template, for example:
base template:
<div class="container">
#import("container") {
<div class="subcontainer">
#import("subcontainer") {
<a href="#">hello from vapor</a>
}
</div>
}
</div>
child template1
#extend("base")
#export("container") {
I'm template1 rewritted container!
}
child template2
#extend("base")
#export("subcontainer") {
I'm template2 rewritted subcontainer!
}
each of the template could be rendered seperatly, after rendering the three templates render results would be
base template:
<div class="container">
<div class="subcontainer">
<a href="#">hello from vapor</a>
</div>
</div>
child template1:
<div class="container">
I'm template1 rewritted container!
</div>
child template2:
<div class="container">
<div class="subcontainer">
I'm template2 rewritted subcontainer!
</div>
</div>
in a big project, it's common for templates that free to choose which tag to rewrite. it will be more free and it will be very useful.
since I'm migrated from python, there are a lot of template languages, such as django-template-language,jinja, mako
takes mako for example, you can see the code Using Named Blocks paragrah shows nested block in them
no vapor app will stop compiling due to this change.
I think it would be cool if we can also render (parts of) a page on the client side. This will make it easy to reload parts of the page without reloading the whole page.
Everything is rendered on the server side currently which adds the requirement of javascript to do the partially reloading...
Sometimes for the purposes of a embedded templates, it would be nice if you could rename variables passed in so that your template is much less clunky. Also, encourages reuse of small chunks of templates.
The with-tag would allow you to use a deeply nested name, as a shorter name within the provided scope.
#with(<complex.nested.value>, <short-name>, โฆ) { /* template */ }
It accepts N pairs of parameters, where the first of each pair is the context's variable, and the second of each pair is a string of the new name you'd like to use.
<!-- Single item rename -->
#with(my.deeply.nested.thing, "myThing") {
<!--Other columnsโฆ--><td>#(myThing.prop)</td><td>#(myThing.anotherProp)</td><td>#(myThing.someOtherProp)</td>
}
<!-- Multiple variable rename -->
#with(
clunky.obj.name, "name",
clunky.obj.val, "val",
clunky-too.foo, "foo"
) {
<ul>#(name): #(val), #(foo)</ul>
}
In Leaf 3 there is currently no easy way to add custom tags. It kinda can be done by overriding the LeafConfig
in services but if there are multiple providers or instances where this happens, one will always lose. One possibility is to make the tags
dictionary mutable to allow it to be extended
In Leaf 2 templates wouldn't be cached during debug builds, and only when running in release. This doesn't appear to have been transitioned over to Leaf 3 and should be reimplemented
Currently, code below:
<ul class="sidenav" id="mobile-demo">#loop(menu, "item"){<li><a href=#(item.href)>#(item.title)</a></li>}</ul>
Produces such output:
<ul class="sidenav" id="mobile-demo"><li><a href=ITEM1_HREF)>ITEM1_TITLE</a></li>
<li><a href=ITEM2_HREF)>ITEM2_TITLE</a></li>
<li><a href=ITEM3_HREF)>ITEM3_TITLE</a></li></ul>
For those who are obsessed with minifying html it would be great if it could produce:
<ul class="sidenav" id="mobile-demo"><li><a href=ITEM1_HREF)>ITEM1_TITLE</a></li><li><a href=ITEM2_HREF)>ITEM2_TITLE</a></li><li><a href=ITEM3_HREF)>ITEM3_TITLE</a></li></ul>
i.e. everything in one line
Leaf 2 allowed us to check whether a variable was set in the current context by writing #if(somevar)
, but in Leaf 3 this no longer works.
To reproduce the problem, creating the following Leaf template:
#if(haters) {
<p>The "haters" variable is set to #(haters)</p>
}
#if(fakers) {
<p>The "fakers" variable is set to #(fakers)</p>
}
If you render that template with the context ["haters": "hating"]
you would expect the first message to be printed but not the second, but as of 3.0 alpha 4 both messages are printed.
final class Markdown: Tag {
let name = "markdown"
func run(stem: Stem, context: Context, tagTemplate: TagTemplate, arguments: [Argument]) throws -> Node? {
guard let string = arguments.first?.value?.string else { return nil }
let html = try markdownToHTML(string)
let unescaped = html.bytes
return .bytes(unescaped)
}
func shouldRender(stem: Stem, context: Context, tagTemplate: TagTemplate, arguments: [Argument], value: Node?) -> Bool {
return true
}
}
markdownToHTML
dependen on
.Package(url: "https://github.com/czechboy0/cmark.swift.git", majorVersion: 0, minor: 1)
it could work !
When building packages with views. You don't want users to copy all the views into their project.
Some of the views would be just fine to have in the package
With the current system this cannot be done
In laravel there was a option to write path as fx
AdminPanel:Login/login
this means it would check if there was a folder in Resources/Views which fitted for /AdminPanel/Login/login.leaf
if this was not found it would take the same path but in the package
I made a small example of how it could work, but the initialisation with workdir kills it a bit.
public func makeWithFallback(_ path: String) throws -> View {
do {
return try make(Droplet().workDir + "/Resources/Views/" + path)
} catch {
return try make(Droplet().workDir + "Packages/AdminPanel-0.1.1/Sources/AdminPanel/Resources/Views/" + path + ".leaf")
}
}
Is this something for next update? It's very handy
I have registered a custom tag to return true if the index is odd and false if even as follows:
class OddEvenTag: BasicTag {
let name = "OddEven"
func run(arguments: ArgumentList) throws -> Node? {
guard
arguments.count == 1,
let index = arguments[0]?.int
else { return Node(nil) }
print(index, index & 1)
return Node((index & 1) == 1)
}
}
The print statement produces satisfyingly good output:
0 0
1 1
2 0
3 1
...
However, when I use the custom tag inside a #loop in a leaf file, such as
#OddEven(index){hello}##else(){bye}
It always instantiates the hello. I've tried hard-coding false into the return statement and it doesn't change the outcome. I have used a (more complex) custom tag before, so I know they can work.
In case you're wondering, I really want to use the tag to alternate the row background colours of a grid!
I have a leaf file that does this:
#if(group.admin) { #(group.admin.firstName) #(group.admin.lastName) }
However, this renders as FirstLast
instead of First Last
. Leaf should respect the spacing between the two objects
Whilst in Leaf there is an #if() there isn't an #ifnot().
I would like to have a readonly field in a form when viewing, but when editing, I would like to remove the readonly attribute. When I show the form I send "update": true or "update": false and then in the html I have:
<input name="fname" type="text" value="#(member.fname)" pattern="alpha" #ifnot(update) {readonly}>
copy the code for #if() as proposed by @Oxtim and use it for the tag #ifnot
I duplicated the If.swift file and created an IfNot.swift file and changed the following:
guard let value = arguments.first?.value else { return false }
// Existence of bool, evaluate bool.
if let bool = value.bool { return !bool }
// Empty string value returns true.
if value.string == "" { return true }
// Otherwise, not-nil returns false.
return false
`
There should no impact.
The alternative is to invert the logic and make update equal false, which will cause confusion. #if(update) {readonly}
when I runโฆ
#for( name in names ) {
instead ofโฆ
#for(name in names) {
I get:
[ ERROR ] TemplateKit.TemplateError.parse: Expected 105 got 110 (names.leaf:11)
I'm not super hung-up on my own personal whitespace style, but Leaf should be smarter than this.
Hi,
Leaf is pretty incredible, but sometimes #equal(...){...}#else(){...} always instantiates the 'else' content, regardless of the value of the 'equal' condition. For example, this:
#equal(user.id,"0"){Add New Person} #else(){Edit #(user.forename) #(user.surname)'s Record}
gives
Add New Person Edit 's Record
when user.id is equal to zero and:
Edit Joe Soap's Record
when it isn't
Whereas:
`#equal(role.role,name.role){}#else(){selected="selected"}
works reliably and sets the appropriate option in a drop-down.
The only difference I can see is that the working one is inside a #loop tag and so role.role is a changing value. I have tried making a fixed field user.zero = 0 and putting:
#equal(user.id,user.zero)...
to see if I was doing something wrong with the literal, but it didn't help. I've also tried putting a lot more white space around the tokens, but to no effect.
These loop variables were documented in https://docs.vapor.codes/3.0/leaf/basics/ and used to provide useful information, but they seem to have stopped working in recent weeks.
#repeat(5) {
hello
}
output:
hello
hello
hello
hello
hello
Could be nice to include something like i
to indicate position.
#repeat(99) {
Hello for the #(i)th time.
}
When adding an int to the index for loops it displays as a float instead of an int.
let index = 0
index + 1 should equal and show up as 1 . However, it shows up as 1.0, 2.0, 3.0, etc
#for(player in players) { <tr><td>#(index+1)</td><td>#(player.name)</td></td></tr> }
1 Bryan
2 Jessica
3 Chris
1.0 Bryan
2.0 Jessica
3.0 Chris
It could be nice to get a number formatting tag to display it to how the user wants it displayed
Maybe this is out of Leaf's scope, but coming from ejs
and erb
, I thought it would be nice to be able to declare variables (I looked everywhere, and if it's already possible, I missed it every time).
If this isn't possible / is out of scope, what would be a proper way to create and pass a variable to all leafs? It's something for the footer, and the footer is #imported
everywhere. It would be desirable to not having to create and pass it every time I view.make
, though ๐
Cheers!
As described in documentation (https://docs.vapor.codes/2.0/leaf/leaf/#index-indexobject-_-index-intstring)
Index: #index(object, _ index: Int|String)ยถ
Hello, #index(friends, 0)!
Hello, #index(friends, "best")!
Currently it has following issues:
I added similar tag, that resolves 1) and 3), 2)
public class KeypathTag: Tag {
public let name = "keypath"
public func run(tagTemplate: TagTemplate, arguments: ArgumentList) throws -> Node? {
guard arguments.count >= 2 else {
return nil
}
var value: Node? = arguments[0]
for i in 1..<arguments.count {
if let array = value?.array, let index = arguments[i]?.int {
value = array[index]
} else if let object = value?.object, let key = arguments[i]?.string {
value = object[key]
} else {
return nil
}
}
return value
}
}
It can search in array and in dictionary, and also has ability to index inside previous result #index(#index(friends, "0"), "name")
, like #keypath(friends, "0", "name")
I'm not sure using the #
as a token is wise. Trying it out I hit quite a few cases where it completely standard HTML/CSS was interpreted as Leaf methods.
A common way to "pop" modals is using an <a>
where the href
is just a #
.
<a href="#" class="btn btn-primary" data-toggle="modal">
(I know you could just use <button>
instead of <a>
but not everybody do that)
You also won't be able to use inline hex-colors, which is also something a lot of people are doing (at least the lazy ones).
<h1 style="color: #ff0000">This heading is red</h1>
There are probably a lot more cases, where the #
would make Leaf conflict with normal standard HTML/CSS. So maybe it would be better/wiser to use a different token?
Or maybe change it to a double ##
? I don't know. All I know is a single #
unfortunately causes quite the trouble :(
Author: @willianpinho
Tag needed to compare different data. Today we only have #equal ()
It is not always possible to do all the checks with #if () and #else ().
For example, I get a dictionary of projects and projects have milestones, I wish only the first one is active and others inactive.
#loop (project.milestones, "milestone") {
#equal (milestone.id, "1") {
Active Milestone: # (milestone.name)
}
#notequal (milestone.id, "1") {
Not Active Milestone # (milestone.name)
}
}
Add #notequal() to leaf
I duplicated the Equal.swift file and created an NotEqual.swift file and changed the following:
Add in Constants.swift
"notequal": NotEqual(),
Create NotEqual.swift
public final class NotEqual: BasicTag {
public enum Error: LeafError {
case expected2Arguments
}
public let name = "NotEqual"
public func run(arguments: ArgumentList) throws -> Node? {
guard arguments.count == 2 else { throw Error.expected2Arguments }
return nil
}
public func shouldRender(
tagTemplate: TagTemplate,
arguments: ArgumentList,
value: Node?
) -> Bool {
return fuzzyEquals(
arguments.first,
arguments.last
)
}
}
fileprivate func fuzzyEquals(_ lhs: Node?, _ rhs: Node?) -> Bool {
let lhs = lhs ?? .null
let rhs = rhs ?? .null
switch lhs.wrapped {
case let .array(lhs):
guard let rhs = rhs.array else { return false }
guard lhs.count != rhs.count else { return false }
for (l, r) in zip(lhs, rhs) where !fuzzyEquals(Node(l), r) { return false }
return true
case let .bool(bool):
return bool != rhs.bool
case let .bytes(bytes):
guard case let .bytes(rhs) = rhs.wrapped else { return false }
return bytes != rhs
case .null:
return rhs.isNull
case let .number(number):
switch number {
case let .double(double):
return double != rhs.double
case let .int(int):
return int != rhs.int
case let .uint(uint):
return uint != rhs.uint
}
case let .object(lhs):
guard let rhs = rhs.object else { return false }
guard lhs.count != rhs.count else { return false }
for (k, v) in lhs where !fuzzyEquals(Node(v), rhs[k]) { return false }
return true
case let .string(string):
return string != rhs.string
case let .date(date):
// FIXME: Add fuzzy date access and equality?
guard case let .date(right) = rhs.wrapped else { return false }
return date != right
}
}
There should no impact.
Need more tests
Consider the following code (replacing the "It works" text of the basic template):
let drop = Droplet()
drop.get { req in
return try drop.view.make("welcome", [
"content": "\r\n\""
])
}
This code works fine on macOS, but causes the process to crash on Linux, both on a physical Ubuntu installation and in Docker. The only output is "Illegal Instruction".
It seems that strings that match the following pattern: \r\n.*["|']
โ in other words, strings that contain a Windows newline and a single or double quote โ trigger the crash when printed into the output using the #()
tag.
break out leaf's parsing and serialization to allow other templating languages to build on top of the lower layers
How to access the session in a template file?
There is no option to add Flash messages and loop them in the template
Leaf.token = "๐"
Hello, ๐(name)
This would be so cool.
I found when I have a loop inside another, the data will be carried forward from one item to another. For example, I have a bunch of data: [
{'title': 'Swift', 'opt': [1, 2, 3], 'option': true}
{'title': 'Vapor', 'image': 'Vapor.png'}
]
If I do #loop(data, "each"), and inside the loop, I have #loop(each.opt, "opt"). It works for the first one, obviously. But it also works for the second one, because the opt from the first is carried forward to the second one for some reason... It has been bugging me for a while, and I am pretty sure my data is alright when I call drop.view.make().
Is there anyone who met the same or similar problem?
loop(friends, "friend") {
Hello, #(friend.name) you are number xxxx in my list of friends!
}
I would want the output to be
Hello, John you are number 0 in my list of friends!
Hello, Jane you are number 1 in my list of friends!
Hello, Jack you are number 2 in my list of friends!
Here's a bit of inconsistency in .leaf parsing. You can still get the job done but still it feels like it should not be like that.
This works fine, and renders expected 'Hey'
#export("body") {
<h1>Hey</h1>
}
And this renders nothing:
#export("body")
{
<h1>Hey</h1>
}
I noticed two things about the html output of Leaf (there might be others that I have not encountered).
This results in html that is perfectly valid but not as pretty to the eye which does not help with debugging.
I have figured out how to solve the first point and I can create a PR if this is helpful. As for the second, I would like to know if this is something people besides me would be interested in. Ideally, any indentation should be able to be turned off as well.
This does not work :
<ul>
#loop(posts, "post")
{
<li>#(post.content)</li>
}
</ul>
This works :
<ul>
#loop(posts, "post") {
<li>#(post.content)</li>
}
</ul>
We need to work on creating a universal syntax highlighter for leaf that we can make a Sublime package for and PR into GitHub.
Would be nice to have syntax highlighting for leaf files in IntelliJ/AppCode
When I do Hi #(user.firstname) #(user.lastname)
I get Hi LemmyKilmister
instead of Hi Lemmy Kilmister
using:
let leaf = try req.make(LeafRenderer.self)
let context = model ?? [String: String]()
let output = leaf.render(template: data, context)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.