Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions flow-server/src/main/java/com/vaadin/flow/component/Component.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
import com.vaadin.flow.internal.nodefeature.ElementData;
import com.vaadin.flow.server.Attributes;
import com.vaadin.flow.shared.Registration;
import com.vaadin.signals.BindingActiveException;
import com.vaadin.signals.Signal;

/**
* A Component is a higher level abstraction of an {@link Element} or a
Expand Down Expand Up @@ -573,6 +575,39 @@ public static <T extends Component> T from(Element element,
return ComponentUtil.componentFromElement(element, componentType, true);
}

/**
* Binds a {@link Signal}'s value to the <code>visible</code> property of
* this component and keeps property synchronized with the signal value
* while the component is in attached state. When the element is in detached
* state, signal value changes have no effect. <code>null</code> signal
* unbinds the existing binding.
* <p>
* While a Signal is bound to a property, any attempt to set the visibility
* manually with {@link #setVisible(boolean)} throws
* {@link com.vaadin.signals.BindingActiveException}. Same happens when
* trying to bind a new Signal while one is already bound.
* <p>
* Example of usage:
*
* <pre>
* ValueSignal&lt;Boolean&gt; signal = new ValueSignal&lt;&gt;(true);
* Span component = new Span();
* add(component);
* component.bindVisible(signal);
* signal.value(false); // The component is set hidden
* </pre>
*
* @param visibleSignal
* the signal to bind or <code>null</code> to unbind any existing
* binding
* @throws BindingActiveException
* thrown when there is already an existing binding
* @see #setVisible(boolean)
*/
public void bindVisible(Signal<Boolean> visibleSignal) {
getElement().bindVisible(visibleSignal);
}

/**
* Sets the component visibility value.
* <p>
Expand Down
35 changes: 34 additions & 1 deletion flow-server/src/main/java/com/vaadin/flow/dom/Element.java
Original file line number Diff line number Diff line change
Expand Up @@ -1321,7 +1321,7 @@ private void setTextContent(String textContent) {
* signal value changes have no effect. <code>null</code> signal unbinds the
* existing binding.
* <p>
* While a Signal is bound to an attribute, any attempt to set the text
* While a Signal is bound to a property, any attempt to set the text
* content manually throws
* {@link com.vaadin.signals.BindingActiveException}. Same happens when
* trying to bind a new Signal while one is already bound.
Expand Down Expand Up @@ -1799,6 +1799,39 @@ public Optional<ShadowRoot> getShadowRoot() {
return Optional.of(ShadowRoot.get(shadowRoot));
}

/**
* Binds a {@link Signal}'s value to the <code>visible</code> property of
* this element and keeps property synchronized with the signal value while
* the element is in attached state. When the element is in detached state,
* signal value changes have no effect. <code>null</code> signal unbinds the
* existing binding.
* <p>
* While a Signal is bound to a property, any attempt to set the visibility
* manually with {@link #setVisible(boolean)} throws
* {@link com.vaadin.signals.BindingActiveException}. Same happens when
* trying to bind a new Signal while one is already bound.
* <p>
* Example of usage:
*
* <pre>
* ValueSignal&lt;Boolean&gt; signal = new ValueSignal&lt;&gt;(true);
* Element element = new Element("span");
* getElement().appendChild(element);
* element.bindVisible(signal);
* signal.value(false); // The element is set hidden
* </pre>
*
* @param visibleSignal
* the signal to bind or <code>null</code> to unbind any existing
* binding
* @throws BindingActiveException
* thrown when there is already an existing binding
* @see #setVisible(boolean)
*/
public void bindVisible(Signal<Boolean> visibleSignal) {
getStateProvider().bindVisibleSignal(this, visibleSignal);
}

/**
* Sets the element visibility value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,19 @@ void appendVirtualChild(StateNode node, Element child, String type,
*/
void visit(StateNode node, NodeVisitor visitor);

/**
* Binds the given signal to the <code>visible</code> property.
* <code>null</code> signal unbinds existing binding.
*
* @param owner
* the owner element for which the signal is bound, not
* <code>null</code>
* @param signal
* the signal to bind or <code>null</code> to unbind any existing
* binding
*/
void bindVisibleSignal(Element owner, Signal<Boolean> signal);

