5

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)

halfer
  • 19,824
  • 17
  • 99
  • 186
yonetpkbji
  • 1,019
  • 2
  • 21
  • 35
  • You can try to add to controller this string: 'print STDERR Dumper $c->req->params;' – alex Dec 03 '13 at 22:19
  • `$c->req->body_data` ... "Returns a Perl representation of POST/PUT body data that is not classic HTML form data, such as JSON, XML, etc. **By default, Catalyst will parse incoming data of the type 'application/json' and return access to that data via this method**" -- so what does `$c->req->body_data` look like? – mob Dec 03 '13 at 22:25
  • 1
    @mob - I did try `$c->req->body_data` as I saw the line in the docs that you showed above, but when using it I get an error `"Can't locate object method "body_data" via package "Catalyst::Request"...`? The other ones eg. `$c->$req->body_parameters` do not produce this error? – yonetpkbji Dec 03 '13 at 22:30
  • Then are you using the latest release of `Catalyst`? The `body_data` method is new since version 5.90049_003 (2013-09-20). – mob Dec 03 '13 at 22:35
  • @mob - My version says `powered by Catalyst 5.90042` – yonetpkbji Dec 03 '13 at 22:36
  • Then either upgrade (so you can use `body_data`) or use `$c->req->body` and parse the JSON yourself. – mob Dec 03 '13 at 22:39
  • @mob - Just upgraded to Catalyst 5.90051 and no more error with `$c->req->body_data` thanks. How do I then go about getting/printing the data that the `$c->req->body_data` method obtains? thanks for your help – yonetpkbji Dec 03 '13 at 22:58
  • Just humour me and try using `$c->req->params->{stateForUserNumber7}` instead of `body_data`. BTW your use of `$data` in line 22 is not going to print anything useful, just a HASH memory reference. Try printing `$data->{x}` or `Dumper($data)` – RET Dec 09 '13 at 23:53
  • @RET - I used `req->params` instead of `body_data` in the foreach loop and it does not give the `undefined value as a HASH reference` any more and the app fires up correctly and is usable. But the foreach loop does not print anything to the debug screen (even when I use `$data->{x}` or `Dumper($data)` in it). It's as if its not actually getting into the foreach loop – yonetpkbji Dec 10 '13 at 00:01
  • I think you're just going to have to keep on poking around in the params hash. Have you tried changing line 19 to dump `$c->req->params` instead of `body_data`? Perhaps the structure is subtly different? – RET Dec 10 '13 at 00:10
  • @RET - When using `print STDERR Dumper $c->req->params;` it does not dump anything... it just gives `$VAR1 = {};`? – yonetpkbji Dec 10 '13 at 00:20
  • Well, that's behaviour consistent with not having any parameters. Does your server log show that any were sent? Never mind, I've realized the problem. Am writing a new answer. Stand by... – RET Dec 10 '13 at 00:27
  • See new answer. I've left the old one in place in case it's useful in its own right to anyone. – RET Dec 10 '13 at 01:03

3 Answers3

4

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!

Wladimir Palant
  • 56,865
  • 12
  • 98
  • 126
2

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.

RET
  • 9,100
  • 1
  • 28
  • 33
  • I found this in Catalyst::Request on cpan: `$req->body_data - Catalyst will parse incoming data of the type 'application/json' and return access to that data via this method.` which may be the reason I get absolutely nothing when I use `print STDERR Dumper $c->req->params;` but do when I use `print STDERR Dumper $c->req->body_data;`? I take this to mean application/json is automatically only handled by `body_data`? – yonetpkbji Dec 10 '13 at 21:41
  • If what you've written in your original post is an accurate reflection of your software, there's a fundamental issue in the design. You cannot support an AJAX call and a default template rendering within the same action without doing something seriously weird. – RET Dec 10 '13 at 21:48
  • The latest and greatest version of Catalyst may indeed read and return JSON automagically, but that doesn't fix the issue with the code you've provided. It would only simplify the `savecanvasstates` action to a degree. – RET Dec 10 '13 at 21:52
  • I tried your new answer... it dosn't seem to ever get into the `savecanvasstates` sub. Ive added some print statements `print STDERR "HELLO\n";` to the sub/action but they never get printed on the debug output of the development server?. thanks a lot – yonetpkbji Dec 10 '13 at 21:57
  • Figured out the above problem - I specified the explicit path `sub savecanvasstates :Path('/homescreen') :Args(0) {` instead of `:Local` and it now gets inside the sub/action. – yonetpkbji Dec 10 '13 at 22:14
  • Glad to hear it - the exact URI architecture wasn't clear enough from the OP to know exactly whether that should be a :Local, :Path or some other action type. Please tick and/or upvote answer(s) that solved your problem. – RET Dec 10 '13 at 22:18
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/42898/discussion-between-perl-user-and-ret) – yonetpkbji Dec 10 '13 at 22:25
1

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.

David Precious
  • 6,544
  • 1
  • 24
  • 31
RET
  • 9,100
  • 1
  • 28
  • 33
  • Excellent, your explanation at the top was very useful thanks. What is the correct way to print certain elements or loop through and print all elements from the `$c->req->body_data` data structure. Thanks for your help – yonetpkbji Dec 04 '13 at 21:55
  • If you just want to dump them out to inspect them, use `$c->log->debug("body_data: " . Dumper($c->req->body_data));`. But the GET and POST parameters should be visible in your server log - there's a table of them in the output for every request cycle. – RET Dec 05 '13 at 01:35
  • What if I wanted to store a particular element of the `body_data` data structure in a variable or store/print a particular branch of the `body_data` data structure. How do I iterate through the elements in this data structure? thanks for your help. – yonetpkbji Dec 05 '13 at 09:49
  • It is just a data structure. Have a look and see what `keys %{$c->req->body_data}` returns. It should be just like the foo and baz examples in my answer. – RET Dec 05 '13 at 10:25
  • When I do the following: `foreach my $var1 (keys %{$c->req->body_data}) { print "$var1\n"; }` I keep getting a `Can't use an undefined value as a HASH reference at...` error. What am I doing wrong here. I get the same error when I try and do `print keys %{$c->req->body_data};`. Thanks for your help – yonetpkbji Dec 05 '13 at 15:39
  • Are you sure you have `body_data` to display? What output are you getting in your development server log? BTW, you should definitely not use `print` (defaulting to `STDOUT`) in Catalyst, it plays havoc with what gets returned to the client. `print STDERR` is fine. – RET Dec 05 '13 at 22:21
  • Yes I definitely think I have `body_data`, please see the update to my question. Thanks for the `print STDERR` tip as well :) – yonetpkbji Dec 06 '13 at 00:00
  • Thanks for the Advent Calendar update, I shall definitely have a look. Also when I use the method you suggested in your update to access the body_data I still get the following error `Caught exception in Organiser::Controller::Homescreen->homescreen "Can't use an undefined value as a HASH reference at...`. I cant see why it is not working though? thanks for your help – yonetpkbji Dec 09 '13 at 18:57
  • I've updated my answer again. You might also want to have a look at the Developer Tools in your browser, and investigate what the browser is sending to your server with each request. That could illuminate things a bit. – RET Dec 09 '13 at 23:11
  • Thanks, I've updated my question again to give some more error details. Also I get a `Bareword found error` when using `$c->log->debug("body_params keys: " _ join(", ", keys %{ $c->req->body_data }));`, thanks very much for your help. – yonetpkbji Dec 09 '13 at 23:45