# This file is Copyright (c) 2000-2003 Eric Andreychek. All rights reserved. # For distribution terms, please see the included LICENSE file. # # $Id: OpenThought.pm,v 1.99 2003/08/29 00:34:35 andreychek Exp $ # package OpenThought; =head1 NAME OpenThought - Web Application Environment which doesn't require page reloads =head1 SYNOPSIS use OpenThought(); my $o = OpenThought->new( $OP ); my $field_data; $field_data->{'myTextBox'} = "Text Box Data"; $field_data->{'myCheckbox'} = "true"; $field_data->{'myRadioBtn'} = "RadioBtnValue"; $field_data->{'mySelectList'} = [ [ "text1", "value1" ], [ "text2", "value2" ], [ "text3", "value3" ], ]; my $html_data; $html_data->{'id_tagname'} = "New HTML Code"; print $o->serialize({ fields => $field_data, html => $html_data, focus => "myTextBox", javascript => $javascript_code }); =head1 DESCRIPTION OpenThought is a powerful and flexible web application environment. OpenThought applications are different from other web applications in that all communication between the browser and the server is performed in the background. This gives a browser the ability to receive data from the server without ever reloading the currently loaded document. Data received can be displayed automatically on the existing page, can access JavaScript functions and variables, and can load new pages. Additionally, OpenThought completely manages all of your session data for you. These features give the look and feel of a full-blown application instead of just an ordinary Web page. OpenThought is extended with L, also described briefly in this documentation. OpenThought works by communicating with the server through the use of a hidden frame. Now some may say "Frames? You're using frames??" No need to fret though, the frame is completely hidden. There is no visible way for your users to tell that they are using an application which makes use of frames. This hidden frame performs all communication with the server for you, and allows your browser to talk to the server without reloading the content currently visible in your web browser. This is how OpenThought can update portions of the screen, and how the server can run JavaScript code in the browser, all without reloading the page. Any time an OpenThought application is loaded, a unique session id is generated for that user. Each time communication takes place between the browser and server, OpenThought is careful to make sure that this session id is sent along with the request. There is nothing you need to do to make this work, it is done for you. When you're programming your application, you can always expect a session id to be sent to the server, and you can easily retrieve it along with the rest of the data sent by the browser. In addition to being able to update portions of a screen, you can also load new pages within the content frame. If you choose to load new pages, your session continues to be maintained. OpenThought gives you an easy way of tieing together multiple web pages, and allows you to build web applications which act like "real" applications. When an application is required, some people would decide to build it in Tk, Visual Basic, Gtk, or any number of other systems which offer a visual interface. But then, your application is either not available on the web, or you have to create a seperate web interface to interact with the backend. Have you ever tried to create a web interface which minics the interface of a non-web based application? The web based application no longer looks or acts like your other interface, it acts like an ordinary webpage. It probably uses several screens to get the same amount of content that your application offered in one. By not ever needing to reload the page, OpenThought offers this application look and feel that's been missing from the web. This allows you to create just one interface, for both LAN and web use. =head1 FUNCTIONS =cut use strict; $OpenThought::VERSION="0.71"; # Include the OpenThought core components use OpenThought::XML2Hash 0.57 (); use OpenThought::Serializer(); use OpenThought::Template(); use OpenPlugin 0.11 (); #/------------------------------------------------------------------------- # function: new # =pod =over 4 =item new $o = OpenThought->new( $OP, { Prefix => "/path/which/overrides/config", OpenThoughtRoot => "/path/which/overrides/config", }); Creates a new OpenThought object. =over 4 =item Parameters =over 4 =item $OP An OpenPlugin object. =item Prefix (optional) The directory prefix OpenThought Data Files were installed under. The value for this is typically set in the OpenThought-httpd-modperl.conf file. However, if you wish to override it, or you don't have the ability to edit that file (ie, you don't have root on the webserver), you can set C here. By default, the installation puts this in C. =item OpenThoughtRoot (optional) The full path to the directory under which all OpenThought Applications will be located. Please include a trailing slash. By default, the installation puts this in C. The value for this is typically set in the OpenThought-httpd-modperl.conf file. However, if you wish to override it, or you don't have the ability to edit that file (ie, you don't have root on the webserver), you can set OpenThoughtRoot here. =back =item Return Value =over 4 =item $o OpenThought object. =back =back =back =cut # The main OpenThought constructor sub new { my ( $this, $op, $args ) = @_; if ( $args ) { if ( exists $args->{ Prefix } ) { $OpenThought::Prefix = $args->{ Prefix }; } if ( exists $args->{ OpenThoughtRoot } ) { $OpenThought::OpenThoughtRoot = $args->{ OpenThoughtRoot }; } } # If we don't have a Prefix set anywhere, try and figure it out from the # location of the config file passed in unless ( $OpenThought::Prefix ) { require File::Basename; my $dir = File::Basename::dirname( $op->config->{_m}{dir} ); $dir =~ s/etc.$//; $OpenThought::Prefix = $dir; } my $class = ref( $this ) || $this; my $self = { OP => $op, }; bless ($self, $class); $op->param->set_incoming( $self->deserialize( $op->param->get_incoming('OpenThought'))) if $op; return $self; } #/------------------------------------------------------------------------- # function: settings # =pod =over 4 =item settings $string = $o->settings({ auto_clear => boolean, max_selectbox_width => size, fetch_start => string, fetch_display => string, fetch_finish => string, runmode_param => string, order => [ "fetchstart", "autoclear" ] }); Alter settings in the currently running OpenThought application. These settings will persist, until otherwise altered. To temporarily change settings, these same options can be used in the serialize function. =over 4 =item Parameters =over 4 =item auto_clear The default behaviour for select lists is to clear themselves when multiple values are being inserted into them. Setting autoclear to a false value is one method of altering that behavior. If auto_clear is false, the contents of a select list are preserved, and any new data is appended to the select list. Any number less then 1 and 'false' are considered false values, everything else is true. When autoclear is set to false, you can still clear a select list by passing in an empty string as a parameter to the select list. =item max_selectbox_width Aside from Netscape 4, all browsers which items into a select box resize that select box to the width of the longest entry. Select box resizing is neat, but sometimes it ends up being much to big, and can adversly affect other parts of your visual layout. This option allows you to modify the size of text going into a SelectBox, so the browser doesn't make the select box too big. Text going into a select box will be truncated if it is longer then the max width set here. The width refers to the maximum amount of characters. This can be any number, or 0 to not constrain the size. =item runmode_param The name of the parameter which holds the run mode. The JavaScript in the browser uses this when you send data the the server -- it figures out which run mode you are trying to run, and sets the appropriate parameters, so L can find it. This, of course, doesn't matter if you aren't using L. =item fetch_start Text to display in the status bar when the browser sends data to the server. =item fetch_display Text to display in the status bar when the browser has received data from the server, and is currently displaying it. =item fetch_finish Text to display in the status bar when the browser finished displaying data it received from the server. =item order As parameters are serialized and executed in a particular order, this parameter allows you to override the default and specify exactly what this order will be. This option is a reference to an array, containing the proper order for all the items in the call. Although this is more useful in the serialize function, it still could be beneficial to alter the order of settings. The current order these are executed in are: auto_clear, max_selectbox_width, runmode_param, fetch_start, fetch_display, and fetch_finish. =back =item Return value =over 4 =item $string A string, which contains the settings to be given to the browser. Often this is just sent directly to the browser by your application, although you can modify it first if you desire. =back =back =back =cut sub settings { my ( $self, $params ) = @_; my $serializer = OpenThought::Serializer->new({ OP => $self->{OP} }); $serializer->params( $params ); return $serializer->output; } #/------------------------------------------------------------------------- # function: serialize # =pod =over 4 =item serialize $string = $o->serialize({ fields => $field_data, html => $html_data, focus => $field_name, javascript => $javascript_code, url => $content_url, $setting => $value, order => [ "javascript", "fields" ], }); Serialize data that you wish to give to the browser. =over 4 =item Parameters =over 4 =item fields This is how you would update HTML form field elements. This element accepts a hash reference containing keys which map to HTML form element names in the HTML document. The value of each hash key will be displayed in the corresponding field. For radio buttons, the value for the hash key should match the C attribute of the radio button element in your HTML code. The radio button with the name and value which matches the hash key and value will be the element that gets selected. Select List elements are a little different. By default, sending a single array reference to a select list will append that item to your existing select list items. Sending in a reference to an array of arrays will cause the list to be rewritten with this new data. You can modify this by using the C setting. Sending a single scalar value to a select list will cause that item to become highlighted. There are examples of all these below. =item html This is how you would update literal HTML code. It accepts a hash reference containing keys which map to HTML id attributes. You can add id tags to nearly any HTML attribute. The value of each hash key will then be displayed within the corresponding id attribute. This effectively allows you to replace any html currently loaded in the browser with whatever content you desire, on the fly. =item focus This allows you to focus a given form field. It accepts a string containing the name of the field within the HTML code which you wish to focus. =item javascript This allows you to run JavaScript code, along with accessing JavaScript functions and variables. It accepts a string containing the JavaScript code you wish to run. This will be run by the browser, within your application's namespace. You do not need to add script tags. =item url The url parameter allows you to load a new html document, in the content frame. All your base files which are loaded remain the same -- meaning your session and such are preserved -- what changes is the user interface, when using this option. It first expires the cache associated with the form elements in the existing page, and then loads the new page you requested. It accepts a string containing the url to load. =item setting This allows you to temporarily change the value of a setting. This is the same as calling the C function, except that when used here, the setting is only changed for this one call, and then it is restored to it's previous value. The keyword to use for this option is not the literal word 'setting', but any of the setting options listed under the settings function. =item order This allows you to alter the order in which items are serialized. Since parameters are serialized and executed in a particular order, this parameter allows you to override the default and specify what this order will be. This is a reference to an array, containing the proper order for all the items in the call. The default order is: settings (see settings function), followed by fields, html, javascript, url, and focus. =back =item Return value =over 4 =item $string A string, which contains the serialized data to be given to the browser. Typically, you would have your application send this directly to the browser, although you can modify it first if you desire. =back =back =back =cut sub serialize { my ( $self, $params ) = @_; my $serializer = OpenThought::Serializer->new({ save_settings => 1, OP => $self->{OP}, }); $serializer->params( $params ); return $serializer->output; } #/------------------------------------------------------------------------- # function: deserialize # =pod =over 4 =item deserialize $hashref = $o->deserialize( OpenThought ) Retrieve the parameters sent to us during a typical OpenThought request. =over 4 =item Parameters =over 4 =item OpenThought The parameter, passed to us from the browser, named 'OpenThought'. This can be retrieved using $OP->param->get_incoming('OpenThought'). =back =item Return value Deserialize returns a hash reference with the following keys: =over 4 =item fields A hash reference containing keys and values which map to the HTML form field names and values we were sent from the browser. =item expr A hash reference containing keys/value pairs as sent to us by the browser. These are expressions which the application developer wanted sent to the server upon a particular event. =item session_id A string containing the session id for your application. =item run_mode A string containing the current run mode. The key C is the C, as set in the config file, or overridden with the C or C functions. =back =back =back =cut sub deserialize { my ( $self, $xml_param ) = @_; if ( not $xml_param or $xml_param eq "1" or $xml_param eq "ui") { $self->{'event_type'} = "init"; return; } my $serializer = OpenThought::Serializer->new({ OP => $self->{OP} }); my $params = $serializer->deserialize( $xml_param ); $self->{'runmode_param'} = $params->{'settings'}{'runmode_param'}; $self->{'event_type'} = $params->{'settings'}{'event'} || "init"; $self->{'runmode'} = $params->{'settings'}{'runmode'}; my $user_params = { fields => $params->{'fields'}, expr => $params->{'expr'}, session_id => $params->{'settings'}{'session_id'}, }; $user_params->{ $self->{'runmode_param'} } = $self->{'runmode'} if defined $params->{'settings'}{'runmode'}; return $user_params; } #/------------------------------------------------------------------------- # function: init # =pod =over 4 =item init $string = $o->init( [ $url ] ) Retrieve the base files required for a browser to load an OpenThought Application =over 4 =item Parameters =over 4 =item $url (optional) If the url where your HTML user interface can be found is different from the one the browser just request, include the url here. Otherwise, it defaults to using the current url to retrieve the HTML. For example, if your browser used http://mydomain.com/OpenThought/thisapp.pl to retrieve the base files, but you want the HTML for the content frame to be loaded from "/content/thisapp.html", you can pass in that relative path as a parameter. This isn't frequently used, as most people would use templates served from the original url (http://mydomain.com/OpenThought/thisapp.pl). If you do choose to use this, it is recommended that you only pass in relative url's. Most browsers consider it a security risk to use JavaScript in combination with frames loaded from different sites. =back =item Return value =over 4 =item $string Returns as a string the html base code that is required in order for your application to run. Often this is just given directly to the browser by your application, although you can modify it first if you desire. =back =back =back =cut # Handle the deprecated "get_application_base" method *get_application_base = \*init; sub init { my ( $self, $url ) = @_; # Create a blank page for the hidden frame. And no, simply using # "about:blank" in the browser won't work because of IE6 under SSL, but # thanks for asking. return "" if $self->event_type eq "blank"; $url ||= $self->{OP}->request->uri; my $template = OpenThought::Template->new( $self->{OP}, $url ); $template->retrieve_template(); $template->insert_parameters(); return $template->return_template(); } #/------------------------------------------------------------------------- # function: event # =pod =over 4 =item event $string = $o->event( init => sub { return $o->init; }, ui => sub { return $self->my_ui_handler }, data => sub { return $self->my_data_handler }, ); Call a subroutine or code reference based on the applications current event. Upon executing the C method, OpenThought determines the particular event that's been generated, and immediatly calls the associated subroutine that you've sent in as a parameter. It is not necessary to pass in all three parameters. Whenever the browser communicates with your application on the server, it generates an event. An event can be either C, C, or C. There is actually one other event type called C. It is recommended that you do absolutally nothing with this. However, for those who like to fiddle with everything, the C event is called whenever the hidden frame is trying to load it's blank page. The C event is usually handled for you within C. If, for some reason, you aren't happy with the blank page it's returning, you can pass in C as a parameter above, and have it return a better blank page. :-) (and no, using "about:blank" in the browser won't work unless you don't care about IE6+ users) =over 4 =item Parameters =over 4 =item init (optional) A reference to a sub or anonymous sub which will be called during the C event. =item ui (optional) A reference to a sub or anonymous sub which will be called during the C event. =item data (optional) A reference to a sub or anonymous sub which will be called during the C event. =back =item Return value =over 4 =item Returns whatever value your subroutine returned. =back =back =back =cut sub event { my $self = shift; my %events = @_; my $event_type = $self->event_type; if ($event_type eq "init" and exists $events{'init'}) { return $events{'init'}(); } elsif ($event_type eq "blank" and exists $events{'blank'}) { return $events{'blank'}(); } # A blank event is handled by "init" if no other handler is offered elsif ($event_type eq "blank" and exists $events{'init'}) { return $events{'init'}(); } elsif ($event_type eq "ui" and exists $events{'ui'}) { return $events{'ui'}(); } elsif ($event_type eq "data" and exists $events{'data'}) { return $events{'data'}(); } else { $self->{OP}->log->info("In event $event_type: no $event_type parameter passed in."); } } #/------------------------------------------------------------------------- # function: event_type # =pod =over 4 =item event_type $string = $o->event_type() Retrieve the type of event the browser initiated. The browser is capable of calling us using C or C. When using C, the browser is expecting to receive data from the server which is can display in place, without reloading the screen. If we're called with C, the browser is expecting HTML content so it can load a new page. Sometimes, it's useful to know how we were called, so we can respond accordingly. =over 4 =item Parameters =over 4 =item none =back =item Return value =over 4 =item [ data | html ] Returns either the scalar C if we were called by C, or the scalar C if we were called by C. =back =back =back =cut sub event_type { my $self = shift; return $self->{'event_type'}; } 1; __END__ =head1 INTERACTING WITH THE BROWSER =head2 Sending Data to the Browser The following methods show you how you can send data from the server to the browser. B You only need a reference to a hash to send data to the browser. If the hashref containing all of our field data is called $field_data, then all we need to do in our code is: # Send the outgoing HTTP Header $OP->httpheader->send_outgoing(); # Populate the form fields with the data within our hashref print $o->serialize({ fields => $field_data }); B $field_data->{'fieldname'} = "data"; B $field_data->{'selectbox_name'} = [ [ "Example 1", "value_one" ], [ "Example 2", "value_two" ], [ "Example 3", "value_three" ], ]; This will set the text of a select box to the left column above, and the value of that text to the right column. In the case that you don't have two columns worth of data you wish to use, you can also do: $field_data->{'selectbox_name'} = [ [ "Example 1" ], [ "Example 2" ], [ "Example 3" ], ]; This makes both the text and value of the selectbox identical, and requires sending less data to the browser (which, of course, saves bandwidth). The above array of arrays will erase the current contents of the select list with the data in the array. To append a single item to the end of the select list, you can use the following: $field_data->{'selectbox_name'} = [ "Example 1", "value_one" ]; You can also use: $field_data->{'selectbox_name'} = [ "Example 1" ]; This will set both the text and value of the entry to "Example 1". Also, if autoclear is set to false (not the default), you can use the following to manually clear the contents of a select list: $field->{'selectbox_name'} = [ "" ]; To select (highlight) an item in an existing list of elements, you can send a select list a single string like so: $field->{'selectbox_name'} = "optionvalue"; Which ever item in the select list has the value C will become highlighted. For more information on how to add data to a select box without erasing the current contents, be sure to check out the C setting. B $field_data->{'checkbox_name'} = "boolean"; To uncheck a checkbox, set value to C, which can be any of: =over 4 =over 4 =item * false | False | FALSE =item * unchecked =item * any number less then 1 =back =back Setting the value to anything other then the above will be interpreted as C, and will cause the checkbox to be checked. B $field_data->{'radiobtn_name'} = "radiobtn_value"; radiobtn_value is the value in the "value=" tag of the radio button. Radio buttons can only be selected, they cannot be directly unselected. The only way to unselect a radio button is to select a different radio button in that group. B $html_data->{'id_tagname'} = "

