[Catalyst-commits] r12444 -
trunk/examples/CatalystAdvent/root/2009/pen
zamolxes at dev.catalyst.perl.org
zamolxes at dev.catalyst.perl.org
Mon Dec 21 14:43:51 GMT 2009
Author: zamolxes
Date: 2009-12-21 14:43:51 +0000 (Mon, 21 Dec 2009)
New Revision: 12444
Added:
trunk/examples/CatalystAdvent/root/2009/pen/22.pod
Removed:
trunk/examples/CatalystAdvent/root/2009/pen/ajax-crud.pod
Log:
reserve slot
Copied: trunk/examples/CatalystAdvent/root/2009/pen/22.pod (from rev 12443, trunk/examples/CatalystAdvent/root/2009/pen/ajax-crud.pod)
===================================================================
--- trunk/examples/CatalystAdvent/root/2009/pen/22.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2009/pen/22.pod 2009-12-21 14:43:51 UTC (rev 12444)
@@ -0,0 +1,369 @@
+=head1 An AJAX CRUD Interface with Catalyst and jQuery
+
+In this example, we will develop an AJAX CRUD (Create Read Update Delete)
+extension to the Catalyst tutorial.
+
+AJAX stands for Asynchronous JavaScript And XML, although these days most
+people use JSON instead of XML. The idea is to make something that's closer to
+a GUI app rather than a traditional web page.
+
+=head2 Installing flexigrid
+
+First, get the tutorial tarball from
+L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/MyApp_Chapter9_FormFu.tgz>.
+
+We'll use flexigrid for our grid, from L<http://www.flexigrid.info/>.
+
+From the flexigrid zip, put the contents of C<css/flexigrid/images> and
+C<css/images> into C<root/static/images/flexigrid>.
+
+C<css/flexigrid/flexigrid.css> goes into C<root/static/css>. Edit the file and
+do an C<< %s!url(images/!url(../images/flexigrid/! >> substitution.
+
+C<flexigrid.js> goes into C<root/static/js>.
+
+Get jQuery from L<http://jqueryjs.googlecode.com/files/jquery-1.3.2.js> and
+save it to C<root/static/js/jquery.js>.
+
+=head2 Creating a Grid
+
+Let's make a controller for ajax stuff:
+
+ package MyApp::Controller::AJAX;
+
+ use strict;
+ use warnings;
+ use parent 'Catalyst::Controller::HTML::FormFu';
+
+ sub index : Path Args(0) {
+ my ($self, $c) = @_;
+
+ $c->stash(
+ no_wrapper => 1,
+ template => 'ajax.tt'
+ );
+ }
+
+ sub end : ActionClass('RenderView') {}
+
+Edit C<root/src/wrapper.tt2> and put this at the top:
+
+ [% IF no_wrapper %]
+ [% content %]
+ [% ELSE %]
+
+And an
+
+ [% END %]
+
+at the bottom.
+
+This allows not using the wrapper for some pages, as we do here.
+
+Now for the HTML:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml"; xml:lang="en" lang="en">
+ <head>
+ <link rel="stylesheet" href="[% c.uri_for('/static/css/flexigrid.css') %]" />
+ <script language="javascript" src="[% c.uri_for('/static/js/jquery.js') %]">
+ </script>
+ <script language="javascript" src="[% c.uri_for('/static/js/flexigrid.js') %]">
+ </script>
+ </head>
+ <body>
+
+ <table id="books_grid" style="display:none"></table>
+
+ <style>
+ .flexigrid div.fbutton .add
+ {
+ background: url([% c.uri_for('/static/images/flexigrid/add.png') %]) no-repeat center left;
+ }
+ .flexigrid div.fbutton .edit
+ {
+ background: url([% c.uri_for('/static/images/pen-16.gif') %]) no-repeat center left;
+ }
+ .flexigrid div.fbutton .delete
+ {
+ background: url([% c.uri_for('/static/images/flexigrid/close.png') %]) no-repeat center left;
+ }
+ </style>
+
+ <script type="text/javascript">
+ function add_book(button, grid) {}
+ function edit_book(button, grid) {}
+ function delete_book(button, grid) {}
+
+ $("#books_grid").flexigrid({
+ url: '/api/grid',
+ dataType: 'json',
+ colModel : [
+ {display: 'id', name : 'id', width : 0, sortable : false, hide: true},
+ {display: 'Title', name : 'title', width : 200, sortable : true, align: 'left'},
+ {display: 'Rating', name : 'rating', width : 30, sortable : true, align: 'right'},
+ {display: 'Author(s)', name : 'authors', width : 275, sortable : true, align: 'left'},
+ ],
+ searchitems : [
+ {display: 'Title', name: 'title', isdefault: true},
+ {display: 'Rating', name: 'rating' },
+ ],
+ buttons : [
+ {name: 'Add', bclass: 'add', onpress : add_book},
+ {name: 'Edit', bclass: 'edit', onpress : edit_book},
+ {name: 'Delete', bclass: 'delete', onpress : delete_book},
+ {separator: true}
+ ],
+ sortname: "title",
+ sortorder: "asc",
+ usepager: true,
+ title: 'Books',
+ useRp: true,
+ rp: 10,
+ width: 550,
+ height: 245
+ });
+ </script>
+ </body>
+ </html>
+
+The C<pen-16.gif> icon is from L<http://brainstormsandraves.com/zips/icons/skdesigns-30-creamcoffee-blog-icons-16x16-10x10.zip>.
+
+Now we need to make an API controller that serves JSON for the grid:
+
+ package MyApp::Controller::API;
+
+ use strict;
+ use warnings;
+ use parent 'Catalyst::Controller::REST';
+
+ __PACKAGE__->config(default => 'application/json');
+
+ sub grid : Local ActionClass('REST') {}
+
+ sub grid_POST {
+ my ($self, $c) = @_;
+
+ my ($page, $search_by, $search_text, $rows, $sort_by, $sort_order) =
+ @{ $c->req->params }{qw/page qtype query rp sortname sortorder/};
+
+ s/\W*(\w+).*/$1/ for $sort_by, $sort_order, $search_by; # sql injections bad
+
+ my %data;
+
+ my $rs = $c->model('DB::Book')->search({}, {
+ page => $page,
+ rows => $rows,
+ order_by => "$sort_by $sort_order",
+ });
+
+ $rs = $rs->search_literal("lower($search_by) LIKE ?", lc($search_text))
+ if $search_by && $search_text;
+
+ $data{total} = $c->model('DB::Book')->count;
+ $data{page} = $page;
+ $data{rows} = [
+ map { +{
+ id => $_->id,
+ cell => [
+ $_->id,
+ $_->title,
+ $_->rating,
+ $_->author_list,
+ ]
+ } } $rs->all
+ ];
+
+ $self->status_ok($c, entity => \%data);
+ }
+
+ 1;
+
+Now start the server and go to L<http://localhost:3000/ajax> and you will see a
+lovely AJAX grid of books.
+
+You can click on the magnifying glass icon at the lower left to search by title
+or rating, search by author is left as an excercise for the reader :)
+
+=head2 Deleting
+
+The Add, Edit and Delete buttons we put at the top of the grid don't work yet.
+
+Let's implement the Delete button.
+
+Expand the C<delete_book> JavaScript function stub to:
+
+ function delete_book(button, grid) {
+ var total_count = $('.trSelected', grid).length;
+ var deleted = 0;
+
+ $.each($('.trSelected', grid), function() {
+ var id = $('td:nth-child(1) div', this).html();
+ $.ajax({
+ url: '/api/book/' + id,
+ type: 'DELETE',
+ data: {},
+ dataType: 'json',
+ success: function() {
+ deleted++;
+ if (deleted == total_count) {
+ $('#books_grid').flexReload();
+ }
+ }
+ });
+ });
+ }
+
+Add to the API controller to support the Delete action:
+
+ sub book : Local ActionClass('REST') {
+ my ($self, $c, $id) = @_;
+
+ $c->stash(book => $c->model('DB::Book')->find($id));
+ }
+
+ sub book_DELETE {
+ my ($self, $c, $id) = @_;
+
+ $c->stash->{book}->delete;
+
+ $self->status_ok($c, entity => { message => 'success' });
+ }
+
+Now restart the server, go back to the grid in your browser and try selecting
+some rows and pressing the Delete button, voila!
+
+=head2 Adding and Editing
+
+For this part we'll need the jQuery Form plugin, which you can get from
+L<http://jquery.malsup.com/form/jquery.form.js?2.36>. Save it as
+C<root/static/js/jquery.form.js>.
+
+Add a C<< <script> >> link at the top of C<ajax.tt> for it:
+
+ <script language="javascript" src="[% c.uri_for('/static/js/jquery.form.js') %]">
+ </script>
+
+And above the C<< <table id="books_grid" ... >> tag we'll put a div to hold the
+popup form.
+
+ <div id="book_form" style="display:none"></div>
+
+A jQuery UI plugin for a popup would probably be nicer for this purpose.
+
+The JavaScript for the Add and Edit buttons is fairly simple:
+
+ function add_book(button, grid) {
+ var form_div = $('#book_form');
+
+ form_div.load('/ajax/book_form_add', null, function() {
+ $('#book_form form').ajaxForm({
+ url: '/ajax/book_form_add',
+ success: function() {
+ form_div.hide();
+ $('#books_grid').flexReload();
+ }
+ });
+ form_div.show();
+ });
+ }
+
+ function edit_book(button, grid) {
+ var id = $('.trSelected td:nth-child(1) div', grid).html();
+ var form_div = $('#book_form');
+ var url = '/ajax/book_form_edit/' + id;
+
+ form_div.load(url, null, function() {
+ $('#book_form form').ajaxForm({
+ url: url,
+ success: function() {
+ form_div.hide();
+ $('#books_grid').flexReload();
+ }
+ });
+ form_div.show();
+ });
+ }
+
+Now we need the AJAX Controller actions, in which we'll use the FormFu stuff
+that's already a part of the tutorial, but without the wrapper since we're
+using C<.load> to load the forms as HTML fragments.
+
+ sub book_form_add : Local Args(0) FormConfig('books/formfu_create.yml') {
+ my ($self, $c) = @_;
+
+ my $form = $c->stash->{form};
+
+ if ($form->submitted_and_valid) {
+ my $book = $c->model('DB::Book')->new_result({});
+ $form->model->update($book);
+ } else {
+ my @author_objs = $c->model("DB::Author")->all();
+ my @authors;
+ foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
+ push(@authors, [$_->id, $_->last_name]);
+ }
+ my $select = $form->get_element({type => 'Select'});
+ $select->options(\@authors);
+ }
+
+ $c->stash(
+ no_wrapper => 1,
+ template => 'books/formfu_create.tt2'
+ );
+ }
+
+ sub book_form_edit : Local Args(1) FormConfig('books/formfu_create.yml') {
+ my ($self, $c, $id) = @_;
+
+ my $form = $c->stash->{form};
+ my $book = $c->model('DB::Book')->find($id);
+
+ if ($form->submitted_and_valid) {
+ $form->model->update($book);
+ } else {
+ my @author_objs = $c->model("DB::Author")->all();
+ my @authors;
+ foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
+ push(@authors, [$_->id, $_->last_name]);
+ }
+ my $select = $form->get_element({type => 'Select'});
+ $select->options(\@authors);
+ $form->model->default_values($book);
+ }
+
+ $c->stash(
+ no_wrapper => 1,
+ template => 'books/formfu_create.tt2'
+ );
+ }
+
+Now try out the Add and Edit buttons, and you should see the new entries or
+your changes in the grid when you click the submit button. Changing the author
+from Edit doesn't work (tutorial bug...)
+
+=head2 Catalyst and AJAX
+
+The Catalyst framework with components such as L<Catalyst::Controller::REST> or
+L<Catalyst::View::JSON> is well suited for making AJAX interfaces, as I hope
+we've demonstrated.
+
+From the Catalyst side, it's just a matter of serving and receiving JSON (or
+XML) data or serving HTML fragments, preferrably from a well designed URI API
+based on REST.
+
+Here we used tools for the jQuery JavaScript framework, but other frameworks
+work just as well, such as ExtJS, Dojo, YUI, Mootools, etc.. You can also use
+the jQuery selector functionality in combination with these other frameworks,
+with C<jQuery.noConflict> mode if they override C<$>.
+
+There are also higher-level tools such as L<Catalyst::Controller::DBIC::API> to
+automate accessing your L<DBIx::Class> schema from JavaScript, and even
+L<Catalyst::Plugin::AutoCRUD> which auto-generates a complete AJAX CRUD
+interface to your L<DBIx::Class> schema using ExtJS.
+
+=head1 AUTHOR
+
+Caelum: Rafael Kitover <rkitover at cpan.org>
+
Deleted: trunk/examples/CatalystAdvent/root/2009/pen/ajax-crud.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2009/pen/ajax-crud.pod 2009-12-21 13:53:54 UTC (rev 12443)
+++ trunk/examples/CatalystAdvent/root/2009/pen/ajax-crud.pod 2009-12-21 14:43:51 UTC (rev 12444)
@@ -1,369 +0,0 @@
-=head1 An AJAX CRUD Interface with Catalyst and jQuery
-
-In this example, we will develop an AJAX CRUD (Create Read Update Delete)
-extension to the Catalyst tutorial.
-
-AJAX stands for Asynchronous JavaScript And XML, although these days most
-people use JSON instead of XML. The idea is to make something that's closer to
-a GUI app rather than a traditional web page.
-
-=head2 Installing flexigrid
-
-First, get the tutorial tarball from
-L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/MyApp_Chapter9_FormFu.tgz>.
-
-We'll use flexigrid for our grid, from L<http://www.flexigrid.info/>.
-
-From the flexigrid zip, put the contents of C<css/flexigrid/images> and
-C<css/images> into C<root/static/images/flexigrid>.
-
-C<css/flexigrid/flexigrid.css> goes into C<root/static/css>. Edit the file and
-do an C<< %s!url(images/!url(../images/flexigrid/! >> substitution.
-
-C<flexigrid.js> goes into C<root/static/js>.
-
-Get jQuery from L<http://jqueryjs.googlecode.com/files/jquery-1.3.2.js> and
-save it to C<root/static/js/jquery.js>.
-
-=head2 Creating a Grid
-
-Let's make a controller for ajax stuff:
-
- package MyApp::Controller::AJAX;
-
- use strict;
- use warnings;
- use parent 'Catalyst::Controller::HTML::FormFu';
-
- sub index : Path Args(0) {
- my ($self, $c) = @_;
-
- $c->stash(
- no_wrapper => 1,
- template => 'ajax.tt'
- );
- }
-
- sub end : ActionClass('RenderView') {}
-
-Edit C<root/src/wrapper.tt2> and put this at the top:
-
- [% IF no_wrapper %]
- [% content %]
- [% ELSE %]
-
-And an
-
- [% END %]
-
-at the bottom.
-
-This allows not using the wrapper for some pages, as we do here.
-
-Now for the HTML:
-
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml"; xml:lang="en" lang="en">
- <head>
- <link rel="stylesheet" href="[% c.uri_for('/static/css/flexigrid.css') %]" />
- <script language="javascript" src="[% c.uri_for('/static/js/jquery.js') %]">
- </script>
- <script language="javascript" src="[% c.uri_for('/static/js/flexigrid.js') %]">
- </script>
- </head>
- <body>
-
- <table id="books_grid" style="display:none"></table>
-
- <style>
- .flexigrid div.fbutton .add
- {
- background: url([% c.uri_for('/static/images/flexigrid/add.png') %]) no-repeat center left;
- }
- .flexigrid div.fbutton .edit
- {
- background: url([% c.uri_for('/static/images/pen-16.gif') %]) no-repeat center left;
- }
- .flexigrid div.fbutton .delete
- {
- background: url([% c.uri_for('/static/images/flexigrid/close.png') %]) no-repeat center left;
- }
- </style>
-
- <script type="text/javascript">
- function add_book(button, grid) {}
- function edit_book(button, grid) {}
- function delete_book(button, grid) {}
-
- $("#books_grid").flexigrid({
- url: '/api/grid',
- dataType: 'json',
- colModel : [
- {display: 'id', name : 'id', width : 0, sortable : false, hide: true},
- {display: 'Title', name : 'title', width : 200, sortable : true, align: 'left'},
- {display: 'Rating', name : 'rating', width : 30, sortable : true, align: 'right'},
- {display: 'Author(s)', name : 'authors', width : 275, sortable : true, align: 'left'},
- ],
- searchitems : [
- {display: 'Title', name: 'title', isdefault: true},
- {display: 'Rating', name: 'rating' },
- ],
- buttons : [
- {name: 'Add', bclass: 'add', onpress : add_book},
- {name: 'Edit', bclass: 'edit', onpress : edit_book},
- {name: 'Delete', bclass: 'delete', onpress : delete_book},
- {separator: true}
- ],
- sortname: "title",
- sortorder: "asc",
- usepager: true,
- title: 'Books',
- useRp: true,
- rp: 10,
- width: 550,
- height: 245
- });
- </script>
- </body>
- </html>
-
-The C<pen-16.gif> icon is from L<http://brainstormsandraves.com/zips/icons/skdesigns-30-creamcoffee-blog-icons-16x16-10x10.zip>.
-
-Now we need to make an API controller that serves JSON for the grid:
-
- package MyApp::Controller::API;
-
- use strict;
- use warnings;
- use parent 'Catalyst::Controller::REST';
-
- __PACKAGE__->config(default => 'application/json');
-
- sub grid : Local ActionClass('REST') {}
-
- sub grid_POST {
- my ($self, $c) = @_;
-
- my ($page, $search_by, $search_text, $rows, $sort_by, $sort_order) =
- @{ $c->req->params }{qw/page qtype query rp sortname sortorder/};
-
- s/\W*(\w+).*/$1/ for $sort_by, $sort_order, $search_by; # sql injections bad
-
- my %data;
-
- my $rs = $c->model('DB::Book')->search({}, {
- page => $page,
- rows => $rows,
- order_by => "$sort_by $sort_order",
- });
-
- $rs = $rs->search_literal("lower($search_by) LIKE ?", lc($search_text))
- if $search_by && $search_text;
-
- $data{total} = $c->model('DB::Book')->count;
- $data{page} = $page;
- $data{rows} = [
- map { +{
- id => $_->id,
- cell => [
- $_->id,
- $_->title,
- $_->rating,
- $_->author_list,
- ]
- } } $rs->all
- ];
-
- $self->status_ok($c, entity => \%data);
- }
-
- 1;
-
-Now start the server and go to L<http://localhost:3000/ajax> and you will see a
-lovely AJAX grid of books.
-
-You can click on the magnifying glass icon at the lower left to search by title
-or rating, search by author is left as an excercise for the reader :)
-
-=head2 Deleting
-
-The Add, Edit and Delete buttons we put at the top of the grid don't work yet.
-
-Let's implement the Delete button.
-
-Expand the C<delete_book> JavaScript function stub to:
-
- function delete_book(button, grid) {
- var total_count = $('.trSelected', grid).length;
- var deleted = 0;
-
- $.each($('.trSelected', grid), function() {
- var id = $('td:nth-child(1) div', this).html();
- $.ajax({
- url: '/api/book/' + id,
- type: 'DELETE',
- data: {},
- dataType: 'json',
- success: function() {
- deleted++;
- if (deleted == total_count) {
- $('#books_grid').flexReload();
- }
- }
- });
- });
- }
-
-Add to the API controller to support the Delete action:
-
- sub book : Local ActionClass('REST') {
- my ($self, $c, $id) = @_;
-
- $c->stash(book => $c->model('DB::Book')->find($id));
- }
-
- sub book_DELETE {
- my ($self, $c, $id) = @_;
-
- $c->stash->{book}->delete;
-
- $self->status_ok($c, entity => { message => 'success' });
- }
-
-Now restart the server, go back to the grid in your browser and try selecting
-some rows and pressing the Delete button, voila!
-
-=head2 Adding and Editing
-
-For this part we'll need the jQuery Form plugin, which you can get from
-L<http://jquery.malsup.com/form/jquery.form.js?2.36>. Save it as
-C<root/static/js/jquery.form.js>.
-
-Add a C<< <script> >> link at the top of C<ajax.tt> for it:
-
- <script language="javascript" src="[% c.uri_for('/static/js/jquery.form.js') %]">
- </script>
-
-And above the C<< <table id="books_grid" ... >> tag we'll put a div to hold the
-popup form.
-
- <div id="book_form" style="display:none"></div>
-
-A jQuery UI plugin for a popup would probably be nicer for this purpose.
-
-The JavaScript for the Add and Edit buttons is fairly simple:
-
- function add_book(button, grid) {
- var form_div = $('#book_form');
-
- form_div.load('/ajax/book_form_add', null, function() {
- $('#book_form form').ajaxForm({
- url: '/ajax/book_form_add',
- success: function() {
- form_div.hide();
- $('#books_grid').flexReload();
- }
- });
- form_div.show();
- });
- }
-
- function edit_book(button, grid) {
- var id = $('.trSelected td:nth-child(1) div', grid).html();
- var form_div = $('#book_form');
- var url = '/ajax/book_form_edit/' + id;
-
- form_div.load(url, null, function() {
- $('#book_form form').ajaxForm({
- url: url,
- success: function() {
- form_div.hide();
- $('#books_grid').flexReload();
- }
- });
- form_div.show();
- });
- }
-
-Now we need the AJAX Controller actions, in which we'll use the FormFu stuff
-that's already a part of the tutorial, but without the wrapper since we're
-using C<.load> to load the forms as HTML fragments.
-
- sub book_form_add : Local Args(0) FormConfig('books/formfu_create.yml') {
- my ($self, $c) = @_;
-
- my $form = $c->stash->{form};
-
- if ($form->submitted_and_valid) {
- my $book = $c->model('DB::Book')->new_result({});
- $form->model->update($book);
- } else {
- my @author_objs = $c->model("DB::Author")->all();
- my @authors;
- foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
- push(@authors, [$_->id, $_->last_name]);
- }
- my $select = $form->get_element({type => 'Select'});
- $select->options(\@authors);
- }
-
- $c->stash(
- no_wrapper => 1,
- template => 'books/formfu_create.tt2'
- );
- }
-
- sub book_form_edit : Local Args(1) FormConfig('books/formfu_create.yml') {
- my ($self, $c, $id) = @_;
-
- my $form = $c->stash->{form};
- my $book = $c->model('DB::Book')->find($id);
-
- if ($form->submitted_and_valid) {
- $form->model->update($book);
- } else {
- my @author_objs = $c->model("DB::Author")->all();
- my @authors;
- foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
- push(@authors, [$_->id, $_->last_name]);
- }
- my $select = $form->get_element({type => 'Select'});
- $select->options(\@authors);
- $form->model->default_values($book);
- }
-
- $c->stash(
- no_wrapper => 1,
- template => 'books/formfu_create.tt2'
- );
- }
-
-Now try out the Add and Edit buttons, and you should see the new entries or
-your changes in the grid when you click the submit button. Changing the author
-from Edit doesn't work (tutorial bug...)
-
-=head2 Catalyst and AJAX
-
-The Catalyst framework with components such as L<Catalyst::Controller::REST> or
-L<Catalyst::View::JSON> is well suited for making AJAX interfaces, as I hope
-we've demonstrated.
-
-From the Catalyst side, it's just a matter of serving and receiving JSON (or
-XML) data or serving HTML fragments, preferrably from a well designed URI API
-based on REST.
-
-Here we used tools for the jQuery JavaScript framework, but other frameworks
-work just as well, such as ExtJS, Dojo, YUI, Mootools, etc.. You can also use
-the jQuery selector functionality in combination with these other frameworks,
-with C<jQuery.noConflict> mode if they override C<$>.
-
-There are also higher-level tools such as L<Catalyst::Controller::DBIC::API> to
-automate accessing your L<DBIx::Class> schema from JavaScript, and even
-L<Catalyst::Plugin::AutoCRUD> which auto-generates a complete AJAX CRUD
-interface to your L<DBIx::Class> schema using ExtJS.
-
-=head1 AUTHOR
-
-Caelum: Rafael Kitover <rkitover at cpan.org>
-
More information about the Catalyst-commits
mailing list