Oracle APEX 23 Course For Beginners

Oracle APEX 23 Course For Beginners
Oracle APEX 23 Course For Beginners

Saturday, 22 October 2022

Using HTML in PL/SQL

What is PL/SQL?



PL/SQL stands for Procedural Language/Structured Query Language. It is a programming language that uses detailed sequential instructions to process data. A PL/SQL program combines SQL commands (such as Select and Update) with procedural commands for tasks, such as manipulating variable values, evaluating IF/THEN logic structure, and creating loop structures that repeat instructions multiple times until the condition satisfies the defined criteria. PL/SQL was expressly designed for this purpose.







The structure of a PL/SQL program block is:

Declare
   Variable declaration
Begin
   Program statements
Exception
   Error-handling statements
End;

PL/SQL program variables are declared in the program’s declaration section using the data declaration syntax shown earlier. The beginning of the declaration section is marked with the reserved word DECLARE. You can declare multiple variables in the declaration section. The body of a PL/SQL block consists of program statements, which can be assigned statements, conditional statements, looping statements, and so on, that lie between the BEGIN and EXCEPTION statements. The exception section contains program statements for error handling. Finally, PL/SQL programs end with the END; statement.

In a PL/SQL program block, the DECLARE and EXCEPTION sections are optional. If there are no variables to declare, you can omit the DECLARE section and start the program with the BEGIN command.

Using HTML and PL/SQL in Oracle Application Express


Oracle Application Express installs with your Oracle database and is comprised of data in tables and PL/SQL code. Whether you are running the Oracle Application Express development environment or an application you built using Oracle Application Express, the process is the same. Your browser sends a URL request that is translated into the appropriate Oracle Application Express PL/SQL call. After the database processes the PL/SQL, the results are relayed back to your browser as HTML. This cycle happens each time you either request or submit a page.

Specific HTML content not handled by Oracle Application Express forms, reports, and charts are generated using the PL/SQL region type. You can use PL/SQL to have more control over dynamically generated HTML within a region.

htp and htf Packages:

htp  (hypertext procedures) and htf (hypertext functions) packages generate HTML tags. These packages translate PL/SQL into HTML that is understood by a Web browser. For instance, the htp.anchor procedure generates the HTML anchor tag, <A>. The following PL/SQL block generates a simple HTML document:

Create or replace procedure hello AS
BEGIN
    htp.htmlopen;                      -- generates <HTML>
    htp.headopen;                      -- generates <HEAD>
    htp.title('Hello');                -- generates <TITLE>Hello</TITLE>
    htp.headclose;                     -- generates </HEAD>
    htp.bodyopen;                      -- generates <BODY>
    htp.header(1, 'Hello');            -- generates <H1>Hello</H1>
    htp.bodyclose;                     -- generates </BODY>
    htp.htmlclose;                     -- generates </HTML>
END;

Oracle provided the htp.p tag to allow you to override any PL/SQL-HTML procedure or even a tag that did not exist. If a developer wishes to use a new HTML tag or simply is unaware of the PL/SQL analog to the html tag, he/she can use the htp.p procedure.

For every htp procedure that generates HTML tags, there is a corresponding htf function with identical parameters. The function versions do not directly generate output on your web page. Instead, they pass their output as return values to the statements that invoked them. See the use of these packages on page 113 of the book. 

 

htp.p / htp.print:

Generates the specified parameter as a string.

htp.p(‘<p>’):

Indicates that the text that comes after the tag is to be formatted as a paragraph.

<strong>Customer:</strong>:

Renders the text they surround in bold.

htf.escape_sc:

Escape_sc is a function that replaces characters that have special meaning in HTML with their escape sequence.

converts occurrence of & to &amp
converts occurrence of “ to &quot
converts occurrence of < to &lt
converts occurrence of > to &gt

To prevent XSS (Cross Site Scripting) attacks, you must call SYS.HTF.ESCAPE_SC to prevent embedded javascript code from being executed when you inject the string into the HTML page.

sys.htf.escape_sc:

