From d10a6ee30e1b61ebebdcbc9ef23fab72de32601f Mon Sep 17 00:00:00 2001 From: Paola De Bartolo Date: Thu, 4 Jun 2026 16:18:23 -0300 Subject: [PATCH 1/3] feat: add listener for share actions Close #12 --- README.md | 15 +++ .../addons/shareeasy/BaseShareEasy.java | 33 ++++++- .../addons/shareeasy/ShareEasyClickEvent.java | 93 +++++++++++++++++++ .../shareeasy/ShareEasyClickListener.java | 43 +++++++++ .../vaadin/addons/shareeasy/enums/Driver.java | 18 ++++ .../frontend/src/fc-sharee-connector.js | 40 +++++++- .../addons/shareeasy/NormalModeDemo.java | 17 +++- .../addons/shareeasy/it/ShareEasyElement.java | 17 ++-- .../shareeasy/it/ShareEasyNormalModeIT.java | 17 ++++ 9 files changed, 283 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/flowingcode/vaadin/addons/shareeasy/ShareEasyClickEvent.java create mode 100644 src/main/java/com/flowingcode/vaadin/addons/shareeasy/ShareEasyClickListener.java diff --git a/README.md b/README.md index 8c49dbe..93aebbc 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This component is a wrapper of [Sharee](https://github.com/parsagholipour/sharee - Default social media drivers to share links easily: Copy, Twitter, Facebook, Linkedin, Whatsapp, Telegram. - Modes to display the Easy Share menu: Fixed, Normal, Hover, Text, Dropdown. - By defaut, locale and keys are in English but custom locales and keys can be defined. +- Listen to share actions to track which driver was clicked. ## Supported versions @@ -114,6 +115,20 @@ NormalShareEasy.create().withNoTitle(true).forComponent(div); add(div); ``` +## Listening to share actions + +Register a listener to be notified when one of the share drivers is clicked. The event exposes the +clicked driver (both as the raw name and, for default drivers, as a `Driver` enum value) and the +share link: + +```java +Div div = new Div(); +NormalShareEasy.create() + .withShareListener(event -> Notification.show("Shared via " + event.getDriverName())) + .forComponent(div); +add(div); +``` + ## Special configuration when using Spring By default, Vaadin Flow only includes ```com/vaadin/flow/component``` to be always scanned for UI components and views. For this reason, the addon might need to be whitelisted in order to display correctly. diff --git a/src/main/java/com/flowingcode/vaadin/addons/shareeasy/BaseShareEasy.java b/src/main/java/com/flowingcode/vaadin/addons/shareeasy/BaseShareEasy.java index 51e0385..5d38941 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/shareeasy/BaseShareEasy.java +++ b/src/main/java/com/flowingcode/vaadin/addons/shareeasy/BaseShareEasy.java @@ -31,6 +31,7 @@ import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.component.dependency.NpmPackage; import elemental.json.JsonObject; +import elemental.json.JsonString; /** * BaseSharee represents the base class for creating Sharee instances. @@ -49,9 +50,11 @@ class BaseShareEasy> { private Options options = new Options(); Map customDrivers = new HashMap<>(); - + protected boolean withDefaultDriversListFirst = true; + private ShareEasyClickListener shareListener; + /** * Sets the share link for the shared content. * @@ -121,6 +124,17 @@ public T withDrivers(ShareEasyDriver... drivers) { return (T) this; } + /** + * Sets the listener that is notified when one of the share drivers is clicked. + * + * @param shareListener the listener to notify on driver clicks + * @return The current instance of the BaseSharee class + */ + public T withShareListener(ShareEasyClickListener shareListener) { + this.shareListener = shareListener; + return (T) this; + } + /** * Initializes the Sharee component with the specified Vaadin component. * @@ -141,6 +155,7 @@ protected JsonObject getJsonObjectOptions() { } private void createSharee() { + registerShareListener(); String shareeOptions = getJsonObjectOptions().toJson(); if(customDrivers.isEmpty()) { this.create(shareeOptions); @@ -162,6 +177,22 @@ private void create(String shareeOptions) { component.getElement().executeJs("fcShareeConnector.create(this, $0)", shareeOptions); } + private void registerShareListener() { + if (shareListener == null) { + return; + } + component.getElement().addEventListener("driver-clicked", event -> { + JsonObject detail = event.getEventData(); + String driverName = getStringOrNull(detail, "event.detail.name"); + String link = getStringOrNull(detail, "event.detail.link"); + shareListener.onShare(new ShareEasyClickEvent(component, driverName, link)); + }).addEventData("event.detail.name").addEventData("event.detail.link"); + } + + private static String getStringOrNull(JsonObject data, String key) { + return data.hasKey(key) && data.get(key) instanceof JsonString ? data.getString(key) : null; + } + /** * Sets custom language keys for the default locale. * diff --git a/src/main/java/com/flowingcode/vaadin/addons/shareeasy/ShareEasyClickEvent.java b/src/main/java/com/flowingcode/vaadin/addons/shareeasy/ShareEasyClickEvent.java new file mode 100644 index 0000000..a3bb648 --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/shareeasy/ShareEasyClickEvent.java @@ -0,0 +1,93 @@ +/*- + * #%L + * Share Easy Add-on + * %% + * Copyright (C) 2023 - 2026 Flowing Code + * %% + * 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. + * #L% + */ +package com.flowingcode.vaadin.addons.shareeasy; + +import java.io.Serializable; +import java.util.Objects; +import java.util.Optional; +import com.flowingcode.vaadin.addons.shareeasy.enums.Driver; +import com.vaadin.flow.component.Component; + +/** + * Event fired when one of the share drivers is clicked. + * + * @author Paola De Bartolo / Flowing Code + */ +@SuppressWarnings("serial") +public class ShareEasyClickEvent implements Serializable { + + private final Component source; + + private final String driverName; + + private final String link; + + /** + * Creates a new share click event. + * + * @param source the component the Share Easy instance is attached to + * @param driverName the name of the clicked driver, e.g. "telegram" + * @param link the share link associated with the driver (the copied URL for the copy driver); may + * be {@code null} for custom drivers without a link + */ + public ShareEasyClickEvent(Component source, String driverName, String link) { + this.source = Objects.requireNonNull(source, "source must not be null"); + this.driverName = Objects.requireNonNull(driverName, "driverName must not be null"); + this.link = link; + } + + /** + * Gets the component the Share Easy instance is attached to. + * + * @return the source component + */ + public Component getSource() { + return source; + } + + /** + * Gets the name of the clicked driver. + * + * @return the driver name, e.g. "telegram" + */ + public String getDriverName() { + return driverName; + } + + /** + * Gets the clicked driver as a {@link Driver default driver}, if it corresponds to one. + * + * @return the matching default {@link Driver}, or an empty {@link Optional} if the clicked driver + * is a custom driver + */ + public Optional getDriver() { + return Driver.fromName(driverName); + } + + /** + * Gets the share link associated with the clicked driver. For the copy driver this is the URL that + * was copied to the clipboard. + * + * @return the share link, or {@code null} for custom drivers without a link + */ + public String getLink() { + return link; + } +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/shareeasy/ShareEasyClickListener.java b/src/main/java/com/flowingcode/vaadin/addons/shareeasy/ShareEasyClickListener.java new file mode 100644 index 0000000..37ee20c --- /dev/null +++ b/src/main/java/com/flowingcode/vaadin/addons/shareeasy/ShareEasyClickListener.java @@ -0,0 +1,43 @@ +/*- + * #%L + * Share Easy Add-on + * %% + * Copyright (C) 2023 - 2026 Flowing Code + * %% + * 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. + * #L% + */ +package com.flowingcode.vaadin.addons.shareeasy; + +import com.vaadin.flow.function.SerializableConsumer; + +/** + * Listener notified when one of the share drivers is clicked. + * + * @author Paola De Bartolo / Flowing Code + */ +@FunctionalInterface +public interface ShareEasyClickListener extends SerializableConsumer { + + /** + * Invoked when a share driver is clicked. + * + * @param event the share click event + */ + void onShare(ShareEasyClickEvent event); + + @Override + default void accept(ShareEasyClickEvent event) { + onShare(event); + } +} diff --git a/src/main/java/com/flowingcode/vaadin/addons/shareeasy/enums/Driver.java b/src/main/java/com/flowingcode/vaadin/addons/shareeasy/enums/Driver.java index f9f6165..5140cb4 100644 --- a/src/main/java/com/flowingcode/vaadin/addons/shareeasy/enums/Driver.java +++ b/src/main/java/com/flowingcode/vaadin/addons/shareeasy/enums/Driver.java @@ -19,6 +19,7 @@ */ package com.flowingcode.vaadin.addons.shareeasy.enums; +import java.util.Optional; import com.flowingcode.vaadin.addons.shareeasy.ShareEasyDriver; /** @@ -40,4 +41,21 @@ private Driver(String name) { public String getName() { return name; } + + /** + * Resolves a default driver from its name (the name carried by the share event), if it matches one + * of the default drivers. + * + * @param name the driver name, e.g. "telegram" + * @return the matching {@link Driver}, or an empty {@link Optional} if the name does not correspond + * to a default driver (for instance, a custom driver) + */ + public static Optional fromName(String name) { + for (Driver driver : values()) { + if (driver.name.equals(name)) { + return Optional.of(driver); + } + } + return Optional.empty(); + } } diff --git a/src/main/resources/META-INF/resources/frontend/src/fc-sharee-connector.js b/src/main/resources/META-INF/resources/frontend/src/fc-sharee-connector.js index 05f26b5..8f2bd64 100644 --- a/src/main/resources/META-INF/resources/frontend/src/fc-sharee-connector.js +++ b/src/main/resources/META-INF/resources/frontend/src/fc-sharee-connector.js @@ -24,6 +24,7 @@ window.fcShareeConnector = { create: function (container, optionsJson) { this._updateXButtonLabel(); this._updateCopyDriverOnClick(); + this._patchDriverNames(); let parsedOptions = JSON.parse(optionsJson); const sharee = new Sharee(container, parsedOptions); }, @@ -31,6 +32,7 @@ window.fcShareeConnector = { createWithCustomDrivers: function (container, optionsJson) { this._updateXButtonLabel(); this._updateCopyDriverOnClick(); + this._patchDriverNames(); let parsedOptions = JSON.parse(optionsJson); // add the custom drivers to the list of drivers let drivers = parsedOptions.drivers.concat(container.customDrivers); @@ -95,6 +97,26 @@ window.fcShareeConnector = { */ _updateCopyDriverOnClick() { Sharee.drivers['copy'].prototype.onClick = function(e) { + // The copy driver copies a URL instead of navigating, so the shared + // "link" is the text that gets copied (the base CopyDriver's getLink() + // is undefined). Dispatch the same "driver-clicked" event the base + // driver emits via super.onClick (lost when onClick is fully replaced + // here), carrying the actual copied URL. + const copyText = this.options?.shareLink || window.location.href + const event = new CustomEvent('driver-clicked', { + detail: { + name: this.getName(), + link: copyText, + originalEvent: e, + }, + bubbles: true, + composed: true, + }); + this.options?.targetElement.dispatchEvent(event); + // Honor preventDefault as the base CopyDriver does after super.onClick. + if (e.defaultPrevented) { + return + } const successText = this.lang['CopiedSuccessfully'] const el = e.currentTarget; const textEl = el.querySelector('div:nth-child(2)') @@ -102,7 +124,6 @@ window.fcShareeConnector = { textEl.innerHTML = this.getButtonText() return } - let copyText = this.options?.shareLink || window.location.href navigator.clipboard.writeText(copyText).then(() => { textEl.innerHTML = successText textEl.style.transition = '300ms all' @@ -125,5 +146,20 @@ window.fcShareeConnector = { Sharee.drivers['x'].prototype.getButtonText = function () { return "X"; } - } + }, + + /** + * Overrides getName on each default driver so that it returns the driver's + * registered key (e.g. "telegram", "copy") instead of the bundled (and + * unstable) class name. This makes the name carried by the "driver-clicked" + * event reliable on the server side. Custom drivers already define their own + * getName in _addDriver, so they are not affected. + */ + _patchDriverNames() { + Object.keys(Sharee.drivers).forEach(function (name) { + Sharee.drivers[name].prototype.getName = function () { + return name; + }; + }); + } } diff --git a/src/test/java/com/flowingcode/vaadin/addons/shareeasy/NormalModeDemo.java b/src/test/java/com/flowingcode/vaadin/addons/shareeasy/NormalModeDemo.java index 609642b..5557cae 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/shareeasy/NormalModeDemo.java +++ b/src/test/java/com/flowingcode/vaadin/addons/shareeasy/NormalModeDemo.java @@ -27,6 +27,7 @@ import com.flowingcode.vaadin.addons.shareeasy.util.CustomDriverOptions; import com.flowingcode.vaadin.addons.shareeasy.util.LanguageKeys; import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.notification.Notification; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; @@ -127,12 +128,26 @@ public NormalModeDemo() { customDrivers.put("Trello", new TrelloDriverOptions()); NormalShareEasy.create().withCustomDrivers(customDrivers).forComponent(normalDiv7); // #if vaadin eq 0 - Div example7 = createContainerDiv("With custom driver for extra social: Trello", normalDiv7); + Div example7 = createContainerDiv("With custom driver for extra social: Trello", normalDiv7); SourceCodeViewer.highlightOnHover(example7, "example7"); add(example7); + addSeparator(); // #endif // show-source add(normalDiv7); // end-block + + // begin-block example8 + Div normalDiv8 = new Div(); + NormalShareEasy.create() + .withShareListener(event -> Notification.show("Shared via " + event.getDriverName())) + .forComponent(normalDiv8); + // #if vaadin eq 0 + Div example8 = createContainerDiv("With share listener", normalDiv8); + SourceCodeViewer.highlightOnHover(example8, "example8"); + add(example8); + // #endif + // show-source add(normalDiv8); + // end-block } } diff --git a/src/test/java/com/flowingcode/vaadin/addons/shareeasy/it/ShareEasyElement.java b/src/test/java/com/flowingcode/vaadin/addons/shareeasy/it/ShareEasyElement.java index e74e794..275d7aa 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/shareeasy/it/ShareEasyElement.java +++ b/src/test/java/com/flowingcode/vaadin/addons/shareeasy/it/ShareEasyElement.java @@ -30,27 +30,32 @@ public List getAllDriversAnchors() { } public boolean copyExists() { - return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__F").exists(); + return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__copy").exists(); } public boolean telegramExists() { - return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__I").exists(); + return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__telegram") + .exists(); } public boolean facebookExists() { - return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__G").exists(); + return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__facebook") + .exists(); } public boolean whatsappExists() { - return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__P").exists(); + return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__whatsapp") + .exists(); } public boolean twitterExists() { - return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__W").exists(); + return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__twitter") + .exists(); } public boolean linkedinExists() { - return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__A").exists(); + return this.$(AnchorElement.class).attributeContains("class", "sharee__driver__linkedin") + .exists(); } public boolean normalModeContainAllDefaultValues() { diff --git a/src/test/java/com/flowingcode/vaadin/addons/shareeasy/it/ShareEasyNormalModeIT.java b/src/test/java/com/flowingcode/vaadin/addons/shareeasy/it/ShareEasyNormalModeIT.java index ac62fdb..64fc813 100644 --- a/src/test/java/com/flowingcode/vaadin/addons/shareeasy/it/ShareEasyNormalModeIT.java +++ b/src/test/java/com/flowingcode/vaadin/addons/shareeasy/it/ShareEasyNormalModeIT.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; import com.vaadin.flow.component.html.testbench.AnchorElement; +import com.vaadin.flow.component.notification.testbench.NotificationElement; public class ShareEasyNormalModeIT extends BaseShareEasyIT { @@ -72,4 +73,20 @@ public void withExtraSocial() { assertTrue("Normal Share Easy shows no extra social", driversCount == DEFAULT_NORMAL_DRIVER_COUNT + 1); } + + @Test + public void withShareListener_clickDriver_firesEvent() { + // The Sharee instances are created asynchronously via executeJs, so wait until the + // share-listener example (the 8th normal share) has rendered before interacting. + waitUntil(driver -> $(ShareEasyElement.class).attributeContains("class", "sharee__normal").all() + .size() > 7); + ShareEasyElement normalWithShareListener = + $(ShareEasyElement.class).attributeContains("class", "sharee__normal").get(7); + AnchorElement copyDriver = normalWithShareListener.$(AnchorElement.class) + .attributeContains("class", "sharee__driver__copy").waitForFirst(); + copyDriver.click(); + NotificationElement notification = $(NotificationElement.class).waitForFirst(); + assertTrue("Share listener was not notified with the clicked driver name", + notification.getText().contains("copy")); + } } From 6f8366ebe3b2ca4d6c72ddc7a315e7158bf76411 Mon Sep 17 00:00:00 2001 From: Paola De Bartolo Date: Thu, 4 Jun 2026 16:18:55 -0300 Subject: [PATCH 2/3] build: bump webdrivermanager to 5.9.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0264d3e..156376e 100644 --- a/pom.xml +++ b/pom.xml @@ -148,7 +148,7 @@ io.github.bonigarcia webdrivermanager - 5.1.1 + 5.9.2 test From 278285fef9cddd9d782f7fa3322419f1141dc3ab Mon Sep 17 00:00:00 2001 From: Paola De Bartolo Date: Thu, 4 Jun 2026 16:21:28 -0300 Subject: [PATCH 3/3] build: set version to 2.2.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 156376e..5df14f4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.vaadin.addons.flowingcode share-easy-addon - 2.1.1-SNAPSHOT + 2.2.0-SNAPSHOT Share Easy Add-on Share Eady Add-on for Vaadin Flow