[Catalyst-commits] r7189 - / trunk/examples/CatalystAdvent/root/2007
trunk/examples/CatalystAdvent/root/2007/pen
zarquon at dev.catalyst.perl.org
zarquon at dev.catalyst.perl.org
Sat Dec 1 21:33:28 GMT 2007
Author: zarquon
Date: 2007-12-01 21:33:28 +0000 (Sat, 01 Dec 2007)
New Revision: 7189
Added:
trunk/examples/CatalystAdvent/root/2007/pen/
trunk/examples/CatalystAdvent/root/2007/pen/1.pod
trunk/examples/CatalystAdvent/root/2007/pen/10.pod
trunk/examples/CatalystAdvent/root/2007/pen/11.pod
trunk/examples/CatalystAdvent/root/2007/pen/12.pod
trunk/examples/CatalystAdvent/root/2007/pen/15.pod
trunk/examples/CatalystAdvent/root/2007/pen/17.pod
trunk/examples/CatalystAdvent/root/2007/pen/2.pod
trunk/examples/CatalystAdvent/root/2007/pen/24.pod
trunk/examples/CatalystAdvent/root/2007/pen/3.pod
trunk/examples/CatalystAdvent/root/2007/pen/4.pod
trunk/examples/CatalystAdvent/root/2007/pen/5.pod
trunk/examples/CatalystAdvent/root/2007/pen/6.pod
trunk/examples/CatalystAdvent/root/2007/pen/7.pod
trunk/examples/CatalystAdvent/root/2007/pen/8.pod
Removed:
trunk/examples/CatalystAdvent/root/2007/1.pod
trunk/examples/CatalystAdvent/root/2007/10.pod
trunk/examples/CatalystAdvent/root/2007/11.pod
trunk/examples/CatalystAdvent/root/2007/12.pod
trunk/examples/CatalystAdvent/root/2007/15.pod
trunk/examples/CatalystAdvent/root/2007/17.pod
trunk/examples/CatalystAdvent/root/2007/2.pod
trunk/examples/CatalystAdvent/root/2007/24.pod
trunk/examples/CatalystAdvent/root/2007/3.pod
trunk/examples/CatalystAdvent/root/2007/4.pod
trunk/examples/CatalystAdvent/root/2007/5.pod
trunk/examples/CatalystAdvent/root/2007/6.pod
trunk/examples/CatalystAdvent/root/2007/7.pod
trunk/examples/CatalystAdvent/root/2007/8.pod
Modified:
/
Log:
r12064 at zaphod: kd | 2007-12-02 08:24:12 +1100
staging area for advent entries in 2007/pen
Property changes on:
___________________________________________________________________
Name: svk:merge
- 1b129c88-ebf4-0310-add9-f09427935aba:/local/catalyst:4278
1c72fc7c-9ce4-42af-bf25-3bfe470ff1e8:/local/Catalyst:11938
3b9770f9-e80c-0410-a7de-cd203d167417:/local/catalyst:3514
dd8ad9ea-0304-0410-a433-df5f223e7bc0:/local/Catalyst:6909
+ 1b129c88-ebf4-0310-add9-f09427935aba:/local/catalyst:4278
1c72fc7c-9ce4-42af-bf25-3bfe470ff1e8:/local/Catalyst:12064
3b9770f9-e80c-0410-a7de-cd203d167417:/local/catalyst:3514
dd8ad9ea-0304-0410-a433-df5f223e7bc0:/local/Catalyst:6909
Deleted: trunk/examples/CatalystAdvent/root/2007/1.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/1.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/1.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,5 +0,0 @@
-=head1 Day 1 - Catalyst Application Design
-
-=head1 AUTHOR
-
-Jon Rockway
Deleted: trunk/examples/CatalystAdvent/root/2007/10.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/10.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/10.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,5 +0,0 @@
-=head1 Day 10 - Rapid CRUD with Catalyst
-
-=head1 AUTHOR
-
-Peter Karman
Deleted: trunk/examples/CatalystAdvent/root/2007/11.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/11.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/11.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,5 +0,0 @@
-=head1 Catalyst Base Classes
-
-=head1 AUTHOR
-
-claco
Deleted: trunk/examples/CatalystAdvent/root/2007/12.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/12.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/12.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,5 +0,0 @@
-=head1 local::lib and Catalyst
-
-=head1 AUTHOR
-
-John Goulah
Deleted: trunk/examples/CatalystAdvent/root/2007/15.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/15.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/15.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,252 +0,0 @@
-=head1 Advanced Search in web DBIx::Class based applications (with tags, full text search and searching by location)
-
-There is a bit of irony that I write that article, for people to learn from it,
-while in fact it is my failing to properly wrap my head around the problem and encapsulate
-my solution into a CPAN library that forces me to write an article in the first
-place. But maybe someone smarter then me will read it and write that CPAN
-module?
-
-=head2 The Problem
-
-It is a common case that on a web site you need an 'advanced search' feature
-that let's the user combine simple predicates into more elaborated queries.
-Usually all the predicates are joined with an 'AND' - and the technique I
-describe here is based on this assumption. At first this task looks pretty
-simple. You have a list of parameters from the web form, corresponding the the
-columns of some database table, you have the values of those parameters and you
-need to find all the records in that table that have those values in those
-columns. You just do:
-
- my @records = $schema->ResultSet( 'MyTable' )->search(
- $reqest->params,
- { page => 1, rows => 5 }
- );
-
-Simple.
-
-Then of course you add parameter validation and filtering - but this is outside
-of the scope of this article.
-
-Then you need to add checks on columns not only in the searched table, but also
-on columns from related records and things become more complicated. What I
-propose here is a solution that works for the simple case, solves the related
-tables case, and also is easily extendable to cover more complicated predicates
-like searching by a conjunction of tags, full text searches or searches by
-location. I also add implementation of those 'advanced' predicates (using the
-PostgreSQL extensions for full text search and location based search).
-
-=head2 The Solution
-
-The solution I propose is this simple module:
-
- package Ymogen2::DB::RSSearchBase;
-
- use strict;
- use warnings;
-
- use base qw( DBIx::Class::ResultSet );
-
- sub advanced_search {
- my ( $self, $params, $attrs ) = @_;
- for my $column ( keys %$params ){
- if( my $search = $self->can( 'search_for_' . $column ) ){
- $self = $self->$search( $params );
- next;
- }
- my ( $full_name, $relation ) = simple_predicate( $column );
- my $join;
- $join = { join => [ $relation ] } if $relation;
- $self = $self->search(
- { $full_name => $params->{$column} },
- $join,
- );
- }
- $self = $self->search( {}, $attrs );
- return (wantarray ? $self->all : $self)
- }
-
-You use it like that:
-
- my @records = $schema->ResultSet( 'MyTable' )->advanced_search(
- \%search_params,
- { page => 1, rows => 5 }
- );
-
-But first you need to make your ResultSet class inherit from it. This can be
-done in several ways, what we do is adding:
-
- __PACKAGE__->resultset_class(__PACKAGE__ . '::ResultSet');
-
- package Ymogen2::DB::Schema::Users::ResultSet;
-
- use base qw( Ymogen2::DB::RSSearchBase );
-
-
-
-to MyTable.pm.
-
-For the simple case it works just like the familiar 'search' method of the
-L<DBIx::Class::ResultSet class>. But it also works for searching in related
-records. For that we have the simple_predicate function. It looks like that:
-
- sub simple_predicate {
- my $field = shift;
- if( $field =~ /(.*?)\.(.*)/ ){
- my $first = $1;
- my $rest = $2;
- my( $column, $join ) = simple_predicate( $rest );
- if ( $join ) {
- return $column, { $first => $join };
- }else{
- return $first . '.' . $column, $first;
- }
- }elsif( $field ){
- return $field;
- }else{
- return;
- }
-
-What it does is parsing column names of the format:
-'relationship1.relationship2.relationship3.column' into 'relationship3.column'
-- the fully qualified column name and a
-'{ relationship1 => { relationship2 => relationship3 } }' hash used for joining
-the appriopriate tables.
-
-(I had also a non-recursive version - but it was not simpler)
-
-So now you can do this:
-
- my @records = $schema->ResultSet( 'MyTable' )->advanced_search(
- {
- column1 => 'value1',
- column2 => 'value2',
- some_relation.column => 'value3',
- some_other_relation.some_third_relation.column => 'value4',
- },
- { page => 1, rows => 5 }
- );
-
-Useful?
-We use it.
-
-=head2 The Extensions
-
-But the real advantage of this approach is how easily it can be extended.
-
-=head3 Tags
-
-For example let say we need to search by conjunction of tags like that:
-
- my @records = $schema->ResultSet( 'MyTable' )->advanced_search( {
- column1 => 'value1',
- some_other_relation.some_third_relation.column => 'value4',
- tags => [ qw/ tag1 tag2 tag3/ ],
- });
-
-What we need is a method called 'search_for_tags' that will do the search. The
-nice thing is that we don't need to warry how this will be combined with the
-rest of the predicates - DBIC will do the right thing (for and 'AND' relation).
-
-Here is the method:
-
- sub search_for_tags {
- my ( $self, $params ) = @_;
- my @tags = @{$params->{tags}};
- my %search_params;
- my $suffix = '';
- my $i = 1;
- for my $tag ( @tags ){
- $search_params{'tags' . $suffix . '.name'} = $tag;
- $suffix = '_' . ++$i;
- }
- my @joins = ( 'tags' ) x scalar( @tags );
- $self = $self->search( \%search_params, {
- join => \@joins,
- }
- );
- return $self;
- }
-
-It builds a query like that:
-
- SELECT * FROM MyTable me, Tags tags, Tags tags_2, Tags tags_3
- WHERE tags.mytable_id = me.id AND tags.tag = 'tag1' AND
- tags_2.mytable_id = me.id AND tags_2.tag = 'tag2' AND
- tags_3.mytable_id = me.id AND tags_3.tag = 'tag3'
-
-This query will use indices and should be fast (a more detailed cover of this
-technique you can find at my blog at:
-http://perlalchemy.blogspot.com/2006/10/tags-and-search-and-dbixclass.html).
-
-*Attention:* You need the 0.08008 version of DBIx::Class for this to work properly.
-
-=head3 Full Text Search
-
-For full text search I use the PostgreSQL tsearch2 engine here.
-
-=head3 Search by Proximity
-
-For searching by proximity I use the PostgreSQL geometric functions
-(http://www.postgresql.org/docs/8.2/interactive/functions-geometry.html).
-There is
-one problem with it - the distance operator assumes planar coordinates,
-while for the interesting thing is to search geografic data with the standard
-latitude/longitude coordinates. In our solution we just don't care about
-being exact and just multiply the 'distance' in degrees by 50 to get approximate
-distance in miles. The actual proportion is about 43 for latitude and 69 for
-longitude at about the London's longitude, it would be possible to get quite
-good results by dividing the latitude and longitude by those numbers in the
-database - but I would rather have good data in the database then more exact
-results. Maybe at some point we shell switch to use some real geografic
-distance functions (I've seen a PosgreSQL extension to do that - but I was
-scared a bit by it's experimental status).
-
-So here is the function used to filter the results by proximity to a place:
-
-sub search_for_distance {
- my ( $self, $rs, $params ) = @_;
- my $lat_long = $params->{lat_long};
- my $distance = $params->{distance} / 50;
- # around London the actual proportions are around 43 for latitude
- # and 69 for longitude
- return $rs->search(
- { "(lat_long <-> '$lat_long'::POINT) < " => \$distance },
- { join => 'location' }
- );
-}
-
-This function assumes there are two parameters on the $params hash: distance
-and lat_long (lattitude/logintude coordinates). The location data in our
-database are in a separate table called 'location'.
-
-We also use another search extension:
-
-sub search_for_lat_long {
- my ( $self, $rs, $params ) = @_;
- my $lat_long = $params->{lat_long};
- $rs = $rs->search( undef,
- {
- join => 'location',
- '+select' => [ \"(lat_long <-> '$lat_long'::POINT) AS distance" ],
- '+as' => 'distance',
- order_by => 'distance ASC',
- }
- );
- return $rs;
-}
-
-This function sorts the results by proximity to the point determined by the
-lat_long coordinates. This way the user does not need to specify the
-maximum distance - the closest results are displayed on the first pages
-anyway - and that is enough for most of the searches.
-
-=head2 And Beyond
-
-In the search by proximity extension I've used ordering of the results. There
-is one problem with this. We use many 'search' calls on the resultset
-to cumulate the predicates - but we cannot do this with the order. Only the
-last 'order_by' parameter used in the 'search' calls is effective. I believe
-it would be useful to have a similar 'cumulative' behaviour for 'order_by'
-and we can add this to 'advanced_search' (or perhaps it can be added to
-the core DBIC search method).
-
Deleted: trunk/examples/CatalystAdvent/root/2007/17.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/17.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/17.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,5 +0,0 @@
-=head1 Catalyst with Ext+Ajax: Editable Data Grids
-
-=head1 AUTHOR
-
-jasonk
Deleted: trunk/examples/CatalystAdvent/root/2007/2.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/2.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/2.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,244 +0,0 @@
-=head1 Day 2 - Getting started with ExtJS screen library
-
-Today we take a look at the ExtJS screen library and how to get started
-using it within a Catalyst web application.
-
-Don't forget to come back on Day 17 for a more advanced example by jasonk in
-"Catalyst with Ext+Ajax: Editable Data Grids"
-L<http://catalyst.perl.org/calendar/2007/17>
-
-=head2 What is ExtJS?
-
-ExtJS L<http://www.extjs.com/> is a cross-browser Javascript library for web
-pages. You can use it to achieve Web 2.0 effects without writing too much
-Javascript code (always a good idea!). It offers abstracted handling for HTML
-elements, Document Object Model (DOM), event handling and AJAX (client-server)
-calls. ExtJS also provides styling (blue, aero and Vista, you can write more)
-and a good selection of widgets including:
-
-=over 2
-
-=item * window
-
-=item * layout
-
-=item * tabs
-
-=item * form
-
-=item * toolbar
-
-=item * menu
-
-=item * tree
-
-=item * combobox
-
-=item * grid
-
-=back
-
-The full range is listed here L<http://extjs.com/learn/Ext_Extensions>
-
-
-The easiest way to see what is possible is to watch it in action:
-
-=over 2
-
-=item * desktop L<http://extjs.com/deploy/dev/examples/desktop/desktop.html>
-
-=item * feed viewer L<http://extjs.com/deploy/dev/examples/feed-viewer/view.html>
-
-=item * photo organiser L<http://extjs.com/deploy/dev/examples/organizer/organizer.html>
-
-=back
-
-For more examples see L<http://extjs.com/deploy/dev/examples/>
-
-
-
-=head2 What web browsers does it work on?
-
-=over 2
-
-=item * Internet Explorer 6+
-
-=item * Firefox 1.5+ (PC, Mac)
-
-=item * Safari 2+
-
-=item * Opera 9+ (PC, Mac)
-
-=back
-
-=head2 What about other Javascript libraries - I've got legacy code
-
-Because it grew out of Yahoo's YUI library and its developers
-wanted to support legacy code, ExtJS has a tiered design that
-allows you to choose the base Javascript adapter library
-
-=over 2
-
-=item * native Ext
-
-=item * YUI
-
-=item * jQuery
-
-=item * Prototype/Script.aculo.us
-
-=back
-
-For new code, I'd recommend native Ext as it is faster to load.
-
-There are more details and a pretty picture of the design at
-L<http://extjs.com/learn/Ext_FAQ#What_other_libraries_are_required_to_run_Ext.3F>
-
-
-=head2 Downloading and installing the ExtJS library
-
-Download ExtJS 1.1.1 from L<http://extjs.com/download>
-
-The stable release, used in this article, is 1.1.1 and that's the one you need.
-The latest development release is Ext 2.0 but be aware that it has a different
-object model to Ext 1.1 and many of the tutorials, docs and code on the site
-still relate to 1.1.
-Once the widgets and documentation have been done for 2.0 I expect there will
-be a rapid shift over in the user community.
-More details at L<http://extjs.com/learn/Ext_1_to_2_Migration_Guide>
-
-=head3 Installation
-
-If you're on Linux, install ExtJS to your web server document root, e.g.
-/var/www/html/ext-1.1. When you want to use it in a Catalyst project create a
-symbolic link from your root/static directory
-
- $ ln -s /var/www/html/ext-1.1.1 root/static/
-
-Otherwise, you can simply unzip the whole lot below root/static.
-
-When running the Catalyst test server, it will expect to find the files there.
-
-For production use, use absolute URLs to the ExtJS javascript files from your
-templates, e.g. http://myserver/ext-1.1.1/ext-core.js, and allow your web server
-to serve them rather than Catalyst. It's much faster.
-
-=head2 Manuals and learning materials
-
-Visit L<http://extjs.com/learn/>. You will find tutorials at
-L<http://extjs.com/learn/Tutorials>.
-
-Bookmark and early on read through the community manual
-L<http://extjs.com/learn/Ext_Manual>.
-
-The archive comes with an ExtJS API reference manual. You can open ext-
-1.1.1/docs/index.html in a browser or if you installed it under your Linux web
-server root it should be accessible at L<http://myserver/ext-1.1.1/docs/>. It's
-also online at L<http://extjs.com/deploy/ext/docs/index.html>. Use this to look
-up methods and attributes for ExtJS objects.
-
-
-=head2 Adding ExtJS to a web page
-
-Firstly you need to include the ExtJS Javascript libraries and stylesheets in
-the <head> section of your HTML page
-
- <link rel="stylesheet" type="text/css" href="/ext-1.1/resources/css/ext-all.css" />
- <script type="text/javascript" src="/ext-1.1/adapter/ext/ext-base.js"></script>
- <script type="text/javascript" src="/ext-1.1/ext-all.js"></script>
-
-In the body section use classes for styling
-
- <body class="xtheme-gray" >
-
-Use named <div> tags to identify content that ExtJS will enhance
-
- <div id="container"><div id="content" class="welcome">
- ...
- </div></div>
-
-Then supply Javascript to tell ExtJS what to do. The following creates a layout
-with one panel called 'content' after the HTML page has finished loading
-
- <script type="text/javascript">
- Thescreen = function(){
- return {
- init: function(){
- var layout = new Ext.BorderLayout(document.body, {
- center: {
- autoScroll: true,
- minTabWidth: 50,
- preferredTabWidth: 150,
- titlebar: true
- }
- });
-
- layout.beginUpdate();
- layout.add('center', new Ext.ContentPanel('content', {title:'ExtJS demo app'}));
- layout.endUpdate();
- }
- }
- }();
- Ext.EventManager.onDocumentReady(Thescreen.init, Thescreen, true);
- </script>
-
-Note the prototype object-based approach used to create the 'Thescreen' object.
-This helps standardise objects and avoid memory leaks.
-See L<http://extjs.com/learn/Manual:Intro:Class_Design> for further explanation.
-
-
-=head2 Simple Example
-
-I've provided a simple working example you can use as a starting point for
-writing ExtJS Catalyst applications. It provides code, a menu, a couple of pages
-and a set of templates initially generated using the Catalyst helpers to give
-a portal page.
-
-
-=head2 Example application code
-
-You can check out the code from the Catalyst repository with
-
- svn co http://dev.catalystframework.org/repos/Catalyst/trunk/examples/ExtJS
-
-
-=head2 Form Architecture Considerations
-
-You have a choice between implementing traditional "round trip" web pages
-and client-server AJAX dynamic web pages seen on Web 2.0 sites.
-
-In the "round trip" case, the user browses to a page, clicks a submit button to
-post data to a server, HTML is sent back then the new page displays. You can
-continue to do this with templates and use ExtJS to enhance the appearance and
-add auto-completers to input fields.
-
-In the second case, you send HTML back once for the first page and then use
-ExtJS to respond to events like button clicks to trigger display changes and
-send/retrieve data to the server via asynchronous data transfers. The ExtJS Form
-widget L<http://extjs.com/learn/Ext_Manual#Forms> handles this and can
-automatically perform front-end data validation and display input warnings from
-the backend. See L<http://extjs.com/deploy/dev/examples/#sample-7> and look at
-the .js files. It's also possible to generate a form dynamically from an XML or
-JSON definition in a data source, so you could hold your form definitions in a
-database and serve them up from a Catalyst data handler.
-
-The choice will depend on how slick a user interface you want and your available
-time, as writing Javascript can be time-consuming. AJAX screens often look better
-but are less accessible for blind visitors and can be harder to debug. For
-testing you would need to consider using a tool like Selenium. Check out
-L<http://www.infoq.com/articles/testing-ajax-selenium>
-L<http://www.infoq.com/news/2007/09/selenium-grid-parallel-testing>
-L<http://search.cpan.org/~lukec/Test-WWW-Selenium-1.13/lib/WWW/Selenium.pm>
-
-
-=head2 Comma Gotcha
-
-If you leave a trailing comma in a Javascript data structure, which is very easy
-to do if you're used to programming Perl, it stops Internet Explorer's parser.
-You'll get a blank page! It's easily spotted by running your output HTML code
-through HTML Tidy.
-
-
-=head1 AUTHOR
-
-peterdragon - Peter Edwards <peter at dragonstaff.co.uk>
Deleted: trunk/examples/CatalystAdvent/root/2007/24.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/24.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/24.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,5 +0,0 @@
-=head1 This Year in Catalyst
-
-=head1 AUTHOR
-
-kd
Deleted: trunk/examples/CatalystAdvent/root/2007/3.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/3.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/3.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,184 +0,0 @@
-=head1 Get more REST - Using YUI and JavaScript for REST
-
-Today, we'll look into building a fully capable REST client in YUI that allows
-record creation, retrieving, updating and deletion (CRUD) as well as searching.
-
-=head2 Start Here First
-
-To make the most of this article, it is important to not only understand the
-basic idea of REST but to also see how it works within Catalyst.
-
-To start out understanding REST, read the excellent article by Ryan Tomayko
-L<http://tomayko.com/articles/2004/12/12/rest-to-my-wife>, about explaining
-REST in simple terms that a non technical person is able to understand. It will
-help a technical person understand it even more.
-
-Up next is to get a good understanding for how REST works inside of Catalyst,
-using Adam Jacob's excellent L<Catalyst::Action::REST> package. The best way
-to get up to speed is to review Day 9 from the 2006 Advent Calendar, at
-L<http://www.catalystframework.org/calendar/2006/9.pod>.
-
-=head2 Following Along
-
-The examples that are listed here are available from the Catalyst subversion
-repository, available via:
-
- svn co http://dev.catalystframework.org/repos/Catalyst/trunk/examples/RestYUI
-
-It is a good idea to check out the sources of the sample application to better
-follow along with the examples. Included is the necessary YUI javascript files
-to enhance this application as you go.
-
-=head2 Why use JavaScript?
-
-The reason why we're required to use JavaScript for a full REST service is
-because of the limited vocabulary that a browser coupled with plain HTML is
-allowed to speak. The browser will only ever send GET and POST requests in
-response to user interaction with an HTML document. However, when using the
-C<XmlHttpRequest> method that is a standard in all Grade A browsers the
-vocabulary is extended to support PUT, DELETE and HEAD.
-
-=head2 The Connection to REST
-
-REST is based off of getting what you ask for. In the simplest form, an
-C<XmlHttpRequest> isn't enough to talk to a REST webservice. There are a few
-major components that fit together to make everything work:
-
-=over
-
-=item Content Type
-
-=item Request Method
-
-=item
-
-=back
-
-=head2 Starting the Catalyst Application
-
-We're taking last year's AdventREST example app and slightly modifying it to
-add support for YUI.
-
-So, to get started you can check out that application from subversion by doing:
-
- svn co http://dev.catalystframework.org/repos/Catalyst/trunk/examples/AdventREST
-
-Or check out the completed RestYUI application.
-
-The only fundamental change to the application is adding the Template Toolkit
-view:
-
- script/adventrest_create.pl view TT TT
-
-Also, we have to grab the static files for YUI itself. As of writing this,
-version 2.3.1 is available from L<http://sourceforge.net/project/downloading.php?group_id=165715&filename=yui_2.3.1.zip>. Check the latest version at
-L<http://developer.yahoo.com/yui/>
-
-After extracting the files from the zip archive, just copy over the .js files to
-the root/static directory.
-
-=head2 Preparing the REST WebService
-
-To gain access to the REST services, we'll be accessing both the C<list> and the
-C<item> actions. The list is going to be pulled by using a customized YUI
-DataSource object.
-
-This will ask the REST service for a list of our people objects, and in a simple
-form is nothing more than a L<DBIx::Class> search:
-
- sub user_list_GET {
- my ( $self, $c ) = @_;
-
- my %user_list;
- my $user_rs = $c->model('DB::User')->search;
- while ( my $user_row = $user_rs->next ) {
- $user_list{ $user_row->user_id } =
- $c->uri_for( '/user/' . $user_row->user_id )->as_string;
- }
- $self->status_ok( $c, entity => \%user_list );
- }
-
-That is from the original REST article, and to make the most of the list we're
-going to enhance the listing to provide some additional meta information that
-enhances the webservice with features such as pagination and other contextual
-information. We'll add in pagination with a CGI parameter "page" and a param
-for the number of items per page called "per_page"
-
- sub user_list_GET {
- my ( $self, $c ) = @_;
- my $page = $c->req->params->{page} || 1;
- my $per_page = $c->req->params->{per_page} || 10;
-
- # We'll use an array now:
- my @user_list;
- my $rs = $c->model('DB::User')
- ->search(undef, { rows => $per_page })->page( $page );
- while ( my $user_row = $rs->next ) {
- push @user_list, {
- $user_row->get_columns,
- uri => $c->uri_for( '/user/' . $user_row->user_id )->as_string
- };
- }
-
- $self->status_ok( $c, entity => {
- result_set => {
- totalResultsAvailable => $rs->pager->total_entries,
- totalResultsReturned => $rs->pager->entries_on_this_page,
- firstResultPosition => $rs->pager->current_page,
- result => [ @user_list ]
- }
- });
- };
-
-So now we have a serialized structure that looks like this in JSON:
-
-=head2 Connecting with YUI
-
-After the webservice is up, it is time to setup the Yahoo DataSource object.
-
-We'll create a simple template off of the index action in C<Root.pm>, so
-create this action in Root.pm:
-
- sub index : Private {
- my ( $self, $c ) = @_;
- $c->forward( $c->view('TT') );
- }
-
-That will just direct the action for "/" to go to TT, and render an "index.tt"
-template.
-
-The C<index.tt> file is pretty basic, and after the HTML tags this is the crux
-of what gets the job done:
-
- /* Create the YAHOO.util.DataSource object, the parameter is the
- URI to your REST service
- */
- this.myDataSource = new YAHOO.util.DataSource("[%
- c.uri_for( c.controller('User').action_for('user_list') ) %]");
- this.myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
- this.myDataSource.connXhrMode = "queueRequests";
- this.myDataSource.responseSchema = {
- resultsList: "result_set.result",
- /* We have to define the fields for usage elsewhere */
- fields: [
- "pk1", "token", "default_lang", "languages",
- "url", "t_created", "t_updated", "actions"
- ]
- };
-
-After this, we have a functional DataSource object that can be tied into a
-DataTable:
-
- myDataTable = new YAHOO.widget.DataTable(
- "kb_list", myColumnDefs,
- this.myDataSource, {
- /* The initialRequest is appended to the URI to set params */
- initialRequest: "page=1&content-type=text/x-json"
- }
- );
-
-=cut
-
-=head1 AUTHOR
-
-J. Shirley
Deleted: trunk/examples/CatalystAdvent/root/2007/4.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/4.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/4.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,7 +0,0 @@
-=head1 Catalyst Configuration
-
-=head1 AUTHOR
-
-???
-
-jayk?
Deleted: trunk/examples/CatalystAdvent/root/2007/5.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/5.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/5.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,5 +0,0 @@
-=head1 $c->uri_for fun
-
-=head1 AUTHOR
-
-purge
Deleted: trunk/examples/CatalystAdvent/root/2007/6.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/6.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/6.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,5 +0,0 @@
-=head1 Mango Overview
-
-=head1 AUTHOR
-
-claco
Deleted: trunk/examples/CatalystAdvent/root/2007/7.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/7.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/7.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,5 +0,0 @@
-=head1 Catalyst Authentication
-
-=head1 AUTHOR
-
-jayk
Deleted: trunk/examples/CatalystAdvent/root/2007/8.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/8.pod 2007-12-01 20:14:18 UTC (rev 7188)
+++ trunk/examples/CatalystAdvent/root/2007/8.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -1,235 +0,0 @@
-
-=head1 Catalyst + Open Flash Chart: Fancy graphs with minimal fuss
-
-=head2 About Open Flash Chart
-
-Open Flash Chart is a flash application that prudoces some very nice-looking,
-interactive (and in some cases animated) charts and graphs for your web
-application. It's also completely free and open source, released under the
-GNU General Public License.
-
-For examples of the kinds of graphs you can produce, visit the home page
-at L<http://teethgrinder.co.uk/open-flash-chart/>.
-
-=head2 Getting Started
-
-This tutorial assumes you already have some Catalyst experience, so we won't
-go into too much detail with the basics of creating an application...
-
- % catalyst.pl AdventOFC
- ...
- % cd AdventOFC
- ...
- % script/adventofc_create.pl view TT TTSite
- ...
-
-=head2 Installing Chart::OFC
-
-The Open Flash Chart application uses a difficult to work with format for it's
-data structure. Fortunately the perl community has guys like Dave Rolsky, who
-recently made a nice perl library called L<Chart::OFC> that makes it easier to
-work with. We're going to install L<Chart::OFC> first, because it includes the
-pieces you need from Open Flash Chart, so you only need to download once...
-
-Assuming you are using L<CPANPLUS>, you can install it like this...
-
- % cpanp
- ...
- CPAN Terminal> i Chart::OFC
-
- Installing Chart::OFC (0.02)
- ...
- Module 'Chart::OFC' installed successfully
- No errors installing all modules
-
- CPAN Terminal> q
-
-Once you have it installed, you need to get the .swf file and copy it into
-your application root directory. If you installed from L<CPANPLUS>, something
-like the following will work.
-
- % cd root/static
- % unzip ~/.cpanplus/*/build/Chart-OFC-*/ofc/*.zip open-flash-chart.swf
-
-If you don't have the L<Chart::OFC> source directory any more, you can
-download the .swf from the Open Flash Chart web site at
-L<http://teethgrinder.co.uk/open-flash-chart/download.php>.
-
-Whether you use the copy from the L<Chart::OFC> source directory, or download
-it from the web site, you will get a .zip file that includes the source as
-well as adapter libraries for various langages. You only need the .swf file
-from the archive though, you won't need any of the other files.
-
-=head2 Adding Open Flash Chart support to your application
-
-Now that you have a basic application, we'll add some charting capabilities
-with OpenFlashChart. The HTML code required to embed a chart is long and
-repetitive, so I like to make a component template to do the boring work
-for me. Create a file in root/lib called 'ofc_swf_object' with the
-following contents:
-
- [%
- DEFAULT
- width = '500'
- height = '300'
- id = 'ofc_chart'
- bgcolor = '#FFFFFF'
- name = 'ofc_chart';
-
- SET swf_url = Catalyst.uri_for(
- '/static/open-flash-chart.swf',
- {
- width = width
- height = height
- data = data
- }
- );
-
- # This is just to keep the html below from being
- # too wide for the sake of the demo, you could always
- # just put this inline if you wanted
- SET cab_download = [
- 'http://fpdownload.macromedia.com'
- '/pub/shockwave/cabs/flash/swflash.cab'
- '#version=8,0,0,0'
- ];
- %]
- [% FILTER collapse %]
- <object
- classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
- codebase="[% cab_download.join( '' ) %]"
- width="[% width %]" height="[% height %]"
- id="[% id %]" align="middle">
- <param name="allowScriptAccess" value="sameDomain" />
- <param name="movie" value="[% swf_url %]" />
- <param name="quality" value="high" />
- <param name="bgcolor" value="[% bgcolor %]" />
- <embed
- src="[% swf_url %]" quality="high"
- bgcolor="[% bgcolor %]" width="[% width %]"
- height="[% height %]" name="[% name %]"
- align="middle" allowScriptAccess="sameDomain"
- type="application/x-shockwave-flash"
- pluginspage="http://www.macromedia.com/go/getflashplayer"
- />
- </object>
- [% END %]
-
-You might notice that there are two copies of basically every configuration
-parameter in this block of code. Like many other things on the web, this is
-because Internet Explorer does it one way (the object part) and everything
-else does it differently (with embed.)
-
-=head2 Using your new Open Flash Chart tools
-
-Now that you have the support pieces you need, it's time to put them to use.
-For the purposes of the demo we're just going to add charts to the home page
-of the application, so we'll be editing the Root controller. To get started,
-add a method to the root controller for the index page...
-
- =head2 index
-
- The index page for our charts.
-
- =cut
-
- sub index : Private {
- my ( $self, $c ) = @_;
-
- $c->stash->{ 'template' } = 'index.tt2';
- }
-
-Next we need to create a template to go along with the index. Since we took
-the time to setup a helper template, this one will be rather small. Put this
-code into your root/src/index.tt2.
-
- [% INCLUDE ofc_swf_object data = Catalyst.uri_for( '/graph' ) %]
-
-If you run script/adventofc_server.pl at this point, you should get a nice
-graph display, although it won't contain any data. In order to get nice
-charts, we need to move onto the next step, the data!
-
-=head2 Generating appropriate data
-
-Next we need to create a controller to produce the appropriate graph
-information. Edit the root controller again, add a new method for the
-graph data (make sure you add C<use Chart::OFC> to the top of your
-controller as well.)
-
- =head2 graph
-
- Data provider for Open Flash Chart graphs.
-
- =cut
-
- sub graph : Local {
- my ( $self, $c ) = @_;
-
-Open Flash Chart can display pie charts, lines and/or bars on a grid, and
-area charts on a grid. You can determine which types of graphs to use by
-which L<Chart::OFC::Dataset> subclasses you provide your data to. For
-the purposes of this demo, we'll create a chart with lines, but first we
-need some sample data.
-
- my %data = (
- Date => [ map { "11/$_" } 1 .. 14 ],
- Ninja => [ map { int( rand( 10 ) ) } 1 .. 14 ],
- Pirate => [ map { int( rand( 10 ) ) } 1 .. 14 ],
- );
-
-For each data point you want to graph, you create an object that is an
-instance of one of the L<Chart::OFC::Dataset> subclasses.
-
- my $ninjas = Chart::OFC::Dataset::LineWithDots->new(
- color => 'black',
- label => 'Ninjas',
- solid_dots => 0,
- values => $data{ 'Ninja' },
- );
- my $pirates = Chart::OFC::Dataset::LineWithDots->new(
- color => 'red',
- label => 'Pirates',
- solid_dots => 1,
- values => $data{ 'Pirate' },
- );
-
-Once you have your datasets, you need to create X and Y axis objects that
-provide the information necessary to label the graph.
-
- my $x_axis = Chart::OFC::XAxis->new(
- axis_label => 'Date',
- labels => $data{ 'Date' },
- );
- my $y_axis = Chart::OFC::YAxis->new(
- axis_label => 'Sightings',
- max => 11,
- label_steps => 1,
- );
-
-Now that all the pieces are assembled, we can use them to build a graph.
-
- my $grid = Chart::OFC::Grid->new(
- title => 'Observer Reports',
- datasets => [ $ninjas, $pirates ],
- x_axis => $x_axis,
- y_axis => $y_axis,
- );
-
-Then all that is left is to send this data to the browser.
-
- $c->response->body( $grid->as_ofc_data );
- }
-
-=head2 Done!
-
-Now just run C<script/adventofc_server.pl>, point your browser at
-L<http://localhost:3000/>, and celebrate your fancy graphs!
-
-=head3 AUTHOR
-
-Jason Kohles, E<lt>email at jasonkohles.comE<gt>
-
-L<http://www.jasonkohles.com/>
-
-=cut
-
Added: trunk/examples/CatalystAdvent/root/2007/pen/1.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/1.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/1.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,5 @@
+=head1 Day 1 - Catalyst Application Design
+
+=head1 AUTHOR
+
+Jon Rockway
Added: trunk/examples/CatalystAdvent/root/2007/pen/10.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/10.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/10.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,5 @@
+=head1 Day 10 - Rapid CRUD with Catalyst
+
+=head1 AUTHOR
+
+Peter Karman
Added: trunk/examples/CatalystAdvent/root/2007/pen/11.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/11.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/11.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,5 @@
+=head1 Catalyst Base Classes
+
+=head1 AUTHOR
+
+claco
Added: trunk/examples/CatalystAdvent/root/2007/pen/12.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/12.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/12.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,5 @@
+=head1 local::lib and Catalyst
+
+=head1 AUTHOR
+
+John Goulah
Added: trunk/examples/CatalystAdvent/root/2007/pen/15.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/15.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/15.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,252 @@
+=head1 Advanced Search in web DBIx::Class based applications (with tags, full text search and searching by location)
+
+There is a bit of irony that I write that article, for people to learn from it,
+while in fact it is my failing to properly wrap my head around the problem and encapsulate
+my solution into a CPAN library that forces me to write an article in the first
+place. But maybe someone smarter then me will read it and write that CPAN
+module?
+
+=head2 The Problem
+
+It is a common case that on a web site you need an 'advanced search' feature
+that let's the user combine simple predicates into more elaborated queries.
+Usually all the predicates are joined with an 'AND' - and the technique I
+describe here is based on this assumption. At first this task looks pretty
+simple. You have a list of parameters from the web form, corresponding the the
+columns of some database table, you have the values of those parameters and you
+need to find all the records in that table that have those values in those
+columns. You just do:
+
+ my @records = $schema->ResultSet( 'MyTable' )->search(
+ $reqest->params,
+ { page => 1, rows => 5 }
+ );
+
+Simple.
+
+Then of course you add parameter validation and filtering - but this is outside
+of the scope of this article.
+
+Then you need to add checks on columns not only in the searched table, but also
+on columns from related records and things become more complicated. What I
+propose here is a solution that works for the simple case, solves the related
+tables case, and also is easily extendable to cover more complicated predicates
+like searching by a conjunction of tags, full text searches or searches by
+location. I also add implementation of those 'advanced' predicates (using the
+PostgreSQL extensions for full text search and location based search).
+
+=head2 The Solution
+
+The solution I propose is this simple module:
+
+ package Ymogen2::DB::RSSearchBase;
+
+ use strict;
+ use warnings;
+
+ use base qw( DBIx::Class::ResultSet );
+
+ sub advanced_search {
+ my ( $self, $params, $attrs ) = @_;
+ for my $column ( keys %$params ){
+ if( my $search = $self->can( 'search_for_' . $column ) ){
+ $self = $self->$search( $params );
+ next;
+ }
+ my ( $full_name, $relation ) = simple_predicate( $column );
+ my $join;
+ $join = { join => [ $relation ] } if $relation;
+ $self = $self->search(
+ { $full_name => $params->{$column} },
+ $join,
+ );
+ }
+ $self = $self->search( {}, $attrs );
+ return (wantarray ? $self->all : $self)
+ }
+
+You use it like that:
+
+ my @records = $schema->ResultSet( 'MyTable' )->advanced_search(
+ \%search_params,
+ { page => 1, rows => 5 }
+ );
+
+But first you need to make your ResultSet class inherit from it. This can be
+done in several ways, what we do is adding:
+
+ __PACKAGE__->resultset_class(__PACKAGE__ . '::ResultSet');
+
+ package Ymogen2::DB::Schema::Users::ResultSet;
+
+ use base qw( Ymogen2::DB::RSSearchBase );
+
+
+
+to MyTable.pm.
+
+For the simple case it works just like the familiar 'search' method of the
+L<DBIx::Class::ResultSet class>. But it also works for searching in related
+records. For that we have the simple_predicate function. It looks like that:
+
+ sub simple_predicate {
+ my $field = shift;
+ if( $field =~ /(.*?)\.(.*)/ ){
+ my $first = $1;
+ my $rest = $2;
+ my( $column, $join ) = simple_predicate( $rest );
+ if ( $join ) {
+ return $column, { $first => $join };
+ }else{
+ return $first . '.' . $column, $first;
+ }
+ }elsif( $field ){
+ return $field;
+ }else{
+ return;
+ }
+
+What it does is parsing column names of the format:
+'relationship1.relationship2.relationship3.column' into 'relationship3.column'
+- the fully qualified column name and a
+'{ relationship1 => { relationship2 => relationship3 } }' hash used for joining
+the appriopriate tables.
+
+(I had also a non-recursive version - but it was not simpler)
+
+So now you can do this:
+
+ my @records = $schema->ResultSet( 'MyTable' )->advanced_search(
+ {
+ column1 => 'value1',
+ column2 => 'value2',
+ some_relation.column => 'value3',
+ some_other_relation.some_third_relation.column => 'value4',
+ },
+ { page => 1, rows => 5 }
+ );
+
+Useful?
+We use it.
+
+=head2 The Extensions
+
+But the real advantage of this approach is how easily it can be extended.
+
+=head3 Tags
+
+For example let say we need to search by conjunction of tags like that:
+
+ my @records = $schema->ResultSet( 'MyTable' )->advanced_search( {
+ column1 => 'value1',
+ some_other_relation.some_third_relation.column => 'value4',
+ tags => [ qw/ tag1 tag2 tag3/ ],
+ });
+
+What we need is a method called 'search_for_tags' that will do the search. The
+nice thing is that we don't need to warry how this will be combined with the
+rest of the predicates - DBIC will do the right thing (for and 'AND' relation).
+
+Here is the method:
+
+ sub search_for_tags {
+ my ( $self, $params ) = @_;
+ my @tags = @{$params->{tags}};
+ my %search_params;
+ my $suffix = '';
+ my $i = 1;
+ for my $tag ( @tags ){
+ $search_params{'tags' . $suffix . '.name'} = $tag;
+ $suffix = '_' . ++$i;
+ }
+ my @joins = ( 'tags' ) x scalar( @tags );
+ $self = $self->search( \%search_params, {
+ join => \@joins,
+ }
+ );
+ return $self;
+ }
+
+It builds a query like that:
+
+ SELECT * FROM MyTable me, Tags tags, Tags tags_2, Tags tags_3
+ WHERE tags.mytable_id = me.id AND tags.tag = 'tag1' AND
+ tags_2.mytable_id = me.id AND tags_2.tag = 'tag2' AND
+ tags_3.mytable_id = me.id AND tags_3.tag = 'tag3'
+
+This query will use indices and should be fast (a more detailed cover of this
+technique you can find at my blog at:
+http://perlalchemy.blogspot.com/2006/10/tags-and-search-and-dbixclass.html).
+
+*Attention:* You need the 0.08008 version of DBIx::Class for this to work properly.
+
+=head3 Full Text Search
+
+For full text search I use the PostgreSQL tsearch2 engine here.
+
+=head3 Search by Proximity
+
+For searching by proximity I use the PostgreSQL geometric functions
+(http://www.postgresql.org/docs/8.2/interactive/functions-geometry.html).
+There is
+one problem with it - the distance operator assumes planar coordinates,
+while for the interesting thing is to search geografic data with the standard
+latitude/longitude coordinates. In our solution we just don't care about
+being exact and just multiply the 'distance' in degrees by 50 to get approximate
+distance in miles. The actual proportion is about 43 for latitude and 69 for
+longitude at about the London's longitude, it would be possible to get quite
+good results by dividing the latitude and longitude by those numbers in the
+database - but I would rather have good data in the database then more exact
+results. Maybe at some point we shell switch to use some real geografic
+distance functions (I've seen a PosgreSQL extension to do that - but I was
+scared a bit by it's experimental status).
+
+So here is the function used to filter the results by proximity to a place:
+
+sub search_for_distance {
+ my ( $self, $rs, $params ) = @_;
+ my $lat_long = $params->{lat_long};
+ my $distance = $params->{distance} / 50;
+ # around London the actual proportions are around 43 for latitude
+ # and 69 for longitude
+ return $rs->search(
+ { "(lat_long <-> '$lat_long'::POINT) < " => \$distance },
+ { join => 'location' }
+ );
+}
+
+This function assumes there are two parameters on the $params hash: distance
+and lat_long (lattitude/logintude coordinates). The location data in our
+database are in a separate table called 'location'.
+
+We also use another search extension:
+
+sub search_for_lat_long {
+ my ( $self, $rs, $params ) = @_;
+ my $lat_long = $params->{lat_long};
+ $rs = $rs->search( undef,
+ {
+ join => 'location',
+ '+select' => [ \"(lat_long <-> '$lat_long'::POINT) AS distance" ],
+ '+as' => 'distance',
+ order_by => 'distance ASC',
+ }
+ );
+ return $rs;
+}
+
+This function sorts the results by proximity to the point determined by the
+lat_long coordinates. This way the user does not need to specify the
+maximum distance - the closest results are displayed on the first pages
+anyway - and that is enough for most of the searches.
+
+=head2 And Beyond
+
+In the search by proximity extension I've used ordering of the results. There
+is one problem with this. We use many 'search' calls on the resultset
+to cumulate the predicates - but we cannot do this with the order. Only the
+last 'order_by' parameter used in the 'search' calls is effective. I believe
+it would be useful to have a similar 'cumulative' behaviour for 'order_by'
+and we can add this to 'advanced_search' (or perhaps it can be added to
+the core DBIC search method).
+
Added: trunk/examples/CatalystAdvent/root/2007/pen/17.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/17.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/17.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,5 @@
+=head1 Catalyst with Ext+Ajax: Editable Data Grids
+
+=head1 AUTHOR
+
+jasonk
Added: trunk/examples/CatalystAdvent/root/2007/pen/2.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/2.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/2.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,244 @@
+=head1 Day 2 - Getting started with ExtJS screen library
+
+Today we take a look at the ExtJS screen library and how to get started
+using it within a Catalyst web application.
+
+Don't forget to come back on Day 17 for a more advanced example by jasonk in
+"Catalyst with Ext+Ajax: Editable Data Grids"
+L<http://catalyst.perl.org/calendar/2007/17>
+
+=head2 What is ExtJS?
+
+ExtJS L<http://www.extjs.com/> is a cross-browser Javascript library for web
+pages. You can use it to achieve Web 2.0 effects without writing too much
+Javascript code (always a good idea!). It offers abstracted handling for HTML
+elements, Document Object Model (DOM), event handling and AJAX (client-server)
+calls. ExtJS also provides styling (blue, aero and Vista, you can write more)
+and a good selection of widgets including:
+
+=over 2
+
+=item * window
+
+=item * layout
+
+=item * tabs
+
+=item * form
+
+=item * toolbar
+
+=item * menu
+
+=item * tree
+
+=item * combobox
+
+=item * grid
+
+=back
+
+The full range is listed here L<http://extjs.com/learn/Ext_Extensions>
+
+
+The easiest way to see what is possible is to watch it in action:
+
+=over 2
+
+=item * desktop L<http://extjs.com/deploy/dev/examples/desktop/desktop.html>
+
+=item * feed viewer L<http://extjs.com/deploy/dev/examples/feed-viewer/view.html>
+
+=item * photo organiser L<http://extjs.com/deploy/dev/examples/organizer/organizer.html>
+
+=back
+
+For more examples see L<http://extjs.com/deploy/dev/examples/>
+
+
+
+=head2 What web browsers does it work on?
+
+=over 2
+
+=item * Internet Explorer 6+
+
+=item * Firefox 1.5+ (PC, Mac)
+
+=item * Safari 2+
+
+=item * Opera 9+ (PC, Mac)
+
+=back
+
+=head2 What about other Javascript libraries - I've got legacy code
+
+Because it grew out of Yahoo's YUI library and its developers
+wanted to support legacy code, ExtJS has a tiered design that
+allows you to choose the base Javascript adapter library
+
+=over 2
+
+=item * native Ext
+
+=item * YUI
+
+=item * jQuery
+
+=item * Prototype/Script.aculo.us
+
+=back
+
+For new code, I'd recommend native Ext as it is faster to load.
+
+There are more details and a pretty picture of the design at
+L<http://extjs.com/learn/Ext_FAQ#What_other_libraries_are_required_to_run_Ext.3F>
+
+
+=head2 Downloading and installing the ExtJS library
+
+Download ExtJS 1.1.1 from L<http://extjs.com/download>
+
+The stable release, used in this article, is 1.1.1 and that's the one you need.
+The latest development release is Ext 2.0 but be aware that it has a different
+object model to Ext 1.1 and many of the tutorials, docs and code on the site
+still relate to 1.1.
+Once the widgets and documentation have been done for 2.0 I expect there will
+be a rapid shift over in the user community.
+More details at L<http://extjs.com/learn/Ext_1_to_2_Migration_Guide>
+
+=head3 Installation
+
+If you're on Linux, install ExtJS to your web server document root, e.g.
+/var/www/html/ext-1.1. When you want to use it in a Catalyst project create a
+symbolic link from your root/static directory
+
+ $ ln -s /var/www/html/ext-1.1.1 root/static/
+
+Otherwise, you can simply unzip the whole lot below root/static.
+
+When running the Catalyst test server, it will expect to find the files there.
+
+For production use, use absolute URLs to the ExtJS javascript files from your
+templates, e.g. http://myserver/ext-1.1.1/ext-core.js, and allow your web server
+to serve them rather than Catalyst. It's much faster.
+
+=head2 Manuals and learning materials
+
+Visit L<http://extjs.com/learn/>. You will find tutorials at
+L<http://extjs.com/learn/Tutorials>.
+
+Bookmark and early on read through the community manual
+L<http://extjs.com/learn/Ext_Manual>.
+
+The archive comes with an ExtJS API reference manual. You can open ext-
+1.1.1/docs/index.html in a browser or if you installed it under your Linux web
+server root it should be accessible at L<http://myserver/ext-1.1.1/docs/>. It's
+also online at L<http://extjs.com/deploy/ext/docs/index.html>. Use this to look
+up methods and attributes for ExtJS objects.
+
+
+=head2 Adding ExtJS to a web page
+
+Firstly you need to include the ExtJS Javascript libraries and stylesheets in
+the <head> section of your HTML page
+
+ <link rel="stylesheet" type="text/css" href="/ext-1.1/resources/css/ext-all.css" />
+ <script type="text/javascript" src="/ext-1.1/adapter/ext/ext-base.js"></script>
+ <script type="text/javascript" src="/ext-1.1/ext-all.js"></script>
+
+In the body section use classes for styling
+
+ <body class="xtheme-gray" >
+
+Use named <div> tags to identify content that ExtJS will enhance
+
+ <div id="container"><div id="content" class="welcome">
+ ...
+ </div></div>
+
+Then supply Javascript to tell ExtJS what to do. The following creates a layout
+with one panel called 'content' after the HTML page has finished loading
+
+ <script type="text/javascript">
+ Thescreen = function(){
+ return {
+ init: function(){
+ var layout = new Ext.BorderLayout(document.body, {
+ center: {
+ autoScroll: true,
+ minTabWidth: 50,
+ preferredTabWidth: 150,
+ titlebar: true
+ }
+ });
+
+ layout.beginUpdate();
+ layout.add('center', new Ext.ContentPanel('content', {title:'ExtJS demo app'}));
+ layout.endUpdate();
+ }
+ }
+ }();
+ Ext.EventManager.onDocumentReady(Thescreen.init, Thescreen, true);
+ </script>
+
+Note the prototype object-based approach used to create the 'Thescreen' object.
+This helps standardise objects and avoid memory leaks.
+See L<http://extjs.com/learn/Manual:Intro:Class_Design> for further explanation.
+
+
+=head2 Simple Example
+
+I've provided a simple working example you can use as a starting point for
+writing ExtJS Catalyst applications. It provides code, a menu, a couple of pages
+and a set of templates initially generated using the Catalyst helpers to give
+a portal page.
+
+
+=head2 Example application code
+
+You can check out the code from the Catalyst repository with
+
+ svn co http://dev.catalystframework.org/repos/Catalyst/trunk/examples/ExtJS
+
+
+=head2 Form Architecture Considerations
+
+You have a choice between implementing traditional "round trip" web pages
+and client-server AJAX dynamic web pages seen on Web 2.0 sites.
+
+In the "round trip" case, the user browses to a page, clicks a submit button to
+post data to a server, HTML is sent back then the new page displays. You can
+continue to do this with templates and use ExtJS to enhance the appearance and
+add auto-completers to input fields.
+
+In the second case, you send HTML back once for the first page and then use
+ExtJS to respond to events like button clicks to trigger display changes and
+send/retrieve data to the server via asynchronous data transfers. The ExtJS Form
+widget L<http://extjs.com/learn/Ext_Manual#Forms> handles this and can
+automatically perform front-end data validation and display input warnings from
+the backend. See L<http://extjs.com/deploy/dev/examples/#sample-7> and look at
+the .js files. It's also possible to generate a form dynamically from an XML or
+JSON definition in a data source, so you could hold your form definitions in a
+database and serve them up from a Catalyst data handler.
+
+The choice will depend on how slick a user interface you want and your available
+time, as writing Javascript can be time-consuming. AJAX screens often look better
+but are less accessible for blind visitors and can be harder to debug. For
+testing you would need to consider using a tool like Selenium. Check out
+L<http://www.infoq.com/articles/testing-ajax-selenium>
+L<http://www.infoq.com/news/2007/09/selenium-grid-parallel-testing>
+L<http://search.cpan.org/~lukec/Test-WWW-Selenium-1.13/lib/WWW/Selenium.pm>
+
+
+=head2 Comma Gotcha
+
+If you leave a trailing comma in a Javascript data structure, which is very easy
+to do if you're used to programming Perl, it stops Internet Explorer's parser.
+You'll get a blank page! It's easily spotted by running your output HTML code
+through HTML Tidy.
+
+
+=head1 AUTHOR
+
+peterdragon - Peter Edwards <peter at dragonstaff.co.uk>
Added: trunk/examples/CatalystAdvent/root/2007/pen/24.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/24.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/24.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,5 @@
+=head1 This Year in Catalyst
+
+=head1 AUTHOR
+
+kd
Added: trunk/examples/CatalystAdvent/root/2007/pen/3.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/3.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/3.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,184 @@
+=head1 Get more REST - Using YUI and JavaScript for REST
+
+Today, we'll look into building a fully capable REST client in YUI that allows
+record creation, retrieving, updating and deletion (CRUD) as well as searching.
+
+=head2 Start Here First
+
+To make the most of this article, it is important to not only understand the
+basic idea of REST but to also see how it works within Catalyst.
+
+To start out understanding REST, read the excellent article by Ryan Tomayko
+L<http://tomayko.com/articles/2004/12/12/rest-to-my-wife>, about explaining
+REST in simple terms that a non technical person is able to understand. It will
+help a technical person understand it even more.
+
+Up next is to get a good understanding for how REST works inside of Catalyst,
+using Adam Jacob's excellent L<Catalyst::Action::REST> package. The best way
+to get up to speed is to review Day 9 from the 2006 Advent Calendar, at
+L<http://www.catalystframework.org/calendar/2006/9.pod>.
+
+=head2 Following Along
+
+The examples that are listed here are available from the Catalyst subversion
+repository, available via:
+
+ svn co http://dev.catalystframework.org/repos/Catalyst/trunk/examples/RestYUI
+
+It is a good idea to check out the sources of the sample application to better
+follow along with the examples. Included is the necessary YUI javascript files
+to enhance this application as you go.
+
+=head2 Why use JavaScript?
+
+The reason why we're required to use JavaScript for a full REST service is
+because of the limited vocabulary that a browser coupled with plain HTML is
+allowed to speak. The browser will only ever send GET and POST requests in
+response to user interaction with an HTML document. However, when using the
+C<XmlHttpRequest> method that is a standard in all Grade A browsers the
+vocabulary is extended to support PUT, DELETE and HEAD.
+
+=head2 The Connection to REST
+
+REST is based off of getting what you ask for. In the simplest form, an
+C<XmlHttpRequest> isn't enough to talk to a REST webservice. There are a few
+major components that fit together to make everything work:
+
+=over
+
+=item Content Type
+
+=item Request Method
+
+=item
+
+=back
+
+=head2 Starting the Catalyst Application
+
+We're taking last year's AdventREST example app and slightly modifying it to
+add support for YUI.
+
+So, to get started you can check out that application from subversion by doing:
+
+ svn co http://dev.catalystframework.org/repos/Catalyst/trunk/examples/AdventREST
+
+Or check out the completed RestYUI application.
+
+The only fundamental change to the application is adding the Template Toolkit
+view:
+
+ script/adventrest_create.pl view TT TT
+
+Also, we have to grab the static files for YUI itself. As of writing this,
+version 2.3.1 is available from L<http://sourceforge.net/project/downloading.php?group_id=165715&filename=yui_2.3.1.zip>. Check the latest version at
+L<http://developer.yahoo.com/yui/>
+
+After extracting the files from the zip archive, just copy over the .js files to
+the root/static directory.
+
+=head2 Preparing the REST WebService
+
+To gain access to the REST services, we'll be accessing both the C<list> and the
+C<item> actions. The list is going to be pulled by using a customized YUI
+DataSource object.
+
+This will ask the REST service for a list of our people objects, and in a simple
+form is nothing more than a L<DBIx::Class> search:
+
+ sub user_list_GET {
+ my ( $self, $c ) = @_;
+
+ my %user_list;
+ my $user_rs = $c->model('DB::User')->search;
+ while ( my $user_row = $user_rs->next ) {
+ $user_list{ $user_row->user_id } =
+ $c->uri_for( '/user/' . $user_row->user_id )->as_string;
+ }
+ $self->status_ok( $c, entity => \%user_list );
+ }
+
+That is from the original REST article, and to make the most of the list we're
+going to enhance the listing to provide some additional meta information that
+enhances the webservice with features such as pagination and other contextual
+information. We'll add in pagination with a CGI parameter "page" and a param
+for the number of items per page called "per_page"
+
+ sub user_list_GET {
+ my ( $self, $c ) = @_;
+ my $page = $c->req->params->{page} || 1;
+ my $per_page = $c->req->params->{per_page} || 10;
+
+ # We'll use an array now:
+ my @user_list;
+ my $rs = $c->model('DB::User')
+ ->search(undef, { rows => $per_page })->page( $page );
+ while ( my $user_row = $rs->next ) {
+ push @user_list, {
+ $user_row->get_columns,
+ uri => $c->uri_for( '/user/' . $user_row->user_id )->as_string
+ };
+ }
+
+ $self->status_ok( $c, entity => {
+ result_set => {
+ totalResultsAvailable => $rs->pager->total_entries,
+ totalResultsReturned => $rs->pager->entries_on_this_page,
+ firstResultPosition => $rs->pager->current_page,
+ result => [ @user_list ]
+ }
+ });
+ };
+
+So now we have a serialized structure that looks like this in JSON:
+
+=head2 Connecting with YUI
+
+After the webservice is up, it is time to setup the Yahoo DataSource object.
+
+We'll create a simple template off of the index action in C<Root.pm>, so
+create this action in Root.pm:
+
+ sub index : Private {
+ my ( $self, $c ) = @_;
+ $c->forward( $c->view('TT') );
+ }
+
+That will just direct the action for "/" to go to TT, and render an "index.tt"
+template.
+
+The C<index.tt> file is pretty basic, and after the HTML tags this is the crux
+of what gets the job done:
+
+ /* Create the YAHOO.util.DataSource object, the parameter is the
+ URI to your REST service
+ */
+ this.myDataSource = new YAHOO.util.DataSource("[%
+ c.uri_for( c.controller('User').action_for('user_list') ) %]");
+ this.myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
+ this.myDataSource.connXhrMode = "queueRequests";
+ this.myDataSource.responseSchema = {
+ resultsList: "result_set.result",
+ /* We have to define the fields for usage elsewhere */
+ fields: [
+ "pk1", "token", "default_lang", "languages",
+ "url", "t_created", "t_updated", "actions"
+ ]
+ };
+
+After this, we have a functional DataSource object that can be tied into a
+DataTable:
+
+ myDataTable = new YAHOO.widget.DataTable(
+ "kb_list", myColumnDefs,
+ this.myDataSource, {
+ /* The initialRequest is appended to the URI to set params */
+ initialRequest: "page=1&content-type=text/x-json"
+ }
+ );
+
+=cut
+
+=head1 AUTHOR
+
+J. Shirley
Added: trunk/examples/CatalystAdvent/root/2007/pen/4.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/4.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/4.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,7 @@
+=head1 Catalyst Configuration
+
+=head1 AUTHOR
+
+???
+
+jayk?
Added: trunk/examples/CatalystAdvent/root/2007/pen/5.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/5.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/5.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,5 @@
+=head1 $c->uri_for fun
+
+=head1 AUTHOR
+
+purge
Added: trunk/examples/CatalystAdvent/root/2007/pen/6.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/6.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/6.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,5 @@
+=head1 Mango Overview
+
+=head1 AUTHOR
+
+claco
Added: trunk/examples/CatalystAdvent/root/2007/pen/7.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/7.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/7.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,5 @@
+=head1 Catalyst Authentication
+
+=head1 AUTHOR
+
+jayk
Added: trunk/examples/CatalystAdvent/root/2007/pen/8.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2007/pen/8.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2007/pen/8.pod 2007-12-01 21:33:28 UTC (rev 7189)
@@ -0,0 +1,235 @@
+
+=head1 Catalyst + Open Flash Chart: Fancy graphs with minimal fuss
+
+=head2 About Open Flash Chart
+
+Open Flash Chart is a flash application that prudoces some very nice-looking,
+interactive (and in some cases animated) charts and graphs for your web
+application. It's also completely free and open source, released under the
+GNU General Public License.
+
+For examples of the kinds of graphs you can produce, visit the home page
+at L<http://teethgrinder.co.uk/open-flash-chart/>.
+
+=head2 Getting Started
+
+This tutorial assumes you already have some Catalyst experience, so we won't
+go into too much detail with the basics of creating an application...
+
+ % catalyst.pl AdventOFC
+ ...
+ % cd AdventOFC
+ ...
+ % script/adventofc_create.pl view TT TTSite
+ ...
+
+=head2 Installing Chart::OFC
+
+The Open Flash Chart application uses a difficult to work with format for it's
+data structure. Fortunately the perl community has guys like Dave Rolsky, who
+recently made a nice perl library called L<Chart::OFC> that makes it easier to
+work with. We're going to install L<Chart::OFC> first, because it includes the
+pieces you need from Open Flash Chart, so you only need to download once...
+
+Assuming you are using L<CPANPLUS>, you can install it like this...
+
+ % cpanp
+ ...
+ CPAN Terminal> i Chart::OFC
+
+ Installing Chart::OFC (0.02)
+ ...
+ Module 'Chart::OFC' installed successfully
+ No errors installing all modules
+
+ CPAN Terminal> q
+
+Once you have it installed, you need to get the .swf file and copy it into
+your application root directory. If you installed from L<CPANPLUS>, something
+like the following will work.
+
+ % cd root/static
+ % unzip ~/.cpanplus/*/build/Chart-OFC-*/ofc/*.zip open-flash-chart.swf
+
+If you don't have the L<Chart::OFC> source directory any more, you can
+download the .swf from the Open Flash Chart web site at
+L<http://teethgrinder.co.uk/open-flash-chart/download.php>.
+
+Whether you use the copy from the L<Chart::OFC> source directory, or download
+it from the web site, you will get a .zip file that includes the source as
+well as adapter libraries for various langages. You only need the .swf file
+from the archive though, you won't need any of the other files.
+
+=head2 Adding Open Flash Chart support to your application
+
+Now that you have a basic application, we'll add some charting capabilities
+with OpenFlashChart. The HTML code required to embed a chart is long and
+repetitive, so I like to make a component template to do the boring work
+for me. Create a file in root/lib called 'ofc_swf_object' with the
+following contents:
+
+ [%
+ DEFAULT
+ width = '500'
+ height = '300'
+ id = 'ofc_chart'
+ bgcolor = '#FFFFFF'
+ name = 'ofc_chart';
+
+ SET swf_url = Catalyst.uri_for(
+ '/static/open-flash-chart.swf',
+ {
+ width = width
+ height = height
+ data = data
+ }
+ );
+
+ # This is just to keep the html below from being
+ # too wide for the sake of the demo, you could always
+ # just put this inline if you wanted
+ SET cab_download = [
+ 'http://fpdownload.macromedia.com'
+ '/pub/shockwave/cabs/flash/swflash.cab'
+ '#version=8,0,0,0'
+ ];
+ %]
+ [% FILTER collapse %]
+ <object
+ classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
+ codebase="[% cab_download.join( '' ) %]"
+ width="[% width %]" height="[% height %]"
+ id="[% id %]" align="middle">
+ <param name="allowScriptAccess" value="sameDomain" />
+ <param name="movie" value="[% swf_url %]" />
+ <param name="quality" value="high" />
+ <param name="bgcolor" value="[% bgcolor %]" />
+ <embed
+ src="[% swf_url %]" quality="high"
+ bgcolor="[% bgcolor %]" width="[% width %]"
+ height="[% height %]" name="[% name %]"
+ align="middle" allowScriptAccess="sameDomain"
+ type="application/x-shockwave-flash"
+ pluginspage="http://www.macromedia.com/go/getflashplayer"
+ />
+ </object>
+ [% END %]
+
+You might notice that there are two copies of basically every configuration
+parameter in this block of code. Like many other things on the web, this is
+because Internet Explorer does it one way (the object part) and everything
+else does it differently (with embed.)
+
+=head2 Using your new Open Flash Chart tools
+
+Now that you have the support pieces you need, it's time to put them to use.
+For the purposes of the demo we're just going to add charts to the home page
+of the application, so we'll be editing the Root controller. To get started,
+add a method to the root controller for the index page...
+
+ =head2 index
+
+ The index page for our charts.
+
+ =cut
+
+ sub index : Private {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{ 'template' } = 'index.tt2';
+ }
+
+Next we need to create a template to go along with the index. Since we took
+the time to setup a helper template, this one will be rather small. Put this
+code into your root/src/index.tt2.
+
+ [% INCLUDE ofc_swf_object data = Catalyst.uri_for( '/graph' ) %]
+
+If you run script/adventofc_server.pl at this point, you should get a nice
+graph display, although it won't contain any data. In order to get nice
+charts, we need to move onto the next step, the data!
+
+=head2 Generating appropriate data
+
+Next we need to create a controller to produce the appropriate graph
+information. Edit the root controller again, add a new method for the
+graph data (make sure you add C<use Chart::OFC> to the top of your
+controller as well.)
+
+ =head2 graph
+
+ Data provider for Open Flash Chart graphs.
+
+ =cut
+
+ sub graph : Local {
+ my ( $self, $c ) = @_;
+
+Open Flash Chart can display pie charts, lines and/or bars on a grid, and
+area charts on a grid. You can determine which types of graphs to use by
+which L<Chart::OFC::Dataset> subclasses you provide your data to. For
+the purposes of this demo, we'll create a chart with lines, but first we
+need some sample data.
+
+ my %data = (
+ Date => [ map { "11/$_" } 1 .. 14 ],
+ Ninja => [ map { int( rand( 10 ) ) } 1 .. 14 ],
+ Pirate => [ map { int( rand( 10 ) ) } 1 .. 14 ],
+ );
+
+For each data point you want to graph, you create an object that is an
+instance of one of the L<Chart::OFC::Dataset> subclasses.
+
+ my $ninjas = Chart::OFC::Dataset::LineWithDots->new(
+ color => 'black',
+ label => 'Ninjas',
+ solid_dots => 0,
+ values => $data{ 'Ninja' },
+ );
+ my $pirates = Chart::OFC::Dataset::LineWithDots->new(
+ color => 'red',
+ label => 'Pirates',
+ solid_dots => 1,
+ values => $data{ 'Pirate' },
+ );
+
+Once you have your datasets, you need to create X and Y axis objects that
+provide the information necessary to label the graph.
+
+ my $x_axis = Chart::OFC::XAxis->new(
+ axis_label => 'Date',
+ labels => $data{ 'Date' },
+ );
+ my $y_axis = Chart::OFC::YAxis->new(
+ axis_label => 'Sightings',
+ max => 11,
+ label_steps => 1,
+ );
+
+Now that all the pieces are assembled, we can use them to build a graph.
+
+ my $grid = Chart::OFC::Grid->new(
+ title => 'Observer Reports',
+ datasets => [ $ninjas, $pirates ],
+ x_axis => $x_axis,
+ y_axis => $y_axis,
+ );
+
+Then all that is left is to send this data to the browser.
+
+ $c->response->body( $grid->as_ofc_data );
+ }
+
+=head2 Done!
+
+Now just run C<script/adventofc_server.pl>, point your browser at
+L<http://localhost:3000/>, and celebrate your fancy graphs!
+
+=head3 AUTHOR
+
+Jason Kohles, E<lt>email at jasonkohles.comE<gt>
+
+L<http://www.jasonkohles.com/>
+
+=cut
+
Property changes on: trunk/examples/CatalystAdvent/root/2007/pen/8.pod
___________________________________________________________________
Name: keywords
+ Id Author Rev Date URL
Name: svn:eol-style
+ native
More information about the Catalyst-commits
mailing list