The SYS prefix is used to signify Oracle’s SYS schema. The HTP and HTF packages normally exist in the SYS schema and APEX relies on them.

Cursor FOR LOOP Statement


The cursor FOR LOOP statement implicitly declares its loop index as a record variable of the row type that a specified cursor returns, and then opens a cursor. With each iteration, the cursor FOR LOOP statement fetches a row from the result set into the record. When there are no more rows to fetch, the cursor FOR LOOP statement closes the cursor. The cursor also closes if a statement inside the loop transfers control outside the loop or raises an exception.

The cursor FOR LOOP statement lets you run a SELECT statement and then immediately loop through the rows of the result set. This statement can use either an implicit or explicit cursor.

If you use the SELECT statement only in the cursor FOR LOOP statement, then specify the SELECT statement inside the cursor FOR LOOP statement, as in Example A. This form of the cursor FOR LOOP statement uses an implicit cursor and is called an implicit cursor FOR LOOP statement. Because the implicit cursor is internal to the statement, you cannot reference it with the name SQL.

Example A - Implicit Cursor FOR LOOP Statement


BEGIN
  FOR item IN (
    SELECT last_name, job_id
    FROM employees
    WHERE job_id LIKE '%CLERK%' AND manager_id > 120
    ORDER BY last_name
  )
  LOOP
    DBMS_OUTPUT.PUT_LINE ('Name = ' || item.last_name || ', Job = ' || item.job_id);
  END LOOP;
END;
/

Result:

Name = Atkinson, Job = ST_CLERK
Name = Bell, Job = SH_CLERK
Name = Bissot, Job = ST_CLERK
...
Name = Walsh, Job = SH_CLERK

If you use the SELECT statement multiple times in the same PL/SQL unit, then define an explicit cursor for it and specify that cursor in the cursor FOR LOOP statement, as in Example B. This form of the cursor FOR LOOP statement is called an explicit cursor FOR LOOP statement. You can use the same explicit cursor elsewhere in the same PL/SQL unit.

Example B - Explicit Cursor FOR LOOP Statement


DECLARE
  CURSOR c1 IS
    SELECT last_name, job_id FROM employees
    WHERE job_id LIKE '%CLERK%' AND manager_id > 120
    ORDER BY last_name;
BEGIN
  FOR item IN c1
  LOOP
    DBMS_OUTPUT.PUT_LINE ('Name = ' || item.last_name || ', Job = ' || item.job_id);
  END LOOP;
END;
/

Result:

Name = Atkinson, Job = ST_CLERK
Name = Bell, Job = SH_CLERK
Name = Bissot, Job = ST_CLERK
...
Name = Walsh, Job = SH_CLERK

From page number 211 to 214 of my book “Create Rapid Low-Code Web Applications in Oracle APEX”, you can see how to use HTML in PL/SQL code. In this blog post, I’ll try to explain that code.

Display Customer Information

This procedure fetches information about the selected customer and presents it in a desirable format (as shown under) using the style named demoCustomerInfo.









Declare
This is the parent PL/SQL block. A nested block is also used under Display Current Orders section. Read about PL/SQL block in What is PL/SQL at the beginning of this post.

l_customer_id varchar2(30) := :P11_CUSTOMER_ID;
Assigns the customer ID from Page 11 to the variable l_customer_id. This variable is used ahead in a SQL statement to fetch details of the selected customer. In PL/SQL, := is called the assignment operator. The value of the variable that is being assigned the new value is placed on the left side of the assignment operator, and the value is placed on the right side of the operator.

:P11_CUSTOMER_ID is called a bind variable. Bind variables are substitution variables that are used in place of literals. You can use bind variables syntax anywhere in Oracle Application Express where you are using SQL or PL/SQL to reference the session state of a specified item. For example:

SELECT * FROM employees WHERE last_name like '%' || :SEARCH_STRING || '%'

In this example, the search string is a page item. If the region type is defined as SQL Query, you can reference the value using standard SQL bind variable syntax. Using bind variables ensures that parsed representations of SQL queries are reused by the database, optimizing memory usage by the server.

