Current section

Files

Jump to
nitrogen_core src lib wf_render_actions.erl
Raw

src/lib/wf_render_actions.erl

% vim: sw=4 ts=4 et ft=erlang
% Nitrogen Web Framework for Erlang
% Copyright (c) 2008-2010 Rusty Klophaus
% See MIT-LICENSE for licensing information.
-module (wf_render_actions).
-include("wf.hrl").
-export ([
render_action_queue/0,
render_actions/2,
render_actions/4,
normalize_path/1,
to_js_id/1,
generate_anchor_script/2
]).
%%% RENDER ACTIONS %%%
render_action_queue() ->
{ok, get_and_render_actions()}.
get_and_render_actions() ->
case wf_context:next_action() of
empty ->
[];
{ok, Action} ->
{ok, JS} = render_actions(Action, undefined),
[JS | get_and_render_actions()]
end.
-spec render_actions(Actions :: actions(),
Anchor :: id()) -> {ok, script()}.
render_actions(Actions, Anchor) ->
render_actions(Actions, Anchor, Anchor, Anchor).
-spec render_actions(Actions :: actions(),
Anchor :: id(),
Trigger :: id(),
Target :: id()) -> {ok, script()}.
render_actions(Actions, Anchor, Trigger, Target) ->
Script = inner_render_actions(Actions, Anchor, Trigger, Target),
{ok, Script}.
% @doc note: inner_render_actions always returns a list of of binaries or strings.
% This may not be necessary. But for now, we'll keep it like this.
-spec inner_render_actions(Actions :: actions(),
Anchor :: id(),
Trigger :: id(),
Target :: id()) -> script().
inner_render_actions(Actions, Anchor, Trigger, Target) ->
if
Actions == [];
Actions==undefined ->
[];
is_binary(Actions) orelse ?IS_STRING(Actions) ->
[Actions];
is_tuple(Actions) ->
Script = inner_render_action(Actions, Anchor, Trigger, Target),
[Script];
is_list(Actions) ->
[inner_render_actions(hd(Actions), Anchor, Trigger, Target)|
inner_render_actions(tl(Actions), Anchor, Trigger, Target)];
true ->
throw({unanticipated_case_in_render_actions, Actions})
end.
-spec inner_render_action(Action :: action_element(),
Anchor :: id(),
Trigger :: id(),
Target :: id()) -> script().
inner_render_action(Action, Anchor, Trigger, Target) when is_tuple(Action) ->
Base = wf_utils:get_actionbase(Action),
Module = Base#actionbase.module,
% Verify that this is an action...
case Base#actionbase.is_action == is_action of
true -> ok;
false -> throw({not_an_action, Action})
end,
% Render...
case Base#actionbase.show_if of
true ->
% Figure out the anchor, trigger, and target...
Anchor1 = wf:coalesce([Base#actionbase.anchor, Anchor]),
Anchor2 = normalize_path(Anchor1),
Trigger1 = wf:coalesce([Base#actionbase.trigger, Trigger, Anchor]),
Trigger2 = normalize_path(Trigger1),
Target1 = wf:coalesce([Base#actionbase.target, Target, Anchor]),
Target2 = normalize_path(Target1),
Base1 = Base#actionbase {
anchor = Anchor2,
trigger = Trigger2,
target = Target2
},
Action1 = wf_utils:replace_with_base(Base1, Action),
% Render the action...
ActionScript = call_action_render(Module, Action1, Anchor2, Trigger2, Target2),
AnchorScript = generate_anchor_script_if_needed(ActionScript, Anchor2, Target2),
case ActionScript /= undefined andalso not(wf_utils:is_iolist_empty(ActionScript)) of
true ->
DepJS = Base1#actionbase.dependency_js,
wrap_in_dependency(DepJS,[AnchorScript, ActionScript]);
false -> []
end;
_ ->
[]
end.
-spec wrap_in_dependency(Url :: undefined | url(),
Script :: script()) -> script().
wrap_in_dependency(undefined, Script) ->
Script;
wrap_in_dependency("", Script) ->
Script;
wrap_in_dependency(Url, Script) ->
[<<"Nitrogen.$dependency_register_function('">>,Url,<<"',function() {">>, Script, <<"});">>].
-spec generate_anchor_script_if_needed(ActionScript :: script(),
Anchor :: id(),
Target :: id()) -> script().
generate_anchor_script_if_needed(ActionScript, Anchor, Target) ->
case needs_anchor_script(ActionScript) of
true -> generate_anchor_script(Anchor, Target);
false -> ""
end.
-spec needs_anchor_script(Script :: script()) -> boolean().
% @doc here we do a rudimentary check if a string starts with
% "Nitrogen.$anchor" discarding leading newlines and looking at the first depth
% where we encounter an element that isn't just another nested list. This
% should be faster than converting the whole script to a binary first just for
% this comparison. That said, it's probably worthwhile to convert to binary
% and then doing all actions on it based on that. Most actions probably aren't
% that longm, so it shouldn't be that big of a performance hit.
needs_anchor_script(<<"Nitrogen.$anchor",_/binary>>) ->
false;
needs_anchor_script("Nitrogen.$anchor" ++ _) ->
false;
needs_anchor_script(<<"\n",Script/binary>>) ->
needs_anchor_script(Script);
needs_anchor_script("\n" ++ Script) ->
needs_anchor_script(Script);
needs_anchor_script([Script|_]) when is_list(Script); is_binary(Script) ->
needs_anchor_script(Script);
needs_anchor_script(_) ->
true.
-spec generate_anchor_script(Anchor :: id(), Target :: id()) -> script().
generate_anchor_script(Anchor, Target) ->
wf:f(<<"\nNitrogen.$anchor('~s', '~s');">>, [Anchor, Target]).
% Calls the render_action/4 function of an action to turn an action record into Javascript.
-spec call_action_render(Module :: module(),
Action :: action_element(),
Anchor :: id(),
Trigger:: id(),
Target :: id()) -> script().
call_action_render(Module, Action, Anchor, Trigger, Target) ->
case wf_utils:ensure_loaded(Module) of
{module, Module} -> ok;
{error, Error} -> throw({error_loading_action_module, Module, {error, Error}})
end,
NewActions = Module:render_action(Action),
inner_render_actions(NewActions, Anchor, Trigger, Target).
% Turn an atom into ".wfid_atom"
% Turn atom.atom... into ".wfid_atom .wfid_atom"
% If it's a string, replace double "##" with ".wfid_"
-spec normalize_path(Path :: atom() | string() | binary()) -> string() | atom().
normalize_path(undefined) ->
undefined;
normalize_path(page) ->
"page";
normalize_path(Path) when is_atom(Path) ->
String = atom_to_list(Path),
Tokens = string:tokens(String, "."),
Tokens1 = [".wfid_" ++ X || X <- Tokens],
string:join(Tokens1, " ");
normalize_path(String) when is_binary(String) ->
normalize_path(binary_to_list(String));
normalize_path(String) ->
case String of
"wfid_" ++ _ -> "." ++ String;
"temp" ++ _ -> ".wfid_" ++ String;
_ -> wf_utils:replace(String, "##", ".wfid_")
end.
-spec to_js_id(P :: [string()]) -> string().
to_js_id(P) ->
P1 = lists:reverse(P),
string:join(P1, ".").