# This file is Copyright (c) 2000-2007 Eric Andreychek. All rights reserved. # For distribution terms, please see the included LICENSE file. package OpenThought; =head1 NAME OpenThought - An AJAX transport and helper library, making AJAX-based page updates trivial =head1 SYNOPSIS use OpenThought(); use CGI(); my $OT = OpenThought->new(); my $q = CGI->new; # First, put everything you wish to give to the browser into a hash my ($fields, $html, $image); $fields->{'myTextBox'} = "Text Box Data"; $fields->{'myCheckbox'} = "true"; $fields->{'myRadioBtn'} = "RadioBtn2Value"; $fields->{'mySelectList'} = [ [ "text1", "value1" ], [ "text2", "value2" ], [ "text3", "value3" ], ]; $html->{'id_tagname'} = "New HTML Code"; $image->{'image_name'} = "http://example.com/my_image.gif"; # You can also execute JavaScript, just put it into a scalar my $javascript_code = "alert('Howdy!')"; # Then send it to the browser using: $OT->param( $fields ); $OT->param( $html ); $OT->param( $image ); $OT->focus( "myTextBox" ); $OT->javascript( $javascript_code ); print $q->header: print $OT->response(); # Or use the utility method: print $q->header; print $OT->response( param => $fields, param => $html, param => $image, focus => "myTextBox", javascript => $javascript_code, ); # In a seperate HTML file, you might have this (which is where you'd first # point the browser, the HTML then calls the Perl when you click the button or # select list)
=head1 DESCRIPTION OpenThought is a library which implements an API for AJAX communication and updates. You can perform updates to form fields, HTML, call JavaScript functions, and more with a trivial amount of code. OpenThought strives to provide a simple yet powerful and flexible means for creating AJAX applications. The interface is simple -- you just build a hash. Hash keys are mapped to field names or id tags in the HTML. The value your hash keys contain is dynamically inserted into the corresponding field (without reloading the page). ==head1 COMPATABILITY OpenThought is compatible with a wide range of browsers, including Internet Explorer 4+, Netscape 4+, Mozilla/Firefox, Safari, Opera, Konqeueror, and others. It detects the browsers capabilities; if the browser doesn't support new functions such as XMLHttpRequest or XMLHTTP, it falls back to using iframes. =head1 METHODS =cut use strict; use Carp; $OpenThought::VERSION="1.99.17"; $OpenThought::DEBUG ||= 0; use vars qw( $DEBUG ); #/------------------------------------------------------------------------- # function: new # =pod =over 4 =item new() $OT = OpenThought->new(); Creates a new OpenThought object. =item Return Value =over 4 =item $OT OpenThought object. =back =back =cut # The main OpenThought constructor sub new { my ( $pkg, $args ) = @_; $args ||= {}; my $class = ref $pkg || $pkg; my $self = { %{ $args }, _persist => $args->{persist} || 0, }; bless ($self, $class); $self->_init(); return $self; } sub _init { my $self = shift; my @settings = qw( log_enabled log_level require channel_type channel_visible channel_url_replace selectbox_max_width selectbox_trim_string selectbox_single_row_mode selectbox_multi_row_mode checkbox_true_value checkbox_false_value radio_null_selection_value data_mode ); delete $self->{_settings} if exists $self->{_settings}; foreach my $setting ( @settings ) { $self->{_settings}{$setting} = []; } $self->{_response} = []; } # Generate, in the proper order, the serialized params and settings sub output { my $self = shift; my ( $save, $serialized_data, $restore ); $save = $serialized_data = $restore = ""; my @settings; foreach my $setting ( keys %{ $self->{_settings} } ) { # There needs to be at least one passed in next unless scalar @{ $self->{_settings}{$setting} } > 0; $serialized_data .= join '', @{ $self->{_settings}{$setting} }; push @settings, $setting; } # Grab all the response data (params, focus, url, javascript, etc) $serialized_data .= join '', @{ $self->{_response} }; # Save/restore the current settings unless told to make them # persist unless( $self->{settings_persist} ) { if (@settings) { $save .= $self->_settings_save( @settings ); $restore .= $self->_settings_restore( @settings ); } } # Hands JavaScript code to the browser. The browser processes the data # automatically as we hand it over -- as far as the browser is concerned, # it is simply loading a new page now (but in the hidden frame). my $code = $self->_add_tags( "${save}${serialized_data}${restore}"); $DEBUG && carp $code ; return $code; } *auto_param = \¶m; *fields = \¶m; *html = \¶m; *images = \¶m; # The user has html, input fields, or images they want displayed in the browser sub param { my ( $self, $data, $options ) = @_; if (ref $data eq "ARRAY") { $options = $data->[1] || {}; $data = $data->[0] || {}; } $data = $self->_as_javascript( $data ); my $save = ""; my $restore = ""; if ($options) { $save = $self->_settings_save( keys %{ $options } ) . $self->settings($options, 1); $restore = $self->_settings_restore( keys %{ $options } ); } $data = "${save}parent.OpenThought.ServerResponse(${data});${restore}"; push @{ $self->{_response} }, $data; return $self->_add_tags($data); } # Calls the Focus function within the browser, which in turn takes the # cursor and puts it into a particular field sub focus { my ( $self, $field ) = @_; my $data = " parent.OpenThought.Focus('$field');"; push @{ $self->{_response} }, $data; return $self->_add_tags($data); } # Send Javascript code to be interpreted by the browser. This would often be # used to call a user defined Javascript function.. an example application of # this would be to use the dynapi Dynamic HTML API Library to create and # manipulate DHTML objects from the server. sub javascript { my ( $self, $javascript_code ) = @_; # NOTE: it really doesn't work to escape the JS! The developer needs to do # it themselves... my $data = " with (parent.document) { $javascript_code }"; push @{ $self->{_response} }, $data; return $self->_add_tags($data); } # Jump to a new page with this url sub url { my ( $self, $url ) = @_; unless ( $url ) { croak "You're missing the parameter to 'url'."; } my $javascript_code; if ( ref $url eq "ARRAY" ) { if ( $url->[0] ) { unless ( not ref $url->[0] ) { croak "The first element of the arrayref passed into 'url' should be a scalar containing the url."; } $javascript_code = "parent.OpenThought.FetchHtml('$url->[0]'"; } if ( $url->[1] ) { unless ( ref $url->[1] eq "HASH" ) { croak "The second element of the arrayref passed into 'url' is optional, but if supplied, must be a hashref."; } foreach my $param ( keys %{ $url->[1] } ) { if ( defined $url->[1]->{ $param } ) { $javascript_code .= ",'$param=$url->[1]{ $param }'"; } else { $javascript_code .= ",'$param'"; } } } } elsif ( not ref $url ) { $javascript_code = "parent.OpenThought.FetchHtml('$url'"; } else { croak "The 'url' method takes either a scalar containing the url, or an an arrayref containing both the url and a hashref with url parameters."; } $javascript_code .= ");"; push @{ $self->{_response} }, $javascript_code; return $self->_add_tags($javascript_code); } # Alter settings within the existing OpenThought Application sub settings { my ( $self, $settings, $return_only ) = @_; unless ( ref $settings eq "HASH" ){ croak "When you pass in settings, they need to be a hash " . 'reference. Either $OT->settings( \%settings ) or ' . '$OT->response( settings => \%settings ).'; } my $data; foreach my $name ( keys %{ $settings } ) { # Persist is special as well. It defines whether the settings # being sent are to remain for the life of this page, or just for this # current request. if( $name eq "settings_persist" ) { $self->{settings_persist} = $settings->{$name}; } # All other parameters are treated the same here elsif ( $self->{_settings}{$name} ) { my $setting = "parent.OpenThought.config.$name = \"" . # $self->_escape_javascript( $settings->{$name} ) . "\");"; $self->_escape_javascript( $settings->{$name} ) . "\";"; unless ($return_only) { push @{ $self->{_settings}{$name} }, $setting; } $data .= $setting; } else { carp "No such setting [$name]."; } } if ($return_only) { return $data; } else { return $self->_add_tags($data); } } =pod =over 4 =item param() $OT->param( \%data, [ \%settings ] ); Update input-type form field elements (text boxes, radio buttons, checkboxes, select lists, text areas, etc), HTML elements, as well as images an image attributes. This method accepts a hash reference containing keys which map to field names, html id's, and image names. The form element, html id, or image will be dynamically updated to contain the value found within the hash key. B: These are very straight forward. The hash values are inserted directly into the input fields matching the hash keys. B: The value for the hash key should match the C attribute of the radio button element in your HTML code. When the hash key and value matches the radio button name and value, that radio button will become checked. B Now your browser can use submit and image buttons to send data to the server without actually refreshing. B The following example shows you how you can use a typical HTML link to send data to the server without causing the page to refresh: Click me! Note: For things to work properly when using links, your JavaScript call has to be done within the C handler, and you need to finish it off using C (ie, just like the example shows ;-) Bheader; print $OT->parse_and_output( auto_param => $data }); The above will fill the following select list: header; print $OT->param($data); That appends the data to this select list: Server: my $q = CGI->new(); my $OT = OpenThought->new(); my $param = $q->param('textbox_example'); warn("We were sent $param"); my $field_data; $field_data->{'textbox_example'} = "Blah blah blah"; print $q->header; $OT->param($field_data); print $OT->response(); =head2 Selectbox Form Elements Client:
Server: my $q = CGI->new(); my $OT = OpenThought->new(); my $param = $q->param('selectbox_example')"; warn("We were sent $param"); my $field_data $field_data->{'selectbox_example'} = [ [ "Example 1", "ex_one" ], [ "Example 2", "ex_two" ], [ "Example 3", "ex_three" ], ]; print $q->header; $OT->param($field_data); print $OT->response(); =head2 Radio Button HTML Elements Client:
Server: my $q = CGI->new(); my $OT = OpenThought->new(); my $param = $q->param('radiobtn_example')"; warn("We were sent $param"); my $field_data; $field_data->{'radio_example'} = "ex_one"; print $q->header; $OT->param($field_data); print $OT->response(); =head2 Checkbox HTML Elements Client:
Server: my $q = CGI->new(); my $OT = OpenThought->new(); my $param = $q->param('checkbox_example')"; warn("We were sent $param"); my $field_data; $field_data->{'checkbox_example'} = "true"; print $q->header; $OT->param($field_data); print $OT->response(); =head2 HTML Example Client:

