소스 검색

Migrate OfflineModeForTouchKit4MobileApps

tags/7.7.11
Erik Lumme 6 년 전
부모
커밋
a9699a3724
2개의 변경된 파일571개의 추가작업 그리고 0개의 파일을 삭제
  1. 570
    0
      documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc
  2. 1
    0
      documentation/articles/contents.asciidoc

+ 570
- 0
documentation/articles/OfflineModeForTouchKit4MobileApps.asciidoc 파일 보기

@@ -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.

+ 1
- 0
documentation/articles/contents.asciidoc 파일 보기

@@ -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]

Loading…
취소
저장