Retrieving values from HTML5 local storage

Hi,

I’ve been thinking about using HTML5 local storage to persist values from my UI, that do not need to be persisted in an actual database, but might be handy for a user to retain between sessions. This might include filters or drafts of data that the user has entered.

The app I’m working on is built with Vaadin 13.0.1 and writing to the local storage is therefor pretty simple:

public void set(final String key, final String value)
	{
		UI.getCurrent()
		  .accessSynchronously(() ->
				   			{
				   				UI.getCurrent()
									 .getPage()
									 .executeJavaScript("localStorage.setItem($0, $1);",
				   									 key,
				   									 value);
							   });
	}

But I did not seem to find an obvious way to retrieve the values stored in the local storage instance from the server-side.

I tried a few approaches like e.g. implementing a custom component that encapsulates the local storage and handles DOM-Events (using @ClientCallable and @EventHandler) to keep up with the changes to the local storage, but this failed to work for me, because I was not able to ensure that the data is loaded before it is actually required in the view.

Is there a “proper way” to achieve this, or am I just blatantly misusing the local storage in such a way?

So after a little bit of tinkering I came up with a working solution to my problem. It’s not pretty, but it works and is probably enough to get started.

This is what my custom component looks like right now:

@Tag("span")
public class LocalStorage extends Component {

  @FunctionalInterface
  public interface LocalStorageInitListener {
    void onInit(LocalStorage storage);
  }

  private Collection<LocalStorageInitListener> listeners = new ArrayList<>();
  private JsonObject localStorage;


  @Override
  protected void onAttach(AttachEvent attachEvent) {
    UI.getCurrent().getSession().accessSynchronously(() -> {
      UI.getCurrent().getPage().executeJavaScript("$0.$server.init(localStorage)", this.getElement());
    });
  }

  public String getString(String key) {
    try {
      return localStorage.getString(key);
    } catch (NullPointerException e) {
      return "";
    }
  }

  public void addInitListener(LocalStorageInitListener listener) {
    listeners.add(listener);
  }

  public void setValue(String key, String value) {
    UI.getCurrent().getPage().executeJavaScript("localStorage.setItem($0, $1)", key, value);
  }

  @ClientCallable
  public void init(JsonObject localStorage) {
    UI.getCurrent().getSession().access(() -> {
      this.localStorage = localStorage;
      listeners.forEach(v -> v.onInit(this));
    });
  }
}

And this is what my view using this looks like:

@Route("test")
public class TestView extends Div {

  private final LocalStorage localStorage = new LocalStorage();

  public TestView() {
    TextField field = new TextField();

    field.setValueChangeMode(ValueChangeMode.EAGER);

    field.addValueChangeListener(l -> {
      localStorage.setValue("search", l.getValue());
    });

    localStorage.addInitListener(ls -> {
      field.setValue(ls.getString("search"));
    });

    add(localStorage, field);
  }
}

The hack that I missed so far was, that I could just add my own init listener that notifies any listeners when the init-method is called from the client.

Obviously this is a pretty awful botch job, so I’d still be interested in a better way to get this done. :slight_smile:

It’s Work on Vaadin 14.
Thanks Dennis.

So this is somewhat opioniated, but if your users may be switching between different devices (e.g. work laptop and ipad) you generally want to store the settings in the database rather than each device.