]> source.dussan.org Git - vaadin-framework.git/commitdiff
Migrate OfflineModeForTouchKit4MobileApps
authorErik Lumme <erik@vaadin.com>
Thu, 14 Sep 2017 09:57:43 +0000 (12:57 +0300)
committerErik Lumme <erik@vaadin.com>
Thu, 14 Sep 2017 09:57:43 +0000 (12:57 +0300)
documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc [new file with mode: 0644]
documentation/articles/contents.asciidoc

diff --git a/documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc b/documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc
new file mode 100644 (file)
index 0000000..1657464
--- /dev/null
@@ -0,0 +1,570 @@
+[[offline-mode-for-touchkit-4-mobile-apps]]
+Offline mode for TouchKit 4 mobile apps
+---------------------------------------
+
+[.underline]#*_Note:_* _Vaadin Touchkit has been discontinued. A community-supported version is
+available https://github.com/parttio/touchkit[on GitHub]._#
+
+[[background]]
+Background
+~~~~~~~~~~
+
+Vaadin is primarily a server-side framework. What happens with the
+application when the server is not available? Although this is possible
+on desktop computers, more often it happens when using a mobile device.
+This is why Vaadin TouchKit allows
+you to define offline behavior. In this article I will tell you all the
+details you need to know about offline mode and how to use it. It is
+written based on Vaadin 7.3 and TouchKit 4.0.0.
+
+Touchkit is a Vaadin
+addon that helps in developing mobile applications. I assume that you
+have some knowledge in Vaadin and how to develop client-side Vaadin
+(GWT) code. I will mention the http://demo.vaadin.com/parking/[Parking
+demo] here a few times and you can find its sources
+https://github.com/vaadin/parking-demo[here]. I suggest that you read
+this article before you try to understand the Parking demo source code,
+it will help you grasp the concepts demonstrated in the demo.
+
+[Error: Macro 'TableOfContents' doesn't exist]
+
+[[demystifying-offline-mode]]
+Demystifying offline mode
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As said before, Vaadin is a server-side framework and that implies that
+when an application is running, there is a lot of communication going on
+between the server and the client. Thus server-side views are not
+accessible when there is no connection. On the other hand, offline
+enabled applications run pure client-side Vaadin (GWT) code without
+connecting the server.
+
+There are a couple of approaches you might take to specify offline
+behavior on the client-side.
+
+1.  Write a fully client-side application for the user to interact with
+when the server is offline.
+2.  Write some views as client-side widgets and, in case the connection
+is lost, disable all the components that might need a server connection.
+
+Let’s take a look at the technical details you need to know.
+
+[[client-side-offline-mode-handling---method-1-checking-the-status]]
+Client-side offline mode handling - method 1: checking the status
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The simplest way to know if the application is online or offline is to
+use this code:
+
+[source,java]
+....
+OfflineModeEntrypoint.get().getNetworkStatus().isAppOnline()
+....
+
+You might use it before sending something to the server or calling an
+RPC, for example. However, the network status might change at any time.
+Method 2 helps you react to those changes.
+
+[[client-side-offline-mode-handling---method-2-handling-events]]
+Client-side offline mode handling - method 2: handling events
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In order to use this method you need an `ApplicationConnection` instance.
+We are going to use its event bus to handle online/offline events.
+Usually you get an `ApplicationConnection` instance from a component
+connector. Here is an example:
+
+[source,java]
+....
+@Connect(MyComponent.class)
+public class MyConnector extends AbstractComponentConnector {
+  @Override
+  protected void init() {
+    super.init();
+
+    getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
+      @Override
+      public void onOnline(final OnlineEvent event) {
+      // do some stuff
+      }
+    });
+
+    getConnection().addHandler(OfflineEvent.TYPE, new OfflineEvent.OfflineHandler() {
+      @Override
+      public void onOffline(final OfflineEvent event) {
+      // do some stuff
+      }
+    });
+  }
+}
+....
+
+Note that this connector will only be created if an instance of
+`MyComponent` is created on the server side and attached to the UI. As an
+option, it might be a `UI` or `Component` extension connector. Otherwise
+your connector will never be instantiated and you will never receive
+these events, so you can rely on them only if you want to show some
+changes in the view or disable some functionality of a view when
+offline. In order to get true offline capabilities, use method 3.
+
+[[client-side-offline-mode-handling---method-3-implementing-offlinemode-interface]]
+Client-side offline mode handling - method 3: implementing OfflineMode interface
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Implementing client-side OfflineMode interface allows you to specify
+true offline-mode behavior: you will receive events also in case the
+page is loaded from cache without network connection at all.
+
+Fortunately, there is a default implementation and you don’t need to
+worry about the implementation details. `DefaultOfflineMode` provides an
+OfflineMode implementation for any TouchKit application. It shows a
+loading indicator and a sad face when the network is down. In most cases
+all you want to do is replace this sad face with something more useful
+(for example Minesweeper or Sudoku), here’s a sample:
+
+[source,java]
+....
+public class MyOfflineMode extends DefaultOfflineMode {
+  @Override
+  protected void buildDefaultContent() {
+    getPanel().clear();
+    getPanel().add(createOfflineApplication()); // might be a full blown GWT UI
+  }
+}
+....
+
+Then you need to specify the implementation in your widgetset definition
+file (*.gwt.xml):
+
+[source,xml]
+....
+<replace-with class="com.mybestapp.widgetset.client.MyOfflineMode">
+  <when-type-is class="com.vaadin.addon.touchkit.gwt.client.offlinemode.OfflineMode" />
+</replace-with>
+....
+
+This is enough for showing an offline UI, it will be shown and hidden
+automatically, `DefaultOfflineMode` will take care of this. If you need a
+more complex functionality, like doing something when going
+offline/online, you might want to override additional methods from
+`DefaultOfflineMode` or implement OfflineMode from scratch. I briefly
+sketch what you need to know about it.
+
+The OfflineMode interface has three methods:
+
+[source,java]
+....
+void activate(ActivationReason);
+boolean deactivate();
+boolean isActive();
+....
+
+Pretty clear, but there are some pitfalls.
+
+Counterintuitively, not all `ActivationReason`{empty}(s) actually require
+activating the offline application view. On
+`ActivationReason.APP_STARTING` you can just show a loading indicator and
+on `ActivationReason.ONLINE_APP_NOT_STARTED` you might want to display a
+reload button or actually hide the offline view. Take a look at the
+`DefaultOfflineMode` implementation and the `TicketViewWidget` in the
+Parking demo.
+
+Second thing to note: `deactivate()` will never be called if i`sActive()`
+returns `false`. So you must track whether the offline mode is active or
+just take a shortcut like this:
+
+[source,java]
+....
+boolean isActive() {
+  return true;
+}
+....
+
+And the last one: regardless of what JavaDoc says, the return value of
+the `deactivate()` method is ignored. You might want to check if this
+changes in future versions.
+
+Note that this client-side
+http://demo.vaadin.com/javadoc/com.vaadin.addon/vaadin-touchkit-agpl/4.0.0/com/vaadin/addon/touchkit/gwt/client/offlinemode/OfflineMode.html[com.vaadin.addon.touchkit.gwt.client.offlinemode.OfflineMode]
+interface has nothing to do with server-side extension
+http://demo.vaadin.com/javadoc/com.vaadin.addon/vaadin-touchkit-agpl/4.0.0/com/vaadin/addon/touchkit/extensions/OfflineMode.html[com.vaadin.addon.touchkit.extensions.OfflineMode]
+class (unfortunate naming).
+
+[[setting-up-the-offline-mode]]
+Setting up the offline mode
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can turn a Vaadin application into an offline-enabled TouchKit
+application by using an extension of `TouchKitServlet` as your servlet
+class. For example, the following might be your servlet declaration in
+your UI class:
+
+[source,java]
+....
+@WebServlet(value = "/*")
+public static class Servlet extends TouchKitServlet /* instead of VaadinServlet */ {}
+....
+
+Below are some details that you might need at some point (or have read
+about in other places and are wondering what they are). You may skip to
+the “Synchronizing data between server and client” section if you just
+want a quick start.
+
+You can check network status (method 1) in any TouchKit application
+(i.e. any application using `TouchKitServlet`), nothing special is
+required.
+
+In order to use the application connection event bus (method 2), offline
+mode must be enabled or no events will be sent. As of TouchKit 4, it is
+enabled by default whenever you use TouchKit. If for some reason you
+want offline mode disabled, annotate your UI class with
+`@OfflineModeEnabled(false)`. Although this is not recommended in TouchKit
+applications, because no message will be shown if the app goes offline,
+not even the standard Vaadin message.
+
+For method 3 (implementing the OfflineMode interface), besides enabling
+offline mode, the
+http://en.wikipedia.org/wiki/Cache_manifest_in_HTML5[HTML5 cache
+manifest] should be enabled. The cache manifest tells the browser to
+cache some files, so that they can be used without a network connection.
+As with the offline mode, it is enabled by default. If you want it
+disabled, annotate your UI class with  `@CacheManifestEnabled(false)`.
+That way your application might be fully functional once starting online
+and then going offline (if it does not need any additional files when
+offline), but will not be able to start when there is no connection.
+
+[[caching-additional-files-for-example-a-custom-theme]]
+Caching additional files, for example a custom theme
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you need some additional files to be cached for offline loading (most
+likely your custom theme), you can add this property to your *.gwt.xml
+file:
+
+[source,xml]
+....
+<set-configuration-property
+    name='touchkit.manifestlinker.additionalCacheRoot'
+    value='path/relative/to/project/root:path/on/the/server' />
+....
+
+Only files having these extensions will be added to the cache manifest:
+.html, .js, .css, .png, .jpg, .gif, .ico, .woff);
+
+If this is a directory, it will be scanned recursively and all the files
+with these extensions will be added to the manifest.
+
+[[offlinemode-extension]]
+OfflineMode extension
+^^^^^^^^^^^^^^^^^^^^^
+
+In addition, you can slightly tweak the offline mode through the
+OfflineMode UI extension.
+
+You can set offline mode timeout (if there’s no response from the server
+during this time, offline mode will be activated), or manually set
+application mode to offline/online (useful for development). There’s
+also a less useful parameter: enable/disable persistent session cookie
+(enabled by default if you use `@PreserveOnRefresh`, which you should do
+for offline mode anyways). That’s all there is in this extension. Usage:
+
+[source,java]
+....
+// somewhere among UI initializaion
+OfflineMode offline = new OfflineMode();
+offline.extend(this);
+offlineModeSettings.setOfflineModeTimeout(5);
+....
+
+Note: it is not compulsory to use this extension, but it helps the
+client side of the Touchkit add-on to find the application connection.
+Without it, it tries to get an application connection for 5 seconds. If
+you suspect that your connection is too slow or the server is very slow
+to respond, you might add a new `OfflineMode().extend(this);` to your UI
+just in case. That should be very rarely needed.
+
+This extension is usually used for synchronizing data between the server
+and the client (covered in the next section), but it can be done through
+any other extension/component -- there is no special support for it in
+OfflineMode extension.
+
+[[synchronizing-data-between-server-and-client]]
+Synchronizing data between server and client
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In a sense, the client is always in “offline mode” between requests from
+the server point of view. Therefore the regular Vaadin way of
+synchronizing data between the client-side widget and the server-side
+(https://vaadin.com/book/-/page/gwt.rpc.html[Vaadin RPC mechanism] and
+https://vaadin.com/book/-/page/gwt.shared-state.html[shared state]) is
+still valid, the difference being that the offline widget is probably
+more complex and the amount of data is greater than that of an average
+component.
+
+As mentioned, the server is not necessarily aware that the client went
+offline for some time, therefore the synchronization should be initiated
+from the client side. So using method 2 or 3, the client side gets an
+event that the connection is online and it sends an RPC call to the
+server. New data might be sent with the notification or asked
+separately, e.g. using
+http://demo.vaadin.com/javadoc/com.vaadin.addon/vaadin-touchkit-agpl/4.0.0/index.html?com/vaadin/addon/touchkit/extensions/LocalStorage.html[LocalStorage]
+(TouchKit provides easy access to
+http://www.w3schools.com/html/html5_webstorage.asp[HTML5 LocalStorage]
+from the server side). The server might send new data through shared
+state.
+
+If we reuse OfflineMode (mentioned in the end of the last section), the
+code might look like this:
+
+[source,java]
+....
+public class MyOfflineModeExtension extends OfflineMode {
+  public MyOfflineModeExtension() {
+    registerRpc(serverRpc);
+  }
+
+  private final SyncDataServerRpc serverRpc = new SyncDataServerRpc() {
+    @Override
+    public void syncData(final Object newData) {
+      doSmth(newData); // update data
+      getState().someProperty = newServerData; // new data from the server to the client
+    }
+  };
+}
+
+@Connect(MyOfflineModeExtension.class)
+public class MyOfflineConnector extends OfflineModeConnector {
+  private final SyncDataServerRpc rpc = RpcProxy.create(SyncDataServerRpc.class, this);
+
+  @Override
+  protected void init() {
+    super.init();
+
+    getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
+      @Override
+      public void onOnline(final OnlineEvent event) {
+        Object new Data = … // get updated data
+        rpc.syncData(newData);
+      }
+    });
+  }
+}
+....
+
+As already said, this does not necessarily have to be done through the
+OfflineMode extension, it can be done using any component connector,
+there is nothing special about OfflineMode.
+
+Another option, a less wordy and more decoupled one, could be done by
+using JavaScript function call.
+
+On the server side:
+
+[source,java]
+....
+JavaScript.getCurrent().addFunction("myapp.syncData",
+    (args) -> { /*sync data, e.g. get it from LocalStorage */});
+....
+
+On the client side:
+
+[source,java]
+....
+// in any connector
+getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
+  @Override
+  public native void onOnline(final OnlineEvent event) /*-{
+    myapp.syncData();
+  }-*/;
+});
+....
+
+Or similar code in client-side OfflineMode implementation:
+
+[source,java]
+....
+MyOfflineMode extends DefaultOfflineMode {
+  @Override
+  public native boolean deactivate() /*-{
+    myapp.syncData();
+  }-*/;
+}
+....
+
+This option is less “the Vaadin way”, but in some cases might be useful.
+
+[[creating-efficient-offline-views]]
+Creating efficient offline views
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are two main concerns with offline-enabled applications:
+
+1.  Maximizing code sharing between online and offline mode.
+2.  Seamlessly switching between offline and online mode.
+
+To share the code for a view that is used both in online and offline,
+you will probably need to create the view as a custom widget, including
+connector and a server-side component class. If you know how to do this
+and understand why it is needed, you can skip to the “Switching between
+online and offline” subsection .
+
+As Vaadin is a server-side framework, the views and the logic are
+usually implemented using server-side Java code. During application
+lifetime, a lot of traffic is sent between the server and the client
+even in a single view. Thus server-side implemented views are not usable
+when there is no connection between server and client.
+
+For very simple views (e.g. providing a list, no data input) it might be
+appropriate to have two separate implementations, one client-side and
+one server-side, as it is quick and easy to build these and you avoid
+the development and code overhead of using client-side views online,
+keeping the server-side advantages for the online version.
+
+For more complex functionality you will need to implement a fully
+client-side view for both online and offline operation and then
+synchronize the data as described in the previous section. Using it
+during a completely offline operation is straightforward: just show the
+view on the screen by an OfflineMode interface implementation in an
+overlay. For server-side usage you will probably need to create a
+https://vaadin.com/book/-/page/gwt.html[server-side component and a
+connector].
+
+[[switching-between-online-and-offline]]
+Switching between online and offline
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+What we want to achieve is that the user doesn’t feel that the
+application went offline or online if he doesn’t need to know that. We
+might show an indicator so that the user is aware, but he should be able
+to do what he did before the switch happened, if this is possible. Also,
+no data should be lost during switching.
+
+[[a-navigatormanager-issue-and-workaround]]
+A NavigatorManager issue and workaround
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Before we go to some deeper details, note that there is an annoying
+`NavigatorManager` behavior related to offline mode: when you click a
+`NagivationButton` while the connection is down (but before offline mode
+was activated) and the target view is not in the DOM yet, the server
+does not respond the system switches to offline mode and then when
+coming back from offline mode, we’re stuck in an empty view.
+
+A workaround for this is to call `NavigatorManagerConnector` to redraw on
+an online event, so this might be put in some connector (you might use
+deferred binding to put this in `NavigatorManagerConnector` itself):
+
+[source,java]
+....
+getConnection().addHandler(OnlineEvent.TYPE, new OnlineEvent.OnlineHandler() {
+  @Override
+  public void onOnline(final OnlineEvent event) {
+    final JsArrayObject<ComponentConnector> jsArray =
+        ConnectorMap.get(getConnection()).getComponentConnectorsAsJsArray();
+
+    for (int i = 0; jsArray.size() > i; i++) {
+      if (jsArray.get(i) instanceof NavigationManagerConnector) {
+        final NavigationManagerConnector connector =
+            (NavigationManagerConnector) jsArray.get(i);
+        connector.forceStateChange();
+      }
+    }
+  }
+});
+....
+
+[[user-experience-considerations-related-to-switching]]
+User experience considerations related to switching
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Here’s an example of what we want to achieve: if the user is filling a
+form, which by design can be filled offline or online, and the network
+suddenly goes down, he should be able to continue filling the form
+without much interference. That means, if we’re using method 3 by
+implementing OfflineMode and showing an overlay on the screen (which is
+done in the Parking demo), the offline overlay will be hiding the real
+online form. At that point the data from the online form is copied to
+the offline form and the user barely notices that something happened.
+That means there are two instances of the form, online one and offline
+one. Another option would be that you have only one instance of the form
+and instead of copying the data, you attach the whole form to a
+different view (thanks to Tomi Virkku for the tip).
+
+In the Parking demo, the ticket view jumps, because the scroll position
+changes and an indicator is added. If the user was in the middle of
+something, he is suddenly interrupted, although no data is lost.
+
+If we want to improve user experience, we could implement it in a better
+way. In case the network goes offline when the user is filling a form,
+we disable all the elements that might fire a request to the server and
+let the user continue filling the form. Of course, the form should be
+implemented completely client-side, and all the suspicious elements
+would be around it, probably navigation/toolbar buttons. Another option
+would be to have all the elements client-side and on click they would be
+checking if there is a connection, before sending anything to the
+server. After the user submits or cancels the form, we can show the
+“true” offline view. Alternatively, it will be the only offline view in
+the application, depending on the specific case.
+
+For example, if you are using a navigator manager, the trick would be to
+keep or find the `VNavigatorManager` and disable its widgets (left and
+right widgets, the ones that are used to navigate):
+
+[source,java]
+....
+getConnection().addHandler(OfflineEvent.TYPE, new OfflineEvent.OfflineHandler() {
+  @Override
+  public void onOffline(final OfflineEvent event) {
+    setWidgetEnabled(getWidget().getNavigationBar().getWidget(0), false);
+  }
+});
+
+void setWidgetEnabled(final Widget widget, final boolean enabled) {
+  widget.setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled);
+
+  if (widget instanceof HasEnabled)
+    ((HasEnabled) widget).setEnabled(enabled);
+
+  // this is just because for some reason VNavigatorButton does not implement HasEnabled, although it has such methods...
+  if (widget instanceof VNavigationButton)
+    ((VNavigationButton) widget).setEnabled(enabled);
+}
+....
+
+Known issues: `HasEnabled` declaration should be fixed soon, but I should
+warn you that for some reason a disabled `NavigationButton` still responds
+to mouse click events, although correctly ignoring touch events.
+
+Same works in the other direction as well, so when an offline form is
+shown and the connection goes up, you just keep the offline form until
+the user submits/cancels, then show the online view again.
+
+This is how you can give the user experience the best experience.
+
+[[phonegap-integration]]
+PhoneGap integration
+~~~~~~~~~~~~~~~~~~~~
+
+As this is not directly related to the topic I will not explain the
+basics here, just a couple of pitfalls that someone familiar with
+PhoneGap might encounter.
+
+http://dev.vaadin.com/ticket/13250[An issue with offline mode on
+PhoneGap] was reported recently and because of that, a new solution was
+found that puts the Vaadin application into an iframe. You can get the
+files for PhoneGap from TouchKit maven archetype (_link no longer available_). However, this solution has its
+drawbacks and you might want
+to disable the iframe. If you do that, you need to copy some files (like
+widgetset) to your PhoneGap project. There is still ongoing discussion
+of how to improve this. No more details here, this was just to warn you.
+
+Another pitfall is that when you specify the URL in archetype’s
+index.html do put the final slash:
+
+[source,java]
+....
+window.vaadinAppUrl = 'http://youraddress.com/path/'; // <--- slash is compulsory!
+....
+
+Without it the application will not load from cache when there’s no
+connection.
index 0fc3ebc5f143da51d15b42d5d5d7b0e7712521da..0c5ce7f0af4c7ef439bad877df66523f325cc0de 100644 (file)
@@ -4,3 +4,4 @@
 == Articles
 - link:LazyQueryContainer.asciidoc[Lazy query container]
 - link:UsingJDBCwithLazyQueryContainerAndFilteringTable.asciidoc[Using JDBC with Lazy Query Container and FilteringTable]
+- link:OfflineModeForTouchKit4MobileApps.asciidoc[Offline mode for TouchKit 4 mobile apps]