Skip to content

Commit 1fe3775

Browse files
committed
Refactor parent-child relations
1 parent 3830236 commit 1fe3775

File tree

4 files changed

+50
-39
lines changed

4 files changed

+50
-39
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ the `View`. It is important to keep this in mind to prevent the `Component` from
157157
violating MVVM responsibility principles.
158158

159159
The `ComponentMediator` is the interface that the `ViewModel` uses to interact with the `Component`. This interface
160-
is needed for two reasons: first, it allows the `ViewModel` to be tested independently; second, it allows the `Component`
161-
to access both the `View` and the `ViewModel`, without exposing the `View` to the `ViewModel`.
160+
is needed for two reasons: first, it allows the `ViewModel` to be tested independently; second, it allows the
161+
`ViewModel` to use the `Component` without knowing the View, since the Component has knowledge of the View.
162162

163163
The `ComponentMediator` is implemented as a non-static inner class within the `Component`, which allows it to work with
164164
both the `View` and the `ViewModel` without violating MVVM principles.

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractChildComponent.java

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ protected abstract class Mediator extends AbstractParentComponent.Mediator imple
3131
private final ReadOnlyObjectWrapper<ParentViewModel> parent = new ReadOnlyObjectWrapper<>();
3232

3333
public Mediator() {
34-
AbstractChildComponent.this.parent.addListener((ov, oldV, newV) -> {
35-
if (newV == null) {
36-
this.parent.set(null);
37-
} else {
38-
this.parent.set(newV.getView().getViewModel());
39-
}
40-
});
34+
this.parent.bind(
35+
AbstractChildComponent.this.parent.map(p -> {
36+
if (p != null) {
37+
return p.getView().getViewModel();
38+
} else {
39+
return null;
40+
}
41+
})
42+
);
4143
}
4244

4345
@Override
@@ -67,16 +69,13 @@ public ParentComponent<?> getParent() {
6769
return this.parent.get();
6870
}
6971

70-
/**
71-
* Sets the parent component for this component.
72-
* <p>
73-
* This method is normally called automatically when the component is added as a child to another component.
74-
* It can also be used explicitly when only the child-to-parent relationship needs to be established, without
75-
* adding the component to the parent's list of children.
76-
*
77-
* @param parent the parent component to set
78-
*/
79-
protected void setParent(ParentComponent<?> parent) {
72+
@Override
73+
public <T extends ParentComponent<?>> T getParent(Class<T> parentClass) {
74+
return (T) getParent();
75+
}
76+
77+
@Override
78+
public void setParent(ParentComponent<?> parent) {
8079
this.parent.set(parent);
8180
}
8281

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractParentComponent.java

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616

1717
package com.techsenger.mvvm4fx.core;
1818

19-
import com.techsenger.toolkit.fx.collections.ListSynchronizer;
19+
import com.techsenger.toolkit.fx.binding.ListBinder;
2020
import java.util.List;
2121
import javafx.collections.FXCollections;
22-
import javafx.collections.ListChangeListener;
2322
import javafx.collections.ObservableList;
2423

2524
/**
@@ -31,7 +30,7 @@ public abstract class AbstractParentComponent<T extends AbstractParentView<?>> e
3130

3231
protected abstract class Mediator extends AbstractComponent.Mediator implements ParentMediator {
3332

34-
private final ListSynchronizer childrenSynchronizer;
33+
private final ListBinder childrenBinder;
3534

3635
private final ObservableList<ChildViewModel> modifiableChildren = FXCollections.observableArrayList();
3736

@@ -40,8 +39,8 @@ protected abstract class Mediator extends AbstractComponent.Mediator implements
4039

4140
public Mediator() {
4241
var outerChildren = AbstractParentComponent.this.modifiableChildren;
43-
childrenSynchronizer = new ListSynchronizer<ChildComponent<?>, ChildViewModel>(outerChildren,
44-
modifiableChildren, (v) -> v.getView().getViewModel());
42+
childrenBinder = ListBinder.bindContent(modifiableChildren, outerChildren,
43+
(v) -> v.getView().getViewModel());
4544
}
4645

4746
@Override
@@ -57,18 +56,6 @@ public ObservableList<ChildViewModel> getChildren() {
5756

5857
public AbstractParentComponent(T view) {
5958
super(view);
60-
modifiableChildren.addListener((ListChangeListener<ChildComponent<?>>) (change) -> {
61-
while (change.next()) {
62-
if (change.wasAdded()) {
63-
change.getAddedSubList().stream().map(e -> (AbstractChildComponent<?>) e)
64-
.forEach(e -> e.setParent(this));
65-
}
66-
if (change.wasRemoved()) {
67-
change.getRemoved().stream().map(e -> (AbstractChildComponent<?>) e)
68-
.forEach(e -> e.setParent(null));
69-
}
70-
}
71-
});
7259
}
7360

7461
@Override
@@ -101,7 +88,13 @@ public ObservableList<ChildComponent<?>> getChildren() {
10188
@Override
10289
protected abstract AbstractParentComponent.Mediator createMediator();
10390

104-
protected ObservableList<ChildComponent<?>> getModifiableChildren() {
105-
return modifiableChildren;
91+
protected void addChild(ChildComponent<?> child) {
92+
this.modifiableChildren.add(child);
93+
child.setParent(this);
94+
}
95+
96+
protected void removeChild(ChildComponent<?> child) {
97+
this.modifiableChildren.remove(child);
98+
child.setParent(null);
10699
}
107100
}

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/ChildComponent.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,31 @@ public interface ChildComponent<T extends ChildView<?>> extends ParentComponent<
3030
*
3131
* @return the property containing the parent component
3232
*/
33-
ReadOnlyObjectProperty<ParentComponent<?>> parentProperty();
33+
ReadOnlyObjectProperty<? extends ParentComponent<?>> parentProperty();
3434

3535
/**
3636
* Returns the value of {@link #parentProperty()}.
3737
*
3838
* @return the parent component, or {@code null} if this component has no parent
3939
*/
4040
ParentComponent<?> getParent();
41+
42+
/**
43+
* Returns the value of {@link #parentProperty()} cast to the specified type.
44+
*
45+
* @param <T> the expected type of the parent component
46+
* @param parentClass the class object representing the expected parent type
47+
* @return the parent component cast to the specified type, or {@code null} if this component has no parent
48+
* @throws ClassCastException if the parent exists but is not of the specified type
49+
*/
50+
<T extends ParentComponent<?>> T getParent(Class<T> parentClass);
51+
52+
/**
53+
* Sets the parent component of this component.
54+
*
55+
* <p>Framework contract: This method is intended to be called exclusively by {@link ParentComponent}
56+
* implementations while managing the component hierarchy. Direct invocation by user code results in undefined
57+
* behavior.
58+
*/
59+
void setParent(ParentComponent<?> parent);
4160
}

0 commit comments

Comments
 (0)