Skip to content

Commit

Permalink
feat: Add CompletableFuture WebStorage API (#19747)
Browse files Browse the repository at this point in the history
Adds CompletableFuture methods for WebStorage.getItem.

Fixes #19743
  • Loading branch information
Dudeplayz authored Aug 7, 2024
1 parent fb453f1 commit ed02ec3
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
* evaluation.
* <p>
* If any of the <code>then</code> or <code>toCompletableFuture</code> methods
* have been invoked before the snippet is sent to the browser, then the there
* will be an additional round trip for sending the results of the evaluation
* back to any registered handler. If the JavaScript execution returns a
* have been invoked before the snippet is sent to the browser, then there will
* be an additional round trip for sending the results of the evaluation back to
* any registered handler. If the JavaScript execution returns a
* <code>Promise</code>, then the result will be sent to the server only when it
* is resolved.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
*/
package com.vaadin.flow.component.page;

import com.vaadin.flow.component.UI;
import java.io.Serializable;
import java.util.concurrent.CompletableFuture;

import org.slf4j.LoggerFactory;

import com.vaadin.flow.component.UI;

/**
* Wrapper for similarly named Browser API. WebStorage may be handy to save some
Expand Down Expand Up @@ -177,35 +181,37 @@ public static void clear(UI ui, Storage storage) {
}

/**
* Asynchronously gets an item from the Storage.localStorage
* Asynchronously gets an item from the local storage.
*
* @param key
* the key for which the value will be fetched
* @param callback
* the callback that gets the value once transferred from the
* client side
* client side or <code>null</code> if the value was not
* available.
*/
public static void getItem(String key, Callback callback) {
getItem(Storage.LOCAL_STORAGE, key, callback);
}

/**
* Asynchronously gets an item from the given storage
* Asynchronously gets an item from the given storage.
*
* @param storage
* the storage
* @param key
* the key for which the value will be fetched
* @param callback
* the callback that gets the value once transferred from the
* client side
* client side or <code>null</code> if the value was not
* available.
*/
public static void getItem(Storage storage, String key, Callback callback) {
getItem(UI.getCurrent(), storage, key, callback);
}

/**
* Asynchronously gets an item from the given storage
* Asynchronously gets an item from the given storage.
*
* @param ui
* the UI for which the storage is related to
Expand All @@ -215,17 +221,90 @@ public static void getItem(Storage storage, String key, Callback callback) {
* the key for which the value will be fetched
* @param callback
* the callback that gets the value once transferred from the
* client side
* client side or <code>null</code> if the value was not
* available.
*/
public static void getItem(UI ui, Storage storage, String key,
Callback callback) {
ui.getPage()
.executeJs("return window[$0].getItem($1);", storage.toString(),
key)
.then(String.class, callback::onValueDetected, s -> {
// for error (most likely non-existing mapping), return null
requestItem(ui, storage, key).then(String.class,
callback::onValueDetected, s -> {
LoggerFactory.getLogger(WebStorage.class.getName()).debug(
"Error while getting value for key '{}' from storage '{}': {}",
key, storage, s);
// fallback to null if there was an error
callback.onValueDetected(null);
});
}

/**
* Asynchronously gets an item from the local storage.
* <p>
* It is not possible to synchronously wait for the result of the execution
* while holding the session lock since the request handling thread that
* makes the result available will also need to lock the session. <br>
* See {@link PendingJavaScriptResult#toCompletableFuture} for more
* information.
*
* @param key
* the key for which the value will be fetched
* @return a CompletableFuture that will be completed with the value once
* transferred from the client side or <code>null</code> if the
* value was not available.
*/
public static CompletableFuture<String> getItem(String key) {
return getItem(Storage.LOCAL_STORAGE, key);
}

/**
* Asynchronously gets an item from the given storage.
* <p>
* It is not possible to synchronously wait for the result of the execution
* while holding the session lock since the request handling thread that
* makes the result available will also need to lock the session. <br>
* See {@link PendingJavaScriptResult#toCompletableFuture} for more
* information.
*
* @param storage
* the storage
* @param key
* the key for which the value will be fetched
* @return a CompletableFuture that will be completed with the value once
* transferred from the client side or <code>null</code> if the
* value was not available.
*/
public static CompletableFuture<String> getItem(Storage storage,
String key) {
return getItem(UI.getCurrent(), storage, key);
}

/**
* Asynchronously gets an item from the given storage.
* <p>
* It is not possible to synchronously wait for the result of the execution
* while holding the session lock since the request handling thread that
* makes the result available will also need to lock the session. <br>
* See {@link PendingJavaScriptResult#toCompletableFuture} for more
* information.
*
* @param ui
* the UI for which the storage is related to
* @param storage
* the storage
* @param key
* the key for which the value will be fetched
* @return a CompletableFuture that will be completed with the value once
* transferred from the client side or <code>null</code> if the
* value was not available.
*/
public static CompletableFuture<String> getItem(UI ui, Storage storage,
String key) {
return requestItem(ui, storage, key).toCompletableFuture(String.class);
}

private static PendingJavaScriptResult requestItem(UI ui, Storage storage,
String key) {
return ui.getPage().executeJs("return window[$0].getItem($1);",
storage.toString(), key);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ public WebStorageView() {
value.setId("input");
NativeButton setData = new NativeButton();
NativeButton detect = new NativeButton();
NativeButton detectCF = new NativeButton();
NativeButton remove = new NativeButton();
NativeButton clear = new NativeButton();
Div msg = new Div();
msg.setId("msg");
add(value, setData, detect, remove, clear, msg);
add(value, setData, detect, detectCF, remove, clear, msg);

value.setValue(LocalDateTime.now().toString());

Expand All @@ -58,6 +59,18 @@ public WebStorageView() {
});
});

detectCF.setText("Detect CompletableFuture");
detectCF.setId("detectCF");
detectCF.addClickListener(e -> {
WebStorage.getItem("test").thenAccept(v -> {
if (v == null) {
msg.setText(VALUE_NOT_SET);
} else {
msg.setText(v);
}
});
});

remove.setText("Remove 'test'");
remove.setId("remove");
remove.addClickListener(e -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
public class WebStorageIT extends ChromeBrowserTest {

@Test
public void testWebstorageSetAndRemove() {
public void testWebStorageSetAndRemove() {
open();

WebElement input = findElement(By.id("input"));
Expand All @@ -49,7 +49,7 @@ public void testWebstorageSetAndRemove() {
}

@Test
public void testWebstorageSetAndClear() {
public void testWebStorageSetAndClear() {
open();

WebElement input = findElement(By.id("input"));
Expand All @@ -72,4 +72,51 @@ public void testWebstorageSetAndClear() {
WebStorageView.VALUE_NOT_SET));
}

@Test
public void testWebStorageSetAndRemove_completableFuture() {
open();

WebElement input = findElement(By.id("input"));
WebElement set = findElement(By.id("setText"));
WebElement detect = findElement(By.id("detectCF"));
WebElement remove = findElement(By.id("remove"));

input.clear();
input.sendKeys("foobar", "\n");

set.click();
detect.click();

waitUntil(ExpectedConditions.textToBe(By.id("msg"), "foobar"));

remove.click();
detect.click();

waitUntil(ExpectedConditions.textToBe(By.id("msg"),
WebStorageView.VALUE_NOT_SET));
}

@Test
public void testWebStorageSetAndClear_completableFuture() {
open();

WebElement input = findElement(By.id("input"));
WebElement set = findElement(By.id("setText"));
WebElement detect = findElement(By.id("detectCF"));
WebElement clear = findElement(By.id("clear"));

input.clear();
input.sendKeys("foobar", "\n");

set.click();
detect.click();

waitUntil(ExpectedConditions.textToBe(By.id("msg"), "foobar"));

clear.click();
detect.click();

waitUntil(ExpectedConditions.textToBe(By.id("msg"),
WebStorageView.VALUE_NOT_SET));
}
}

0 comments on commit ed02ec3

Please sign in to comment.