The use of bind variables is encouraged in APEX. Bind variables help you protect your Oracle APEX application from SQL injection attacks. Bind variables work in much the same way as passing data to a stored procedure. Bind variables automatically treat all input data as “flat” data and never mistake it for SQL code. Besides the prevention of SQL injection attacks, there are other performance-related benefits to its use.

You declare a field item as a bind variable by prefixing a colon character (:), like this:
:P11_CUSTOMER_OPTIONS

When using bind variable syntax, remember the following rules:
  • Bind variable names must correspond to an item name. 
  • Bind variable names are not case-sensitive.
  • Bind variable names cannot be longer than 30 characters (that is, they must be a valid Oracle identifier).
Although page item and application item names can be up to 255 characters, if you intend to use an application item within SQL using bind variable syntax, the item name must be 30 characters or less.

begin
Read What is PL/SQL at the beginning of this post.

if :P11_CUSTOMER_OPTIONS = 'NEW' then
l_customer_id := :P18_CUSTOMER_ID;
end if;


P11_CUSTOMER_OPTIONS is a radio group item that is created in section 6.6.6. This item has two values “Existing Customer” and “New Customer” as shown in the following figure. In the above conditional code, if the user opts to create a new customer, then the new customer ID is moved from Page 18 to the variable l_customer_id (a new customer record is generated through Page 18).

for x in (select * from demo_customers where customer_id = l_customer_id)
loop
Initiated the FOR loop to locate and fetch the record of the selected customer in demo_customers table.

htp.p('<div class="demoCustomerInfo">');
The <div> tag defines a division or a section in an HTML document. This is the opening tag that references demoCustomerInfo class in CSS to format the following elements. Click to see how Cascading Style Sheet is used in Oracle Application Express.

htp.p('<strong>Customer:</strong>');
Displays the label “Customer:” in bold.

htp.p('<p>');
The paragraph opening tag.

htp.p(htf.escape_sc(x.cust_first_name) || ' ' || htf.escape_sc(x.cust_last_name) || '<br />');
Concatenates customers’ first and last names using the || symbol. The <br /> is an XHTML tag and inserts a single line break.

htp.p(sys.htf.escape_sc(x.cust_street_address1) || '<br />');
Show customer’s first address on a new line.

if x.cust_street_address2 is not null then
  sys.htp.p(sys.htf.escape_sc(x.cust_street_address2) || '<br />');
end if;
It’s a condition to check whether the customer’s second address is not null. If it’s not, print it on a new line.

htp.p(sys.htf.escape_sc(x.cust_city) || ', ' || x.cust_state || '  ' || htf.escape_sc(x.cust_postal_code));
Displays city, state and postal code data on the same row separating each other with a comma and a blank space.

htp.p('</p>');
The paragraph end tag.

end loop;
The loop terminates here after selecting the customer with relevant information.

htp.p('</div>');
The div tag terminates here. Following is the output you see for the above code.


 

 

 

 

 

Display Products

Here you create a section on your web page to display all products along with their prices and include an option through which users can add products to their cart. 



















htp.p('<div class="demoProducts" >');
Creates a division based on the class demoProducts.

htp.p('<strong class="demoTitle">P R O D U C T S</strong>');
Displays the section title in bold on top of column headings using the sub-class demoTitle.