Old HTML

Server: my $q = CGI->new(); my $OT = OpenThought->new(); my $param = $q->param('html-example')"; warn("We were sent $param"); my $data; $data->{'html_example'} = "New HTML"; print $q->header(); $OT->param($data); print $OT->response(); } =head2 Image Example Client: Server: my $q = CGI->new(); my $OT = OpenThought->new(); my $param = $q->param('img_example')"; warn("We were sent image $param"); my $data; $data->{'img_example'} = "/images/image2.png"; print $q->header(); $OT->param($data); print $OT->response(); } =head2 JavaScript Example Client: onClick="OpenThought.CallUrl( 'javascript.pl' )"> Server: my $q = CGI->new(); my $OT = OpenThought->new(); my $js = qq!var greet="Hello World"; alert(greet); !; print $q->header(); $OT->javascript($js); $OT->response(); } =head1 EXAMPLE USING CGI::Application =head2 The .pm File This is an example package built using L together with OpenThought. This is just a package, you'll need an instance script (.pl file) to call it. That's just a handful of lines, and is well documented in L. package Example; use strict; use base 'CGI::Application'; use OpenThought(); # Somewhat of a constructor -- called automatically by CGI::Application (and # before setup()) sub cgiapp_init { my $self = shift; # Store the OpenThought object for later use $self->param('OpenThought' => OpenThought->new()); } # Set up the run modes -- called automatically by CGI::Application sub setup { my $self = shift; $self->run_modes( 'mode1' => 'init_example', 'mode2' => 'some_screen', 'mode3' => 'do_stuff', 'mode4' => 'do_something_else', 'mode5' => 'another_one', ); $self->start_mode('mode1'); } # The default run mode, called if no parameters were sent. This would # normally return an html page (ie, the first page of the website). sub init_example { my $self = shift; my $OT = $self->param('OpenThought'); return $self->show_html_for_initial_screen(); } # An example run mode sub do_stuff { my $self = shift; my $q = $self->query; my $OT = $self->param('OpenThought'); $data = {...}; # Assume we got some sort of interesting data here $OT->param($data); return $OT->response(); } =head1 AUTHOR Eric Andreychek (eric at openthought.net) =head1 THANKS TO JJ < jj at jonallen dot info > John Jewitt < john at jjspc dot demon dot co dot uk > Buddy Burden < buddy at thinkgeek dot com > Brent Ashley < brent at ashleyit dot com > Greg Pomerantz < gmp216 at nyu dot edu > =head1 COPYRIGHT and LICENSE OpenThought is Copyright (c) 2000-2007 by Eric Andreychek. This module is free software; you can redistribute it and/or modify it under the terms of either: a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" which comes with this module. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the Artistic License for more details. =head1 BUGS Bug hunting season has been good. All known bugs have been eradicated. If you happen to run across one, please let me know and I'd be more then happy to take care of it. But real hackers would send a patch ;-) =head1 SEE ALSO L L