Compare commits

...

5 Commits

Author SHA1 Message Date
Jesse Brault
9495849dc9 Update TODO with finished tasks for 0.1.3. 2025-01-27 14:38:26 -06:00
Jesse Brault
f6071909b6 Fix broken OutletTests, found and fixed bug with ancestor searching. 2025-01-27 14:37:35 -06:00
Jesse Brault
2b3cd3120c Add OutletContainer and check to renderer of Outlet. 2025-01-27 14:18:49 -06:00
Jesse Brault
369dc51779 Added hasAncestor and findNearestAncestor(Class) methods to ComponentContext. 2025-01-27 14:16:00 -06:00
Jesse Brault
148ced050b Move WithHtml to lib package. 2025-01-27 14:08:57 -06:00
8 changed files with 68 additions and 7 deletions

View File

@ -34,8 +34,8 @@ For example:
- [x] wvcc bug: Nested static view classes are not seen by compiler
- This required tweaking how the configurations are passed around. Ultimately, we should strive for less complexity
in this regard.
- [ ] `OutletContainer` trait or interface for components which can contain an `<Outlet />` child.
- [ ] `Context` should have methods for simply finding an ancestor of a certain type without the need for a predicate.
- [x] `OutletContainer` trait or interface for components which can contain an `<Outlet />` child.
- [x] `Context` should have methods for simply finding an ancestor of a certain type without the need for a predicate.
## 0.1.2
- [x] `Outlet` component for rendering children like so:

View File

@ -99,6 +99,23 @@ public interface ComponentContext {
return ancestorClass.cast(this.findNearestAncestor(matching.and(ancestorClass::isInstance)));
}
default <T extends ViewComponent> @Nullable T findNearestAncestor(Class<T> ancestorClass) {
return ancestorClass.cast(this.findNearestAncestor(ancestorClass::isInstance));
}
boolean hasAncestor(Predicate<? super ViewComponent> matching);
default <T extends ViewComponent> boolean hasAncestor(
Class<T> ancestorClass,
Predicate<? super ViewComponent> matching
) {
return this.hasAncestor(matching.and(ancestorClass::isInstance));
}
default <T extends ViewComponent> boolean hasAncestor(Class<T> ancestorClass) {
return this.hasAncestor(ancestorClass::isInstance);
}
List<ViewComponent> getAllAncestors();
}

View File

@ -70,7 +70,10 @@ public class DefaultComponentContext implements ComponentContext {
public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) {
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
if (componentStack.size() > 1) {
for (final var ancestor : componentStack.subList(1, componentStack.size() -1)) {
// 1/27/25: earlier this was originally componentStack.size() - 1 as the second argument to sublist().
// On examination today, it didn't make sense, because it would be chopping off the farthest ancestor from
// search. So I removed it, in accordance with the implementation in hasAncestor().
for (final var ancestor : componentStack.subList(1, componentStack.size())) {
if (matching.test(ancestor)) {
return ancestor;
}
@ -79,6 +82,19 @@ public class DefaultComponentContext implements ComponentContext {
return null;
}
@Override
public boolean hasAncestor(Predicate<? super ViewComponent> matching) {
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
if (componentStack.size() > 1) {
for (final var ancestor : componentStack.subList(1, componentStack.size())) {
if (matching.test(ancestor)) {
return true;
}
}
}
return false;
}
@Override
public List<ViewComponent> getAllAncestors() {
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();

View File

@ -5,7 +5,6 @@ import groowt.view.component.ComponentRenderException
import groowt.view.component.context.ComponentContext
import groowt.view.component.context.ComponentScope.TypeAndFactory
import groowt.view.component.factory.ComponentFactory
import groowt.view.component.web.WithHtml
class IntrinsicHtml extends DelegatingWebViewComponent implements WithHtml {

View File

@ -1,6 +1,7 @@
package groowt.view.component.web.lib
import groowt.view.View
import groowt.view.component.ComponentRenderException
import groowt.view.component.runtime.DefaultComponentWriter
class Outlet extends DelegatingWebViewComponent {
@ -14,6 +15,11 @@ class Outlet extends DelegatingWebViewComponent {
@Override
protected View getDelegate() {
return { Writer w ->
if (!context.hasAncestor(OutletContainer)) {
throw new ComponentRenderException(
"<Outlet> is being used outside of a component implementing OutletContainer."
)
}
def cw = new DefaultComponentWriter(w, context.renderContext, context)
givenChildren.each { cw << it }
}

View File

@ -0,0 +1,5 @@
package groowt.view.component.web.lib
import groowt.view.component.web.WebViewComponent
interface OutletContainer extends WebViewComponent {}

View File

@ -1,4 +1,4 @@
package groowt.view.component.web
package groowt.view.component.web.lib
trait WithHtml {

View File

@ -1,17 +1,35 @@
package groowt.view.component.web.lib
import groowt.view.component.web.WebViewComponentContext
import groowt.view.component.web.WebViewComponentScope
import org.junit.jupiter.api.Test
class OutletTests extends AbstractWebViewComponentTests {
static final class DummyOutletContainer extends Echo implements OutletContainer {
DummyOutletContainer() {
super([:])
}
}
@Override
void configureContext(WebViewComponentContext context) {
context.configureRootScope(WebViewComponentScope) {
addWithNoArgConstructor(DummyOutletContainer)
}
}
@Test
void smokeScreen() {
doTest('<Outlet />', '')
doTest('<OutletTests.DummyOutletContainer><Outlet /></OutletTests.DummyOutletContainer>', '')
}
@Test
void withChildren() {
doTest('<Echo items={[0, 1, 2]}><Outlet children={items} /></Echo>', '012')
doTest('<OutletTests.DummyOutletContainer><Echo items={[0, 1, 2]}><Outlet children={items} /></Echo></OutletTests.DummyOutletContainer>', '012')
}
}