File Coverage

blib/lib/CGI/Application/Demo/Ajax.pm
Criterion Covered Total %
statement 24 98 24.4
branch 0 6 0.0
condition 0 4 0.0
subroutine 8 14 57.1
pod 3 6 50.0
total 35 128 27.3


line stmt bran cond sub pod time code
1             package CGI::Application::Demo::Ajax;
2              
3             # Author:
4             # Ron Savage
5             #
6             # Note:
7             # \t = 4 spaces || die.
8              
9 1     1   39553 use base 'CGI::Application';
  1         3  
  1         1720  
10 1     1   11946 use strict;
  1         4  
  1         48  
11 1     1   7 use warnings;
  1         3  
  1         37  
12              
13 1     1   2669 use CGI;
  1         21549  
  1         9  
14              
15 1     1   1953 use Config::Tiny;
  1         1371  
  1         78  
16              
17 1     1   359011 use DBI;
  1         20412  
  1         73  
18              
19 1     1   2359 use HTML::Template;
  1         17877  
  1         45  
20              
21 1     1   1183 use JSON::XS;
  1         118233  
  1         2272  
22              
23             our $VERSION = '1.04';
24              
25             # -----------------------------------------------
26              
27             sub build_search_form
28             {
29 0     0 0   my($self) = @_;
30              
31             # Build the search form and the corresponding Javascript.
32              
33 0           $$self{'_search_js'} -> param(form_action => $$self{'_form_action'});
34 0           $$self{'_search_js'} -> param(sid => '');
35 0           $$self{'_search_form'} -> param(sid => '');
36              
37             # Keep YUI happy by ensuring the HTML is one long string...
38              
39 0           $$self{'_search_form'} = $$self{'_search_form'} -> output();
40 0           $$self{'_search_form'} =~ s/\n//g;
41              
42 0           $self -> log('Leaving build_search_template');
43              
44 0           return ($$self{'_search_js'} -> output(), $$self{'_search_form'});
45              
46             } # End of build_search_form.
47              
48             # -----------------------------------------------
49              
50             sub initialize
51             {
52 0     0 1   my($self) = @_;
53 0           my(@search_form) = $self -> build_search_form();
54              
55             # Generate the Javascript which will be called upon page load.
56              
57 0           my($head_init) = <
58             make_search_name_focus();
59             EJS
60              
61             # Generate the Javascript which will do all the work.
62              
63 0           my($head_js) = <
64             $search_form[0]
65              
66             function make_search_name_focus(eve)
67             {
68             document.search_form.target.focus();
69             }
70             EJS
71              
72             # Generate the web page itself.
73              
74 0           $$self{'_content'} -> param(content => $search_form[1]);
75 0           $$self{'_web_page'} -> param(container => $$self{'_content'} -> output() );
76 0           $$self{'_web_page'} -> param(head_init => $head_init);
77 0           $$self{'_web_page'} -> param(head_js => $head_js);
78              
79 0           $self -> log('Leaving initialize');
80              
81 0           return $$self{'_web_page'} -> output();
82              
83             } # End of initialize.
84              
85             # -----------------------------------------------
86             # TODO: Make this sub a module one day?
87              
88             sub load_config_file
89             {
90 0     0 0   my($self) = @_;
91 0           my($name) = '.htajax.conf';
92              
93             # Find this file and grab the config file from the same dir.
94              
95 0           my($path);
96              
97 0           for (keys %INC)
98             {
99 0 0         next if ($_ !~ m|CGI/Application/Demo/Ajax.pm|);
100              
101 0           ($path = $INC{$_}) =~ s/Ajax.pm/$name/;
102             }
103              
104             # Check the global section.
105              
106 0           $$self{'_config'} = Config::Tiny -> read($path);
107 0           $$self{'_section'} = '_';
108              
109 0 0         if (! $$self{'_config'}{$$self{'_section'} }{'host'})
110             {
111 0           Carp::croak "Config file '$path' does not contain 'host' within the global section";
112             }
113              
114             # Check [x] where x is host=x within the global section.
115              
116 0           $$self{'_section'} = $$self{'_config'}{$$self{'_section'}}{'host'};
117              
118 0 0         if (! $$self{'_config'}{$$self{'_section'}}{'tmpl_path'})
119             {
120 0           Carp::croak "Config file '$path' does not contain 'tmpl_path' within the section [$$self{'_section'}]";
121             }
122              
123             } # End of load_config_file.
124              
125             # -----------------------------------------------
126              
127             sub log
128             {
129 0     0 0   my($self, $s) = @_;
130 0           my($sth) = $$self{'_dbh'} -> prepare("insert into $$self{'_log_table'} (message) values (?)");
131 0           my $time = localtime();
132              
133 0   0       $sth -> execute($time . ': ' . ($s || '') );
134              
135             } # End of log.
136              
137             # -----------------------------------------------
138              
139             sub search
140             {
141 0     0 1   my($self) = @_;
142 0           my($output) = [];
143 0           my($q) = $self -> query();
144 0   0       my($target) = $q -> param('target') || '.';
145              
146             # Read the data from our multi-million dollar RDBMS.
147              
148 0           push @$output,
149             {
150             name => 'Ron',
151             role => 'Programmer',
152             };
153              
154 0           push @$output,
155             {
156             name => 'Zoe',
157             role => 'Female dog',
158             };
159              
160 0           push @$output,
161             {
162             name => 'Zigzag',
163             role => 'Male dog',
164             };
165              
166 0           $self -> log("Database returned @{[scalar @$output]} results");
  0            
167              
168             # Filter based on user input...
169              
170 0           @$output = grep{$$_{'name'} =~ /$target/i} @$output;
  0            
171              
172 0           $self -> log("Filter returned @{[scalar @$output]} results");
  0            
173 0           $self -> log('Leaving search');
174              
175 0           return JSON::XS -> new() -> encode({results => $output});
176              
177             } # End of search.
178              
179             # -----------------------------------------------
180              
181             sub setup
182             {
183 0     0 1   my($self) = @_;
184              
185 0           $self -> load_config_file();
186 0           $self -> run_modes([qw/initialize search/]);
187 0           $self -> start_mode('initialize');
188              
189             # Use aliases to shorten names.
190              
191 0           $$self{'_form_action'} = $$self{'_config'}{$$self{'_section'} }{'form_action'};
192 0           $$self{'_session_driver'} = $$self{'_config'}{$$self{'_section'} }{'session_driver'};
193 0           $$self{'_temp_dir'} = $$self{'_config'}{$$self{'_section'} }{'temp_dir'};
194 0           $$self{'_tmpl_path'} = $$self{'_config'}{$$self{'_section'} }{'tmpl_path'};
195 0           $$self{'_yui_url'} = $$self{'_config'}{$$self{'_section'} }{'yui_url'};
196              
197             # Load all the templates.
198              
199 0           $self -> tmpl_path($$self{'_tmpl_path'});
200              
201 0           $$self{'_content'} = $self -> load_tmpl('content.tmpl');
202 0           $$self{'_search_form'} = $self -> load_tmpl('search.tmpl');
203 0           $$self{'_search_js'} = $self -> load_tmpl('search.js');
204 0           $$self{'_web_page'} = $self -> load_tmpl('web.page.tmpl');
205              
206 0           $$self{'_web_page'} -> param(yui_url => $$self{'_yui_url'});
207              
208             # Connect to the database for logging.
209              
210 0           $$self{'_dbh'} = DBI -> connect("DBI:CSV:f_dir=$$self{'_temp_dir'}");
211 0           $$self{'_log_table'} = 'ajax.log';
212              
213 0           $$self{'_dbh'} -> do("drop table $$self{'_log_table'}");
214              
215 0           my($sth) = $$self{'_dbh'} -> prepare("create table $$self{'_log_table'}(message varchar(255) )");
216              
217 0           $sth -> execute();
218              
219 0           my($q) = $self -> query();
220              
221 0           $self -> log('=' x 50);
222 0           $self -> log("Param: $_ => " . $q -> param($_) ) for $q -> param();
223 0           $self -> log('Leaving setup');
224              
225             } # End of setup.
226              
227             # -----------------------------------------------
228              
229             1;
230              
231             =head1 NAME
232              
233             C - A search engine using CGI::Application, AJAX and JSON
234              
235             =head1 Synopsis
236              
237             Either:
238              
239             #!/usr/bin/perl
240              
241             use CGI::Application::Demo::Ajax;
242              
243             CGI::Application::Demo::Ajax -> new() -> run();
244              
245             or:
246              
247             #!/usr/bin/perl
248              
249             use strict;
250             use warnings;
251              
252             use CGI::Application::Dispatch;
253             use CGI::Fast;
254             use FCGI::ProcManager;
255              
256             # ---------------------
257              
258             my($proc_manager) = FCGI::ProcManager -> new({processes => 2});
259              
260             $proc_manager -> pm_manage();
261              
262             my($cgi);
263              
264             while ($cgi = CGI::Fast -> new() )
265             {
266             $proc_manager -> pm_pre_dispatch();
267             CGI::Application::Dispatch -> dispatch
268             (
269             args_to_new => {QUERY => $cgi},
270             prefix => 'CGI::Application::Demo',
271             table =>
272             [
273             '' => {app => 'Ajax', rm => 'initialize'},
274             '/search' => {app => 'Ajax', rm => 'search'},
275             ],
276             );
277             $proc_manager -> pm_post_dispatch();
278             }
279              
280             =head1 Description
281              
282             C demonstrates how to use C together with AJAX and JSON.
283              
284             It ships with:
285              
286             =over 4
287              
288             =item Two C instance scripts: ajax.cgi and ajax
289              
290             ajax.cgi is a trivial C script, while ajax is a fancy script using C and C.
291              
292             =item A text configuration file: .htajax.conf
293              
294             This will be installed into the same directory as Ajax.pm. And that's where Ajax.pm looks for it.
295              
296             By default, form_action is /cgi-bin/ajax.cgi, so you'll need to edit it to use form_action=/local/ajax.
297              
298             Also, the default logging directory is /tmp, so this might call for another edit of .htajax.conf.
299              
300             =item A set of C templates: *.tmpl
301              
302             =item This Perl module: C
303              
304             =back
305              
306             =head1 Distributions
307              
308             This module is available as a Unix-style distro (*.tgz).
309              
310             See http://savage.net.au/Perl-modules/html/installing-a-module.html for
311             help on unpacking and installing distros.
312              
313             =head1 Installation
314              
315             All these assume your doc root is /var/www.
316              
317             =head2 Install YUI
318              
319             Browse to http://developer.yahoo.com/yui/, download, and unzip into htdocs:
320              
321             shell>cd /var/www
322             shell>sudo unzip ~/Desktop/yui_2.7.0b.zip
323              
324             This creates /var/www/yui, and yui_url in .htajax.conf must match.
325              
326             =head2 Install the module
327              
328             Install this as you would for any C module:
329              
330             Unpack the distro, and then either:
331              
332             perl Build.PL
333             ./Build
334             ./Build test
335             sudo ./Build install
336              
337             or:
338              
339             perl Makefile.PL
340             make (or dmake)
341             make test
342             make install
343              
344             =head2 Install the C files.
345              
346             shell>cd /var/www
347             shell>sudo mkdir -p assets/templates/cgi/application/demo/ajax
348             shell>cp distro's/htdocs/*.tmpl to assets/templates/cgi/application/demo/ajax
349              
350             Alternately, edit the now installed .htajax.conf, to adjust tmpl_path.
351              
352             =head2 Install the trivial instance script
353              
354             shell>cp distro's/htdocs/ajax.cgi to /usr/lib/cgi-bin
355             shell>sudo chmod 755 /usr/lib/cgi-bin/ajax.cgi
356              
357             =head2 Install the fancy instance script
358              
359             shell>cd /var/www
360             shell>sudo mkdir local
361             shell>cp distro's/htdocs/ajax to local
362             shell>sudo chmod 755 local/ajax
363              
364             =head2 Configure C to use local/ajax
365              
366             If in fancy mode, add these to httpd.conf:
367              
368             LoadModule fcgid_module modules/mod_fcgid.so
369              
370             and:
371              
372            
373             SetHandler fcgid-script
374             Options ExecCGI
375             Order deny,allow
376             Deny from all
377             Allow from 127.0.0.1
378            
379              
380             And restart C.
381              
382             =head2 Start searching
383              
384             Point your broswer at http://127.0.0.1/cgi-bin/ajax.cgi (trivial script), or
385             http://127.0.0.1/local/ajax (fancy script, nice-and-clean URL).
386              
387             =head1 The Flow of Control
388              
389             Here's a step-by-step description of what's happening:
390              
391             =over 4
392              
393             =item You initialize the process
394              
395             Point your web client at http://127.0.0.1/cgi-bin/ajax.cgi or http://127.0.0.1/local/ajax.
396              
397             This is equivalent to C<< CGI::Application::Demo::Ajax -> new() -> run() >>.
398              
399             Since there is no run mode input, the code defaults to Ajax.pm's sub initialize(). See sub setup() for details.
400              
401             =item The code assembles the default web page
402              
403             The work is done in Ajax.pm's sub initialize().
404              
405             This page is sent from the server to the client.
406              
407             It contains the contents of web.page.tmpl, with both search.js and search.tmpl embedded therein.
408              
409             Of course, it also contains a minimal set of YUI Javascript files.
410              
411             =item The client accepts the response
412              
413             The default web page is displayed.
414              
415             =item You input a search term
416              
417             The C form in search.tmpl is set to not submit, but rather to call the Javascript function search_onsubmit(),
418             which lives in search.js.
419              
420             It's actually the copy of this code, now inside web.page.tmpl, now inside your client, which gets executed.
421              
422             =item The C form is submitted
423              
424             Here, Javascript does the submit, in such a way as to also specify a call-back (Javascript) function, search_callback(),
425             which will handle the response from the server.
426              
427             This function also lives in search.js.
428              
429             =item Ajax.pm runs again
430              
431             This time a run mode was submitted, either as form data or as path info data.
432              
433             And this means that when using the fancy script, you don't need the line in search.tmp referring to the hidden form
434             variable 'rm', because of the path info '/search' in search_onsubmit().
435              
436             =item sub search() carries out the search.
437              
438             The run mode causes Ajax.pm's sub search() to be the sub which gets executed this time.
439              
440             It assembles the results, and uses C to encode them.
441              
442             =item The server replies
443              
444             The results of the search are sent to the client.
445              
446             =item The client accepts the response
447              
448             When the client receives the message, these events occur, in this order:
449              
450             =over 4
451              
452             =item Control passes to search_callback(), the call-back function
453              
454             =item The data is decoded from JSON to text by a YAHOO.lang.JSON object
455              
456             =item The data is moved into a YAHOO.util.LocalDataSource object
457              
458             =item The data is formatted as it's moved into a YAHOO.widget.DataTable object
459              
460             =back
461              
462             This object displays its data automatically. Actually, the object's constructor displays the data, which is why
463             we call new by assigning the object to a Javascript variable, data_table.
464              
465             =back
466              
467             =head2 Next
468              
469             It should be obvious that the code in Ajax.pm's sub search() can be extended in any manner, to pass more complex
470             hash refs to the Javascript function search_callback().
471              
472             This data can then be ignored by the Javascript, or you can extend the responseSchema and column_defs to display it.
473              
474             Given this framework, extending these data structures is basically effortless.
475              
476             =head1 Author
477              
478             C was written by Ron Savage Iron@savage.net.auE> in 2009.
479              
480             Home page: http://savage.net.au/index.html
481              
482             =head1 Copyright
483              
484             Australian copyright (c) 2009, Ron Savage.
485             All Programs of mine are 'OSI Certified Open Source Software';
486             you can redistribute them and/or modify them under the terms of
487             The Artistic License, a copy of which is available at:
488             http://www.opensource.org/licenses/index.html
489              
490             =cut