All Downloads are FREE. Search and download functionalities are using the official Maven repository.

arez.dom.IdleStatus Maven / Gradle / Ivy

The newest version!
package arez.dom;

import akasha.AddEventListenerOptions;
import akasha.EventListener;
import akasha.TimerHandler;
import akasha.WindowGlobal;
import arez.ArezContext;
import arez.Disposable;
import arez.Task;
import arez.annotations.Action;
import arez.annotations.ArezComponent;
import arez.annotations.ContextRef;
import arez.annotations.Feature;
import arez.annotations.Memoize;
import arez.annotations.Observable;
import arez.annotations.OnActivate;
import arez.annotations.OnDeactivate;
import arez.annotations.PostConstruct;
import arez.annotations.PreDispose;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Nonnull;

/**
 * An Arez browser component that tracks when the user is idle. A user is considered idle if they have not
 * interacted with the browser for a specified amount of time. The component declares state that tracks when
 * the user is "idle". A user is considered idle if they have not interacted with the browser
 * for a specified amount of time.
 *
 * 

Application code can observe the idle state via accessing {@link #isIdle()}. * Typically this is done in a tracking transaction such as those defined by autorun.

* *

The "amount of time" is defined by the Observable value "timeout" accessible via * {@link #getTimeout()} and mutable via {@link #setTimeout(long)}.

* *

The "not interacted with the browser" is detected by listening for interaction * events on the browser. The list of events that the model listens for is controlled via * {@link #getEvents()} and {@link #setEvents(Set)}. It should be noted that if * there is no observer observing the idle state then the model will remove listeners * so as not to have any significant performance impact.

* *

A very simple example

*
{@code
 * import com.google.gwt.core.client.EntryPoint;
 * import akasha.Console;
 * import arez.Arez;
 * import arez.dom.IdleStatus;
 *
 * public class IdleStatusExample
 *   implements EntryPoint
 * {
 *   public void onModuleLoad()
 *   {
 *     final IdleStatus idleStatus = IdleStatus.create();
 *     Arez.context().autorun( () -> {
 *       final String message = "Interaction Status: " + ( idleStatus.isIdle() ? "Idle" : "Active" );
 *       Console.log( message );
 *     } );
 *   }
 * }
 * }
*/ @ArezComponent( requireId = Feature.DISABLE ) public abstract class IdleStatus { private static final long DEFAULT_TIMEOUT = 2000L; @Nonnull private final TimerHandler _timeoutCallback = this::onTimeout; @Nonnull private final EventListener _listener = e -> tryResetLastActivityTime(); @Nonnull private Set _events = new HashSet<>( Arrays.asList( "keydown", "touchstart", "scroll", "mousemove", "mouseup", "mousedown", "wheel" ) ); /** * True if an Observer is watching idle state. */ private boolean _active; /** * The id of timeout scheduled action, 0 if none set. */ private int _timeoutId; /** * Create an instance of this model. * * @return an instance of IdleStatus. */ @Nonnull public static IdleStatus create() { return create( DEFAULT_TIMEOUT ); } /** * Create an instance of this model. * * @param timeout the duration to after activity before becoming idle. * @return an instance of IdleStatus. */ @Nonnull public static IdleStatus create( final long timeout ) { return new Arez_IdleStatus( timeout ); } IdleStatus() { } @ContextRef abstract ArezContext context(); @PostConstruct void postConstruct() { resetLastActivityTime(); } @PreDispose void preDispose() { cancelTimeout(); } /** * Return true if the user is idle. * * @return true if the user is idle, false otherwise. */ @Memoize public boolean isIdle() { if ( isRawIdle() ) { return true; } else { final int timeToWait = getTimeToWait(); if ( timeToWait > 0 ) { if ( 0 == _timeoutId ) { scheduleTimeout( timeToWait ); } return false; } else { return true; } } } @OnActivate void onIdleActivate() { _active = true; _events.forEach( e -> WindowGlobal.addEventListener( e, _listener, AddEventListenerOptions.of().passive( true ) ) ); } @OnDeactivate void onIdleDeactivate() { _active = false; _events.forEach( e -> WindowGlobal.removeEventListener( e, _listener ) ); } /** * Short cut observable field checked after idle state is confirmed. */ @Observable abstract void setRawIdle( boolean rawIdle ); abstract boolean isRawIdle(); /** * Return the duration for which no events should be received for the idle condition to be triggered. * * @return the timeout. */ @Observable( initializer = Feature.ENABLE ) public abstract long getTimeout(); /** * Set the timeout. * * @param timeout the timeout. */ public abstract void setTimeout( long timeout ); /** * Return the set of events to listen to. * * @return the set of events. */ @Nonnull @Observable public Set getEvents() { return _events; } /** * Specify the set of events to listen to. * If the model is already active, the listeners will be updated to reflect the new events. * * @param events the set of events. */ public void setEvents( @Nonnull final Set events ) { final Set oldEvents = _events; _events = new HashSet<>( events ); updateListeners( oldEvents ); } /** * Synchronize listeners against the dom based on new events. */ private void updateListeners( @Nonnull final Set oldEvents ) { if ( _active ) { //Remove any old events oldEvents.stream(). filter( e -> !_events.contains( e ) ). forEach( e -> WindowGlobal.removeEventListener( e, _listener ) ); // Add any new events _events.stream(). filter( e -> !oldEvents.contains( e ) ). forEach( e -> WindowGlobal.addEventListener( e, _listener ) ); } } /** * Return the time at which the last monitored event was received. * * @return the time at which the last event was received. */ @Observable public abstract long getLastActivityAt(); abstract void setLastActivityAt( long lastActivityAt ); private int getTimeToWait() { return (int) ( getLastActivityAt() + getTimeout() - System.currentTimeMillis() ); } private void cancelTimeout() { WindowGlobal.clearTimeout( _timeoutId ); _timeoutId = 0; } private void scheduleTimeout( final int timeToWait ) { _timeoutId = WindowGlobal.setTimeout( _timeoutCallback, timeToWait ); } @Action void onTimeout() { _timeoutId = 0; final int timeToWait = getTimeToWait(); if ( timeToWait > 0 ) { scheduleTimeout( timeToWait ); } else { setRawIdle( true ); } } void tryResetLastActivityTime() { if ( context().isTransactionActive() ) { // This can be called anytime an event occurs ... which can // actually occur during the middle of a transaction (i.e. a browser exception event) // So if we are in the middle of a transaction, just trigger its execution for later. context().task( this::doResetLastActivityTime, Task.Flags.DISPOSE_ON_COMPLETE ); } else { doResetLastActivityTime(); } } void doResetLastActivityTime() { // As the tryResetLastActivityTime can be scheduled later, // it is possible that this will be invoked after the object has been disposed if( Disposable.isNotDisposed( this ) ) { resetLastActivityTime(); } } @Action void resetLastActivityTime() { setRawIdle( false ); setLastActivityAt( System.currentTimeMillis() ); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy