1 package BS
::HTTPD
::Appgets
;
5 use CGI qw
/escapeHTML/;
9 our @ISA = qw
/Exporter/;
11 our @EXPORT = qw
/o
link abutton set_request set_httpd js
12 js_ajaxobj_func capture form entry submit
/;
16 BS::HTTPD::Appgets - Some utility functions for web applications
20 This module mostly exports these functions:
28 =item B<set_request ($ref)>
30 This function sets the current request the output is appended
37 my ($httpd, $req) = @_;
40 o "<html><body><h1>test</h1></body></html>";
48 sub set_request
{ $REQ = $_[0] }
50 =item B<capture ($block)>
52 C<capture> temporarily redirects the output done in C<$block> and returns it.
54 This function should be called with a block as argument like this:
57 o ("<html><body>Hi</body></html>")
60 The return value will be simply the concationated output as it would be sent to the
61 callback or appended to the reference given to C<set_output>.
79 This function will append all arguments it gets and
80 append that to the current output context, which is either
81 set by the C<capture> function or C<set_request>.
83 If it is called outside a C<capture> function it will just forward
84 everything to the C<o> method of C<set_request>.
89 if (ref $REQ ne 'SCALAR') {
90 $REQ->o (join '', @_);
96 =item B<form ($block, $callback)>
98 This function will generate a html form for you, which you can fill
99 with your own input elements. The C<$callback> will be called when the next
100 request is handled and if the form was submitted. It will be executed before any
101 of your content callbacks are run.
102 The C<form> function has a special prototype which allows this syntax:
106 entry (\$new_element);
107 o '<input type="submit" value="append"/>'
109 push @list, $new_element;
112 This function is just a convenience wrapper around the C<form> method
113 of the L<BS::HTTPD> object.
118 my ($blk, $formcb) = @_;
119 $curform = { next_field_idx
=> 1 };
120 my $f = capture
{ $blk->() };
121 my $thisform = $curform;
126 for (keys %{$thisform->{flds
}}) {
127 ${$thisform->{flds
}->{$_}} = $req->parm ("field$_");
132 o
($REQ->form ($f, $set_refs));
135 =item B<entry ($ref)>
137 This function will output a text input form field via the C<o> function
138 which will set the scalar reference to the value of the text field
139 when the form is submitted.
141 See also the C<form> function above for an example.
147 my $idx = $curform->{next_field_idx
}++;
148 $curform->{flds
}->{$idx} = $ref;
149 o
"<input type=\"text\" name=\"field$idx\" value=\"".escapeHTML
($$ref)."\" />";
152 =item B<submit ($label)>
154 This function will output a submit button with the label C<$label>.
160 o
"<input type=\"submit\" value=\"".escapeHTML
($lbl)."\" />";
165 This function will output the C<@strs> appended enclosed in a HTML
166 script tag for javascript.
168 See also the C<o> function.
173 o
("<script type=\"text/javascript\">\n");
178 =item B<js_ajaxobj_func ($funcname)>
180 This function will output javascript compatibility cruft code
181 to get a XMLHttpRequest object. The javascript function C<$funcname>
182 will be declared and can be called in javascript code with the
183 content callback as first argument:
185 js_ajaxobj_func 'newxhreq';
188 function response_cb (xh, textcontent) {
192 var xh = newxhreq (response_cb);
193 xh.open ("GET", "/", true)
197 The first argument of the C<response_cb> is the XMLHttpRequest object
198 and the second the responseText of the finished request.
202 sub js_ajaxobj_func
{
205 function $funcname (content_cb) {
208 if( !window.XMLHttpRequest ) XMLHttpRequest = function()
210 try{ return new ActiveXObject("Msxml2.XMLHTTP.6.0") }catch(e){}
211 try{ return new ActiveXObject("Msxml2.XMLHTTP.3.0") }catch(e){}
212 try{ return new ActiveXObject("Msxml2.XMLHTTP") }catch(e){}
213 try{ return new ActiveXObject("Microsoft.XMLHTTP") }catch(e){}
214 throw new Error("Could not find an XMLHttpRequest alternative.")
217 xh = new XMLHttpRequest ();
219 xh.onreadystatechange = function () {
220 if (xh.readyState == 4 && xh.status == 200) {
221 content_cb (xh, xh.responseText);
229 =item B<link ($label, $callback, $newurl)>
231 This does exactly the same as the C<link> method of L<BS::HTTPD::Request>
232 just uses the current request as object and prints out the link via the C<o>
247 =item B<$BS::HTTPD::Appgets::JSON_JS>
249 This variable contains the javascript source of the JSON serializer
250 and deserializer described in L<http://www.JSON.org/js.html>.
252 You can use this in your application by for example output it via the C<js> function
255 js ($BS::HTTPD::Appgets::JSON_JS);
261 our $JSON_JS = <<'JSON_JS_CODE';
268 No warranty expressed or implied. Use at your own risk.
270 See http://www.JSON.org
/js
.html
272 This file creates a global JSON object containing two methods
:
274 JSON
.stringify
(value
, whitelist
)
275 value any JavaScript value
, usually an object
or array
.
277 whitelist an optional array parameter that determines how object
278 values are stringified
.
280 This method produces a JSON text from a JavaScript value
.
281 There are three possible ways to stringify an object
, depending
282 on the optional whitelist parameter
.
284 If an object has a toJSON method
, then the toJSON
() method will be
285 called
. The value returned from the toJSON method will be
288 Otherwise
, if the optional whitelist parameter is an array
, then
289 the elements of the array will be used to
select members of the
290 object
for stringification
.
292 Otherwise
, if there is
no whitelist parameter
, then all of the
293 members of the object will be stringified
.
295 Values that
do not have JSON representaions
, such as undefined
or
296 functions
, will
not be serialized
. Such
values in objects will be
297 dropped
; in arrays will be replaced with null
.
298 JSON
.stringify
(undefined
) returns undefined
. Dates will be
299 stringified as quoted ISO dates
.
303 var text
= JSON
.stringify
(['e', {pluribus
: 'unum'}]);
304 // text is
'["e",{"pluribus":"unum"}]'
306 JSON
.parse
(text
, filter
)
307 This method parses a JSON text to produce an object
or
308 array
. It can throw a SyntaxError exception
.
310 The optional filter parameter is a function that can filter
and
311 transform the results
. It receives
each of the
keys and values, and
312 its
return value is used instead of the original value
. If it
313 returns what it received
, then structure is
not modified
. If it
314 returns undefined then the member is deleted
.
318 // Parse the text
. If a key contains the string
'date' then
319 // convert the value to a date
.
321 myData
= JSON
.parse
(text
, function
(key
, value
) {
322 return key
.indexOf
('date') >= 0 ? new Date
(value
) : value
;
325 This is a reference implementation
. You are free to copy
, modify
, or
328 Use your own copy
. It is extremely unwise to load third party
329 code into your pages
.
332 /*jslint evil: true */
336 /*members
"\b", "\t", "\n", "\f", "\r", "\"", JSON
, "\\", apply
,
337 charCodeAt
, floor
, getUTCDate
, getUTCFullYear
, getUTCHours
,
338 getUTCMinutes
, getUTCMonth
, getUTCSeconds
, hasOwnProperty
, join, length,
339 parse
, propertyIsEnumerable
, prototype, push, replace
, stringify
, test
,
347 function f
(n
) { // Format integers to have at least two digits
.
348 return n
< 10 ?
'0' + n
: n
;
351 Date
.prototype.toJSON
= function
() {
353 // Eventually
, this method will be based on the date
.toISOString method
.
355 return this
.getUTCFullYear
() + '-' +
356 f
(this
.getUTCMonth
() + 1) + '-' +
357 f
(this
.getUTCDate
()) + 'T' +
358 f
(this
.getUTCHours
()) + ':' +
359 f
(this
.getUTCMinutes
()) + ':' +
360 f
(this
.getUTCSeconds
()) + 'Z';
364 var m
= { // table of character substitutions
374 function stringify
(value
, whitelist
) {
375 var a
, // The array holding the partial texts
.
376 i
, // The
loop counter
.
377 k
, // The member key
.
379 r
= /["\\\x00-\x1f\x7f-\x9f]/g,
380 v
; // The member value
.
382 switch
(typeof value
) {
385 // If the string contains
no control characters
, no quote characters
, and no
386 // backslash characters
, then we can safely slap some quotes around it
.
387 // Otherwise we must also replace the offending characters with safe sequences
.
389 return r
.test
(value
) ?
390 '"' + value
.replace
(r
, function
(a
) {
396 return '\\u00' + Math
.floor
(c
/ 16).toString
(16) +
397 (c
% 16).toString
(16);
403 // JSON numbers must be finite
. Encode non
-finite numbers as null
.
405 return isFinite
(value
) ? String
(value
) : 'null';
409 return String
(value
);
413 // Due to a specification blunder
in ECMAScript
,
414 // typeof null is
'object', so watch out
for that case
.
420 // If the object has a toJSON method
, call it
, and stringify the result
.
422 if (typeof value
.toJSON
=== 'function') {
423 return stringify
(value
.toJSON
());
426 if (typeof value
.length === 'number' &&
427 !(value
.propertyIsEnumerable
('length'))) {
429 // The object is an array
. Stringify every element
. Use null as a placeholder
430 // for non
-JSON
values.
433 for (i
= 0; i
< l
; i
+= 1) {
434 a
.push(stringify
(value
[i
], whitelist
) || 'null');
437 // Join all of the elements together
and wrap them
in brackets
.
439 return '[' + a
.join(',') + ']';
443 // If a whitelist
(array of
keys) is provided
, use it to
select the components
446 l
= whitelist
.length;
447 for (i
= 0; i
< l
; i
+= 1) {
449 if (typeof k
=== 'string') {
450 v
= stringify
(value
[k
], whitelist
);
452 a
.push(stringify
(k
) + ':' + v
);
458 // Otherwise
, iterate through all of the
keys in the object
.
461 if (typeof k
=== 'string') {
462 v
= stringify
(value
[k
], whitelist
);
464 a
.push(stringify
(k
) + ':' + v
);
470 // Join all of the member texts together
and wrap them
in braces
.
472 return '{' + a
.join(',') + '}';
477 stringify
: stringify
,
478 parse
: function
(text
, filter
) {
481 function walk
(k
, v
) {
483 if (v
&& typeof v
=== 'object') {
485 if (Object
.prototype.hasOwnProperty
.apply
(v
, [i
])) {
487 if (n
!== undefined
) {
499 // Parsing happens
in three stages
. In the first stage
, we run the text against
500 // regular expressions that look
for non
-JSON patterns
. We are especially
501 // concerned with
'()' and 'new' because they can cause invocation
, and '='
502 // because it can cause mutation
. But just to be safe
, we want to reject all
505 // We
split the first stage into
4 regexp operations
in order to work around
506 // crippling inefficiencies
in IE
's and Safari's regexp engines
. First we
507 // replace all backslash pairs with
'@' (a non
-JSON character
). Second
, we
508 // replace all simple value tokens with
']' characters
. Third
, we
delete all
509 // open brackets that follow a colon
or comma
or that begin the text
. Finally
,
510 // we look to see that the remaining characters are only whitespace
or ']' or
511 // ',' or ':' or '{' or '}'. If that is so
, then the text is safe
for eval.
513 if (/^[\],:{}\s]*$/.test
(text
.replace
(/\\./g, '@').
514 replace
(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
515 replace
(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
517 // In the second stage we
use the
eval function to compile the text into a
518 // JavaScript structure
. The
'{' operator is subject to a syntactic ambiguity
519 // in JavaScript
: it can begin a block
or an object literal
. We wrap the text
520 // in parens to eliminate the ambiguity
.
522 j
= eval('(' + text
+ ')');
524 // In the optional third stage
, we recursively walk the new structure
, passing
525 // each name
/value pair to a filter function
for possible transformation
.
527 return typeof filter
=== 'function' ? walk
('', j
) : j
;
530 // If the text is
not JSON parseable
, then a SyntaxError is thrown
.
532 throw new SyntaxError
('parseJSON');