feat(for tag): Support where expressions (#96)
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
|
- `for` block now can contain `where` expression to filter array items. For example `{% for item in items where item > 1 %}` is now supported.
|
||||||
- `if` blocks may now contain else if (`elif`) conditions.
|
- `if` blocks may now contain else if (`elif`) conditions.
|
||||||
|
|
||||||
```html+django
|
```html+django
|
||||||
@@ -16,7 +17,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 0.8.0
|
## 0.8.0
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ class ForNode : NodeType {
|
|||||||
let loopVariable:String
|
let loopVariable:String
|
||||||
let nodes:[NodeType]
|
let nodes:[NodeType]
|
||||||
let emptyNodes: [NodeType]
|
let emptyNodes: [NodeType]
|
||||||
|
let `where`: Expression?
|
||||||
|
|
||||||
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
|
class func parse(_ parser:TokenParser, token:Token) throws -> NodeType {
|
||||||
let components = token.components()
|
let components = token.components()
|
||||||
|
|
||||||
guard components.count == 4 && components[2] == "in" else {
|
guard components.count >= 2 && components[2] == "in" &&
|
||||||
throw TemplateSyntaxError("'for' statements should use the following 'for x in y' `\(token.contents)`.")
|
(components.count == 4 || (components.count >= 6 && components[4] == "where")) else {
|
||||||
|
throw TemplateSyntaxError("'for' statements should use the following 'for x in y where condition' `\(token.contents)`.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let loopVariable = components[1]
|
let loopVariable = components[1]
|
||||||
@@ -28,32 +30,48 @@ class ForNode : NodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let filter = try parser.compileFilter(variable)
|
let filter = try parser.compileFilter(variable)
|
||||||
return ForNode(resolvable: filter, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes)
|
let `where`: Expression?
|
||||||
|
if components.count >= 6 {
|
||||||
|
`where` = try parseExpression(components: Array(components.suffix(from: 5)), tokenParser: parser)
|
||||||
|
} else {
|
||||||
|
`where` = nil
|
||||||
|
}
|
||||||
|
return ForNode(resolvable: filter, loopVariable: loopVariable, nodes: forNodes, emptyNodes:emptyNodes, where: `where`)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(resolvable: Resolvable, loopVariable:String, nodes:[NodeType], emptyNodes:[NodeType]) {
|
init(resolvable: Resolvable, loopVariable:String, nodes:[NodeType], emptyNodes:[NodeType], where: Expression? = nil) {
|
||||||
self.resolvable = resolvable
|
self.resolvable = resolvable
|
||||||
self.loopVariable = loopVariable
|
self.loopVariable = loopVariable
|
||||||
self.nodes = nodes
|
self.nodes = nodes
|
||||||
self.emptyNodes = emptyNodes
|
self.emptyNodes = emptyNodes
|
||||||
|
self.where = `where`
|
||||||
}
|
}
|
||||||
|
|
||||||
func render(_ context: Context) throws -> String {
|
func render(_ context: Context) throws -> String {
|
||||||
let values = try resolvable.resolve(context)
|
let values = try resolvable.resolve(context)
|
||||||
|
|
||||||
if let values = values as? [Any] , values.count > 0 {
|
if var values = values as? [Any], values.count > 0 {
|
||||||
let count = values.count
|
if let `where` = self.where {
|
||||||
return try values.enumerated().map { index, item in
|
values = try values.filter({ item -> Bool in
|
||||||
let forContext: [String: Any] = [
|
return try context.push(dictionary: [loopVariable: item]) { () -> Bool in
|
||||||
"first": index == 0,
|
try `where`.evaluate(context: context)
|
||||||
"last": index == (count - 1),
|
}
|
||||||
"counter": index + 1,
|
})
|
||||||
]
|
}
|
||||||
|
if values.count > 0 {
|
||||||
|
let count = values.count
|
||||||
|
return try values.enumerated().map { index, item in
|
||||||
|
let forContext: [String: Any] = [
|
||||||
|
"first": index == 0,
|
||||||
|
"last": index == (count - 1),
|
||||||
|
"counter": index + 1,
|
||||||
|
]
|
||||||
|
|
||||||
return try context.push(dictionary: [loopVariable: item, "forloop": forContext]) {
|
return try context.push(dictionary: [loopVariable: item, "forloop": forContext]) {
|
||||||
try renderNodes(nodes, context)
|
try renderNodes(nodes, context)
|
||||||
}
|
}
|
||||||
}.joined(separator: "")
|
}.joined(separator: "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return try context.push {
|
return try context.push {
|
||||||
|
|||||||
@@ -63,6 +63,21 @@ func testForNode() {
|
|||||||
try expect(try node.render(context)) == "112233"
|
try expect(try node.render(context)) == "112233"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$0.it("renders the given nodes while filtering items using where expression") {
|
||||||
|
let nodes: [NodeType] = [VariableNode(variable: "item"), VariableNode(variable: "forloop.counter")]
|
||||||
|
let `where` = try parseExpression(components: ["item", ">", "1"], tokenParser: TokenParser(tokens: [], environment: Environment()))
|
||||||
|
let node = ForNode(resolvable: Variable("items"), loopVariable: "item", nodes: nodes, emptyNodes: [], where: `where`)
|
||||||
|
try expect(try node.render(context)) == "2132"
|
||||||
|
}
|
||||||
|
|
||||||
|
$0.it("renders the given empty nodes when all items filtered out with where expression") {
|
||||||
|
let nodes: [NodeType] = [VariableNode(variable: "item")]
|
||||||
|
let emptyNodes: [NodeType] = [TextNode(text: "empty")]
|
||||||
|
let `where` = try parseExpression(components: ["item", "==", "0"], tokenParser: TokenParser(tokens: [], environment: Environment()))
|
||||||
|
let node = ForNode(resolvable: Variable("emptyItems"), loopVariable: "item", nodes: nodes, emptyNodes: emptyNodes, where: `where`)
|
||||||
|
try expect(try node.render(context)) == "empty"
|
||||||
|
}
|
||||||
|
|
||||||
$0.it("can render a filter") {
|
$0.it("can render a filter") {
|
||||||
let templateString = "{% for article in ars|default:articles %}" +
|
let templateString = "{% for article in ars|default:articles %}" +
|
||||||
"- {{ article.title }} by {{ article.author }}.\n" +
|
"- {{ article.title }} by {{ article.author }}.\n" +
|
||||||
|
|||||||
@@ -19,6 +19,17 @@ A for loop allows you to iterate over an array found by variable lookup.
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
The ``for`` tag can contain optional ``where`` expression to filter out
|
||||||
|
elements on which this expression evaluates to false.
|
||||||
|
|
||||||
|
.. code-block:: html+django
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for user in users where user.name != "Kyle" %}
|
||||||
|
<li>{{ user }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
The ``for`` tag can take an optional ``{% empty %}`` block that will be
|
The ``for`` tag can take an optional ``{% empty %}`` block that will be
|
||||||
displayed if the given list is empty or could not be found.
|
displayed if the given list is empty or could not be found.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user