New HTML Code

"; This inserts the code "

New HTML Code

" inside the tag with the id attribute labeled 'id_tagname'. This replaces any text or code that may have originally existed within that tag. Updating HTML does not work in Netscape 4.x, as it has a rather odd DOM. It is capable of working if someone felt like coding it, let me know if you're interested :-) B You can give the focus to any form element within the browser simply by saying: $o->serialize({ focus => "fieldname" }); B You can easily send JavaScript to the browser, allowing you to call JavaScript functions, access JavaScript variables, and even create new functions -- all from the server. The following calls the JavaScript 'alert' function: $o->serialize({ javascript => "alert('Hello!');" }); The next example calls the hypothetical javascript function 'myfunction', using C and C as arguments to that function: $o->serialize({ javascript => "myfunction(param1, param2);" }); You can send any JavaScript you want, but make sure it's properly formated code. OpenThought does not validate whether or not your JavaScript syntax is correct, the browser will be your judge! If something isn't working, pull up your browser's JavaScript console, it may provide you with some insight as to what isn't working properly. You do not need to include script tags. B There are plenty of cases where it may be desirable load a new page within your content frame. Loading a new page is quite simple, and can be initiated from the server, or from the browser. Here is an example of how you might tell the browser, from the server, to load a new URL: $o->serialize({ url => '/OpenThought/newurl.pl' }); This function will have the browser call the perl script 'newurl.pl'. It's then up to newurl.pl to deliver some sort of content back to the browser. Although you are loading a new page, the session is preserved across this call since only the content frame is being updated. You may also load a new page from the browser when a user clicks a button or link. See the 'FetchHtml()' function in L below. B You can send the results of a database search directly to the browser, and have the data from the results put into their respective fields. You only need one thing in order for this to work -- the field names in your database need to match your field names in the HTML. For example: my $sql = "SELECT name, address, phone, age, married " . "FROM sometable WHERE name="Tim Toady"; my $sth = $dbh->prepare($sql); $sth->execute; $field_data = $sth->fetchrow_hashref; In this case, lets say we have 'name', 'address', 'phone', and 'age' as text fields in our HTML, and 'married' is a checkbox. As soon as we send $field_data to the browser, these fields (which must exist) will all be filled in with the appropriate data. This also works for select lists: my $sql = "SELECT name, ssn FROM sometable"; my $sth = $dbh->prepare($sql); $sth->execute; $field_data->{'people'} = $sth->fetchall_arrayref; This selects the name and social security number from everyone in the table, and will allow us to use it to populate a select list named 'people'. =head2 Sending Data to the Server You can send data from the browser to the server anytime an event occurs. Events are often generated by clicking buttons or links. JavaScript functions like onMouseOver, onClick, onChange, etc.. they can all allow you to cash in on an event, and you can take advantage of them to send data to the server at that time. The two JavaScript functions available to you for communicating with the server are B and B. Their usage and parameters are identical, but they perform very different functions. B is used when you want to interact with the server, optionally update the content frame, all without reloading the page. The function B, however, does reload the page. It's sole purpose is to fetch new HTML content for your content screen, replacing your existing HTML with the content it is given. This is a fancy way of saying it just loads a new page. The following are some examples of how you might use these two functions. In any of the following situations, the two functions are interchangable. It all just depends on what you want to happen. B