I have 2 modules:
test2
test
Then in event function, it use #wf:insert_after to add content from the test2:get_content("Dynamic").
Expected behavior:
New element "Dynamic content" appeared This part is working.
User click on button in "Static" section that was generated from the test2:get_content/1 function,
Now: 6. User click on button in "Dynamic" secion that was generated using test2:get_content/1 function, and added to the page using wf:insert_after() call.
Realized behavior - nothing happened.
wf:wire does not work.
Q: How to enure that wf:wire() works fo the elements that are added using wf:insert_XXX function?
%% -*- mode: nitrogen -*-
-module (test).
-compile(export_all).
-include_lib("../deps/nitrogen_core/include/wf.hrl").
main() ->
#template { file="./priv/templates/bare.html",
bindings=[{'PageTitle',<<"Test">>}]}.
title() ->
#template{bindings = L} = main(),
proplists:get_value('PageTitle',L).
body() ->
Body = [
#panel{class="w3-panel w3-green",
body =
[#p{text = "some text"},
#button {
id=myBtn,
text="myBtn",
postback={btn_clicked,myBtn}
},
#panel{id = id_insert_here}
]}
],
wf:wire(myBtn, #event{ type= click, actions = [ #alert{text="MyBtn clicked"} ]}),
Body,
Content = test2:get_content("Pre-compiled"),
Body2 = [Body,Content],
Body2.
event({btn_clicked,myBtn} = Event) ->
io:format("Event: ~p",[Event]),
Content = test2:get_content("Dynamic"),
wf:insert_after(id_insert_here,Content);
event(Event) ->
io:format(" Unexpected Event: ~p ",[Event]),
ok.
%% end of module test
Module test2:
-module(test2).
-compile(export_all).
-include_lib("../deps/nitrogen_core/include/wf.hrl").
get_content(Title) ->
MyId = wf:temp_id(),
BtnText = io_lib:format("Btn_~p",[MyId]),
Body = [
#panel{class="w3-panel w3-yellow",
body =
[#p{text = Title},
#button {
id=MyId,
text=BtnText
}]
}],
Msg = io_lib:format("Btn: ~p clicked",[MyId]),
wf:wire(MyId, #event{ type= click, actions = [ #alert{text= Msg} ]}),
Body.
Great Question, and one that needs a proper answer in the nitrogen docs.
The short answer is that in test2:get_content
, you should just replace wf:wire
with wf:defer
.
The reasoning for it is a little longer, but it goes like this:
In both situations (both static and dynamic), when test2:get_content
is called it does the following in this order:
In a static request (or rather, in nitrogen parlance, a first_request
, since in Nitrogen, a "static request" refers to purely static content like requesting image files or whatever), this is perfectly fine. Static content is returned, placed into the page, and then any wired actions are inserted into the [[[script]]]
section at the bottom of the template.
In a dynamic request, however, the order of operations becomes important.
The test2:get_content
function you've defined does the same thing, but the function is evaluated and the content is getting returned to the wf:insert_after
function, which then inserts the elements into the DOM.
The bug resides subtly in the above paragraph, but I'll fit in the overall context to make it more obvious. With the dynamic request:
wf:insert
_after (or wf:insert_before
, or wf:update
, or wf:replace
or any of those) needs to test2:get_content
to be evaluated first. So evaluate test2:get_content()
wf:insert_after
takes those returned elements and wires them to the page.What happens here is that because the wf:insert_after
is effectively happening last, the wf:wire
to the element goes nowhere. That is, the element wf:wire
is trying to be attached to doesn't exist in the DOM yet.
It's like this pseudocode:
$('#my_new_element').click(function(){do_something}));
$('#body').insert_bottom("<button id=my_new_element>click</button>");
Using wf:defer
in place of wf:wire
ensures the wiring happens after the insert_XXX
function evaluates.