htp.p('<table width="100%" cellspacing="0" cellpadding="0" border="0">
Here you are initiating to draw a table. The <table> tag defines an HTML table. An HTML table consists of the <table> element and one or more <tr>, <th>, and <td> elements. The <tr> element defines a table row, the <th> element defines a table header, and the <td> element defines a table cell.

The Width attribute specifies the width of a table. In this instance, we’re setting the width in percent of the surrounding element. In the style sheet, we set the area for demoProducts to 58% and for demoCurrentOrder to 38% div.demoProducts{clear:both;float:left;width:58%;margin:16px 0 0 0;padding:0 8px 0 0;}. This division meant that the elements in the demoProducts class would consume 58% of the overall region whereas demoCurrentOrder elements would share 38%. If you set the table’s width to 50%, the product section would look like the following figure. Setting 50% width instructs the browser to use half of the demoProducts area we specified in the CSS rule. 











<thead>
<tr><th class="left">Product</th><th>Price</th><th>Add to Cart</th></tr>
</thead>
The <thead> tag is used to group header content in an HTML table. The <thead> element is used in conjunction with the <tbody> and <tfoot> elements to specify each part of a table (header, body, footer). The <tr> tag creates a row for the column heading. The three <th> tags specify the headings i.e. Product, Price, and Add to Cart. The class=”left” declaration is included that points towards the CSS rule div.demoProducts .left{text-align:left;} to align the title “Product” to the left. 

<tbody>');
The <tbody> tag is used to group the body content in an HTML table.

for c1 in (select product_id, '<a href="javascript:productPopup(''' || product_id || ''');">' || sys.htf.escape_sc(product_name) || '</a>' product_name,  list_price, 'Add to Cart' add_to_order
from demo_product_info
where product_avail = 'Y'
order by product_name) loop
This FOR loop fetches Product ID, Product Name, and List Price columns from the products table. To display Add > link in the table, we appended a column aliased add_to_order and populated all rows with a constant value 'Add to Cart'. For information on FOR LOOP see Cursor FOR LOOP Statement section earlier in this post.

The <a href="javascript:productPopup(''' || product_id || ''');">' || sys.htf.escape_sc(product_name) || '</a> tag defines an anchor. An anchor can be used in two ways:

1.      To create a link to another document, by using the href attribute
2.      To create a bookmark inside a document, by using the name attribute

The <a> element is usually referred to as a link or a hyperlink. The most important attribute of the <a> element is the href attribute, which specifies the URL of the page the link goes to. In the above scenario, the first portion <a href="javascript:productPopup(''' || product_id || ''');"> calls a JavaScript function named productPopup by passing the product_id value. The second portion, || sys.htf.escape_sc(product_name) || '</a> concatenates product name to the former one to display product name as a link. The function, in turn, opens a popup to display details based on the product ID for the clicked product.


sys.htp.p('<tr><td class="left">'||c1.product_name||'</td><td>'||trim(to_char(c1.list_price,'999G999G990D00'))||
        '</td><td><a href="f?p=&APP_ID.:12:'||:app_session||':ADD:::P12_PRODUCT_ID:'||
        c1.product_id||'" class="demoAddtoCart">Add &gt;</a></td></tr>');
The c1 prefix in front of column names, points to the FOR LOOP cursor. The TRIM function in the expression trim(to_char(c1.list_price,'999G999G990D00')) takes a character expression and returns that expression with leading and/or trailing pad characters removed. This expression initially formats the list price column to add a thousand separator and decimal places. Next, it converts the numeric price value to text expression using the TO_CHAR function and finally applies the TRIM function. The TO_CHAR function converts a DATETIME, number, or NTEXT expression to a TEXT expression in a specified format. The tables that follow list the elements of a number format model that we used above and provide some examples.

Element
Example
Description
0
0999
Returns leading zeros.

9990
Returns trailing zeros.
9
9999
Returns value with the specified number of digits with a leading space if positive or with a leading minus if negative. Leading zeros are blank, except for a zero value, which returns a zero for the integer part of the fixed-point number.
D
99D99
Returns in the specified position the decimal character, which is the current value of the NLS_NUMERIC_CHARACTER parameter. The default is a period (.).
G
9G999
Returns in the specified position the group separator (the current value of the NLS_NUMERIC_CHARACTER parameter. The default is comma [,]). You can specify multiple group separators in a number format model.

Use the following SQL statement to check the current value for decimal and group separator characters:

select value from v$nls_parameters where parameter='NLS_NUMERIC_CHARACTERS';



The expression <a href="f?p=&APP_ID.:12:'||:app_session||':ADD:::P12_PRODUCT_ID:'||c1.product_id||'" class="demoAddtoCart">Add &gt;</a> creates a link with an ADD request. The value of REQUEST is the name of the button the user clicks or the name of the tab the user selects. For example, suppose you have a button with the name CHANGE, and the label Apply Change. When a user clicks the button, the value of REQUEST is CHANGE. In section 6.10.7 on page number 117 of the book, we created the following process named Add product to the order collection.

for x in (select * from demo_product_info where product_id = :P12_PRODUCT_ID)
loop
  apex_collection.add_member(p_collection_name => 'ORDER',
    p_c001 => x.product_id,
    p_c002 => x.product_name,
    p_c003 => x.list_price,
    p_c004 => 1);
end loop;

During the process creation, we selected Request=Expression 1 in Condition Type and entered ADD in Expression 1. The ADD request in the above <a> tag is referencing the same expression. When a user clicks the ADD> link on the web page, the URL sends the ADD request to the above process along with the product’s ID. In return, the process adds the product to the selected item list. The &gt is used to show the” >” symbol after the ADD label.

end loop;
End of FOR loop.

htp.p('</tbody></table>');
Table and table body closing tags.

htp.p('</div>');
The Closing div tag.


Display Current Order

htp.p('<div class="demoCurrentOrder" >');
Defines the <div> tag and utilizes the demoCurrentOrder class for this section.

htp.p('<strong class="demoTitle">Current Order</strong>');
Displays section heading as follows.




Declare
This is the nested or child block. To nest a block means to embed one or more PL/SQL blocks inside another PL/SQL block to allow you to have better control over the program’s execution.

c number := 0; t number := 0;
Declared two numeric counter variables and initialized them with zero. The variable c is used to evaluate whether any product is selected in the current order while the variable t stores the total value.

Begin
-- loop over cart values
for c1 in (select c001 pid, c002 i, to_number(c003) p, count(c002) q, sum(c003) ep,  'Remove' remove
from apex_collections
where collection_name = 'ORDER'
group by c001, c002, c003
order by c001)
loop
The collection enables you to temporarily capture one or more non-scalar values. You can use collections to store rows and columns currently in session state so they can be accessed, manipulated, or processed during a user’s specific session. You can think of a collection as a bucket in which you temporarily store and name rows of information.

Every collection contains a named list of data elements (or members) which can have up to 50 character attributes (varchar2 (4000)), 5 numbers, 5 dates, 1 XML type, 1 BLOB, and 1 CLOB attribute. You insert, update, and delete collection information using the PL/SQL API APEX_COLLECTION.

When you create a new collection, you must give it a name that cannot exceed 255 characters. Note that collection names are not case-sensitive and will be converted to uppercase. Once the collection is named, you can access the values (members of a collection) in the collection by running a SQL query against the database view APEX_COLLECTIONS.

The APEX_COLLECTIONS view has the following definition:

COLLECTION_NAME         NOT NULL VARCHAR2(255)
SEQ_ID                                  NOT NULL NUMBER
C001                                       VARCHAR2(4000)
C002                                       VARCHAR2(4000)
C003                                       VARCHAR2(4000)  
C004                                       VARCHAR2(4000)  
C005                                       VARCHAR2(4000) 
...
C050                                       VARCHAR2(4000)
N001                                       NUMBER
N002                                       NUMBER
N003                                       NUMBER
N004                                       NUMBER
N005                                       NUMBER    
CLOB001                               CLOB
BLOB001                               BLOB 
XMLTYPE001                        XMLTYPE
MD5_ORIGINAL                  VARCHAR2(4000) 

Use the APEX_COLLECTIONS view in an application just as you would use any other table or view in an application, for example:

Select c001, c002, c003, n001, clob001 
From APEX_collections 
Where collection_name = 'DEPARTMENTS'

Note that you can't read apex_collection using external tools. A collection is related to an APEX session and not available outside of it. However, using the following statement you can query WWV_FLOW_COLLECTION_MEMBERS$. It is in this table that APEX stores its collection data.

Select c001,c002,c003,c004 from APEX_040000.wwv_flow_collection_members$;

The CREATE_OR_TRUNCATE_COLLECTION method creates a new collection if the named collection does not exist. If the named collection already exists, this method truncates it. Truncating a collection empties it, but leaves it in place.

On page number 102 - section 6.6.7, we created a process named Create or Truncate Order Collection under the page rendering section and used the following statement to create a collection named ORDER:

apex_collection.create_or_truncate_collection (p_collection_name => 'ORDER');

In the above loop, we’re selecting records from the same ORDER collection. Columns from apex_collections in the select statement correspond to:

Column
Corresponds To
C001 – pid
Product ID
C002 – i
Product Name
C003 – p
List Price
C004
Quantity

htp.p('<div class="sideCartItem"><a href="'||
    'f?p=&APP_ID.:12:&SESSION.:REMOVE:::P12_PRODUCT_ID:'||c1.pid||
    '"><img src="#IMAGE_PREFIX#delete.gif" alt="Remove from cart" title="Remove from cart" /></a>&nbsp;&nbsp;
The above <a> tag creates a link with a REMOVE request. This time it uses product ID from the collection. In section 6.10.7 on page number 117, there is a process named Remove product from the order collection (as shown below) where the request expression is set to REMOVE.

for x in (select seq_id, c001 from apex_collections
where collection_name = 'ORDER' and c001 = :P12_PRODUCT_ID)
loop
   apex_collection.delete_member(p_collection_name => 'ORDER', p_seq => x.seq_id);
end loop;

In HTML, images are defined with the <img> tag. The <img> tag has no closing tag. To display an image on a page, you need to use the src attribute. Src stands for "source". The value of the src attribute is the URL of the image you want to display.

Syntax for defining an image:
<img src="url" alt="some_text"/>

The URL points to the location where the image is stored. The value of IMAGE_PREFIX determines the virtual path the Web server uses to point to the images directory distributed with Oracle Application Express. We used “delete.gif” which is displayed in front of the product name. The required alt attribute specifies an alternate text for an image if the image cannot be displayed.

When a user clicks the remove link [X] in the Current Order section, the above URL sends the REMOVE request to the above process along with the product ID. The DELETE_MEMBER procedure deletes a specified member from a given named collection using the p_seq => x.seq_id parameter which is the sequence ID of the collection member to be deleted.

<a href="javascript:productPopup('''||c1.pid||''');">'||c1.i||'</a>
The above link allows you to see product details in the Current Order section as well and calls the popup through the JavaScript function. c1.pid is the product ID passed on to the function. c1.i is concatenated in the URL to display the product name as a link.  

<span>'||trim(to_char(c1.p,'$999G999G999D00'))||'</span>
<span>Quantity: '||c1.q||'</span>
<span class="subtotal">Subtotal: '||trim(to_char(c1.ep,'$999G999G999D00'))||'</span>
The above three lines of code display price, quantity, and sub-total of the selected product in the Current Order section as shown below:





</div>');
The ending div tag.

c := c + 1;
This counter increments the value of c with 1 at the end of each loop. The variable c is used to calculate the number of items selected in the current order.

t := t + c1.ep;
Similar to the variable c, t is also incremented with the sum of the list price (c1.ep) to calculate the total order value.

end loop;

if c > 0 then
    sys.htp.p('<div class="sideCartTotal">
    <p>Items: <span>'||c||'</span></p>
    <p class="sideCartTotal">Total: <span>'||trim(to_char(t,'$999G999G999D00'))||'</span></p>
</div>');
else
    sys.htp.p('<p class="sideCartTotal">You have no items in your current order.</p>');
end if;
The if c > 0 condition evaluates whether a product is selected in the current order. A value other than zero in this variable indicates the addition of product(s). If the current order has some items added, show the label Total: along with the value, which is stored in the variable t. If no items are selected, show the message defined in the else section.

end;
Mark the end of the child PL/SQL block.

htp.p('</div>');
Div tag closed.

end;
Parent PL/SQL block ended.


Also see:

Display Data Dynamically In A Gauge Chart

In this tutorial, we will learn how to display customer's ordered data in a gauge chart dynamically. As you choose a customer name from ...