From 0afc55e62a3a1dcf2c5796e837e837d584f7e2a6 Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Thu, 4 Dec 2025 13:05:12 +0400 Subject: [PATCH 01/28] Initial infrastructure jmix-framework/jmix_4185 --- .../flowui/component/HasFacetsComponents.java | 59 +++++++++++++++++++ .../main/java/io/jmix/flowui/facet/Facet.java | 16 ++--- .../java/io/jmix/flowui/facet/FacetOwner.java | 23 ++++++++ .../io/jmix/flowui/fragment/Fragment.java | 12 +++- .../jmix/flowui/fragment/FragmentFacets.java | 25 ++++++++ .../jmix/flowui/fragment/FragmentUtils.java | 23 ++++++++ .../fragment/impl/FragmentFacetsImpl.java | 47 +++++++++++++++ .../io/jmix/flowui/impl/FragmentsImpl.java | 3 + .../main/java/io/jmix/flowui/view/View.java | 4 +- .../java/io/jmix/flowui/view/ViewFacets.java | 38 +----------- .../jmix/flowui/view/impl/ViewFacetsImpl.java | 56 +++--------------- .../io/jmix/flowui/xml/facet/FacetLoader.java | 10 ++-- .../jmix/flowui/xml/facet/FacetProvider.java | 4 +- .../xml/facet/loader/AbstractFacetLoader.java | 6 +- .../flowui/xml/facet/loader/FacetLoader.java | 8 +-- .../xml/layout/loader/FragmentLoader.java | 32 ++++++++-- .../io/jmix/flowui/view/fragment.xsd | 1 + 17 files changed, 255 insertions(+), 112 deletions(-) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/HasFacetsComponents.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FacetOwner.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentFacets.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/impl/FragmentFacetsImpl.java diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/HasFacetsComponents.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/HasFacetsComponents.java new file mode 100644 index 0000000000..61742a0da7 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/HasFacetsComponents.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.component; + +import io.jmix.flowui.facet.Facet; +import org.springframework.lang.Nullable; + +import java.util.stream.Stream; + +/** + * Interface defining methods for managing facet API elements. + */ +public interface HasFacetsComponents { + + /** + * Adds a facet to a view. + * + * @param facet the facet to be added + */ + void addFacet(Facet facet); + + /** + * Returns a facet by its ID. + * + * @param id the identifier of the facet to retrieve + * @return the facet corresponding to the given identifier, + * or {@code null} if no facet is associated with the identifier + */ + @Nullable + Facet getFacet(String id); + + /** + * Removes the specified facet from the view. + * + * @param facet the facet to be removed + */ + void removeFacet(Facet facet); + + /** + * Returns a stream of all facets associated with the view. + * + * @return a stream of {@link Facet} instances associated with the view + */ + Stream getFacets(); +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/Facet.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/Facet.java index 87279ea5d8..2aca60bf6e 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/Facet.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/Facet.java @@ -16,11 +16,11 @@ package io.jmix.flowui.facet; -import io.jmix.flowui.view.View; +import com.vaadin.flow.component.Composite; import org.springframework.lang.Nullable; /** - * Non-visual component of a {@link View}. + * Non-visual component of a {@link FacetOwner}. */ public interface Facet { @@ -40,15 +40,17 @@ public interface Facet { void setId(@Nullable String id); /** - * @return a view containing this facet + * @param the type of the owner + * @return an owner containing this facet */ @Nullable - View getOwner(); + & FacetOwner> T getOwner(); /** - * Sets a view containing this facet. + * Sets an owner containing this facet. * - * @param owner a view containing this facet + * @param owner an owner containing this facet + * @param the type of the owner */ - void setOwner(@Nullable View owner); + & FacetOwner> void setOwner(@Nullable T owner); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FacetOwner.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FacetOwner.java new file mode 100644 index 0000000000..3ddbb8a88f --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FacetOwner.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet; + +/** + * Marker interface for components that can have facets. + */ +public interface FacetOwner { +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/Fragment.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/Fragment.java index 8586756ee4..3e3aa4a1cb 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/Fragment.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/Fragment.java @@ -22,6 +22,7 @@ import com.vaadin.flow.shared.Registration; import io.jmix.flowui.UiComponents; import io.jmix.flowui.component.UiComponentUtils; +import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.sys.event.UiEventsManager; import io.jmix.flowui.view.Subscribe; import io.jmix.flowui.view.Target; @@ -53,12 +54,13 @@ * * @param the type of the content */ -public abstract class Fragment extends Composite implements FragmentOwner { +public abstract class Fragment extends Composite implements FragmentOwner, FacetOwner { protected UiComponents uiComponents; protected FragmentData fragmentData; protected FragmentActions fragmentActions; + protected FragmentFacets fragmentFacets; protected FragmentOwner parentController; @@ -86,6 +88,14 @@ protected void setFragmentActions(FragmentActions fragmentActions) { this.fragmentActions = fragmentActions; } + protected FragmentFacets getFragmentFacets() { + return fragmentFacets; + } + + protected void setFragmentFacets(FragmentFacets fragmentFacets) { + this.fragmentFacets = fragmentFacets; + } + protected FragmentOwner getParentController() { return parentController; } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentFacets.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentFacets.java new file mode 100644 index 0000000000..dc09db3467 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentFacets.java @@ -0,0 +1,25 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.fragment; + +import io.jmix.flowui.component.HasFacetsComponents; + +/** + * Interface for managing facets associated with a {@link Fragment}. + */ +public interface FragmentFacets extends HasFacetsComponents { +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java index 119a3cfb67..66b896bc4a 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java @@ -117,6 +117,29 @@ public static void setFragmentActions(Fragment fragment, fragment.setFragmentActions(actions); } + /** + * Returns the {@link FragmentFacets} associated with the specified {@link Fragment}. + * + * @param fragment the {@link Fragment} which to retrieve the associated {@link FragmentFacets}, + * must not be {@code null} + * @return the {@link FragmentFacets} associated with the given {@link Fragment} + */ + public static FragmentFacets getFragmentFacets(Fragment fragment) { + return fragment.getFragmentFacets(); + } + + /** + * Sets the {@link FragmentFacets} for the specified {@link Fragment}. + * + * @param fragment the {@link Fragment} for which the {@link FragmentFacets} are to be set, + * must not be {@code null} + * @param fragmentFacets the {@link FragmentFacets} to associate with the specified {@link Fragment}, + * must not be {@code null} + */ + public static void setFragmentFacets(Fragment fragment, FragmentFacets fragmentFacets) { + fragment.setFragmentFacets(fragmentFacets); + } + /** * Gets the owner of the passed {@link Fragment}. * diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/impl/FragmentFacetsImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/impl/FragmentFacetsImpl.java new file mode 100644 index 0000000000..62a0c2405d --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/impl/FragmentFacetsImpl.java @@ -0,0 +1,47 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.fragment.impl; + +import com.vaadin.flow.component.Composite; +import io.jmix.flowui.facet.FacetOwner; +import io.jmix.flowui.facet.impl.AbstractFacetComponentsHolder; +import io.jmix.flowui.fragment.Fragment; +import io.jmix.flowui.fragment.FragmentFacets; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * Implementation of {@link FragmentFacets} interface. This class manages a collection of facets associated with a + * specific {@link Fragment}. + */ +@Component("flowui_FragmentFacets") +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class FragmentFacetsImpl extends AbstractFacetComponentsHolder implements FragmentFacets { + + protected final Fragment fragment; + + public FragmentFacetsImpl(Fragment fragment) { + this.fragment = fragment; + } + + @Override + protected & FacetOwner> T getOwner() { + //noinspection unchecked + return (T) fragment; + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FragmentsImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FragmentsImpl.java index 40be3d15de..d188a456f7 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FragmentsImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FragmentsImpl.java @@ -107,6 +107,9 @@ public void init(ComponentLoader.Context hostContext, Fragment fragment) { FragmentActions actions = applicationContext.getBean(FragmentActions.class, fragment); FragmentUtils.setFragmentActions(fragment, actions); + FragmentFacets facets = applicationContext.getBean(FragmentFacets.class, fragment); + FragmentUtils.setFragmentFacets(fragment, facets); + FragmentLoaderContext context; String descriptorPath = FragmentUtils.resolveDescriptorPath(fragment.getClass()); if (descriptorPath != null) { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/View.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/View.java index e2bbaa3e83..2d5c7a55df 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/View.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/View.java @@ -25,6 +25,7 @@ import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.event.view.ViewClosedEvent; import io.jmix.flowui.event.view.ViewOpenedEvent; +import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.fragment.FragmentOwner; import io.jmix.flowui.kit.meta.StudioIgnore; import io.jmix.flowui.model.ViewData; @@ -68,7 +69,8 @@ * @param type of the root UI component */ public class View extends Composite - implements BeforeEnterObserver, AfterNavigationObserver, BeforeLeaveObserver, HasDynamicTitle, FragmentOwner { + implements BeforeEnterObserver, AfterNavigationObserver, BeforeLeaveObserver, HasDynamicTitle, + FragmentOwner, FacetOwner { private ApplicationContext applicationContext; private MeterRegistry meterRegistry; diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/ViewFacets.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/ViewFacets.java index 74adfec511..6db27d8c65 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/ViewFacets.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/ViewFacets.java @@ -16,44 +16,10 @@ package io.jmix.flowui.view; -import io.jmix.flowui.facet.Facet; -import org.springframework.lang.Nullable; - -import java.util.stream.Stream; +import io.jmix.flowui.component.HasFacetsComponents; /** * Interface for managing facets associated with a {@link View}. */ -public interface ViewFacets { - - /** - * Adds a facet to a view. - * - * @param facet the facet to be added - */ - void addFacet(Facet facet); - - /** - * Returns a facet by its ID. - * - * @param id the identifier of the facet to retrieve - * @return the facet corresponding to the given identifier, - * or {@code null} if no facet is associated with the identifier - */ - @Nullable - Facet getFacet(String id); - - /** - * Removes the specified facet from the view. - * - * @param facet the facet to be removed - */ - void removeFacet(Facet facet); - - /** - * Returns a stream of all facets associated with the view. - * - * @return a stream of {@link Facet} instances associated with the view - */ - Stream getFacets(); +public interface ViewFacets extends HasFacetsComponents { } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/impl/ViewFacetsImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/impl/ViewFacetsImpl.java index 8273c065ea..ab01e03d7a 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/impl/ViewFacetsImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/impl/ViewFacetsImpl.java @@ -16,19 +16,17 @@ package io.jmix.flowui.view.impl; +import com.vaadin.flow.component.Composite; import io.jmix.flowui.facet.Facet; +import io.jmix.flowui.facet.FacetOwner; +import io.jmix.flowui.facet.impl.AbstractFacetComponentsHolder; import io.jmix.flowui.view.View; import io.jmix.flowui.view.ViewFacets; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; -import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; -import java.util.HashSet; import java.util.Set; -import java.util.stream.Stream; - -import static io.jmix.core.common.util.Preconditions.checkNotNullArgument; /** * Implementation of the {@link ViewFacets} interface. This class manages a collection of facets @@ -36,7 +34,7 @@ */ @Component("flowui_ViewFacets") @Scope(BeanDefinition.SCOPE_PROTOTYPE) -public class ViewFacetsImpl implements ViewFacets { +public class ViewFacetsImpl extends AbstractFacetComponentsHolder implements ViewFacets { protected final View view; @@ -47,48 +45,8 @@ public ViewFacetsImpl(View view) { } @Override - public void addFacet(Facet facet) { - checkNotNullArgument(facet); - - if (facets == null) { - facets = new HashSet<>(); - } - - if (!facets.contains(facet)) { - facets.add(facet); - facet.setOwner(view); - } - } - - @Nullable - @Override - public Facet getFacet(String id) { - checkNotNullArgument(id); - - if (facets == null) { - return null; - } - - return facets.stream() - .filter(f -> id.equals(f.getId())) - .findFirst() - .orElse(null); - } - - @Override - public void removeFacet(Facet facet) { - checkNotNullArgument(facet); - - if (facets != null - && facets.remove(facet)) { - facet.setOwner(null); - } - } - - @Override - public Stream getFacets() { - return facets == null - ? Stream.empty() - : facets.stream(); + protected & FacetOwner> T getOwner() { + //noinspection unchecked + return (T) view; } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java index a203109e9f..c255d264bb 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java @@ -22,7 +22,7 @@ import io.jmix.flowui.Facets; import io.jmix.flowui.exception.GuiDevelopmentException; import io.jmix.flowui.facet.Facet; -import io.jmix.flowui.xml.layout.ComponentLoader.ComponentContext; +import io.jmix.flowui.xml.layout.ComponentLoader; import org.dom4j.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; @@ -73,7 +73,7 @@ protected void setApplicationContext(ApplicationContext applicationContext) { * @param context loading context * @return loaded facet */ - public Facet load(Element element, ComponentContext context) { + public Facet load(Element element, ComponentLoader.Context context) { io.jmix.flowui.xml.facet.loader.FacetLoader facetLoader = getLoader(element, context); if (facetLoader == null) { // fallback @@ -87,7 +87,7 @@ public Facet load(Element element, ComponentContext context) { @Nullable protected io.jmix.flowui.xml.facet.loader.FacetLoader getLoader(Element element, - ComponentContext context) { + ComponentLoader.Context context) { Class> loaderClass = facetLoaderResolver.getLoader(element); @@ -101,7 +101,7 @@ protected io.jmix.flowui.xml.facet.loader.FacetLoader getLoader(Element eleme protected io.jmix.flowui.xml.facet.loader.FacetLoader initLoader( Element element, Class> loaderClass, - ComponentContext context + ComponentLoader.Context context ) { Constructor> constructor; @@ -128,7 +128,7 @@ protected io.jmix.flowui.xml.facet.loader.FacetLoader initLoader( // for backward compatibility @Deprecated(since = "3.0", forRemoval = true) @SuppressWarnings({"unchecked", "rawtypes"}) - protected Facet _load(Element element, ComponentContext context) { + protected Facet _load(Element element, ComponentLoader.Context context) { Collection> facetProviders = registrations.get(element.getName()); FacetProvider facetProvider = beanSelector.selectFrom(facetProviders); if (facetProvider == null) { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetProvider.java index 43ee4532bb..317dc13f1a 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetProvider.java @@ -18,7 +18,7 @@ import io.jmix.flowui.facet.Facet; import io.jmix.flowui.sys.registration.FacetRegistrationBuilder; -import io.jmix.flowui.xml.layout.ComponentLoader.ComponentContext; +import io.jmix.flowui.xml.layout.ComponentLoader; import org.dom4j.Element; /** @@ -53,5 +53,5 @@ public interface FacetProvider { * @param element XML element * @param context loading context */ - void loadFromXml(T facet, Element element, ComponentContext context); + void loadFromXml(T facet, Element element, ComponentLoader.Context context); } \ No newline at end of file diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractFacetLoader.java index 8fc5d9b332..8089896ff7 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractFacetLoader.java @@ -30,7 +30,7 @@ */ public abstract class AbstractFacetLoader implements FacetLoader { - protected ComponentLoader.ComponentContext context; + protected ComponentLoader.Context context; protected Facets facets; protected LoaderSupport loaderSupport; @@ -59,12 +59,12 @@ public void setElement(Element element) { } @Override - public ComponentLoader.ComponentContext getContext() { + public ComponentLoader.Context getContext() { return context; } @Override - public void setContext(ComponentLoader.ComponentContext context) { + public void setContext(ComponentLoader.Context context) { this.context = context; } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FacetLoader.java index 41b4feb95c..f095f398b8 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FacetLoader.java @@ -66,14 +66,14 @@ public interface FacetLoader { /** * Returns the context associated with the current facet loading and initialization process. * - * @return the {@link ComponentLoader.ComponentContext} instance + * @return the {@link ComponentLoader.Context} instance */ - ComponentLoader.ComponentContext getContext(); + ComponentLoader.Context getContext(); /** * Sets the context for loading and initializing facet components. * - * @param context the {@link ComponentLoader.ComponentContext} instance to set + * @param context the {@link ComponentLoader.Context} instance to set */ - void setContext(ComponentLoader.ComponentContext context); + void setContext(ComponentLoader.Context context); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/FragmentLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/FragmentLoader.java index 130138b95d..2b2e6a44eb 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/FragmentLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/FragmentLoader.java @@ -21,11 +21,10 @@ import io.jmix.flowui.UiComponents; import io.jmix.flowui.component.HasDataComponents; import io.jmix.flowui.exception.GuiDevelopmentException; -import io.jmix.flowui.fragment.Fragment; -import io.jmix.flowui.fragment.FragmentActions; -import io.jmix.flowui.fragment.FragmentData; -import io.jmix.flowui.fragment.FragmentUtils; +import io.jmix.flowui.facet.Facet; +import io.jmix.flowui.fragment.*; import io.jmix.flowui.kit.action.Action; +import io.jmix.flowui.xml.facet.FacetLoader; import io.jmix.flowui.xml.layout.ComponentLoader; import io.jmix.flowui.xml.layout.LoaderResolver; import io.jmix.flowui.xml.layout.support.ActionLoaderSupport; @@ -53,6 +52,7 @@ public class FragmentLoader { protected LoaderSupport loaderSupport; protected ActionLoaderSupport actionLoaderSupport; protected DataComponentsLoaderSupport dataComponentsLoaderSupport; + protected FacetLoader facetLoader; protected final FragmentLoaderContext context; protected final Element element; @@ -82,6 +82,7 @@ public void createContent() { loadData(element); loadActions(element); + loadFacets(element); ComponentLoader componentLoader = getLoader(contentElement); componentLoader.initComponent(); @@ -140,6 +141,21 @@ protected Action loadDeclarativeAction(Element element) { getActionLoaderSupport().loadDeclarativeAction(element)); } + protected void loadFacets(Element element) { + Element facetsElement = element.element("facets"); + if (facetsElement == null) { + return; + } + + FragmentFacets fragmentFacets = FragmentUtils.getFragmentFacets(context.getFragment()); + FacetLoader loader = getFacetLoader(); + + for (Element facetElement : facetsElement.elements()) { + Facet facet = loader.load(facetElement, context); + fragmentFacets.addFacet(facet); + } + } + @SuppressWarnings("rawtypes") protected ComponentLoader getLoader(Element element) { Class loaderClass = loaderResolver.getLoader(element); @@ -196,6 +212,14 @@ public T create(Class type) { return loader; } + protected FacetLoader getFacetLoader() { + if (facetLoader == null) { + facetLoader = applicationContext.getBean(FacetLoader.class); + } + + return facetLoader; + } + protected ActionLoaderSupport getActionLoaderSupport() { if (actionLoaderSupport == null) { actionLoaderSupport = applicationContext.getBean(ActionLoaderSupport.class, context); diff --git a/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd b/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd index ab39cf015f..9724fd2b5f 100644 --- a/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd +++ b/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd @@ -29,6 +29,7 @@ + From 3e7cda7d3f7c4963084447defd3e9d77505a9f2e Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Thu, 4 Dec 2025 13:05:51 +0400 Subject: [PATCH 02/28] Initial infrastructure jmix-framework/jmix_4185 --- .../jmix/flowui/facet/impl/AbstractFacet.java | 12 +-- .../impl/AbstractFacetComponentsHolder.java | 84 +++++++++++++++++++ 2 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractFacetComponentsHolder.java diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractFacet.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractFacet.java index 4f9f4a396e..0ab21d080e 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractFacet.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractFacet.java @@ -16,8 +16,9 @@ package io.jmix.flowui.facet.impl; +import com.vaadin.flow.component.Composite; import io.jmix.flowui.facet.Facet; -import io.jmix.flowui.view.View; +import io.jmix.flowui.facet.FacetOwner; import org.springframework.lang.Nullable; /** @@ -27,7 +28,7 @@ public abstract class AbstractFacet implements Facet { protected String id; - protected View owner; + protected Composite owner; @Nullable @Override @@ -42,12 +43,13 @@ public void setId(@Nullable String id) { @Nullable @Override - public View getOwner() { - return owner; + public & FacetOwner> T getOwner() { + //noinspection unchecked + return (T) owner; } @Override - public void setOwner(@Nullable View owner) { + public & FacetOwner> void setOwner(@Nullable T owner) { this.owner = owner; } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractFacetComponentsHolder.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractFacetComponentsHolder.java new file mode 100644 index 0000000000..c424939b7d --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractFacetComponentsHolder.java @@ -0,0 +1,84 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.impl; + +import com.vaadin.flow.component.Composite; +import io.jmix.flowui.component.HasFacetsComponents; +import io.jmix.flowui.facet.Facet; +import io.jmix.flowui.facet.FacetOwner; +import org.springframework.lang.Nullable; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import static io.jmix.core.common.util.Preconditions.checkNotNullArgument; + +/** + * Abstract implementation of {@link HasFacetsComponents}. + */ +public abstract class AbstractFacetComponentsHolder implements HasFacetsComponents { + + protected Set facets = null; // lazily initialized linked hash set + + @Override + public void addFacet(Facet facet) { + checkNotNullArgument(facet); + + if (facets == null) { + facets = new HashSet<>(); + } + + if (!facets.contains(facet)) { + facets.add(facet); + facet.setOwner(getOwner()); + } + } + + @Nullable + @Override + public Facet getFacet(String id) { + checkNotNullArgument(id); + + if (facets == null) { + return null; + } + + return facets.stream() + .filter(facet -> id.equals(facet.getId())) + .findAny() + .orElse(null); + } + + @Override + public void removeFacet(Facet facet) { + checkNotNullArgument(facet); + + if (facets != null && facets.remove(facet)) { + facet.setOwner(null); + } + } + + @Override + public Stream getFacets() { + return facets == null + ? Stream.empty() + : facets.stream(); + } + + protected abstract & FacetOwner> T getOwner(); +} From 9b9a650bf62e16050a2c30154ee1db4203c53057 Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Thu, 4 Dec 2025 13:20:57 +0400 Subject: [PATCH 03/28] Timer facet refactoring jmix-framework/jmix_4185 --- .../io/jmix/flowui/facet/impl/TimerImpl.java | 25 ++++++++++--------- .../flowui/xml/facet/TimerFacetProvider.java | 4 +-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/TimerImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/TimerImpl.java index 81f1125343..bc73c5e218 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/TimerImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/TimerImpl.java @@ -17,14 +17,15 @@ package io.jmix.flowui.facet.impl; import com.vaadin.flow.component.ComponentEventListener; +import com.vaadin.flow.component.Composite; import com.vaadin.flow.shared.Registration; +import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.facet.Timer; import io.jmix.flowui.kit.component.timer.JmixTimer; -import io.jmix.flowui.view.View; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.lang.Nullable; + import java.util.function.Consumer; /** @@ -105,10 +106,10 @@ public void setId(@Nullable String id) { } @Override - public void setOwner(@Nullable View owner) { + public & FacetOwner> void setOwner(@Nullable T owner) { if (owner != null) { super.setOwner(owner); - registerInView(owner); + registerInOwner(owner); } else { if (this.owner != null) { unregisterInView(this.owner); @@ -117,7 +118,7 @@ public void setOwner(@Nullable View owner) { } } - protected void registerInView(View owner) { + protected void registerInOwner(Composite owner) { if (owner.isAttached()) { attachTimer(owner); } else { @@ -126,28 +127,28 @@ protected void registerInView(View owner) { addDetachListener(owner); } - protected void attachTimer(View owner) { + protected void attachTimer(Composite owner) { owner.getContent().getElement().appendChild(timerImpl.getElement()); } - protected void registerOnAttach(View owner) { + protected void registerOnAttach(Composite owner) { owner.addAttachListener(e -> attachTimer(owner)); } - protected void addDetachListener(View owner) { + protected void addDetachListener(Composite owner) { owner.addDetachListener(e -> detachTimer(owner)); } - protected void detachTimer(View owner) { + protected void detachTimer(Composite owner) { owner.getContent().getElement().removeChild(timerImpl.getElement()); - // If view is annotated with @PreserveOnRefresh and user refreshes a page, - // Vaadin creates new UI and move the same server side objects there. + // If an owner is annotated with @PreserveOnRefresh and a user refreshes a page, + // Vaadin creates a new UI and moves the same server side objects there. // But Timer isn't a server side component, so we need to remove it from the node tree manually timerImpl.getElement().removeFromTree(); } - protected void unregisterInView(View owner) { + protected void unregisterInView(Composite owner) { detachTimer(owner); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/TimerFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/TimerFacetProvider.java index f1c262dfd4..8b1e9996a0 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/TimerFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/TimerFacetProvider.java @@ -54,11 +54,11 @@ public String getFacetTag() { } @Override - public void loadFromXml(Timer timer, Element element, ComponentLoader.ComponentContext context) { + public void loadFromXml(Timer timer, Element element, ComponentLoader.Context context) { loadTimer(timer, element, context); } - protected void loadTimer(Timer timer, Element element, ComponentLoader.ComponentContext context) { + protected void loadTimer(Timer timer, Element element, ComponentLoader.Context context) { String id = loaderSupport.loadString(element, "id") .orElseThrow(() -> new IllegalStateException("Timer id must be defined")); timer.setId(id); From 297a3707465ec28e1187b3c58151acace3a5201b Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Fri, 12 Dec 2025 10:47:22 +0400 Subject: [PATCH 04/28] DynAttr facet refactoring jmix-framework/jmix_4185 --- .../DynAttrEmbeddingStrategies.java | 2 +- .../dynattrflowui/facet/DynAttrFacetImpl.java | 19 ++++++++++++------- .../facet/DynAttrFacetProvider.java | 17 +++++++++-------- .../impl/BaseEmbeddingStrategy.java | 16 +++++++--------- .../impl/DataGridEmbeddingStrategy.java | 4 +--- .../dynattrflowui/impl/EmbeddingStrategy.java | 3 +-- .../impl/FormEmbeddingStrategy.java | 3 +-- .../xml/facet/loader/DynAttrFacetLoader.java | 12 ++++++------ 8 files changed, 38 insertions(+), 38 deletions(-) diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/DynAttrEmbeddingStrategies.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/DynAttrEmbeddingStrategies.java index 9c2de7657c..c4419ce75a 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/DynAttrEmbeddingStrategies.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/DynAttrEmbeddingStrategies.java @@ -35,7 +35,7 @@ public DynAttrEmbeddingStrategies(@Autowired(required = false) List owner) { + public void embedAttributes(Component component, Component owner) { if (embeddingStrategies != null) { for (EmbeddingStrategy strategy : embeddingStrategies) { if (strategy.supportComponent(component)) { diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetImpl.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetImpl.java index 1ecd0fd89b..22ba0a1bed 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetImpl.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetImpl.java @@ -16,11 +16,14 @@ package io.jmix.dynattrflowui.facet; +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.server.streams.DownloadHandler; import io.jmix.core.annotation.Internal; import io.jmix.dynattrflowui.impl.AttributeDefaultValues; +import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.facet.impl.AbstractFacet; import io.jmix.flowui.view.StandardDetailView; -import io.jmix.flowui.view.View; import io.jmix.flowui.view.ViewControllerUtils; import org.springframework.lang.Nullable; @@ -34,19 +37,21 @@ public DynAttrFacetImpl(AttributeDefaultValues attributeDefaultValues) { } @Override - public void setOwner(@Nullable View owner) { + public & FacetOwner> void setOwner(@Nullable T owner) { super.setOwner(owner); subscribe(); } private void subscribe() { - View view = getOwner(); - if (view == null) { - throw new IllegalStateException("DynAttrFacet is not attached to Frame"); + FacetOwner owner = getOwner(); + if (owner == null) { + throw new IllegalStateException("%s is not attached to owner" + .formatted(DynAttrFacet.class.getSimpleName())); } - if (view instanceof StandardDetailView) { - ViewControllerUtils.addInitEntityEventListener((StandardDetailView) view, e -> attributeDefaultValues.initDefaultAttributeValues(e.getEntity())); + if (owner instanceof StandardDetailView view) { + ViewControllerUtils.addInitEntityEventListener(view, + e -> attributeDefaultValues.initDefaultAttributeValues(e.getEntity())); } } } diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetProvider.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetProvider.java index 6f5f7985a1..d9540360e5 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetProvider.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetProvider.java @@ -16,12 +16,12 @@ package io.jmix.dynattrflowui.facet; +import com.vaadin.flow.component.Component; import io.jmix.core.annotation.Internal; import io.jmix.dynattrflowui.DynAttrEmbeddingStrategies; import io.jmix.dynattrflowui.impl.AttributeDefaultValues; import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.sys.registration.FacetRegistrationBuilder; -import io.jmix.flowui.view.View; import io.jmix.flowui.xml.facet.FacetProvider; import io.jmix.flowui.xml.layout.ComponentLoader; import org.dom4j.Element; @@ -37,7 +37,8 @@ public class DynAttrFacetProvider implements FacetProvider { protected final DynAttrEmbeddingStrategies embeddingStrategies; protected final AttributeDefaultValues attributeDefaultValues; - public DynAttrFacetProvider(DynAttrEmbeddingStrategies embeddingStrategies, AttributeDefaultValues attributeDefaultValues) { + public DynAttrFacetProvider(DynAttrEmbeddingStrategies embeddingStrategies, + AttributeDefaultValues attributeDefaultValues) { this.embeddingStrategies = embeddingStrategies; this.attributeDefaultValues = attributeDefaultValues; } @@ -58,11 +59,11 @@ public String getFacetTag() { } @Override - public void loadFromXml(DynAttrFacet facet, Element element, ComponentLoader.ComponentContext context) { - View view = context.getView(); - context.addInitTask(__ -> { - UiComponentUtils.traverseComponents(view, component -> - embeddingStrategies.embedAttributes(component, view)); - }); + public void loadFromXml(DynAttrFacet facet, Element element, ComponentLoader.Context context) { + Component owner = context.getOrigin(); + // TODO: kd, check to getting all components in case of fragment + context.addInitTask(__ -> + UiComponentUtils.traverseComponents(owner, component -> + embeddingStrategies.embedAttributes(component, owner))); } } diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/BaseEmbeddingStrategy.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/BaseEmbeddingStrategy.java index d95f244148..f53a1fd8f5 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/BaseEmbeddingStrategy.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/BaseEmbeddingStrategy.java @@ -25,8 +25,6 @@ import io.jmix.flowui.accesscontext.UiEntityAttributeContext; import io.jmix.flowui.accesscontext.UiEntityContext; import io.jmix.flowui.model.*; -import io.jmix.flowui.view.View; -import org.springframework.util.Assert; import java.util.Comparator; import java.util.List; @@ -53,30 +51,30 @@ protected BaseEmbeddingStrategy(Metadata metadata, protected abstract void setLoadDynamicAttributes(Component component); - protected abstract void embed(Component component, View owner, List attributes); + protected abstract void embed(Component component, List attributes); @Override - public void embed(Component component, View owner) { - if (getWindowId(owner) != null) { + public void embed(Component component, Component owner) { + if (getOwnerId(owner) != null) { MetaClass entityMetaClass = getEntityMetaClass(component); if (metadataTools.isJpaEntity(entityMetaClass)) { List attributes = findVisibleAttributes( entityMetaClass, - getWindowId(owner), component.getId().orElse("")); + getOwnerId(owner), component.getId().orElse("")); if (!attributes.isEmpty()) { setLoadDynamicAttributes(component); } - embed(component, owner, attributes); + embed(component, attributes); } } } - protected String getWindowId(View view) { - return view.getId().orElseThrow(); + protected String getOwnerId(Component owner) { + return owner.getId().orElseThrow(); } protected void setLoadDynamicAttributes(InstanceContainer container) { diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/DataGridEmbeddingStrategy.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/DataGridEmbeddingStrategy.java index 3baa371faa..d9b88fd84c 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/DataGridEmbeddingStrategy.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/DataGridEmbeddingStrategy.java @@ -17,7 +17,6 @@ package io.jmix.dynattrflowui.impl; import com.vaadin.flow.component.Component; -import com.vaadin.flow.data.renderer.Renderer; import io.jmix.core.AccessManager; import io.jmix.core.DataManager; import io.jmix.core.Metadata; @@ -32,7 +31,6 @@ import io.jmix.flowui.component.grid.DataGrid; import io.jmix.flowui.data.EntityDataUnit; import io.jmix.flowui.data.grid.ContainerDataGridItems; -import io.jmix.flowui.view.View; import java.util.List; @@ -57,7 +55,7 @@ public boolean supportComponent(Component component) { } @Override - protected void embed(Component component, View owner, List attributes) { + protected void embed(Component component, List attributes) { DataGrid dataGrid = (DataGrid) component; for (AttributeDefinition attribute : attributes) { addAttributeColumn(dataGrid, attribute); diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/EmbeddingStrategy.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/EmbeddingStrategy.java index 2429808b1a..d5dd4ffc08 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/EmbeddingStrategy.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/EmbeddingStrategy.java @@ -17,11 +17,10 @@ package io.jmix.dynattrflowui.impl; import com.vaadin.flow.component.Component; -import io.jmix.flowui.view.View; public interface EmbeddingStrategy { boolean supportComponent(Component component); - void embed(Component component, View owner); + void embed(Component component, Component owner); } diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/FormEmbeddingStrategy.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/FormEmbeddingStrategy.java index 72c6e6ac12..00c8f984ae 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/FormEmbeddingStrategy.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/impl/FormEmbeddingStrategy.java @@ -34,7 +34,6 @@ import io.jmix.flowui.data.ValueSource; import io.jmix.flowui.data.ValueSourceProvider; import io.jmix.flowui.data.value.ContainerValueSourceProvider; -import io.jmix.flowui.view.View; import org.springframework.util.Assert; import java.util.List; @@ -65,7 +64,7 @@ public boolean supportComponent(Component component) { } @Override - protected void embed(Component component, View view, List attributes) { + protected void embed(Component component, List attributes) { FormLayout form = (FormLayout) component; for (AttributeDefinition attribute : attributes) { addAttributeComponent((JmixFormLayout) form, attribute); diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/xml/facet/loader/DynAttrFacetLoader.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/xml/facet/loader/DynAttrFacetLoader.java index 8f96d2197d..86a2770166 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/xml/facet/loader/DynAttrFacetLoader.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/xml/facet/loader/DynAttrFacetLoader.java @@ -16,11 +16,11 @@ package io.jmix.dynattrflowui.xml.facet.loader; +import com.vaadin.flow.component.Component; import io.jmix.dynattrflowui.DynAttrEmbeddingStrategies; import io.jmix.dynattrflowui.facet.DynAttrFacet; import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.impl.FacetsImpl; -import io.jmix.flowui.view.View; import io.jmix.flowui.xml.facet.FacetProvider; import io.jmix.flowui.xml.facet.loader.AbstractFacetLoader; @@ -45,11 +45,11 @@ public void loadFacet() { } } - View view = context.getView(); - context.addInitTask(__ -> { - UiComponentUtils.traverseComponents(view, component -> - getEmbeddingStrategies().embedAttributes(component, view)); - }); + // TODO: kd, check to getting all components in case of fragment + Component owner = context.getOrigin(); + context.addInitTask(__ -> + UiComponentUtils.traverseComponents(owner, component -> + getEmbeddingStrategies().embedAttributes(component, owner))); } protected DynAttrEmbeddingStrategies getEmbeddingStrategies() { From 6473eceb0c7c772db5bb233c299febe23c23d823 Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Mon, 15 Dec 2025 13:02:59 +0400 Subject: [PATCH 05/28] SettingsFacet Part 1 --- .../test_support/AbstractSettingsTest.java | 4 +- .../flowui/facet/impl/SettingsFacetImpl.java | 9 +- .../settings/AbstractUiComponentSettings.java | 49 +++ .../AbstractUiComponentSettingsJson.java | 360 ++++++++++++++++++ .../facet/settings/AbstractViewSettings.java | 3 + .../settings/ComponentSettingsManager.java | 62 +++ ...java => ComponentSettingsManagerImpl.java} | 42 +- .../settings/ComponentSettingsRegistry.java | 47 +++ ...ava => ComponentSettingsRegistryImpl.java} | 14 +- .../facet/settings/FragmentSettings.java | 30 ++ .../facet/settings/FragmentSettingsJson.java | 28 ++ .../facet/settings/UiComponentSettings.java | 182 +++++++++ .../flowui/facet/settings/ViewSettings.java | 169 +------- .../ViewSettingsComponentManager.java | 20 +- .../ViewSettingsComponentRegistry.java | 29 +- .../facet/settings/ViewSettingsJson.java | 325 +--------------- .../xml/facet/SettingsFacetProvider.java | 10 +- 17 files changed, 842 insertions(+), 541 deletions(-) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractUiComponentSettings.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractUiComponentSettingsJson.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsManager.java rename jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/{ViewSettingsComponentManagerImpl.java => ComponentSettingsManagerImpl.java} (71%) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsRegistry.java rename jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/{ViewSettingsComponentRegistryImpl.java => ComponentSettingsRegistryImpl.java} (83%) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/FragmentSettings.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/FragmentSettingsJson.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/UiComponentSettings.java diff --git a/jmix-flowui/flowui-data/src/test/java/test_support/AbstractSettingsTest.java b/jmix-flowui/flowui-data/src/test/java/test_support/AbstractSettingsTest.java index 28fd0a198e..c43c341fd8 100644 --- a/jmix-flowui/flowui-data/src/test/java/test_support/AbstractSettingsTest.java +++ b/jmix-flowui/flowui-data/src/test/java/test_support/AbstractSettingsTest.java @@ -17,8 +17,8 @@ package test_support; import io.jmix.core.security.SystemAuthenticator; +import io.jmix.flowui.facet.settings.ComponentSettingsManager; import io.jmix.flowui.facet.settings.ViewSettings; -import io.jmix.flowui.facet.settings.ViewSettingsComponentManager; import io.jmix.flowui.facet.settings.ViewSettingsJson; import io.jmix.flowui.settings.UserSettingsService; import io.jmix.flowui.testassist.UiTestUtils; @@ -37,7 +37,7 @@ public abstract class AbstractSettingsTest { @Autowired protected SystemAuthenticator authenticator; @Autowired - protected ViewSettingsComponentManager settingsManager; + protected ComponentSettingsManager settingsManager; @Autowired protected ViewNavigationSupport navigationSupport; diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetImpl.java index 2f37f2c11e..15c5f77766 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetImpl.java @@ -25,10 +25,7 @@ import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.facet.SettingsFacet; import io.jmix.flowui.facet.UrlQueryParametersFacet; -import io.jmix.flowui.facet.settings.SettingsFacetUrlQueryParametersHelper; -import io.jmix.flowui.facet.settings.ViewSettings; -import io.jmix.flowui.facet.settings.ViewSettingsComponentManager; -import io.jmix.flowui.facet.settings.ViewSettingsJson; +import io.jmix.flowui.facet.settings.*; import io.jmix.flowui.settings.UserSettingsCache; import io.jmix.flowui.settings.UserSettingsService; import io.jmix.flowui.sys.autowire.ReflectionCacheManager; @@ -55,7 +52,7 @@ public class SettingsFacetImpl extends AbstractFacet implements SettingsFacet { protected SettingsFacetUrlQueryParametersHelper settingsHelper; protected ReflectionCacheManager reflectionCacheManager; - protected ViewSettingsComponentManager settingsManager; + protected ComponentSettingsManager settingsManager; protected UserSettingsCache userSettingsCache; @Nullable private final UserSettingsService userSettingsService; @@ -80,7 +77,7 @@ public class SettingsFacetImpl extends AbstractFacet implements SettingsFacet { public SettingsFacetImpl(SettingsFacetUrlQueryParametersHelper settingsHelper, ReflectionCacheManager reflectionCacheManager, UserSettingsCache userSettingsCache, - ViewSettingsComponentManager settingsManager, + ComponentSettingsManager settingsManager, @Nullable UserSettingsService userSettingsService) { this.settingsHelper = settingsHelper; this.reflectionCacheManager = reflectionCacheManager; diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractUiComponentSettings.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractUiComponentSettings.java new file mode 100644 index 0000000000..5d18527e88 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractUiComponentSettings.java @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.settings; + +import com.vaadin.flow.component.Component; + +/** + * Abstract base class that partially implements the {@link UiComponentSettings} interface + * and provides common functionality related to managing settings for a specific {@link Component}. + */ +public abstract class AbstractUiComponentSettings> + implements UiComponentSettings { + + protected String ownerId; + protected boolean modified = false; + + protected AbstractUiComponentSettings(String ownerId) { + this.ownerId = ownerId; + } + + @Override + public String getOwnerId() { + return ownerId; + } + + @Override + public void setModified(boolean modified) { + this.modified = modified; + } + + @Override + public boolean isModified() { + return modified; + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractUiComponentSettingsJson.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractUiComponentSettingsJson.java new file mode 100644 index 0000000000..3a1646ae16 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractUiComponentSettingsJson.java @@ -0,0 +1,360 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.settings; + +import com.google.common.base.Strings; +import com.google.gson.*; +import io.jmix.core.annotation.Internal; +import io.jmix.core.common.util.Preconditions; +import org.springframework.lang.Nullable; + +import java.util.Optional; + +/** + * Abstract base class for {@link UiComponentSettings} implementations that use JSON structure. + * + * @param type of settings + * @param type of settings JSON + */ +@Internal +public abstract class AbstractUiComponentSettingsJson< + S extends UiComponentSettings, + J extends AbstractUiComponentSettingsJson + > + extends AbstractUiComponentSettings { + + protected JsonArray root; + protected Gson gson; + + protected AbstractUiComponentSettingsJson(String ownerId) { + super(ownerId); + + initGson(); + } + + protected void initGson() { + gson = new GsonBuilder() + .create(); + } + + @Override + public S put(String id, String key, @Nullable String value) { + checkNotNullPutConditions(id, key); + + JsonObject object = getObjectOrCreate(id); + + object.addProperty(key, value); + + put(object, id); + + //noinspection unchecked + return (S) this; + } + + @Override + public S put(String id, String key, @Nullable Integer value) { + checkNotNullPutConditions(id, key); + + JsonObject object = getObjectOrCreate(id); + + object.addProperty(key, value); + + put(object, id); + + //noinspection unchecked + return (S) this; + } + + @Override + public S put(String id, String key, @Nullable Long value) { + checkNotNullPutConditions(id, key); + + JsonObject object = getObjectOrCreate(id); + + object.addProperty(key, value); + + put(object, id); + + //noinspection unchecked + return (S) this; + } + + @Override + public S put(String id, String key, @Nullable Double value) { + checkNotNullPutConditions(id, key); + + JsonObject object = getObjectOrCreate(id); + + object.addProperty(key, value); + + put(object, id); + + //noinspection unchecked + return (S) this; + } + + @Override + public S put(String id, String key, @Nullable Boolean value) { + checkNotNullPutConditions(id, key); + + JsonObject object = getObjectOrCreate(id); + + object.addProperty(key, value); + + put(object, id); + + //noinspection unchecked + return (S) this; + } + + @Override + public S put(Settings settings) { + Preconditions.checkNotNullArgument(settings); + + if (settings.getId() == null) { + throw new IllegalArgumentException("Cannot put settings with null id"); + } + + put(gson.toJsonTree(settings), settings.getId()); + + //noinspection unchecked + return (S) this; + } + + /** + * @param json JSON object that represents settings + * @return current instance of {@link AbstractUiComponentSettingsJson} + */ + public J put(JsonObject json) { + Preconditions.checkNotNullArgument(json); + + if (isValueNull(json, "id")) { + throw new IllegalArgumentException("Cannot put settings, json must have not null id"); + } + + String id = json.getAsJsonPrimitive("id").getAsString(); + put(json, id); + + //noinspection unchecked + return (J) this; + } + + @Override + public S delete(String id) { + Preconditions.checkNotNullArgument(id); + + JsonObject component = getObject(id); + + if (component != null) { + root.remove(component); + + setModified(true); + } + + //noinspection unchecked + return (S) this; + } + + @Override + public S delete(String id, String key) { + Preconditions.checkNotNullArgument(id); + Preconditions.checkNotNullArgument(key); + + JsonObject component = getObject(id); + + if (component != null) { + component.remove(key); + + setModified(true); + } + + //noinspection unchecked + return (S) this; + } + + /** + * @param id e.g. component id + * @return JSON object that represents settings + */ + public Optional getJsonSettings(String id) { + Preconditions.checkNotNullArgument(id); + + return Optional.ofNullable(getObject(id)); + } + + @Override + public Optional getString(String id, String key) { + checkNotNullGetConditions(id, key); + + JsonObject object = getObject(id); + + if (object == null || isValueNull(object, key)) + return Optional.empty(); + + return object.keySet().contains(key) ? + Optional.of(object.getAsJsonPrimitive(key).getAsString()) : Optional.empty(); + } + + @Override + public Optional getInteger(String id, String key) { + checkNotNullGetConditions(id, key); + + JsonObject object = getObject(id); + + if (object == null || isValueNull(object, key)) + return Optional.empty(); + + return object.keySet().contains(key) ? + Optional.of(object.getAsJsonPrimitive(key).getAsInt()) : Optional.empty(); + } + + @Override + public Optional getLong(String id, String key) { + checkNotNullGetConditions(id, key); + + JsonObject object = getObject(id); + + if (object == null || isValueNull(object, key)) + return Optional.empty(); + + return object.keySet().contains(key) ? + Optional.of(object.getAsJsonPrimitive(key).getAsLong()) : Optional.empty(); + } + + @Override + public Optional getDouble(String id, String key) { + checkNotNullGetConditions(id, key); + + JsonObject object = getObject(id); + + if (object == null || isValueNull(object, key)) + return Optional.empty(); + + return object.keySet().contains(key) ? + Optional.of(object.getAsJsonPrimitive(key).getAsDouble()) : Optional.empty(); + } + + @Override + public Optional getBoolean(String id, String key) { + checkNotNullGetConditions(id, key); + + JsonObject object = getObject(id); + + if (object == null || isValueNull(object, key)) + return Optional.empty(); + + return object.keySet().contains(key) ? + Optional.of(object.getAsJsonPrimitive(key).getAsBoolean()) : Optional.empty(); + } + + @Override + public Optional getSettings(String id, Class settingsClass) { + Preconditions.checkNotNullArgument(id); + Preconditions.checkNotNullArgument(settingsClass); + + JsonObject json = getObject(id); + if (json == null) + return Optional.empty(); + + return Optional.ofNullable(gson.fromJson(json, settingsClass)); + } + + @Override + public T getSettingsOrCreate(String id, Class settingsClass) { + Preconditions.checkNotNullArgument(id); + Preconditions.checkNotNullArgument(settingsClass); + + return getSettings(id, settingsClass).orElseGet(() -> { + JsonObject json = new JsonObject(); + json.addProperty("id", id); + return settingsClass.cast(gson.fromJson(json, settingsClass)); + }); + } + + @Override + public void initialize(@Nullable String raw) { + root = Strings.isNullOrEmpty(raw) + ? new JsonArray() + : gson.fromJson(raw, JsonArray.class); + } + + @Override + public String serialize() { + return gson.toJson(root); + } + + protected void put(JsonElement json, String id) { + initRoot(); + + delete(id); + + root.add(json); + + setModified(true); + } + + protected JsonObject getObjectOrCreate(String id) { + JsonObject object = getObject(id); + if (object == null) { + object = new JsonObject(); + object.addProperty("id", id); + } + + return object; + } + + @Nullable + protected JsonObject getObject(String objectId) { + initRoot(); + + for (JsonElement jsonElement : root) { + JsonObject object = (JsonObject) jsonElement; + + boolean keyExist = object.keySet().contains("id"); + if (keyExist) { + String id = object.getAsJsonPrimitive("id").getAsString(); + if (id.equals(objectId)) { + return object; + } + } + } + return null; + } + + protected void checkNotNullPutConditions(String id, String key) { + Preconditions.checkNotNullArgument(id, "Cannot put settings when object id is null"); + Preconditions.checkNotNullArgument(key, "Cannot put settings when key is null"); + } + + protected void checkNotNullGetConditions(String id, String property) { + Preconditions.checkNotNullArgument(id, "Cannot get settings when object id is null"); + Preconditions.checkNotNullArgument(property, "Cannot get settings when key is null"); + } + + protected void initRoot() { + if (root == null) { + root = new JsonArray(); + } + } + + protected boolean isValueNull(JsonObject json, String key) { + if (json.keySet().contains(key)) + return json.get(key).isJsonNull(); + + return true; + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractViewSettings.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractViewSettings.java index cb91f3aa64..4e4114a8ef 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractViewSettings.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/AbstractViewSettings.java @@ -21,7 +21,10 @@ /** * Abstract base class that partially implements the {@link ViewSettings} interface * and provides common functionality related to managing settings for a specific {@link View}. + * + * @deprecated use {@link AbstractUiComponentSettings} instead */ +@Deprecated(since = "3.0", forRemoval = true) public abstract class AbstractViewSettings implements ViewSettings { protected String viewId; diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsManager.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsManager.java new file mode 100644 index 0000000000..0f84e134ed --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsManager.java @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.settings; + +import com.vaadin.flow.component.Component; +import io.jmix.flowui.facet.settings.component.binder.ComponentSettingsBinder; +import io.jmix.flowui.facet.settings.component.binder.DataGridSettingsBinder; + +import java.util.Collection; + +/** + * Saves and restores settings for components. + */ +public interface ComponentSettingsManager { + + /** + * Applies settings from {@link UiComponentSettings} to provided components. + *

+ * Note, component should have and id and {@link ComponentSettingsBinder}. Otherwise, it will be skipped + * while applying settings. See {@link DataGridSettingsBinder} as an example. + * + * @param components components to apply settings + * @param settings {@link UiComponentSettings} + */ + void applySettings(Collection components, UiComponentSettings settings); + + /** + * Applies data loading settings from {@link UiComponentSettings} to provided components. + *

+ * Note, component should have and id and {@link ComponentSettingsBinder}. Otherwise, it will be skipped + * while applying settings. See {@link DataGridSettingsBinder} as an example. + * + * @param components components to apply settings + * @param settings {@link UiComponentSettings} + */ + void applyDataLoadingSettings(Collection components, UiComponentSettings settings); + + /** + * Persists settings if they are changed or {@link UiComponentSettings#isModified()} returns {@code true}. + *

+ * Note, component should have and id and {@link ComponentSettingsBinder}. Otherwise, it will be skipped + * while saving settings. See {@link DataGridSettingsBinder} as an example. + * + * @param components components to save settings + * @param settings {@link UiComponentSettings} + */ + void saveSettings(Collection components, UiComponentSettings settings); +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentManagerImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsManagerImpl.java similarity index 71% rename from jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentManagerImpl.java rename to jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsManagerImpl.java index d1c8f8ae03..d61eed7099 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentManagerImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Haulmont. + * Copyright 2025 Haulmont. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,29 +29,29 @@ import java.util.Collection; /** - * Implementation of {@link ViewSettingsComponentManager} that applies, saves, and manages settings for components - * using {@link ViewSettingsComponentRegistry} and {@link UserSettingsCache}. + * Implementation of {@link ComponentSettingsManager} that applies, saves, and manages settings for components + * using {@link ComponentSettingsRegistry} and {@link UserSettingsCache}. */ @Internal -@org.springframework.stereotype.Component("flowui_ViewSettingsComponentManagerImpl") -public class ViewSettingsComponentManagerImpl implements ViewSettingsComponentManager { +@org.springframework.stereotype.Component("flowui_ComponentSettingsManagerImpl") +public class ComponentSettingsManagerImpl implements ComponentSettingsManager { - private static final Logger log = LoggerFactory.getLogger(ViewSettingsComponentManagerImpl.class); + private static final Logger log = LoggerFactory.getLogger(ComponentSettingsManagerImpl.class); - protected ViewSettingsComponentRegistry settingsRegistry; + protected ComponentSettingsRegistry settingsRegistry; protected UserSettingsCache userSettingsCache; - public ViewSettingsComponentManagerImpl(ViewSettingsComponentRegistry settingsRegistry, - UserSettingsCache userSettingsCache) { + public ComponentSettingsManagerImpl(ComponentSettingsRegistry settingsRegistry, + UserSettingsCache userSettingsCache) { this.settingsRegistry = settingsRegistry; this.userSettingsCache = userSettingsCache; } @SuppressWarnings("unchecked") @Override - public void applySettings(Collection components, ViewSettings viewSettings) { + public void applySettings(Collection components, UiComponentSettings componentSettings) { Preconditions.checkNotNullArgument(components); - Preconditions.checkNotNullArgument(viewSettings); + Preconditions.checkNotNullArgument(componentSettings); for (Component component : components) { if (Strings.isNullOrEmpty(component.getId().orElse(null)) @@ -65,7 +65,7 @@ public void applySettings(Collection components, ViewSettings viewSet settingsRegistry.getSettingsBinder(component.getClass()); Class settingsClass = settingsRegistry.getSettingsClass(component.getClass()); - Settings settings = viewSettings.getSettingsOrCreate(component.getId().get(), settingsClass); + Settings settings = componentSettings.getSettingsOrCreate(component.getId().get(), settingsClass); binder.applySettings(component, settings.as()); } @@ -73,9 +73,9 @@ public void applySettings(Collection components, ViewSettings viewSet @SuppressWarnings("unchecked") @Override - public void applyDataLoadingSettings(Collection components, ViewSettings viewSettings) { + public void applyDataLoadingSettings(Collection components, UiComponentSettings componentSettings) { Preconditions.checkNotNullArgument(components); - Preconditions.checkNotNullArgument(viewSettings); + Preconditions.checkNotNullArgument(componentSettings); for (Component component : components) { if (!settingsRegistry.isSettingsRegisteredFor(component.getClass()) @@ -91,7 +91,7 @@ public void applyDataLoadingSettings(Collection components, ViewSetti settingsRegistry.getSettingsBinder(component.getClass()); if (binder instanceof DataLoadingSettingsBinder) { - Settings settings = viewSettings.getSettingsOrCreate(component.getId().get(), settingsClass); + Settings settings = componentSettings.getSettingsOrCreate(component.getId().get(), settingsClass); ((DataLoadingSettingsBinder) binder).applyDataLoadingSettings(component, settings.as()); } } @@ -99,9 +99,9 @@ public void applyDataLoadingSettings(Collection components, ViewSetti @SuppressWarnings("unchecked") @Override - public void saveSettings(Collection components, ViewSettings viewSettings) { + public void saveSettings(Collection components, UiComponentSettings componentSettings) { Preconditions.checkNotNullArgument(components); - Preconditions.checkNotNullArgument(viewSettings); + Preconditions.checkNotNullArgument(componentSettings); boolean isModified = false; @@ -115,7 +115,7 @@ public void saveSettings(Collection components, ViewSettings viewSett Class settingsClass = settingsRegistry.getSettingsClass(component.getClass()); - Settings settings = viewSettings.getSettingsOrCreate(component.getId().get(), settingsClass); + Settings settings = componentSettings.getSettingsOrCreate(component.getId().get(), settingsClass); ComponentSettingsBinder binder = (ComponentSettingsBinder) settingsRegistry.getSettingsBinder(component.getClass()); @@ -124,12 +124,12 @@ public void saveSettings(Collection components, ViewSettings viewSett if (settingsChanged) { isModified = true; - viewSettings.put(settings); + componentSettings.put(settings); } } - if (isModified || viewSettings.isModified()) { - userSettingsCache.put(viewSettings.getViewId(), viewSettings.serialize()); + if (isModified || componentSettings.isModified()) { + userSettingsCache.put(componentSettings.getOwnerId(), componentSettings.serialize()); } } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsRegistry.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsRegistry.java new file mode 100644 index 0000000000..80c13f405c --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsRegistry.java @@ -0,0 +1,47 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.settings; + +import com.vaadin.flow.component.Component; +import io.jmix.flowui.component.details.JmixDetails; +import io.jmix.flowui.facet.settings.component.binder.ComponentSettingsBinder; + +/** + * Provides information for which component registered settings class. + */ +public interface ComponentSettingsRegistry { + + /** + * @param componentClass component class (e.g. {@link JmixDetails}) + * @return component settings class + * @throws IllegalStateException if there is no component settings class registered for the component class + */ + Class getSettingsClass(Class componentClass); + + /** + * @param componentClass component class (e.g. {@link JmixDetails}) + * @return settings binder + * @throws IllegalStateException if there is no component settings binder registered for the component class + */ + ComponentSettingsBinder getSettingsBinder(Class componentClass); + + /** + * @param componentClass component class (e.g. {@link JmixDetails}) + * @return {@code true} if settings are registered for the component class + */ + boolean isSettingsRegisteredFor(Class componentClass); +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentRegistryImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsRegistryImpl.java similarity index 83% rename from jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentRegistryImpl.java rename to jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsRegistryImpl.java index a024e6df5c..87db3d8b8e 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentRegistryImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ComponentSettingsRegistryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Haulmont. + * Copyright 2025 Haulmont. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,17 +32,17 @@ * Collects {@link ComponentSettingsBinder} and provides information for which component registered settings class. */ @Internal -@org.springframework.stereotype.Component("flowui_ViewSettingsComponentRegistryImpl") -public class ViewSettingsComponentRegistryImpl implements ViewSettingsComponentRegistry, InitializingBean { +@org.springframework.stereotype.Component("flowui_ComponentSettingsRegistryImpl") +public class ComponentSettingsRegistryImpl implements ComponentSettingsRegistry, InitializingBean { - private static final Logger log = LoggerFactory.getLogger(ViewSettingsComponentRegistryImpl.class); + private static final Logger log = LoggerFactory.getLogger(ComponentSettingsRegistryImpl.class); protected List> binders; protected Map, ComponentSettingsBinder> componentBinders = new ConcurrentHashMap<>(); - public ViewSettingsComponentRegistryImpl(List> binders) { + public ComponentSettingsRegistryImpl(List> binders) { this.binders = binders; } @@ -62,7 +62,7 @@ public Class getSettingsClass(Class com return binder.getSettingsClass(); } - throw new IllegalStateException(String.format("Can't find settings class for '%s'", componentClass)); + throw new IllegalStateException("Can't find settings class for '%s'".formatted(componentClass)); } @Override @@ -74,7 +74,7 @@ public Class getSettingsClass(Class com return binder; } - throw new IllegalStateException(String.format("Cannot find settings binder for '%s'", componentClass)); + throw new IllegalStateException("Cannot find settings binder for '%s'".formatted(componentClass)); } @Override diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/FragmentSettings.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/FragmentSettings.java new file mode 100644 index 0000000000..6879319462 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/FragmentSettings.java @@ -0,0 +1,30 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.settings; + +import io.jmix.flowui.facet.SettingsFacet; +import io.jmix.flowui.fragment.Fragment; + +/** + * Base interface for classes that collect component settings from {@link Fragment}. It provides an API for + * putting, getting, and removing settings.ø + * + * @see SettingsFacet + * @see UiComponentSettings + */ +public interface FragmentSettings extends UiComponentSettings { +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/FragmentSettingsJson.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/FragmentSettingsJson.java new file mode 100644 index 0000000000..7ef6ef07cf --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/FragmentSettingsJson.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.settings; + +/** + * {@link FragmentSettings} that uses JSON structure. + */ +public class FragmentSettingsJson extends AbstractUiComponentSettingsJson + implements FragmentSettings { + + public FragmentSettingsJson(String ownerId) { + super(ownerId); + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/UiComponentSettings.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/UiComponentSettings.java new file mode 100644 index 0000000000..abb5548633 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/UiComponentSettings.java @@ -0,0 +1,182 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.settings; + +import com.vaadin.flow.component.Component; +import io.jmix.flowui.facet.settings.component.JmixDetailsSettings; +import org.springframework.lang.Nullable; + +import java.util.Optional; + +public interface UiComponentSettings> { + + /** + * @return a {@link Component} id to which settings are corresponded + */ + String getOwnerId(); + + /** + * Set to {@code true} if screen settings changed manually. It guarantees that settings will be persisted. + * + * @param modified whether settings were modified + */ + void setModified(boolean modified); + + /** + * @return {@code true} if settings were modified + */ + boolean isModified(); + + /** + * Puts a value with {@link String} type. Will replace value if the same key already exists. + * + * @param id e.g. component id + * @param key key with which associated provided value, e.g., component's width or some state + * @param value {@link String} value + * @return current instance of {@link UiComponentSettings} + */ + S put(String id, String key, @Nullable String value); + + /** + * Puts a value with {@link Integer} type. Will replace value if the same key already exists. + * + * @param id e.g. component id + * @param key key with which associated provided value, e.g., component's property or some state + * @param value {@link Integer} value + * @return current instance of {@link UiComponentSettings} + */ + S put(String id, String key, @Nullable Integer value); + + /** + * Puts a value with {@link Long} type. Will replace value if the same key already exists. + * + * @param id e.g. component id + * @param key key with which associated provided value, e.g., component's property or some state + * @param value {@link Long} value + * @return current instance of {@link UiComponentSettings} + */ + S put(String id, String key, @Nullable Long value); + + /** + * Puts a value with {@link Double} type. Will replace value if the same key already exists. + * + * @param id e.g. component id + * @param key key with which associated provided value, e.g., component's property or some state + * @param value {@link Double} value + * @return current instance of {@link UiComponentSettings} + */ + S put(String id, String key, @Nullable Double value); + + /** + * Puts a value with {@link Boolean}. Will replace value if the same key already exists. + * + * @param id e.g. component id + * @param key key with which associated provided value, e.g., component's property or some state + * @param value {@link Boolean} value + * @return current instance of {@link UiComponentSettings} + */ + S put(String id, String key, @Nullable Boolean value); + + /** + * Puts component's settings, e.g {@link JmixDetailsSettings}. If setting with provided id already exists it will be + * replaced. + * + * @param settings object of settings + * @return current instance of {@link UiComponentSettings} + */ + S put(Settings settings); + + /** + * Deletes component's settings by identifier if they exist. + * + * @param id id to remove, e.g. component id + * @return current instance of {@link UiComponentSettings} + */ + S delete(String id); + + /** + * Deletes a key of an object with a provided identifier if it exists. + * + * @param id e.g. component id + * @param key object's key to remove + * @return current instance of {@link UiComponentSettings} + */ + S delete(String id, String key); + + /** + * @param id e.g. component id + * @param key object's key + * @return {@link String} value wrapped in {@code Optional} + */ + Optional getString(String id, String key); + + /** + * @param id e.g. component id + * @param key object's key + * @return {@link Integer} value wrapped in {@code Optional} + */ + Optional getInteger(String id, String key); + + /** + * @param id e.g. component id + * @param key object's key + * @return {@link Long} value wrapped in {@code Optional} + */ + Optional getLong(String id, String key); + + /** + * @param id e.g. component id + * @param key object's key + * @return {@link Double} value wrapped in {@code Optional} + */ + Optional getDouble(String id, String key); + + /** + * @param id e.g. component id + * @param key object's key + * @return {@link Boolean} value wrapped in {@code Optional} + */ + Optional getBoolean(String id, String key); + + /** + * @param id e.g. component id + * @param settingsClass settings class + * @param type of settings class + * @return component settings wrapped in {@code Optional} + */ + Optional getSettings(String id, Class settingsClass); + + /** + * @param id e.g. component id + * @param settingsClass settings class + * @param type of settings class + * @return object of settings if exists otherwise return created settings with corresponding id + */ + T getSettingsOrCreate(String id, Class settingsClass); + + /** + * Initializes the current instance from serialized settings. + * + * @param rawSettings serialized settings + */ + void initialize(@Nullable String rawSettings); + + /** + * @return serialized settings + */ + String serialize(); +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettings.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettings.java index 4308417712..80fd642597 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettings.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettings.java @@ -17,174 +17,27 @@ package io.jmix.flowui.facet.settings; import io.jmix.flowui.facet.SettingsFacet; -import io.jmix.flowui.facet.settings.component.JmixDetailsSettings; import io.jmix.flowui.view.View; -import org.springframework.lang.Nullable; - -import java.util.Optional; /** - * Base interface for classes that collect component settings from {@link View}. It provides API for - * putting, getting, removing settings. + * Base interface for classes that collect component settings from {@link View}. It provides an API for + * putting, getting, and removing settings. * * @see SettingsFacet - * @see ViewSettingsComponentManager + * @see UiComponentSettings + * @see ComponentSettingsManager */ -public interface ViewSettings { +public interface ViewSettings extends UiComponentSettings { /** * @return a {@link View} id to which settings are corresponded + * @deprecated use {@link #getOwnerId()} instead */ + @Deprecated(since = "3.0", forRemoval = true) String getViewId(); - /** - * Set to {@code true} if screen settings changed manually. It guarantees that settings will be persisted. - * - * @param modified whether settings were modified - */ - void setModified(boolean modified); - - /** - * @return {@code true} if settings were modified - */ - boolean isModified(); - - /** - * Puts a value with {@link String} type. Will replace value if the same key already exist. - * - * @param id e.g. component id - * @param key key with which associated provided value, e.g. component's width or some state - * @param value {@link String} value - * @return current instance of {@link ViewSettings} - */ - ViewSettings put(String id, String key, @Nullable String value); - - /** - * Puts a value with {@link Integer} type. Will replace value if the same key already exist. - * - * @param id e.g. component id - * @param key key with which associated provided value, e.g. component's property or some state - * @param value {@link Integer} value - * @return current instance of {@link ViewSettings} - */ - ViewSettings put(String id, String key, @Nullable Integer value); - - /** - * Puts a value with {@link Long} type. Will replace value if the same key already exist. - * - * @param id e.g. component id - * @param key key with which associated provided value, e.g. component's property or some state - * @param value {@link Long} value - * @return current instance of {@link ViewSettings} - */ - ViewSettings put(String id, String key, @Nullable Long value); - - /** - * Puts a value with {@link Double} type. Will replace value if the same key already exist. - * - * @param id e.g. component id - * @param key key with which associated provided value, e.g. component's property or some state - * @param value {@link Double} value - * @return current instance of {@link ViewSettings} - */ - ViewSettings put(String id, String key, @Nullable Double value); - - /** - * Puts a value with {@link Boolean}. Will replace value if the same key already exist. - * - * @param id e.g. component id - * @param key key with which associated provided value, e.g. component's property or some state - * @param value {@link Boolean} value - * @return current instance of {@link ViewSettings} - */ - ViewSettings put(String id, String key, @Nullable Boolean value); - - /** - * Puts component's settings, e.g {@link JmixDetailsSettings}. If setting with provided id already exist it will be - * replaced. - * - * @param settings object of settings - * @return current instance of {@link ViewSettings} - */ - ViewSettings put(Settings settings); - - /** - * Deletes component's settings by identifier if they exist. - * - * @param id id to remove, e.g. component id - * @return current instance of {@link ViewSettings} - */ - ViewSettings delete(String id); - - /** - * Deletes a key of object with provided identifier if it exists. - * - * @param id e.g. component id - * @param key object's key to remove - * @return current instance of {@link ViewSettings} - */ - ViewSettings delete(String id, String key); - - /** - * @param id e.g. component id - * @param key object's key - * @return {@link String} value wrapped in {@code Optional} - */ - Optional getString(String id, String key); - - /** - * @param id e.g. component id - * @param key object's key - * @return {@link Integer} value wrapped in {@code Optional} - */ - Optional getInteger(String id, String key); - - /** - * @param id e.g. component id - * @param key object's key - * @return {@link Long} value wrapped in {@code Optional} - */ - Optional getLong(String id, String key); - - /** - * @param id e.g. component id - * @param key object's key - * @return {@link Double} value wrapped in {@code Optional} - */ - Optional getDouble(String id, String key); - - /** - * @param id e.g. component id - * @param key object's key - * @return {@link Boolean} value wrapped in {@code Optional} - */ - Optional getBoolean(String id, String key); - - /** - * @param id e.g. component id - * @param settingsClass settings class - * @param type of settings class - * @return component settings wrapped in {@code Optional} - */ - Optional getSettings(String id, Class settingsClass); - - /** - * @param id e.g. component id - * @param settingsClass settings class - * @param type of settings class - * @return object of settings if exists otherwise return created settings with corresponding id - */ - T getSettingsOrCreate(String id, Class settingsClass); - - /** - * Initializes current instance from serialized settings. - * - * @param rawSettings serialized settings - */ - void initialize(@Nullable String rawSettings); - - /** - * @return serialized settings - */ - String serialize(); + @Override + default String getOwnerId() { + return getViewId(); + } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentManager.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentManager.java index 024a1d9f63..228ad64611 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentManager.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentManager.java @@ -25,8 +25,11 @@ /** * Saves and restores settings for components. + * + * @deprecated use {@link ComponentSettingsManager} instead */ -public interface ViewSettingsComponentManager { +@Deprecated(since = "3.0", forRemoval = true) +public interface ViewSettingsComponentManager extends ComponentSettingsManager { /** * Applies settings from {@link ViewSettings} to provided components. @@ -39,6 +42,11 @@ public interface ViewSettingsComponentManager { */ void applySettings(Collection components, ViewSettings viewSettings); + @Override + default void applySettings(Collection components, UiComponentSettings settings) { + applySettings(components, (ViewSettings) settings); + } + /** * Applies data loading settings from {@link ViewSettings} to provided components. *

@@ -50,6 +58,11 @@ public interface ViewSettingsComponentManager { */ void applyDataLoadingSettings(Collection components, ViewSettings viewSettings); + @Override + default void applyDataLoadingSettings(Collection components, UiComponentSettings settings) { + applyDataLoadingSettings(components, (ViewSettings) settings); + } + /** * Persists settings if they are changed or {@link ViewSettings#isModified()} returns {@code true}. *

@@ -60,4 +73,9 @@ public interface ViewSettingsComponentManager { * @param viewSettings screen settings */ void saveSettings(Collection components, ViewSettings viewSettings); + + @Override + default void saveSettings(Collection components, UiComponentSettings settings) { + saveSettings(components, (ViewSettings) settings); + } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentRegistry.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentRegistry.java index 553e6b9a6d..d321c71e20 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentRegistry.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsComponentRegistry.java @@ -16,32 +16,11 @@ package io.jmix.flowui.facet.settings; -import com.vaadin.flow.component.Component; -import io.jmix.flowui.component.details.JmixDetails; -import io.jmix.flowui.facet.settings.component.binder.ComponentSettingsBinder; - /** * Provides information for which component registered settings class. + * + * @deprecated use {@link ComponentSettingsRegistry} instead */ -public interface ViewSettingsComponentRegistry { - - /** - * @param componentClass component class (e.g. {@link JmixDetails}) - * @return component settings class - * @throws IllegalStateException if there is no component settings class registered for the component class - */ - Class getSettingsClass(Class componentClass); - - /** - * @param componentClass component class (e.g. {@link JmixDetails}) - * @return settings binder - * @throws IllegalStateException if there is no component settings binder registered for the component class - */ - ComponentSettingsBinder getSettingsBinder(Class componentClass); - - /** - * @param componentClass component class (e.g. {@link JmixDetails}) - * @return {@code true} if settings is registered for the component class - */ - boolean isSettingsRegisteredFor(Class componentClass); +@Deprecated(since = "3.0", forRemoval = true) +public interface ViewSettingsComponentRegistry extends ComponentSettingsRegistry { } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsJson.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsJson.java index d1aaa0b292..6fec34ed9b 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsJson.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/settings/ViewSettingsJson.java @@ -16,331 +16,24 @@ package io.jmix.flowui.facet.settings; -import com.google.common.base.Strings; -import com.google.gson.*; import io.jmix.core.annotation.Internal; -import io.jmix.core.common.util.Preconditions; -import io.jmix.flowui.view.View; -import org.springframework.lang.Nullable; - -import java.util.Optional; /** - * {@link View} settings that uses JSON structure. + * {@link ViewSettings} that uses JSON structure. */ @Internal -public class ViewSettingsJson extends AbstractViewSettings { - - protected JsonArray root; - - protected Gson gson; - - public ViewSettingsJson(String viewId) { - super(viewId); - - initGson(); - } - - protected void initGson() { - gson = new GsonBuilder() - .create(); - } - - @Override - public ViewSettings put(String id, String key, @Nullable String value) { - checkNotNullPutConditions(id, key); - - JsonObject object = getObjectOrCreate(id); - - object.addProperty(key, value); - - put(object, id); - - return this; - } - - @Override - public ViewSettings put(String id, String key, @Nullable Integer value) { - checkNotNullPutConditions(id, key); - - JsonObject object = getObjectOrCreate(id); - - object.addProperty(key, value); - - put(object, id); - - return this; - } - - @Override - public ViewSettings put(String id, String key, @Nullable Long value) { - checkNotNullPutConditions(id, key); - - JsonObject object = getObjectOrCreate(id); - - object.addProperty(key, value); - - put(object, id); - - return this; - } - - @Override - public ViewSettings put(String id, String key, @Nullable Double value) { - checkNotNullPutConditions(id, key); - - JsonObject object = getObjectOrCreate(id); - - object.addProperty(key, value); +public class ViewSettingsJson extends AbstractUiComponentSettingsJson + implements ViewSettings { - put(object, id); + protected String viewId; - return this; + public ViewSettingsJson(String ownerId) { + super(ownerId); + this.viewId = ownerId; } @Override - public ViewSettings put(String id, String key, @Nullable Boolean value) { - checkNotNullPutConditions(id, key); - - JsonObject object = getObjectOrCreate(id); - - object.addProperty(key, value); - - put(object, id); - - return this; - } - - @Override - public ViewSettings put(Settings settings) { - Preconditions.checkNotNullArgument(settings); - - if (settings.getId() == null) { - throw new IllegalArgumentException("Cannot put settings with null id"); - } - - put(gson.toJsonTree(settings), settings.getId()); - - return this; - } - - /** - * @param json json object that represents settings - * @return current instance of {@link ViewSettings} - */ - public ViewSettingsJson put(JsonObject json) { - Preconditions.checkNotNullArgument(json); - - if (isValueNull(json, "id")) { - throw new IllegalArgumentException("Cannot put settings, json must have not null id"); - } - - String id = json.getAsJsonPrimitive("id").getAsString(); - put(json, id); - - return this; - } - - @Override - public ViewSettings delete(String id) { - Preconditions.checkNotNullArgument(id); - - JsonObject component = getObject(id); - - if (component != null) { - root.remove(component); - - setModified(true); - } - - return this; - } - - @Override - public ViewSettings delete(String id, String key) { - Preconditions.checkNotNullArgument(id); - Preconditions.checkNotNullArgument(key); - - JsonObject component = getObject(id); - - if (component != null) { - component.remove(key); - - setModified(true); - } - - return this; - } - - /** - * @param id e.g. component id - * @return json object that represents settings - */ - public Optional getJsonSettings(String id) { - Preconditions.checkNotNullArgument(id); - - return Optional.ofNullable(getObject(id)); - } - - @Override - public Optional getString(String id, String key) { - checkNotNullGetConditions(id, key); - - JsonObject object = getObject(id); - - if (object == null || isValueNull(object, key)) - return Optional.empty(); - - return object.keySet().contains(key) ? - Optional.of(object.getAsJsonPrimitive(key).getAsString()) : Optional.empty(); - } - - @Override - public Optional getInteger(String id, String key) { - checkNotNullGetConditions(id, key); - - JsonObject object = getObject(id); - - if (object == null || isValueNull(object, key)) - return Optional.empty(); - - return object.keySet().contains(key) ? - Optional.of(object.getAsJsonPrimitive(key).getAsInt()) : Optional.empty(); - } - - @Override - public Optional getLong(String id, String key) { - checkNotNullGetConditions(id, key); - - JsonObject object = getObject(id); - - if (object == null || isValueNull(object, key)) - return Optional.empty(); - - return object.keySet().contains(key) ? - Optional.of(object.getAsJsonPrimitive(key).getAsLong()) : Optional.empty(); - } - - @Override - public Optional getDouble(String id, String key) { - checkNotNullGetConditions(id, key); - - JsonObject object = getObject(id); - - if (object == null || isValueNull(object, key)) - return Optional.empty(); - - return object.keySet().contains(key) ? - Optional.of(object.getAsJsonPrimitive(key).getAsDouble()) : Optional.empty(); - } - - @Override - public Optional getBoolean(String id, String key) { - checkNotNullGetConditions(id, key); - - JsonObject object = getObject(id); - - if (object == null || isValueNull(object, key)) - return Optional.empty(); - - return object.keySet().contains(key) ? - Optional.of(object.getAsJsonPrimitive(key).getAsBoolean()) : Optional.empty(); - } - - @Override - public Optional getSettings(String id, Class settingsClass) { - Preconditions.checkNotNullArgument(id); - Preconditions.checkNotNullArgument(settingsClass); - - JsonObject json = getObject(id); - if (json == null) - return Optional.empty(); - - return Optional.ofNullable(gson.fromJson(json, settingsClass)); - } - - @Override - public T getSettingsOrCreate(String id, Class settingsClass) { - Preconditions.checkNotNullArgument(id); - Preconditions.checkNotNullArgument(settingsClass); - - return getSettings(id, settingsClass).orElseGet(() -> { - JsonObject json = new JsonObject(); - json.addProperty("id", id); - return settingsClass.cast(gson.fromJson(json, settingsClass)); - }); - } - - @Override - public void initialize(@Nullable String raw) { - root = Strings.isNullOrEmpty(raw) - ? new JsonArray() - : gson.fromJson(raw, JsonArray.class); - } - - @Override - public String serialize() { - return gson.toJson(root); - } - - protected void put(JsonElement json, String id) { - initRoot(); - - delete(id); - - root.add(json); - - setModified(true); - } - - protected JsonObject getObjectOrCreate(String id) { - JsonObject object = getObject(id); - if (object == null) { - object = new JsonObject(); - object.addProperty("id", id); - } - - return object; - } - - @Nullable - protected JsonObject getObject(String objectId) { - initRoot(); - - for (JsonElement jsonElement : root) { - JsonObject object = (JsonObject) jsonElement; - - boolean keyExist = object.keySet().contains("id"); - if (keyExist) { - String id = object.getAsJsonPrimitive("id").getAsString(); - if (id.equals(objectId)) { - return object; - } - } - } - return null; - } - - protected void checkNotNullPutConditions(String id, String key) { - Preconditions.checkNotNullArgument(id, "Cannot put settings when object id is null"); - Preconditions.checkNotNullArgument(key, "Cannot put settings when key is null"); - } - - protected void checkNotNullGetConditions(String id, String property) { - Preconditions.checkNotNullArgument(id, "Cannot get settings when object id is null"); - Preconditions.checkNotNullArgument(property, "Cannot get settings when key is null"); - } - - protected void initRoot() { - if (root == null) { - root = new JsonArray(); - } - } - - protected boolean isValueNull(JsonObject json, String key) { - if (json.keySet().contains(key)) - return json.get(key).isJsonNull(); - - return true; + public String getViewId() { + return viewId; } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java index 7b543fbdb0..6689b9dc19 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java @@ -20,8 +20,8 @@ import io.jmix.flowui.exception.GuiDevelopmentException; import io.jmix.flowui.facet.SettingsFacet; import io.jmix.flowui.facet.impl.SettingsFacetImpl; +import io.jmix.flowui.facet.settings.ComponentSettingsManager; import io.jmix.flowui.facet.settings.SettingsFacetUrlQueryParametersHelper; -import io.jmix.flowui.facet.settings.ViewSettingsComponentManager; import io.jmix.flowui.settings.UserSettingsCache; import io.jmix.flowui.settings.UserSettingsService; import io.jmix.flowui.sys.autowire.ReflectionCacheManager; @@ -48,7 +48,7 @@ public class SettingsFacetProvider implements FacetProvider { protected LoaderSupport loaderSupport; protected SettingsFacetUrlQueryParametersHelper settingsHelper; protected ReflectionCacheManager reflectionCacheManager; - protected ViewSettingsComponentManager settingsManager; + protected ComponentSettingsManager settingsManager; protected UserSettingsCache userSettingsCache; @Nullable private final UserSettingsService userSettingsService; @@ -57,7 +57,7 @@ public SettingsFacetProvider(LoaderSupport loaderSupport, SettingsFacetUrlQueryParametersHelper settingsHelper, ReflectionCacheManager reflectionCacheManager, UserSettingsCache userSettingsCache, - ViewSettingsComponentManager settingsManager, + ComponentSettingsManager settingsManager, @Nullable UserSettingsService userSettingsService) { this.loaderSupport = loaderSupport; this.settingsHelper = settingsHelper; @@ -83,7 +83,7 @@ public String getFacetTag() { } @Override - public void loadFromXml(SettingsFacet facet, Element element, ComponentLoader.ComponentContext context) { + public void loadFromXml(SettingsFacet facet, Element element, ComponentLoader.Context context) { loaderSupport.loadString(element, "id", facet::setId); loaderSupport.loadBoolean(element, "auto", facet::setAuto); @@ -112,7 +112,7 @@ protected List filterExcludedIds(Map components) { .collect(Collectors.toList()); } - protected Map loadComponents(ComponentLoader.ComponentContext context, Element root) { + protected Map loadComponents(ComponentLoader.Context context, Element root) { List components = root.elements("component"); if (CollectionUtils.isEmpty(components)) { return Collections.emptyMap(); From 17c39eea866130de420eda75a5f44a921f29706e Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Mon, 15 Dec 2025 16:05:04 +0400 Subject: [PATCH 06/28] SettingsFacet Part 2 --- .../GenericFilterMakeDefaultAction.java | 3 +- .../component/tabsheet/TabSheetUtils.java | 77 +++++-- .../java/io/jmix/flowui/facet/FacetOwner.java | 9 + .../flowui/facet/FragmentSettingsFacet.java | 27 +++ .../io/jmix/flowui/facet/SettingsFacet.java | 95 +++++---- .../jmix/flowui/facet/ViewSettingsFacet.java | 28 +++ ...etImpl.java => AbstractSettingsFacet.java} | 197 ++++++------------ .../facet/impl/FragmentSettingsFacetImpl.java | 130 ++++++++++++ .../flowui/facet/impl/SettingsFacetUtils.java | 6 +- .../facet/impl/ViewSettingsFacetImpl.java | 131 ++++++++++++ .../java/io/jmix/flowui/impl/FacetsImpl.java | 13 +- .../xml/facet/SettingsFacetProvider.java | 4 +- .../xml/facet/loader/SettingsFacetLoader.java | 9 +- 13 files changed, 526 insertions(+), 203 deletions(-) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentSettingsFacet.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/ViewSettingsFacet.java rename jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/{SettingsFacetImpl.java => AbstractSettingsFacet.java} (59%) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentSettingsFacetImpl.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewSettingsFacetImpl.java diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterMakeDefaultAction.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterMakeDefaultAction.java index 13ed8c121b..035958f006 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterMakeDefaultAction.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterMakeDefaultAction.java @@ -22,6 +22,7 @@ import io.jmix.flowui.component.genericfilter.GenericFilter; import io.jmix.flowui.component.genericfilter.model.FilterConfigurationModel; import io.jmix.flowui.facet.SettingsFacet; +import io.jmix.flowui.facet.ViewSettingsFacet; import io.jmix.flowui.facet.settings.ViewSettings; import io.jmix.flowui.facet.settings.component.GenericFilterSettings; import io.jmix.flowui.icon.Icons; @@ -72,7 +73,7 @@ protected boolean isApplicable() { public void execute() { checkTarget(); - SettingsFacet settingsFacet = ViewControllerUtils.getViewFacet(getParentView(), SettingsFacet.class); + ViewSettingsFacet settingsFacet = ViewControllerUtils.getViewFacet(getParentView(), ViewSettingsFacet.class); Preconditions.checkNotNullArgument(settingsFacet, "The view doesn't contain %s", SettingsFacet.class.getSimpleName()); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/tabsheet/TabSheetUtils.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/tabsheet/TabSheetUtils.java index a1d8b33f2c..18478d467c 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/tabsheet/TabSheetUtils.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/tabsheet/TabSheetUtils.java @@ -20,13 +20,19 @@ import com.vaadin.flow.component.tabs.Tab; import io.jmix.core.annotation.Internal; import io.jmix.flowui.component.UiComponentUtils; +import io.jmix.flowui.facet.FragmentSettingsFacet; import io.jmix.flowui.facet.SettingsFacet; +import io.jmix.flowui.facet.ViewSettingsFacet; import io.jmix.flowui.facet.impl.SettingsFacetUtils; +import io.jmix.flowui.facet.settings.UiComponentSettings; +import io.jmix.flowui.fragment.Fragment; +import io.jmix.flowui.fragment.FragmentUtils; import io.jmix.flowui.view.View; import io.jmix.flowui.view.ViewControllerUtils; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; @Internal @@ -37,8 +43,21 @@ public static void updateTabContent(JmixTabSheet tabSheet, Tab tab, Component co } public static void applySettingsToTabContent(JmixTabSheet tabSheet, Tab tab) { - View view = UiComponentUtils.getView(tabSheet); + Optional> settingsFacet; + Component owner; + + Fragment fragment = UiComponentUtils.findFragment(tabSheet); + if (fragment != null) { + settingsFacet = getFragmentFacet(fragment); + owner = fragment; + } else { + View view = UiComponentUtils.getView(tabSheet); + settingsFacet = getViewFacet(view); + owner = view; + } + Component tabContent = tabSheet.getContentByTab(tab); + List tabComponents = new ArrayList<>(); if (UiComponentUtils.isContainer(tabContent)) { tabComponents.addAll(UiComponentUtils.getComponents(tabContent)); @@ -46,25 +65,41 @@ public static void applySettingsToTabContent(JmixTabSheet tabSheet, Tab tab) { tabComponents.add(tabContent); } - ViewControllerUtils.getViewFacets(view).getFacets().forEach(facet -> { - if (facet instanceof SettingsFacet settingsFacet) { - - if (settingsFacet.getSettings() == null) { - throw new IllegalStateException("SettingsFacet is not attached to the view"); - } - - Consumer applySettingsDelegate = - settingsFacet.getApplySettingsDelegate(); - - if (applySettingsDelegate != null) { - applySettingsDelegate.accept(new SettingsFacet.SettingsContext( - view, - new ArrayList<>(tabComponents), - settingsFacet.getSettings())); - } else { - SettingsFacetUtils.applySettings(settingsFacet, tabComponents); - } - } - }); + settingsFacet.ifPresent(facet -> processSettingsFacet(facet, owner, tabComponents)); + } + + private static Optional> getFragmentFacet(Fragment fragment) { + return FragmentUtils.getFragmentFacets(fragment).getFacets() + .filter(facet -> facet instanceof FragmentSettingsFacet) + .map(FragmentSettingsFacet.class::cast) + .findAny(); + } + + private static Optional> getViewFacet(View view) { + return ViewControllerUtils.getViewFacets(view).getFacets() + .filter(facet -> facet instanceof ViewSettingsFacet) + .map(ViewSettingsFacet.class::cast) + .findAny(); + } + + private static > void processSettingsFacet(SettingsFacet settingsFacet, Component owner, + List tabComponents) { + if (settingsFacet.getSettings() == null) { + throw new IllegalStateException("SettingsFacet is not attached to the view"); + } + + Consumer> applySettingsDelegate = + settingsFacet.getApplySettingsDelegate(); + + if (applySettingsDelegate != null) { + SettingsFacet.SettingsContext context = new SettingsFacet.SettingsContext<>( + owner, + new ArrayList<>(tabComponents), + settingsFacet.getSettings()); + + applySettingsDelegate.accept(context); + } else { + SettingsFacetUtils.applySettings(settingsFacet, tabComponents); + } } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FacetOwner.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FacetOwner.java index 3ddbb8a88f..5de869bf74 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FacetOwner.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FacetOwner.java @@ -16,8 +16,17 @@ package io.jmix.flowui.facet; +import java.util.Optional; + /** * Marker interface for components that can have facets. */ public interface FacetOwner { + + /** + * Gets the id of the owner. + * + * @return the id, or and empty optional if no id has been set + */ + Optional getId(); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentSettingsFacet.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentSettingsFacet.java new file mode 100644 index 0000000000..17bc2c74db --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentSettingsFacet.java @@ -0,0 +1,27 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet; + +import io.jmix.flowui.facet.settings.FragmentSettings; + +/** + * Marker interface representing a facet that manages {@link FragmentSettings}. + * Provides functionality for retrieving and storing configuration settings + * associated with a {@link io.jmix.flowui.fragment.Fragment}. + */ +public interface FragmentSettingsFacet extends SettingsFacet { +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/SettingsFacet.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/SettingsFacet.java index 89b0ebed76..fe375a54a5 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/SettingsFacet.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/SettingsFacet.java @@ -20,6 +20,7 @@ import com.vaadin.flow.component.DetachEvent; import io.jmix.flowui.component.details.JmixDetails; import io.jmix.flowui.component.grid.DataGrid; +import io.jmix.flowui.facet.settings.UiComponentSettings; import io.jmix.flowui.facet.settings.ViewSettings; import io.jmix.flowui.facet.settings.component.binder.ComponentSettingsBinder; import io.jmix.flowui.view.View; @@ -32,15 +33,15 @@ import java.util.function.Consumer; /** - * Provides ability to save component states when {@link View} is closed and restore them + * Provides an ability to save component states when {@link FacetOwner} is closed and restore them * when it's opened again. *

* For instance, it can be 'opened' state from {@link JmixDetails} or columns order in {@link DataGrid}. *

* Note, facet works with components that contain an id and have {@link ComponentSettingsBinder}. - * Otherwise, it cannot match saved settings with component. + * Otherwise, it cannot match saved settings with a component. */ -public interface SettingsFacet extends Facet { +public interface SettingsFacet> extends Facet { /** * @return the mode in which facet is worked. {@code true} if facet includes all components and {@code false} @@ -62,13 +63,13 @@ public interface SettingsFacet extends Facet { void setAuto(boolean auto); /** - * @return {@link View} settings or {@code null} if facet is not attached to the {@link View} + * @return {@link UiComponentSettings} or {@code null} if facet is not attached to the {@link FacetOwner} */ @Nullable - ViewSettings getSettings(); + S getSettings(); /** - * Restores settings for components. Facet applies settings on {@link View}'s {@link ReadyEvent}. + * Restores settings for components. Facet applies settings on {@link FacetOwner Owner's} {@link ReadyEvent}. */ void applySettings(); @@ -76,19 +77,19 @@ public interface SettingsFacet extends Facet { * Restores settings that related with data loading. For instance, it applies sort columns and its * direction in {@link DataGrid} before data loading. *

- * Facet applies data loading settings on {@link View}'s {@link BeforeShowEvent}. + * Facet applies data loading settings on {@link FacetOwner Owner's} {@link BeforeShowEvent}. */ void applyDataLoadingSettings(); /** - * Persists settings to store. Facet saves settings on {@link View}'s {@link DetachEvent}. + * Persists settings to store. Facet saves settings on {@link FacetOwner Owner's} {@link DetachEvent}. */ void saveSettings(); /** * Adds component ids that should be managed when {@link #isAuto()} returns {@code false}. *

- * Note, component must be attached to the {@link View}, otherwise it will be ignored. + * Note, component must be attached to the {@link FacetOwner Owner's}, otherwise it will be ignored. * * @param ids component ids */ @@ -114,9 +115,9 @@ public interface SettingsFacet extends Facet { Set getExcludedComponentIds(); /** - * Collection depends on {@link #isAuto()} property. If {@link #isAuto()} returns {@code true}, collection will be - * filled by {@link View}'s components, otherwise collection will be filled by components that explicitly added by - * {@link #addComponentIds(String...)}. + * Collection depends on {@link #isAuto()} property. If {@link #isAuto()} returns {@code true}, a collection will be + * filled by {@link FacetOwner Owner's} components, otherwise a collection will be filled by components + * that explicitly added by {@link #addComponentIds(String...)}. * * @return components collection that is used for applying and saving settings */ @@ -126,78 +127,81 @@ public interface SettingsFacet extends Facet { * @return apply settings delegate or {@code null} if not set */ @Nullable - Consumer getApplySettingsDelegate(); + Consumer> getApplySettingsDelegate(); /** - * Sets handler that should be invoked instead of default facet's logic for applying settings. + * Sets handler that should be invoked instead of the default facet's logic for applying settings. *

* For instance: - *

-     * @Install(to = "settingsFacet", subject = "applySettingsDelegate")
-     * private void onApplySettings(SettingsFacet.SettingsContext settingsContext) {
-     *     settingsFacet.applySettings();
+     * 
 {@code
+     *      @Install(to = "settingsFacet", subject = "applySettingsDelegate")
+     *      private void onApplySettings(SettingsFacet.SettingsContext settingsContext) {
+     *          settingsFacet.applySettings();
+     *      }
      * }
      * 
* * @param delegate handler to set */ - void setApplySettingsDelegate(@Nullable Consumer delegate); + void setApplySettingsDelegate(@Nullable Consumer> delegate); /** * @return apply data loading settings delegate or {@code null} if not set */ @Nullable - Consumer getApplyDataLoadingSettingsDelegate(); + Consumer> getApplyDataLoadingSettingsDelegate(); /** - * Sets handler that should be invoked instead of default facet's logic for applying data loading settings. + * Sets handler that should be invoked instead of the default facet's logic for applying data loading settings. *

* For instance: - *

-     * @Install(to = "settingsFacet", subject = "applyDataLoadingSettingsDelegate")
-     * private void onApplyDataLoadingSettings(SettingsFacet.SettingsContext settingsContext) {
-     *     settingsFacet.applyDataLoadingSettings();
+     * 
 {@code
+     *      @Install(to = "settingsFacet", subject = "applyDataLoadingSettingsDelegate")
+     *      private void onApplyDataLoadingSettings(SettingsFacet.SettingsContext settingsContext) {
+     *          settingsFacet.applyDataLoadingSettings();
+     *      }
      * }
      * 
* * @param delegate handler to set */ - void setApplyDataLoadingSettingsDelegate(@Nullable Consumer delegate); + void setApplyDataLoadingSettingsDelegate(@Nullable Consumer> delegate); /** * @return save settings delegate or {@code null} if not set */ @Nullable - Consumer getSaveSettingsDelegate(); + Consumer> getSaveSettingsDelegate(); /** - * Sets handler that should be invoked instead of default facet's logic for saving settings. + * Sets handler that should be invoked instead of the default facet's logic for saving settings. *

* For instance: - *

-     * @Install(to = "settingsFacet", subject = "saveSettingsDelegate")
-     * private void onSaveSettings(SettingsFacet.SettingsContext settingsContext) {
-     *     settingsFacet.saveSettings();
+     * 
{@code
+     *      @Install(to = "settingsFacet", subject = "saveSettingsDelegate")
+     *      private void onSaveSettings(SettingsFacet.SettingsContext settingsContext) {
+     *          settingsFacet.saveSettings();
+     *      }
      * }
      * 
* * @param delegate handler to set */ - void setSaveSettingsDelegate(@Nullable Consumer delegate); + void setSaveSettingsDelegate(@Nullable Consumer> delegate); /** - * Provides information about source component and components that should be managed by facet. + * Provides information about a source component and components that facet should manage. */ - class SettingsContext { + class SettingsContext> { protected Component source; protected Collection components; - protected ViewSettings viewSettings; + protected S settings; - public SettingsContext(Component source, Collection components, ViewSettings viewSettings) { + public SettingsContext(Component source, Collection components, S settings) { this.source = source; this.components = components; - this.viewSettings = viewSettings; + this.settings = settings; } /** @@ -216,9 +220,22 @@ public Collection getComponents() { /** * @return {@link View} settings + * @deprecated use {@link #getSettings()} instead */ + @Deprecated(since = "3.0", forRemoval = true) public ViewSettings getViewSettings() { - return viewSettings; + if (settings instanceof ViewSettings viewSettings) { + return viewSettings; + } + + throw new IllegalStateException("Settings are not of %s type".formatted(ViewSettings.class.getSimpleName())); + } + + /** + * @return component settings + */ + public S getSettings() { + return settings; } } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/ViewSettingsFacet.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/ViewSettingsFacet.java new file mode 100644 index 0000000000..1a74710401 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/ViewSettingsFacet.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet; + +import io.jmix.flowui.facet.settings.ViewSettings; +import io.jmix.flowui.view.View; + +/** + * Marker interface representing a facet that manages {@link ViewSettings}. + * Provides functionality for retrieving and storing configuration settings + * associated with a {@link View}. + */ +public interface ViewSettingsFacet extends SettingsFacet { +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractSettingsFacet.java similarity index 59% rename from jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetImpl.java rename to jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractSettingsFacet.java index 15c5f77766..be073dae30 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractSettingsFacet.java @@ -19,19 +19,20 @@ import com.vaadin.flow.component.Component; import com.vaadin.flow.component.ComponentEvent; import com.vaadin.flow.component.ComponentEventListener; -import com.vaadin.flow.component.DetachEvent; +import com.vaadin.flow.component.Composite; import com.vaadin.flow.router.QueryParameters; import com.vaadin.flow.shared.Registration; +import io.jmix.flowui.component.HasFacetsComponents; import io.jmix.flowui.component.UiComponentUtils; +import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.facet.SettingsFacet; import io.jmix.flowui.facet.UrlQueryParametersFacet; -import io.jmix.flowui.facet.settings.*; +import io.jmix.flowui.facet.settings.ComponentSettingsManager; +import io.jmix.flowui.facet.settings.SettingsFacetUrlQueryParametersHelper; +import io.jmix.flowui.facet.settings.UiComponentSettings; import io.jmix.flowui.settings.UserSettingsCache; import io.jmix.flowui.settings.UserSettingsService; import io.jmix.flowui.sys.autowire.ReflectionCacheManager; -import io.jmix.flowui.view.View; -import io.jmix.flowui.view.ViewControllerUtils; -import io.jmix.flowui.view.ViewFacets; import org.apache.commons.collections4.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,12 +44,13 @@ import java.util.stream.Collectors; /** - * An implementation of the {@link SettingsFacet} interface that provides functionality - * for managing and applying settings related to views and their components. + * An abstract implementation of the {@link SettingsFacet} interface that provides functionality + * for managing and applying settings related to {@link FacetOwner} and their components. */ -public class SettingsFacetImpl extends AbstractFacet implements SettingsFacet { +public abstract class AbstractSettingsFacet> extends AbstractFacet + implements SettingsFacet { - private static final Logger log = LoggerFactory.getLogger(SettingsFacetImpl.class); + private static final Logger log = LoggerFactory.getLogger(AbstractSettingsFacet.class); protected SettingsFacetUrlQueryParametersHelper settingsHelper; protected ReflectionCacheManager reflectionCacheManager; @@ -60,25 +62,21 @@ public class SettingsFacetImpl extends AbstractFacet implements SettingsFacet { protected Set componentIds; protected Set excludedComponentIds; - protected ViewSettings viewSettings; + protected S componentSettings; protected boolean auto = false; - protected ViewEventListener beforeShowListener; - protected ViewEventListener readyListener; - protected ViewEventListener detachListener; - - protected Consumer applySettingsDelegate; - protected Consumer applyDataLoadingSettingsDelegate; - protected Consumer saveSettingsDelegate; + protected Consumer> applySettingsDelegate; + protected Consumer> applyDataLoadingSettingsDelegate; + protected Consumer> saveSettingsDelegate; protected QueryParameters viewQueryParameters; - public SettingsFacetImpl(SettingsFacetUrlQueryParametersHelper settingsHelper, - ReflectionCacheManager reflectionCacheManager, - UserSettingsCache userSettingsCache, - ComponentSettingsManager settingsManager, - @Nullable UserSettingsService userSettingsService) { + protected AbstractSettingsFacet(SettingsFacetUrlQueryParametersHelper settingsHelper, + ReflectionCacheManager reflectionCacheManager, + UserSettingsCache userSettingsCache, + ComponentSettingsManager settingsManager, + @Nullable UserSettingsService userSettingsService) { this.settingsHelper = settingsHelper; this.reflectionCacheManager = reflectionCacheManager; this.settingsManager = settingsManager; @@ -97,8 +95,8 @@ public void setAuto(boolean auto) { } @Override - public ViewSettings getSettings() { - return viewSettings; + public S getSettings() { + return componentSettings; } @Override @@ -107,7 +105,7 @@ public void applySettings() { components = excludeUrlQueryParametersFacetComponents(components); - applyViewSettings(components); + applyComponentSettings(components); } @Override @@ -116,14 +114,14 @@ public void applyDataLoadingSettings() { components = excludeUrlQueryParametersFacetComponents(components); - applyDataLoadingViewSettings(components); + applyDataLoadingComponentSettings(components); } @Override public void saveSettings() { Collection components = getManagedComponents(); - saveViewSettings(components); + saveComponentSettings(components); } @Override @@ -157,58 +155,58 @@ public Set getExcludedComponentIds() { @Override public Collection getManagedComponents() { - checkAttachedToView(); + checkAttachedToOwner(); Collection viewComponents = UiComponentUtils.getComponents( - Objects.requireNonNull(getView()).getContent()); + Objects.requireNonNull(getOwnerComponent()).getContent()); return getManagedComponentsFromCollection(viewComponents); } @Nullable @Override - public Consumer getApplySettingsDelegate() { + public Consumer> getApplySettingsDelegate() { return applySettingsDelegate; } @Override - public void setApplySettingsDelegate(@Nullable Consumer delegate) { + public void setApplySettingsDelegate(@Nullable Consumer> delegate) { this.applySettingsDelegate = delegate; } @Nullable @Override - public Consumer getApplyDataLoadingSettingsDelegate() { + public Consumer> getApplyDataLoadingSettingsDelegate() { return applyDataLoadingSettingsDelegate; } @Override - public void setApplyDataLoadingSettingsDelegate(@Nullable Consumer delegate) { + public void setApplyDataLoadingSettingsDelegate(@Nullable Consumer> delegate) { this.applyDataLoadingSettingsDelegate = delegate; } @Nullable @Override - public Consumer getSaveSettingsDelegate() { + public Consumer> getSaveSettingsDelegate() { return saveSettingsDelegate; } @Override - public void setSaveSettingsDelegate(@Nullable Consumer delegate) { + public void setSaveSettingsDelegate(@Nullable Consumer> delegate) { this.saveSettingsDelegate = delegate; } @Override - public void setOwner(@Nullable View owner) { + public & FacetOwner> void setOwner(@Nullable T owner) { super.setOwner(owner); - unsubscribeViewLifecycle(); + unsubscribeOwnerLifecycle(); if (owner != null) { - viewSettings = createViewSettings(owner); - initViewSettings(viewSettings); + componentSettings = createSettings(owner); + initSettings(componentSettings); - subscribeViewLifecycle(); + subscribeOwnerLifecycle(); if (!isSettingsEnabled()) { log.warn("SettingsFacet does not work for '{}' because UserSettingsService implementation is not available", @@ -236,102 +234,41 @@ protected Collection getManagedComponentsFromCollection(Collection view) { - return new ViewSettingsJson(view.getId() - .orElseThrow(() -> - new IllegalStateException("Cannot create " + ViewSettings.class.getSimpleName() + - " because " + view.getClass().getSimpleName() + " does not contain an id"))); - } + protected abstract S createSettings(FacetOwner owner); - protected void initViewSettings(ViewSettings viewSettings) { + protected void initSettings(S viewSettings) { if (isSettingsEnabled()) { - String rawSettings = userSettingsCache.get(viewSettings.getViewId()); + String rawSettings = userSettingsCache.get(viewSettings.getOwnerId()); viewSettings.initialize(rawSettings); } } - protected void unsubscribeViewLifecycle() { - if (beforeShowListener != null) { - beforeShowListener.unsubscribe(); - beforeShowListener = null; - } - if (readyListener != null) { - readyListener.unsubscribe(); - readyListener = null; - } - if (detachListener != null) { - detachListener.unsubscribe(); - detachListener = null; - } - } - - protected void subscribeViewLifecycle() { - View view = getView(); - - beforeShowListener = new ViewEventListener(view, View.BeforeShowEvent.class, this::onViewBeforeShow); - readyListener = new ViewEventListener(view, View.ReadyEvent.class, this::onViewReady); - detachListener = new ViewEventListener(view, DetachEvent.class, this::onViewDetach); - detachListener = new ViewEventListener(view, View.QueryParametersChangeEvent.class, this::onQueryParametersChange); - } - - protected void onViewBeforeShow(ComponentEvent event) { - checkAttachedToView(); - - if (applyDataLoadingSettingsDelegate != null) { - applyDataLoadingSettingsDelegate.accept(createSettingsContext()); - } else { - applyDataLoadingSettings(); - } - } - - protected void onViewReady(ComponentEvent event) { - checkAttachedToView(); - - if (applySettingsDelegate != null) { - applySettingsDelegate.accept(createSettingsContext()); - } else { - applySettings(); - } - } - - protected void onViewDetach(ComponentEvent event) { - checkAttachedToView(); + protected abstract void unsubscribeOwnerLifecycle(); - if (saveSettingsDelegate != null) { - saveSettingsDelegate.accept(createSettingsContext()); - } else { - saveSettings(); - } - } - - protected void onQueryParametersChange(ComponentEvent event) { - if (event instanceof View.QueryParametersChangeEvent) { - viewQueryParameters = ((View.QueryParametersChangeEvent) event).getQueryParameters(); - } - } + protected abstract void subscribeOwnerLifecycle(); - protected void applyViewSettings(Collection components) { + protected void applyComponentSettings(Collection components) { if (isSettingsEnabled()) { - settingsManager.applySettings(components, viewSettings); + settingsManager.applySettings(components, componentSettings); } } - protected void applyDataLoadingViewSettings(Collection components) { + protected void applyDataLoadingComponentSettings(Collection components) { if (isSettingsEnabled()) { - settingsManager.applyDataLoadingSettings(components, viewSettings); + settingsManager.applyDataLoadingSettings(components, componentSettings); } } - protected void saveViewSettings(Collection components) { + protected void saveComponentSettings(Collection components) { if (isSettingsEnabled()) { - settingsManager.saveSettings(components, viewSettings); + settingsManager.saveSettings(components, componentSettings); } } - protected void checkAttachedToView() { + protected void checkAttachedToOwner() { if (getOwner() == null) { throw new IllegalStateException( - SettingsFacet.class.getSimpleName() + " is not attached to " + View.class.getSimpleName()); + SettingsFacet.class.getSimpleName() + " is not attached to " + FacetOwner.class.getSimpleName()); } } @@ -341,17 +278,16 @@ protected boolean isSettingsEnabled() { protected Collection excludeUrlQueryParametersFacetComponents(Collection components) { Collection resultComponents = new ArrayList<>(components); - View view = getView(); if (viewQueryParameters == null || viewQueryParameters.getParameters().isEmpty()) { return resultComponents; } - ViewFacets viewFacets = ViewControllerUtils.getViewFacets(view); - List urlQueryFacets = viewFacets.getFacets() + HasFacetsComponents facets = getFacets(); + List urlQueryFacets = facets.getFacets() .filter(f -> f instanceof UrlQueryParametersFacet) - .map(f -> (UrlQueryParametersFacet) f) + .map(UrlQueryParametersFacet.class::cast) .toList(); if (urlQueryFacets.isEmpty()) { @@ -372,29 +308,31 @@ protected Collection excludeUrlQueryParametersFacetComponents(Collect return resultComponents; } - protected View getView() { - checkAttachedToView(); + protected abstract HasFacetsComponents getFacets(); + + protected Composite getOwnerComponent() { + checkAttachedToOwner(); // Used only to hide inspection, cannot be null here return Objects.requireNonNull(getOwner()); } - protected SettingsContext createSettingsContext() { - View owner = getView(); + protected SettingsContext createSettingsContext() { + Composite owner = getOwnerComponent(); - return new SettingsContext(owner, getManagedComponents(), viewSettings); + return new SettingsContext<>(owner, getManagedComponents(), componentSettings); } - protected class ViewEventListener { + protected class OwnerEventListener { - protected View view; + protected FacetOwner owner; protected Class eventClass; protected Consumer> listener; protected Registration registration; - public ViewEventListener(View view, Class eventClass, Consumer> listener) { - this.view = view; + public OwnerEventListener(FacetOwner owner, Class eventClass, Consumer> listener) { + this.owner = owner; this.eventClass = eventClass; this.listener = listener; @@ -403,14 +341,15 @@ public ViewEventListener(View view, Class eventClass, Consumer) event -> listener.accept(event)); + registration = (Registration) addListenerMethod.invoke(owner, + (ComponentEventListener) event -> listener.accept(event)); } catch (Error e) { throw e; } catch (Throwable e) { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentSettingsFacetImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentSettingsFacetImpl.java new file mode 100644 index 0000000000..9760707597 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentSettingsFacetImpl.java @@ -0,0 +1,130 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.impl; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.DetachEvent; +import com.vaadin.flow.router.QueryParameters; +import io.jmix.flowui.component.HasFacetsComponents; +import io.jmix.flowui.component.UiComponentUtils; +import io.jmix.flowui.facet.FacetOwner; +import io.jmix.flowui.facet.FragmentSettingsFacet; +import io.jmix.flowui.facet.settings.ComponentSettingsManager; +import io.jmix.flowui.facet.settings.FragmentSettings; +import io.jmix.flowui.facet.settings.FragmentSettingsJson; +import io.jmix.flowui.facet.settings.SettingsFacetUrlQueryParametersHelper; +import io.jmix.flowui.fragment.Fragment; +import io.jmix.flowui.fragment.FragmentUtils; +import io.jmix.flowui.settings.UserSettingsCache; +import io.jmix.flowui.settings.UserSettingsService; +import io.jmix.flowui.sys.autowire.ReflectionCacheManager; +import io.jmix.flowui.view.View; +import org.jspecify.annotations.Nullable; + +public class FragmentSettingsFacetImpl extends AbstractSettingsFacet + implements FragmentSettingsFacet { + + protected OwnerEventListener readyListener; + protected OwnerEventListener ownerViewDetachListener; + protected OwnerEventListener ownerViewQueryParametersChangeListener; + + protected QueryParameters viewQueryParameters; + + public FragmentSettingsFacetImpl(SettingsFacetUrlQueryParametersHelper settingsHelper, + ReflectionCacheManager reflectionCacheManager, + UserSettingsCache userSettingsCache, + ComponentSettingsManager settingsManager, + @Nullable UserSettingsService userSettingsService) { + super(settingsHelper, reflectionCacheManager, userSettingsCache, settingsManager, userSettingsService); + } + + @Override + protected FragmentSettings createSettings(FacetOwner owner) { + return new FragmentSettingsJson(owner.getId() + .orElseThrow(() -> + new IllegalStateException("Cannot create " + FragmentSettings.class.getSimpleName() + + " because " + owner.getClass().getSimpleName() + " does not contain an id")) + ); + } + + @Override + protected void unsubscribeOwnerLifecycle() { + if (readyListener != null) { + readyListener.unsubscribe(); + readyListener = null; + } + + if (ownerViewDetachListener != null) { + ownerViewDetachListener.unsubscribe(); + ownerViewDetachListener = null; + } + + if (ownerViewQueryParametersChangeListener != null) { + ownerViewQueryParametersChangeListener.unsubscribe(); + ownerViewQueryParametersChangeListener = null; + } + } + + @Override + protected void subscribeOwnerLifecycle() { + Fragment fragment = ((Fragment) getOwnerComponent()); + View ownerView = UiComponentUtils.getView(fragment); + + readyListener = new OwnerEventListener(fragment, Fragment.ReadyEvent.class, this::onFragmentReady); + ownerViewDetachListener = new OwnerEventListener(ownerView, DetachEvent.class, this::onOwnerViewDetach); + ownerViewQueryParametersChangeListener = new OwnerEventListener(ownerView, + View.QueryParametersChangeEvent.class, this::onQueryParametersChange); + } + + protected void onFragmentReady(ComponentEvent componentEvent) { + checkAttachedToOwner(); + + if (applyDataLoadingSettingsDelegate != null) { + applyDataLoadingSettingsDelegate.accept(createSettingsContext()); + } else { + applyDataLoadingSettings(); + } + + if (applySettingsDelegate != null) { + applySettingsDelegate.accept(createSettingsContext()); + } else { + applySettings(); + } + } + + private void onOwnerViewDetach(ComponentEvent componentEvent) { + checkAttachedToOwner(); + + if (saveSettingsDelegate != null) { + saveSettingsDelegate.accept(createSettingsContext()); + } else { + saveSettings(); + } + } + + protected void onQueryParametersChange(ComponentEvent event) { + if (event instanceof View.QueryParametersChangeEvent) { + viewQueryParameters = ((View.QueryParametersChangeEvent) event).getQueryParameters(); + } + } + + @Override + protected HasFacetsComponents getFacets() { + Fragment fragment = (Fragment) getOwnerComponent(); + return FragmentUtils.getFragmentFacets(fragment); + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetUtils.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetUtils.java index d3d7266460..fff6e9e505 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetUtils.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/SettingsFacetUtils.java @@ -35,9 +35,9 @@ public final class SettingsFacetUtils { * @param components the collection of components to which the settings are to be applied * @throws UnsupportedOperationException if the provided settings facet is not of a supported type */ - public static void applySettings(SettingsFacet settingsFacet, Collection components) { - if (settingsFacet instanceof SettingsFacetImpl settingsFacetImpl) { - settingsFacetImpl.applyViewSettings(settingsFacetImpl.getManagedComponentsFromCollection(components)); + public static void applySettings(SettingsFacet settingsFacet, Collection components) { + if (settingsFacet instanceof AbstractSettingsFacet abstractSettingsFacet) { + abstractSettingsFacet.applyComponentSettings(abstractSettingsFacet.getManagedComponentsFromCollection(components)); } else { throw new UnsupportedOperationException(String.format( "Settings facet with type %s isn't supported yet", settingsFacet.getClass())); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewSettingsFacetImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewSettingsFacetImpl.java new file mode 100644 index 0000000000..62bc245645 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewSettingsFacetImpl.java @@ -0,0 +1,131 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.impl; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.component.DetachEvent; +import io.jmix.flowui.component.HasFacetsComponents; +import io.jmix.flowui.facet.FacetOwner; +import io.jmix.flowui.facet.SettingsFacet; +import io.jmix.flowui.facet.ViewSettingsFacet; +import io.jmix.flowui.facet.settings.ComponentSettingsManager; +import io.jmix.flowui.facet.settings.SettingsFacetUrlQueryParametersHelper; +import io.jmix.flowui.facet.settings.ViewSettings; +import io.jmix.flowui.facet.settings.ViewSettingsJson; +import io.jmix.flowui.settings.UserSettingsCache; +import io.jmix.flowui.settings.UserSettingsService; +import io.jmix.flowui.sys.autowire.ReflectionCacheManager; +import io.jmix.flowui.view.View; +import io.jmix.flowui.view.ViewControllerUtils; +import org.jspecify.annotations.Nullable; + +/** + * An implementation of the {@link SettingsFacet} interfacet that provides functionality for managing + * and applying settings related to {@link View} and their components. + */ +public class ViewSettingsFacetImpl extends AbstractSettingsFacet + implements ViewSettingsFacet { + + protected OwnerEventListener beforeShowListener; + protected OwnerEventListener readyListener; + protected OwnerEventListener detachListener; + protected OwnerEventListener queryParametersChangeListener; + + public ViewSettingsFacetImpl(SettingsFacetUrlQueryParametersHelper settingsHelper, + ReflectionCacheManager reflectionCacheManager, + UserSettingsCache userSettingsCache, + ComponentSettingsManager settingsManager, + @Nullable UserSettingsService userSettingsService) { + super(settingsHelper, reflectionCacheManager, userSettingsCache, settingsManager, userSettingsService); + } + + @Override + protected ViewSettings createSettings(FacetOwner owner) { + return new ViewSettingsJson(owner.getId() + .orElseThrow(() -> + new IllegalStateException("Cannot create " + ViewSettings.class.getSimpleName() + + " because " + owner.getClass().getSimpleName() + " does not contain an id"))); + } + + @Override + protected void unsubscribeOwnerLifecycle() { + if (beforeShowListener != null) { + beforeShowListener.unsubscribe(); + beforeShowListener = null; + } + if (readyListener != null) { + readyListener.unsubscribe(); + readyListener = null; + } + if (detachListener != null) { + detachListener.unsubscribe(); + detachListener = null; + } + } + + @Override + protected void subscribeOwnerLifecycle() { + View view = ((View) getOwnerComponent()); + + beforeShowListener = new OwnerEventListener(view, View.BeforeShowEvent.class, this::onViewBeforeShow); + readyListener = new OwnerEventListener(view, View.ReadyEvent.class, this::onViewReady); + detachListener = new OwnerEventListener(view, DetachEvent.class, this::onViewDetach); + queryParametersChangeListener = new OwnerEventListener(view, View.QueryParametersChangeEvent.class, this::onQueryParametersChange); + } + + protected void onViewBeforeShow(ComponentEvent event) { + checkAttachedToOwner(); + + if (applyDataLoadingSettingsDelegate != null) { + applyDataLoadingSettingsDelegate.accept(createSettingsContext()); + } else { + applyDataLoadingSettings(); + } + } + + protected void onViewReady(ComponentEvent event) { + checkAttachedToOwner(); + + if (applySettingsDelegate != null) { + applySettingsDelegate.accept(createSettingsContext()); + } else { + applySettings(); + } + } + + protected void onViewDetach(ComponentEvent event) { + checkAttachedToOwner(); + + if (saveSettingsDelegate != null) { + saveSettingsDelegate.accept(createSettingsContext()); + } else { + saveSettings(); + } + } + + protected void onQueryParametersChange(ComponentEvent event) { + if (event instanceof View.QueryParametersChangeEvent) { + viewQueryParameters = ((View.QueryParametersChangeEvent) event).getQueryParameters(); + } + } + + @Override + protected HasFacetsComponents getFacets() { + View view = (View) getOwnerComponent(); + return ViewControllerUtils.getViewFacets(view); + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FacetsImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FacetsImpl.java index 4430fab2b3..d97f70ba8d 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FacetsImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FacetsImpl.java @@ -21,10 +21,7 @@ import io.jmix.flowui.Facets; import io.jmix.flowui.facet.*; import io.jmix.flowui.facet.Timer; -import io.jmix.flowui.facet.impl.DataLoadCoordinatorImpl; -import io.jmix.flowui.facet.impl.SettingsFacetImpl; -import io.jmix.flowui.facet.impl.TimerImpl; -import io.jmix.flowui.facet.impl.UrlQueryParametersFacetImpl; +import io.jmix.flowui.facet.impl.*; import io.jmix.flowui.xml.facet.FacetProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +52,11 @@ public class FacetsImpl implements Facets, ApplicationContextAware { register(DataLoadCoordinatorImpl.class, DataLoadCoordinator.class); register(UrlQueryParametersFacetImpl.class, UrlQueryParametersFacet.class); register(TimerImpl.class, Timer.class); - register(SettingsFacetImpl.class, SettingsFacet.class); + + // use view settings by default + register(ViewSettingsFacetImpl.class, SettingsFacet.class); + register(ViewSettingsFacetImpl.class, ViewSettingsFacet.class); + register(FragmentSettingsFacetImpl.class, FragmentSettingsFacet.class); } @Override @@ -100,7 +101,7 @@ public T create(Class facetClass) { } public void register(Class facetClass, Class replacedFacet) { - if(getFacetInfo(facetClass).isPresent()) { + if (getFacetInfo(facetClass).isPresent()) { log.trace("Facet with `{}` class has already registered", facetClass); return; } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java index 6689b9dc19..fb8f2d815c 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java @@ -19,7 +19,7 @@ import com.google.common.base.Strings; import io.jmix.flowui.exception.GuiDevelopmentException; import io.jmix.flowui.facet.SettingsFacet; -import io.jmix.flowui.facet.impl.SettingsFacetImpl; +import io.jmix.flowui.facet.impl.ViewSettingsFacetImpl; import io.jmix.flowui.facet.settings.ComponentSettingsManager; import io.jmix.flowui.facet.settings.SettingsFacetUrlQueryParametersHelper; import io.jmix.flowui.settings.UserSettingsCache; @@ -74,7 +74,7 @@ public Class getFacetClass() { @Override public SettingsFacet create() { - return new SettingsFacetImpl(settingsHelper, reflectionCacheManager, userSettingsCache, settingsManager, userSettingsService); + return new ViewSettingsFacetImpl(settingsHelper, reflectionCacheManager, userSettingsCache, settingsManager, userSettingsService); } @Override diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/SettingsFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/SettingsFacetLoader.java index e71057743f..fc3829ef0c 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/SettingsFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/SettingsFacetLoader.java @@ -18,6 +18,7 @@ import com.google.common.base.Strings; import io.jmix.flowui.exception.GuiDevelopmentException; +import io.jmix.flowui.facet.FragmentSettingsFacet; import io.jmix.flowui.facet.SettingsFacet; import io.jmix.flowui.impl.FacetsImpl; import io.jmix.flowui.xml.facet.FacetProvider; @@ -31,10 +32,14 @@ import java.util.Map; import java.util.stream.Collectors; -public class SettingsFacetLoader extends AbstractFacetLoader { +public class SettingsFacetLoader extends AbstractFacetLoader> { @Override - protected SettingsFacet createFacet() { + protected SettingsFacet createFacet() { + if (context instanceof ComponentLoader.FragmentContext) { + return facets.create(FragmentSettingsFacet.class); + } + return facets.create(SettingsFacet.class); } From 465c9331e99c37aa5db88b920fd42322d5e4629d Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Tue, 16 Dec 2025 13:15:55 +0400 Subject: [PATCH 07/28] Separate loaders for separate implementations --- .../jmix/flowui/fragment/FragmentUtils.java | 16 ++++++ .../java/io/jmix/flowui/impl/FacetsImpl.java | 10 ++-- .../xml/facet/DefaultFacetLoaderConfig.java | 12 +++-- ....java => AbstractSettingsFacetLoader.java} | 12 +---- .../loader/FragmentSettingsFacetLoader.java | 28 ++++++++++ .../facet/loader/ViewSettingsFacetLoader.java | 28 ++++++++++ .../io/jmix/flowui/view/fragment.xsd | 51 ++++++++++++++++++- 7 files changed, 139 insertions(+), 18 deletions(-) rename jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/{SettingsFacetLoader.java => AbstractSettingsFacetLoader.java} (90%) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentSettingsFacetLoader.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewSettingsFacetLoader.java diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java index 66b896bc4a..9fc75120ec 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java @@ -174,6 +174,22 @@ public static boolean sameId(Component component, String id) { return componentId.isPresent() && id.equals(componentId.get()); } + /** + * Returns the component with the given id. + * + * @param fragment fragment to find component from + * @param id component id + * @return the component with the given id + * @throws IllegalStateException if fragment content doesn't contain components + * @throws IllegalArgumentException if component with given id is not found + */ + public static Component getComponent(Fragment fragment, String id) { + return findComponent(fragment, id) + .orElseThrow(() -> new IllegalArgumentException( + "Component with id '%s' not fount".formatted(id) + )); + } + /** * Returns an {@link Optional} describing the component with given fragment id, * or an empty {@link Optional}. diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FacetsImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FacetsImpl.java index d97f70ba8d..55c1d5cb7f 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FacetsImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FacetsImpl.java @@ -49,12 +49,16 @@ public class FacetsImpl implements Facets, ApplicationContextAware { protected Set facets = ConcurrentHashMap.newKeySet(); { - register(DataLoadCoordinatorImpl.class, DataLoadCoordinator.class); + // use view dataLoadCoordinator by default for backward compatibility + register(ViewDataLoadCoordinator.class, DataLoadCoordinator.class); + register(ViewDataLoadCoordinatorImpl.class, ViewDataLoadCoordinator.class); + register(FragmentDataLoadCoordinatorImpl.class, FragmentDataLoadCoordinator.class); + register(UrlQueryParametersFacetImpl.class, UrlQueryParametersFacet.class); register(TimerImpl.class, Timer.class); - // use view settings by default - register(ViewSettingsFacetImpl.class, SettingsFacet.class); + // use view settings by default for backward compatibility + register(ViewSettingsFacet.class, SettingsFacet.class); register(ViewSettingsFacetImpl.class, ViewSettingsFacet.class); register(FragmentSettingsFacetImpl.class, FragmentSettingsFacet.class); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DefaultFacetLoaderConfig.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DefaultFacetLoaderConfig.java index 2e0a069ecc..f5922edfeb 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DefaultFacetLoaderConfig.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DefaultFacetLoaderConfig.java @@ -50,9 +50,15 @@ public Class> getLoader(Element element) { } protected void initDefaultLoaders() { - loaders.put("dataLoadCoordinator", DataLoadCoordinatorFacetLoader.class); - loaders.put("urlQueryParameters", UrlQueryParametersFacetLoader.class); + loaders.put("dataLoadCoordinator", ViewDataLoadCoordinatorFacetLoader.class); + loaders.put("fragmentDataLoadCoordinator", FragmentDataLoadCoordinatorFacetLoader.class); + + loaders.put("urlQueryParameters", ViewUrlQueryParametersFacetLoader.class); + loaders.put("fragmentUrlQueryParameters", FragmentUrlQueryParametersFacetLoader.class); + loaders.put("timer", TimerFacetLoader.class); - loaders.put("settings", SettingsFacetLoader.class); + + loaders.put("settings", ViewSettingsFacetLoader.class); + loaders.put("fragmentSettings", FragmentSettingsFacetLoader.class); } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/SettingsFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractSettingsFacetLoader.java similarity index 90% rename from jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/SettingsFacetLoader.java rename to jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractSettingsFacetLoader.java index fc3829ef0c..dbf58541c8 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/SettingsFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractSettingsFacetLoader.java @@ -18,7 +18,6 @@ import com.google.common.base.Strings; import io.jmix.flowui.exception.GuiDevelopmentException; -import io.jmix.flowui.facet.FragmentSettingsFacet; import io.jmix.flowui.facet.SettingsFacet; import io.jmix.flowui.impl.FacetsImpl; import io.jmix.flowui.xml.facet.FacetProvider; @@ -32,16 +31,7 @@ import java.util.Map; import java.util.stream.Collectors; -public class SettingsFacetLoader extends AbstractFacetLoader> { - - @Override - protected SettingsFacet createFacet() { - if (context instanceof ComponentLoader.FragmentContext) { - return facets.create(FragmentSettingsFacet.class); - } - - return facets.create(SettingsFacet.class); - } +public abstract class AbstractSettingsFacetLoader extends AbstractFacetLoader> { @Override public void loadFacet() { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentSettingsFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentSettingsFacetLoader.java new file mode 100644 index 0000000000..a8c6deb2d2 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentSettingsFacetLoader.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.xml.facet.loader; + +import io.jmix.flowui.facet.FragmentSettingsFacet; +import io.jmix.flowui.facet.SettingsFacet; + +public class FragmentSettingsFacetLoader extends AbstractSettingsFacetLoader { + + @Override + protected SettingsFacet createFacet() { + return facets.create(FragmentSettingsFacet.class); + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewSettingsFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewSettingsFacetLoader.java new file mode 100644 index 0000000000..2eead00bb7 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewSettingsFacetLoader.java @@ -0,0 +1,28 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.xml.facet.loader; + +import io.jmix.flowui.facet.SettingsFacet; +import io.jmix.flowui.facet.ViewSettingsFacet; + +public class ViewSettingsFacetLoader extends AbstractSettingsFacetLoader { + + @Override + protected SettingsFacet createFacet() { + return facets.create(ViewSettingsFacet.class); + } +} diff --git a/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd b/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd index 9724fd2b5f..debac70387 100644 --- a/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd +++ b/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd @@ -29,12 +29,61 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 06cb5c5b2dfc5c8aee075566c41264d45c3bf258 Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Tue, 16 Dec 2025 13:16:18 +0400 Subject: [PATCH 08/28] DataLoadCoordinator facet refactoring --- .../flowui/facet/DataLoadCoordinator.java | 12 +- .../facet/FragmentDataLoadCoordinator.java | 24 ++++ .../flowui/facet/ViewDataLoadCoordinator.java | 36 ++++++ .../AbstractOnEventLoadTrigger.java | 68 ++++++++++ .../OnFragmentEventLoadTrigger.java | 35 ++++++ .../OnViewEventLoadTrigger.java | 35 +----- ....java => AbstractDataLoadCoordinator.java} | 59 +++++---- .../impl/FragmentDataLoadCoordinatorImpl.java | 71 +++++++++++ .../impl/ViewDataLoadCoordinatorImpl.java | 64 ++++++++++ .../DataLoadCoordinatorFacetProvider.java | 4 +- ...stractDataLoadCoordinatorFacetLoader.java} | 78 ++---------- ...ragmentDataLoadCoordinatorFacetLoader.java | 117 ++++++++++++++++++ .../ViewDataLoadCoordinatorFacetLoader.java | 102 +++++++++++++++ 13 files changed, 568 insertions(+), 137 deletions(-) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentDataLoadCoordinator.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/ViewDataLoadCoordinator.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/AbstractOnEventLoadTrigger.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/OnFragmentEventLoadTrigger.java rename jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/{DataLoadCoordinatorImpl.java => AbstractDataLoadCoordinator.java} (81%) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentDataLoadCoordinatorImpl.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewDataLoadCoordinatorImpl.java rename jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/{DataLoadCoordinatorFacetLoader.java => AbstractDataLoadCoordinatorFacetLoader.java} (65%) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewDataLoadCoordinatorFacetLoader.java diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/DataLoadCoordinator.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/DataLoadCoordinator.java index 1270abec4f..8adfac5a3d 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/DataLoadCoordinator.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/DataLoadCoordinator.java @@ -19,13 +19,15 @@ import com.vaadin.flow.component.Component; import io.jmix.flowui.model.DataLoader; import io.jmix.flowui.model.InstanceContainer; +import io.jmix.flowui.fragment.Fragment; +import io.jmix.flowui.view.View; import org.springframework.lang.Nullable; import java.util.List; /** - * A non-visual component for coordinating data loading operations in a view. - * Manages the association of data loaders to various triggers, such as view + * A non-visual component for coordinating data loading operations in a {@link FacetOwner}. + * Manages the association of data loaders to various triggers, such as view or fragment * events, container item changes, or component value changes. */ public interface DataLoadCoordinator extends Facet { @@ -50,7 +52,9 @@ public interface DataLoadCoordinator extends Facet { * * @param loader loader * @param eventClass event class + * @deprecated use {@link ViewDataLoadCoordinator#addOnViewEventLoadTrigger(DataLoader, Class)} instead */ + @Deprecated(since = "3.0", forRemoval = true) void addOnViewEventLoadTrigger(DataLoader loader, Class eventClass); /** @@ -76,7 +80,9 @@ void addOnComponentValueChangedLoadTrigger(DataLoader loader, Component componen /** * Configures triggers automatically relying upon parameter prefixes. All data containers that don't have a prefixed - * parameter in the query string, are configured to be triggered on {@code BeforeShowEvent} or {@code AttachEvent}. + * parameter in the query string are configured to be triggered + * on {@link View.BeforeShowEvent} in case of {@link View} facet owner + * or {@link Fragment.ReadyEvent} in case of {@link Fragment} facet owner or {@code AttachEvent}. */ void configureAutomatically(); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentDataLoadCoordinator.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentDataLoadCoordinator.java new file mode 100644 index 0000000000..1d629d7d3d --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentDataLoadCoordinator.java @@ -0,0 +1,24 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet; + +import io.jmix.flowui.model.DataLoader; + +public interface FragmentDataLoadCoordinator extends DataLoadCoordinator { + + void addOnFragmentEventLoadTrigger(DataLoader loader, Class eventClass); +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/ViewDataLoadCoordinator.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/ViewDataLoadCoordinator.java new file mode 100644 index 0000000000..883dc4dce0 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/ViewDataLoadCoordinator.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet; + +import io.jmix.flowui.model.DataLoader; +import io.jmix.flowui.view.View; + +/** + * A non-visual component for coordinating data loading operations in a {@link View}. + * Manages the association of data loaders to various triggers, such as view + * events, container item changes, or component value changes. + */ +public interface ViewDataLoadCoordinator extends DataLoadCoordinator { + + /** + * Adds trigger on view event. + * + * @param loader loader + * @param eventClass event class + */ + void addOnViewEventLoadTrigger(DataLoader loader, Class eventClass); +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/AbstractOnEventLoadTrigger.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/AbstractOnEventLoadTrigger.java new file mode 100644 index 0000000000..3789230726 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/AbstractOnEventLoadTrigger.java @@ -0,0 +1,68 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.dataloadcoordinator; + +import com.vaadin.flow.component.ComponentEventListener; +import io.jmix.flowui.facet.DataLoadCoordinator; +import io.jmix.flowui.facet.FacetOwner; +import io.jmix.flowui.model.DataLoader; +import io.jmix.flowui.sys.autowire.ReflectionCacheManager; + +import java.lang.invoke.MethodHandle; + +/** + * An abstract implementation of the {@link DataLoadCoordinator.Trigger} interface. It registers an event listener on a + * specified {@link FacetOwner FacetOwner's} event and invokes the associated {@link DataLoader} when the event occurs, + * triggering data loading operations. + */ +public class AbstractOnEventLoadTrigger implements DataLoadCoordinator.Trigger { + + protected final DataLoader loader; + + protected AbstractOnEventLoadTrigger(FacetOwner owner, ReflectionCacheManager reflectionCacheManager, + DataLoader loader, Class eventClass) { + initTrigger(reflectionCacheManager, owner, eventClass); + + this.loader = loader; + } + + protected void initTrigger(ReflectionCacheManager reflectionCacheManager, FacetOwner owner, Class eventClass) { + MethodHandle addListenerMethod = reflectionCacheManager.getTargetAddListenerMethod( + owner.getClass(), eventClass, null + ); + if (addListenerMethod == null) { + throw new IllegalStateException("Cannot find addListener method for " + eventClass); + } + + try { + addListenerMethod.invoke(owner, (ComponentEventListener) event -> load()); + } catch (Error e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException("Unable to add listener for " + eventClass, e); + } + } + + protected void load() { + loader.load(); + } + + @Override + public DataLoader getLoader() { + return loader; + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/OnFragmentEventLoadTrigger.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/OnFragmentEventLoadTrigger.java new file mode 100644 index 0000000000..6b0d30da80 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/OnFragmentEventLoadTrigger.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.dataloadcoordinator; + +import io.jmix.flowui.facet.DataLoadCoordinator; +import io.jmix.flowui.fragment.Fragment; +import io.jmix.flowui.model.DataLoader; +import io.jmix.flowui.sys.autowire.ReflectionCacheManager; + +/** + * Implementation of the {@link DataLoadCoordinator.Trigger} interface. It registers an event listener on a + * specified {@link Fragment Fragment's} event and invokes the associated {@link DataLoader} when the event occurs, + * triggering data loading operations. + */ +public class OnFragmentEventLoadTrigger extends AbstractOnEventLoadTrigger { + + public OnFragmentEventLoadTrigger(Fragment fragment, ReflectionCacheManager reflectionCacheManager, + DataLoader loader, Class eventClass) { + super(fragment, reflectionCacheManager, loader, eventClass); + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/OnViewEventLoadTrigger.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/OnViewEventLoadTrigger.java index 20fd026bfc..c876a9f58a 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/OnViewEventLoadTrigger.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/OnViewEventLoadTrigger.java @@ -16,49 +16,20 @@ package io.jmix.flowui.facet.dataloadcoordinator; -import com.vaadin.flow.component.ComponentEventListener; import io.jmix.flowui.facet.DataLoadCoordinator; import io.jmix.flowui.model.DataLoader; import io.jmix.flowui.sys.autowire.ReflectionCacheManager; import io.jmix.flowui.view.View; -import java.lang.invoke.MethodHandle; - /** * Implementation of the {@link DataLoadCoordinator.Trigger} interface. - * It registers an event listener on a specified view's event and invokes the associated + * It registers an event listener on a specified {@link View View's} event and invokes the associated * {@link DataLoader} when the event occurs, triggering data loading operations. */ -public class OnViewEventLoadTrigger implements DataLoadCoordinator.Trigger { - - protected final DataLoader loader; +public class OnViewEventLoadTrigger extends AbstractOnEventLoadTrigger { public OnViewEventLoadTrigger(View view, ReflectionCacheManager reflectionCacheManager, DataLoader loader, Class eventClass) { - MethodHandle addListenerMethod = reflectionCacheManager.getTargetAddListenerMethod( - view.getClass(), eventClass, null - ); - if (addListenerMethod == null) { - throw new IllegalStateException("Cannot find addListener method for " + eventClass); - } - - try { - addListenerMethod.invoke(view, (ComponentEventListener) event -> load()); - } catch (Error e) { - throw e; - } catch (Throwable e) { - throw new RuntimeException("Unable to add listener for " + eventClass, e); - } - - this.loader = loader; - } - - protected void load() { - loader.load(); - } - - @Override - public DataLoader getLoader() { - return loader; + super(view, reflectionCacheManager, loader, eventClass); } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/DataLoadCoordinatorImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractDataLoadCoordinator.java similarity index 81% rename from jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/DataLoadCoordinatorImpl.java rename to jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractDataLoadCoordinator.java index 32272f113d..78e31fd0ea 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/DataLoadCoordinatorImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/AbstractDataLoadCoordinator.java @@ -18,24 +18,21 @@ import com.google.common.base.Strings; import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.Composite; import io.jmix.core.DevelopmentException; import io.jmix.core.impl.QueryParamValuesManager; import io.jmix.core.querycondition.Condition; import io.jmix.core.querycondition.JpqlCondition; import io.jmix.core.querycondition.LogicalCondition; -import io.jmix.flowui.component.UiComponentUtils; +import io.jmix.flowui.component.HasDataComponents; import io.jmix.flowui.facet.DataLoadCoordinator; import io.jmix.flowui.facet.dataloadcoordinator.OnComponentValueChangedLoadTrigger; import io.jmix.flowui.facet.dataloadcoordinator.OnContainerItemChangedLoadTrigger; import io.jmix.flowui.facet.dataloadcoordinator.OnViewEventLoadTrigger; import io.jmix.flowui.model.DataLoader; import io.jmix.flowui.model.InstanceContainer; -import io.jmix.flowui.model.ViewData; import io.jmix.flowui.model.impl.DataLoadersHelper; import io.jmix.flowui.sys.autowire.ReflectionCacheManager; -import io.jmix.flowui.view.View; -import io.jmix.flowui.view.View.BeforeShowEvent; -import io.jmix.flowui.view.ViewControllerUtils; import org.springframework.lang.Nullable; import java.util.ArrayList; @@ -46,12 +43,12 @@ import java.util.stream.Stream; /** - * Implementation of the {@link DataLoadCoordinator} interface. - * This class is responsible for managing data loading triggers in a UI view. It provides + * An abstract implementation of the {@link DataLoadCoordinator} interface. + * This class is responsible for managing data loading triggers in a UI. It provides * mechanisms to configure triggers and associate them with data loaders, components, or instance containers. * These triggers allow automatic data loading by observing changes in specific sources or events. */ -public class DataLoadCoordinatorImpl extends AbstractFacet implements DataLoadCoordinator { +public abstract class AbstractDataLoadCoordinator extends AbstractFacet implements DataLoadCoordinator { protected static final Pattern LIKE_PATTERN = Pattern.compile("\\s+like\\s+:([\\w$]+)"); @@ -63,8 +60,8 @@ public class DataLoadCoordinatorImpl extends AbstractFacet implements DataLoadCo protected ReflectionCacheManager reflectionCacheManager; private final QueryParamValuesManager queryParamValuesManager; - public DataLoadCoordinatorImpl(ReflectionCacheManager reflectionCacheManager, - QueryParamValuesManager queryParamValuesManager) { + public AbstractDataLoadCoordinator(ReflectionCacheManager reflectionCacheManager, + QueryParamValuesManager queryParamValuesManager) { this.reflectionCacheManager = reflectionCacheManager; this.queryParamValuesManager = queryParamValuesManager; } @@ -84,11 +81,6 @@ public List getTriggers() { return Collections.unmodifiableList(triggers); } - @Override - public void addOnViewEventLoadTrigger(DataLoader loader, Class eventClass) { - triggers.add(new OnViewEventLoadTrigger(getOwnerNN(), reflectionCacheManager, loader, eventClass)); - } - @Override public void addOnContainerItemChangedLoadTrigger(DataLoader loader, InstanceContainer container, @Nullable String param) { @@ -117,15 +109,16 @@ public void addOnComponentValueChangedLoadTrigger(DataLoader loader, Component c @Override public void configureAutomatically() { - View owner = getOwnerNN(); - ViewData viewData = ViewControllerUtils.getViewData(owner); + HasDataComponents data = getOwnerData(); - getUnconfiguredLoaders(viewData).forEach(loader -> configureAutomatically(loader, owner)); + getUnconfiguredLoaders(data).forEach(this::configureAutomatically); } - protected Stream getUnconfiguredLoaders(ViewData viewData) { - return viewData.getLoaderIds().stream() - .map(viewData::getLoader) + protected abstract HasDataComponents getOwnerData(); + + protected Stream getUnconfiguredLoaders(HasDataComponents data) { + return data.getLoaderIds().stream() + .map(data::getLoader) .distinct() .filter(this::loaderIsNotConfiguredYet); } @@ -136,7 +129,7 @@ protected boolean loaderIsNotConfiguredYet(DataLoader loader) { .noneMatch(configuredLoader -> configuredLoader == loader); } - protected void configureAutomatically(DataLoader loader, View view) { + protected void configureAutomatically(DataLoader loader) { List queryParameters = DataLoadersHelper.getQueryParameters(loader).stream() .filter(paramName -> !queryParamValuesManager.supports(paramName)) @@ -148,26 +141,30 @@ protected void configureAutomatically(DataLoader loader, View view) { // add triggers on container/component events for (String parameter : allParameters) { if (parameter.startsWith(containerPrefix)) { - InstanceContainer container = ViewControllerUtils.getViewData(view) + InstanceContainer container = getOwnerData() .getContainer(parameter.substring(containerPrefix.length())); addOnContainerItemChangedLoadTrigger(loader, container, parameter); } else if (parameter.startsWith(componentPrefix)) { String componentId = parameter.substring(componentPrefix.length()); - Component component = UiComponentUtils.getComponent(view, componentId); + Component component = findComponent(componentId); LikeClause likeClause = findLikeClause(loader, parameter); addOnComponentValueChangedLoadTrigger(loader, component, parameter, likeClause); } } - // if the loader has no parameters in query, add trigger on BeforeShowEvent + // if the loader has no parameters in a query, add trigger on the default event if (queryParameters.isEmpty()) { - addOnViewEventLoadTrigger(loader, BeforeShowEvent.class); + addOnDefaultEventLoadTrigger(loader); } } + protected abstract Component findComponent(String componentId); + + protected abstract void addOnDefaultEventLoadTrigger(DataLoader loader); + protected boolean containsLikeClause(Condition condition, String parameter) { if (condition instanceof JpqlCondition) { String where = ((JpqlCondition) condition).getWhere(); @@ -239,12 +236,12 @@ protected boolean containsLikeClause(String query, String parameter) { return false; } - protected View getOwnerNN() { - View view = getOwner(); - if (view == null) { - throw new IllegalStateException("Owner view is null"); + protected Composite getOwnerNN() { + Composite owner = getOwner(); + if (owner == null) { + throw new IllegalStateException("Owner is null"); } - return view; + return owner; } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentDataLoadCoordinatorImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentDataLoadCoordinatorImpl.java new file mode 100644 index 0000000000..dde4a1d3d8 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentDataLoadCoordinatorImpl.java @@ -0,0 +1,71 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.impl; + +import com.vaadin.flow.component.Component; +import io.jmix.core.impl.QueryParamValuesManager; +import io.jmix.flowui.component.HasDataComponents; +import io.jmix.flowui.facet.FragmentDataLoadCoordinator; +import io.jmix.flowui.facet.dataloadcoordinator.OnFragmentEventLoadTrigger; +import io.jmix.flowui.fragment.Fragment; +import io.jmix.flowui.fragment.FragmentUtils; +import io.jmix.flowui.model.DataLoader; +import io.jmix.flowui.sys.autowire.ReflectionCacheManager; + +public class FragmentDataLoadCoordinatorImpl extends AbstractDataLoadCoordinator + implements FragmentDataLoadCoordinator { + + public FragmentDataLoadCoordinatorImpl(ReflectionCacheManager reflectionCacheManager, + QueryParamValuesManager queryParamValuesManager) { + super(reflectionCacheManager, queryParamValuesManager); + } + + @Override + public void addOnFragmentEventLoadTrigger(DataLoader loader, Class eventClass) { + triggers.add(new OnFragmentEventLoadTrigger(getOwnerNN(), reflectionCacheManager, loader, eventClass)); + } + + @Override + protected void addOnDefaultEventLoadTrigger(DataLoader loader) { + // if the loader has no parameters in a query, add trigger on Fragment.ReadyEvent + addOnFragmentEventLoadTrigger(loader, Fragment.ReadyEvent.class); + } + + @Override + protected Component findComponent(String componentId) { + return FragmentUtils.getComponent(getOwnerNN(), componentId); + } + + @Override + protected HasDataComponents getOwnerData() { + return FragmentUtils.getFragmentData(getOwnerNN()); + } + + @Override + protected Fragment getOwnerNN() { + return (Fragment) super.getOwnerNN(); + } + + /** + * @deprecated use {@link #addOnFragmentEventLoadTrigger(DataLoader, Class)} instead + */ + @Deprecated(since = "3.0", forRemoval = true) + @Override + public void addOnViewEventLoadTrigger(DataLoader loader, Class eventClass) { + throw new UnsupportedOperationException("FragmentDataLoadCoordinator does not support View events"); + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewDataLoadCoordinatorImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewDataLoadCoordinatorImpl.java new file mode 100644 index 0000000000..df38aa3117 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewDataLoadCoordinatorImpl.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.facet.impl; + +import com.vaadin.flow.component.Component; +import io.jmix.core.impl.QueryParamValuesManager; +import io.jmix.flowui.component.HasDataComponents; +import io.jmix.flowui.component.UiComponentUtils; +import io.jmix.flowui.facet.ViewDataLoadCoordinator; +import io.jmix.flowui.facet.dataloadcoordinator.OnViewEventLoadTrigger; +import io.jmix.flowui.model.DataLoader; +import io.jmix.flowui.sys.autowire.ReflectionCacheManager; +import io.jmix.flowui.view.View; +import io.jmix.flowui.view.ViewControllerUtils; + +public class ViewDataLoadCoordinatorImpl extends AbstractDataLoadCoordinator + implements ViewDataLoadCoordinator { + + public ViewDataLoadCoordinatorImpl(ReflectionCacheManager reflectionCacheManager, + QueryParamValuesManager queryParamValuesManager) { + super(reflectionCacheManager, queryParamValuesManager); + } + + // implementation of ViewDataLoadCoordinator interface shouldn't be removed + @Override + public void addOnViewEventLoadTrigger(DataLoader loader, Class eventClass) { + triggers.add(new OnViewEventLoadTrigger(getOwnerNN(), reflectionCacheManager, loader, eventClass)); + } + + @Override + protected void addOnDefaultEventLoadTrigger(DataLoader loader) { + // if the loader has no parameters in a query, add trigger on View.BeforeShowEvent + addOnViewEventLoadTrigger(loader, View.BeforeShowEvent.class); + } + + @Override + protected Component findComponent(String componentId) { + return UiComponentUtils.getComponent(getOwnerNN(), componentId); + } + + @Override + protected HasDataComponents getOwnerData() { + return ViewControllerUtils.getViewData(getOwnerNN()); + } + + @Override + protected View getOwnerNN() { + return (View) super.getOwnerNN(); + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java index 08457cd97e..53e3afaa95 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java @@ -23,7 +23,7 @@ import io.jmix.flowui.exception.GuiDevelopmentException; import io.jmix.flowui.facet.DataLoadCoordinator; import io.jmix.flowui.facet.DataLoadCoordinator.LikeClause; -import io.jmix.flowui.facet.impl.DataLoadCoordinatorImpl; +import io.jmix.flowui.facet.impl.ViewDataLoadCoordinatorImpl; import io.jmix.flowui.model.DataLoader; import io.jmix.flowui.model.InstanceContainer; import io.jmix.flowui.model.ViewData; @@ -63,7 +63,7 @@ public Class getFacetClass() { @Override public DataLoadCoordinator create() { - return new DataLoadCoordinatorImpl(reflectionCacheManager, queryParamValuesManager); + return new ViewDataLoadCoordinatorImpl(reflectionCacheManager, queryParamValuesManager); } @Override diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/DataLoadCoordinatorFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java similarity index 65% rename from jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/DataLoadCoordinatorFacetLoader.java rename to jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java index 5de28166cf..0a0b84f898 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/DataLoadCoordinatorFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java @@ -25,22 +25,14 @@ import io.jmix.flowui.model.DataLoader; import io.jmix.flowui.model.InstanceContainer; import io.jmix.flowui.model.ViewData; -import io.jmix.flowui.view.View; import io.jmix.flowui.view.ViewControllerUtils; import io.jmix.flowui.xml.facet.FacetProvider; import io.jmix.flowui.xml.layout.ComponentLoader; import org.dom4j.Element; import org.springframework.lang.Nullable; - -public class DataLoadCoordinatorFacetLoader extends AbstractFacetLoader { - - @Override - protected DataLoadCoordinator createFacet() { - DataLoadCoordinator facet = facets.create(DataLoadCoordinator.class); - facet.setOwner(context.getView()); - return facet; - } +public abstract class AbstractDataLoadCoordinatorFacetLoader + extends AbstractFacetLoader { @Override public void loadFacet() { @@ -66,7 +58,7 @@ protected void loadAuto(Element element) { loaderSupport.loadBoolean(element, "auto") .ifPresent(auto -> { if (auto) { - context.addPreInitTask(new AutoConfigurationInitTask(resultFacet)); + getComponentContext().addPreInitTask(new AutoConfigurationInitTask(resultFacet)); } }); } @@ -77,53 +69,22 @@ protected void loadRefreshElements(Element element) { } } - protected void loadRefresh(DataLoadCoordinator facet, Element element) { - String loaderId = loaderSupport.loadString(element, "loader") - .orElseThrow(() -> - new GuiDevelopmentException("'dataLoadCoordinator/refresh' element has no 'loader' attribute", - context)); - - for (Element eventElement : element.elements()) { - switch (eventElement.getName()) { - case "onViewEvent" -> loadOnViewEvent(facet, loaderId, eventElement); - case "onContainerItemChanged" -> loadOnContainerItemChanged(facet, loaderId, eventElement); - case "onComponentValueChanged" -> loadOnComponentValueChanged(facet, loaderId, eventElement); - default -> throw new GuiDevelopmentException( - "Unsupported nested element in 'dataLoadCoordinator/refresh': %s" - .formatted(eventElement.getName()), - context); - } - } - } - - protected void loadOnViewEvent(DataLoadCoordinator facet, String loaderId, Element element) { - String type = loadEventRequiredAttribute(element, "type"); - - Class eventClass = switch (type) { - case "Init" -> View.InitEvent.class; - case "BeforeShow" -> View.BeforeShowEvent.class; - case "Ready" -> View.ReadyEvent.class; - default -> throw new GuiDevelopmentException("Unsupported 'dataLoadCoordinator/refresh/onViewEvent.event'" + - " value: " + type, context); - }; - - context.addPreInitTask(new OnViewEventLoadTriggerInitTask(facet, loaderId, eventClass)); - } + protected abstract void loadRefresh(T facet, Element element); - protected void loadOnContainerItemChanged(DataLoadCoordinator facet, String loaderId, Element element) { + protected void loadOnContainerItemChanged(T facet, String loaderId, Element element) { String container = loadEventRequiredAttribute(element, "container"); String param = loadParam(element); - context.addPreInitTask(new OnContainerItemChangedLoadTriggerInitTask(facet, loaderId, container, param)); + getComponentContext().addPreInitTask(new OnContainerItemChangedLoadTriggerInitTask(facet, loaderId, container, param)); } - protected void loadOnComponentValueChanged(DataLoadCoordinator facet, String loaderId, Element element) { + protected void loadOnComponentValueChanged(T facet, String loaderId, Element element) { String component = loadEventRequiredAttribute(element, "component"); String param = loadParam(element); DataLoadCoordinator.LikeClause likeClause = loadLikeClause(element); - context.addPreInitTask(new OnComponentValueChangedLoadTriggerInitTask( + getComponentContext().addPreInitTask(new OnComponentValueChangedLoadTriggerInitTask( facet, loaderId, component, param, likeClause)); } @@ -145,28 +106,7 @@ protected DataLoadCoordinator.LikeClause loadLikeClause(Element element) { .orElse(DataLoadCoordinator.LikeClause.NONE); } - @SuppressWarnings("ClassCanBeRecord") - public static class OnViewEventLoadTriggerInitTask implements ComponentLoader.InitTask { - - protected final DataLoadCoordinator facet; - protected final String loaderId; - protected final Class eventClass; - - public OnViewEventLoadTriggerInitTask(DataLoadCoordinator facet, String loaderId, Class eventClass) { - this.facet = facet; - this.loaderId = loaderId; - this.eventClass = eventClass; - } - - @Override - public void execute(ComponentLoader.Context context) { - Preconditions.checkNotNullArgument(facet.getOwner()); - - ViewData viewData = ViewControllerUtils.getViewData(facet.getOwner()); - DataLoader loader = viewData.getLoader(loaderId); - facet.addOnViewEventLoadTrigger(loader, eventClass); - } - } + protected abstract ComponentLoader.ComponentContext getComponentContext(); @SuppressWarnings("ClassCanBeRecord") public static class OnContainerItemChangedLoadTriggerInitTask implements ComponentLoader.InitTask { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java new file mode 100644 index 0000000000..5a7e786008 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.xml.facet.loader; + +import com.vaadin.flow.component.Composite; +import io.jmix.core.common.util.Preconditions; +import io.jmix.flowui.exception.GuiDevelopmentException; +import io.jmix.flowui.facet.FacetOwner; +import io.jmix.flowui.facet.FragmentDataLoadCoordinator; +import io.jmix.flowui.fragment.FragmentData; +import io.jmix.flowui.fragment.FragmentUtils; +import io.jmix.flowui.model.DataLoader; +import io.jmix.flowui.view.View; +import io.jmix.flowui.xml.layout.ComponentLoader; +import org.dom4j.Element; + +public class FragmentDataLoadCoordinatorFacetLoader + extends AbstractDataLoadCoordinatorFacetLoader { + + @Override + protected FragmentDataLoadCoordinator createFacet() { + FragmentDataLoadCoordinator facet = facets.create(FragmentDataLoadCoordinator.class); + facet.setOwner((Composite & FacetOwner) context.getOrigin()); + return facet; + } + + @Override + protected ComponentLoader.ComponentContext getComponentContext() { + return findHostViewContext((ComponentLoader.FragmentContext) context); + } + + protected void loadRefresh(FragmentDataLoadCoordinator facet, Element element) { + String loaderId = loaderSupport.loadString(element, "loader") + .orElseThrow(() -> + new GuiDevelopmentException( + "'fragmentDataLoadCoordinator/refresh' element has no 'loader' attribute", + context)); + + for (Element eventElement : element.elements()) { + switch (eventElement.getName()) { + case "onFragmentEvent" -> loadOnFragmentEvent(facet, loaderId, eventElement); + case "onContainerItemChanged" -> loadOnContainerItemChanged(facet, loaderId, eventElement); + case "onComponentValueChanged" -> loadOnComponentValueChanged(facet, loaderId, eventElement); + default -> throw new GuiDevelopmentException( + "Unsupported nested element in 'fragmentDataLoadCoordinator/refresh': %s" + .formatted(eventElement.getName()), + context); + } + } + } + + protected void loadOnFragmentEvent(FragmentDataLoadCoordinator facet, String loaderId, Element element) { + String type = loadEventRequiredAttribute(element, "type"); + + Class eventClass; + + if ("Ready".equals(type)) { + eventClass = View.ReadyEvent.class; + } else { + throw new GuiDevelopmentException( + "Unsupported 'fragmentDataLoadCoordinator/refresh/oFragmentEvent.event'" + + " value: " + type, context); + } + + getComponentContext().addPreInitTask(new OnFragmentEventLoadTriggerInitTask(facet, loaderId, eventClass)); + } + + protected ComponentLoader.ComponentContext findHostViewContext(ComponentLoader.FragmentContext fragmentContext) { + ComponentLoader.Context currentContext = fragmentContext; + while (currentContext.getParentContext() != null) { + currentContext = currentContext.getParentContext(); + } + + if (currentContext instanceof ComponentLoader.ComponentContext viewContext) { + return viewContext; + } + + throw new IllegalStateException("%s has no parent view context" + .formatted(fragmentContext.getClass().getSimpleName())); + } + + public static class OnFragmentEventLoadTriggerInitTask implements ComponentLoader.InitTask { + + protected final FragmentDataLoadCoordinator facet; + protected final String loaderId; + protected final Class eventClass; + + public OnFragmentEventLoadTriggerInitTask(FragmentDataLoadCoordinator facet, String loaderId, Class eventClass) { + this.facet = facet; + this.loaderId = loaderId; + this.eventClass = eventClass; + } + + @Override + public void execute(ComponentLoader.Context context) { + Preconditions.checkNotNullArgument(facet.getOwner()); + + FragmentData fragmentData = FragmentUtils.getFragmentData(facet.getOwner()); + DataLoader loader = fragmentData.getLoader(loaderId); + facet.addOnFragmentEventLoadTrigger(loader, eventClass); + } + } +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewDataLoadCoordinatorFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewDataLoadCoordinatorFacetLoader.java new file mode 100644 index 0000000000..a6ee7f2393 --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewDataLoadCoordinatorFacetLoader.java @@ -0,0 +1,102 @@ +/* + * Copyright 2025 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.xml.facet.loader; + +import com.vaadin.flow.component.Composite; +import io.jmix.core.common.util.Preconditions; +import io.jmix.flowui.exception.GuiDevelopmentException; +import io.jmix.flowui.facet.FacetOwner; +import io.jmix.flowui.facet.ViewDataLoadCoordinator; +import io.jmix.flowui.model.DataLoader; +import io.jmix.flowui.model.ViewData; +import io.jmix.flowui.view.View; +import io.jmix.flowui.view.ViewControllerUtils; +import io.jmix.flowui.xml.layout.ComponentLoader; +import org.dom4j.Element; + +public class ViewDataLoadCoordinatorFacetLoader + extends AbstractDataLoadCoordinatorFacetLoader { + + @Override + protected ViewDataLoadCoordinator createFacet() { + ViewDataLoadCoordinator facet = facets.create(ViewDataLoadCoordinator.class); + facet.setOwner((Composite & FacetOwner) context.getOrigin()); + return facet; + } + + protected void loadRefresh(ViewDataLoadCoordinator facet, Element element) { + String loaderId = loaderSupport.loadString(element, "loader") + .orElseThrow(() -> + new GuiDevelopmentException("'dataLoadCoordinator/refresh' element has no 'loader' attribute", + context)); + + for (Element eventElement : element.elements()) { + switch (eventElement.getName()) { + case "onViewEvent" -> loadOnViewEvent(facet, loaderId, eventElement); + case "onContainerItemChanged" -> loadOnContainerItemChanged(facet, loaderId, eventElement); + case "onComponentValueChanged" -> loadOnComponentValueChanged(facet, loaderId, eventElement); + default -> throw new GuiDevelopmentException( + "Unsupported nested element in 'dataLoadCoordinator/refresh': %s" + .formatted(eventElement.getName()), + context); + } + } + } + + protected void loadOnViewEvent(ViewDataLoadCoordinator facet, String loaderId, Element element) { + String type = loadEventRequiredAttribute(element, "type"); + + Class eventClass = switch (type) { + case "Init" -> View.InitEvent.class; + case "BeforeShow" -> View.BeforeShowEvent.class; + case "Ready" -> View.ReadyEvent.class; + default -> throw new GuiDevelopmentException("Unsupported 'dataLoadCoordinator/refresh/onViewEvent.event'" + + " value: " + type, context); + }; + + getComponentContext().addPreInitTask(new OnViewEventLoadTriggerInitTask(facet, loaderId, eventClass)); + } + + @Override + protected ComponentLoader.ComponentContext getComponentContext() { + // should be component context in case on view loading + return (ComponentLoader.ComponentContext) context; + } + + @SuppressWarnings("ClassCanBeRecord") + public static class OnViewEventLoadTriggerInitTask implements ComponentLoader.InitTask { + + protected final ViewDataLoadCoordinator facet; + protected final String loaderId; + protected final Class eventClass; + + public OnViewEventLoadTriggerInitTask(ViewDataLoadCoordinator facet, String loaderId, Class eventClass) { + this.facet = facet; + this.loaderId = loaderId; + this.eventClass = eventClass; + } + + @Override + public void execute(ComponentLoader.Context context) { + Preconditions.checkNotNullArgument(facet.getOwner()); + + ViewData viewData = ViewControllerUtils.getViewData(facet.getOwner()); + DataLoader loader = viewData.getLoader(loaderId); + facet.addOnViewEventLoadTrigger(loader, eventClass); + } + } +} From d5f2a3418a04605132bcb10142809344ff671234 Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Wed, 17 Dec 2025 10:14:49 +0400 Subject: [PATCH 09/28] UrlQUeryParameters refactoring --- .../genericfilter/GenericFilterAction.java | 8 ++++ .../GenericFilterMakeDefaultAction.java | 19 +++++--- .../genericfilter/GenericFilterSupport.java | 43 ++++++++++++++----- .../facet/impl/FragmentSettingsFacetImpl.java | 5 +-- .../impl/UrlQueryParametersFacetImpl.java | 17 ++++++-- .../facet/impl/ViewSettingsFacetImpl.java | 2 +- ...tractUrlQueryParametersBinderProvider.java | 9 ++++ ...ilterUrlQueryParametersBinderProvider.java | 3 +- ...ilterUrlQueryParametersBinderProvider.java | 2 +- ...ationUrlQueryParametersBinderProvider.java | 2 +- ...ilterUrlQueryParametersBinderProvider.java | 2 +- .../jmix/flowui/fragment/FragmentUtils.java | 19 ++++++++ .../flowui/sys/autowire/AutowireUtils.java | 4 ++ .../FragmentElementsDependencyInjector.java | 9 ++++ .../jmix/flowui/view/ViewControllerUtils.java | 4 +- .../jmix/flowui/view/impl/ViewFacetsImpl.java | 5 --- .../DataLoadCoordinatorFacetProvider.java | 10 +++-- .../xml/facet/DefaultFacetLoaderConfig.java | 4 +- .../UrlQueryParametersFacetProvider.java | 29 ++++++++++--- .../xml/facet/loader/AbstractFacetLoader.java | 14 ++++++ ...ragmentDataLoadCoordinatorFacetLoader.java | 16 +------ .../loader/UrlQueryParametersFacetLoader.java | 6 ++- .../loader/component/GenericFilterLoader.java | 36 +++++++--------- ...endarUrlQueryParametersBinderProvider.java | 3 +- 24 files changed, 183 insertions(+), 88 deletions(-) diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterAction.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterAction.java index f755058629..7ce1443f73 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterAction.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterAction.java @@ -17,6 +17,7 @@ package io.jmix.flowui.action.genericfilter; import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.Composite; import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.shared.Registration; import io.jmix.flowui.action.ExecutableAction; @@ -25,6 +26,8 @@ import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.component.genericfilter.GenericFilter; import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent; +import io.jmix.flowui.fragment.Fragment; +import io.jmix.flowui.fragment.FragmentUtils; import io.jmix.flowui.kit.action.ActionPerformedEvent; import io.jmix.flowui.kit.action.ActionVariant; import io.jmix.flowui.kit.component.KeyCombination; @@ -210,4 +213,9 @@ protected void checkTarget() { protected View getParentView() { return UiComponentUtils.getView(target); } + + protected Composite getParent() { + Fragment fragment = UiComponentUtils.findFragment(target); + return fragment != null ? fragment : UiComponentUtils.getView(target); + } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterMakeDefaultAction.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterMakeDefaultAction.java index 035958f006..b8bc931faa 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterMakeDefaultAction.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterMakeDefaultAction.java @@ -20,14 +20,13 @@ import io.jmix.core.common.util.Preconditions; import io.jmix.flowui.action.ActionType; import io.jmix.flowui.component.genericfilter.GenericFilter; +import io.jmix.flowui.component.genericfilter.GenericFilterSupport; import io.jmix.flowui.component.genericfilter.model.FilterConfigurationModel; import io.jmix.flowui.facet.SettingsFacet; -import io.jmix.flowui.facet.ViewSettingsFacet; -import io.jmix.flowui.facet.settings.ViewSettings; +import io.jmix.flowui.facet.settings.UiComponentSettings; import io.jmix.flowui.facet.settings.component.GenericFilterSettings; import io.jmix.flowui.icon.Icons; import io.jmix.flowui.kit.icon.JmixFontIcon; -import io.jmix.flowui.view.ViewControllerUtils; import org.springframework.beans.factory.annotation.Autowired; /** @@ -38,6 +37,8 @@ public class GenericFilterMakeDefaultAction extends GenericFilterAction settingsFacet = + filterSupport.getFacet(filterSupport.findCurrentOwner(target), SettingsFacet.class); Preconditions.checkNotNullArgument(settingsFacet, "The view doesn't contain %s", SettingsFacet.class.getSimpleName()); - ViewSettings settings = settingsFacet.getSettings(); + UiComponentSettings settings = settingsFacet.getSettings(); Preconditions.checkNotNullArgument(settings, "%s isn't attached to the view", SettingsFacet.class.getSimpleName()); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/GenericFilterSupport.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/GenericFilterSupport.java index 82a7355630..d2ff8fbc54 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/GenericFilterSupport.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/GenericFilterSupport.java @@ -17,6 +17,7 @@ package io.jmix.flowui.component.genericfilter; import com.google.common.collect.ImmutableSet; +import com.vaadin.flow.component.Composite; import io.jmix.core.Metadata; import io.jmix.core.annotation.Internal; import io.jmix.core.common.util.Preconditions; @@ -35,9 +36,12 @@ import io.jmix.flowui.component.genericfilter.model.FilterConfigurationModel; import io.jmix.flowui.component.genericfilter.model.GenericFilterConfigurationConverter; import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent; +import io.jmix.flowui.facet.Facet; import io.jmix.flowui.facet.SettingsFacet; -import io.jmix.flowui.facet.settings.ViewSettings; +import io.jmix.flowui.facet.settings.UiComponentSettings; import io.jmix.flowui.facet.settings.component.GenericFilterSettings; +import io.jmix.flowui.fragment.Fragment; +import io.jmix.flowui.fragment.FragmentUtils; import io.jmix.flowui.model.DataComponents; import io.jmix.flowui.model.InstanceContainer; import io.jmix.flowui.model.ViewData; @@ -242,6 +246,25 @@ public void refreshConfigurationDefaultValues(Configuration configuration) { } } + @Nullable + public Composite findCurrentOwner(GenericFilter genericFilter) { + Composite currentOwner = UiComponentUtils.findFragment(genericFilter); + if (currentOwner == null) { + currentOwner = UiComponentUtils.findView(genericFilter); + } + + return currentOwner; + } + + @Nullable + public T getFacet(@Nullable Composite currentOwner, Class facetClass) { + return currentOwner instanceof Fragment fragment + ? FragmentUtils.getFragmentFacet(fragment, facetClass) + : currentOwner instanceof View view + ? ViewControllerUtils.getViewFacet(view, facetClass) + : null; + } + protected Configuration initFilterConfiguration(String id, String name, Configuration existedConfiguration, @@ -372,18 +395,18 @@ protected FilterConfigurationModel loadFilterConfigurationModel(boolean isNewCon protected boolean isDefaultForMeFieldVisible(Configuration currentConfiguration, FilterConfigurationModel configurationModel) { - View currentView = UiComponentUtils.findView(currentConfiguration.getOwner()); - - if (currentConfiguration.getOwner().getId().isEmpty() || currentView == null) { + Composite currentOwner = findCurrentOwner(currentConfiguration.getOwner()); + if (currentConfiguration.getOwner().getId().isEmpty() || currentOwner == null) { return false; } - SettingsFacet settingsFacet = ViewControllerUtils.getViewFacet(currentView, SettingsFacet.class); + SettingsFacet settingsFacet = getFacet(currentOwner, SettingsFacet.class); + if (settingsFacet == null) { return false; } - ViewSettings settings = settingsFacet.getSettings(); + UiComponentSettings settings = settingsFacet.getSettings(); if (settings != null) { settings.getSettings(currentConfiguration.getOwner().getId().get(), GenericFilterSettings.class) @@ -423,8 +446,8 @@ protected FilterConfigurationModel getFilterConfigurationModel(Configuration con if (genericFilter.getId().isPresent() && dataConfigurationDetail.isDefaultForMeFieldVisible()) { - SettingsFacet settingsFacet = ViewControllerUtils - .getViewFacet(UiComponentUtils.getView(genericFilter), SettingsFacet.class); + Composite currentOwner = findCurrentOwner(genericFilter); + SettingsFacet settingsFacet = getFacet(currentOwner, SettingsFacet.class); if (settingsFacet != null) { saveFilterSettings(settingsFacet, genericFilter.getId().get(), configurationModel); @@ -451,10 +474,10 @@ protected FilterConfigurationModel createFilterConfigurationModel(Configuration return configurationModel; } - protected void saveFilterSettings(SettingsFacet settingsFacet, + protected void saveFilterSettings(SettingsFacet settingsFacet, String filterId, FilterConfigurationModel configurationModel) { - ViewSettings settings = settingsFacet.getSettings(); + UiComponentSettings settings = settingsFacet.getSettings(); Preconditions.checkNotNullArgument(settings, "%s is not attached to the view", SettingsFacet.class.getSimpleName()); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentSettingsFacetImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentSettingsFacetImpl.java index 9760707597..6deb32e906 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentSettingsFacetImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/FragmentSettingsFacetImpl.java @@ -18,7 +18,6 @@ import com.vaadin.flow.component.ComponentEvent; import com.vaadin.flow.component.DetachEvent; -import com.vaadin.flow.router.QueryParameters; import io.jmix.flowui.component.HasFacetsComponents; import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.facet.FacetOwner; @@ -33,7 +32,7 @@ import io.jmix.flowui.settings.UserSettingsService; import io.jmix.flowui.sys.autowire.ReflectionCacheManager; import io.jmix.flowui.view.View; -import org.jspecify.annotations.Nullable; +import org.springframework.lang.Nullable; public class FragmentSettingsFacetImpl extends AbstractSettingsFacet implements FragmentSettingsFacet { @@ -42,8 +41,6 @@ public class FragmentSettingsFacetImpl extends AbstractSettingsFacet owner) { + public & FacetOwner> void setOwner(@Nullable T owner) { super.setOwner(owner); if (queryParametersChangeRegistration != null) { @@ -79,11 +81,18 @@ public void setOwner(@Nullable View owner) { } if (owner != null && !UiComponentUtils.isComponentAttachedToDialog(owner)) { + View view; + if (owner instanceof View) { + view = (View) owner; + } else { + view = UiComponentUtils.getView(owner); + } + queryParametersChangeRegistration = ViewControllerUtils - .addQueryParametersChangeListener(owner, this::onViewQueryParametersChanged); + .addQueryParametersChangeListener(view, this::onViewQueryParametersChanged); initialComponentsStateRegistration = ViewControllerUtils - .addRestoreComponentsStateEventListener(owner, this::onRestoreInitialComponentsState); - postReadyRegistration = ViewControllerUtils.addPostReadyListener(owner, this::onPostReady); + .addRestoreComponentsStateEventListener(view, this::onRestoreInitialComponentsState); + postReadyRegistration = ViewControllerUtils.addPostReadyListener(view, this::onPostReady); } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewSettingsFacetImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewSettingsFacetImpl.java index 62bc245645..fa932b4ba7 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewSettingsFacetImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/ViewSettingsFacetImpl.java @@ -31,7 +31,7 @@ import io.jmix.flowui.sys.autowire.ReflectionCacheManager; import io.jmix.flowui.view.View; import io.jmix.flowui.view.ViewControllerUtils; -import org.jspecify.annotations.Nullable; +import org.springframework.lang.Nullable; /** * An implementation of the {@link SettingsFacet} interfacet that provides functionality for managing diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/AbstractUrlQueryParametersBinderProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/AbstractUrlQueryParametersBinderProvider.java index 74c0c4902a..606fd3c6b4 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/AbstractUrlQueryParametersBinderProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/AbstractUrlQueryParametersBinderProvider.java @@ -16,6 +16,8 @@ package io.jmix.flowui.facet.urlqueryparameters; +import com.vaadin.flow.component.Component; +import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.exception.GuiDevelopmentException; import io.jmix.flowui.facet.UrlQueryParametersFacet; import io.jmix.flowui.view.navigation.UrlParamSerializer; @@ -24,6 +26,7 @@ import org.dom4j.Element; import org.springframework.lang.Nullable; + /** * Base class for URL query binder providers. * @@ -52,4 +55,10 @@ protected String loadRequiredAttribute(Element element, String name, ComponentLo protected String loadAttribute(Element element, String name) { return loaderSupport.loadString(element, name).orElse(null); } + + protected static Component getComponent(Component owner, String componentId) { + return UiComponentUtils.findComponent(owner, componentId) + .orElseThrow(() -> new IllegalArgumentException( + String.format("Component with id '%s' not found", componentId))); + } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/DataGridFilterUrlQueryParametersBinderProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/DataGridFilterUrlQueryParametersBinderProvider.java index 5b00948cac..c82df81859 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/DataGridFilterUrlQueryParametersBinderProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/DataGridFilterUrlQueryParametersBinderProvider.java @@ -99,7 +99,8 @@ public void execute(ComponentLoader.Context context) { Preconditions.checkState(facet.getOwner() != null, "%s owner is not set", UrlQueryParametersFacet.NAME); - com.vaadin.flow.component.Component component = UiComponentUtils.getComponent(facet.getOwner(), componentId); + com.vaadin.flow.component.Component component = getComponent(facet.getOwner(), componentId); + if (!(component instanceof Grid)) { throw new IllegalStateException(String.format("'%s' is not a %s component", componentId, Grid.class.getSimpleName())); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/GenericFilterUrlQueryParametersBinderProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/GenericFilterUrlQueryParametersBinderProvider.java index 6c91f9d86c..983a390873 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/GenericFilterUrlQueryParametersBinderProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/GenericFilterUrlQueryParametersBinderProvider.java @@ -104,7 +104,7 @@ public void execute(ComponentLoader.Context context) { Preconditions.checkState(facet.getOwner() != null, "%s owner is not set", UrlQueryParametersFacet.NAME); - com.vaadin.flow.component.Component component = UiComponentUtils.getComponent(facet.getOwner(), componentId); + com.vaadin.flow.component.Component component = getComponent(facet.getOwner(), componentId); if (!(component instanceof GenericFilter)) { throw new IllegalStateException(String.format("'%s' is not a generic filter component", componentId)); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/PaginationUrlQueryParametersBinderProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/PaginationUrlQueryParametersBinderProvider.java index f0efa3fce2..23e524c5ac 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/PaginationUrlQueryParametersBinderProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/PaginationUrlQueryParametersBinderProvider.java @@ -90,7 +90,7 @@ public void execute(ComponentLoader.Context context) { Preconditions.checkState(facet.getOwner() != null, "%s owner is not set", UrlQueryParametersFacet.NAME); - com.vaadin.flow.component.Component component = UiComponentUtils.getComponent(facet.getOwner(), componentId); + com.vaadin.flow.component.Component component = getComponent(facet.getOwner(), componentId); if (!(component instanceof PaginationComponent)) { throw new IllegalStateException(String.format("'%s' is not a pagination component", componentId)); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/PropertyFilterUrlQueryParametersBinderProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/PropertyFilterUrlQueryParametersBinderProvider.java index fc3fd06e83..4f2a7d9ad1 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/PropertyFilterUrlQueryParametersBinderProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/urlqueryparameters/PropertyFilterUrlQueryParametersBinderProvider.java @@ -107,7 +107,7 @@ public void execute(ComponentLoader.Context context) { Preconditions.checkState(facet.getOwner() != null, "%s owner is not set", UrlQueryParametersFacet.NAME); - com.vaadin.flow.component.Component component = UiComponentUtils.getComponent(facet.getOwner(), componentId); + com.vaadin.flow.component.Component component = getComponent(facet.getOwner(), componentId); if (!(component instanceof PropertyFilter)) { throw new IllegalStateException(String.format("'%s' is not a property filter component", componentId)); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java index 9fc75120ec..7cb69cee51 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/fragment/FragmentUtils.java @@ -23,6 +23,7 @@ import com.vaadin.flow.server.Attributes; import io.jmix.core.DevelopmentException; import io.jmix.flowui.component.UiComponentUtils; +import io.jmix.flowui.facet.Facet; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationListener; import org.springframework.lang.Nullable; @@ -140,6 +141,24 @@ public static void setFragmentFacets(Fragment fragment, FragmentFacets fragme fragment.setFragmentFacets(fragmentFacets); } + /** + * Returns a specific type of {@link Facet} from the passed {@link Fragment}. + * + * @param fragment the {@link Fragment} from which the facet is to be retrieved; must not be {@code null} + * @param facetClass the class type of the facet to retrieve; must not be {@code null} + * @param the type of the facet + * @return the facet of the specified type if found; otherwise, {@code null} + */ + @SuppressWarnings("unchecked") + @Nullable + public static T getFragmentFacet(Fragment fragment, Class facetClass) { + return (T) fragment.getFragmentFacets() + .getFacets() + .filter(facet -> facetClass.isAssignableFrom(facet.getClass())) + .findAny() + .orElse(null); + } + /** * Gets the owner of the passed {@link Fragment}. * diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/sys/autowire/AutowireUtils.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/sys/autowire/AutowireUtils.java index ce57138556..d26cf038eb 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/sys/autowire/AutowireUtils.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/sys/autowire/AutowireUtils.java @@ -822,6 +822,10 @@ private static Facet findFacetCandidate(Composite component, String targetId) return ViewControllerUtils.getViewFacets(view).getFacet(targetId); } + if (component instanceof Fragment fragment) { + return FragmentUtils.getFragmentFacets(fragment).getFacet(targetId); + } + return null; } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/sys/autowire/FragmentElementsDependencyInjector.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/sys/autowire/FragmentElementsDependencyInjector.java index deb5b49324..87adf16c28 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/sys/autowire/FragmentElementsDependencyInjector.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/sys/autowire/FragmentElementsDependencyInjector.java @@ -21,6 +21,7 @@ import com.vaadin.flow.component.Composite; import io.jmix.core.JmixOrder; import io.jmix.flowui.component.UiComponentUtils; +import io.jmix.flowui.facet.Facet; import io.jmix.flowui.fragment.Fragment; import io.jmix.flowui.fragment.FragmentActions; import io.jmix.flowui.fragment.FragmentData; @@ -93,6 +94,14 @@ protected Object getAutowiredInstance(Class type, String name, Composite c .map(c -> ((HasActions) c)) .map(component -> component.getAction(elements[elements.length - 1])) .orElse(null); + } else if (Facet.class.isAssignableFrom(type)) { + String[] elements = parse(name); + if (elements.length != 1) { + throw new IllegalStateException("Can't autowire %s. Incorrect path: %s" + .formatted(Facet.class.getSimpleName(), name)); + } + + return FragmentUtils.getFragmentFacets(fragment).getFacet(name); } else if (MessageBundle.class == type) { return createMessageBundle(fragment); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/ViewControllerUtils.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/ViewControllerUtils.java index 9f2f35e572..1a6f8b1f91 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/ViewControllerUtils.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/ViewControllerUtils.java @@ -191,8 +191,8 @@ public static void setViewFacets(View view, ViewFacets viewFacets) { /** * Returns a specific type of {@link Facet} associated with the given {@link View}. * - * @param view the {@link View} from which the facet is to be retrieved; must not be null - * @param facetClass the class type of the facet to retrieve; must not be null + * @param view the {@link View} from which the facet is to be retrieved; must not be {@code null} + * @param facetClass the class type of the facet to retrieve; must not be {@code null} * @param the type of the facet * @return the facet of the specified type if found; otherwise, {@code null} */ diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/impl/ViewFacetsImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/impl/ViewFacetsImpl.java index ab01e03d7a..49eaffdae3 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/impl/ViewFacetsImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/view/impl/ViewFacetsImpl.java @@ -17,7 +17,6 @@ package io.jmix.flowui.view.impl; import com.vaadin.flow.component.Composite; -import io.jmix.flowui.facet.Facet; import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.facet.impl.AbstractFacetComponentsHolder; import io.jmix.flowui.view.View; @@ -26,8 +25,6 @@ import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; -import java.util.Set; - /** * Implementation of the {@link ViewFacets} interface. This class manages a collection of facets * associated with a specific {@link View}. @@ -38,8 +35,6 @@ public class ViewFacetsImpl extends AbstractFacetComponentsHolder implements Vie protected final View view; - protected Set facets = null; // lazily initialized linked hash set - public ViewFacetsImpl(View view) { this.view = view; } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java index 53e3afaa95..28040c9094 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java @@ -17,12 +17,14 @@ package io.jmix.flowui.xml.facet; import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.Composite; import io.jmix.core.common.util.Preconditions; import io.jmix.core.impl.QueryParamValuesManager; import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.exception.GuiDevelopmentException; import io.jmix.flowui.facet.DataLoadCoordinator; import io.jmix.flowui.facet.DataLoadCoordinator.LikeClause; +import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.facet.impl.ViewDataLoadCoordinatorImpl; import io.jmix.flowui.model.DataLoader; import io.jmix.flowui.model.InstanceContainer; @@ -72,18 +74,18 @@ public String getFacetTag() { } @Override - public void loadFromXml(DataLoadCoordinator facet, Element element, ComponentContext context) { - facet.setOwner(context.getView()); + public void loadFromXml(DataLoadCoordinator facet, Element element, ComponentLoader.Context context) { + facet.setOwner((Composite & FacetOwner) context.getOrigin()); loaderSupport.loadString(element, "id", facet::setId); loaderSupport.loadString(element, "containerPrefix", facet::setContainerPrefix); loaderSupport.loadString(element, "componentPrefix", facet::setComponentPrefix); for (Element loaderEl : element.elements("refresh")) { - loadRefresh(facet, context, loaderEl); + loadRefresh(facet, (ComponentContext) context, loaderEl); } - loadAuto(facet, element, context); + loadAuto(facet, element, (ComponentContext) context); } protected void loadAuto(DataLoadCoordinator facet, Element element, ComponentContext context) { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DefaultFacetLoaderConfig.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DefaultFacetLoaderConfig.java index f5922edfeb..e12820a895 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DefaultFacetLoaderConfig.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DefaultFacetLoaderConfig.java @@ -53,9 +53,7 @@ protected void initDefaultLoaders() { loaders.put("dataLoadCoordinator", ViewDataLoadCoordinatorFacetLoader.class); loaders.put("fragmentDataLoadCoordinator", FragmentDataLoadCoordinatorFacetLoader.class); - loaders.put("urlQueryParameters", ViewUrlQueryParametersFacetLoader.class); - loaders.put("fragmentUrlQueryParameters", FragmentUrlQueryParametersFacetLoader.class); - + loaders.put("urlQueryParameters", UrlQueryParametersFacetLoader.class); loaders.put("timer", TimerFacetLoader.class); loaders.put("settings", ViewSettingsFacetLoader.class); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/UrlQueryParametersFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/UrlQueryParametersFacetProvider.java index c1a5d74925..ea4ad5e86d 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/UrlQueryParametersFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/UrlQueryParametersFacetProvider.java @@ -16,17 +16,20 @@ package io.jmix.flowui.xml.facet; +import com.vaadin.flow.component.Composite; import io.jmix.flowui.exception.GuiDevelopmentException; +import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.facet.UrlQueryParametersFacet; import io.jmix.flowui.facet.impl.UrlQueryParametersFacetImpl; import io.jmix.flowui.facet.urlqueryparameters.UrlQueryParametersBinderProvider; import io.jmix.flowui.sys.registration.FacetRegistrationBuilder; import io.jmix.flowui.view.navigation.RouteSupport; -import io.jmix.flowui.xml.layout.ComponentLoader.ComponentContext; +import io.jmix.flowui.xml.layout.ComponentLoader; import io.jmix.flowui.xml.layout.support.LoaderSupport; import org.apache.commons.collections4.CollectionUtils; import org.dom4j.Element; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import java.util.Collections; import java.util.List; @@ -35,7 +38,7 @@ * @deprecated use {@link FacetRegistrationBuilder} instead */ @Deprecated(since = "3.0", forRemoval = true) -@org.springframework.stereotype.Component("flowui_UrlQueryParametersFacetProvider") +@Component("flowui_UrlQueryParametersFacetProvider") public class UrlQueryParametersFacetProvider implements FacetProvider { protected LoaderSupport loaderSupport; @@ -68,8 +71,8 @@ public String getFacetTag() { } @Override - public void loadFromXml(UrlQueryParametersFacet facet, Element element, ComponentContext context) { - facet.setOwner(context.getView()); + public void loadFromXml(UrlQueryParametersFacet facet, Element element, ComponentLoader.Context context) { + facet.setOwner((Composite & FacetOwner) context.getOrigin()); loaderSupport.loadString(element, "id", facet::setId); @@ -78,10 +81,10 @@ public void loadFromXml(UrlQueryParametersFacet facet, Element element, Componen } } - protected void loadBinder(UrlQueryParametersFacet facet, Element element, ComponentContext context) { + protected void loadBinder(UrlQueryParametersFacet facet, Element element, ComponentLoader.Context context) { for (UrlQueryParametersBinderProvider binderProvider : binderProviders) { if (binderProvider.supports(element)) { - binderProvider.load(facet, element, context); + binderProvider.load(facet, element, findHostViewContext(context)); return; } } @@ -90,4 +93,18 @@ protected void loadBinder(UrlQueryParametersFacet facet, Element element, Compon String.format("Unsupported nested element in '%s': %s", getFacetTag(), element.getName()), context); } + + protected ComponentLoader.ComponentContext findHostViewContext(ComponentLoader.Context fragmentContext) { + ComponentLoader.Context currentContext = fragmentContext; + while (currentContext.getParentContext() != null) { + currentContext = currentContext.getParentContext(); + } + + if (currentContext instanceof ComponentLoader.ComponentContext viewContext) { + return viewContext; + } + + throw new IllegalStateException("%s has no parent view context" + .formatted(fragmentContext.getClass().getSimpleName())); + } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractFacetLoader.java index 8089896ff7..f2fe3516d3 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractFacetLoader.java @@ -48,6 +48,20 @@ public void setApplicationContext(ApplicationContext applicationContext) { this.loaderSupport = applicationContext.getBean(LoaderSupport.class); } + protected ComponentLoader.ComponentContext findHostViewContext(ComponentLoader.Context fragmentContext) { + ComponentLoader.Context currentContext = fragmentContext; + while (currentContext.getParentContext() != null) { + currentContext = currentContext.getParentContext(); + } + + if (currentContext instanceof ComponentLoader.ComponentContext viewContext) { + return viewContext; + } + + throw new IllegalStateException("%s has no parent view context" + .formatted(fragmentContext.getClass().getSimpleName())); + } + @Override public Element getElement() { return element; diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java index 5a7e786008..c8195860d7 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java @@ -40,7 +40,7 @@ protected FragmentDataLoadCoordinator createFacet() { @Override protected ComponentLoader.ComponentContext getComponentContext() { - return findHostViewContext((ComponentLoader.FragmentContext) context); + return findHostViewContext(context); } protected void loadRefresh(FragmentDataLoadCoordinator facet, Element element) { @@ -79,20 +79,6 @@ protected void loadOnFragmentEvent(FragmentDataLoadCoordinator facet, String loa getComponentContext().addPreInitTask(new OnFragmentEventLoadTriggerInitTask(facet, loaderId, eventClass)); } - protected ComponentLoader.ComponentContext findHostViewContext(ComponentLoader.FragmentContext fragmentContext) { - ComponentLoader.Context currentContext = fragmentContext; - while (currentContext.getParentContext() != null) { - currentContext = currentContext.getParentContext(); - } - - if (currentContext instanceof ComponentLoader.ComponentContext viewContext) { - return viewContext; - } - - throw new IllegalStateException("%s has no parent view context" - .formatted(fragmentContext.getClass().getSimpleName())); - } - public static class OnFragmentEventLoadTriggerInitTask implements ComponentLoader.InitTask { protected final FragmentDataLoadCoordinator facet; diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/UrlQueryParametersFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/UrlQueryParametersFacetLoader.java index 36cc2772ae..74ff99bd55 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/UrlQueryParametersFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/UrlQueryParametersFacetLoader.java @@ -16,7 +16,9 @@ package io.jmix.flowui.xml.facet.loader; +import com.vaadin.flow.component.Composite; import io.jmix.flowui.exception.GuiDevelopmentException; +import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.facet.UrlQueryParametersFacet; import io.jmix.flowui.facet.urlqueryparameters.UrlQueryParametersBinderProvider; import io.jmix.flowui.impl.FacetsImpl; @@ -33,7 +35,7 @@ public class UrlQueryParametersFacetLoader extends AbstractFacetLoader & FacetOwner) context.getOrigin()); return facet; } @@ -59,7 +61,7 @@ public void loadFacet() { protected void loadBinder(Element element) { for (UrlQueryParametersBinderProvider binderProvider : getBinderProviders()) { if (binderProvider.supports(element)) { - binderProvider.load(resultFacet, element, context); + binderProvider.load(resultFacet, element, findHostViewContext(context)); return; } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/component/GenericFilterLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/component/GenericFilterLoader.java index d7dc5071d8..ddeba63a97 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/component/GenericFilterLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/component/GenericFilterLoader.java @@ -18,6 +18,7 @@ import com.vaadin.flow.component.Component; import com.vaadin.flow.component.ComponentUtil; +import com.vaadin.flow.component.Composite; import io.jmix.flowui.action.genericfilter.GenericFilterAction; import io.jmix.flowui.component.filter.FilterComponent; import io.jmix.flowui.component.filter.SingleFilterComponent; @@ -25,15 +26,17 @@ import io.jmix.flowui.component.genericfilter.Configuration; import io.jmix.flowui.component.genericfilter.FilterUtils; import io.jmix.flowui.component.genericfilter.GenericFilter; +import io.jmix.flowui.component.genericfilter.GenericFilterSupport; import io.jmix.flowui.component.genericfilter.configuration.DesignTimeConfiguration; import io.jmix.flowui.component.genericfilter.inspector.FilterPropertiesInspector; import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent; import io.jmix.flowui.exception.GuiDevelopmentException; import io.jmix.flowui.facet.DataLoadCoordinator; +import io.jmix.flowui.facet.FacetOwner; +import io.jmix.flowui.fragment.Fragment; import io.jmix.flowui.kit.action.Action; import io.jmix.flowui.model.DataLoader; import io.jmix.flowui.view.View; -import io.jmix.flowui.view.ViewControllerUtils; import io.jmix.flowui.xml.layout.ComponentLoader; import io.jmix.flowui.xml.layout.inittask.AbstractInitTask; import io.jmix.flowui.xml.layout.loader.AbstractComponentLoader; @@ -45,6 +48,7 @@ public class GenericFilterLoader extends AbstractComponentLoader { protected ActionLoaderSupport actionLoaderSupport; + protected GenericFilterSupport genericFilterSupport; @Override protected GenericFilter createComponent() { @@ -262,14 +266,23 @@ protected ActionLoaderSupport getActionLoaderSupport() { return actionLoaderSupport; } + protected GenericFilterSupport getGenericFilterSupport() { + if (genericFilterSupport == null) { + genericFilterSupport = applicationContext.getBean(GenericFilterSupport.class, context); + } + + return genericFilterSupport; + } + protected void applyFilterIfNeeded() { getContext().addInitTask(new AbstractInitTask() { @Override public void execute(Context context) { - View view = findParentView(context); + Composite parent = getGenericFilterSupport().findCurrentOwner(resultComponent); DataLoadCoordinator dataLoadCoordinator = - ViewControllerUtils.getViewFacet(view, DataLoadCoordinator.class); + getGenericFilterSupport().getFacet(parent, DataLoadCoordinator.class); + if (dataLoadCoordinator == null || dataLoadCoordinator.getTriggers().isEmpty()) { List filterComponents = @@ -291,21 +304,4 @@ public void execute(Context context) { } }); } - - protected View findParentView(Context context) { - - Component origin; - Context parentContext = context; - - do { - origin = parentContext.getOrigin(); - parentContext = parentContext.getParentContext(); - } while (!(origin instanceof View) && parentContext != null); - - if (origin instanceof View view) { - return view; - } else { - throw new GuiDevelopmentException("Cannot find parent " + View.class.getSimpleName(), context); - } - } } diff --git a/jmix-fullcalendar/fullcalendar-flowui/src/main/java/io/jmix/fullcalendarflowui/facet/urlqueryparameters/FullCalendarUrlQueryParametersBinderProvider.java b/jmix-fullcalendar/fullcalendar-flowui/src/main/java/io/jmix/fullcalendarflowui/facet/urlqueryparameters/FullCalendarUrlQueryParametersBinderProvider.java index c4123da3c2..019eae7a6f 100644 --- a/jmix-fullcalendar/fullcalendar-flowui/src/main/java/io/jmix/fullcalendarflowui/facet/urlqueryparameters/FullCalendarUrlQueryParametersBinderProvider.java +++ b/jmix-fullcalendar/fullcalendar-flowui/src/main/java/io/jmix/fullcalendarflowui/facet/urlqueryparameters/FullCalendarUrlQueryParametersBinderProvider.java @@ -18,7 +18,6 @@ import com.google.common.base.Preconditions; import com.vaadin.flow.component.Component; -import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.facet.UrlQueryParametersFacet; import io.jmix.flowui.facet.urlqueryparameters.AbstractUrlQueryParametersBinderProvider; import io.jmix.flowui.view.navigation.UrlParamSerializer; @@ -81,7 +80,7 @@ public void execute(ComponentLoader.Context context) { Preconditions.checkState(facet.getOwner() != null, "%s owner is not set", UrlQueryParametersFacet.NAME); - Component component = UiComponentUtils.getComponent(facet.getOwner(), componentId); + Component component = getComponent(facet.getOwner(), componentId); if (!(component instanceof FullCalendar)) { throw new IllegalStateException(String.format("'%s' is not a '%s' component", componentId, FullCalendar.class.getSimpleName())); From 33a4eeef87338f40dccbd44f13f9d3e4f80cf521 Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Wed, 17 Dec 2025 11:32:59 +0400 Subject: [PATCH 10/28] Revert breaking changes --- .../facet/DynAttrFacetProvider.java | 17 ++++----- .../xml/facet/loader/DynAttrFacetLoader.java | 5 ++- .../flowui/kit/meta/facet/StudioFacets.java | 37 ++++++++++++++++++- .../AbstractOnEventLoadTrigger.java | 2 +- .../io/jmix/flowui/facet/impl/TimerImpl.java | 4 +- .../DataLoadCoordinatorFacetProvider.java | 10 ++--- .../io/jmix/flowui/xml/facet/FacetLoader.java | 6 +-- .../jmix/flowui/xml/facet/FacetProvider.java | 4 +- .../xml/facet/SettingsFacetProvider.java | 4 +- .../flowui/xml/facet/TimerFacetProvider.java | 4 +- .../UrlQueryParametersFacetProvider.java | 29 +++------------ ...bstractDataLoadCoordinatorFacetLoader.java | 4 +- .../loader/AbstractSettingsFacetLoader.java | 4 +- .../loader/UrlQueryParametersFacetLoader.java | 5 ++- .../io/jmix/flowui/view/fragment.xsd | 4 +- 15 files changed, 77 insertions(+), 62 deletions(-) diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetProvider.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetProvider.java index d9540360e5..6f5f7985a1 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetProvider.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/facet/DynAttrFacetProvider.java @@ -16,12 +16,12 @@ package io.jmix.dynattrflowui.facet; -import com.vaadin.flow.component.Component; import io.jmix.core.annotation.Internal; import io.jmix.dynattrflowui.DynAttrEmbeddingStrategies; import io.jmix.dynattrflowui.impl.AttributeDefaultValues; import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.sys.registration.FacetRegistrationBuilder; +import io.jmix.flowui.view.View; import io.jmix.flowui.xml.facet.FacetProvider; import io.jmix.flowui.xml.layout.ComponentLoader; import org.dom4j.Element; @@ -37,8 +37,7 @@ public class DynAttrFacetProvider implements FacetProvider { protected final DynAttrEmbeddingStrategies embeddingStrategies; protected final AttributeDefaultValues attributeDefaultValues; - public DynAttrFacetProvider(DynAttrEmbeddingStrategies embeddingStrategies, - AttributeDefaultValues attributeDefaultValues) { + public DynAttrFacetProvider(DynAttrEmbeddingStrategies embeddingStrategies, AttributeDefaultValues attributeDefaultValues) { this.embeddingStrategies = embeddingStrategies; this.attributeDefaultValues = attributeDefaultValues; } @@ -59,11 +58,11 @@ public String getFacetTag() { } @Override - public void loadFromXml(DynAttrFacet facet, Element element, ComponentLoader.Context context) { - Component owner = context.getOrigin(); - // TODO: kd, check to getting all components in case of fragment - context.addInitTask(__ -> - UiComponentUtils.traverseComponents(owner, component -> - embeddingStrategies.embedAttributes(component, owner))); + public void loadFromXml(DynAttrFacet facet, Element element, ComponentLoader.ComponentContext context) { + View view = context.getView(); + context.addInitTask(__ -> { + UiComponentUtils.traverseComponents(view, component -> + embeddingStrategies.embedAttributes(component, view)); + }); } } diff --git a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/xml/facet/loader/DynAttrFacetLoader.java b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/xml/facet/loader/DynAttrFacetLoader.java index 86a2770166..e49075dc51 100644 --- a/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/xml/facet/loader/DynAttrFacetLoader.java +++ b/jmix-dynattr/dynattr-flowui/src/main/java/io/jmix/dynattrflowui/xml/facet/loader/DynAttrFacetLoader.java @@ -23,6 +23,7 @@ import io.jmix.flowui.impl.FacetsImpl; import io.jmix.flowui.xml.facet.FacetProvider; import io.jmix.flowui.xml.facet.loader.AbstractFacetLoader; +import io.jmix.flowui.xml.layout.ComponentLoader; public class DynAttrFacetLoader extends AbstractFacetLoader { @@ -39,8 +40,8 @@ public void loadFacet() { if (facets instanceof FacetsImpl facetsImpl) { FacetProvider provider = facetsImpl.getProvider(DynAttrFacet.class); - if (provider != null) { - provider.loadFromXml(resultFacet, element, context); + if (provider != null && context instanceof ComponentLoader.ComponentContext componentContext) { + provider.loadFromXml(resultFacet, element, componentContext); return; } } diff --git a/jmix-flowui/flowui-kit/src/main/java/io/jmix/flowui/kit/meta/facet/StudioFacets.java b/jmix-flowui/flowui-kit/src/main/java/io/jmix/flowui/kit/meta/facet/StudioFacets.java index a5ac67716e..2da0fa1551 100644 --- a/jmix-flowui/flowui-kit/src/main/java/io/jmix/flowui/kit/meta/facet/StudioFacets.java +++ b/jmix-flowui/flowui-kit/src/main/java/io/jmix/flowui/kit/meta/facet/StudioFacets.java @@ -26,7 +26,7 @@ public interface StudioFacets { @StudioFacet( name = "DataLoadCoordinator", - classFqn = "io.jmix.flowui.facet.DataLoadCoordinator", + classFqn = "io.jmix.flowui.facet.ViewDataLoadCoordinator", category = "Facets", xmlElement = "dataLoadCoordinator", icon = "io/jmix/flowui/kit/meta/icon/facet/dataLoadCoordinator.svg", @@ -43,6 +43,25 @@ public interface StudioFacets { ) void dataLoadCoordinator(); + @StudioFacet( + name = "DataLoadCoordinator", + classFqn = "io.jmix.flowui.facet.FragmentDataLoadCoordinator", + category = "Facets", + xmlElement = "fragmentDataLoadCoordinator", + icon = "io/jmix/flowui/kit/meta/icon/facet/dataLoadCoordinator.svg", + documentationLink = "%VERSION%/flow-ui/facets/dataLoadCoordinator.html", + properties = { + @StudioProperty(xmlAttribute = "id", type = StudioPropertyType.COMPONENT_ID), + @StudioProperty(xmlAttribute = "auto", type = StudioPropertyType.BOOLEAN, + defaultValue = "false", initialValue = "true"), + @StudioProperty(xmlAttribute = "componentPrefix", type = StudioPropertyType.STRING, + defaultValue = "component_"), + @StudioProperty(xmlAttribute = "containerPrefix", type = StudioPropertyType.STRING, + defaultValue = "container_"), + } + ) + void fragmentDataLoadCoordinator(); + @StudioFacet( name = "UrlQueryParameters", classFqn = "io.jmix.flowui.facet.UrlQueryParametersFacet", @@ -74,7 +93,7 @@ public interface StudioFacets { @StudioFacet( name = "Settings", - classFqn = "io.jmix.flowui.facet.SettingsFacet", + classFqn = "io.jmix.flowui.facet.ViewSettingsFacet", category = "Facets", xmlElement = "settings", icon = "io/jmix/flowui/kit/meta/icon/facet/settings.svg", @@ -85,4 +104,18 @@ public interface StudioFacets { } ) void settings(); + + @StudioFacet( + name = "Settings", + classFqn = "io.jmix.flowui.facet.FragmentSettingsFacet", + category = "Facets", + xmlElement = "fragmentSettings", + icon = "io/jmix/flowui/kit/meta/icon/facet/settings.svg", + properties = { + @StudioProperty(xmlAttribute = "id", type = StudioPropertyType.COMPONENT_ID), + @StudioProperty(xmlAttribute = "auto", type = StudioPropertyType.BOOLEAN, + defaultValue = "false", initialValue = "true"), + } + ) + void fragmentSettings(); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/AbstractOnEventLoadTrigger.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/AbstractOnEventLoadTrigger.java index 3789230726..7e722b0649 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/AbstractOnEventLoadTrigger.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/dataloadcoordinator/AbstractOnEventLoadTrigger.java @@ -29,7 +29,7 @@ * specified {@link FacetOwner FacetOwner's} event and invokes the associated {@link DataLoader} when the event occurs, * triggering data loading operations. */ -public class AbstractOnEventLoadTrigger implements DataLoadCoordinator.Trigger { +public abstract class AbstractOnEventLoadTrigger implements DataLoadCoordinator.Trigger { protected final DataLoader loader; diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/TimerImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/TimerImpl.java index bc73c5e218..19b448d4e6 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/TimerImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/impl/TimerImpl.java @@ -112,7 +112,7 @@ public & FacetOwner> void setOwner(@Nullable T owner) { registerInOwner(owner); } else { if (this.owner != null) { - unregisterInView(this.owner); + unregisterInOwner(this.owner); } super.setOwner(null); } @@ -148,7 +148,7 @@ protected void detachTimer(Composite owner) { timerImpl.getElement().removeFromTree(); } - protected void unregisterInView(Composite owner) { + protected void unregisterInOwner(Composite owner) { detachTimer(owner); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java index 28040c9094..53e3afaa95 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/DataLoadCoordinatorFacetProvider.java @@ -17,14 +17,12 @@ package io.jmix.flowui.xml.facet; import com.vaadin.flow.component.Component; -import com.vaadin.flow.component.Composite; import io.jmix.core.common.util.Preconditions; import io.jmix.core.impl.QueryParamValuesManager; import io.jmix.flowui.component.UiComponentUtils; import io.jmix.flowui.exception.GuiDevelopmentException; import io.jmix.flowui.facet.DataLoadCoordinator; import io.jmix.flowui.facet.DataLoadCoordinator.LikeClause; -import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.facet.impl.ViewDataLoadCoordinatorImpl; import io.jmix.flowui.model.DataLoader; import io.jmix.flowui.model.InstanceContainer; @@ -74,18 +72,18 @@ public String getFacetTag() { } @Override - public void loadFromXml(DataLoadCoordinator facet, Element element, ComponentLoader.Context context) { - facet.setOwner((Composite & FacetOwner) context.getOrigin()); + public void loadFromXml(DataLoadCoordinator facet, Element element, ComponentContext context) { + facet.setOwner(context.getView()); loaderSupport.loadString(element, "id", facet::setId); loaderSupport.loadString(element, "containerPrefix", facet::setContainerPrefix); loaderSupport.loadString(element, "componentPrefix", facet::setComponentPrefix); for (Element loaderEl : element.elements("refresh")) { - loadRefresh(facet, (ComponentContext) context, loaderEl); + loadRefresh(facet, context, loaderEl); } - loadAuto(facet, element, (ComponentContext) context); + loadAuto(facet, element, context); } protected void loadAuto(DataLoadCoordinator facet, Element element, ComponentContext context) { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java index c255d264bb..4344e18d46 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java @@ -75,9 +75,9 @@ protected void setApplicationContext(ApplicationContext applicationContext) { */ public Facet load(Element element, ComponentLoader.Context context) { io.jmix.flowui.xml.facet.loader.FacetLoader facetLoader = getLoader(element, context); - if (facetLoader == null) { + if (facetLoader == null && context instanceof ComponentLoader.ComponentContext componentContext) { // fallback - return _load(element, context); + return _load(element, componentContext); } facetLoader.initFacet(); @@ -128,7 +128,7 @@ protected io.jmix.flowui.xml.facet.loader.FacetLoader initLoader( // for backward compatibility @Deprecated(since = "3.0", forRemoval = true) @SuppressWarnings({"unchecked", "rawtypes"}) - protected Facet _load(Element element, ComponentLoader.Context context) { + protected Facet _load(Element element, ComponentLoader.ComponentContext context) { Collection> facetProviders = registrations.get(element.getName()); FacetProvider facetProvider = beanSelector.selectFrom(facetProviders); if (facetProvider == null) { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetProvider.java index 317dc13f1a..43ee4532bb 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetProvider.java @@ -18,7 +18,7 @@ import io.jmix.flowui.facet.Facet; import io.jmix.flowui.sys.registration.FacetRegistrationBuilder; -import io.jmix.flowui.xml.layout.ComponentLoader; +import io.jmix.flowui.xml.layout.ComponentLoader.ComponentContext; import org.dom4j.Element; /** @@ -53,5 +53,5 @@ public interface FacetProvider { * @param element XML element * @param context loading context */ - void loadFromXml(T facet, Element element, ComponentLoader.Context context); + void loadFromXml(T facet, Element element, ComponentContext context); } \ No newline at end of file diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java index fb8f2d815c..8e3529cb10 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/SettingsFacetProvider.java @@ -83,7 +83,7 @@ public String getFacetTag() { } @Override - public void loadFromXml(SettingsFacet facet, Element element, ComponentLoader.Context context) { + public void loadFromXml(SettingsFacet facet, Element element, ComponentLoader.ComponentContext context) { loaderSupport.loadString(element, "id", facet::setId); loaderSupport.loadBoolean(element, "auto", facet::setAuto); @@ -112,7 +112,7 @@ protected List filterExcludedIds(Map components) { .collect(Collectors.toList()); } - protected Map loadComponents(ComponentLoader.Context context, Element root) { + protected Map loadComponents(ComponentLoader.ComponentContext context, Element root) { List components = root.elements("component"); if (CollectionUtils.isEmpty(components)) { return Collections.emptyMap(); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/TimerFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/TimerFacetProvider.java index 8b1e9996a0..f1c262dfd4 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/TimerFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/TimerFacetProvider.java @@ -54,11 +54,11 @@ public String getFacetTag() { } @Override - public void loadFromXml(Timer timer, Element element, ComponentLoader.Context context) { + public void loadFromXml(Timer timer, Element element, ComponentLoader.ComponentContext context) { loadTimer(timer, element, context); } - protected void loadTimer(Timer timer, Element element, ComponentLoader.Context context) { + protected void loadTimer(Timer timer, Element element, ComponentLoader.ComponentContext context) { String id = loaderSupport.loadString(element, "id") .orElseThrow(() -> new IllegalStateException("Timer id must be defined")); timer.setId(id); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/UrlQueryParametersFacetProvider.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/UrlQueryParametersFacetProvider.java index ea4ad5e86d..c1a5d74925 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/UrlQueryParametersFacetProvider.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/UrlQueryParametersFacetProvider.java @@ -16,20 +16,17 @@ package io.jmix.flowui.xml.facet; -import com.vaadin.flow.component.Composite; import io.jmix.flowui.exception.GuiDevelopmentException; -import io.jmix.flowui.facet.FacetOwner; import io.jmix.flowui.facet.UrlQueryParametersFacet; import io.jmix.flowui.facet.impl.UrlQueryParametersFacetImpl; import io.jmix.flowui.facet.urlqueryparameters.UrlQueryParametersBinderProvider; import io.jmix.flowui.sys.registration.FacetRegistrationBuilder; import io.jmix.flowui.view.navigation.RouteSupport; -import io.jmix.flowui.xml.layout.ComponentLoader; +import io.jmix.flowui.xml.layout.ComponentLoader.ComponentContext; import io.jmix.flowui.xml.layout.support.LoaderSupport; import org.apache.commons.collections4.CollectionUtils; import org.dom4j.Element; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import java.util.Collections; import java.util.List; @@ -38,7 +35,7 @@ * @deprecated use {@link FacetRegistrationBuilder} instead */ @Deprecated(since = "3.0", forRemoval = true) -@Component("flowui_UrlQueryParametersFacetProvider") +@org.springframework.stereotype.Component("flowui_UrlQueryParametersFacetProvider") public class UrlQueryParametersFacetProvider implements FacetProvider { protected LoaderSupport loaderSupport; @@ -71,8 +68,8 @@ public String getFacetTag() { } @Override - public void loadFromXml(UrlQueryParametersFacet facet, Element element, ComponentLoader.Context context) { - facet.setOwner((Composite & FacetOwner) context.getOrigin()); + public void loadFromXml(UrlQueryParametersFacet facet, Element element, ComponentContext context) { + facet.setOwner(context.getView()); loaderSupport.loadString(element, "id", facet::setId); @@ -81,10 +78,10 @@ public void loadFromXml(UrlQueryParametersFacet facet, Element element, Componen } } - protected void loadBinder(UrlQueryParametersFacet facet, Element element, ComponentLoader.Context context) { + protected void loadBinder(UrlQueryParametersFacet facet, Element element, ComponentContext context) { for (UrlQueryParametersBinderProvider binderProvider : binderProviders) { if (binderProvider.supports(element)) { - binderProvider.load(facet, element, findHostViewContext(context)); + binderProvider.load(facet, element, context); return; } } @@ -93,18 +90,4 @@ protected void loadBinder(UrlQueryParametersFacet facet, Element element, Compon String.format("Unsupported nested element in '%s': %s", getFacetTag(), element.getName()), context); } - - protected ComponentLoader.ComponentContext findHostViewContext(ComponentLoader.Context fragmentContext) { - ComponentLoader.Context currentContext = fragmentContext; - while (currentContext.getParentContext() != null) { - currentContext = currentContext.getParentContext(); - } - - if (currentContext instanceof ComponentLoader.ComponentContext viewContext) { - return viewContext; - } - - throw new IllegalStateException("%s has no parent view context" - .formatted(fragmentContext.getClass().getSimpleName())); - } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java index 0a0b84f898..115174169d 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java @@ -40,8 +40,8 @@ public void loadFacet() { if (facets instanceof FacetsImpl facetsImpl) { FacetProvider provider = facetsImpl.getProvider(DataLoadCoordinator.class); - if (provider != null) { - provider.loadFromXml(resultFacet, element, context); + if (provider != null && context instanceof ComponentLoader.ComponentContext componentContext) { + provider.loadFromXml(resultFacet, element, componentContext); return; } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractSettingsFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractSettingsFacetLoader.java index dbf58541c8..f099da9047 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractSettingsFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractSettingsFacetLoader.java @@ -39,8 +39,8 @@ public void loadFacet() { if (facets instanceof FacetsImpl facetsImpl) { FacetProvider provider = facetsImpl.getProvider(SettingsFacet.class); - if (provider != null) { - provider.loadFromXml(resultFacet, element, context); + if (provider != null && context instanceof ComponentLoader.ComponentContext componentContext) { + provider.loadFromXml(resultFacet, element, componentContext); return; } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/UrlQueryParametersFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/UrlQueryParametersFacetLoader.java index 74ff99bd55..a065b03cf9 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/UrlQueryParametersFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/UrlQueryParametersFacetLoader.java @@ -23,6 +23,7 @@ import io.jmix.flowui.facet.urlqueryparameters.UrlQueryParametersBinderProvider; import io.jmix.flowui.impl.FacetsImpl; import io.jmix.flowui.xml.facet.FacetProvider; +import io.jmix.flowui.xml.layout.ComponentLoader; import org.dom4j.Element; import org.springframework.core.OrderComparator; @@ -45,8 +46,8 @@ public void loadFacet() { if (facets instanceof FacetsImpl facetsImpl) { FacetProvider provider = facetsImpl.getProvider(UrlQueryParametersFacet.class); - if (provider != null) { - provider.loadFromXml(resultFacet, element, context); + if (provider != null && context instanceof ComponentLoader.ComponentContext componentContext) { + provider.loadFromXml(resultFacet, element, componentContext); return; } } diff --git a/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd b/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd index debac70387..bf3e8614ec 100644 --- a/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd +++ b/jmix-flowui/flowui/src/main/resources/io/jmix/flowui/view/fragment.xsd @@ -40,8 +40,8 @@ - - + + From 2116f87b73dc58a0e7ebff7fbfe9bf5fcb2f1033 Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Wed, 17 Dec 2025 11:51:46 +0400 Subject: [PATCH 11/28] Minor JavaDoc fixes --- .../io/jmix/flowui/facet/FragmentDataLoadCoordinator.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentDataLoadCoordinator.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentDataLoadCoordinator.java index 1d629d7d3d..f7591348eb 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentDataLoadCoordinator.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/facet/FragmentDataLoadCoordinator.java @@ -16,8 +16,14 @@ package io.jmix.flowui.facet; +import io.jmix.flowui.fragment.Fragment; import io.jmix.flowui.model.DataLoader; +/** + * A non-visual component for coordinating data loading operations in a {@link Fragment}. + * Manages the association of data loaders to various triggers, such as fragment + * events, container item changes, or component value changes. + */ public interface FragmentDataLoadCoordinator extends DataLoadCoordinator { void addOnFragmentEventLoadTrigger(DataLoader loader, Class eventClass); From 15a2fa87920169fdee114e8e3e3ab4956cda559a Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Wed, 17 Dec 2025 11:53:59 +0400 Subject: [PATCH 12/28] Revert breaking changes --- .../io/jmix/flowui/xml/facet/loader/TimerFacetLoader.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/TimerFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/TimerFacetLoader.java index 0bda439b34..7dc4ee919f 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/TimerFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/TimerFacetLoader.java @@ -20,6 +20,7 @@ import io.jmix.flowui.facet.Timer; import io.jmix.flowui.impl.FacetsImpl; import io.jmix.flowui.xml.facet.FacetProvider; +import io.jmix.flowui.xml.layout.ComponentLoader; import org.dom4j.Element; import java.util.Objects; @@ -37,8 +38,8 @@ public void loadFacet() { if (facets instanceof FacetsImpl facetsImpl) { FacetProvider provider = facetsImpl.getProvider(Timer.class); - if (provider != null) { - provider.loadFromXml(resultFacet, element, context); + if (provider != null && context instanceof ComponentLoader.ComponentContext componentContext) { + provider.loadFromXml(resultFacet, element, componentContext); return; } } From 1badca5b6d68fed417491c44a6fe5f9f0776b6ac Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Thu, 18 Dec 2025 09:44:53 +0400 Subject: [PATCH 13/28] Add `PreInitTask` for the ComponentLoader.Context --- .../io/jmix/flowui/impl/FragmentsImpl.java | 6 ++++ .../io/jmix/flowui/xml/facet/FacetLoader.java | 5 +-- ...bstractDataLoadCoordinatorFacetLoader.java | 8 ++--- ...ragmentDataLoadCoordinatorFacetLoader.java | 7 +--- .../ViewDataLoadCoordinatorFacetLoader.java | 8 +---- .../flowui/xml/layout/ComponentLoader.java | 32 +++++++++---------- .../loader/AbstractComponentLoader.java | 7 ---- .../layout/loader/AbstractLoaderContext.java | 21 +++++++++++- .../layout/loader/ComponentLoaderContext.java | 20 ------------ 9 files changed, 50 insertions(+), 64 deletions(-) diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FragmentsImpl.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FragmentsImpl.java index d188a456f7..6958da84eb 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FragmentsImpl.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/impl/FragmentsImpl.java @@ -144,6 +144,12 @@ protected String getFullOriginId(ComponentLoader.Context hostContext, Fragment fragment, @Nullable FragmentLoaderContext context) { + if (context != null) { + // Pre InitTasks must be executed before DependencyManager + // invocation to have precedence over @Subscribe methods + context.executePreInitTasks(); + } + autowireFragment(fragment); // Init tasks should be executed after fragment is autowired, diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java index 4344e18d46..6eb8a42cdf 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/FacetLoader.java @@ -75,9 +75,10 @@ protected void setApplicationContext(ApplicationContext applicationContext) { */ public Facet load(Element element, ComponentLoader.Context context) { io.jmix.flowui.xml.facet.loader.FacetLoader facetLoader = getLoader(element, context); - if (facetLoader == null && context instanceof ComponentLoader.ComponentContext componentContext) { + if (facetLoader == null) { // fallback - return _load(element, componentContext); + // we can cast safety because loaders exist for the fragment facets + return _load(element, (ComponentLoader.ComponentContext) context); } facetLoader.initFacet(); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java index 115174169d..bacb2b8abc 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/AbstractDataLoadCoordinatorFacetLoader.java @@ -58,7 +58,7 @@ protected void loadAuto(Element element) { loaderSupport.loadBoolean(element, "auto") .ifPresent(auto -> { if (auto) { - getComponentContext().addPreInitTask(new AutoConfigurationInitTask(resultFacet)); + context.addPreInitTask(new AutoConfigurationInitTask(resultFacet)); } }); } @@ -75,7 +75,7 @@ protected void loadOnContainerItemChanged(T facet, String loaderId, Element elem String container = loadEventRequiredAttribute(element, "container"); String param = loadParam(element); - getComponentContext().addPreInitTask(new OnContainerItemChangedLoadTriggerInitTask(facet, loaderId, container, param)); + context.addPreInitTask(new OnContainerItemChangedLoadTriggerInitTask(facet, loaderId, container, param)); } protected void loadOnComponentValueChanged(T facet, String loaderId, Element element) { @@ -84,7 +84,7 @@ protected void loadOnComponentValueChanged(T facet, String loaderId, Element ele String param = loadParam(element); DataLoadCoordinator.LikeClause likeClause = loadLikeClause(element); - getComponentContext().addPreInitTask(new OnComponentValueChangedLoadTriggerInitTask( + context.addPreInitTask(new OnComponentValueChangedLoadTriggerInitTask( facet, loaderId, component, param, likeClause)); } @@ -106,8 +106,6 @@ protected DataLoadCoordinator.LikeClause loadLikeClause(Element element) { .orElse(DataLoadCoordinator.LikeClause.NONE); } - protected abstract ComponentLoader.ComponentContext getComponentContext(); - @SuppressWarnings("ClassCanBeRecord") public static class OnContainerItemChangedLoadTriggerInitTask implements ComponentLoader.InitTask { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java index c8195860d7..1b42512592 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/FragmentDataLoadCoordinatorFacetLoader.java @@ -38,11 +38,6 @@ protected FragmentDataLoadCoordinator createFacet() { return facet; } - @Override - protected ComponentLoader.ComponentContext getComponentContext() { - return findHostViewContext(context); - } - protected void loadRefresh(FragmentDataLoadCoordinator facet, Element element) { String loaderId = loaderSupport.loadString(element, "loader") .orElseThrow(() -> @@ -76,7 +71,7 @@ protected void loadOnFragmentEvent(FragmentDataLoadCoordinator facet, String loa " value: " + type, context); } - getComponentContext().addPreInitTask(new OnFragmentEventLoadTriggerInitTask(facet, loaderId, eventClass)); + context.addPreInitTask(new OnFragmentEventLoadTriggerInitTask(facet, loaderId, eventClass)); } public static class OnFragmentEventLoadTriggerInitTask implements ComponentLoader.InitTask { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewDataLoadCoordinatorFacetLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewDataLoadCoordinatorFacetLoader.java index a6ee7f2393..94d9c7369e 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewDataLoadCoordinatorFacetLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/facet/loader/ViewDataLoadCoordinatorFacetLoader.java @@ -68,13 +68,7 @@ protected void loadOnViewEvent(ViewDataLoadCoordinator facet, String loaderId, E " value: " + type, context); }; - getComponentContext().addPreInitTask(new OnViewEventLoadTriggerInitTask(facet, loaderId, eventClass)); - } - - @Override - protected ComponentLoader.ComponentContext getComponentContext() { - // should be component context in case on view loading - return (ComponentLoader.ComponentContext) context; + context.addPreInitTask(new OnViewEventLoadTriggerInitTask(facet, loaderId, eventClass)); } @SuppressWarnings("ClassCanBeRecord") diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/ComponentLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/ComponentLoader.java index 0af4e04f8a..209d581104 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/ComponentLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/ComponentLoader.java @@ -62,6 +62,22 @@ interface Context { */ String getMessageGroup(); + /** + * Adds Pre {@link InitTask} that will be executed according to the + * origin component lifecycle. + *

+ * Note: Pre InitTasks will be executed before DependencyManager + * invocation to have precedence over @Subscribe methods + * + * @param task a task to add + */ + void addPreInitTask(InitTask task); + + /** + * Executes all added {@link InitTask}s + */ + void executePreInitTasks(); + /** * Adds {@link InitTask} that will be executed according to the * origin component lifecycle. @@ -96,22 +112,6 @@ interface ComponentContext extends Context { */ View getView(); - /** - * Adds Pre {@link InitTask} that will be executed according to the - * origin component lifecycle. - *

- * Note: Pre InitTasks will be executed before DependencyManager - * invocation to have precedence over @Subscribe methods - * - * @param task a task to add - */ - void addPreInitTask(InitTask task); - - /** - * Executes all added {@link InitTask}s - */ - void executePreInitTasks(); - /** * Add {@link ComponentLoader.AutowireTask} that will be executed according to the origin component lifecycle. * diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/AbstractComponentLoader.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/AbstractComponentLoader.java index ea96f3c75c..f1efdb7bdf 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/AbstractComponentLoader.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/AbstractComponentLoader.java @@ -65,13 +65,6 @@ public void setContext(Context context) { this.context = context; } - protected ComponentContext getComponentContext() { - checkState(context instanceof ComponentContext, - "'context' must implement " + ComponentContext.class.getName()); - - return (ComponentContext) context; - } - protected abstract T createComponent(); @Override diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/AbstractLoaderContext.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/AbstractLoaderContext.java index 8931d46f90..870c35aa25 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/AbstractLoaderContext.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/AbstractLoaderContext.java @@ -16,7 +16,6 @@ package io.jmix.flowui.xml.layout.loader; -import com.vaadin.flow.component.Component; import io.jmix.flowui.component.HasDataComponents; import io.jmix.flowui.kit.component.HasActions; import io.jmix.flowui.xml.layout.ComponentLoader; @@ -36,6 +35,7 @@ public abstract class AbstractLoaderContext implements ComponentLoader.Context { protected HasActions actionsHolder; protected String messageGroup; + protected List preInitTasks; protected List initTasks; @Override @@ -84,6 +84,25 @@ public void setDataHolder(HasDataComponents dataHolder) { this.dataHolder = dataHolder; } + @Override + public void addPreInitTask(ComponentLoader.InitTask task) { + if (preInitTasks == null) { + preInitTasks = new ArrayList<>(); + } + + preInitTasks.add(task); + } + + @Override + public void executePreInitTasks() { + if (CollectionUtils.isNotEmpty(preInitTasks)) { + for (ComponentLoader.InitTask initTask : preInitTasks) { + initTask.execute(this); + } + preInitTasks.clear(); + } + } + @Override public void addInitTask(ComponentLoader.InitTask task) { if (initTasks == null) { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/ComponentLoaderContext.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/ComponentLoaderContext.java index 25d4d549b4..56f1cfb17e 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/ComponentLoaderContext.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/xml/layout/loader/ComponentLoaderContext.java @@ -32,7 +32,6 @@ public class ComponentLoaderContext extends AbstractLoaderContext implements Com protected String fullFrameId; protected String currentFrameId; - protected List preInitTasks; protected List autowireTasks; public ComponentLoaderContext() { @@ -60,15 +59,6 @@ public void setCurrentFrameId(String currentFrameId) { this.currentFrameId = currentFrameId; } - @Override - public void addPreInitTask(ComponentLoader.InitTask task) { - if (preInitTasks == null) { - preInitTasks = new ArrayList<>(); - } - - preInitTasks.add(task); - } - @Override public void addAutowireTask(ComponentLoader.AutowireTask task) { if (autowireTasks == null) { @@ -78,16 +68,6 @@ public void addAutowireTask(ComponentLoader.AutowireTask task) { autowireTasks.add(task); } - @Override - public void executePreInitTasks() { - if (CollectionUtils.isNotEmpty(preInitTasks)) { - for (ComponentLoader.InitTask initTask : preInitTasks) { - initTask.execute(this); - } - preInitTasks.clear(); - } - } - @Override public void executeAutowireTasks() { if (CollectionUtils.isNotEmpty(autowireTasks)) { From 2753116ce0b7dcf442e2304a2308025a16e92f9d Mon Sep 17 00:00:00 2001 From: Dmitry Kremnev Date: Thu, 18 Dec 2025 12:20:32 +0400 Subject: [PATCH 14/28] Fix fragment unique IDs for UrlQueryParametersFacet --- .../kit/meta/element/StudioDataElements.java | 12 ++++ .../element/onFragmentEventLoadTrigger.svg | 19 +++++++ .../onFragmentEventLoadTrigger_dark.svg | 19 +++++++ .../flowui/component/UiComponentUtils.java | 20 +++++++ .../flowui/facet/DataLoadCoordinator.java | 2 - .../facet/FragmentDataLoadCoordinator.java | 6 ++ .../flowui/facet/ViewDataLoadCoordinator.java | 9 --- .../impl/FragmentDataLoadCoordinatorImpl.java | 11 ++-- ...ataGridFilterUrlQueryParametersBinder.java | 18 +++++- ...GenericFilterUrlQueryParametersBinder.java | 57 ++++++++++++++++--- .../PaginationUrlQueryParametersBinder.java | 50 ++++++++++++++-- ...ropertyFilterUrlQueryParametersBinder.java | 18 +++++- ...bstractDataLoadCoordinatorFacetLoader.java | 38 +++++++++++++ ...ragmentDataLoadCoordinatorFacetLoader.java | 31 +++++++--- .../ViewDataLoadCoordinatorFacetLoader.java | 28 --------- .../io/jmix/flowui/view/fragment.xsd | 3 + 16 files changed, 272 insertions(+), 69 deletions(-) create mode 100644 jmix-flowui/flowui-kit/src/main/resources/io/jmix/flowui/kit/meta/icon/element/onFragmentEventLoadTrigger.svg create mode 100644 jmix-flowui/flowui-kit/src/main/resources/io/jmix/flowui/kit/meta/icon/element/onFragmentEventLoadTrigger_dark.svg diff --git a/jmix-flowui/flowui-kit/src/main/java/io/jmix/flowui/kit/meta/element/StudioDataElements.java b/jmix-flowui/flowui-kit/src/main/java/io/jmix/flowui/kit/meta/element/StudioDataElements.java index 6319b04a5d..75b6026bad 100644 --- a/jmix-flowui/flowui-kit/src/main/java/io/jmix/flowui/kit/meta/element/StudioDataElements.java +++ b/jmix-flowui/flowui-kit/src/main/java/io/jmix/flowui/kit/meta/element/StudioDataElements.java @@ -164,6 +164,18 @@ public interface StudioDataElements { ) void onViewEventLoadTrigger(); + @StudioElement( + name = "OnFragmentEventLoadTrigger", + classFqn = "io.jmix.flowui.facet.dataloadcoordinator.OnFragmentEventLoadTrigger", + xmlElement = "onFragmentEvent", + icon = "io/jmix/flowui/kit/meta/icon/element/onFragmentEventLoadTrigger.svg", + properties = { + @StudioProperty(xmlAttribute = "type", type = StudioPropertyType.ENUMERATION, + options = {"Ready", "Host.Init", "Host.BeforeShow", "Host.Ready"}, required = true) + } + ) + void onFragmentEventLoadTrigger(); + @StudioElement( name = "OnComponentValueChangedLoadTrigger", classFqn = "io.jmix.flowui.facet.dataloadcoordinator.OnComponentValueChangedLoadTrigger", diff --git a/jmix-flowui/flowui-kit/src/main/resources/io/jmix/flowui/kit/meta/icon/element/onFragmentEventLoadTrigger.svg b/jmix-flowui/flowui-kit/src/main/resources/io/jmix/flowui/kit/meta/icon/element/onFragmentEventLoadTrigger.svg new file mode 100644 index 0000000000..6c0da22250 --- /dev/null +++ b/jmix-flowui/flowui-kit/src/main/resources/io/jmix/flowui/kit/meta/icon/element/onFragmentEventLoadTrigger.svg @@ -0,0 +1,19 @@ + + + + + diff --git a/jmix-flowui/flowui-kit/src/main/resources/io/jmix/flowui/kit/meta/icon/element/onFragmentEventLoadTrigger_dark.svg b/jmix-flowui/flowui-kit/src/main/resources/io/jmix/flowui/kit/meta/icon/element/onFragmentEventLoadTrigger_dark.svg new file mode 100644 index 0000000000..39b166a6a2 --- /dev/null +++ b/jmix-flowui/flowui-kit/src/main/resources/io/jmix/flowui/kit/meta/icon/element/onFragmentEventLoadTrigger_dark.svg @@ -0,0 +1,19 @@ + + + + + diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/UiComponentUtils.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/UiComponentUtils.java index 071a0d7442..2b608ef324 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/UiComponentUtils.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/UiComponentUtils.java @@ -22,6 +22,7 @@ import com.vaadin.flow.component.page.PendingJavaScriptResult; import com.vaadin.flow.component.shared.HasPrefix; import com.vaadin.flow.component.shared.HasSuffix; +import com.vaadin.flow.server.Attributes; import com.vaadin.flow.server.StreamResource; import io.jmix.core.FileRef; import io.jmix.core.FileStorageLocator; @@ -539,6 +540,25 @@ public static Fragment findFragment(Component component) { return parent.map(UiComponentUtils::findFragment).orElse(null); } + /** + * Gets the id of the root element of this component. + *

+ * Gets the id of the root element of this component, depending on the attachment context: + *