wf.erl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. -module(wf).
  2. -include_lib("n4u/include/n4u.hrl").
  3. -include_lib("n4u/include/n4u_api.hrl").
  4. -compile([export_all, nowarn_export_all]).
  5. -define(ACTION_BASE(Module), ancestor=action, trigger, target, module=Module, actions, source=[]).
  6. -record(jq, {?ACTION_BASE(action_jq), property, method, args=[], right, format="~s"}).
  7. % Here is Nitrogen Web Framework compatible API
  8. % Please read major changes made to N2O and
  9. % how to port existing Nitrogen sites at http://synrc.com/apps/n2o
  10. % Update DOM wf:update
  11. update(Target, Elements) ->
  12. wf:wire(#jq{target=Target,property=outerHTML,right=Elements,format="'~s'"}).
  13. insert_top(Tag,Target, Elements) ->
  14. Pid = self(),
  15. Ref = make_ref(),
  16. spawn(fun() -> R = wf:render(Elements), Pid ! {R,Ref,wf:actions()} end),
  17. {Render,Ref,Actions} = receive {_, Ref, _} = A -> A end,
  18. wf:wire(wf:f(
  19. "qi('~s').insertBefore("
  20. "(function(){var div = qn('~s'); div.innerHTML = '~s'; return div.firstChild; })(),"
  21. "qi('~s').firstChild);",
  22. [Target,Tag,Render,Target])),
  23. wf:wire(wf:render(Actions)).
  24. insert_bottom(Tag, Target, Elements) ->
  25. Pid = self(),
  26. Ref = make_ref(),
  27. spawn(fun() -> R = wf:render(Elements), Pid ! {R,Ref,wf:actions()} end),
  28. {Render,Ref,Actions} = receive {_, Ref, _} = A -> A end,
  29. wf:wire(wf:f(
  30. "(function(){ var div = qn('~s'); div.innerHTML = '~s';"
  31. "qi('~s').appendChild(div.firstChild); })();",
  32. [Tag,Render,Target])),
  33. wf:wire(wf:render(Actions)).
  34. insert_adjacent(Command,Target, Elements) ->
  35. Pid = self(),
  36. Ref = make_ref(),
  37. spawn(fun() -> R = wf:render(Elements), Pid ! {R,Ref,wf:actions()} end),
  38. {Render,Ref,Actions} = receive {_, Ref, _} = A -> A end,
  39. wf:wire(wf:f("qi('~s').insertAdjacentHTML('~s', '~s');",[Target,Command,Render])),
  40. wf:wire(wf:render(Actions)).
  41. insert_top(Target, Elements) when element(1,Elements) == tr -> insert_top(tbody,Target, Elements);
  42. insert_top(Target, Elements) -> insert_top('div',Target, Elements).
  43. insert_bottom(Target, Elements) when element(1,Elements) == tr -> insert_bottom(tbody, Target, Elements);
  44. insert_bottom(Target, Elements) -> insert_bottom('div', Target, Elements).
  45. insert_before(Target, Elements) -> insert_adjacent(beforebegin,Target, Elements).
  46. insert_after(Target, Elements) -> insert_adjacent(afterend,Target, Elements).
  47. remove(Target) ->
  48. wf:wire("var x=qi('"++wf:to_list(Target)++"'); x && x.parentNode.removeChild(x);").
  49. % Wire JavaScript wf:wire
  50. wire(Actions) -> action_wire:wire(Actions).
  51. % Spawn async processes wf:async, wf:flush
  52. async(Function) -> n4u_async:async(Function).
  53. start(#handler{}=Handler) -> n4u_async:start(Handler).
  54. stop(Name) -> n4u_async:stop(Name).
  55. restart(Name) -> n4u_async:restart(Name).
  56. async(Name,Function) -> n4u_async:async(Name,Function).
  57. flush() -> n4u_async:flush().
  58. flush(Key) -> n4u_async:flush(Key).
  59. % Redirect and purge connection wf:redirect
  60. redirect({http,Url}) -> wf:header(<<"Location">>,wf:to_binary(Url)), wf:state(status,302), [];
  61. redirect(Url) -> wf:wire(#jq{target='window.top',property=location,args=simple,right=Url}).
  62. header(K,V) -> wf:context((?CTX)#cx{req=wf:header(K,V,?REQ)}).
  63. % Message Bus communications wf:reg wf:send
  64. -ifndef(REGISTRATOR).
  65. -define(REGISTRATOR, (wf:config(n4u, mq, n4u_mq))).
  66. -endif.
  67. send(Pool, Message) -> ?REGISTRATOR:send(Pool,Message).
  68. reg(Pool) -> ?REGISTRATOR:reg(Pool).
  69. reg(Pool,Value) -> ?REGISTRATOR:reg(Pool,Value).
  70. unreg(Pool) -> ?REGISTRATOR:unreg(Pool).
  71. % Pickling wf:pickle
  72. -ifndef(PICKLER).
  73. -define(PICKLER, (wf:config(n4u, pickler, n4u_pickle))).
  74. -endif.
  75. pickle(Data) -> ?PICKLER:pickle(Data).
  76. depickle(SerializedData) -> ?PICKLER:depickle(SerializedData).
  77. depickle(SerializedData, TTLSeconds) -> ?PICKLER:depickle(SerializedData, TTLSeconds).
  78. % Error handler wf:error_page
  79. -ifndef(ERRORING).
  80. -define(ERRORING, (application:get_env(n4u, erroring, n4u_error))).
  81. -endif.
  82. stack(Error, Reason) -> ?ERRORING:stack(Error, Reason).
  83. error_page(Class,Error) -> ?ERRORING:error_page(Class, Error).
  84. % Session handling wf:session wf:user wf:role
  85. -ifndef(SESSION).
  86. -define(SESSION, (application:get_env(n4u, session, n4u_session))).
  87. -endif.
  88. session(Key) -> ?SESSION:get_value(Key,undefined).
  89. session(Key, Value) -> ?SESSION:set_value(Key, Value).
  90. session_default(Key, DefaultValue) -> ?SESSION:get_value(Key, DefaultValue).
  91. clear_session() -> ?SESSION:clear().
  92. user() -> wf:session(<<"user">>).
  93. user(User) -> wf:session(<<"user">>,User).
  94. clear_user() -> wf:session(<<"user">>,undefined).
  95. logout() -> clear_user(), clear_session().
  96. invalidate_cache() -> ets:foldl(fun(X, _A) -> wf:cache(element(1,X)) end, 0, caching).
  97. cache(Key, undefined) -> ets:delete(caching,Key);
  98. cache(Key, Value) -> ets:insert(caching, {Key, n4u_session:till(calendar:local_time(), n4u_session:ttl()), Value}), Value.
  99. cache(Key, Value, Till) -> ets:insert(caching,{Key,Till,Value}), Value.
  100. cache(Key) ->
  101. Res = ets:lookup(caching,Key),
  102. Val = case Res of [] -> undefined; [Value] -> Value; Values -> Values end,
  103. case Val of undefined -> undefined;
  104. {_,infinity,X} -> X;
  105. {_,Expire,X} -> case Expire < calendar:local_time() of
  106. true -> ets:delete(caching,Key), undefined;
  107. false -> X end end.
  108. % Process State
  109. state(Key) -> erlang:get(Key).
  110. state(Key,Value) -> erlang:put(Key,Value).
  111. % Context Variables and URL Query Strings from ?REQ and ?CTX wf:q wf:qc wf:qp
  112. q(Key) -> Val = get(Key), case Val of undefined -> qc(Key); A -> A end.
  113. qc(Key) -> qc(Key,?CTX).
  114. qc(Key,Ctx) -> proplists:get_value(to_binary(Key),Ctx#cx.params).
  115. qp(Key) -> qp(Key,?REQ).
  116. qp(Key,Req) -> {Params,_} = params(Req), proplists:get_value(to_binary(Key),Params).
  117. lang() -> ?CTX#cx.lang.
  118. % Cookies
  119. cookies() -> n4u_cx:cookies().
  120. cookie(Name) -> lists:keyfind(Name,1,cookies()).
  121. cookie(Name,Value) -> cookie(Name,Value,"/", 24 * 60 * 60).
  122. cookie(Name,Value,Path,TTL) -> n4u_cx:add_cookie(Name,Value,Path,TTL).
  123. % Bridge Information
  124. -ifndef(BRIDGE).
  125. -define(BRIDGE, (application:get_env(n4u, bridge, n4u_cowboy))).
  126. -endif.
  127. cookie_req(Cookie,Req) -> ?BRIDGE:cookie(Cookie, Req).
  128. cookie_req(Name, Value, Path, TTL, Req) -> ?BRIDGE:cookie(Name, Value, Path, TTL, Req).
  129. params(Req) -> ?BRIDGE:params(Req).
  130. form(Req) -> ?BRIDGE:form(Req).
  131. cookies_req(Req) -> ?BRIDGE:cookies(Req).
  132. headers(Req) -> ?BRIDGE:headers(Req).
  133. get_header(Name, Req, Default) -> ?BRIDGE:get_header(Name, Req, Default).
  134. peer(Req) -> ?BRIDGE:peer(Req).
  135. path(Req) -> ?BRIDGE:path(Req).
  136. request_body(Req) -> ?BRIDGE:request_body(Req).
  137. delete_cookie(Cookie,Req) -> ?BRIDGE:delete_cookie(Cookie,Req).
  138. set_header(Name, Val, Req) -> ?BRIDGE:set_header(Name, Val, Req).
  139. header(Name,Val,Req) -> ?BRIDGE:header(Name, Val, Req).
  140. response(Html,Req) -> ?BRIDGE:response(Html,Req).
  141. reply(Status,Req) -> ?BRIDGE:reply(Status,Req).
  142. % Logging API
  143. -define(LOGGER, (wf:config(n4u, log_backend, n4u_log))).
  144. log_modules() -> [wf].
  145. log_level() -> info.
  146. -define(LOG_MODULES, (wf:config(n4u, log_modules, wf))).
  147. -define(LOG_LEVEL, (wf:config(n4u, log_level, wf))).
  148. log_level(none) -> 3;
  149. log_level(error) -> 2;
  150. log_level(warning) -> 1;
  151. log_level(_) -> 0.
  152. log(Module, String, Args, Fun) ->
  153. case log_level(Fun) < log_level(?LOG_LEVEL:log_level()) of
  154. true -> skip;
  155. false -> case ?LOG_MODULES:log_modules() of
  156. any -> ?LOGGER:Fun(Module, String, Args);
  157. Allowed -> case lists:member(Module, Allowed) of
  158. true -> ?LOGGER:Fun(Module, String, Args);
  159. false -> skip end end end.
  160. info(Module, String, Args) -> log(Module, String, Args, info).
  161. info(String, Args) -> log(?MODULE, String, Args, info).
  162. info(String) -> log(?MODULE, String, [], info).
  163. warning(Module, String, Args) -> log(Module, String, Args, warning).
  164. warning(String, Args) -> log(?MODULE, String, Args, warning).
  165. warning(String) -> log(?MODULE, String, [], warning).
  166. error(Module, String, Args) -> log(Module, String, Args, error).
  167. error(String, Args) -> log(?MODULE, String, Args, error).
  168. error(String) -> log(?MODULE, String, [], error).
  169. % Convert and Utils API
  170. display(Element,Status) ->
  171. wf:wire("{ var x = qi('"++
  172. wf:to_list(Element)++"'); if (x) x.style.display = '"++wf:to_list(Status)++"'; }").
  173. show(Element) -> display(Element,block).
  174. hide(Element) -> display(Element,none).
  175. atom(List) when is_list(List) -> wf:to_atom(string:join([ wf:to_list(L) || L <- List],"_"));
  176. atom(Scalar) -> wf:to_atom(Scalar).
  177. f(S) -> wf_utils:f(S).
  178. f(S, Args) -> wf_utils:f(S, Args).
  179. coalesce(L) -> wf_utils:coalesce(L).
  180. json(Json) -> jsone:encode(Json).
  181. to_list(T) -> wf_convert:to_list(T).
  182. to_atom(T) -> wf_convert:to_atom(T).
  183. to_binary(T) -> wf_convert:to_binary(T).
  184. to_integer(T) -> wf_convert:to_integer(T).
  185. jse(String) -> wf_convert:js_escape(String).
  186. js_escape(String) -> wf_convert:js_escape(String).
  187. hte(S) -> wf_convert:html_encode(S).
  188. hte(S, Encode) -> wf_convert:html_encode(S, Encode).
  189. html_encode(S) -> wf_convert:html_encode(S).
  190. html_encode(S, Encode) -> wf_convert:html_encode(S, Encode).
  191. url_encode(S) -> wf_convert:url_encode(S).
  192. url_decode(S) -> wf_convert:url_decode(S).
  193. hex_encode(S) -> wf_convert:hex(S).
  194. hex_decode(S) -> wf_convert:unhex(S).
  195. join(List,Delimiter) -> wf_convert:join(List,Delimiter).
  196. format(Term) -> wf_convert:format(Term).
  197. % These api are not really API
  198. unique_integer() -> try erlang:unique_integer() catch _:_ -> {MS,S,US} = erlang:timestamp(), (MS*1000000+S)*1000000+US end.
  199. temp_id() -> "auto" ++ integer_to_list(unique_integer() rem 1000000).
  200. append(List, Key, Value) -> case Value of undefined -> List; _A -> [{Key, Value}|List] end.
  201. render(X) -> wf_render:render(X).
  202. init_context(R)-> n4u_cx:init_context(R).
  203. actions() -> get(actions).
  204. actions(Ac) -> put(actions, Ac).
  205. script() -> get(script).
  206. script(Script) -> put(script, Script).
  207. context() -> ?CTX.
  208. context(Cx) -> put(context, Cx).
  209. context(Cx,Proto) -> n4u_cx:context(Cx, Proto).
  210. context(Cx,Proto,UserCx) -> n4u_cx:context(Cx, Proto, UserCx).
  211. add_action(Action) -> n4u_cx:add_action(Action).
  212. fold(Fun,Handlers,Ctx) -> n4u_cx:fold(Fun,Handlers,Ctx).
  213. config_multiple(Keys) -> [config(Key, "") || Key <- Keys].
  214. config(Key) -> config(n4u, Key, "").
  215. config(App, Key) -> config(App, Key, "").
  216. config(App, Key, Default) -> wf_utils:config(App, Key, Default).
  217. version() -> proplists:get_value(vsn, element(2, application:get_all_key(n4u))).
  218. setkey(Name,Pos,List,New) ->
  219. case lists:keyfind(Name,Pos,List) of
  220. false -> [New|List];
  221. _Element -> lists:keyreplace(Name,Pos,List,New) end.