Oracle Web Applications: PL/SQL Developer's Introduction

Oracle Web Applications: PL/SQL Developer's IntroductionSearch this book
Previous: 8.1 Designing a Web ApplicationChapter 8
Developing Applications
Next: 8.3 Example 2: A Discussion Forum
 

8.2 Example 1: An Anonymous Survey

Our first application will let users fill out anonymous surveys using a web browser. Surveys and opinion polls are some of the most common web applications, and usually consist of a list of questions. Users respond by selecting an answer from a small list of options. Most systems also have an option that lets users see the tabulated results for each survey.

This section walks through a simple process you can use to create a generic survey system. Our first step is to design a storyboard to define each screen in the system. This sketch helps us in the next step: designing a generic data model that we can use to construct each page. Our last step is to actually code the system.

8.2.1 Storyboard

In a typical survey application, the first screen presents a list of all available surveys. There are usually two options for each survey: to answer it or to view its tabulated results. If the user decides to answer a survey, she's presented with a bunch of questions and a corresponding list of possible answers. She then answers the questions and presses "Submit" to save the responses in a database table. If the user chooses to view the results of a survey, she's presented with a table summarizing all the previous responses. Figure 8.2 is a simple storyboard that captures these functions.

Figure 8.2: The storyboard for the anonymous survey

Figure 8.2

8.2.2 Data Model

We can use the storyboard to design a data model. The first storyboard screen tells us that we need some sort of table to hold the survey list. The screen used to respond to a survey suggests three more tables. The first table holds the text of each question, the second holds the possible answers for each question, and the third holds the actual user responses. The "view results" screen queries these tables.

Figure 8.3 shows a data model that uses these four tables. The SURVEY table contains information about the survey itself, such as its name, description, and the date range during which it is available. The rows in QUESTIONS represent the individual questions on a survey. ANSWERS defines the list of valid responses for each question. The final table, RESPONSES, holds the actual responses given by the respondents.

Figure 8.3: A data design for the anonymous survey

Figure 8.3

8.2.3 Implementation Notes

Since the survey application is a fairly simple system, we can implement it using a single package, which we'll call EMP_SURVEY. We'll store all the application objects, including code and tables, in a schema named SURVEY.

8.2.4 The EMP_SURVEY Package

Now that we've got the basic screen layout and data model, we're finally ready to develop the actual package. Table 8.1 shows the five procedures contained in EMP_SURVEY.


Table 8.1: The EMP_SURVEY Package

Procedure

Parameters

Description

display_survey_list

None

Generates an HTML list of available surveys, with descriptions.

answer_survey

i_survey_id IN VARCHAR2

Creates the HTML form that allows a user to respond to the survey.

process_survey

question IN response_array

DEFAULT emp_survey.no_

response

response IN response_array

DEFAULT emp_survey.no_

response

Inserts the user's answers into the RESPONSES table.

view_results

i_survey_id IN VARCHAR2

Prints each question in the survey. Together, view_results and print_answers tabulate the results for the survey.

print_answers

i_question_id IN VARCHAR2

Prints the corresponding summary information for a question. Together, view_results and print_answers tabulate the results for the survey.

Like all packages, EMP_SURVEY requires a specification and a body.

8.2.4.1 Specification

We can translate the storyboard almost directly into a package specification. The four boxes on the diagram, each of which represents a screen in the system, indicate that we'll need at least four procedures. The lines connecting the boxes give us the formal parameter list for each procedure. Not everything is on the storyboard, though; we'll also need to declare an array to hold the user's survey answers. We'll discuss how to use this when we write the program to create the form.

The code for the EMP_SURVEY package is as follows:

/* Formatted by PL/Formatter v.1.1.13 */
CREATE OR REPLACE PACKAGE emp_survey
AS
   
   /*
   || Datatype used to hold the responses to the survey
   */
   TYPE response_array IS TABLE OF VARCHAR2(20)
      INDEX BY BINARY_INTEGER;
   no_response response_array;
   
   -- Display the list of available surveys
   PROCEDURE display_survey_list;
   
   -- Display the form so that the user can respond
   PROCEDURE answer_survey (i_survey_id IN VARCHAR2);
   
   -- Save the responses
   PROCEDURE process_survey (
      question IN response_array DEFAULT emp_survey.no_response,
      response IN response_array DEFAULT emp_survey.no_response
      );
   
   -- Display the results of a survey
   PROCEDURE view_results (i_survey_id IN VARCHAR2);

END;

8.2.4.2 Body

With the basic design complete, all that's left is to fill in the pieces. In the survey system, this consists largely of building screens based on the information in our data model. Recalling the specification, we have to write five procedures for the package body: display_survey_list, answer_survey, process_survey, view_results, and print_answers.

8.2.4.2.1 The display_survey_list procedure

The first procedure, display_survey_list, generates the first page of the storyboard. This page presents an HTML list of all available surveys along with their descriptions. Each survey requires two hyperlinks: one to link to the "response" page and one to link to the "view results" page.

