arez.dom.IdleStatus Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of arez-extras-dom Show documentation
Show all versions of arez-extras-dom Show documentation
Dom: Arez browser components that make DOM properties observable
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