PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/js/src/js.erl

https://github.com/gebi/jungerl
Erlang | 629 lines | 230 code | 87 blank | 312 comment | 1 complexity | b48c8ecd8f8493c4e6e8a28e7a1c9480 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, AGPL-1.0
  1. %%%-------------------------------------------------------------------
  2. %%% Created : 18 Jun 2005 by Tobbe <tobbe@tornkvist.org>
  3. %%% Desc. : A rip-off of Ruby's javascript_helper.rb + interface
  4. %%% to other good Javascript libraries.
  5. %%%-------------------------------------------------------------------
  6. -module(js).
  7. -export([link_to_function/2, link_to_function/3,
  8. link_to_remote/2, link_to_remote/3,
  9. observe_field/2, observe_form/2,
  10. periodically_call_remote/1, periodically_call_remote_S/1,
  11. form_remote_tag/1, prototype_js/0, fd2qs_js/0,
  12. tabtastic/1, tabtastic_js/0, tabtastic_css/0]).
  13. -import(lists,[flatten/1, member/2]).
  14. %%%
  15. %%% Use the following from another application to get hold of
  16. %%% the javascript files to be delivered in the head.
  17. %%% Example:
  18. %%%
  19. %%% <script type="text/javascript" src="/prototype.yaws"></script>
  20. %%%
  21. %%% <script type="text/javascript" src="/tabtastic_js.yaws"></script>
  22. %%% <link rel="stylesheet" type="text/css" href="/tabtastic_css.yaws">
  23. %%%
  24. %%%
  25. %%% In the file prototype.yaws, we call: js:prototype_js/0
  26. %%% tabtastic_js.yaws, we call: js:tabtastic_js/0
  27. %%% tabtastic_css.yaws, we call: js:tabtastic_css/0
  28. %%%
  29. %%%
  30. prototype_js() ->
  31. Dir = code:priv_dir(?MODULE),
  32. file:read_file(filename:join([Dir,"docroot","js","prototype.js"])).
  33. fd2qs_js() ->
  34. Dir = code:priv_dir(?MODULE),
  35. file:read_file(filename:join([Dir,"docroot","js","fd2qs.js"])).
  36. tabtastic_js() ->
  37. Dir = code:priv_dir(?MODULE),
  38. file:read_file(filename:join([Dir,"docroot","Tabtastic","tabtastic.js"])).
  39. tabtastic_css() ->
  40. Dir = code:priv_dir(?MODULE),
  41. file:read_file(filename:join([Dir,"docroot","Tabtastic","tabtastic.css"])).
  42. %%%
  43. %%% --- TABTASTIC ---
  44. %%% Takes a list of {DivID, Label, ActiveBool, Ehtml} and returns expanded Tabastic Ehtml
  45. %%%
  46. tabtastic(L) ->
  47. {'div', [{id, "pagecontent"}],
  48. tabtastic_toc(L) ++
  49. tabtastic_divs(L)}.
  50. tabtastic_toc(L) ->
  51. AA = integer_to_list(element(3,erlang:now())), % quick and dirty...
  52. [{a, [{name, AA}], []},
  53. {h2, [{class, "tabset_label"}], "Table of Contents"},
  54. {ul, [{class, "tabset_tabs"}],
  55. [{li,[],
  56. {a, [{href,"#"++AA}, {name,Id}]++is_active(Bool), Label}} ||
  57. {Id, Label, Bool, _} <- L]}].
  58. tabtastic_divs(L) ->
  59. [{'div', [{id, Id}, {class, "tabset_content"}],
  60. [{h2, [{class, "tabset_label"}], Label},
  61. Ehtml]} || {Id, Label, _, Ehtml} <- L].
  62. is_active(true) -> [{class, "active"}];
  63. is_active(_) -> [].
  64. %%% ================================================================
  65. %%% Returns a link to a remote action defined by <tt>options[:url]</tt>
  66. %%% (using the url_for format) that's called in the background using
  67. %%% XMLHttpRequest. The result of that request can then be inserted into a
  68. %%% DOM object whose id can be specified with <tt>options[:update]</tt>.
  69. %%% Usually, the result would be a partial prepared by the controller with
  70. %%% either render_partial or render_partial_collection.
  71. %%%
  72. %%% Examples:
  73. %%% link_to_remote "Delete this post", :update => "posts", :url => { :action => "destroy", :id => post.id }
  74. %%% link_to_remote(image_tag("refresh"), :update => "emails", :url => { :action => "list_emails" })
  75. %%%
  76. %%% By default, these remote requests are processed asynchronous during
  77. %%% which various callbacks can be triggered (for progress indicators and
  78. %%% the likes).
  79. %%%
  80. %%% Example:
  81. %%% link_to_remote word,
  82. %%% :url => { :action => "undo", :n => word_counter },
  83. %%% :complete => "undoRequestCompleted(request)"
  84. %%%
  85. %%% The callbacks that may be specified are:
  86. %%%
  87. %%% <tt>:loading</tt>:: Called when the remote document is being
  88. %%% loaded with data by the browser.
  89. %%% <tt>:loaded</tt>:: Called when the browser has finished loading
  90. %%% the remote document.
  91. %%% <tt>:interactive</tt>:: Called when the user can interact with the
  92. %%% remote document, even though it has not
  93. %%% finished loading.
  94. %%% <tt>:complete</tt>:: Called when the XMLHttpRequest is complete.
  95. %%%
  96. %%% If you for some reason or another need synchronous processing (that'll
  97. %%% block the browser while the request is happening), you can specify
  98. %%% <tt>options[:type] = :synchronous</tt>.
  99. %%%
  100. %%% You can customize further browser side call logic by passing
  101. %%% in Javascript code snippets via some optional parameters. In
  102. %%% their order of use these are:
  103. %%%
  104. %%% <tt>:confirm</tt>:: Adds confirmation dialog.
  105. %%% <tt>:condition</tt>:: Perform remote request conditionally
  106. %%% by this expression. Use this to
  107. %%% describe browser-side conditions when
  108. %%% request should not be initiated.
  109. %%% <tt>:before</tt>:: Called before request is initiated.
  110. %%% <tt>:after</tt>:: Called immediately after request was
  111. %%% initiated and before <tt>:loading</tt>.
  112. %%% ================================================================
  113. %%%
  114. %%% Erlang example:
  115. %%%
  116. %%% <div id="time_div">
  117. %%% I don't have the time, but
  118. %%% <%= js:link_to_remote("click here",
  119. %%% [{update, "time_div"},
  120. %%% {type, asynchronous},
  121. %%% {position, "after"},
  122. %%% {url, "/say_when.yaws" }]) %>
  123. %%% and I will look it up.
  124. %%% </div>
  125. %%%
  126. %%% ================================================================
  127. link_to_remote(Name, Function) ->
  128. link_to_remote(Name, Function, []).
  129. link_to_remote(Name, Opts, HtmlOpts) ->
  130. link_to_function(Name, remote_function(Opts), HtmlOpts).
  131. %%% ================================================================
  132. %%% Periodically calls the specified url (<tt>options[:url]</tt>) every
  133. %%% <tt>options[:frequency]</tt> seconds (default is 10).
  134. %%% Usually used to update a specified div (<tt>options[:update]</tt>) with
  135. %%% the results of the remote call. The options for specifying the target
  136. %%% with :url and defining callbacks is the same as link_to_remote.
  137. %%% ================================================================
  138. periodically_call_remote(Opts) ->
  139. Code = periodically_call_remote_S(Opts),
  140. content_tag(script, {pre_html, Code}, x_get(html_options, Opts, [])).
  141. %%% Return only the string.
  142. periodically_call_remote_S(Opts) ->
  143. Frequency = x_get(frequency, Opts, "10"),
  144. "new PeriodicalExecuter(function() {"++
  145. remote_function(Opts)++"}, "++Frequency++")".
  146. %%% ================================================================
  147. %%% Returns a form tag that will submit using XMLHttpRequest in the
  148. %%% background instead of the regular reloading POST arrangement.
  149. %%% Even though it's using Javascript to serialize the form elements,
  150. %%% the form submission will work just like a regular submission as
  151. %%% viewed by the receiving side (all elements available in @params).
  152. %%% The options for specifying the target with :url and defining callbacks
  153. %%% is the same as link_to_remote.
  154. %%% ================================================================
  155. form_remote_tag(Opts0) ->
  156. Opts = x_set(form, Opts0, "true"),
  157. HtmlOpts = x_set(onsubmit, x_get(html, Opts, []),
  158. remote_function(Opts)++"; return false;"),
  159. tag(form, x_set(html, Opts, HtmlOpts), true).
  160. %%% ================================================================
  161. %%% Returns a link that'll trigger a javascript +function+ using the
  162. %%% onclick handler and return false after the fact.
  163. %%%
  164. %%% Examples:
  165. %%% link_to_function "Greeting", "alert('Hello world!')"
  166. %%% link_to_function(image_tag("delete"), "if confirm('Really?'){ do_delete(); }")
  167. %%%
  168. %%% ================================================================
  169. link_to_function(Name, Function) ->
  170. link_to_function(Name, Function, []).
  171. link_to_function(Name, Function, HtmlOpts) ->
  172. content_tag(a, Name,
  173. [{href, "#"},
  174. {onclick, Function++"; return false;"} |
  175. HtmlOpts]).
  176. %%% ================================================================
  177. %%% Observes the field with the DOM ID specified by +field_id+ and makes
  178. %%% an Ajax when its contents have changed.
  179. %%%
  180. %%% Required +options+ are:
  181. %%% <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
  182. %%% this field will be detected.
  183. %%% <tt>:url</tt>:: +url_for+-style options for the action to call
  184. %%% when the field has changed.
  185. %%%
  186. %%% Additional options are:
  187. %%% <tt>:update</tt>:: Specifies the DOM ID of the element whose
  188. %%% innerHTML should be updated with the
  189. %%% XMLHttpRequest response text.
  190. %%% <tt>:with</tt>:: A Javascript expression specifying the
  191. %%% parameters for the XMLHttpRequest. This defaults
  192. %%% to 'value', which in the evaluated context
  193. %%% refers to the new field value.
  194. %%%
  195. %%% Additionally, you may specify any of the options documented in
  196. %%% +link_to_remote.
  197. %%% ================================================================
  198. observe_field(FieldId, Opts) ->
  199. content_tag(script,
  200. {pre_html, observe_field_S(FieldId, Opts)},
  201. [{type, "text/javascript"}]).
  202. observe_field_S(FieldId, Opts) ->
  203. build_observer("Form.Element.Observer", FieldId, Opts).
  204. %%% ================================================================
  205. %%% Like +observe_field+, but operates on an entire form identified by the
  206. %%% DOM ID +form_id+. +options+ are the same as +observe_field+, except
  207. %%% the default value of the <tt>:with</tt> option evaluates to the
  208. %%% serialized (request string) value of the form.
  209. %%% ================================================================
  210. observe_form(FormId, Opts) ->
  211. content_tag(script,
  212. {pre_html, observe_form_S(FormId, Opts)},
  213. [{type, "text/javascript"}]).
  214. observe_form_S(FormId, Opts) ->
  215. build_observer("Form.Observer", FormId, Opts).
  216. %%% ================================================================
  217. %%% Creates a form tag and hidden <iframe> necessary for the upload progress
  218. %%% status messages to be displayed in a designated +div+ on your page.
  219. %%%
  220. %%% == Upload IDs
  221. %%%
  222. %%% For the view and the controller to know about the same upload they must share
  223. %%% a common +upload_id+. This helper prepares the next available +upload_id+ when
  224. %%% called. There are other methods which use the +upload_id+ so the order in which
  225. %%% you include your content is important. Any content that depends on the
  226. %%% +upload_id+ will use the one defined +form_tag_with_upload_progress+
  227. %%% otherwise you will need to explicitly declare the +upload_id+ shared among
  228. %%% your progress elements.
  229. %%%
  230. %%% Status container after form:
  231. %%%
  232. %%% <%= form_tag_with_upload_progress %>
  233. %%% <%= end_form_tag %>
  234. %%%
  235. %%% <%= upload_status_tag %>
  236. %%%
  237. %%% Status container before form:
  238. %%%
  239. %%% <% my_upload_id = next_upload_id %>
  240. %%%
  241. %%% <%= upload_status_tag %>
  242. %%%
  243. %%% <%= form_tag_with_upload_progress :upload_id => my_upload_id %>
  244. %%% <%= end_form_tag %>
  245. %%%
  246. %%% It is recommended that the helpers manage the +upload_id+ parameter.
  247. %%%
  248. %%% == Options
  249. %%%
  250. %%% +form_tag_with_upload_progress+ uses similar options as +form_tag+
  251. %%% yet accepts another hash for the options used for the +upload_status+ action.
  252. %%%
  253. %%% <tt>url_for_options</tt>:: The same options used by +form_tag+ including:
  254. %%% <tt>:upload_id</tt>:: the upload id used to uniquely identify this upload
  255. %%%
  256. %%% <tt>options</tt>:: similar options to +form_tag+ including:
  257. %%% <tt>begin</tt>:: Javascript code that executes before the first status update occurs.
  258. %%% <tt>finish</tt>:: Javascript code that executes after the action that receives the post returns.
  259. %%% <tt>:frequency</tt>:: number of seconds between polls to the upload status action.
  260. %%%
  261. %%% <tt>status_url_for_options</tt>:: options passed to +url_for+ to build the url
  262. %%% for the upload status action.
  263. %%% <tt>:controller</tt>:: defines the controller to be used for the update status action
  264. %%% <tt>:action</tt>:: defines the action to be used for the update status action
  265. %%%
  266. %%% Parameters passed to the action defined by status_url_for_options
  267. %%%
  268. %%% <tt>:upload_id</tt>:: the upload_id automatically generated by +form_tag_with_upload_progress+ or the user defined id passed to this method.
  269. %%%
  270. %%% ================================================================
  271. %%form_tag_with_upload_progress(UrlOpts, Opts, StatOpts, ParamsForUrl) ->
  272. %%
  273. %% %% Setup the action URL and the server-side upload_status action for
  274. %% %% polling of status during the upload
  275. %%
  276. %% UploadId = x_get(upload_id, Opts, random_upload_id()),
  277. %% put(current_upload_id, UploadId),
  278. %% UploadActionUrl = x_get(url, UrlOpts, "/upload_action.yaws"),
  279. %% StatOpts1 = x_defaults(url,
  280. %% x_defaults(upload_id, StatOpts, UploadId),
  281. %% "/upload_status.yaws"),
  282. %% StatusUrl = x_get(url, StatOpts1),
  283. %%
  284. %% %% Prepare the form options. Dynamically change the target and URL to
  285. %% %% enable the status updating only if javascript is enabled, otherwise
  286. %% %% perform the form submission in the same frame.
  287. %%
  288. %% UploadTarget = x_get(target, Opts, upload_target_id()),
  289. %% UploadIdParam = url_append_qs("upload_id="++UploadId),
  290. %%
  291. %% %% Externally :begin and :finish are the entry and exit points
  292. %% %% Internally, :finish is called :complete
  293. %%
  294. %% JsOpts = [{decay, x_get(decay, Opts, frequency_decay())},
  295. %% {frequency, x_get(frequency, Opts, frequency())}],
  296. %%
  297. %% UpdaterOpts = js_opts_map(JsOpts),
  298. %%
  299. %% %% Finish off the updating by forcing the progress bar to 100%
  300. %% %% because the results of the post may load and finish in the
  301. %% %% IFRAME before the last status update is loaded.
  302. %%
  303. %% Opts1 = x_set(complete, Opts,
  304. %% upload_progress_update_bar_js(100)++
  305. %% x_get(finish, Opts, "")),
  306. %%
  307. %% Opts2 = x_get(script, Opts1, "true"),
  308. %%
  309. %% Updater = upload_update_object() ++
  310. %% " = new Ajax.PeriodicalUpdater('"++status_tag_id()++"',"++
  311. %% StatusUrl++
  312. %% options_for_ajax(Opts2++UpdaterOpts),
  313. %%
  314. %% Updater2 = case x_get('begin', Opts2) of
  315. %% {true, Begin} -> Begin++"; "++Updater;
  316. %% false -> Updater
  317. %% end,
  318. %% Updater3 = upload_progress_update_bar_js(0)++"; "++Updater2,
  319. %%
  320. %% %% Touch up the form action and target to use the given target
  321. %% %% instead of the default one. Then start the updater.
  322. %%
  323. %% OnSubmit = "if (this.action.indexOf('upload_id') < 0){ this.action += '"++
  324. %% escape_javascript(UploadIdParam)++"'; }"++
  325. %% "this.target = '"++escape_javascript(UploadTarget)++"';"++
  326. %% Updater3++"; return true",
  327. %%
  328. %% Opts3 = x_set(multipart, x_set(onsubmit, Opts2, OnSubmit), "true"),
  329. %%
  330. %% Opts4 = x_dels(['begin',finish,complete,frequency,decay,script], Opts3),
  331. %%
  332. %% %% Create the tags
  333. %% %% If a target for the form is given then avoid creating the hidden IFRAME
  334. %%
  335. %% Tag = form_tag(Opts4),
  336. %% case x_member(target, Opts4) of
  337. %% {true, _} -> Tag;
  338. %% false ->
  339. %% Tag ++
  340. %% content_tag(iframe, "",
  341. %% [{id, UploadTarget},
  342. %% {name, UploadTarget},
  343. %% {src, ""},
  344. %% {style, "width:0px;height:0px;border:0"}])
  345. %% end.
  346. %%% Default number of seconds between client updates
  347. frequency() -> "2.0".
  348. %%% Factor to decrease the frequency when the +upload_status+ action
  349. %%% returns the same results. To disable update decay, set this
  350. %%% constant to +false+
  351. frequency_decay() -> "1.8".
  352. random_upload_id() ->
  353. {_,_,X} = erlang:now(),
  354. i2l(X rem 97).
  355. %%% Append part of query string to Url
  356. url_append_qs(Url, Qs) ->
  357. url_append_qs(Url, Qs, true).
  358. url_append_qs([$?|T], Qs, _) -> [$?|url_append_qs(T, Qs, false)];
  359. url_append_qs([H|T], Qs, First) -> [H|url_append_qs(T, Qs, First)];
  360. url_append_qs([], Qs, true) -> "?"++Qs;
  361. url_append_qs([], Qs, false) -> "&"++Qs.
  362. %%% Default values
  363. upload_target_id() -> "UploadTarget"++get(current_upload_id).
  364. status_tag_id() -> "UploadStatus"++get(current_upload_id).
  365. progress_bar_id() -> "UploadProgressBar"++get(current_upload_id).
  366. upload_update_object() -> "document.uploadStatus"++get(current_upload_id).
  367. %%%
  368. %%% Internal functions
  369. %%%
  370. escape_javascript(S) -> S. % FIXME
  371. %%form_tag(Opts) ->
  372. %% Hs1 = x_default(method, Opts, "post"),
  373. %% Hs2 = x_swap(multipart, Hs1, {enctype, "multipart/form-data"}),
  374. %% tag(form, x_set(html, Opts, Hs2), true).
  375. content_tag(Tag, Content, Opts) ->
  376. {Tag, Opts, [Content]}.
  377. tag(Tag, Opts) ->
  378. tag(Tag, Opts, false).
  379. tag(Tag, Opts, true) -> {Tag, Opts};
  380. tag(Tag, Opts, false) -> {Tag, Opts, []}.
  381. %%tag(Tag, Opts, Bool) when Bool==true; Bool==false ->
  382. %% "<"++a2l(Tag)++" "++tag_options(Opts)++
  383. %% if (Bool) -> ">"; true -> "/>" end.
  384. tag_options(Opts) ->
  385. lists:foldr(fun({K,V}, Acc) ->
  386. a2l(K)++"=\""++a2l(V)++"\" "++Acc
  387. end,
  388. [], Opts).
  389. remote_function(Opts) ->
  390. JsOpts = options_for_ajax(Opts),
  391. Func = [case x_member(update, Opts) of
  392. {true, Val} ->
  393. "new Ajax.Updater('"++a2l(Val)++"', ";
  394. false ->
  395. "new Ajax.Request("
  396. end,
  397. "'"++a2l(x_get(url, Opts))++"'",
  398. add_not_nil(", ", JsOpts), ")"],
  399. flatten(x_confirm(Opts,
  400. x_condition(Opts,
  401. x_after(Opts,
  402. x_before(Opts, Func))))).
  403. add_not_nil(_, []) -> [];
  404. add_not_nil(S, L) -> S++L.
  405. x_before(Opts, Func) ->
  406. case x_member(before, Opts) of
  407. {true, Val} -> a2l(Val)++"; "++ Func;
  408. false -> Func
  409. end.
  410. x_after(Opts, Func) ->
  411. case x_member('after', Opts) of
  412. {true, Val} -> Func++"; "++a2l(Val);
  413. false -> Func
  414. end.
  415. x_condition(Opts, Func) ->
  416. case x_member(condition, Opts) of
  417. {true, Val} -> "if ("++a2l(Val)++") { "++Func++"; }";
  418. false -> Func
  419. end.
  420. x_confirm(Opts, Func) ->
  421. case x_member(confirm, Opts) of
  422. {true, Val} -> "if (confirm('"++escape(a2l(Val))++"') { "++Func++"; }";
  423. false -> Func
  424. end.
  425. x_member(Key, [{Key, Val}|_]) -> {true, Val};
  426. x_member(Key, [_|T]) -> x_member(Key, T);
  427. x_member(_, []) -> false.
  428. x_get(Key, Opts) ->
  429. x_get(Key, Opts, "").
  430. x_get(Key, Opts, Def) ->
  431. case x_member(Key, Opts) of
  432. {true, Val} -> Val;
  433. _ -> Def
  434. end.
  435. %%% Replace option iff it exist.
  436. x_replace(Key, [{Key,_}|T], Val) -> [{Key,Val}|T];
  437. x_replace(Key, [H|T], Val) -> [H|x_replace(Key, T, Val)];
  438. x_replace(_, [], _) -> [].
  439. %%% Replace option or create it.
  440. x_set(Key, [{Key,_}|T], Val) -> [{Key,Val}|T];
  441. x_set(Key, [H|T], Val) -> [H|x_set(Key, T, Val)];
  442. x_set(Key, [], Val) -> [{Key,Val}].
  443. %%% Set option iff it don't exist.
  444. x_default(Key, [{Key,_} = H|T], _) -> [H|T];
  445. x_default(Key, [H|T], Val) -> [H|x_default(Key, T, Val)];
  446. x_default(Key, [], Val) -> [{Key,Val}].
  447. %%% Swap singleton option
  448. x_swap1(H, [H|T], E) -> [E|T];
  449. x_swap1(X, [H|T], E) -> [H|x_swap1(X, T, E)];
  450. x_swap1(_, [], _) -> [].
  451. %%% Delete option.
  452. x_del(Key, [{Key,_}|T]) -> T;
  453. x_del(Key, [H|T]) -> [H|x_del(Key, T)];
  454. x_del(_, []) -> [].
  455. %%% Delete options
  456. x_dels(Keys, Opts) ->
  457. lists:foldl(fun(K, Acc) -> x_del(K, Acc) end, Opts, Keys).
  458. %%% Option value => list
  459. x_a2l(Key, [{Key,Val}|T]) -> [{Key,a2l(Val)}|T];
  460. x_a2l(Key, [H|T]) -> [H|x_a2l(Key, T)];
  461. x_a2l(_, []) -> [].
  462. %%% Escape carrier returns and single and double quotes for Javascript segments.
  463. escape(Str) ->
  464. Str. % FIXME
  465. options_for_ajax(Opts) ->
  466. js_opts_map(js_options(build_callbacks(Opts))).
  467. js_opts_map(JsOpts) ->
  468. "{"++lists:foldl(fun({X,Y}, Acc) ->
  469. X++":"++Y++add_not_nil(", ", Acc)
  470. end,
  471. "",
  472. JsOpts)++"}".
  473. js_options([{type, synchronous}|T]) ->
  474. [{"asynchronous", "false"}|js_options(T)];
  475. js_options([{type, asynchronous}|T]) ->
  476. [{"asynchronous", "true"}|js_options(T)];
  477. js_options([{method, Method}|T]) ->
  478. [{"method", "'"++a2l(Method)++"'"}|js_options(T)];
  479. js_options([{position, Pos}|T]) ->
  480. [{"insertion", "Insertion."++camelize(a2l(Pos))}|js_options(T)];
  481. js_options([form|T]) ->
  482. [{"parameters", "'Form.serialize(this)'"}|js_options(T)];
  483. js_options([{with, With}|T]) ->
  484. [{"parameters", a2l(With)}|js_options(T)];
  485. js_options([_|T]) ->
  486. js_options(T);
  487. js_options([]) ->
  488. [].
  489. build_observer(Klass, Name, Opts) ->
  490. Opts2 = case x_member(update, Opts) of
  491. {true, _} ->
  492. case x_member(with, Opts) of
  493. {true, _} ->
  494. x_a2l(with, Opts);
  495. false ->
  496. x_set(with, Opts, "value")
  497. end;
  498. false ->
  499. Opts
  500. end,
  501. Callback = remote_function(Opts2),
  502. Frequency = x_get(frequency, Opts2, "10"),
  503. "new "++a2l(Klass)++"('"++a2l(Name)++"', "++
  504. Frequency++", function(element, value) {"++
  505. Callback++"})".
  506. %%% Take a list [{complete, "undoRequestCompleted(request)"},...]
  507. %%% and return it a bit massaged...
  508. build_callbacks(Opts) ->
  509. Callbacks = callbacks(),
  510. lists:foldl(fun({Callback,Code} = E, Acc) ->
  511. case member(Callback, Callbacks) of
  512. {true, Code} ->
  513. [{"on"++capitalize(Callback),
  514. "function(request){"++a2l(Code)++"}"}|Acc];
  515. false ->
  516. [E|Acc]
  517. end
  518. end, [], Opts).
  519. %%%
  520. %%% The callbacks that may be specified are:
  521. %%%
  522. %%% loading Called when the remote document is being
  523. %%% loaded with data by the browser.
  524. %%% loaded Called when the browser has finished loading
  525. %%% the remote document.
  526. %%% interactive Called when the user can interact with the
  527. %%% remote document, even though it has not
  528. %%% finished loading.
  529. %%% complete Called when the XMLHttpRequest is complete.
  530. %%%
  531. callbacks() ->
  532. [uninitialized, loading, loaded, interactive, complete].
  533. %%% "helLo_world" => "Hello_world"
  534. capitalize([H|T]) when H>=$a,H=<$z -> [H-32|T];
  535. capitalize(Str) -> Str.
  536. %%% "helLo_world" => "helloWorld" ?
  537. camelize(Str) ->
  538. capitalize(Str). % FIXME
  539. a2l(A) when atom(A) -> atom_to_list(A);
  540. a2l(L) when list(L) -> L.
  541. i2l(I) when integer(I) -> integer_to_list(I);
  542. i2l(L) when list(L) -> L.