Monday, February 1, 2010

Presenting at ODTUG Kaleidoscope 2010


I'll be presenting at ODTUG Kaleidoscope in Washington DC this year. If you haven't already signed up I suggest you do so before March 24th as they have an early bird special.

I've been to the past 2 Kaleidoscope conferences where I've learned a great deal about APEX and met some great people. It doesn't matter if you're a seasoned APEX developer or new to APEX, there's always something to learn and a lot of excellent presentations for all levels.

This year I'll be giving 2 presentations on APEX (http://www.odtugkaleidoscope.com/apex.html#dsouza)

Enhancing APEX Security: APEX has some excellent built-in configurable security features. This presentation will go over some extra functionality you can add to your APEX applications that will make it more secure both in the front-end and back-end. Primary focus will be on "enhanced session state protection" and "poor man's" VPD for Oracle XE.

How to be Creative: Using the APEX Dictionary to Create Solutions: The APEX dictionary is a very useful tool which can help enhance existing features in APEX. This presentation will cover how you can use the dictionary to resolve your problems. It will include some real-life issues and how the APEX dictionary was used to resolve them

Of course these presentations may be slightly altered based on the new features and functionality of APEX 4.0.

I look forward to seeing everyone in Washington.

Sunday, January 3, 2010

How to Automatically Remove Items and Values in APEX URLs

A while ago I wrote about how to pass multi-select lists through the URL: http://apex-smb.blogspot.com/2009/07/apex-how-to-pass-multiselect-list.html. This method works, however it will only support up to 4000 characters as a result of the functions it uses.

What happens when a user selects more than 4000 characters? You could update the processes to handle clobs etc. I took a different approach when faced with this problem. Instead of trying to update the process to handle clobs I asked: Why am I passing the values through the URL? Wouldn't it be easier if I didn't pass values through the URL?

When I first thought about this, I thought I could run a process on each page that would set the appropriate variables to the calling page. But then developers would have to look in the page branch and page processes to see what items were being passed to which pages. This sounded confusing and goes against the APEX framework. I really don't like having developers do extra work when they don't need to or go against standards already put in place.

I took a different approach to this problem. Instead of having a special page process to pass values from one page to another I was able to "intercept" the page branch and modify it before it was executed. I did this by taking the page branch and manually setting the item/value pairs, then removing them from the page branch. Here's an example of the final solution: http://apex.oracle.com/pls/otn/f?p=20195:2800.

Here are some screen shots from the demo application:

Page Branch configuration:


Passing in values via the URL (note that I'd like "X" to be "abc:def", however it's just abc)


Running the page process to manually remove the item/value pairs from the URL:


Before reviewing the code, it's important to note the following assumptions. I made these assumptions to make the code demo easier to read.

- Only 1 branch is defined on the page with no conditions etc.
- The branch only contains at most 1 clear cache
- The branch passes as most 1 item/value pair to the other page

- Create page process: Remove URL Params
- Branch Point: On Submit After Processing

Note: You could easily turn this into an application process to be run throughout your application

-- NOTE: For the purpose of this example I'm assuming the following:
-- Only 1 branch is defined with no conditions etc.
-- The branch only contains at most 1 clear cache
-- The branch passes as most 1 item/value pair to the other page

DECLARE
v_branch_action apex_application_page_branches.branch_action%TYPE;
p_app_id apex_application_page_branches.application_id%TYPE := :app_id;
p_page_id apex_application_page_branches.page_id%TYPE := :app_page_id;
v_clear_cache NUMBER;
v_item_names VARCHAR2 (4000);
v_item_values VARCHAR2 (4000);
BEGIN
-- Get branch action
-- The branch action is the URL before it is parsed by APEX
SELECT branch_action
INTO v_branch_action
FROM apex_application_page_branches
WHERE application_id = p_app_id AND page_id = p_page_id;

-- Get the clear cache options
v_clear_cache :=
SUBSTR (v_branch_action,
INSTR (v_branch_action, ':', 1, 5) + 1,
INSTR (v_branch_action, ':', 1, 6)
- INSTR (v_branch_action, ':', 1, 5)
- 1
);

-- Manually clear the cache if required
IF v_clear_cache IS NOT NULL
THEN
apex_util.clear_page_cache (p_page_id => v_clear_cache);
END IF;

-- Get Page Param values
v_item_names :=
SUBSTR (v_branch_action,
INSTR (v_branch_action, ':', 1, 6) + 1,
INSTR (v_branch_action, ':', 1, 7)
- INSTR (v_branch_action, ':', 1, 6)
- 1
);
v_item_values :=
SUBSTR (v_branch_action,
INSTR (v_branch_action, ':', 1, 7) + 1,
INSTR (v_branch_action, '&success_msg=', 1, 1)
- INSTR (v_branch_action, ':', 1, 7)
- 1
);

-- If item/value pairs exist, manually set the values
IF v_item_names IS NOT NULL
THEN
-- See: http://apex-smb.blogspot.com/2009/07/apexapplicationdosubstitutions.html for more info on apex_application.do_substitutions
apex_util.set_session_state
(p_name => v_item_names,
p_value => apex_application.do_substitutions (TRIM (v_item_values))
);
END IF;

-- Modify the branch action
-- Remove the clear cache option if applicable
IF v_clear_cache IS NOT NULL
THEN
v_branch_action :=
SUBSTR (v_branch_action, 1, INSTR (v_branch_action, ':', 1, 5))
|| SUBSTR (v_branch_action, INSTR (v_branch_action, ':', 1, 6));
END IF;