The following procedure uses the SURVEY table to create the list. For each row in the table, the procedure creates a new HTML list item based on the survey_name and survey_desc columns. It also creates the two hyperlinks that include the survey_id as a parameter in the query string:

PROCEDURE display_survey_list
IS
   -- Create cursor of all surveys that are active
   -- Done by testing the start_date and end_date columns
   -- of the SURVEY table
   CURSOR survey_cur
   IS
      SELECT *
        FROM survey
       WHERE SYSDATE BETWEEN start_date AND end_date;
   survey_rec survey_cur%ROWTYPE;
   rec_count NUMBER DEFAULT 0;

BEGIN
   HTP.print ('<title>Available Surveys</title>');
   HTP.print ('<body bgcolor=white>');
   HTP.print ('<ol>');   -- Begin an ordered list
   OPEN survey_cur;
   LOOP
      FETCH survey_cur INTO survey_rec;
      EXIT WHEN survey_cur%notfound;
      HTP.print ('<li>');
      HTP.bold (survey_rec.survey_name);
      HTP.print ('<br>');
      HTP.print ('<i>' || survey_rec.survey_desc || '</i><br>');
      -- Put an anchor to take the survey
      HTP.anchor (
         'emp_survey.answer_survey?i_survey_id=' ||
         survey_rec.survey_id,
         'Take the survey'
      );
      -- Put an anchor to view the results
      HTP.anchor (
         'emp_survey.view_results?i_survey_id=' ||
         survey_rec.survey_id,
         'View the results'
      );
      HTP.print ('<p></li>');
   END LOOP;
   HTP.print ('</ol>');
   CLOSE survey_cur;
END display_survey_list;	

Figure 8.4 shows the procedure's output.

Figure 8.4: The main screen of the survey application

Figure 8.4

8.2.4.2.2 The answer_survey procedure

The next procedure creates the HTML form that allows a user to respond to the survey. answer_survey has one parameter, survey_id, to indicate the survey the user selected on the preceding screen. answer_survey uses the rows of the QUESTION table for the specified survey to build corresponding rows in a two-column table. The first column in the table holds the question's text. The second column holds two form <input> elements. The first element, a hidden field containing the question's ID, is needed to associate the user's answer with a corresponding question. The second element, created by the OWA_UTIL.LISTPRINT procedure, is a selectable list of the question's possible answers as they appear in the ANSWERS table. When the user submits the form, both the hidden field and the response field are passed as parameter arrays to the process_survey procedure.

The code for this procedure is as follows:

PROCEDURE answer_survey (i_survey_id IN VARCHAR2)
IS
   CURSOR q_cur
   IS
      SELECT *
        FROM questions
       WHERE survey_id = i_survey_id
       ORDER BY question_num;
   
   q_rec q_cur%ROWTYPE;
   stmt VARCHAR2(500);

BEGIN
   HTP.title ('Survey');
   HTP.print ('<body bgcolor=white>');
   HTP.print ('<form action=emp_survey.process_survey>');
   HTP.print ('<table width=80%>');
   OPEN q_cur;
   LOOP
      FETCH q_cur INTO q_rec;
      EXIT WHEN q_cur%notfound;
      -- Start a new row in the HTML table
      HTP.print ('<tr>');
      -- Print the question in column 1
      HTP.print ('<th align=left valign=top>');
      HTP.print (q_rec.question_text);
      HTP.print ('</th>');
      -- Put the question_id and select list in column 2
      HTP.print ('<td>');
      -- Put the question_id in as a hidden field
      HTP.formhidden (
         cname => 'question',
         cvalue => q_rec.question_id
      );
      -- Use owa_util.listprint to build the LOV for the answer 
      stmt :=
         'select answer_id, answer_text, default_ans_flag ';
      stmt := stmt || ' from survey.answers where';
      stmt := stmt || ' question_id = ' || q_rec.question_id;
      stmt := stmt || ' order by answer_order';
      OWA_UTIL.listprint (stmt, 'response', 4, FALSE);
      HTP.print ('</td>');
      HTP.print ('</tr>');
   END LOOP;
   HTP.print ('</table>');
   HTP.formsubmit;
   CLOSE q_cur;
END;

Figure 8.5 shows the output of this procedure for a sample survey.

Figure 8.5: The form used to answer a survey

Figure 8.5

8.2.4.2.3 The process_survey procedure

The third procedure, process_survey, inserts the user's answers into the RESPONSES table. It accepts two response_array parameters (see the specification), question and response. For each question on the answer form, these parameters hold (respectively) the ID of the question as defined in QUESTIONS and the ID of the user's corresponding answer as defined in ANSWERS.

The procedure loops through each element, inserting a new row in the RESPONSES table for each question. The procedure calls the display_survey_list procedure to return the user to the main screen:

PROCEDURE process_survey (
   question IN response_array DEFAULT emp_survey.no_response,
   response IN response_array DEFAULT emp_survey.no_response
   )
IS
   
   count NUMBER DEFAULT 0;

