Blog PostsStatistics Feedback About Login  


Go

Blog Selection


Latest news from Twitter (messages from Twitter with hashtag #apexblogs - in Realtime)
whitehorsesnl (Whitehorses) at 2010-02-08 22:50:03
Whiteblog: APEX webservice references and ACL #apexblogs http://bit.ly/csr29G

lgcarrier (Louis-Guillaume) at 2010-02-08 19:40:44
don't forget to add the doctype declaration in your page template #apexblogs

patrickwolf (Patrick Wolf) at 2010-02-08 16:08:39
Will speak at the Bulgarian Oracle Usergroup Spring conference - http://www.bgoug.org/en/events/details/67.html #apex4 #apexblogs

patrickwolf (Patrick Wolf) at 2010-02-08 16:07:45
RT @cczarski: Package for OS interaction with SQL and PL/SQL: new Version 0.9 released http://is.gd/7Vz3P - feedback very welcome #oracle #apexblogs

anjeli2001 (anjeli2001) at 2010-02-08 15:46:18
new post about howto change an usual apex nav bar into sexy extJS toolbar via template.... http://tinyurl.com/sexy-toolbar #apexblogs

peekbee (Paul Brookes) at 2010-02-08 14:47:01
FancyBox plugin for jQuery - http://bit.ly/dtjNWP #apex #apex4 #apexblogs #oracle

cczarski (Carsten Czarsk) at 2010-02-08 09:56:40
Package for OS interaction with SQL and PL/SQL: new Version 0.9 released http://is.gd/7Vz3P - feedback very welcome #oracle #apexblogs

mvzoest (Michel van Zoest) at 2010-02-05 16:07:05
Finished "1Z1-450-ENU Oracle Application Express 3.2: Developing Web Applications" beta exam. Phew. #apexblogs

simongadd1 (Simon Gadd) at 2010-02-04 14:29:24
Sticking #Oracle 10g XE on my Asus 1005ha. Running Windows 7 RC1. Gonna see if my wee eee can be used for #APEX demos & presos #APEXblogs

cczarski (Carsten Czarsk) at 2010-02-04 12:12:38
Event "SecureFiles and the 11.2 DB Filesystem in Germany (M,S,D,HH,DD 02/2010) - http://tinyurl.com/yzfqzn4 #apexblogs

simongadd1 (Simon Gadd) at 2010-02-03 18:17:15
#UKOUG #APEX SIG March 31st is shaping up. Four confirmed speakers, a 'Speed Networking' session and an 'Ask the Expert' session. #APEXblogs

patrickwolf (Patrick Wolf) at 2010-02-03 17:42:29
Added several plug-in utility functions to make handling of lov sql and dynamic sql statements in general super easy. #APEX4 #apexblogs

aejes (John Scott) at 2010-02-03 16:58:50
@neilkod yep, definitely can. have a look at the APEX_UTIL.GET_PRINT_DOCUMENT API routine (will return a BLOB you can store). #apexblogs

simongadd1 (Simon Gadd) at 2010-02-03 16:10:57
When will #Oracle begin #APEX Certification? #APEXblogs

clark_b (Bruce Clark) at 2010-02-03 15:05:00
Difficully with Tabular Form pagination in ApEx 3.2.1 when using standard report termplate - had to use other termplate #apexblogs

1 
AJAX based select list in APEX
4
Tobias Arnhold 08-FEB-2010 21:30

I looked through the Web for a simple way using a select list with dynamic data exchange inside my APEX application. What I found was just amazing (Thanks to Scott):

ajax-select-list-code-generator for APEX

Select list code generator

Output:

Show Comments (0)

MySQL/Oracle XE integration: Invalid identifier problem
4
Tobias Arnhold 08-FEB-2010 21:04

I linked a MySQL table into an OracleXE database (Short How to) and discovered a really strange behavior. When I tried an usual select about the MySQL table from my sqlplus client an error occurred: ORA-00904: "last_name": Invalid identifier

Here the whole description:

-- Error:
Connected to Oracle Database 10g Express Edition Release 10.2.0.1.0
Connected as test_user

SQL> SELECT "last_name" FROM tbl_users@MYSQL_USER u;

SELECT "last_name" FROM tbl_users@MYSQL_USER u

ORA-00904: "last_name": ungültiger Bezeichner

SQL>

-- MySQL DDL TABLE:
DROP TABLE IF EXISTS 'my_sqldb'.'tbl_users';
CREATE TABLE 'my_sqldb'.'tbl_users' (
'u_id' int(10) unsigned NOT NULL auto_increment,
'last_name' varchar(50) NOT NULL,
'forename' varchar(50) NOT NULL,
'department_id' int(10) unsigned NOT NULL,
PRIMARY KEY ('u_id')
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;

Solution:
Query MySQL table through Oracle APEX database using Oracle database link fails
Issue while querying MySQL tables through Oracle Generic Connectivity Using ODBC

Solution description:

...I changed the character set settings to "utf8" of v5.1.6 mysql odbc driver
through "Details -> Misc Options" as suggested and finally the problem was
resolved - the query returned the correct results...


Show Comments (0)

APEX navigation bar in sexy extJS toolbar...
4
Anja Hildebrandt 08-FEB-2010 16:35

It's been a while ;).... But i have a lot of new APEX and ExtJS stuff to blog about. I've been working on a complete extJs based template for my latest APEX project... So here is my first part of the upcoming series.... It's about how to get the usual apex navigation bar to show up in a sexy extJS toolbar...
There are 3 options i always have in my toolbar - logout, help, home... I usually use image buttons, so my definition looks something like this...


And in more detail....

Now we have to build up a little JSON object via navigation bar template.... This is what i put in my page template....

navigation bar
[{
xtype: 'box',
el: 'my_reg_pos_08'
},'->'#BAR_BODY#]
navigation bar entry
,{
xtype: 'box',
autoEl: {
tag: 'a',
href: '#LINK#',
cn: '<img src="#IMAGE#" alt="#TEXT#" />',
title : '#TEXT#'
}
},{xtype: 'tbspacer', width: 10}
As you can see, I build the complete json string to define my toolbar items.... I also include one of my region-position-divs (defined in the body below), because i need a bit of form-magic inside my toolbar....
The body looks something like this...

body

<div id="my_nav_bar"></div>
<div id="myTabs"></div>
<div id="my_messages">#GLOBAL_NOTIFICATION##SUCCESS_MESSAGE##NOTIFICATION_MESSAGE#</div>
<div id="my_box_body" width="100%">#BOX_BODY#</div>
<div id="my_reg_pos_01">#REGION_POSITION_01#</div>
<div id="my_reg_pos_02">#REGION_POSITION_02#</div>
<div id="my_reg_pos_03">#REGION_POSITION_03#</div>
<div id="my_reg_pos_04">#REGION_POSITION_04#</div>
<div id="my_reg_pos_05">#REGION_POSITION_05#</div>
<div id="my_reg_pos_06">#REGION_POSITION_06#</div>
<div id="my_reg_pos_07">#REGION_POSITION_07#</div>
<div id="my_reg_pos_08">#REGION_POSITION_08#</div>

Ext.onReady(function(){
var nb = #NAVIGATION_BAR#;
var tb = new Ext.Toolbar({renderTo: 'my_nav_bar', items: nb});

var myform = new Ext.ux.FormViewport({
layout:'border',
items:[new Ext.Panel({
region:'north',
id: 'my_toolbar',
height: 40,
collapsible: false,
margins:'0 0 0 0',
allowDomMove:false,
tbar: tb
}),new Ext.Panel({
region:'south',
id: 'footer',
height: 22,
collapsible: false,
margins:'0 0 0 0',
html:'<div id="DeveloperToolbar"></div>'
}),
new Ext.Panel({
region:'west',
id:'my_info',
title:'Info',
split:true,
width: 220,
minSize: 175,
maxSize: 400,
collapsible: true,
margins:'40 0 5 5',
cmargins:'40 0 5 5',
contentEl: 'my_reg_pos_01',
layoutConfig:{
animate:true
}
}),new Ext.Panel({region:'center',
margins:'40 5 5 5',
id: 'my_workarea',
items: [tabs],
layout : "fit"
})
]
});
})
I don't use any usual html table layout. I only define a bunch of div regions according to the region positions, messages, navigation bar etc...
In the onReady-function i use the #NAVIGATION_BAR# substitution string to put my created json string inside a variable.
var nb = #NAVIGATION_BAR#;
Then i use nb as item parameter for my toolbar object ...
var tb = new Ext.Toolbar({renderTo: 'my_nav_bar', items: nb});
After that i am able to use tb in my actual page layout.... If everything works, it shoul look something like this....



In case your wondering... I used the customized viewport from Mark Lancaster to make sure, that my elements stay inside my apex form...
To get my developer toolbar inside the footer i used another tip from Mark, which you find here...







Show Comments (0)

Well Fancy that!
4
Paul Brookes 08-FEB-2010 14:44

I came across a jQuery plugin called FancyBox for creating nice "floating" overlay windows, which I have incorporated into my online store demo.  Just click on the product name and it will display the details in a modal window. 

It doesn't currently work in IE 6, although I'm not sure about later versions, so you're best viewing it in FF.
Show Comments (0)

Creating an ExtJS tree with PL/JSON
4
Tobias Arnhold 04-FEB-2010 23:49

I searched for a smoother way building ExtJS trees in APEX and found a great post from Anja Hildebrandt: Nice trees with ExtJS (Erstellung eines ExtJS-Baums…)

She used the PL/JSON Utility from Lewis Cunningham: PL/JSON by jkrogsboell, lewiscunningham

During the time now Lewis developed a new version of the utility and Anjas code didn't work anymore. I updated her source code with the new version of PL/JSON 0.8.6.

First I show how you build JSON output with the PL/JSON scripts:
Example - ex4.sql

SQL> declare
2 obj json;
3 procedure p(v varchar2) as begin dbms_output.put_line(null);dbms_output.put_line(v); end;
4 begin
5 p('you can also put json or json_lists as values:');
6 obj := json(); --fresh json;
7 obj.put('text', 'Audi');
8 obj.put('id', 100);
9 obj.put('leaf', json_bool(true));
10 obj.put('href', 'f?p=110:1:');
11 obj.put('children', json_list('[{"text": "A4"},{"text": "A5"}]'));
12 obj.print;
13 end;
14 /


you can also put json or json_lists as values:
{
"text" : "Audi",
"id" : 100,
"leaf" : true,
"href" : "f?p=305:1:",
"children" : [{
"text" : "A4"
}, {
"text" : "A5"
}]
}

PL/SQL procedure successfully completed

PL/JSON forum example
 
SQL>
SQL> DECLARE
2 resultset json;
3 row_list json_list := json_list();
4 columns1 json;
5 num_rows number := 2;
6 columns_length number := 3;
7 procedure p(v varchar2) as begin dbms_output.put_line(null);dbms_output.put_line(v); end;
8 BEGIN
9 p('Data:');
10 FOR i IN 1 .. num_rows
11 LOOP
12 columns1 := json();
13 columns1.put('rownum', i);
14 FOR x IN 1 .. columns_length
15 LOOP
16 columns1.put('column' || x, 'Testdata');
17 END LOOP;
18 row_list.add_elem(columns1.to_anydata);
19 END LOOP;
20 resultset := json();
21 resultset.put('ResultSet', row_list);
22 resultset.print;
23 END;
24 /


Data:
{
"ResultSet" : [{
"rownum" : 1,
"column1" : "Testdata",
"column2" : "Testdata",
"column3" : "Testdata"
}, {
"rownum" : 2,
"column1" : "Testdata",
"column2" : "Testdata",
"column3" : "Testdata"
}]
}

PL/SQL procedure successfully completed

SQL>

Updated tree package of Anja

create or replace package PKG_EXTJS_JSON is

-- Author : AHILDEBRANDT
-- : TARNHOLD
-- Created : 10.08.2009
-- Updated : 04.02.2010
-- Purpose : check if category has sub-categories
function hasChildren(i_cat_id in number) return boolean;

-- Purpose : create JSON-Object for category;
-- recursive
function getJsonObject(i_cat_id in number default 0) return json;

-- Purpose : function that calls getJsonObject and returns the result as string
function getTreeDataDynamic return varchar2;

end PKG_EXTJS_JSON;

create or replace package body PKG_EXTJS_JSON is
/*
-- Author : AHILDEBRANDT
-- : TARNHOLD
-- Created : 10.08.2009
-- Updated : 04.02.2010
-- Purpose : check if category has sub-categories
*/
function hasChildren(i_cat_id in number) return boolean is
v_count number:=0;
begin
select nvl(count(*),0) into v_count from tbl_categories where c_par_id=i_cat_id and c_active = 'YES';
if v_count>0 then
return true;
else
return false;
end if;
end;

/*
-- Author : AHILDEBRANDT
-- : TARNHOLD
-- Created : 10.08.2009
-- Updated : 04.02.2010
-- Created : 10.08.2009
-- Purpose : create JSON-Object for category;
-- recursive
*/
function getJsonObject(i_cat_id in number default 0) return json is
v_json json:=json();
v_row_list json_list := json_list();
v_arr_id NUMBER;
v_ele_id NUMBER;
begin

if i_cat_id = 1 then -- if category is root then set root-node
v_json.put(pair_name => 'id', pair_value => i_cat_id);
v_json.put(pair_name => 'text', pair_value => 'Root');
else -- else --> usual node; use category infos
for cat in (select * from tbl_categories where c_id=i_cat_id and c_active = 'YES') loop
v_json.put(pair_name => 'id', pair_value => i_cat_id);
v_json.put(pair_name => 'text', pair_value => cat.c_name);
end loop;
end if;

if not hasChildren(i_cat_id) then -- if node has no children
v_json.put(pair_name => 'leaf', pair_value => json_bool(true)); -- mark as leaf
v_json.put(pair_name => 'href', pair_value => 'f?p=110:1:'||v('APP_SESSION')); -- set a link if needed
-- v_json.put(pair_name => 'href', pair_value => 'javascript:show_cat('||i_cat_id|| ');'); -- set a javascript function
else -- sonst
v_json.put(pair_name => 'leaf', pair_value => json_bool(false)); -- mark as node with children
for child in (select * from tbl_categories where c_par_id=i_cat_id and c_active = 'YES') loop -- loop through all sub-categories
-- create JSON-object for sub-category using recursive call and the append to array
v_row_list.add_elem(getJsonObject(i_cat_id => child.c_id).to_anydata);
end loop;
v_json.put(pair_name => 'children', pair_value => v_row_list); -- add array for subcategories
end if;

return v_json;

end;

/*
-- Author : AHILDEBRANDT
-- : TARNHOLD
-- Created : 10.08.2009
-- Updated : 04.02.2010
-- Purpose : function that calls getJsonObject and returns the result as string
*/
function getTreeDataDynamic return varchar2 is
v_json json:=json();
begin
v_json:=getJsonObject(i_cat_id => 1);
return('['||v_json.to_char||']');
end;

end PKG_EXTJS_JSON;

Utilities like this makes developing much more faster!
Show Comments (0)

My first Apex 4 plugin: Flight Info from Web Service
4
Oradude 04-FEB-2010 11:55



One of the exciting new features in Apex 4 is the support for plugin regions and items. This feature has huge potential, and will make development with Apex even more efficient, productive, and fun. There are already several plugins out there, and I think we will see a lot of interesting work in this area after Apex 4 is released.

Here is my own first attempt at a (useful) plugin: A region plugin that displays up-to-date flight information for airports in Norway, based on public flight data provided by Avinor, the company that operates the Norwegian airport network.

Avinor has a simple web service that provides flight information in XML format.

I am sure there are similar (web) services for flight information in other countries (feel free to leave a comment below if you know of any).

Here is the PL/SQL code behind the plugin:



procedure render_my_plugin (
p_region in apex_plugin.t_region,
p_plugin in apex_plugin.t_plugin,
p_is_printer_friendly in boolean )
as
l_clob clob;
l_airport_code varchar2(20) := p_region.attribute_01;
l_direction varchar2(20) := p_region.attribute_02;
begin

l_clob := apex_web_service.make_rest_request(
p_url => 'http://flydata.avinor.no/XmlFeed.asp',
p_http_method => 'GET',
p_parm_name => apex_util.string_to_table('airport:direction'),
p_parm_value => apex_util.string_to_table(l_airport_code || ':' || l_direction )
);

if l_direction = 'D' then
htp.p('<p><b>Departures from ' || l_airport_code || '</b></p>');
else
htp.p('<p><b>Arrivals to ' || l_airport_code || '</b></p>');
end if;

htp.p('<table width="100%">');
htp.p('<tr><td>AIRLINE</td><td>FLIGHT</td><td>AIRPORT</td><td>TIME</td><td>GATE</td></tr>');

for l_rec in (
SELECT *
FROM XMLTABLE ('//airport/flights/flight'
PASSING XMLTYPE(l_clob)
COLUMNS unique_id varchar2(100) path '@uniqueID',
airline varchar2(10) path 'airline',
flight_id varchar2(20) path 'flight_id',
airport varchar2(20) path 'airport',
schedule_time varchar2(100) path 'schedule_time',
gate varchar2(100) path 'gate')
ORDER BY airline, flight_id) loop

htp.p('<tr><td>' || l_rec.airline || '</td><td>' || l_rec.flight_id || '</td><td>' || l_rec.airport || '</td><td>' || l_rec.schedule_time || '</td><td>' || l_rec.gate || '</td></tr>');

end loop;

htp.p('</table>');

htp.p('<a href="http://www.avinor.no">Flight data from Avinor.</a> Last updated: ' || to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss'));


end render_my_plugin;






The code illustrates several concepts:
  • How to render a region plugin using the PL/SQL Web Toolkit (HTP.P) calls
  • How to retrieve values from the attributes defined for the plugin
  • Using the new APEX_WEB_SERVICE.MAKE_REST_REQUEST function to retrieve a web page as a CLOB
  • Using the XMLTABLE function to transform XML into a recordset that can be used in a SELECT

An export of my plugin can be downloaded here, and installed into your own Apex 4 application.

After the plugin has been installed, using the plugin is as simple as adding a Region (of type Plugin) to the page, and configuring the values for Airport and Direction (the plugin attributes) in the region definition.



You can see a live demo of the plugin here (public page, does not require authentication):

http://tryapexnow.com/apex/f?p=test4ea:plugin_demo:0

Note that for this page, I've also taken advantage of the built-in region caching feature of Apex. The region cache duration is set to 10 minutes, which prevents us from hitting the remote web service for every page view. I really like that you can switch on region caching in Apex without writing a single line of code.

Conclusion: Apex 4 plugins rock!
Show Comments (0)

Nice URL in a Public Facing Oracle APEX Application
4
Håvard Kristiansen 04-FEB-2010 10:33

UPDATE: Try out Morten Bråtens excellent post on creating a REST web service with PL/SQL for the simplest answer to beautifying APEX URLs. The main difference between our solutions is that he uses a PL/SQL-procedure to actually do the rewrite, and I use an Apache map file. If I only had known... Ah, well, you live, you learn.

The Oracle APEX URL construct can be a bit cryptic for the uninvited, people and spiders alike. Here is a (a, not THE) technique to alleviate this. It is loosely based on a similar solution I made for an ancient Oracle Portal solution quite a few years back. You need an Apache (OHS/OAS or regular Apache) in front for this to work. The API is tested for Apache 2.2 and Oracle XE/11g, but it should run equally well on OHS/OAS (Apache 1.3-2.0).

Why Do You Need Nice URLs?
This is the place to start; Why would you consider rewriting the native APEX URL? There are a few good reasons why, but generalizations won't work here. The question is why do YOU need this for YOUR application? Consider it, and reconsider it, because there are no silver bullets here. Any path you choose will inevitably cause more work, add complexity and even more gray hairs when your carefully thought out assumptions suddenly changes. Not to mention adding overhead to your webserver when processing requests. Perhaps you will even start loosing hair for all I know. The more sophisticated application, the more work added for beautifying the URLs.

For an intranet environment I can see no good reasons to do this. None.

The Alternatives
There are already some designs out there to help you on your way. Patrick Wolf has a list of the more popular ones. Kris Rice's post Better APEX URLs also uses a map file. The <iframe> solution is also worth considering, depending on your need. There are also some very creative Apache rewrite solutions described in the OTN APEX-forum.

My Attempt
Let's just call it that, an attempt. It did not turn out as streamlined as I had hoped for, but for my need (and the effort I was prepared to put into it), it suffices.

I wanted to create content based nice URLs, and the content in question here resides in an Oracle database table. I also wanted to control the output from the database to reduce the number of moving parts.

This is how it works:
  • You can define a "page type" based on a table with a primary key, and create mapping between APEX URL and a nice URL based on that
  • You can manually create mapping between APEX URL and a nice URL to fully control both APEX and nice URL
  • The APEX to nice URL mapping will be written to an Apache map file
  • Apache checks the content of the map file for a potential rewrite
  • If the map file changes, Apache will cache the new version automagically
See examples below the first script to get a feel for it. The rewrites will not get in the way of the normal APEX URLs, so it will not break existing functionality.

The Database Code
The following script will create two tables, one to hold the mapping between source table (where content resides) and the APEX page(s) where content will be displayed. The other table is where the actual nice to APEX URL-mapping resides, the content of this table is what gets written to the Apache rewrite map file.

The script will also create a directory where the URL map file will be written, and a package to handle all the in-betweens. The schema user must have the appropriate privileges to create these objects for the script to succeed. Replace path to APACHECONF directory to desired location.
-- change path to suite your environment
create or replace directory APACHECONF as 'C:\Oracle\OraXE\Apache\conf'
/

create table app_page_type (
page_id number
, source_table varchar2(32)
, source_table_pk_col varchar2(32)
, url_path varchar2(255)
, pk_page_parameter varchar2(255)
, name_column varchar2(32)
)
/

create unique index app_page_type_uk
on app_page_type (page_id, source_table)
/

create table app_page_url_mapping
(
page_id number not null
, source_table varchar2(32)
, source_table_id number
, apex_url varchar2(4000) not null
, nice_url varchar2(4000) not null
, nice_title varchar2(255)
)
/

create unique index app_page_url_mapping_uk
on app_page_url_mapping (page_id, source_table, source_table_id)
/

create or replace package app_page_p
as
-- create url-friendly construct, 255 chars long
-- based on a name.
function get_nice_name (
p_name in varchar2
, p_source_table_id in varchar2 default null
, p_url_end in varchar2 default '.html'
) return varchar2;
-- get nice url based on source table, can be used
-- directly from APEX application
function get_nice_url (
p_page_id in number
, p_source_table in varchar2
, p_source_table_id in varchar2
) return varchar2;
-- get nice url from APEX page_id, will only return
-- mappings without source table defined
function get_nice_url (
p_page_id in number
) return varchar2;
-- create mapping based on page type and source table
procedure create_mapping (
p_source_table in varchar2
, p_source_table_id in varchar2
, p_name in varchar2 default null);
-- create complete manual mapping
procedure create_manual_mapping (
p_page_id in number
, p_apex_url in varchar2
, p_nice_url in varchar2
, p_nice_title in varchar2 default null);
--remove mapping when source is removed
procedure remove_mapping (
p_source_table in varchar2
, p_source_table_id in number
);
-- remove manual mapping to page_id
procedure remove_manual_mapping (
p_page_id in number
);
-- synchronize map file with mapping table
procedure synchronize_mapfile;
end;
/

create or replace package body app_page_p
as
-- global variables
-- change variables to suite your environment
g_app_id number := 1000; --APEX app_id for public application
g_app_name varchar2(255) := 'My test app'; --descriptive name of application
g_dad_path varchar2(255) := '/pls/apex'; --dad or location
g_map_file varchar2(255) := 'map.txt'; --name of map file
g_tmp_map_file varchar2(255) := 'map_tmp.txt'; --name of temporary map file
g_map_dir varchar2(255) := 'APACHECONF'; --name of directory
-- get nice url from source_table
function get_nice_url (
p_page_id in number
, p_source_table in varchar2
, p_source_table_id in varchar2
) return varchar2 is
l_ret varchar2(255);
begin
select tab.nice_url
into l_ret
from app_page_url_mapping tab
where tab.page_id = p_page_id
and tab.source_table = p_source_table
and tab.source_table_id = p_source_table_id;
return l_ret;
exception
when no_data_found
then
return null;
end;
-- get nice url from page_id, manually created
function get_nice_url (
p_page_id in number
) return varchar2 is
l_ret varchar2(255);
begin
for r in (select tab.nice_url
from app_page_url_mapping tab
where tab.page_id = p_page_id
and tab.source_table_id is null)
loop
l_ret := r.nice_url;
exit;
end loop;
return l_ret;
exception
when no_data_found
then
return null;
end;
-- create url-friendly construct, 255 chars long
function get_nice_name (
p_name in varchar2
, p_source_table_id in varchar2 default null
, p_url_end in varchar2 default '.html'
) return varchar2 is
l_ret varchar2(4000) := p_name;
l_sub number;
begin
-- special national translation (excerpt)
l_ret := translate(lower(l_ret), 'æøå ', 'aoa_');
l_ret := regexp_replace(l_ret, '([^[:alnum:]|_])', '', 1, 0, 'i');
l_sub := nvl(length(to_char(p_source_table_id)||p_url_end),0);
l_ret := substr(l_ret, 1, 255-l_sub);
l_ret := l_ret||p_source_table_id||p_url_end;
return l_ret;
end;
-- lookup column value for source_table and source_table_id
function get_source_name (
p_source_table in varchar2
, p_source_table_id in number
, p_source_pk_col in varchar2
, p_source_name_col in varchar2
) return varchar2
is
l_sql varchar2(4000) := 'select '||p_source_name_col||' pk_col'||chr(10)
||' from '||p_source_table||chr(10)
||' where '||p_source_pk_col||' = :1';
l_ret varchar2(4000);
begin
execute immediate l_sql into l_ret using in p_source_table_id;
return l_ret;
end;
-- create the actual mapping
procedure create_mapping (
p_source_table in varchar2
, p_source_table_id in varchar2
, p_name in varchar2 default null
) is
l_orig_name varchar2(4000);
l_nice_name varchar2(255);
l_apex_url varchar2(4000);
l_nice_url varchar2(4000);
l_nice_title varchar2(4000);
begin
-- delete previous entries
delete from app_page_url_mapping
where source_table = p_source_table
and source_table_id = p_source_table_id;
for r in (select pt.*
from app_page_type pt
where pt.source_table = p_source_table)
loop
if p_name is null
then
l_orig_name := get_source_name(r.source_table, p_source_table_id, r.source_table_pk_col, r.name_column);
else
l_orig_name := p_name;
end if;
l_nice_name := get_nice_name(l_orig_name, p_source_table_id, '.html');
l_apex_url := g_dad_path||'/f?p='||g_app_id||':'||r.page_id||':0::::'||r.pk_page_parameter||':'||p_source_table_id;
l_nice_url := r.url_path||'/'||l_nice_name;
l_nice_title := substr(g_app_name||' - '||l_orig_name, 1, 255);
-- insert new entry
insert into app_page_url_mapping (
page_id
, source_table
, source_table_id
, apex_url
, nice_url
, nice_title)
values (
r.page_id
, r.source_table
, p_source_table_id
, l_apex_url
, l_nice_url
, l_nice_title);
end loop;
-- synchronize url-mapping file
synchronize_mapfile;
end;
-- create manual mapping
procedure create_manual_mapping (
p_page_id in number
, p_apex_url in varchar2
, p_nice_url in varchar2
, p_nice_title in varchar2 default null
) is
begin
-- educated guess on delete
delete from app_page_url_mapping
where page_id = p_page_id
and source_table is null;
-- insert new entry
insert into app_page_url_mapping (
page_id
, apex_url
, nice_url
, nice_title)
values (
p_page_id
, p_apex_url
, p_nice_url
, p_nice_title);
-- synchronize map file
synchronize_mapfile;
end;
procedure remove_mapping (
p_source_table in varchar2
, p_source_table_id in number
) is
begin
delete from app_page_url_mapping
where source_table = p_source_table
and source_table_id = p_source_table_id;
synchronize_mapfile;
end;
procedure remove_manual_mapping (
p_page_id in number
) is
begin
delete from app_page_url_mapping
where page_id = p_page_id
and source_table is null;
synchronize_mapfile;
end;
-- normal write to file procedure
procedure write_to_file (
p_file_name in varchar2
, p_directory in varchar2
, p_content in clob
) is
l_file utl_file.file_type;
l_buffer raw(32000);
l_amount binary_integer := 32000;
l_pos integer := 1;
l_blob blob;
l_blob_left number;
l_blob_length number;
l_file_content blob;
l_src_offset integer := 1;
l_dest_offset integer := 1;
l_lang_ctx integer := dbms_lob.default_lang_ctx;
l_warn integer;
begin
-- converting from clob to blob
dbms_lob.createtemporary(l_file_content, false);
dbms_lob.converttoblob(l_file_content, p_content, dbms_lob.getlength(p_content), l_dest_offset, l_src_offset, 1, l_lang_ctx, l_warn);
l_blob_length := dbms_lob.getlength(l_file_content);
l_blob_left := l_blob_length;
-- open the destination file.
l_file := utl_file.fopen(p_directory,p_file_name,'WB', 32760);
-- if small enough for a single write
if l_blob_length < 32760 then
utl_file.put_raw(l_file,l_file_content);
utl_file.fflush(l_file);
else -- write in pieces
l_pos := 1;
while l_pos < l_blob_length
loop
dbms_lob.read(l_file_content,l_amount,l_pos,l_buffer);
utl_file.put_raw(l_file,l_buffer);
utl_file.fflush(l_file);
-- set the start position for the next cut
l_pos := l_pos + l_amount;
-- set the end position if less than 32000 bytes
l_blob_left := l_blob_left - l_amount;
if l_blob_left < 32000 then
l_amount := l_blob_left;
end if;
end loop;
end if;
utl_file.fclose(l_file);
dbms_lob.freetemporary(l_file_content);
exception
when others then
dbms_lob.freetemporary(l_file_content);
-- close the file if something goes wrong.
if utl_file.is_open(l_file) then
utl_file.fclose(l_file);
end if;
raise;
end;
-- synchronize map file
procedure synchronize_mapfile
is
l_map clob;
begin
l_map := '# Generated file, manual changes will suddenly disappear'||chr(10);
l_map := l_map || '# Last generated: '||to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss')||chr(10);
for r in (select *
from app_page_url_mapping)
loop
l_map := l_map||r.nice_url||' '||r.apex_url||chr(10);
end loop;
write_to_file(g_tmp_map_file, g_map_dir, l_map);
-- if everything goes well, we rename and overwrite
utl_file.frename(g_map_dir, g_tmp_map_file, g_map_dir, g_map_file, true);
end;
end;
/

Configuring Apache
Apache will handle the actual rewrites based on the generated map file. Include the following in your httpd.conf (after backing it up)
## Rewrite URLs that exists in map.txt
RewriteMap nicetoapex txt:c:/oracle/oraxe/apache/conf/map.txt
RewriteCond %{REQUEST_URI} /(([^/]+)(/.*)*)$
RewriteCond %2 =articles.html [OR]
RewriteCond %2 =article [OR]
RewriteCond %2 =albums.html [OR]
RewriteCond %2 =album
RewriteCond ${nicetoapex:%{REQUEST_URI}|NOT_FOUND} !NOT_FOUND
RewriteRule ^(/.*) ${nicetoapex:$1}%{QUERY_STRING} [P]
Note the forward slashes in the map file path (even for Windows installs). The code must be included directly into your VirtualHost-directive (if you have one), and not your default APEX Location-directive or some such. The reason is that the rewrite expects the nice-URL, not something that looks like '/pls/apex'.

Also note that I have included some criteria for when the map file will be checked, this is to exclude any number of requests not related directly to APEX pages. This is a very manual step, but can be minimized if you decide to prefix all your nice URLs with a common string. If you prefix your URLs you also will not force Apache to go through all the conditions before the attempted rewrite. A prefix kind of defeated the purpose of nice URLs for me, so I will update and reload Apache when changes are needed. A bit masochistic, I know...

The very cryptic "!NOT_FOUND" line above simply states that if the URL is not part of the map file, then don't rewrite (${nicetoapex:$1} will then have no value). There are probably easier ways to achieve this, but my knowledge of Apache is limited to what google serves me at the first page of the search result...

Apache will cache a copy of the map file for every start/restart/reload, and if the file is updated (mtime changed) it will re-read the file automagically. That is a nice feature, and saves you a ton of hassle.

You are not home free yet, APEX submits pages with relative paths (which is quite natural), but that will break your nice URL hierarchy. To fix this add:
## Handle calls to wwv_flow.accept
RewriteCond %{REQUEST_URI} ^/wwv_flow.accept
RewriteRule ^/(.*) /pls/apex/$1 [P]
This will rewrite all requests ending with wwv_flow.accept and point it to your dad-location. Depending on your application, it may be necessary to include more rewrite conditions for this rewriterule (like wwv_flow.show).

For good measure you can also include a rewrite to handle the default "start page" of your site with the following:
## Redirect server home page to Apex application
RewriteRule ^/$ http://localhost:8080/pls/apex/f?p=1000:1:0 [P,L]
RewriteRule ^/index.html$ http://localhost:8080/pls/apex/f?p=1000:1:0 [P,L]
Restart Apache for every change of httpd.conf for the changes to take effect.


Mapping URLs From Table Content
First off you have to define a relationship between the APEX-pages showing the content, and the database table where the content resides. The table APEX_PAGE_TYPE holds this information.
insert into app_page_type (
page_id
, source_table
, source_table_pk_col
, url_path
, pk_page_parameter
, name_column)
values (
3 -- APEX page_id
, 'ARTICLES' -- Source Table
, 'ARTICLES_ID' -- Source table primary key
, '/article' -- Desired URL-prefix
, 'P3_ARTICLE_ID' -- APEX page item id that holds the primary key
, 'TITLE') -- Source table column that content title
/
commit
/
In the example above I want to generate a nice URL for articles in the ARTICLES-table. APEX page_id=3 will show the content, based on the value of page item P3_ARTICLE_ID. This bit you only have to do once for each page type/source table association.

When I insert/update a row in the ARTICLES-table with ARTICLE_ID=0, I can generate a nice URL with the following statements:
begin
app_page_p.create_mapping( 'ARTICLES' -- source table name
, 0); -- source table id
end;
/
commit
/
This is the code you normally would run following a publication of an article. If the TITLE-column has value 'APEX 4.0 - It is finally here!', your map-file should now contain the following:
#       Generated file, manual changes will suddenly disappear
# Last generated: 03.02.2010 22:19:22
/article/apex_40__it_is_finally_here0.html /pls/apex/f?p=1000:3:0::::P3_ARTICLE_ID:0
You can have more than one page type associated with the same database table, and URLs for all pages will be generated from the statement above. If all works now, you should be able to point your browser to http://<yoursite>/article/apex_40__it_is_finally_here0.html

Manually Mappig URLs
Some APEX pages will probably not be directly linked to a database table, to accommodate this, the API offers a possibility to manually create page mappings with the following statements (note, there is no need for a page type to be defined first, as there is no content table to associate with that APEX page):
begin
app_page_p.create_manual_mapping(2 -- APEX page_id
, '/pls/apex/f?p=1000:2:0' -- APEX URL
, '/articles.html' -- Nice URL
, 'Articles'); -- Nice name
end;
/
commit
/
If you ran this after the previous example, your map file should look like this:
#       Generated file, manual changes will suddenly disappear
# Last generated: 03.02.2010 22:21:01
/articles.html /pls/apex/f?p=1000:2:0
/article/apex_40__it_is_finally_here0.html /pls/apex/f?p=1000:3:0::::P3_ARTICLE_ID:0
And likewise you should be able to see http://<yourhostname>/articles.html in a browser.

Pitfalls, Improvements and Considerations
What is the first thing you noticed about the API? No APP_ID-parameters! This version does not support more than one public application, but extending the code to accommodate this should be fairly trivial if the need should arise.

The second observation is the lack of support for more than one primary key column. The API must be modified to support this.

The third observation is the lack of APEX parameter support. Including RP or other such parameters will probably get onto your wish-list pretty soon. The API can be altered to achieve this, either through generating more mappings, or attaching parameters in a normal form to the nice-URL, and rewriting it to the correct APEX syntax in the Apache.

In the current version, there is no support for URL-hierarchy. If you want to incorporate an URL-path based on an hierarchy defined by the mapping API, you have to extend the API, or map it yourself by APP_PAGE_TYPE and APP_PAGE_URL_MAPPING.

You may also consider using the Apache httxt2dbm-utility to create a binary format DBM-file of the original map file, which is considerably faster than mapping with a txt-file. Adding a dbms_scheduler job to execute an external program should do the trick. Apache rewrite syntax is almost identical, you just have to tell it the map-type has changed.

I seriously wish there was an easy way to just delete a line from an existing file. I for one, could not conjure up a good way to do this from PL/SQL, hence the complete rewrite of the file. Maybe Perl could do a better job of it? Any old how, this portion of the API is well suited for a more asynchronous execution. In a normal production environment, the Apache is located in a DMZ, and not on the same server as the database. In that case the map file has to be moved from the database server to the webserver. The API does not support moving files through FTP/SFTP, but you can check out http://www.orafaq.com/scripts/plsql/ftpclient.txt for a plain PL/SQL FTP-solution, or be creative with an external procedure.

A Small Bonus
If you want to set the APEX page title according to the source table, you can modify the page template and include a function to return the nice_name column of the APP_PAGE_URL_MAPPING-table. The function will then be common for all pages in your application. The title of your webpage continually crops up as one of the more important tags for certain search engines.

In Conclusion
If you decide to use the API, then I suggest you start thinking about how to design your applications to use it, especially considering branching, tabs and other native APEX components that gravitate towards the normal APEX URL construct. If your application branches out with the old URL, there would be little point in attempting to rewrite the URLs. As I stated earlier, there are no silver bullets, and there will be more work than just using native APEX URLs.

If you are aiming at more sophisticated public applications, I would seriously consider NOT using it. This API (in it's present state at least) is aimed at more simple applications.

Oh, and did I mention you use this code at your own risk? Well, you do!

Enjoy :-)
Show Comments (0)

Range Slider Plugin
4
Paul Brookes 02-FEB-2010 20:12

I've spent a little time updating my online shop demo recently. I've still a long way to go but initially I am trying to get as much core functionality built into the search page, such as trying not to submit the page at all and do as much with Ajax as I can.  From hereonin I think I shall refer to this as 'Ajaxifying' my application.  Ok maybe not.

You can go to the demo here....Online Store Demo

Something to note.  You can drag and drop the product images into the shopping basket and the basket inventory will update, but this doesn't work with pagination.  If you use the range slider this will reset pagination and it will work again.  Also note, that this page only works properly with Firefox, which I think maybe down to Apex 4 EA not yet being supported in IE (it doesn't work with Chrome either).

You will notice that I have created a new plugin which I have based on Patrick Wolf's slider plugin, but this uses 2 markers on the slider to represent a range of high and low.   You can also specify default high and low values for the slider.  You can get the range slider plugin here. (let me know if this link doesn't work).  As plugins only return a single value, then the value returned will be made up of 2 values separated by a comma (ie '100,4500').  You will of course need to extract each value from the item you base on the plugin if you want to refer to them in a report Where clause.

I will post some more in the next day or so as I make other improvements.

(Thanks to Anthony for help with the live binding of the drag and drop/slider dynamic actions)

UPDATE:  One thing I forgot to mention is that I added a plugin parameter for the range slider that allows you to specify a jQuery theme from a select list.  This is not the most efficient way of applying themes to all jQuery based plugins on a page/application, but it gives you a flavour of what can be done.
Show Comments (0)

APEX Packager Utility
4
Tyler Muth 02-FEB-2010 19:28

I’ve been working on a pretty substantial APEX application with Jason Straub and Sharon Kennedy for the last x months (where x > estimated time). It will likely be an APEX Packaged application which consists of the APEX Application, DDL scripts for all schema objects, and install scripts for ~20 image / JavaScript / CSS [...]]]>
Show Comments (0)

