From 5d0a8693edb2b7dc4a603d082b88b1f977f6d52b Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 24 Jun 2021 12:44:32 +0200 Subject: [PATCH] Qute - loop section - introduce the {#else} block - this block is executed if there are no items to iterate --- docs/src/main/asciidoc/qute-reference.adoc | 25 ++++++++++---- .../io/quarkus/qute/LoopSectionHelper.java | 28 +++++++++++---- .../io/quarkus/qute/SectionHelperFactory.java | 34 +++++++++++++++++++ .../java/io/quarkus/qute/LoopSectionTest.java | 8 +++++ 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 4b1a6b2a9c291..41e1521dea43e 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -518,9 +518,9 @@ A section helper that defines the logic of a section can "execute" any of the bl [[loop_section]] ==== Loop Section -The loop section makes it possible to iterate over an instance of `Iterable`, `Map` 's entry set, `Stream` and an `Integer`. +The loop section makes it possible to iterate over an instance of `Iterable`, `Iterator`, array, `Map` 's entry set, `Stream` and an `Integer`. It has two flavors. -The first one is using the `each` name alias. +The first one is using the `each` name and `it` is an implicit alias for the iteration element. [source] ---- @@ -528,9 +528,9 @@ The first one is using the `each` name alias. {it.name} <1> {/each} ---- -<1> `it` is an implicit alias. `name` is resolved against the current iteration element. +<1> `name` is resolved against the current iteration element. -The other form is using the `for` name alias and can specify the alias used to reference the iteration element: +The other form is using the `for` name and can specify the alias used to reference the iteration element: [source] ---- @@ -547,7 +547,9 @@ It's also possible to access the iteration metadata inside the loop: {count}. {it.name} <1> {/each} ---- -<1> `count` represents one-based index. Metadata also include zero-based `index`, `hasNext`, `odd` and `even`. +<1> `count` represents one-based index. + +TIP: The iteration metadata also include zero-based `index`, `hasNext`, `odd` and `even` properties. The `for` statement also works with integers, starting from 1. In the example below, considering that `total = 3`: @@ -558,13 +560,24 @@ The `for` statement also works with integers, starting from 1. In the example be {/for} ---- -The output will be: +And the output will be: [source] ---- 1:2:3: ---- +A loop section may define the `{#else}` block that is executed when there are no items to iterate: + +[source] +---- +{#for item in items} + {item.name} +{#else} + No items. +{/for} +---- + [[if_section]] ==== If Section diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java index 11db3b76398ed..0d69dd1ae7c94 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java @@ -2,9 +2,11 @@ import static io.quarkus.qute.Parameter.EMPTY; +import io.quarkus.qute.SectionHelperFactory.SectionInitContext; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -20,13 +22,18 @@ public class LoopSectionHelper implements SectionHelper { private static final String DEFAULT_ALIAS = "it"; + private static final String ELSE = "else"; + private static final String ALIAS = "alias"; + private static final String ITERABLE = "iterable"; private final String alias; private final Expression iterable; + private final SectionBlock elseBlock; - LoopSectionHelper(String alias, Expression iterable) { - this.alias = Parameter.EMPTY.equals(alias) ? DEFAULT_ALIAS : alias; - this.iterable = Objects.requireNonNull(iterable); + LoopSectionHelper(SectionInitContext context) { + this.alias = context.getParameterOrDefault(ALIAS, DEFAULT_ALIAS); + this.iterable = Objects.requireNonNull(context.getExpression(ITERABLE)); + this.elseBlock = context.getBlock(ELSE); } @SuppressWarnings("unchecked") @@ -47,7 +54,12 @@ public CompletionStage resolve(SectionResolutionContext context) { results.add(nextElement(iterator.next(), idx++, iterator.hasNext(), context)); } if (results.isEmpty()) { - return ResultNode.NOOP; + // Execute the {#else} block if present + if (elseBlock != null) { + return context.execute(elseBlock, context.resolutionContext()); + } else { + return ResultNode.NOOP; + } } if (results.size() == 1) { return results.get(0); @@ -129,9 +141,7 @@ public static class Factory implements SectionHelperFactory { public static final String HINT_ELEMENT = ""; public static final String HINT_PREFIX = " getDefaultAliases() { @@ -147,9 +157,13 @@ public ParametersInfo getParameters() { .build(); } + public List getBlockLabels() { + return Collections.singletonList(ELSE); + } + @Override public LoopSectionHelper initialize(SectionInitContext context) { - return new LoopSectionHelper(context.getParameter(ALIAS), context.getExpression(ITERABLE)); + return new LoopSectionHelper(context); } @Override diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java index f644ff3241b7c..b261e4deb109c 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/SectionHelperFactory.java @@ -116,14 +116,34 @@ default public Map getParameters() { return getBlocks().get(0).parameters; } + /** + * + * @return {@code true} if the main block declares a parameter of the given name + */ default public boolean hasParameter(String name) { return getParameters().containsKey(name); } + /** + * + * @return the parameter, or null/{@link Parameter.EMPTY} if the main block does not declare a parameter of the given + * name + */ default public String getParameter(String name) { return getParameters().get(name); } + /** + * + * @param name + * @param defaultValue + * @return the param or the default value if not specified + */ + default public String getParameterOrDefault(String name, String defaultValue) { + String param = getParameter(name); + return param == null || Parameter.EMPTY.equals(param) ? defaultValue : param; + } + /** * Note that the expression must be registered in the {@link SectionHelperFactory#initializeBlock(Scope, BlockInfo)} * first. @@ -144,6 +164,20 @@ default public String getParameter(String name) { public List getBlocks(); + /** + * + * @param label + * @return the first block with the given label, or {code null} if no such exists + */ + default SectionBlock getBlock(String label) { + for (SectionBlock block : getBlocks()) { + if (label.equals(block.label)) { + return block; + } + } + return null; + } + public Engine getEngine(); } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/LoopSectionTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/LoopSectionTest.java index 01577a0d10de1..43e7eb4e56cfd 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/LoopSectionTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/LoopSectionTest.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -184,4 +185,11 @@ void testScope() { assertTrue(result.contains("hellllllo"), result); } + @Test + public void testElseBlock() { + Engine engine = Engine.builder().addDefaults().build(); + assertEquals("No items.", + engine.parse("{#for i in items}{item}{#else}No items.{/for}").data("items", Collections.emptyList()).render()); + } + }