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