001package arez.dom;
002
003import arez.ComputableValue;
004import arez.annotations.Action;
005import arez.annotations.ArezComponent;
006import arez.annotations.ComputableValueRef;
007import arez.annotations.DepType;
008import arez.annotations.Feature;
009import arez.annotations.Memoize;
010import arez.annotations.Observable;
011import arez.annotations.OnActivate;
012import arez.annotations.OnDeactivate;
013import java.util.Date;
014import javax.annotation.Nonnull;
015import jsinterop.annotations.JsFunction;
016import jsinterop.annotations.JsMethod;
017import jsinterop.annotations.JsPackage;
018import jsinterop.annotations.JsProperty;
019import jsinterop.annotations.JsType;
020
021/**
022 * An observable model that declares state that tracks when the user is "online".
023 * The online state is essentially a reflection of the browsers "navigator.onLine"
024 * value. If an observer is observing the model, the model listens for changes from
025 * the browser and updates the online state as appropriate. However if there is no
026 * observer for the state, the model will not listen to to the browser events so as
027 * not to have any significant performance impact.
028 *
029 * <p>A very simple example</p>
030 * <pre>{@code
031 * import com.google.gwt.core.client.EntryPoint;
032 * import arez.Arez;
033 * import arez.networkstatus.NetworkStatus;
034 *
035 * public class NetworkStatusExample
036 *   implements EntryPoint
037 * {
038 *   public void onModuleLoad()
039 *   {
040 *     final NetworkStatus networkStatus = NetworkStatus.create();
041 *     Arez.context().observer( () -> {
042 *       System.out.println( "Network Status: " + ( networkStatus.isOnLine() ? "Online" : "Offline" ) );
043 *       if ( networkStatus.isOffLine() )
044 *       {
045 *         System.out.println( "Offline since: " + networkStatus.getLastChangedAt() );
046 *       }
047 *     } );
048 *   }
049 * }
050 * }</pre>
051 */
052@ArezComponent( requireId = Feature.DISABLE )
053public abstract class NetworkStatus
054{
055  @JsFunction
056  private interface EventListener
057  {
058    void handleEvent( Object event );
059  }
060
061  @JsType( isNative = true, name = "Navigator", namespace = JsPackage.GLOBAL )
062  private static class Navigator
063  {
064    @JsProperty( name = "onLine" )
065    native boolean onLine();
066  }
067
068  @Nonnull
069  private final EventListener _listener = e -> updateOnlineStatus( getOnLineComputableValue() );
070
071  /**
072   * Create an instance of NetworkStatus.
073   *
074   * @return the NetworkStatus instance.
075   */
076  @Nonnull
077  public static NetworkStatus create()
078  {
079    return new Arez_NetworkStatus( new Date() );
080  }
081
082  NetworkStatus()
083  {
084  }
085
086  /**
087   * Return true if the browser is offline, false otherwise.
088   *
089   * @return true if the browser is offline, false otherwise.
090   */
091  public boolean isOffLine()
092  {
093    return !isOnLine();
094  }
095
096  /**
097   * Return true if the browser is online, false otherwise.
098   *
099   * @return true if the browser is online, false otherwise.
100   */
101  @Memoize( depType = DepType.AREZ_OR_EXTERNAL )
102  public boolean isOnLine()
103  {
104    return navigator().onLine();
105  }
106
107  /**
108   * Return the last time at which online status changed.
109   * This will default to the time the component was created, otherwise
110   * the time at which the online status was changed.
111   *
112   * @return the last time at which online status changed.
113   */
114  @Observable
115  @Nonnull
116  public abstract Date getLastChangedAt();
117
118  abstract void setLastChangedAt( @Nonnull Date lastChangedAt );
119
120  @ComputableValueRef
121  abstract ComputableValue<?> getOnLineComputableValue();
122
123  @OnActivate
124  void onOnLineActivate()
125  {
126    addEventListener( "online", _listener );
127    addEventListener( "offline", _listener );
128  }
129
130  @OnDeactivate
131  void onOnLineDeactivate()
132  {
133    removeEventListener( "online", _listener );
134    removeEventListener( "offline", _listener );
135  }
136
137  @Action
138  void updateOnlineStatus( @Nonnull final ComputableValue<?> computableValue )
139  {
140    computableValue.reportPossiblyChanged();
141    setLastChangedAt( new Date() );
142  }
143
144  @JsProperty( name = "navigator", namespace = JsPackage.GLOBAL )
145  private static native Navigator navigator();
146
147  @JsMethod( name = "addEventListener", namespace = JsPackage.GLOBAL )
148  private static native void addEventListener( String type, EventListener listener );
149
150  @JsMethod( name = "removeEventListener", namespace = JsPackage.GLOBAL )
151  private static native void removeEventListener( String type, EventListener listener );
152}