Current section
Files
Jump to
Current section
Files
src/elements/other/element_delay_body.erl
% vim: sw=4 ts=4 et ft=erlang
% Nitrogen Web Framework for Erlang
% Copyright (c) 2020 Jesse Gumm
% See MIT-LICENSE for licensing information.
-module (element_delay_body).
-include("wf.hrl").
-export([
reflect/0,
transform_element/1,
event/1
]).
-spec reflect() -> [atom()].
reflect() -> record_info(fields, delay_body).
-spec transform_element(#delay_body{}) -> body().
transform_element(#delay_body{delegate=Delegate0, tag=Tag, delay=Delay, method=Method0, placeholder=Placeholder}) when is_integer(Delay) ->
ID = wf:temp_id(),
Delegate = wf:coalesce([Delegate0, wf:page_module()]),
%% Comet maintains a separate page state from the main page state, so to work it must be simple.
%% Also, if we're caching this element, the necessary state information wouldn't be established for subsequent requests.
%% Also a result, in these two situations, we must force the simple method.
ForceSimpleMethod = wf_context:type()==comet orelse wf_context:caching()==true,
Method = ?WF_IF(ForceSimpleMethod, simple, Method0),
case Method of
simple ->
wf:defer(#event{type=timer, delay=Delay, delegate=?MODULE, postback={do_delay_action, ID, Delegate, Tag}});
optimized ->
queue_delayed_body(Delegate, ID, Tag, Delay)
end,
#span{id=ID, body=Placeholder}.
queue_delayed_body(Delegate, ID, Tag, Delay) ->
%?PRINT(status(Delay)),
add_to_queue(Delegate, ID, Tag, Delay),
maybe_wire_postback(Delay).
wire_key(Delay) ->
{element_delay_body_wired, Delay}.
tag_key(Delay) ->
{element_delay_tags, Delay}.
add_to_queue(Delegate, ID, Tag, Delay) ->
Key = tag_key(Delay),
Queue = wf:state_default(Key, queue:new()),
NewQ = queue:in({ID, Delegate, Tag}, Queue),
%?PRINT(NewQ),
wf:state(Key, NewQ).
%status(Delay) ->
% {wf:state(wire_key(Delay)), wf:state(tag_key(Delay))}.
maybe_wire_postback(Delay) ->
Key = wire_key(Delay),
case wf:state_default(Key, false) of
true ->
% we've already wired this request, so we don't need to send it again
do_nothing;
_ ->
wf:state(Key, true),
wf:defer(#event{type=timer, delay=Delay, delegate=?MODULE, postback={do_delay_actions, Delay}})
end.
reset_wire_key(Delay) ->
Key = wire_key(Delay),
wf:state(Key, false).
reset_tag_key(Delay) ->
Key = tag_key(Delay),
wf:state(Key, queue:new()).
event({do_delay_action, ID, Delegate, Tag}) ->
Body = Delegate:delay_body_event(Tag),
wf:replace(ID, Body);
event({do_delay_actions, Delay}) ->
reset_wire_key(Delay), % we reset this in case we have to queue another action with the same timeout with a later postback
TagKey = tag_key(Delay),
NewQ = queue:new(),
TagQueue = wf:state_default(TagKey, NewQ),
Tags = queue:to_list(TagQueue),
lists:foreach(fun({ID, Delegate, Tag}) ->
Body = Delegate:delay_body_event(Tag),
wf:replace(ID, Body)
end, Tags),
reset_tag_key(Delay).