Storing User Preferences

only for RuBoard - do not distribute or recompile

In this section, we ll write an application, prefs.pl, that uses sessions to manage user profiles. The particular scenario involves management of page-display preferences, although for simplicity we ll limit the scope of these preferences to a block of text displayed in the pages generated by the application itself. At the end of this section, you ll find a few suggestions for other ways to use profiles by making a few changes to the concepts presented here.

The script prefs.pl is designed to keep track of page-display preferences stored in a session record: You can select a font size (small, medium, large), and text attributes (bold, italic). It presents a page that shows some sample text displayed using your current preferences, and a form that enables you to change those preferences. In addition, to demonstrate the fact that sometimes you want users to be able to delete their profiles, the application also provides a Log Out operation that causes the session containing the preferences to be destroyed.

The script also demonstrates how to move information in both directions between a form and a session record, with special attention to the fact that form elements may have non-scalar values. This enables you to store form values in a session record, and to initialize a form from the contents of the session.

The script begins with some code that checks for a cookie and opens a session. This is much like we ve done before, except that it assigns an explicit expiration date to the cookie to cause the browser to remember it across restarts. User profiles tend to be the sorts of things that have a long lifetime. This means the cookies associated with them also must be long-lived, so it s necessary to assign each session cookie an explicit expiration date to make sure the browser remembers it well into the future. (If we give a user a cookie that contains no explicit expiration value, it will be forgotten when the browser exits. That is undesirable for this application.)

 $sess_id = cookie ("sess_id");  # get value of sess_id cookie if it's present  $sess_ref = WebDB::Session->open (undef, $sess_id) if defined ($sess_id);  if (!defined ($sess_ref))       # no cookie or couldn't retrieve old session  {     defined ($sess_ref = WebDB::Session->open (undef, undef))          or error ("Could not create session: $WebDB::Session::errstr");      $cookie = cookie (-name => "sess_id",                          -value => $sess_ref->session_id (),                          -path => url (-absolute => 1),                          -expires => "+1y"); # expire in one year  } 

