Search code examples
jqueryajaxjsonperlcatalyst

Perl Catalyst - Obtaining JSON data from AJAX POST request


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)


Solution

  • 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.