/**
* Sets the {@code node} visibility.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ public void visit(StateNode node, NodeVisitor visitor) {
visitor.visit(ElementType.REGULAR, Element.get(node));
}

@Override
public void bindVisibleSignal(Element owner, Signal<Boolean> signal) {
throw new UnsupportedOperationException();
}

@Override
public void setVisible(StateNode node, boolean visible) {
throw new UnsupportedOperationException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,21 @@ public void visit(StateNode node, NodeVisitor visitor) {
}
}

@Override
public void bindVisibleSignal(Element owner, Signal<Boolean> signal) {
assert owner.getNode().hasFeature(ElementData.class);
owner.getNode().getFeature(ElementData.class).bindVisibleSignal(owner,
signal);
}

@Override
public void setVisible(StateNode node, boolean visible) {
assert node.hasFeature(ElementData.class);
ElementData feature = node.getFeature(ElementData.class);
if (feature.hasSignal(NodeProperties.VISIBLE)) {
throw new BindingActiveException(
"setVisible is not allowed while a binding exists.");
}
node.getFeature(ElementData.class).setVisible(visible);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@ protected Node<?> getNode(StateNode node) {
return ShadowRoot.get(node);
}

@Override
public void bindVisibleSignal(Element owner, Signal<Boolean> signal) {
throw new UnsupportedOperationException();
}

@Override
public void setVisible(StateNode node, boolean visible) {
throw new UnsupportedOperationException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,9 @@
import java.util.stream.Stream;

import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementEffect;
import com.vaadin.flow.internal.JacksonCodec;
import com.vaadin.flow.internal.ReflectTools;
import com.vaadin.flow.internal.StateNode;
import com.vaadin.flow.shared.Registration;
import com.vaadin.signals.BindingActiveException;
import com.vaadin.signals.Signal;

/**
Expand Down Expand Up @@ -141,11 +138,6 @@ public static boolean isValidValueType(Serializable value) {
|| StateNode.class.isAssignableFrom(type);
}

public boolean hasSignal(String key) {
return super.get(key) instanceof SignalBinding binding
&& binding.signal() != null && binding.registration() != null;
}

@Override
public void updateFromClient(String key, Serializable value) {
if (hasSignal(key)) {
Expand Down Expand Up @@ -173,29 +165,8 @@ public void updateFromClient(String key, Serializable value) {
* given property
*/
public void bindSignal(Element owner, String name, Signal<?> signal) {
SignalBinding previousSignalBinding;
if (super.get(name) instanceof SignalBinding binding) {
previousSignalBinding = binding;
} else {
previousSignalBinding = null;
}
if (signal != null && hasSignal(name)) {
throw new BindingActiveException();
}
Registration registration = signal != null
? ElementEffect.bind(owner, signal,
(element, value) -> setPropertyFromSignal(name, value))
: null;
if (signal == null && previousSignalBinding != null) {
if (previousSignalBinding.registration() != null) {
previousSignalBinding.registration().remove();
}
// revert to plain stored value (may be null)
put(name, get(name), false);
} else {
put(name, new SignalBinding(signal, registration, get(name)),
false);
}
bindSignal(owner, name, signal,
(element, value) -> setPropertyFromSignal(name, value));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import tools.jackson.databind.node.ObjectNode;

import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementEffect;
import com.vaadin.flow.internal.JacksonUtils;
import com.vaadin.flow.internal.NodeOwner;
import com.vaadin.flow.internal.StateNode;
Expand Down Expand Up @@ -92,32 +91,14 @@ public void set(String attribute, String value) {
* @param signal
* the signal to bind or <code>null</code> to unbind any existing
* binding
* @throws com.vaadin.signals.BindingActiveException
* thrown when there is already an existing binding for the
* given attribute
*/
public void bindSignal(Element owner, String attribute,
Signal<String> signal) {
SignalBinding previousSignalBinding;
if (super.get(attribute) instanceof SignalBinding binding) {
previousSignalBinding = binding;
} else {
previousSignalBinding = null;
}
if (signal != null && previousSignalBinding != null
&& previousSignalBinding.signal() != null) {
throw new BindingActiveException();
}

Registration registration = signal != null ? ElementEffect.bind(owner,
signal, (element, value) -> doSet(attribute, value)) : null;
if (signal == null && previousSignalBinding != null) {
if (previousSignalBinding.registration() != null) {
previousSignalBinding.registration().remove();
}
put(attribute, get(attribute), false);
} else {
put(attribute,
new SignalBinding(signal, registration, get(attribute)),
false);
}
bindSignal(owner, attribute, signal,
(element, value) -> doSet(attribute, value));
}

/**
Expand All @@ -139,12 +120,6 @@ public boolean has(String attribute) {
return false;
}

private boolean hasSignal(String attribute) {
Serializable value = super.get(attribute);
return value instanceof SignalBinding binding
&& binding.signal() != null;
}

/**
* Removes the named attribute.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import tools.jackson.databind.node.BaseJsonNode;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.internal.StateNode;
import com.vaadin.signals.Signal;

/**
* Map of basic element information.
Expand Down Expand Up @@ -107,7 +109,11 @@ public void setVisible(boolean visible) {
* @return Element is visible by default
*/
public boolean isVisible() {
return !Boolean.FALSE.equals(get(NodeProperties.VISIBLE));
var value = get(NodeProperties.VISIBLE);
return !Boolean.FALSE
.equals(value instanceof SignalBinding signalBinding
? signalBinding.value()
: value);
}

/**
Expand All @@ -132,4 +138,33 @@ public void setJavaClass(Class<? extends Component> componentClass) {
public String getJavaClass() {
return getOrDefault(NodeProperties.JAVA_CLASS, (String) null);
}

/**
* Binds the given signal to the <code>visible</code> property.
* <code>null</code> signal unbinds existing binding.
*
* @param owner
* the element owning the property, not <code>null</code>
* @param signal
* the signal to bind or <code>null</code> to unbind any existing
* binding
* @throws com.vaadin.signals.BindingActiveException
* thrown when there is already an existing binding for the
* <code>visible</code> property
*/
public void bindVisibleSignal(Element owner, Signal<Boolean> signal) {
bindSignal(owner, NodeProperties.VISIBLE, signal,
(element, value) -> putVisibleSignalValue(value));
}

private void putVisibleSignalValue(Boolean value) {
boolean booleanValue = (value != null) ? value : Boolean.FALSE;
if (hasSignal(NodeProperties.VISIBLE)) {
SignalBinding b = (SignalBinding) super.get(NodeProperties.VISIBLE);
put(NodeProperties.VISIBLE, new SignalBinding(b.signal(),
b.registration(), booleanValue));
} else {
put(NodeProperties.VISIBLE, booleanValue);
}
}
}
Loading
Loading