After opening the session, we check to see whether it needs to be initialized. The font size always has a value for an existing session, so we can assume that the session is new if size is undefined:

 if (!defined ($sess_ref->attr ("size")))  {     $sess_ref->attr ("size", "3");      # medium size      $sess_ref->attr ("style", undef);   # plain style  } 

The font size value is a simple scalar. The possible values we ll allow are 1, 3, and 5, where 3 is the value for medium. The text style can be empty or have up to two values, but at the moment we ll set it to undef to signify plain text (neither bold nor italic).

After the session is ready to go, we can use it to interpret the user s instructions:

  • If this is the first invocation, just display the sample text and preferences form.

  • If the user submitted new preferences, save them in the session record, and then display the sample text and the form, updated to reflect the modified preferences.

  • If the user wants to log out, destroy the session record and the cookie. This causes the preferences to be forgotten.

Each of these actions is handled by the dispatch code:

 my $choice = lc (param ("choice")); # get choice, lowercased  if ($choice eq "")                  # initial invocation  {     $page .= display_prefs_form ($sess_ref);    # display sample text and form      $sess_ref->close ();  }  elsif ($choice eq "submit")         # user submitted new preferences  {     process_form ($sess_ref);                   # modify preferences      $page .= display_prefs_form ($sess_ref);    # display sample text and form      $sess_ref->close ();  }  elsif ($choice eq "log out")        # destroy session and cookie  {     $sess_ref->delete ();      $page .= p ("The session has been terminated.");      # Create cookie that tells browser to destroy the one it's storing      $cookie = cookie (-name => "sess_id",                          -value => $sess_ref->session_id (),                          -path => url (-absolute => 1),                          -expires => "-1d");     # "expire yesterday"  }  else  {     error ("Logic error, unknown choice: $choice");  } 

Any HTML generated within the dispatch code is saved in a string, not printed immediately. That technique is one we ve used before, to defer output until after we know for sure whether a cookie needs to be sent in the headers that precede the page content:

 print header (-cookie => $cookie),          start_html (-title => "Prefs Demo", -bgcolor => "white"),          $page,          end_html (); 

display_prefs_form() displays the sample text and the form that enables you to change your preferences:

 sub display_prefs_form  { my $sess_ref = shift;  my (@style, $page, $sample);      # Generate some sample text      $sample = <<EOF;  Now is the time for all good men to come to the aid of their  quick brown foxes that jump over lazy sleeping dogs.  EOF      # Apply the current preferences to the sample text.  The font is      # a single scalar value.  The style might have no value, a single      # value, or multiple values.  In the latter case it must be accessed      # by reference rather than as a scalar.      $sample = font ({-size => $sess_ref->attr ("size")}, $sample);      if (!defined ($sess_ref->attr ("style")))           # undef      {         @style = ();                                    # empty list      }      elsif (ref ($sess_ref->attr ("style")) ne "ARRAY")  # bold OR italic      {         @style = ( $sess_ref->attr ("style") );         # list of one element      }      else                                                # bold AND italic      {         @style = @{$sess_ref->attr ("style")};          # list of two elements      }      $sample = b ($sample) if grep (/^bold$/, @style);      $sample = i ($sample) if grep (/^italic$/, @style);      $page .= p ("Here is some text displayed using your current preferences:")              . hr () . p ($sample) . hr ();      # Extract preferences from session record and stuff them into the      # parameter environment so they initialize the fields in the      # preference-changing form.      foreach my $name ("size", "style")      {         param (-name => $name, -value => $sess_ref->attr ($name));      }      $page .= start_form (-action => url ())              . p (:"To modify your preferences, make the changes below.\n"                  . "Your choices will be applied on the next page\n"                  . "when you select the Submit button.")              . p ("Font size:")              . radio_group (-name => "size",                              -values => ["1", "3", "5"],                              -labels => {                                         "1" => "small",                                          "3" => "medium",                                          "5" => "large"                                          }                              )              . p ("Text style:")              . checkbox_group (-name => "style", -values => ["bold", "italic"])              . br () . br ()              . submit (-name => "choice", -value => "Submit")              . " "              . submit (-name => "choice", -value => "Log Out")              . end_form ();      return ($page);  } 

display_prefs_form() uses the session record two ways: to determine the format of the sample text and as a source of values for initializing the form elements. The font is easy to pull from the session record and apply to the sample text, because it always has a single scalar value. Handling the style value is trickier. Either or both of the bold and italic values can be enabled, so the style value in the session record might be undef, a scalar, or an array reference. The value is extracted and converted to an array no matter what form it takes in the session, so that whatever style attributes are present can be ascertained easily using grep(). The code shown in display_prefs_form() explicitly sets the @style array to the empty list for the undef case, to make the action for the no style value case explicit. That s not strictly necessary, because @style is initialized to the empty list by default. Here s a simpler way to convert the style values to an array:

 if (ref ($sess_ref->attr ("style")) eq "ARRAY")  {     @style = @{$sess_ref->attr ("style")};  }  elsif (defined ($sess_ref->attr ("style")))  {     @style = ( $sess_ref->attr ("style") );  } 

After displaying the text, display_prefs_form() extracts the preferences from the session record again and loads them into the script s parameter space. The purpose of this is to exploit CGI.pm s sticky form behavior that allows parameter values in the environment to be used to initialize form elements. CGI.pm correctly handles parameter values whether they are scalars or references, so we don t have to do any special messing around in the parameter initialization loop.

Each time the user submits new preference selections, process_form() examines the form s field values and saves them in the session record:

 sub process_form  { my $sess_ref = shift;      # Extract form values and save them to the session record      foreach my $name ("size", "style")      {         my @val = param ($name);          # store multiple-valued elements by reference,          # and empty or single-valued elements as scalars          $sess_ref->attr ($name, (@val > 1 ? [ @@val ] :: shift (@val)));      }  } 

The primary issue here is that arrays cannot be stored directly in a session record. They must be stored by reference, so the loop determines whether any given field value is a scalar or an array, and then stores a scalar or an array reference accordingly. If you select both bold and italic style values, param() returns an array of two values for the style parameter. The code in process_form() converts this to an array reference and stores that in the session record.

Try out prefs.pl by invoking it and changing your preferences a few times to verify that it displays the sample text appropriately. You can see from this the immediate effect of modifications you submit on the contents of the session record. You also can test the script s behavior in other ways:

  • Invoke prefs.pl and submit some changes to your preferences. Then close the browser window and reinvoke the script. Your changed preferences should be remembered.

  • Quit and restart your browser, and then reinvoke prefs.pl. Your preferences still should be remembered. This happens because we assigned an expiration date to the session cookie. Had we not done so, the browser would have deleted the cookie and the script would have started a new session for you, initialized to the default display preferences. (The original session would have become orphaned as well.)

  • If you select Log Out, and then invoke the script again, you should get a new page showing the sample text and form displayed using the default preferences. This happens because Log Out destroys your session, so the next invocation needs to generate a new one.

Other Applications for User Profiles

Sessions that contain profile or preference information can be used for many purposes, including the following:

  • Create a profile to remember name, address, and phone number when a user visits your online store. Then use the information to automatically initialize form fields when the user visits your store later. This makes it easier for your customers to order products from your store.

  • Let your visitors tell you which kinds of news items they re most interested in seeing. Use this interests profile on your current news page to display headlines only for items in those categories they want to see. The same idea applies to auction sites; you can provide information about new items in categories a user happens to be interested in.

  • For an online help desk, remember the last several documents a user asked to look at. When the user next visits, present a short menu allowing those documents to be recalled easily, on the assumption that they are still the ones of most immediate interest.

Remember that prefs.pl generates a cookie that specifically applies only to itself by using the script s full pathname as the path value. To manage profiles that apply to a wider range of pages, you ll need to generate a cookie with a less-specific path value, such as "/" to create a site-wide cookie, and you should pick a more-specific cookie name that is less generic than sess_id !

only for RuBoard - do not distribute or recompile


MySQL and Perl for the Web
MySQL and Perl for the Web
ISBN: 0735710546
EAN: 2147483647
Year: 2005
Pages: 77
Authors: Paul DuBois

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net