Perl Catalyst - Obtaining JSON data from AJAX POST request

4.2k views Asked by At

I am trying to obtain the JSON data that is sent to the server via an AJAX POST when a button is clicked on my homescreen page on my Catalyst application:

The AJAX POST that sends the JSON data to the server:

$("#saveCanvasStates").click(function () {
// button to save canvas states to a database

// Serialize the states array
var JsonStringForTransport = JSON.stringify({stateForUserNumber7: states});

// POST the JSON to the server
var thePost = $.ajax({
    url: 'homescreen',
    type: 'POST',
    dataType: 'json',
    data: JsonStringForTransport,
    contentType: 'application/json; charset=utf-8'
});

I also have the following Catalyst Controller for the homescreen page which the button that sends the AJAX POST is located on:

Catalyst Controller:

package MyProject::Controller::Homescreen;

use strict;
use warnings;
use parent 'Catalyst::Controller';
use Data::Dumper;

__PACKAGE__->config->{namespace} = '';

sub homescreen :Path('/homescreen') :Args(0)  {

        my ( $self, $c ) = @_;

        $c->stash({title => 'Home Screen',
                   pagetype => 'html',
                   template => 'homescreen.html'
                 });


#here is where I think I need to obtain the JSON data from the AJAX POST request 
#and save it to my database

}

1;

Once I have this JSON data in a form I can work with I will then be saving it to a Postgres database.

From looking at the CPAN docs for Catalyst::Request, as it's my understanding this is what to refer to when dealing with request stuff, it's possible to use the following to do stuff with AJAX POST data?:

  • $c->$req->body_data
  • $c->$req->body_parameters
  • $c->$req->body_params

But I am unsure about the best way to get the data into a form I can then insert into my database and which one of the methods should be used in preference?

I can find very little documentation that has helped me.

Update (relating to RET's answer)

I definitely have body_data to display because when I do:

print Dumper($c->req->body_data);

I get the following printed in my development server log:

$VAR1 = {
          'stateForUserNumber7' => [
                                     {
                                       'width' => 102,
                                       'offsetY' => 56,
                                       'x' => 11,
                                       'height' => 102,
                                       'image' => {},
                                       'y' => 14,
                                       'contextIndex' => 2,
                                       'dragging' => bless( do{\(my $o = 0)}, 'Cpanel::JSON::XS::Boolean' ),
                                       'offsetX' => 73
                                     },
                                     {
                                       'width' => 102,
                                       'offsetY' => 34,
                                       'x' => 103,
                                       'height' => 102,
                                       'image' => {},
                                       'y' => 17,
                                       'contextIndex' => 3,
                                       'dragging' => $VAR1->{'stateForUserNumber7'}[0]{'dragging'},
                                       'offsetX' => 46
                                     }
                                   ]
        };
[info] *** Request 15 (1.250/s) [17427] [Fri Dec  6 00:02:22 2013] ***
[debug] Path is "homescreen"
[debug] "POST" request for "homescreen" from "192.168.1.100"
[debug] Rendering template "homescreen.html"
[debug] Response Code: 200; Content-Type: text/html; charset=utf-8; Content-Length: 7010
[info] Request took 0.025343s (39.459/s)
.------------------------------------------------------------+-----------.
| Action                                                     | Time      |
+------------------------------------------------------------+-----------+
| /homescreen                                                | 0.014044s |
| /end                                                       | 0.001992s |
|  -> Organiser::View::TT->process                           | 0.001058s |
'------------------------------------------------------------+-----------'

Further update

This is the error it gives in the development server output when using -d:

Caught exception in Organiser::Controller::Homescreen->homescreen "Can't use an undefined value as a HASH reference at /home/fred/Organiser/script/../lib/Organiser/Controller/Homescreen.pm line 21."

This is the error I get from Stack Trace when running the development server:

Stack Trace
Package                             Line    File
Organiser::Controller::Homescreen   21    /home/fred/Organiser/lib/Organiser/Controller/Homescreen.pm

18: 
19: print STDERR Dumper $c->req->body_data; 
20: 
21: foreach my $data (@{$c->req->body_data->{stateForUserNumber7}}) {     <-- it highlights in bold this line
22:      print "DOLLAR DATA $data\n"; 
23: } 
24:

Organiser::Controller::Root     17  /home/fred/Organiser/lib/Organiser/Controller/Root.pm

14: sub index :Path :Args(0) { 
15: my ( $self, $c ) = @_; 
16: 
17: $c->forward('homescreen');       <-- it highlights in bold this line
18: 
19: } 
20: 

Using Firebug this is the POST request that is occurring (after I comment out the foreach that is making it error)

Source
{"stateForUserNumber7":[{"dragging":false,"contextIndex":4,"image":{},"x":108,"y":4,"width":102,"height":102,"offsetX":45,"offsetY":65}]}

It is always stateForUserNumber7 (I should have named it master_user or something really)

3

There are 3 answers

7
RET On BEST ANSWER

My other answer is (hopefully) useful information for anyone debugging Catalyst/JSON issues. But subsequent updates to the question have shown the problem here is actually something entirely different.

There are not enough actions to support the required functionality here. If the index action forwards to the homescreen action which renders the homescreen.html template, then the $.json() call has to be to some other action which is responsible for handling the data save request and nothing else.

package MyProject::Controller::Homescreen;

use strict;
use warnings;
use parent 'Catalyst::Controller';
use JSON;

sub homescreen :Path('/homescreen') :Args(0)  {

    my ( $self, $c ) = @_;
    $c->stash({title => 'Home Screen',
               pagetype => 'html',
               template => 'homescreen.html'
    });
}

sub savecanvasstates :Local {
    my ($self, $c, @args) = @_;
    my $p = $c->req->param->{stateForUserNumber7} || die "wrong parameters!";
    my $result = {};
    foreach my $r (@{$p}) {
        # ... do something with $r->{x} etc
        $result->{output} = # construct whatever object the ajax caller expects
    }
    $c->res->body(to_json($result))
}

1;

And in some nearby JavaScript:

$("#saveCanvasStates").click(function () {
    // button to save canvas states to a database

    // Serialize the states array
    var JsonStringForTransport = JSON.stringify({stateForUserNumber7: states});

    // POST the JSON to the server
    var thePost = $.ajax({
        url: 'savecanvasstates',
        type: 'POST',
        dataType: 'json',
        data: JsonStringForTransport,
        contentType: 'application/json; charset=utf-8',
        success: function(data, textStatus){ ... }
        error: function(XMLHttpRequest, textStatus, errorThrown){ ... }
    });
});

I hope all that makes what you have to do clearer.

10
RET On

Your Catalyst app won't receive JSON. The dataType argument tells $.ajax() how to parse the data returned from the server. What's sent to the server is a bog-standard GET or POST request with HTML form parameters.

By the time Catalyst receives them, they're plain old keys inside $c->req->params, perfectly suitable for whatever database actions you want to whip up.

So if

/* JS */
JsonStringForTransport = { foo: "bar", baz: "quux" };

then at the Catalyst end, you'll receive:

# perl
my $foo = $c->req->params->{foo}; # or $c->req->body_params->{foo}
if ($c->req->params->{baz} eq "quux"){ ... }

... and so on.


UPDATE

So body_data contains a hash (unsurprisingly), which has a key stateForUserNumber7 that contains a reference to an array of hashes. That's what that Dumper output is telling you.

You should be able to access body_data using the following:

my $first_x = $c->req->body_data->{stateForUserNumber7}->[0]->{x};
print STDERR "first_x = $first_x\n"; # should print first_x = 11

However, it's more likely you'll want to do something like:

foreach my $data (@{$c->req->body_data->{stateForUserNumber7}}) {
    # do something with $data->{x}, $data->{y}, $data->{height} etc
}

UPDATE

jnap has published a very good article on how to use body_data in the 2013 Catalyst Advent Calendar. If you are using this feature in your app instead of the more traditional params, you should definitely take the time to read and absorb it.


UPDATE

Sorry, misspoke earlier regarding dataType, so I've corrected that. But it doesn't change the fundamentals of my answer.

I think you're going to have to introspect your body_data to figure out what's going wrong. Is it always 'stateForUserNumber7', or does that vary from request to request? What does

$c->log->debug("body_params keys: " _ join(", ", keys %{ $c->req->body_data }));

produce?

You have to use the development server (i.e. bin/myapp_server.pl) so that you get the full debug output to assist you. Debugging via Apache or FastCGI is not recommended at all.

0
John Napiorkowski On

Although the built in logging stuff is great when you are making logs to persist and to do analysts on, I find it not terribly using for basic debugging. I've lately been using Devel::Dwarn which is an easy install. This is like a warn dumper + rolled up very neatly.

If you start your app like so

perl -MDevel::Dwarn ...

You can stick Dwarn $ref and get a nice stderr output:

Dwarn $c->req->body_data

Should give you a neat review. Plus if you drop the -MDevel::Dwarn you'll get errors in all the places you accidentally left Dwarn out (something I should have done with the borked release of Runner 002 last month).

If that is confusing to you just try:

(after cpanm Devel::Dwarn)

use Devel::Dwarn;
Dwarn $c->req->body_data

You should get a deep structure output to your console. Best of luck!