/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.ajax;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.OnLoadHeaderItem;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.time.Duration;
import org.danekja.java.util.function.serializable.SerializableConsumer;
/**
* A behavior that generates an AJAX update callback at a regular interval.
*
* @since 1.2
*
* @author Igor Vaynberg (ivaynberg)
*
* @see #onTimer(AjaxRequestTarget)
* @see #restart(IPartialPageRequestHandler)
* @see #stop(IPartialPageRequestHandler)
*/
public abstract class AbstractAjaxTimerBehavior extends AbstractDefaultAjaxBehavior
{
private static final long serialVersionUID = 1L;
/** The update interval */
private Duration updateInterval;
private boolean stopped = false;
/**
* Construct.
*
* @param updateInterval
* Duration between AJAX callbacks
*/
public AbstractAjaxTimerBehavior(final Duration updateInterval)
{
setUpdateInterval(updateInterval);
}
/**
* Sets the update interval duration. This method should only be called within the
* {@link #onTimer(AjaxRequestTarget)} method.
*
* @param updateInterval
*/
protected final void setUpdateInterval(Duration updateInterval)
{
if (updateInterval == null || updateInterval.getMilliseconds() <= 0)
{
throw new IllegalArgumentException("Invalid update interval");
}
this.updateInterval = updateInterval;
}
/**
* Returns the update interval
*
* @return The update interval
*/
public final Duration getUpdateInterval()
{
return updateInterval;
}
@Override
public void renderHead(Component component, IHeaderResponse response)
{
super.renderHead(component, response);
if (isStopped() == false)
{
addTimeout(response);
}
}
/**
* @param updateInterval
* Duration between AJAX callbacks
* @return JS script
*/
protected final String getJsTimeoutCall(final Duration updateInterval)
{
CharSequence js = getCallbackScript();
return String.format("Wicket.Timer.set('%s', function(){%s}, %d);",
getComponent().getMarkupId(), js, updateInterval.getMilliseconds());
}
/**
*
* @see org.apache.wicket.ajax.AbstractDefaultAjaxBehavior#respond(AjaxRequestTarget)
*/
@Override
protected final void respond(final AjaxRequestTarget target)
{
if (shouldTrigger())
{
onTimer(target);
if (shouldTrigger())
{
addTimeout(target.getHeaderResponse());
return;
}
}
clearTimeout(target.getHeaderResponse());
}
/**
* Decides whether the timer behavior should render its JavaScript to re-trigger
* it after the update interval.
*
* @return {@code true} if the behavior is not stopped, it is enabled and still attached to
* any component in the page or to the page itself
*/
protected boolean shouldTrigger()
{
return isStopped() == false &&
isEnabled(getComponent()) &&
(getComponent() instanceof Page || getComponent().findParent(Page.class) != null);
}
/**
* Listener method for the AJAX timer event.
*
* @param target
* The request target
*/
protected abstract void onTimer(final AjaxRequestTarget target);
/**
* @return {@code true} if has been stopped via {@link #stop(IPartialPageRequestHandler)}
*/
public final boolean isStopped()
{
return stopped;
}
/**
* Re-enables the timer if already stopped
*
* @param target
* may be null
*/
public final void restart(final IPartialPageRequestHandler target)
{
if (stopped == true)
{
stopped = false;
if (target != null)
{
addTimeout(target.getHeaderResponse());
}
}
}
private void addTimeout(IHeaderResponse headerResponse)
{
headerResponse.render(OnLoadHeaderItem.forScript(getJsTimeoutCall(updateInterval)));
}
private void clearTimeout(IHeaderResponse headerResponse)
{
headerResponse.render(OnLoadHeaderItem.forScript("Wicket.Timer.clear('" + getComponent().getMarkupId() + "');"));
}
/**
* Stops the timer.
*
* @param target
* may be null
*/
public final void stop(final IPartialPageRequestHandler target)
{
if (stopped == false)
{
stopped = true;
if (target != null)
{
clearTimeout(target.getHeaderResponse());
}
}
}
@Override
public void onRemove(Component component)
{
component.getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent(target -> clearTimeout(target.getHeaderResponse()));
}
@Override
protected void onUnbind()
{
getComponent().getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent(target -> clearTimeout(target.getHeaderResponse()));
}
/**
* Creates an {@link AbstractAjaxTimerBehavior} based on lambda expressions
*
* @param interval
* the interval the timer
* @param onTimer
* the consumer which accepts the {@link AjaxRequestTarget}
* @return the {@link AbstractAjaxTimerBehavior}
*/
public static AbstractAjaxTimerBehavior onTimer(Duration interval, SerializableConsumer<AjaxRequestTarget> onTimer)
{
Args.notNull(onTimer, "onTimer");
return new AbstractAjaxTimerBehavior(interval)
{
private static final long serialVersionUID = 1L;
@Override
protected void onTimer(AjaxRequestTarget target)
{
onTimer.accept(target);
}
};
}
}