BEGIN
   HTP.title ('Saving Responses');
   HTP.print ('<body bgcolor=white>');
   -- COUNT attribute available in PL/SQL 2.3
   FOR count IN 1 .. question.count
   LOOP
      INSERT INTO responses (response_id,question_id,answer_id)
         VALUES (
            response_seq.nextval,
            question (item_count),
            response (item_count)
          );
   END LOOP;
   HTP.print ('<h1>Values Saved</h1><hr>');
   display_survey_list;
END;

8.2.4.2.4 The view_results procedure

The last procedure in the application tabulates the results for the survey specified by the survey_id parameter. The procedure is split into two parts: a main procedure (view_results) to print each question and a secondary procedure (print_answers) to print a corresponding summary. The code for the main procedure is as follows:

PROCEDURE view_results (i_survey_id IN VARCHAR2)
IS
   CURSOR q_cur
   IS
      SELECT *
        FROM questions
       WHERE survey_id = i_survey_id
       ORDER BY question_num;
   q_rec q_cur%ROWTYPE;
BEGIN
   HTP.title ('Survey Responses');
   HTP.print ('<body bgcolor=white>');
   HTP.print ('<ol>');
   OPEN q_cur;
   LOOP
      FETCH q_cur INTO q_rec;
      EXIT WHEN q_cur%notfound;
      HTP.print ('<li>');
      HTP.print ('<b>' || q_rec.question_text || '</b>');
      HTP.print ('<br>');
      -- Call another procedure to generate the response summary
      print_answers (q_rec.question_id);
      HTP.print ('</td>');
   END LOOP;
   HTP.print ('</ol>');
   CLOSE q_cur;
END;

The secondary procedure, print_answers, generates the summary information for a question. The procedure opens a cursor based on the ANSWERS table to retrieve all the answers for a particular question. It then queries the RESPONSES table to calculate the percentages for that response. The code for the procedure is as follows:

PROCEDURE print_answers (i_question_id IN VARCHAR2)
IS
   CURSOR ans_cur
   IS
      SELECT *
        FROM answers
       WHERE question_id = i_question_id
       ORDER BY answer_order;
   ans_rec ans_cur%ROWTYPE;
   total_responses NUMBER;
   num_responses NUMBER;
BEGIN
   -- Fetch the total number of responses
   SELECT COUNT (*)
     INTO total_responses
     FROM responses
    WHERE question_id = i_question_id;
   HTP.print ('<table width=50%>');
   OPEN ans_cur;
   LOOP
      FETCH ans_cur INTO ans_rec;
      EXIT WHEN ans_cur%notfound;
      HTP.print ('<tr>');
      -- Print question text
      HTP.print ('<td nowrap>' ||
                 ans_rec.answer_text ||
                 '</td>');
      -- Fetch and print number of responses
      SELECT COUNT (*)
        INTO num_responses
        FROM responses
       WHERE answer_id = ans_rec.answer_id;
      HTP.print ('<td align=right>' ||
                 ROUND (
                    num_responses / total_responses * 100,
                    2
                 ) ||
                 '%</td>');
      HTP.print ('</tr>');
   END LOOP;
   HTP.print ('</table>');
   CLOSE ans_cur;
END;

Figure 8.6 shows the combined results of these procedures.

Figure 8.6: A sample summary

Figure 8.6

8.2.5 Security Privileges

Our last formal step is to make the package accessible to the user's web browser. Rather than creating a new PL/SQL agent for the SURVEY schema, we can make the package available to an existing agent's schema (in this case, WEBTEST). This makes the system more secure and reduces maintenance for the webmaster. Here are the steps to follow:

  1. Log in to the SURVEY schema using SQL*Plus.

  2. Grant EXECUTE privileges on the EMP_SURVEY package to the agent account (WEBTEST).

  3. Connect to the agent account (again, WEBTEST).

  4. Create a synonym called EMP_SURVEY for survey.emp_survey.

Figure 8.7 shows how these commands are used in SQL*Plus.

Figure 8.7: Using SQL*Plus to grant privileges to the PL/SQL agent

Figure 8.7

8.2.6 Summary

Developing the anonymous survey has taught us several things about web development. First, we have seen that a storyboard is a good place to start when faced with a new application. We can use the information on our diagram to define how the user will navigate, get a good idea of the database tables we'll need, and get a jump-start on defining the package specification. Second, this example has illustrated how to use parameter arrays to pass multiple field values, as well as how (and under what circumstances) you should use hidden fields. We'll expand these ideas further in the next sample application.


Previous: 8.1 Designing a Web ApplicationOracle Web Applications: PL/SQL Developer's IntroductionNext: 8.3 Example 2: A Discussion Forum
8.1 Designing a Web ApplicationBook Index8.3 Example 2: A Discussion Forum

The Oracle Library Navigation

Copyright (c) 2000 O'Reilly & Associates. All rights reserved.

Library Home Oracle PL/SQL Programming, 2nd. Ed. Guide to Oracle 8i Features Oracle Built-in Packages Advanced PL/SQL Programming with Packages Oracle Web Applications Oracle PL/SQL Language Pocket Reference Oracle PL/SQL Built-ins Pocket Reference