Subject: | [PATCH] Handle file uploads by embedding response in <textarea> |
This patch (+docs + tests) adds an 'embed response in textarea' feature to the module:
=head1 WORKAROUND FOR FILE UPLOADS
Unfortunately XMLHttpsRequest does not support file uploads. A common
workaround is to use a hidden IFrame to process the upload, and return JSON
content embedded in a C<E<lt>textareaE<gt>> element to avoid mangling HTML
entities. See the 'Files' tab of L<http://malsup.com/jquery/form/#code-samples>
for more details.
You can enable embedding JSON in a textarea with L</allow_textarea_embed>, and
control which request parameter is used with L</textarea_embed_param>. An
embedded response will look like this:
Content-type: text/html
<html><body><textarea>{ "result": "foo" }</textarea></body></html>
Subject: | Catalyst-View-JSON-0.24-textarea-embeds.patch |
diff -ruN Catalyst-View-JSON-0.24.orig/lib/Catalyst/View/JSON.pm Catalyst-View-JSON-0.24/lib/Catalyst/View/JSON.pm
--- Catalyst-View-JSON-0.24.orig/lib/Catalyst/View/JSON.pm 2008-03-03 09:44:14.000000000 +0000
+++ Catalyst-View-JSON-0.24/lib/Catalyst/View/JSON.pm 2008-04-07 13:02:53.000000000 +0100
@@ -8,7 +8,7 @@
use NEXT;
use Catalyst::Exception;
-__PACKAGE__->mk_accessors(qw( allow_callback callback_param expose_stash encoding json_dumper no_x_json_header ));
+__PACKAGE__->mk_accessors(qw( allow_callback callback_param allow_textarea_embed textarea_embed_param expose_stash encoding json_dumper no_x_json_header ));
sub new {
my($class, $c, $arguments) = @_;
@@ -80,6 +80,10 @@
my $cb = $cb_param ? $c->req->param($cb_param) : undef;
$self->validate_callback_param($cb) if $cb;
+ my $txt_param = $self->allow_textarea_embed
+ ? ($self->textarea_embed_param || 'embed_in_textarea') : undef;
+ my $embed_in_txt = $txt_param ? $c->req->param($txt_param) : undef;
+
my $json = $self->json_dumper->($data, $self, $c); # weird order to be backward compat
# When you set encoding option in View::JSON, this plugin DWIMs
@@ -92,7 +96,9 @@
$json = Encode::encode($encoding, $json);
}
- if (($c->req->user_agent || '') =~ /Opera/) {
+ if ($embed_in_txt) {
+ $c->res->content_type("text/html; charset=$encoding");
+ } elsif (($c->req->user_agent || '') =~ /Opera/) {
$c->res->content_type("application/x-javascript; charset=$encoding");
} else {
$c->res->content_type("application/json; charset=$encoding");
@@ -109,9 +115,11 @@
$output = "\xEF\xBB\xBF";
}
+ $output .= '<html><body><textarea>' if $embed_in_txt;
$output .= "$cb(" if $cb;
$output .= $json;
$output .= ");" if $cb;
+ $output .= '</textarea></body></html>' if $embed_in_txt;
$c->res->output($output);
}
@@ -171,6 +179,18 @@
Name of URI parameter to specify JSON callback function name. Defaults
to C<callback>. Only effective when C<allow_callback> is turned on.
+=item allow_textarea_embed
+
+Flag to allow embedding JSON responses in textareas by adding
+C<embed_in_textarea=1>. Defaults to 0 (doesn't allow embedding in textareas).
+See L</WORKAROUND FOR FILE UPLOADS> for details.
+
+=item textarea_embed_param
+
+Name of URI parameter to determine whether or not to embed the JSON response
+in a textarea. Defaults to C<embed_in_textarea>. Only effective when
+C<allow_textarea_embed> is turned on.
+
=item expose_stash
Scalar, List or regular expression object, to specify which stash keys are
@@ -311,6 +331,22 @@
L<http://ajaxian.com/archives/jsonp-json-with-padding> for more about
JSONP.
+=head1 WORKAROUND FOR FILE UPLOADS
+
+Unfortunately XMLHttpsRequest does not support file uploads. A common
+workaround is to use a hidden IFrame to process the upload, and return JSON
+content embedded in a C<E<lt>textareaE<gt>> element to avoid mangling HTML
+entities. See the 'Files' tab of L<http://malsup.com/jquery/form/#code-samples>
+for more details.
+
+You can enable embedding JSON in a textarea with L</allow_textarea_embed>, and
+control which request parameter is used with L</textarea_embed_param>. An
+embedded response will look like this:
+
+ Content-type: text/html
+
+ <html><body><textarea>{ "result": "foo" }</textarea></body></html>
+
=head1 INTEROPERABILITY
JSON use is still developing and has not been standardized. This
diff -ruN Catalyst-View-JSON-0.24.orig/t/01_server.t Catalyst-View-JSON-0.24/t/01_server.t
--- Catalyst-View-JSON-0.24.orig/t/01_server.t 2008-03-03 09:24:13.000000000 +0000
+++ Catalyst-View-JSON-0.24/t/01_server.t 2008-04-07 12:25:26.000000000 +0100
@@ -13,7 +13,7 @@
plan skip_all => "JSON 2.04 is needed for testing";
}
-plan tests => 40;
+plan tests => 46;
BEGIN {
no warnings 'redefine';
@@ -142,4 +142,20 @@
is $data->{foo}, "fake";
}
+{
+ my $request = HTTP::Request->new( GET => "http://localhost/foo7?embed_in_textarea=1" );
+
+ ok( my $response = request($request), 'Request' );
+ ok( $response->is_success, 'Response Successful 2xx' );
+ is( $response->code, 200, 'Response Code' );
+ is_deeply( [ $response->content_type ], [ 'text/html', 'charset=utf-8' ], 'content type' );
+
+ my $body = $response->content;
+ like( $body, qr|^<html><body><textarea>.+</textarea></body></html>$|, 'embedded in textarea' );
+
+ my ($d1,$json,$d2) = split(qr|</?textarea>|, $body);
+
+ my $data = JSON::from_json($json);
+ is_deeply( $data, {json_foo => 'bar', json_baz => [1, 2, 3]}, 'extracted JSON data' );
+}
diff -ruN Catalyst-View-JSON-0.24.orig/t/lib/TestApp.pm Catalyst-View-JSON-0.24/t/lib/TestApp.pm
--- Catalyst-View-JSON-0.24.orig/t/lib/TestApp.pm 2008-03-03 09:24:13.000000000 +0000
+++ Catalyst-View-JSON-0.24/t/lib/TestApp.pm 2008-04-07 12:58:53.000000000 +0100
@@ -65,6 +65,17 @@
$c->forward('TestApp::View::JSON2');
}
+sub foo7 : Global {
+ my( $self, $c ) = @_;
+ my $jc = $c->component('View::JSON');
+ $jc->expose_stash(qr/^json_/);
+ $jc->allow_textarea_embed(1);
+ $jc->textarea_embed_param('embed_in_textarea');
+ $c->stash->{json_foo} = "bar";
+ $c->stash->{json_baz} = [ 1, 2, 3 ];
+ $c->forward('TestApp::View::JSON');
+}
+
sub finalize_error {
my $c = shift;
$c->res->header('X-Error' => $c->error->[0]);