-- Remove the item name/value pairs if applicable
IF v_item_names IS NOT NULL
THEN
-- Remove item Names
v_branch_action :=
SUBSTR (v_branch_action, 1, INSTR (v_branch_action, ':', 1, 6))
|| SUBSTR (v_branch_action, INSTR (v_branch_action, ':', 1, 7));
-- Remove item values
v_branch_action :=
SUBSTR (v_branch_action, 1, INSTR (v_branch_action, ':', 1, 7))
|| SUBSTR (v_branch_action,
INSTR (v_branch_action, '&success_msg=', 1, 1)
);
END IF;

-- Set the new branch action
apex_application.g_branch_action (1) := v_branch_action;
END;

Tuesday, December 1, 2009

APEX Orphaned Application Files

If you allow end users to upload files to your APEX application you may have a lot of "orphaned" files in apex_application_files and not even realize it.

Orphaned files are files that exist in APEX_APPLICATION_FILES that are not associated with an application. This can happen for several reasons, the most common are:

  • Files uploaded in Shared Components that aren't associated to an application

  • End users uploading files then purposely keep them in APEX_APPLICATION_FILES

  • End users uploading files and an error occurs



You can easily identify orphaned files using the APEX_APPLICATION_FILES view:

SELECT *
FROM apex_application_files
WHERE flow_id = 0 -- flow_id is the same as application_id

All 3 situations listed above will result in the file uploaded with flow_id = 0. The last 2 points, files uploaded from end users, can result in files that you may no longer need. I don't recommend that you keep uploaded files in the APEX_APPLICATION_FILES view. Instead you should move them immediately to a custom table.

The main problem comes from the third point. When a user uploads a file and a validation fails. In this situation the file is uploaded to APEX_APPLICATION_FILES and then the validation fails. Even though the validation failed, the file still resides in APEX_APPLICATION_FILES. The following screen shot demonstrates this issue.


To resolve this issue, I run the following application process which automatically "tags" uploaded files with a flow_id of -1. By doing so you can run a nightly process to delete any files that have a flow_id of -1.

Application Process: AP_TAG_APEX_FILES
Sequence: -100
Point: On Submit: After Page Submission - Before Computations and Validations

-- AP_TAG_APEX_FILES
BEGIN
FOR x IN (SELECT v (item_name) item_name_value
FROM apex_application_page_items
WHERE application_id = :app_id
AND page_id = :app_page_id
AND display_as = 'File Browse...')
LOOP
UPDATE apex_application_files aaf
SET aaf.flow_id = -1
WHERE aaf.NAME = x.item_name_value;
END LOOP;
END;

Monday, November 16, 2009

Using Decimals in APEX Page Numbers



When creating an APEX page you can use decimals in the page number. If you logically group your pages by page numbers and run out of page numbers this may be useful. For example if you create pages 1 through 10 then realize you'd like a page right beside page 5, you could create Page 5.1.

I don't personally recommend using decimals in page numbers as you can't use decimals in item names, so P5.1_X won't work. Of course you may find some other use for creating pages with decimals.

If you're concerned about logically grouping pages for your development try initially separating pages by increments of 100 or by using Page Groups. To configure Page Groups go to the main application development page and click on "Page Groups" on the right hand side. Once you've setup the group and assigned pages, go back to the main development page and under the "View" drop down select "Groups".

Tuesday, October 27, 2009

Standardizing Help Text in APEX


I had a requirement where the same item name was used in various areas of the application. To maintain consistancy, and to minimize development time, I decided to create a "help framework" to standardize on the help text for these items.

Note: I know APEX comes with a great tool to maintain consistancy for your page items called User Interface Defaults, however it would not work in this case.

The goal was for developers not to have to enter any help text for commonly named page items. Instead, when applicable, the help text would automatically be retreived from a common area so we'd only need to maintain it in one place. The design also had to handle custom help text (i.e. if we needed to override the default help text).

To do this I used Tool Tip Help (see: http://apex-smb.blogspot.com/2009/09/tooltip-help-in-apex-alternative-to.html) and made the following modifications:

- Create a Default Help Page
Page Name: Default Help Page

Create a region called "Default Help Items" and add all the items you want default help text for. For example, I created P10 and added the following items: P10_EMPNO, P10_ENAME. In each of these items I added the default help text for similarly named items.

- Modify the Application Process AP_GET_HELP_TEXT

BEGIN
FOR x IN (SELECT ''
|| item_help_text
|| '
' help_html
FROM (SELECT a.item_name,
NVL (a.item_help_text, dflt_help_text.item_help_text)
item_help_text
FROM apex_application_page_items a,
(SELECT 'P' || :app_page_id || '_' || LTRIM (item_name, 'P10_')
item_name,
item_help_text
FROM apex_application_page_items
WHERE application_id = :app_id
AND page_id = 10 -- Enter Default Help Page number here (and modify select statement above)
AND item_help_text IS NOT NULL) dflt_help_text
WHERE a.application_id = :app_id
AND a.page_id = :app_page_id
AND a.item_name = dflt_help_text.item_name(+)
AND NVL (a.item_help_text, 'null') != '@NO_HELP@')
WHERE item_help_text IS NOT NULL)
LOOP
HTP.p (x.help_html);
END LOOP;
END;


Now when I run the application, any item that is called PXX_EMPNO or PXX_ENAME will try to use the help text from P10 for the corresponding items. Developers can easily override the default help text by filling in specific help text for an item or by entering in "@NO_HELP@"