diff --git a/domino-ui-shared/pom.xml b/domino-ui-shared/pom.xml index 8dabbb838..67232f605 100644 --- a/domino-ui-shared/pom.xml +++ b/domino-ui-shared/pom.xml @@ -5,7 +5,7 @@ domino-ui-parent org.dominokit - 2.0.1 + 2.0.2 jar 4.0.0 diff --git a/domino-ui-tools/mdi-icons-processor/pom.xml b/domino-ui-tools/mdi-icons-processor/pom.xml index e2e7801ef..e51f5755c 100644 --- a/domino-ui-tools/mdi-icons-processor/pom.xml +++ b/domino-ui-tools/mdi-icons-processor/pom.xml @@ -5,7 +5,7 @@ domino-ui-tools org.dominokit - 2.0.1 + 2.0.2 4.0.0 diff --git a/domino-ui-tools/pom.xml b/domino-ui-tools/pom.xml index 1d008ed76..0cf2c6f84 100644 --- a/domino-ui-tools/pom.xml +++ b/domino-ui-tools/pom.xml @@ -5,7 +5,7 @@ domino-ui-parent org.dominokit - 2.0.1 + 2.0.2 4.0.0 diff --git a/domino-ui-webjar/pom.xml b/domino-ui-webjar/pom.xml index 2e5972d55..1b3de4790 100644 --- a/domino-ui-webjar/pom.xml +++ b/domino-ui-webjar/pom.xml @@ -5,7 +5,7 @@ domino-ui-parent org.dominokit - 2.0.1 + 2.0.2 jar 4.0.0 diff --git a/domino-ui/pom.xml b/domino-ui/pom.xml index d9b77dbee..b7941785e 100644 --- a/domino-ui/pom.xml +++ b/domino-ui/pom.xml @@ -6,7 +6,7 @@ org.dominokit domino-ui-parent - 2.0.1 + 2.0.2 domino-ui diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/carousel/Carousel.java b/domino-ui/src/main/java/org/dominokit/domino/ui/carousel/Carousel.java index 470b24dc1..33e45b849 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/carousel/Carousel.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/carousel/Carousel.java @@ -19,11 +19,16 @@ import static org.dominokit.domino.ui.utils.Domino.*; import elemental2.dom.HTMLDivElement; +import elemental2.dom.WheelEvent; import java.util.ArrayList; import java.util.List; +import jsinterop.base.Js; +import org.dominokit.domino.ui.config.CarouselConfig; +import org.dominokit.domino.ui.config.HasComponentConfig; import org.dominokit.domino.ui.elements.AnchorElement; import org.dominokit.domino.ui.elements.DivElement; import org.dominokit.domino.ui.elements.OListElement; +import org.dominokit.domino.ui.events.EventType; import org.dominokit.domino.ui.icons.lib.Icons; import org.dominokit.domino.ui.style.CssClass; import org.dominokit.domino.ui.style.GenericCss; @@ -39,7 +44,8 @@ * * @see BaseDominoElement */ -public class Carousel extends BaseDominoElement { +public class Carousel extends BaseDominoElement + implements HasComponentConfig { private final OListElement indicatorsElement; private final DivElement slidesElement; @@ -57,6 +63,7 @@ public class Carousel extends BaseDominoElement { private Slide targetSlide; private int autoSlideDuration = 3000; private boolean attached = false; + private CarouselConfig carouselConfig; /** * Factory method to create an empty Carousel @@ -69,7 +76,6 @@ public static Carousel create() { /** Creates and empty Carousel */ public Carousel() { - element = div() .appendChild(indicatorsElement = ol().addCss(carousel_indicators)) @@ -89,11 +95,7 @@ public Carousel() { .appendChild( Icons.chevron_right() .addCss(GenericCss.dui_vertical_center, dui_font_size_12)) - .addEventListener( - "click", - evt -> { - next(); - })) + .addEventListener("click", evt -> next())) .addCss(carousel); timer = new Timer() { @@ -105,6 +107,21 @@ public void run() { addAttachListener(); addDetachListener(); init(this); + addEventListener( + EventType.wheel.getName(), + evt -> { + if (getConfig().isScrollCarouselWithWheel()) { + evt.preventDefault(); + WheelEvent wheelEvent = Js.uncheckedCast(evt); + if (wheelEvent.deltaY > 0) { + nextSlide(); + } + + if (wheelEvent.deltaY < 0) { + prevSlide(); + } + } + }); } /** @@ -406,6 +423,16 @@ public Slide getActiveSlide() { return activeSlide; } + @Override + public CarouselConfig getOwnConfig() { + return carouselConfig; + } + + public Carousel setConfig(CarouselConfig config) { + this.carouselConfig = config; + return this; + } + public enum SlideDirection { NONE, /** CSS class for previous indicator */ diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/config/CarouselConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/config/CarouselConfig.java new file mode 100644 index 000000000..120ec447c --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/config/CarouselConfig.java @@ -0,0 +1,23 @@ +/* + * Copyright © 2019 Dominokit + * + * 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 org.dominokit.domino.ui.config; + +public interface CarouselConfig extends ComponentConfig { + /** @return boolean, true to allow scrolling a carousel with mouse scroll wheel. */ + default boolean isScrollCarouselWithWheel() { + return false; + } +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/config/DatatableConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/config/DatatableConfig.java new file mode 100644 index 000000000..16a27ac28 --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/config/DatatableConfig.java @@ -0,0 +1,22 @@ +/* + * Copyright © 2019 Dominokit + * + * 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 org.dominokit.domino.ui.config; + +public interface DatatableConfig extends ComponentConfig { + default boolean isRemoveSummaryRecordsForEmptyTable() { + return false; + } +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/config/DelayedActionConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/config/DelayedActionConfig.java new file mode 100644 index 000000000..4653b86b7 --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/config/DelayedActionConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright © 2019 Dominokit + * + * 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 org.dominokit.domino.ui.config; + +public interface DelayedActionConfig extends ComponentConfig { + + /** + * Use to globally configure the delayed actions delay. + * + * @return the delay in milliseconds, default to 200ms + */ + default int getDelayedExecutionDefaultDelay() { + return 200; + } + + /** + * Use to globally configure select/suggest box search box typeahead delay. + * + * @return the delay in milliseconds, default to 1000ms + */ + default int getDefaultSelectableBoxTypeAheadDelay() { + return 1000; + } + + /** + * Use to globally configure Suggest box search box typeahead delay. + * + * @return the delay in milliseconds, default to {@link + * DelayedActionConfig#getDefaultSelectableBoxTypeAheadDelay()} + */ + default int getSuggestBoxTypeAheadDelay() { + return getDefaultSelectableBoxTypeAheadDelay(); + } + + /** + * Use to globally configure Select box search box typeahead delay. + * + * @return the delay in milliseconds, default to {@link + * DelayedActionConfig#getDefaultSelectableBoxTypeAheadDelay()} + */ + default int getSelectBoxTypeAheadDelay() { + return getDefaultSelectableBoxTypeAheadDelay(); + } + + /** + * Use to globally configure notification duration. + * + * @return the delay in milliseconds, default to 4000ms. + */ + default int getDefaultNotificationDuration() { + return 4000; + } + + /** + * Use to globally configure the delay before a datebox start parsing the value while tying in the + * date box input. + * + * @return the delay in milliseconds, default to {@link + * DelayedActionConfig#getDelayedExecutionDefaultDelay()}. + */ + default int getDateBoxDefaultInputParseDelay() { + return getDelayedExecutionDefaultDelay(); + } +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/config/HasComponentConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/config/HasComponentConfig.java index 14aa22626..6fca00de3 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/config/HasComponentConfig.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/config/HasComponentConfig.java @@ -15,6 +15,8 @@ */ package org.dominokit.domino.ui.config; +import static java.util.Objects.nonNull; + import org.dominokit.domino.ui.utils.DominoUIConfig; /** HasComponentConfig interface. */ @@ -25,6 +27,13 @@ public interface HasComponentConfig { * @return a T object */ default T getConfig() { + if (nonNull(getOwnConfig())) { + return getOwnConfig(); + } return (T) DominoUIConfig.CONFIG.getUIConfig(); } + + default T getOwnConfig() { + return null; + } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/config/SearchConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/config/SearchConfig.java index e1d9e41cf..7eb990eaa 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/config/SearchConfig.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/config/SearchConfig.java @@ -19,7 +19,7 @@ * Implementations of this interface can be used to configure defaults for {@link * org.dominokit.domino.ui.search.SearchBox} component */ -public interface SearchConfig extends ComponentConfig { +public interface SearchConfig extends DelayedActionConfig { /** * Use this method to define the default auto search delay for SearchBox in milliseconds @@ -29,7 +29,7 @@ public interface SearchConfig extends ComponentConfig { * @return an integer delay in milliseconds */ default int getAutoSearchDelay() { - return 200; + return getDelayedExecutionDefaultDelay(); } /** diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/config/UIConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/config/UIConfig.java index 0811b5548..9d3c58307 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/config/UIConfig.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/config/UIConfig.java @@ -29,4 +29,7 @@ public interface UIConfig UploadConfig, StepperConfig, CalendarConfig, - TimePickerConfig {} + TimePickerConfig, + DelayedActionConfig, + DatatableConfig, + CarouselConfig {} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/config/UploadConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/config/UploadConfig.java index 493f4f385..e3b990f17 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/config/UploadConfig.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/config/UploadConfig.java @@ -81,4 +81,15 @@ default FilePreviewFactory getFilePreviewFactory() { default Supplier> getDefaultFilePreviewContainer() { return DefaultFilePreviewContainer::new; } + + /** + * when the user uploaded a set of files that are less or equals the max uploads allowed, and they + * are already uploaded, should we allow him to upload a new set of files that are in total with + * the previous batch could overflow the max allowed upload limit. + * + * @return boolean default true + */ + default boolean isMaxUploadsOverflowAllowed() { + return true; + } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/TableConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/TableConfig.java index 43a42c643..cd1753291 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/TableConfig.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/TableConfig.java @@ -390,6 +390,15 @@ public List> getFlattenColumns() { .collect(Collectors.toList()); } + /** + * Retrieves only the leaf columns of the data table. + * + * @return A list of {@link ColumnConfig} representing all columns, flattened. + */ + public List> getLeafColumns() { + return columns.stream().flatMap(col -> col.leafColumns().stream()).collect(Collectors.toList()); + } + /** * Retrieves the columns of the DataTable as grouped. * diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/column/ColumnHeaderFilterPlugin.java b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/column/ColumnHeaderFilterPlugin.java index 48eaa42c6..a53e394bc 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/column/ColumnHeaderFilterPlugin.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/column/ColumnHeaderFilterPlugin.java @@ -18,6 +18,7 @@ import static java.util.Objects.nonNull; import static org.dominokit.domino.ui.datatable.DataTableStyles.dui_datatable_column_filter; +import static org.dominokit.domino.ui.datatable.DataTableStyles.dui_datatable_row; import static org.dominokit.domino.ui.utils.Domino.*; import elemental2.dom.HTMLElement; @@ -55,6 +56,7 @@ public class ColumnHeaderFilterPlugin implements DataTablePlugin { @Override public void init(DataTable dataTable) { this.datatable = dataTable; + this.filtersRowElement.addCss(dui_datatable_row); } /** diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/filter/header/DelayedHeaderFilterInput.java b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/filter/header/DelayedHeaderFilterInput.java index a84d746b2..73d202712 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/filter/header/DelayedHeaderFilterInput.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/filter/header/DelayedHeaderFilterInput.java @@ -22,6 +22,8 @@ import elemental2.dom.HTMLElement; import elemental2.dom.HTMLInputElement; +import org.dominokit.domino.ui.config.HasComponentConfig; +import org.dominokit.domino.ui.config.SearchConfig; import org.dominokit.domino.ui.datatable.ColumnConfig; import org.dominokit.domino.ui.datatable.model.Category; import org.dominokit.domino.ui.datatable.model.Filter; @@ -30,7 +32,6 @@ import org.dominokit.domino.ui.datatable.plugins.column.ColumnHeaderFilterPlugin; import org.dominokit.domino.ui.forms.InputFormField; import org.dominokit.domino.ui.utils.DelayedTextInput; -import org.dominokit.domino.ui.utils.DominoUIConfig; import org.dominokit.domino.ui.utils.HasPlaceHolder; /** @@ -44,9 +45,10 @@ */ public abstract class DelayedHeaderFilterInput< B extends InputFormField, T, V> - implements ColumnHeaderFilterPlugin.HeaderFilter { + implements ColumnHeaderFilterPlugin.HeaderFilter, HasComponentConfig { private B input; private DelayedTextInput delayedTextInput; + private SearchConfig config; /** Creates a new instance of DelayedHeaderFilterInput with a default placeholder. */ public DelayedHeaderFilterInput() { @@ -68,8 +70,16 @@ public DelayedHeaderFilterInput(String placeHolder) { delayedTextInput = DelayedTextInput.create( - getInputElement(), - DominoUIConfig.CONFIG.getUIConfig().getTableTextHeaderFilterSearchDelay()); + getInputElement(), getConfig().getTableTextHeaderFilterSearchDelay()); + } + + public void setOwnConfig(SearchConfig config) { + this.config = config; + } + + @Override + public SearchConfig getOwnConfig() { + return config; } /** diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/EmptyStatePlugin.java b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/EmptyStatePlugin.java index 4764fc19d..7fba6e4ad 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/EmptyStatePlugin.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/EmptyStatePlugin.java @@ -19,6 +19,9 @@ import org.dominokit.domino.ui.datatable.DataTable; import org.dominokit.domino.ui.datatable.events.TableDataUpdatedEvent; import org.dominokit.domino.ui.datatable.plugins.DataTablePlugin; +import org.dominokit.domino.ui.elements.TDElement; +import org.dominokit.domino.ui.elements.TFootElement; +import org.dominokit.domino.ui.elements.TableRowElement; import org.dominokit.domino.ui.icons.Icon; import org.dominokit.domino.ui.layout.EmptyState; import org.dominokit.domino.ui.utils.ChildHandler; @@ -47,6 +50,9 @@ public class EmptyStatePlugin implements DataTablePlugin { private EmptyState emptyState; + private TableRowElement rowElement = tr(); + private TDElement stateCell = td(); + private TFootElement footer; /** * Creates and returns a new instance of {@code EmptyStatePlugin} with the provided icon and @@ -72,18 +78,42 @@ public EmptyStatePlugin(Icon emptyStateIcon, String title) { } @Override - public void onAfterAddTable(DataTable dataTable) { + public void init(DataTable dataTable) { + rowElement + .addCss(dui_table_row) + .appendChild(stateCell.addCss(dui_table_cell).appendChild(emptyState)); + } + + /** + * Invoked when the footer is added to the DataTable. + * + * @param datatable The DataTable to which the footer is added. + */ + @Override + public void onFooterAdded(DataTable datatable) { + this.footer = datatable.footerElement(); + this.footer.appendChild(rowElement); + } + + @Override + public void onAfterAddTable(DataTable dataTable) { dataTable.addTableEventListener( TableDataUpdatedEvent.DATA_UPDATED, event -> { TableDataUpdatedEvent tableDataUpdatedEvent = (TableDataUpdatedEvent) event; + long columnsCount = + dataTable.getTableConfig().getLeafColumns().stream() + .filter(c -> !c.isHidden()) + .count(); + stateCell.setAttribute("colspan", columnsCount); + if (tableDataUpdatedEvent.getTotalCount() == 0) { - emptyState.show(); + rowElement.show(); } else { - emptyState.hide(); + rowElement.hide(); } }); - dataTable.element().appendChild(emptyState.element()); + this.footer.insertFirst(rowElement); } /** diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/SummaryPlugin.java b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/SummaryPlugin.java index 91e9928e8..4974805b7 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/SummaryPlugin.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/SummaryPlugin.java @@ -16,11 +16,16 @@ package org.dominokit.domino.ui.datatable.plugins.summary; +import static java.util.Objects.isNull; + import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.dominokit.domino.ui.datatable.DataTable; +import org.dominokit.domino.ui.datatable.events.TableDataUpdatedEvent; +import org.dominokit.domino.ui.datatable.events.TableEvent; import org.dominokit.domino.ui.datatable.plugins.DataTablePlugin; +import org.dominokit.domino.ui.datatable.plugins.HasPluginConfig; import org.dominokit.domino.ui.elements.TFootElement; import org.dominokit.domino.ui.utils.BaseDominoElement; @@ -40,11 +45,13 @@ * @param The type of data in the DataTable. * @param The type of data in the summary row. */ -public class SummaryPlugin implements DataTablePlugin { +public class SummaryPlugin + implements DataTablePlugin, HasPluginConfig, SummaryPluginConfig> { private List> summaryRows = new ArrayList<>(); private DataTable dataTable; private TFootElement footer; + private SummaryPluginConfig config = SummaryPluginConfig.of(); /** * Initializes the SummaryPlugin with the DataTable. @@ -74,8 +81,10 @@ public void onFooterAdded(DataTable datatable) { * @return This SummaryPlugin instance. */ public SummaryPlugin setSummaryRecords(Collection records) { - summaryRows.forEach(BaseDominoElement::remove); - summaryRows.clear(); + removeSummaryRecords(); + if (this.config.isRemoveOnEmptyData() && this.dataTable.getRecords().isEmpty()) { + return this; + } List recordsList = new ArrayList<>(records); for (int i = 0; i < recordsList.size(); i++) { SummaryRow summaryRow = new SummaryRow<>(recordsList.get(i), i, this.dataTable); @@ -86,6 +95,46 @@ public SummaryPlugin setSummaryRecords(Collection records) { return this; } + public void removeSummaryRecords() { + summaryRows.forEach(BaseDominoElement::remove); + summaryRows.clear(); + } + + @Override + public void handleEvent(TableEvent event) { + if (TableDataUpdatedEvent.DATA_UPDATED.equals(event.getType())) { + if (config.isRemoveOnEmptyData() && ((TableDataUpdatedEvent) event).getData().isEmpty()) { + removeSummaryRecords(); + } + } + } + + /** + * Sets the configuration for the SummaryPlugin. + * + * @param config The SummaryPlugin to set. + * @return The SummaryPlugin instance. + */ + @Override + public SummaryPlugin setConfig(SummaryPluginConfig config) { + if (isNull(config)) { + this.config = SummaryPluginConfig.of(); + } else { + this.config = config; + } + return this; + } + + /** + * Gets the current configuration of the SummaryPlugin. + * + * @return The current SummaryPluginConfig. + */ + @Override + public SummaryPluginConfig getConfig() { + return config; + } + /** * Specifies the order in which this plugin should be applied relative to other plugins. Plugins * with lower order values are applied first. diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/SummaryPluginConfig.java b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/SummaryPluginConfig.java new file mode 100644 index 000000000..5247b3ff0 --- /dev/null +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/datatable/plugins/summary/SummaryPluginConfig.java @@ -0,0 +1,59 @@ +/* + * Copyright © 2019 Dominokit + * + * 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 org.dominokit.domino.ui.datatable.plugins.summary; + +import org.dominokit.domino.ui.config.DatatableConfig; +import org.dominokit.domino.ui.datatable.plugins.PluginConfig; +import org.dominokit.domino.ui.utils.DominoUIConfig; + +public class SummaryPluginConfig implements PluginConfig { + + private boolean removeOnEmptyData; + + public SummaryPluginConfig(boolean removeOnEmptyData) { + this.removeOnEmptyData = removeOnEmptyData; + } + + public static SummaryPluginConfig of() { + return new SummaryPluginConfig( + DominoUIConfig.CONFIG.getUIConfig().isRemoveSummaryRecordsForEmptyTable()); + } + + public static SummaryPluginConfig of(boolean removeOnEmptyData) { + return new SummaryPluginConfig(removeOnEmptyData); + } + + /** + * @return boolean, true will cause the plugin to remove the summary records for empty data + * tables, false will keep them, default to {@link + * DatatableConfig#isRemoveSummaryRecordsForEmptyTable()} + */ + public boolean isRemoveOnEmptyData() { + return removeOnEmptyData; + } + + /** + * Use this to configure the plugin to remove/keep the summary records when the datatable has no + * records. + * + * @param removeOnEmptyData boolean, true to remove the records, false to keep them. + * @return same config instance. + */ + public SummaryPluginConfig setRemoveOnEmptyData(boolean removeOnEmptyData) { + this.removeOnEmptyData = removeOnEmptyData; + return this; + } +} diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/datepicker/Calendar.java b/domino-ui/src/main/java/org/dominokit/domino/ui/datepicker/Calendar.java index 2ae43e194..78423aa6d 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/datepicker/Calendar.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/datepicker/Calendar.java @@ -113,20 +113,21 @@ public Calendar(Date date, DateTimeFormatInfo dateTimeFormatInfo, CalendarInitCo this.dateTimeFormatInfo = dateTimeFormatInfo; this.config = config; - this.root = - div() - .addCss(dui_calendar) - .apply(calendar -> header = LazyChild.of(CalendarHeader.create(this), calendar)) - .appendChild(selectors = CalendarSelectors.create(this)) - .appendChild( - calendarBody = - div() - .addCss(dui_calendar_body) - .appendChild(calendarMonth = CalendarMonth.create(this)) - .appendChild(yearMonthPicker = YearMonthPicker.create(this).hide())) - .apply( - calendar -> - this.footer = LazyChild.of(div().addCss(dui_calendar_footer), calendar)); + this.root = div().addCss(dui_calendar); + + getConfig().getPlugins().forEach(plugin -> plugin.onInit(this)); + init(this); + + this.root + .apply(calendar -> header = LazyChild.of(CalendarHeader.create(this), calendar)) + .appendChild(selectors = CalendarSelectors.create(this)) + .appendChild( + calendarBody = + div() + .addCss(dui_calendar_body) + .appendChild(calendarMonth = CalendarMonth.create(this)) + .appendChild(yearMonthPicker = YearMonthPicker.create(this).hide())) + .apply(calendar -> this.footer = LazyChild.of(div().addCss(dui_calendar_footer), calendar)); this.root.addEventListener( CalendarCustomEvents.DATE_NAVIGATION_CHANGED, @@ -134,11 +135,10 @@ public Calendar(Date date, DateTimeFormatInfo dateTimeFormatInfo, CalendarInitCo evt.stopPropagation(); CalendarCustomEvents.UpdateDateEventData dateData = CalendarCustomEvents.UpdateDateEventData.of((CustomEvent) evt); - Date updatedDate = new Date(dateData.getTimestamp()); - onDateViewUpdate(updatedDate); + this.date = new Date(dateData.getTimestamp()); + onDateViewUpdate(this.date); calendarMonth.show(); yearMonthPicker.hide(); - this.date = updatedDate; }); this.root.addEventListener( @@ -148,9 +148,8 @@ public Calendar(Date date, DateTimeFormatInfo dateTimeFormatInfo, CalendarInitCo CalendarCustomEvents.UpdateDateEventData dateData = CalendarCustomEvents.UpdateDateEventData.of((CustomEvent) evt); Date oldDate = this.date; - Date updatedDate = new Date(dateData.getTimestamp()); - this.date = updatedDate; - onDateSelectionChanged(updatedDate); + this.date = new Date(dateData.getTimestamp()); + onDateSelectionChanged(this.date); calendarMonth.show(); yearMonthPicker.hide(); triggerChangeListeners(oldDate, this.date); @@ -170,11 +169,10 @@ public Calendar(Date date, DateTimeFormatInfo dateTimeFormatInfo, CalendarInitCo calendarBody.removeCss(dui_p_x_1_5); } }); - init(this); + onDateViewUpdate(this.date); onDateSelectionChanged(this.date); onDateTimeFormatChanged(); - getConfig().getPlugins().forEach(plugin -> plugin.onInit(this)); } /** @@ -313,6 +311,17 @@ public Calendar setDate(Date date) { return this; } + /** + * Resets the calendar view to the month view if it is on month/year selection view + * + * @return same Calendar instance + */ + public Calendar resetView() { + calendarMonth.show(); + yearMonthPicker.hide(); + return this; + } + /** @return the {@link DateTimeFormatInfo} used by this calendar instance */ @Override public DateTimeFormatInfo getDateTimeFormatInfo() { diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/datepicker/CalendarDay.java b/domino-ui/src/main/java/org/dominokit/domino/ui/datepicker/CalendarDay.java index 873552117..3798310ba 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/datepicker/CalendarDay.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/datepicker/CalendarDay.java @@ -15,7 +15,6 @@ */ package org.dominokit.domino.ui.datepicker; -import static org.dominokit.domino.ui.utils.Domino.*; import static org.dominokit.domino.ui.utils.Domino.div; import static org.dominokit.domino.ui.utils.Domino.span; @@ -65,13 +64,6 @@ public CalendarDay(IsCalendar calendar, Date date, int day, boolean inRange) { root = div() - .addClickListener( - evt -> { - this.dispatchEvent( - CalendarCustomEvents.dateSelectionChanged( - new Date(this.date.getYear(), this.date.getMonth(), this.date.getDate()) - .getTime())); - }) .addCss( dui_calendar_day, BooleanCssClass.of(dui_month_day_in_range, inRange), @@ -84,6 +76,13 @@ public CalendarDay(IsCalendar calendar, Date date, int day, boolean inRange) { .addCss(dui_calendar_day_number) .textContent(String.valueOf(date.getDate()))); init(this); + addClickListener( + evt -> { + this.dispatchEvent( + CalendarCustomEvents.dateSelectionChanged( + new Date(this.date.getYear(), this.date.getMonth(), this.date.getDate()) + .getTime())); + }); } /** diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/elements/ImageElement.java b/domino-ui/src/main/java/org/dominokit/domino/ui/elements/ImageElement.java index 0231ea4fe..6db5ed674 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/elements/ImageElement.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/elements/ImageElement.java @@ -54,4 +54,15 @@ public static ImageElement of(HTMLImageElement e) { public ImageElement(HTMLImageElement element) { super(element); } + + /** + * Sets the src for the image element + * + * @param src String image source + * @return same component + */ + public ImageElement src(String src) { + setAttribute("src", src); + return this; + } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/DateBox.java b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/DateBox.java index 7c4bda3a4..dfdcdcabe 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/DateBox.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/DateBox.java @@ -149,6 +149,7 @@ public DateBox( .setOpenOnClick(this.openOnClick) .setPosition(BEST_MIDDLE_DOWN_UP) .appendChild(this.calendar) + .addCloseListener(component -> calendar.resetView()) .addOnRemoveListener( popover -> { withOpenOnFocusToggleListeners(false, field -> focus()); @@ -183,13 +184,37 @@ public DateBox( clearInvalid(); } catch (IllegalArgumentException ignored) { if (parseStrict) { - invalidate("Unable to parse date value " + value); + invalidate(getLabels().calendarInvalidDateFormat(value)); } DomGlobal.console.warn("Unable to parse date value " + value); } } }); + getInputElement() + .addEventListener( + "input", + evt -> { + DelayedExecution.execute( + () -> { + String value = getStringValue(); + if (value.isEmpty()) { + clear(); + } else { + try { + withValue(getFormattedValue(value)); + clearInvalid(); + } catch (IllegalArgumentException ignored) { + if (parseStrict) { + invalidate(getLabels().calendarInvalidDateFormat(value)); + } + DomGlobal.console.warn("Unable to parse date value " + value); + } + } + }, + config().getUIConfig().getDateBoxDefaultInputParseDelay()); + }); + appendChild( PrimaryAddOn.of( getConfig() diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/Radio.java b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/Radio.java index beb23542f..d0b807525 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/Radio.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/Radio.java @@ -101,7 +101,6 @@ public Radio(T value, String label) { inputElement.addEventListener( "change", evt -> { - DomGlobal.console.info("CHANGEEEEEEEED.!"); if (isEnabled() && !isReadOnly()) { setChecked(isChecked(), isChangeListenersPaused()); } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/TimeBox.java b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/TimeBox.java index fc622922a..59237b8e4 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/TimeBox.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/TimeBox.java @@ -158,7 +158,7 @@ public TimeBox(Date date, DateTimeFormatInfo dateTimeFormatInfo) { clearInvalid(); } catch (IllegalArgumentException ignored) { if (parseStrict) { - invalidate("Unable to parse date value " + value); + invalidate(getLabels().timePickerInvalidTimeFormat(value)); } DomGlobal.console.warn("Unable to parse date value " + value); } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/AbstractSelect.java b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/AbstractSelect.java index 04d1b98f4..5232b5e5a 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/AbstractSelect.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/AbstractSelect.java @@ -80,6 +80,7 @@ public abstract class AbstractSelect< private SpanElement placeHolderElement; private InputElement inputElement; private InputElement typingElement; + private int typeAheadDelay = -1; /** * Default constructor which initializes the underlying structures, sets up event listeners, and @@ -103,7 +104,7 @@ public AbstractSelect() { .setTabIndex(-1) .onKeyPress(keyEvents -> keyEvents.alphanumeric(Event::stopPropagation))); - DelayedTextInput.create(typingElement, 1000) + DelayedTextInput.create(typingElement, getTypeAheadDelay()) .setDelayedAction( () -> { optionsMenu @@ -209,6 +210,17 @@ public AbstractSelect() { }))); } + private int getTypeAheadDelay() { + return typeAheadDelay > 0 + ? typeAheadDelay + : config().getUIConfig().getSelectBoxTypeAheadDelay(); + } + + public C setTypeAheadDelay(int delay) { + this.typeAheadDelay = delay; + return (C) this; + } + /** * Opens the options menu allowing user to select an option, unless the select is read-only or * disabled. @@ -265,11 +277,15 @@ protected C clearValue(boolean silent) { new ArrayList<>(selection) .forEach( item -> { - item.deselect(silent); - if (silent) { - OptionMeta.get(item) - .ifPresent(meta -> onOptionDeselected((O) meta.getOption(), silent)); - } + withPausedChangeListeners( + select -> { + item.deselect(silent); + if (silent) { + OptionMeta.get(item) + .ifPresent( + meta -> onOptionDeselected((O) meta.getOption(), silent)); + } + }); }); }); diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/AbstractSuggestBox.java b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/AbstractSuggestBox.java index a08f26129..3b5d83c91 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/AbstractSuggestBox.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/AbstractSuggestBox.java @@ -27,6 +27,7 @@ import java.util.Optional; import jsinterop.base.Js; import org.dominokit.domino.ui.IsElement; +import org.dominokit.domino.ui.config.DelayedActionConfig; import org.dominokit.domino.ui.elements.DivElement; import org.dominokit.domino.ui.elements.InputElement; import org.dominokit.domino.ui.forms.AbstractFormElement; @@ -110,7 +111,7 @@ public abstract class AbstractSuggestBox< private boolean autoSelect = true; /** The type-ahead delay in milliseconds. */ - private int typeAheadDelay = 1000; + private int typeAheadDelay = -1; /** * Creates an instance of {@code AbstractSuggestBox} with the specified suggestions store. @@ -175,7 +176,7 @@ public AbstractSuggestBox(SuggestionsStore store) { }))); getInputElement() - .onKeyDown( + .onKeyUp( keyEvents -> { keyEvents .onArrowDown( @@ -223,6 +224,14 @@ public AbstractSuggestBox(SuggestionsStore store) { onBackspace(); } } + }) + .onDelete( + evt -> { + if (!isReadOnly() && !isDisabled()) { + evt.stopPropagation(); + evt.preventDefault(); + onBackspace(); + } }); }); } @@ -263,12 +272,16 @@ public C setAutoSelect(boolean autoSelect) { } /** - * Gets the type-ahead delay in milliseconds. + * Gets the type-ahead delay in milliseconds; this will return the value specified using {@link + * AbstractSuggestBox#setTypeAheadDelay(int)} if greater than 0 otherwise, this will return the + * value specified in {@link DelayedActionConfig#getSuggestBoxTypeAheadDelay()}. * * @return The type-ahead delay in milliseconds. */ public int getTypeAheadDelay() { - return typeAheadDelay; + return typeAheadDelay > 0 + ? typeAheadDelay + : config().getUIConfig().getSuggestBoxTypeAheadDelay(); } /** @@ -286,7 +299,7 @@ public void setTypeAheadDelay(int typeAheadDelayInMillis) { * the suggestions from the store, highlighting the matched portions, and updating the options * menu. */ - private void search() { + protected void search() { if (store != null) { loader.start(); optionsMenu.removeAll(); @@ -703,6 +716,39 @@ public C withOptionsMenu(ChildHandler> handler) { return (C) this; } + /** + * Use to change the default search loader of a suggest box. + * + * @param loader + * @return same component instance + */ + public C setLoader(Loader loader) { + this.loader = loader; + return (C) this; + } + + /** + * Use to apply a function on search loader of a suggest box. + * + * @param handler + * @return same component instance + */ + public C withLoader(ChildHandler handler) { + handler.apply((C) this, loader); + return (C) this; + } + + /** + * Use to apply a function on element hosting the suggest box search loader. + * + * @param handler + * @return same component instance + */ + public C withLoaderElement(ChildHandler> handler) { + handler.apply((C) this, loaderElement); + return (C) this; + } + /** * An interface for rendering the value of a suggestion option in a suggest box. * diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/MultiSelect.java b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/MultiSelect.java index 4442aef7b..267d88ae2 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/MultiSelect.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/MultiSelect.java @@ -154,6 +154,7 @@ protected void onOptionSelected(SelectOption option, boolean silent) { updateTextValue(); fieldInput.appendChild(option); selectedOptions.add(option); + getInputElement().element().focus(); } /** diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/MultiSuggestBox.java b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/MultiSuggestBox.java index cb079cb5f..0b1ef30b9 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/MultiSuggestBox.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/MultiSuggestBox.java @@ -116,7 +116,6 @@ public void onOptionSelected(O option) { } withOption(option); fieldInput.appendChild(option); - selectedOptions.add(option); option.bindTo(this); } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/Select.java b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/Select.java index 3c8e2f0c2..956fb3862 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/Select.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/Select.java @@ -118,6 +118,7 @@ public Select withOption(SelectOption option, boolean silent) { } } autoValidate(); + getInputElement().element().focus(); return this; } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/SuggestBox.java b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/SuggestBox.java index 658632ba0..f065c1511 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/SuggestBox.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/forms/suggest/SuggestBox.java @@ -101,10 +101,7 @@ public V getValue() { /** Handles the "Backspace" key event. */ @Override protected void onBackspace() { - if (nonNull(selectedOption)) { - selectedOption.remove(); - selectedOption = null; - } + clearValue(isChangeListenersPaused()); } /** @@ -164,6 +161,7 @@ private void updateTextValue() { @Override public void onOptionDeselected(O option) { option.remove(); + V oldValue = this.selectedOption.getValue(); if (Objects.equals(this.selectedOption, option)) { this.selectedOption = null; } @@ -184,8 +182,12 @@ public void onOptionDeselected(O option) { protected SuggestBox clearValue(boolean silent) { if (nonNull(selectedOption)) { V oldValue = getValue(); - withPauseChangeListenersToggle(true, field -> onOptionDeselected(selectedOption)); - + withPauseChangeListenersToggle( + true, + field -> { + onOptionDeselected(selectedOption); + getInputElement().element().value = null; + }); if (!silent) { triggerClearListeners(oldValue); triggerChangeListeners(oldValue, getValue()); @@ -194,6 +196,8 @@ protected SuggestBox clearValue(boolean silent) { if (isAutoValidation()) { autoValidate(); } + } else { + withPauseChangeListenersToggle(true, field -> getInputElement().element().value = null); } return this; @@ -209,7 +213,7 @@ public String getStringValue() { if (nonNull(this.selectedOption)) { return String.valueOf(this.selectedOption.getValue()); } - return null; + return getInputElement().element().value; } /** Handles actions to be performed after an option is selected. */ diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/CalendarLabels.java b/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/CalendarLabels.java index d26ac5508..15d900098 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/CalendarLabels.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/CalendarLabels.java @@ -29,4 +29,13 @@ public interface CalendarLabels extends Labels { default String calendarInvalidDateFormat() { return "Invalid date format"; } + /** + * Returns the localized label for the "Invalid date format" message related to calendar input. + * + * @param value the current invalid value + * @return The localized label for "Invalid date format". + */ + default String calendarInvalidDateFormat(String value) { + return calendarInvalidDateFormat() + " : " + value; + } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/TimePickerLabels.java b/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/TimePickerLabels.java index 6a2f8bb95..102670882 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/TimePickerLabels.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/TimePickerLabels.java @@ -75,4 +75,14 @@ default String ampm() { default String timePickerInvalidTimeFormat() { return "Invalid time format"; } + + /** + * Gets the error message for an invalid time format in the time picker. + * + * @param value the current invalid value + * @return The error message for an invalid time format. + */ + default String timePickerInvalidTimeFormat(String value) { + return timePickerInvalidTimeFormat() + " : " + value; + } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/UploadLabels.java b/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/UploadLabels.java index 6e21d8c7a..3ec2b4f0a 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/UploadLabels.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/i18n/UploadLabels.java @@ -55,7 +55,14 @@ default String getDefaultUploadCanceledMessage() { * @param current The current number of uploads. * @return The error message for exceeding the maximum allowed uploads. */ - default String getMaxFileErrorMessage(int maxFiles, int current) { - return "The maximum allowed uploads is : " + maxFiles + ", You have " + current; + default String getMaxFileErrorMessage(int maxFiles, int current, int added, int ignored) { + return "The maximum allowed uploads is : " + + maxFiles + + ", You have : " + + current + + ", added : " + + added + + ", ignored : " + + ignored; } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/icons/LabeledIcon.java b/domino-ui/src/main/java/org/dominokit/domino/ui/icons/LabeledIcon.java index b2ab41d13..44e615325 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/icons/LabeledIcon.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/icons/LabeledIcon.java @@ -22,6 +22,7 @@ import elemental2.dom.HTMLElement; import org.dominokit.domino.ui.elements.SpanElement; import org.dominokit.domino.ui.style.WavesElement; +import org.dominokit.domino.ui.utils.ChildHandler; /** * A component that combines an icon and a text label, allowing you to create labeled icons with @@ -30,6 +31,8 @@ public class LabeledIcon extends WavesElement { private final SpanElement element; + private final SpanElement textElement; + private final Icon icon; /** * Creates a labeled icon with the provided icon and text, positioned to the left. @@ -52,8 +55,9 @@ public LabeledIcon(Icon icon, String text, IconPosition position) { element = span() .addCss(dui_labeled_icon) - .appendChild(icon) - .appendChild(span().addCss(dui_icon_text, dui_text_ellipsis).textContent(text)); + .appendChild(this.icon = icon) + .appendChild( + textElement = span().addCss(dui_icon_text, dui_text_ellipsis).textContent(text)); init(this); position.apply(this); } @@ -82,6 +86,50 @@ public static LabeledIcon create(Icon icon, String text, IconPosition positio return new LabeledIcon(icon, text, position); } + /** + * Changes the text of the labeled icon + * + * @param text the new text. + * @return same component + */ + public LabeledIcon setText(String text) { + this.textElement.setTextContent(text); + return this; + } + + /** + * Apply a handler to the labeledIcon text element. + * + * @param handler the handler to be applied + * @return same component + */ + public LabeledIcon withTextElement(ChildHandler handler) { + handler.apply(this, textElement); + return this; + } + + /** + * Apply a handler to the labeledIcon icon element. + * + * @param handler the handler to be applied + * @return same component + */ + public LabeledIcon withIcon(ChildHandler> handler) { + handler.apply(this, icon); + return this; + } + + /** + * Applies a new position for the labeledIcon text. + * + * @param position The new position to be applied + * @return same component. + */ + public LabeledIcon setIconPosition(IconPosition position) { + position.apply(this); + return this; + } + /** @dominokit-site-ignore {@inheritDoc} */ @Override public HTMLElement element() { diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/layout/NavBar.java b/domino-ui/src/main/java/org/dominokit/domino/ui/layout/NavBar.java index 0ff40e737..51abfd14c 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/layout/NavBar.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/layout/NavBar.java @@ -24,6 +24,7 @@ import org.dominokit.domino.ui.elements.HeadingElement; import org.dominokit.domino.ui.elements.NavElement; import org.dominokit.domino.ui.elements.SmallElement; +import org.dominokit.domino.ui.elements.SpanElement; import org.dominokit.domino.ui.utils.*; /** @@ -57,6 +58,7 @@ public class NavBar extends BaseDominoElement { private NavElement root; private HeadingElement title; + private SpanElement titleTextElement; private LazyChild description; private DivElement body; @@ -84,7 +86,7 @@ public NavBar() { root = nav() .addCss(dui_nav_bar) - .appendChild(title = h(4).addCss(dui_nav_title)) + .appendChild(title = h(4).appendChild(titleTextElement = span()).addCss(dui_nav_title)) .appendChild(body = div().addCss(dui_nav_body)); description = LazyChild.of(small().addCss(dui_nav_description), title); init(this); @@ -118,7 +120,7 @@ public NavBar(String title, String description) { * @return This {@code NavBar} instance. */ public NavBar setTitle(String title) { - this.title.setTextContent(title); + this.titleTextElement.setTextContent(title); return this; } @@ -159,6 +161,17 @@ public NavBar withDescription(ChildHandler handler) { return this; } + /** + * Allows customization of the title text element. + * + * @param handler The handler for customizing the title text element. + * @return This {@code NavBar} instance. + */ + public NavBar withTitleTextElement(ChildHandler handler) { + handler.apply(this, titleTextElement); + return this; + } + /** * Gets the title element. * @@ -186,6 +199,11 @@ public String getTitle() { return title.getTextContent(); } + /** @return the element containing the text of the title. */ + public SpanElement getTitleTextElement() { + return titleTextElement; + } + /** * Gets the text of the description displayed in the navigation bar. * diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/menu/Menu.java b/domino-ui/src/main/java/org/dominokit/domino/ui/menu/Menu.java index 1bb8ed0c7..c4ec45df1 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/menu/Menu.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/menu/Menu.java @@ -156,6 +156,7 @@ public class Menu extends BaseDominoElement> private EventListener lostFocusListener; private boolean closeOnBlur = DominoUIConfig.CONFIG.isClosePopupOnBlur(); private OpenMenuCondition openMenuCondition = (menu) -> true; + private List mediaQueryRecords = new ArrayList<>(); /** * Factory method to create a new Menu instance. @@ -355,19 +356,6 @@ public Menu() { element.addEventListener("keydown", keyboardNavigation); - MediaQuery.addOnSmallAndDownListener( - () -> { - if (centerOnSmallScreens) { - this.smallScreen = true; - } - }); - MediaQuery.addOnMediumAndUpListener( - () -> { - if (centerOnSmallScreens) { - this.smallScreen = false; - backArrowContainer.remove(); - } - }); backIcon = LazyChild.of(Icons.keyboard_backspace().addCss(dui_menu_back_icon), menuHeader); backIcon.whenInitialized( () -> { @@ -403,6 +391,38 @@ public Menu() { } }; + nowAndWhenAttached( + () -> { + mediaQueryRecords.add( + MediaQuery.addOnSmallAndDownListener( + () -> { + if (centerOnSmallScreens) { + this.smallScreen = true; + } + })); + + mediaQueryRecords.add( + MediaQuery.addOnMediumAndUpListener( + () -> { + if (centerOnSmallScreens) { + this.smallScreen = false; + backArrowContainer.remove(); + } + })); + + DomGlobal.document.body.addEventListener("blur", lostFocusListener, true); + if (this.dropDown) { + document.addEventListener("scroll", repositionListener, true); + } + }); + + nowAndWhenDetached( + () -> { + DomGlobal.document.body.removeEventListener("blur", lostFocusListener, true); + document.removeEventListener("scroll", repositionListener, true); + mediaQueryRecords.forEach(MediaQuery.MediaQueryListenerRecord::remove); + }); + this.addEventListener(EventType.touchstart.getName(), Event::stopPropagation); this.addEventListener(EventType.touchend.getName(), Event::stopPropagation); } @@ -1378,7 +1398,6 @@ private void doOpen(boolean focus) { menuHeader.get().insertFirst(backArrowContainer); } show(); - DomGlobal.document.body.addEventListener("blur", lostFocusListener, true); } } @@ -1570,7 +1589,6 @@ public Menu close() { menuTarget -> { menuTarget.getTargetElement().element().focus(); }); - DomGlobal.document.body.removeEventListener("blur", lostFocusListener, true); if (isSearchable()) { searchBox.get().clearSearch(); } @@ -1785,7 +1803,6 @@ private void setDropDown(boolean dropdown) { if (dropdown) { this.setAttribute("domino-ui-root-menu", true).setAttribute(DOMINO_UI_AUTO_CLOSABLE, true); menuElement.elevate(Elevation.LEVEL_1); - document.addEventListener("scroll", repositionListener, true); } else { this.removeAttribute("domino-ui-root-menu").removeAttribute(DOMINO_UI_AUTO_CLOSABLE); menuElement.elevate(Elevation.NONE); diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/notifications/Notification.java b/domino-ui/src/main/java/org/dominokit/domino/ui/notifications/Notification.java index ae382c713..c1a4c37b2 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/notifications/Notification.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/notifications/Notification.java @@ -55,7 +55,7 @@ public class Notification extends BaseDominoElement messageSpan; private final LazyChild closeButton; - private int duration = 4000; + private int duration = -1; private Transition inTransition = Transition.FADE_IN; private Transition outTransition = Transition.FADE_OUT; private SwapCssClass position = SwapCssClass.of(dui_ntf_top_left); @@ -277,13 +277,19 @@ public Notification expand() { .callback( e -> { if (!infinite) { - close(duration); + close(getDuration()); } }) .animate(); return this; } + private int getDuration() { + return this.duration > 0 + ? this.duration + : config().getUIConfig().getDefaultNotificationDuration(); + } + /** Closes the notification immediately. */ public final void close() { close(0); diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/pagination/AdvancedPagination.java b/domino-ui/src/main/java/org/dominokit/domino/ui/pagination/AdvancedPagination.java index 581dfd411..cc657eb18 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/pagination/AdvancedPagination.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/pagination/AdvancedPagination.java @@ -15,6 +15,7 @@ */ package org.dominokit.domino.ui.pagination; +import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import static org.dominokit.domino.ui.utils.Domino.*; @@ -133,7 +134,9 @@ public AdvancedPagination(int pages, int pageSize) { pagesSelect = Select.create() .addChangeListener( - (oldValue, newValue) -> moveToPage(newValue, isChangeListenersPaused())); + (oldValue, newValue) -> { + moveToPage(isNull(newValue) ? 1 : newValue, isChangeListenersPaused()); + }); pagesList.insertAfter( PagerNavItem.create(pagesSelect) diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/popover/Popover.java b/domino-ui/src/main/java/org/dominokit/domino/ui/popover/Popover.java index d4caa916e..b664fd086 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/popover/Popover.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/popover/Popover.java @@ -21,6 +21,8 @@ import elemental2.dom.EventListener; import elemental2.dom.HTMLElement; +import java.util.ArrayList; +import java.util.List; import org.dominokit.domino.ui.IsElement; import org.dominokit.domino.ui.animations.Transition; import org.dominokit.domino.ui.collapsible.AnimationCollapseStrategy; @@ -47,6 +49,7 @@ */ public class Popover extends BasePopover { + private static boolean asDialog = false; /** Static initialization block to add a global click event listener for closing popovers. */ static { document.body.addEventListener( @@ -54,14 +57,18 @@ public class Popover extends BasePopover { element -> { ModalBackDrop.INSTANCE.closePopovers(""); }); + + MediaQuery.addOnSmallAndDownListener(() -> asDialog = true); + + MediaQuery.addOnMediumAndUpListener(() -> asDialog = false); } private final EventListener showListener; private boolean openOnClick = true; private boolean closeOnEscape = true; - private boolean asDialog = false; private final DropDirection dialog = DropDirection.MIDDLE_SCREEN; private boolean modal = false; + private final List mediaListenersRecords = new ArrayList<>(); /** * Creates a new `Popover` instance for the specified HTML element target. @@ -102,14 +109,6 @@ public Popover(HTMLElement target) { new AnimationCollapseStrategy( Transition.FADE_IN, Transition.FADE_OUT, CollapsibleDuration._300ms)); - MediaQuery.addOnSmallAndDownListener( - () -> { - this.asDialog = true; - }); - MediaQuery.addOnMediumAndUpListener( - () -> { - this.asDialog = false; - }); addCollapseListener(() -> removeEventListener(DUI_REMOVE_POPOVERS, closeAllListener)); } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/search/SearchBox.java b/domino-ui/src/main/java/org/dominokit/domino/ui/search/SearchBox.java index 04f2bc04b..9c91863a5 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/search/SearchBox.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/search/SearchBox.java @@ -71,7 +71,7 @@ public class SearchBox extends BaseDominoElement implements HasLabels, HasComponentConfig { - private int autoSearchDelay; + private int autoSearchDelay = -1; private DivElement root; private final TextBox textBox; private boolean autoSearch = true; @@ -94,7 +94,7 @@ public static SearchBox create() { /** Constructs a new `SearchBox` instance with default settings. */ public SearchBox() { init(this); - this.autoSearchDelay = getConfig().getAutoSearchDelay(); + root = div().addCss(dui_quick_search); searchIcon = Icons.magnify() @@ -152,7 +152,7 @@ public void run() { autoSearchEventListener = evt -> { autoSearchTimer.cancel(); - autoSearchTimer.schedule(autoSearchDelay); + autoSearchTimer.schedule(getAutoSearchDelay()); }; setAutoSearch(true); @@ -224,7 +224,7 @@ public SearchBox setAutoSearch(boolean autoSearch) { * @return The auto search delay. */ public int getAutoSearchDelay() { - return autoSearchDelay; + return this.autoSearchDelay > 0 ? this.autoSearchDelay : getConfig().getAutoSearchDelay(); } /** diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/upload/DefaultFilePreviewContainer.java b/domino-ui/src/main/java/org/dominokit/domino/ui/upload/DefaultFilePreviewContainer.java index faf549d54..73ed1f8d2 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/upload/DefaultFilePreviewContainer.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/upload/DefaultFilePreviewContainer.java @@ -18,6 +18,7 @@ import static org.dominokit.domino.ui.utils.Domino.*; import elemental2.dom.HTMLDivElement; +import org.dominokit.domino.ui.grid.Column; import org.dominokit.domino.ui.grid.Row; import org.dominokit.domino.ui.utils.BaseDominoElement; import org.dominokit.domino.ui.utils.ChildHandler; @@ -50,7 +51,13 @@ public DefaultFilePreviewContainer() { */ @Override public DefaultFilePreviewContainer appendChild(FileItem fileItem) { - rootRow.span2(fileItem); + rootRow.appendChild( + Column.span2() + .apply( + self -> { + self.appendChild(fileItem); + fileItem.onDetached(mutationRecord -> self.remove()); + })); return this; } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/upload/FileItem.java b/domino-ui/src/main/java/org/dominokit/domino/ui/upload/FileItem.java index f6884df22..70196f88d 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/upload/FileItem.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/upload/FileItem.java @@ -442,8 +442,8 @@ public List getSuccessUploadHandlers() { * @return This FileItem instance for method chaining. */ public FileItem cancel() { + canceled = true; if (request != null) { - canceled = true; request.abort(); } return this; diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/upload/FileUpload.java b/domino-ui/src/main/java/org/dominokit/domino/ui/upload/FileUpload.java index 3522538df..54d8802f0 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/upload/FileUpload.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/upload/FileUpload.java @@ -16,12 +16,25 @@ package org.dominokit.domino.ui.upload; import static java.util.Objects.nonNull; -import static org.dominokit.domino.ui.utils.Domino.*; +import static org.dominokit.domino.ui.utils.Domino.div; +import static org.dominokit.domino.ui.utils.Domino.elementOf; +import static org.dominokit.domino.ui.utils.Domino.input; import static org.dominokit.domino.ui.utils.DominoUIConfig.CONFIG; -import elemental2.dom.*; -import java.util.*; +import elemental2.dom.DragEvent; +import elemental2.dom.Element; +import elemental2.dom.File; +import elemental2.dom.FileList; +import elemental2.dom.HTMLDivElement; +import elemental2.dom.HTMLElement; +import elemental2.dom.XMLHttpRequest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Collectors; import jsinterop.base.Js; import org.dominokit.domino.ui.IsElement; import org.dominokit.domino.ui.config.HasComponentConfig; @@ -98,6 +111,8 @@ public class FileUpload extends BaseDominoElement private UploadRequestSender requestSender = (XMLHttpRequest::send); private DropEffect dropEffect; + private UploadConfig config; + private boolean showPreview = true; /** * Creates a new instance of the `FileUpload` component. @@ -195,7 +210,7 @@ public FileUpload( elementOf(filesContainer.element()).addCss(dui_file_preview_container); init(this); root.addClickListener(evt -> hiddenFileInput.element().click()); - hiddenFileInput.addEventListener("change", evt -> uploadFiles(hiddenFileInput.element().files)); + hiddenFileInput.addEventListener("change", evt -> tryUpload(hiddenFileInput.element().files)); root.addEventListener( "drop", evt -> { @@ -210,14 +225,7 @@ public FileUpload( ((DragEvent) evt).dataTransfer.dropEffect = effect.getEffect(); } }); - int maxAllowed = hiddenFileInput.element().multiple ? maxAllowedUploads : 1; - if (maxAllowed > files.length) { - messagesContainer - .clearElement() - .setTextContent(getLabels().getMaxFileErrorMessage(maxAllowed, files.length)); - } else { - uploadFiles(files); - } + tryUpload(files); removeHover(); }); root.addEventListener( @@ -242,6 +250,37 @@ public FileUpload( }); } + private void tryUpload(FileList files) { + + int maxAllowed = isMultiUpload() ? maxAllowedUploads : 1; + int addedFiles = addedFileItems.size(); + List uploadedFiles = + addedFileItems.stream().filter(FileItem::isUploaded).collect(Collectors.toList()); + int remainingAllowed = Math.max(0, maxAllowed - (addedFiles - uploadedFiles.size())); + int existing = addedFiles - uploadedFiles.size(); + int ignored = files.length - remainingAllowed; + if (files.length > remainingAllowed) { + if (getConfig().isMaxUploadsOverflowAllowed()) { + uploadedFiles.forEach(FileItem::remove); + List toBeUploaded = files.asList().subList(0, remainingAllowed); + uploadFiles(toBeUploaded); + + messagesContainer + .clearElement() + .setTextContent( + getLabels() + .getMaxFileErrorMessage(maxAllowed, existing, remainingAllowed, ignored)); + } else { + messagesContainer + .clearElement() + .setTextContent( + getLabels().getMaxFileErrorMessage(maxAllowed, existing, 0, files.length)); + } + } else { + uploadFiles(files.asList()); + } + } + /** * Creates a new instance of the `FileUpload` component with a custom file preview factory, file * preview container, and decoration element. @@ -294,8 +333,9 @@ public FileUpload setDecoration(Element decoration) { * * @param maxAllowedUploads The maximum number of allowed uploads. */ - public void setMaxAllowedUploads(int maxAllowedUploads) { + public FileUpload setMaxAllowedUploads(int maxAllowedUploads) { this.maxAllowedUploads = maxAllowedUploads; + return this; } /** @@ -333,17 +373,29 @@ private void addHover() { * * @param files The list of files to upload. */ - public void uploadFiles(FileList files) { - for (int i = 0; i < files.length; i++) { - File file = files.item(i); + public FileUpload uploadFiles(List files) { + for (int i = 0; i < files.size(); i++) { + File file = files.get(i); addFilePreview(file); } hiddenFileInput.element().value = ""; + return this; } /** Uploads all added files to the server. */ - public void uploadAllFiles() { + public FileUpload uploadAllFiles() { addedFileItems.forEach(fileItem -> fileItem.upload(requestSender)); + return this; + } + + @Override + public UploadConfig getOwnConfig() { + return config; + } + + public FileUpload setConfig(UploadConfig config) { + this.config = config; + return this; } /** @@ -353,21 +405,24 @@ public void uploadAllFiles() { */ private void addFilePreview(File file) { if (isMultiUpload()) { - removeFileItems(); + removeUploadedFiles(); } FileItem fileItem = FileItem.create(file, new UploadOptions(), filePreviewFactory, this); + fileItem.addRemoveHandler( + removedFile -> { + addedFileItems.remove(fileItem); + }); + fileItemHandlers.forEach(handler -> handler.handle(fileItem)); fileItem.validateSize(); - filesContainer.appendChild(fileItem); - addedFileItems.add(fileItem); + if (showPreview) { + filesContainer.appendChild(fileItem); + } - fileItem.addRemoveHandler( - removedFile -> { - addedFileItems.remove(fileItem); - }); + addedFileItems.add(fileItem); if (fileItem.isCanceled()) { fileItem.remove(); @@ -397,6 +452,12 @@ public HTMLDivElement element() { */ public FileUpload setMultiUpload(boolean multiUpload) { hiddenFileInput.element().multiple = multiUpload; + if (multiUpload) { + hiddenFileInput.setAttribute("multiple", true); + } else { + hiddenFileInput.removeAttribute("multiple"); + } + return this; } @@ -511,6 +572,13 @@ public FileUpload removeFileItems() { return this; } + private void removeUploadedFiles() { + List uploaded = + addedFileItems.stream().filter(FileItem::isUploaded).collect(Collectors.toList()); + addedFileItems.removeAll(uploaded); + uploaded.forEach(FileItem::remove); + } + /** * Gets a list of file item handlers that are executed when a file is added to the component. * @@ -575,6 +643,23 @@ public FileUpload setDropEffect(DropEffect dropEffect) { return this; } + /** @return true if uploaded files will show a preview in the preview container */ + public boolean isShowPreview() { + return showPreview; + } + + /** + * When set to true, uploaded files will show a preview in the preview container, otherwise they + * wont + * + * @param showPreview boolean. + * @return same component instance + */ + public FileUpload setShowPreview(boolean showPreview) { + this.showPreview = showPreview; + return this; + } + /** * A functional interface for handling file items when they are added to the `FileUpload` * component. diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BaseDominoElement.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BaseDominoElement.java index d3ead7fe6..fa22d7d7b 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BaseDominoElement.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BaseDominoElement.java @@ -694,6 +694,24 @@ public T nowOrWhenAttached(Runnable handler) { return (T) this; } + /** + * Executes a given handler either immediately if the element is already detached from the DOM or + * when it gets detached. + * + * @param handler The handler to execute. + * @return The modified DOM element. + */ + @Editor.Ignore + public T nowOrWhenDetached(Runnable handler) { + if (isAttached()) { + onDetached(mutationRecord -> handler.run()); + } else { + handler.run(); + } + dominoUuidInitializer.apply(); + return (T) this; + } + /** * Executes a given handler when the element is attached to the DOM. If the element is already * attached, the handler is executed immediately. @@ -711,6 +729,23 @@ public T nowAndWhenAttached(Runnable handler) { return (T) this; } + /** + * Executes a given handler when the element is detached from the DOM. If the element is already + * detached, the handler is executed immediately. + * + * @param handler The handler to execute. + * @return The modified DOM element. + */ + @Editor.Ignore + public T nowAndWhenDetached(Runnable handler) { + if (!isAttached()) { + handler.run(); + } + onDetached(mutationRecord -> handler.run()); + dominoUuidInitializer.apply(); + return (T) this; + } + /** * Registers a resize handler to be notified when the size of this element changes. * diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BodyObserver.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BodyObserver.java index f4b195222..e3479b497 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BodyObserver.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/BodyObserver.java @@ -24,13 +24,16 @@ import elemental2.core.JsArray; import elemental2.dom.CustomEvent; import elemental2.dom.CustomEventInit; +import elemental2.dom.Element; import elemental2.dom.Event; import elemental2.dom.HTMLElement; import elemental2.dom.MutationObserver; import elemental2.dom.MutationObserverInit; import elemental2.dom.MutationRecord; import elemental2.dom.Node; +import java.util.HashSet; import java.util.List; +import java.util.Set; import jsinterop.base.Js; /** @@ -96,52 +99,64 @@ private static void observe() { private static void onElementsAppended(MutationRecord record) { List nodes = record.addedNodes.asList(); + Set processed = new HashSet<>(); for (int i = 0; i < nodes.size(); i++) { Node elementNode = Js.uncheckedCast(nodes.get(i)); if (Node.ELEMENT_NODE == elementNode.nodeType) { HTMLElement element = Js.uncheckedCast(elementNode); + List> childElements = + elements.elementOf(element).querySelectorAll("[" + ATTACH_UID_KEY + "]"); if (element.hasAttribute(ATTACH_UID_KEY)) { - element.dispatchEvent( - new CustomEvent<>(AttachDetachEventType.attachedType(elements.elementOf(element)))); + String type = AttachDetachEventType.attachedType(elements.elementOf(element)); + if (!processed.contains(type)) { + processed.add(type); + element.dispatchEvent(new CustomEvent<>(type)); + } } - elements - .elementOf(element) - .querySelectorAll("[" + ATTACH_UID_KEY + "]") - .forEach( - child -> { - CustomEventInit ceinit = CustomEventInit.create(); - ceinit.setDetail(record); - CustomEvent event = - new CustomEvent<>( - AttachDetachEventType.attachedType(elements.elementOf(child)), ceinit); - child.element().dispatchEvent(event); - }); + + childElements.forEach( + child -> { + CustomEventInit ceinit = CustomEventInit.create(); + ceinit.setDetail(record); + String type = AttachDetachEventType.attachedType(elements.elementOf(child)); + if (!processed.contains(type)) { + processed.add(type); + CustomEvent event = new CustomEvent<>(type, ceinit); + child.element().dispatchEvent(event); + } + }); } } } private static void onElementsRemoved(MutationRecord record) { List nodes = record.removedNodes.asList(); + Set processed = new HashSet<>(); for (int i = 0; i < nodes.size(); i++) { Node elementNode = Js.uncheckedCast(nodes.get(i)); if (Node.ELEMENT_NODE == elementNode.nodeType) { HTMLElement element = Js.uncheckedCast(elementNode); + List> childElements = + elements.elementOf(element).querySelectorAll("[" + DETACH_UID_KEY + "]"); if (element.hasAttribute(DETACH_UID_KEY)) { - element.dispatchEvent( - new Event(AttachDetachEventType.detachedType(elements.elementOf(element)))); + String type = AttachDetachEventType.detachedType(elements.elementOf(element)); + if (!processed.contains(type)) { + processed.add(type); + element.dispatchEvent(new Event(type)); + } } - elements - .elementOf(element) - .querySelectorAll("[" + DETACH_UID_KEY + "]") - .forEach( - child -> { - CustomEventInit ceinit = CustomEventInit.create(); - ceinit.setDetail(record); - CustomEvent event = - new CustomEvent<>( - AttachDetachEventType.detachedType(elements.elementOf(child)), ceinit); - child.element().dispatchEvent(event); - }); + + childElements.forEach( + child -> { + String type = AttachDetachEventType.detachedType(elements.elementOf(child)); + if (!processed.contains(type)) { + processed.add(type); + CustomEventInit ceinit = CustomEventInit.create(); + ceinit.setDetail(record); + CustomEvent event = new CustomEvent<>(type, ceinit); + child.element().dispatchEvent(event); + } + }); } } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/Clipboard.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/Clipboard.java index 4b716e44d..04045061d 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/Clipboard.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/Clipboard.java @@ -16,10 +16,13 @@ package org.dominokit.domino.ui.utils; import elemental2.core.JsArray; +import elemental2.dom.DomGlobal; import elemental2.promise.Promise; +import jsinterop.annotations.JsOverlay; import jsinterop.annotations.JsPackage; import jsinterop.annotations.JsType; import jsinterop.base.Any; +import jsinterop.base.Js; /** * A class for interacting with the clipboard to read and write data. @@ -59,4 +62,24 @@ public class Clipboard { * @return A promise that resolves when the text data is successfully written to the clipboard. */ public native Promise writeText(String text); + + @JsOverlay + public static Promise> get() { + return Js.uncheckedCast(DomGlobal.window.navigator).clipboard.read(); + } + + @JsOverlay + public static Promise getText() { + return Js.uncheckedCast(DomGlobal.window.navigator).clipboard.readText(); + } + + @JsOverlay + public static Promise put(ClipboardItem item) { + return Js.uncheckedCast(DomGlobal.window.navigator).clipboard.write(item); + } + + @JsOverlay + public static Promise put(String text) { + return Js.uncheckedCast(DomGlobal.window.navigator).clipboard.writeText(text); + } } diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/DelayedExecution.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/DelayedExecution.java index 6b30fd05b..71fc727b3 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/DelayedExecution.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/DelayedExecution.java @@ -15,6 +15,7 @@ */ package org.dominokit.domino.ui.utils; +import org.dominokit.domino.ui.config.DelayedActionConfig; import org.gwtproject.timer.client.Timer; /** @@ -33,6 +34,16 @@ */ public class DelayedExecution { + /** + * Executes the specified {@code delayedAction} after the specified delay defined in {@link + * DelayedActionConfig#getDelayedExecutionDefaultDelay()}. + * + * @param delayedAction The action to be executed after the delay. + */ + public static void execute(DelayedAction delayedAction) { + execute(delayedAction, DominoUIConfig.CONFIG.getUIConfig().getDelayedExecutionDefaultDelay()); + } + /** * Executes the specified {@code delayedAction} after the specified {@code delay} in milliseconds. * diff --git a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/DelayedTextInput.java b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/DelayedTextInput.java index f21244c57..0bfade70f 100644 --- a/domino-ui/src/main/java/org/dominokit/domino/ui/utils/DelayedTextInput.java +++ b/domino-ui/src/main/java/org/dominokit/domino/ui/utils/DelayedTextInput.java @@ -60,6 +60,22 @@ public static DelayedTextInput create( return new DelayedTextInput(inputElement, delay, delayedAction); } + /** + * Creates a {@code DelayedTextInput} instance for the given HTML input element with a default + * delay and an action to execute on text input changes. + * + * @param inputElement The HTML input element to monitor for text input changes. + * @param delayedAction The action to execute when text input changes after the specified delay. + * @return A {@code DelayedTextInput} instance. + */ + public static DelayedTextInput create( + HTMLInputElement inputElement, DelayedAction delayedAction) { + return new DelayedTextInput( + inputElement, + DominoUIConfig.CONFIG.getUIConfig().getDelayedExecutionDefaultDelay(), + delayedAction); + } + /** * Creates a {@code DelayedTextInput} instance for the given HTML input element with a specified * delay. @@ -72,6 +88,18 @@ public static DelayedTextInput create(HTMLInputElement inputElement, int delay) return new DelayedTextInput(inputElement, delay); } + /** + * Creates a {@code DelayedTextInput} instance for the given HTML input element with a default + * delay. + * + * @param inputElement The HTML input element to monitor for text input changes. + * @return A {@code DelayedTextInput} instance. + */ + public static DelayedTextInput create(HTMLInputElement inputElement) { + return new DelayedTextInput( + inputElement, DominoUIConfig.CONFIG.getUIConfig().getDelayedExecutionDefaultDelay()); + } + /** * Creates a {@code DelayedTextInput} instance for the given DominoElement with a specified delay. * @@ -84,6 +112,19 @@ public static DelayedTextInput create(DominoElement inputEleme return create(inputElement.element(), delay); } + /** + * Creates a {@code DelayedTextInput} instance for the given DominoElement with a default delay. + * + * @param inputElement The DominoElement wrapping the HTML input element to monitor for text input + * changes. + * @return A {@code DelayedTextInput} instance. + */ + public static DelayedTextInput create(DominoElement inputElement) { + return create( + inputElement.element(), + DominoUIConfig.CONFIG.getUIConfig().getDelayedExecutionDefaultDelay()); + } + /** * Creates a {@code DelayedTextInput} instance for the given InputElement with a specified delay. * @@ -96,6 +137,19 @@ public static DelayedTextInput create(InputElement inputElement, int delay) { return create(inputElement.element(), delay); } + /** + * Creates a {@code DelayedTextInput} instance for the given InputElement with a default delay. + * + * @param inputElement The DominoElement wrapping the HTML input element to monitor for text input + * changes. + * @return A {@code DelayedTextInput} instance. + */ + public static DelayedTextInput create(InputElement inputElement) { + return create( + inputElement.element(), + DominoUIConfig.CONFIG.getUIConfig().getDelayedExecutionDefaultDelay()); + } + /** * Constructs a {@code DelayedTextInput} instance for the given HTML input element with a * specified delay. diff --git a/pom.xml b/pom.xml index a0bd89b7f..e1bc3b3d7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.dominokit domino-ui-parent - 2.0.1 + 2.0.2 pom domino-ui-parent @@ -68,7 +68,7 @@ HEAD-SNAPSHOT - 2.0.1 + 2.0.2 11 11