New Content on APEX Home Page
4
David Peake 02-FEB-2010 11:18

It has been a while since I updated the content on the APEX OTN Home page.

Finally got some time to refresh the content including two books, three podcasts, some great customer case studies, and reshuffled the section up top to make it easier to find OBE's etc. Take a few minutes to review this excellent new content.

I also added all of the outstanding requests from consulting companies, blogs, internet applications and commercial applications - Apologies to those who had to wait so long to be added.

We now have listed:
101 Consulting Companies
63 Blogs
28 Commercial Applications
27 Internet Applications
12 Hosting Companies

An impressive list. If you're not listed and want to be it is as easy as sending me an email with details and images.

Regards,
David
Show Comments (0)

 1 2 3 4 5 6 7 8 9 10  Next »








 
 
Blog Roll
  • APEXtras
  • Andy Tulley
  • Anja Hildebrandt
  • Anthony Rayner
  • Anton Nielsen
  • Apex Blog
  • Apex dbe pl
  • Ben Burrell (Munky)
  • Bernard Fischer-Wasels
  • Bradley Brown
  • Carl Backstrom
  • Carsten Cerny
  • Carston Czarski
  • Christopher Beck
  • Dan Durbaca
  • Dan Mcghan
  • David Njoku
  • David Peake
  • Denes Kubicek
  • Dietmar Aust
  • Dimitri Gielis
  • Dirk McComsey
  • Doug Gault
  • Douwe Pieter van den Bos
  • Duncan Mein
  • E-Dba
  • Evgeny Timoshinin
  • German APEX Community
  • Håvard Kristiansen
  • IAdvise
  • Ilmar Kerm
  • Insum
  • Jason Aughenbaugh
  • Jason M.
  • Jason Straub
  • Jean-Phillipe Pinte
  • Jeff Holoman
  • Jeffrey Kemp
  • Joel Kallman
  • John Scott
  • Jon Trostheim
  • Jornica
  • João Oliveira
  • Kristian Jones
  • Learco Brizzi
  • Louis-Guillaume Carrier-Bédard
  • Marc Sewtz
  • Mark Lancaster
  • Martin Giffy D'Souza
  • Niels de Bruijn
  • Noel Portugal
  • Oracle Nerd
  • Oracle Quirks
  • Oradude
  • PL/GMaps
  • Patrick Wolf
  • Paul Brookes
  • Paulo Vale
  • Pawel Barut
  • Peter De Boer
  • Peter Manchev
  • Peter Raganitsch
  • RCI
  • Roel Hartman
  • Rutger de Ruiter
  • Sara Blair
  • Sathish Kumar
  • Scott Spendolini
  • Stew Stryker
  • Sujay Dutta
  • Sumnertech Blog
  • Tobias Arnhold
  • Tyler Muth
  • Unknown APEX
  • Wei Zheng

    © Created and Hosted by Apex Evangelists