<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8575920265455783910</id><updated>2012-02-03T12:01:09.028+01:00</updated><category term='apache'/><category term='plsql'/><category term='xml'/><category term='apex'/><category term='dialog'/><category term='reports'/><category term='google maps'/><category term='ckeditor'/><category term='docx'/><category term='apex40'/><category term='listener'/><category term='rtf'/><category term='dynamic actions'/><category term='pdf'/><category term='oracle'/><category term='chrome'/><category term='utl_smtp'/><category term='adf'/><category term='pagination'/><category term='popup'/><category term='fckeditor'/><category term='spatial'/><category term='file upload'/><category term='text index'/><category term='off topic'/><category term='hotkeys'/><category term='xe'/><category term='backup'/><title type='text'>Monkey on Oracle</title><subtitle type='html'>I read, I repeat. Sometimes I even adapt, that's called evolution.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>34</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-7532704732471460965</id><published>2012-01-30T20:55:00.000+01:00</published><updated>2012-01-30T20:55:00.031+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adf'/><title type='text'>The Art of Self-preservation</title><content type='html'>This post is aimed at all those poor souls that find themselves facing Oracle ADF coming from a PL/SQL background (Forms, APEX or plain old database development).&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-JvF3Rn4NEy8/TtVK1EASFxI/AAAAAAAAAak/CsbdjjQnP1w/s1600/adfmonster1.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/-JvF3Rn4NEy8/TtVK1EASFxI/AAAAAAAAAak/CsbdjjQnP1w/s1600/adfmonster1.gif" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;b&gt;Normal First Reactions&lt;/b&gt;&lt;br /&gt;&lt;i&gt;Despair&lt;/i&gt;. Most of us have felt it in the beginning, and it is considered perfectly normal. The feeling of constant despair will diminish slightly over time, but relapses will be frequent.&lt;br /&gt;&lt;br /&gt;You will feel as if you have stepped 20 odd years back in time; yes, it &lt;i&gt;has&lt;/i&gt; to be this hard to make even the simplest CRUD application.&lt;br /&gt;&lt;br /&gt;You will have to pass some fairly elevated hurdles; yes, you &lt;i&gt;have&lt;/i&gt; to learn (or at least have some knowledge of) Java, JSF, Expression Language, Groovy, CSS, HTML, Javascript and last but absolutely not least ADF itself.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Start in the Right End&lt;/b&gt;&lt;br /&gt;You (or more likely your superior) bought the sales pitch, and thought you arrived to a mature, complete development framework specifically suited to leverage your existing Oracle investments. What you got was the biggest jigsaw puzzle ever, and all the corners missing. To even attempt fiddling around with the pieces, you have to have some basic skills.&lt;br /&gt;&lt;br /&gt;ADF is a Java framework (although Java developers would call it xml-based). Java is core. You have to learn the stuff. You will not get far without Java. It is that simple. After all, the kids all learn it at school these days, how hard can it be? The answer is like always; 10% talent, 90% practice, it will take time to be good at it.&lt;br /&gt;&lt;br /&gt;As a starting point, I recommend &lt;a href="http://www.amazon.com/Head-First-Java-Kathy-Sierra/dp/0596009208"&gt;Head First Java&lt;/a&gt;. Not only is it educational, it will also help you to realize just how bad all other introductory programming books actually are.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Next Step&lt;/b&gt;&lt;br /&gt;As Java frameworks go, ADF is pretty big. Full JEE stack with lots of bells and whistles, and an abundance of XML-files. The good news is that you can achieve some pretty sophisticated behavior by declaration, the bad news is that you have to know where to turn the knobs. There are a lot of knobs.&lt;br /&gt;&lt;br /&gt;First you have to learn the grand picture of the framework;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The model, aka the meat, or business components&lt;/li&gt;&lt;li&gt;The other model. Err? This is the binding layer, or view backend model or some such&lt;/li&gt;&lt;li&gt;The controller thingy with task flows&lt;/li&gt;&lt;li&gt;The view, as in GUI, and not to be confused with the view object in the first model. Argh...&lt;/li&gt;&lt;/ul&gt;&lt;a href="http://blogs.oracle.com/grantronald/"&gt;Grant Ronald&lt;/a&gt; will help you on the way with his book&amp;nbsp;&lt;a href="http://www.amazon.com/Quick-Start-Oracle-Fusion-Development/dp/0071744282"&gt;Quick Start Guide to Oracle Fusion Development: Oracle JDeveloper and Oracle ADF&lt;/a&gt;. Do not let the lengthy title put you off, Mr. Ronald understands where you are coming from, and will gently nudge you in the right direction.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Ready Yet?&lt;/b&gt;&lt;br /&gt;Well, that depends. Do you want to create tutorial applications, or the real thing? You should not balk at tutorials, that is not what I am saying. It is just that real business users have never had any clue how to best work with &lt;i&gt;&amp;lt;insert technology/framework here&amp;gt;&lt;/i&gt;, they just have &lt;i&gt;needs&lt;/i&gt; (of which, you are of course grateful, as it gives you work).&lt;br /&gt;&lt;br /&gt;Users do not want to have plain CRUD anymore,&amp;nbsp;&lt;i&gt;they want to have an extremely functional and visually appealing working environment delivered at lightning speed&lt;/i&gt;. Business users will push you to the edge, they are both educated within their domain, and most likely used to some seriously fast client server applications.&lt;br /&gt;&lt;br /&gt;Nervous?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Relax...&lt;/b&gt;&lt;br /&gt;Stay sane just a bit longer. Take a deep breath, there will eventually be a light at the end of the tunnel.&amp;nbsp;Let us hope it is not an oncoming train ;-)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://blogs.oracle.com/shay/"&gt;Shay Shmeltzer&lt;/a&gt; has a more elaborate (and infinitely better) approach on how to get started with ADF in &lt;a href="http://blogs.oracle.com/shay/entry/how_do_i_start_learning_oracle_adf_and_jdeveloper"&gt;this blog post&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;To be continued...&lt;/i&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-7532704732471460965?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/7532704732471460965/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2012/01/art-of-self-preservation.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7532704732471460965'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7532704732471460965'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2012/01/art-of-self-preservation.html' title='The Art of Self-preservation'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-JvF3Rn4NEy8/TtVK1EASFxI/AAAAAAAAAak/CsbdjjQnP1w/s72-c/adfmonster1.gif' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-5236279116993298041</id><published>2012-01-26T21:33:00.000+01:00</published><updated>2012-01-26T21:33:44.804+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='adf'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='off topic'/><title type='text'>Updated Statement of Direction</title><content type='html'>...Of sorts. Actually, I did not even know I had one until now. But here goes:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-hkR_spmFDes/Tpgr2IoWO5I/AAAAAAAAALM/QJnX3nk0yeQ/s1600/startup.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="123" src="http://3.bp.blogspot.com/-hkR_spmFDes/Tpgr2IoWO5I/AAAAAAAAALM/QJnX3nk0yeQ/s400/startup.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;My long absence from this blog are based on all the right reasons. Family. Life. You know, &lt;a href="http://www.oraclenerd.com/2011/09/oow-2011-no-soup-for-you.html"&gt;the things that matters&lt;/a&gt;. And some reasons slightly off. Like ADF. Yup, &lt;i&gt;Oracle Application Development Framework&lt;/i&gt;. Just writing it out loud like that is probably going to make Dimitri's &lt;a href="http://dgielis.blogspot.com/2011/09/updates-on-apexblogs.html"&gt;blog feed processor choke&lt;/a&gt; all over again, and get this blog instantly blacklisted from anything slightly PL/SQL related for all time.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Old Road&lt;/b&gt;&lt;br /&gt;My fascination with APEX has not diminished by far, and I hope to revisit extremelyproductivefundevelopingsmoothookingdatacentricapplications-land in the future. But for now, the "other" framework is more than enough.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The New Direction&lt;/b&gt;&lt;br /&gt;Being a consultant will make you do strange things from time to time. This is one of those. Am I a Java-programmer? No. Do I know JSF? No. Am I a Groovy programmer? No. Am I at least proficient at Expression Language? No. How about ADF-skills? Well, some. I guess. Luckily, Oracle says I only need &lt;a href="http://blogs.oracle.com/jheadstart/entry/how_to_become_an_oracle"&gt;a week, or at least a day&lt;/a&gt; to become an expert. Hmm. I had to pass on OOW last year, due to a project using ADF... Catch 22 all over again.&lt;br /&gt;&lt;br /&gt;I cannot say I am that sorry, actually. Learning new stuff (i.e. banging my head against a wall), and really trying to understand it (i.e. repeatedly), is part of my nature.&lt;br /&gt;&lt;br /&gt;It was a professional change long overdue.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Blog&lt;/b&gt;&lt;br /&gt;I will continue to post my progress with ADF (or lack thereof) here, feel free to divert this blog from your favorite feed reader... Oh, and some APEX stuff will probably still make it here from time to time.&lt;br /&gt;&lt;br /&gt;You can go wash your eyes now :D&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-5236279116993298041?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/5236279116993298041/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2012/01/updated-statement-of-direction.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/5236279116993298041'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/5236279116993298041'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2012/01/updated-statement-of-direction.html' title='Updated Statement of Direction'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-hkR_spmFDes/Tpgr2IoWO5I/AAAAAAAAALM/QJnX3nk0yeQ/s72-c/startup.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-9056247040892178739</id><published>2011-10-22T21:37:00.000+02:00</published><updated>2011-10-22T21:37:54.219+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xe'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><title type='text'>Install, Rinse, Repeat</title><content type='html'>Every time I start installing Oracle on Linux I always end up with the same dark thoughts: Why does it have to be so &lt;i&gt;hard&lt;/i&gt;! An the worst part is: It isn't! But let me be precise with the last statement: It isn't hard if you &lt;i&gt;know how&lt;/i&gt;! But in the beginning, you don't, so it is... You get the picture.&lt;br /&gt;&lt;br /&gt;On my last endeavor to create a shiny new VirtualBox machine, I decided to take notes.&amp;nbsp;Here is what I aimed at:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-Vpl2gRAKEH4/TqHi0gU-gdI/AAAAAAAAAZc/sMbrUft5AKk/s1600/001.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="230" src="http://1.bp.blogspot.com/-Vpl2gRAKEH4/TqHi0gU-gdI/AAAAAAAAAZc/sMbrUft5AKk/s400/001.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Yes, both Apache httpd and Apache Tomcat present. I needed a versatile development vm, with a known (to me) web server, and a known (again, to me) web container working with APEX, the APEX Listener and Oracle 11g XE database.&lt;br /&gt;&lt;br /&gt;There &lt;i&gt;will&lt;/i&gt; be typos, there will &lt;strike&gt;probably&lt;/strike&gt; &lt;strike&gt;most likely&lt;/strike&gt;&amp;nbsp;of course&amp;nbsp;be things I have forgotten to write down. On the whole, I hope it will save you some grey hairs (or in my case, loss of gray hairs :-))&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Beware, the iframe controls will not be very responsive before the document has completed loading, and the document is quite large. Go for the direct access if it gets too troublesome...&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Direct access to the Google doc is here:&amp;nbsp;&lt;a href="https://docs.google.com/document/pub?id=19TZ5Yqa-W6edWvPhWtH9d0D3iPEKFkGwckncUDKCaIY"&gt;https://docs.google.com/document/pub?id=19TZ5Yqa-W6edWvPhWtH9d0D3iPEKFkGwckncUDKCaIY&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Here goes:&lt;br /&gt;&lt;br /&gt;&lt;iframe height="800px" src="https://docs.google.com/document/pub?id=19TZ5Yqa-W6edWvPhWtH9d0D3iPEKFkGwckncUDKCaIY&amp;amp;embedded=true" width="900px"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-9056247040892178739?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/9056247040892178739/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2011/10/install-rinse-repeat.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/9056247040892178739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/9056247040892178739'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2011/10/install-rinse-repeat.html' title='Install, Rinse, Repeat'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-Vpl2gRAKEH4/TqHi0gU-gdI/AAAAAAAAAZc/sMbrUft5AKk/s72-c/001.jpg' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-7188345996177137057</id><published>2011-10-21T10:25:00.000+02:00</published><updated>2011-10-21T10:25:51.028+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='hotkeys'/><title type='text'>Updated Hotkeys for APEX 4.1</title><content type='html'>I finally got around to updating the hotkeys userscript for Application Express 4.1. There were some changes in the APEX Builder html that made an update necessary.&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-flntDVTRlH4/TqB8JRYWotI/AAAAAAAAAZQ/E87zIaS7Jdo/s1600/hotkeys.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="300" src="http://3.bp.blogspot.com/-flntDVTRlH4/TqB8JRYWotI/AAAAAAAAAZQ/E87zIaS7Jdo/s400/hotkeys.jpg" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Cool &lt;a href="http://www.flickr.com/photos/josefstuefer/5137407"&gt;image by Josef Stuefer&lt;/a&gt;, available by &lt;a href="http://creativecommons.org/licenses/by/2.0/"&gt;CC BY 2.0&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;I have to say, that after I started using shortcut keys in the builder I never looked back. These days I get confused when developing in a browser where the script is not installed.&lt;br /&gt;&lt;br /&gt;The script can be downloaded here:&amp;nbsp;&lt;a href="http://userscripts.org/scripts/show/115495"&gt;http://userscripts.org/scripts/show/115495&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Just click the big green Install-button, and you can start using it. The script is tested with Google Chrome, but should work equally well with Firefox.&lt;br /&gt;&lt;br /&gt;The keys are the same old:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;F8: Run Page&lt;/li&gt;&lt;li&gt;F9: Apply Changes&lt;/li&gt;&lt;li&gt;Alt + F9: Clicks the highlighted button&lt;/li&gt;&lt;li&gt;Shift + F9: Cancel&lt;/li&gt;&lt;li&gt;Alt + PageDown: Next&lt;/li&gt;&lt;li&gt;Alt + PageUp: Previous&lt;/li&gt;&lt;li&gt;Ctrl + Shift + S: Shared Components&lt;/li&gt;&lt;li&gt;Ctrl + Shift + E: Edit Page&lt;/li&gt;&lt;li&gt;Ctrl + Shift + Z: Toggle return to page on/off&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;If you disagree with the key mapping; then change them! The source is there for the changing :-)&lt;br /&gt;&lt;br /&gt;If there are any grievous errors, then do not hesitate to call...&lt;br /&gt;&lt;br /&gt;The old hotkeys userscript for APEX 4.0 is still available here:&amp;nbsp;&lt;a href="http://userscripts.org/scripts/show/81058"&gt;http://userscripts.org/scripts/show/81058&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-7188345996177137057?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/7188345996177137057/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2011/10/updated-hotkeys-for-apex-41.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7188345996177137057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7188345996177137057'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2011/10/updated-hotkeys-for-apex-41.html' title='Updated Hotkeys for APEX 4.1'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-flntDVTRlH4/TqB8JRYWotI/AAAAAAAAAZQ/E87zIaS7Jdo/s72-c/hotkeys.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-135376227252679786</id><published>2011-10-20T21:14:00.000+02:00</published><updated>2011-10-20T21:14:06.464+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='file upload'/><category scheme='http://www.blogger.com/atom/ns#' term='listener'/><title type='text'>Multiple File Upload with jQuery and APEX Listener</title><content type='html'>Did you ever want to do this?&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;object class="BLOGGER-youtube-video" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" data-thumbnail-src="http://3.gvt0.com/vi/E7MvsY5Tkl8/0.jpg" height="266" width="320"&gt;&lt;param name="movie" value="http://www.youtube.com/v/E7MvsY5Tkl8&amp;fs=1&amp;source=uds" /&gt;&lt;param name="bgcolor" value="#FFFFFF" /&gt;&lt;embed width="320" height="266"  src="http://www.youtube.com/v/E7MvsY5Tkl8&amp;fs=1&amp;source=uds" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;&lt;br /&gt;Envious of the fancy multiple image upload functionality I saw from Google+, I set out to do the same. This is more of a log of observations than a full recipe. There is no APEX involved but the listener. To follow this you have to have an APEX Listener v1.1 (or higher) up and running against an Oracle 10g (or higher) database. &lt;i&gt;This will not work with Internet Explorer or Opera (not multiple files), I have only tested on Google Chrome.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;jQuery to the Rescue&lt;/b&gt;&lt;br /&gt;jQuery has a lot (capital L type of lot) of plugins, one really nice I stumbled upon was Sebastian Tschan's jQuery-file-upload. Demo is &lt;a href="http://aquantum-demo.appspot.com/file-upload"&gt;here&lt;/a&gt;, download is &lt;a href="https://github.com/blueimp/jQuery-File-Upload/archives/master"&gt;here&lt;/a&gt;, documentation is &lt;a href="https://github.com/blueimp/jQuery-File-Upload/wiki"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Setup Database Objects&lt;/b&gt;&lt;br /&gt;The images has to go somewhere, and I created an images table in the hr-schema:&lt;br /&gt;&lt;pre class="brush: sql"&gt;   --drop table images cascade constraints;&lt;br /&gt;   --drop sequence images_pk_seq;&lt;br /&gt;&lt;br /&gt;   create table images (&lt;br /&gt;      image_id        number,&lt;br /&gt;      content_type    varchar2(255),&lt;br /&gt;      filename        varchar2(4000),&lt;br /&gt;      image           blob,&lt;br /&gt;      constraint img_pk primary key (image_id) using index)&lt;br /&gt;   /&lt;br /&gt;&lt;br /&gt;   create sequence images_pk_seq;&lt;br /&gt;&lt;br /&gt;   create or replace trigger trg_images_bi &lt;br /&gt;   before insert on images &lt;br /&gt;   referencing new as new old as old for each row&lt;br /&gt;   declare&lt;br /&gt;      l_new_id number;&lt;br /&gt;   begin&lt;br /&gt;      if :new.image_id is null then&lt;br /&gt;         select images_pk_seq.nextval into l_new_id from dual;&lt;br /&gt;         :new.image_id := l_new_id;&lt;br /&gt;      end if;&lt;br /&gt;   exception&lt;br /&gt;   when others then&lt;br /&gt;      raise;&lt;br /&gt;   end trg_images_bi;&lt;br /&gt;   /&lt;br /&gt;&lt;br /&gt;   alter trigger trg_images_bi enable;&lt;br /&gt;&lt;br /&gt;   grant all on hr.images to APEX_PUBLIC_USER;&lt;br /&gt;&lt;/pre&gt;You have to grant insert/delete at the least to the user defined in the listener (normally APEX_PUBLIC_USER).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Setup Imagehandler Resource Template&lt;/b&gt;&lt;br /&gt;This is where the actual data manipulation is going to happen. You can import all resource templates in this example by importing this file: &lt;a href="http://apex.oracle.com/pls/apex/p?n=20244374429223405662"&gt;resources.zip&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Log into listenerAdmin (http://myhost/apex/listenerAdmin).&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Click Resource Template&lt;/li&gt;&lt;li&gt;Click Add Resource Template&lt;/li&gt;&lt;li&gt;Click Add Handler, and choose method POST&lt;/li&gt;&lt;li&gt;The Post handler will handle all uploads and inserts&lt;/li&gt;&lt;li&gt;Click Add Parameter, and set the following: status,&amp;nbsp;X-APEX-STATUS-CODE, Header, Out, Integer&lt;/li&gt;&lt;li&gt;Click Add Parameter, and set the following:&amp;nbsp;location,&amp;nbsp;X-APEX-FORWARD, Header, Out, String&lt;/li&gt;&lt;li&gt;Click Add Parameter, and set the following: file, filename, Header, In, String (This should actually map to the filename, and be inserted with the rest of the data. For some reason, this does not work, and I'm too lazy to find out why...)&lt;/li&gt;&lt;li&gt;Add the data as seen below, and click Save&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-Vwagvxzm9Q0/Tp_uuKmRlxI/AAAAAAAAAY4/_z8jq9IAB28/s1600/imagehandler+-+post.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://4.bp.blogspot.com/-Vwagvxzm9Q0/Tp_uuKmRlxI/AAAAAAAAAY4/_z8jq9IAB28/s400/imagehandler+-+post.jpg" width="385" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;   declare&lt;br /&gt;      l_image_id number;&lt;br /&gt;   begin&lt;br /&gt;      insert into hr.images (content_type, image, filename) values (:contentType, :body, :file) returning image_id into l_image_id;&lt;br /&gt;      commit;&lt;br /&gt;      /* This status will actually not be seen, as we have to redirect to get control of response */&lt;br /&gt;      :status := 201;&lt;br /&gt;      :location := 'imagehandler/response/'||l_image_id;&lt;br /&gt;   end;&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Click the imagehandler resource template&lt;/li&gt;&lt;li&gt;Click Add Handler&lt;/li&gt;&lt;li&gt;Choose method DELETE&lt;/li&gt;&lt;li&gt;Click Add Parameter, and set the following:&amp;nbsp;imageid, &amp;lt;blank&amp;gt;, URI, IN, Integer&lt;/li&gt;&lt;li&gt;Click Add Parameter, and set the following:&amp;nbsp;status,&amp;nbsp;X-APEX-STATUS-CODE, Header, OUT, Integer&lt;/li&gt;&lt;li&gt;Add the data as seen below, click Save&lt;/li&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-N2NAfZwmBB4/Tp_utO-HsYI/AAAAAAAAAYo/SaCoyar8YXc/s1600/imagehandler+-+delete.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://3.bp.blogspot.com/-N2NAfZwmBB4/Tp_utO-HsYI/AAAAAAAAAYo/SaCoyar8YXc/s400/imagehandler+-+delete.jpg" width="385" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;   begin&lt;br /&gt;      delete from hr.images where image_id = :imageid; &lt;br /&gt;      commit;&lt;br /&gt;      :status := 200;&lt;br /&gt;   end;&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Click the imagehandler resource template&lt;/li&gt;&lt;li&gt;Click Add Parameter, and set the following:&amp;nbsp;imageid, &amp;lt;blank&amp;gt;, URI, IN, Integer&lt;/li&gt;&lt;li&gt;Click the GET tabAdd the code as seen below, click Save&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-VlrFN2nIhUc/Tp_utrxVtHI/AAAAAAAAAYw/nQVWoGUFnSc/s1600/imagehandler+-+get.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://3.bp.blogspot.com/-VlrFN2nIhUc/Tp_utrxVtHI/AAAAAAAAAYw/nQVWoGUFnSc/s400/imagehandler+-+get.jpg" width="385" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class="brush: sql"&gt;   select content_type, image&lt;br /&gt;   from   hr.images&lt;br /&gt;   where  image_id = :imageid&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Setup Imagehandler Response Resource Template&lt;/b&gt;&lt;br /&gt;I have yet to find a way to get a PL/SQL Block resource to respond with a json array, only single json attributes. It is a shame really, giving control of the response to the developer would have helped a lot. As it stands now, the client will not get a proper response (201 - created), and we have to add an extra resource template. If there is a way, please do tell, the docs gives nada here.&lt;br /&gt;&lt;br /&gt;This jQuery plugin requires a json array, and in order to do that, we have to cheat.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Click Add Resource&lt;/li&gt;&lt;li&gt;Click Add Parameter, and set the following:&amp;nbsp;imageid, &amp;lt;blank&amp;gt;, URI, IN, Integer&lt;/li&gt;&lt;li&gt;Template Add the code as seen below, click Save&lt;/li&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-Y-VUNcZXI2U/Tp_uuVPWo2I/AAAAAAAAAZA/fbskPfZTbnQ/s1600/imagehandler+-+response.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="400" src="http://3.bp.blogspot.com/-Y-VUNcZXI2U/Tp_uuVPWo2I/AAAAAAAAAZA/fbskPfZTbnQ/s400/imagehandler+-+response.jpg" width="385" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre class="brush: sql"&gt;   select 'application/json','[{"name":"'||:imageid||'.jpg","size":'||dbms_lob.getlength(img.image)||',"url":"\/apex\/imagehandler\/'||image_id||'","thumbnail_url":"\/apex\/imagehandler\/'||image_id||'","delete_url":"\/apex\/imagehandler/'||image_id||'","delete_type":"DELETE"}]'&lt;br /&gt;   from hr.images img&lt;br /&gt;   where img.image_id = :imageid&lt;br /&gt;&lt;/pre&gt;Looks a bit greek, I know. You can look at the server response for more details, but basically you are returning file name, download url, thumbnail url and delete uri/method in a json array.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Setup jQuery Plugin&lt;/b&gt;&lt;br /&gt;Nothing to it really, download and unzip the archive somewhere reachable on a web server or appserver. In this example I unzipped and renamed the folder so that I could reach the example/index.html on http://myhost/js/multiupload/example/index.html.&lt;br /&gt;&lt;br /&gt;You have to make one change in the example/index.html to make it work: Point the form action to your image handler:&lt;br /&gt;&lt;blockquote&gt;&amp;lt;form action="/apex/imagehandler/0" method="POST" enctype="multipart/form-data"&amp;gt;&amp;nbsp;&lt;/blockquote&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Notice how we have to add {imageid} at the end, parameters are not optional (at least not in this version of the listener), so if it is left out, it will not match the resource templates URI pattern.&amp;nbsp;&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;Ready for a test run!&amp;nbsp;&lt;/div&gt;&lt;br /&gt;Some notes on working with the Application Express Listener&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The documentation is a bit lacking (to say the least)&lt;/li&gt;&lt;li&gt;Debugging is hard, 500 server error does not say much&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;This is a very simplistic example, there are no security involved, and it is not embedded into an APEX page. Anyhow, &amp;nbsp;as a quick example, it (barely) serves the purpose.&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-135376227252679786?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/135376227252679786/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2011/10/multiple-file-upload-with-jquery-and.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/135376227252679786'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/135376227252679786'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2011/10/multiple-file-upload-with-jquery-and.html' title='Multiple File Upload with jQuery and APEX Listener'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-Vwagvxzm9Q0/Tp_uuKmRlxI/AAAAAAAAAY4/_z8jq9IAB28/s72-c/imagehandler+-+post.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-4683105127393654622</id><published>2010-11-08T09:42:00.000+01:00</published><updated>2010-11-08T09:42:39.727+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='apex40'/><category scheme='http://www.blogger.com/atom/ns#' term='pagination'/><category scheme='http://www.blogger.com/atom/ns#' term='reports'/><title type='text'>Refresh Report Region and Pagination</title><content type='html'>When using the refresh action to refresh reports in Oracle APEX 4.0, you inevitably will encounter the problem of lost pagination. The refresh action does really refresh, regardless of which page of the report the user was currently watching.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;It Just Won't Tell Me&lt;/b&gt;&lt;br /&gt;APEX knows the current pagination of a report. If you go to another page and back, or just press F5, you will see that APEX knows which page of your report you were on. Where can I get that user data? I have looked at the documented API's, and the closest thing I can see, is the apex_application.g_flow_current_min_row. The problem is that this is only available at render time and maps to pg_min_row request value. Otherwise, I only get the value 1 (first row).&lt;br /&gt;&lt;br /&gt;It knows, but won't tell me!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Dirty Approach&lt;/b&gt;&lt;br /&gt;If there is a will, there is a way. I looked into the APEX tables, and especially wwv_flow_data. If you look, you will see a record containing the user session instance and report region id. Item value will be a colon separated value starting with the current min row, followed by current max rows (probably). That looks useful, but you need to keep your head about this. The value column is a CLOB data type, and a pretty special format, so if you attempt to use that data, be prepared to do a rewrite next time you patch your APEX installation.&lt;br /&gt;&lt;br /&gt;If you notice in your pagination scheme, the pagination link executes a javascript called $a_report_Split (basically a wrapper for old (but rewritten) $a_report). So if I could use wwv_flow_data to get the second parameter of $a_report_Split right (&amp;lt;pMin&amp;gt;_&amp;lt;pMax&amp;gt;_&amp;lt;pFetched&amp;gt;), then I would be good to go..? For a while, anyway. $a_report_Split is not a documented API javascript function, and may also be subject to change in the upcoming releases&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Dirty Solution&lt;/b&gt;&lt;br /&gt;Access to wwv_flow_data directly is not something you want to do in a normal APEX installation. Creating a very single minded function in a privileged schema (granted select on wwv_flow_data) and granting access to the function to my application schema was the best I could come up with.&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace function &amp;lt;privileged schema user&amp;gt;.get_current_pagination (    &lt;br /&gt;         p_region_id    in       number&lt;br /&gt;      ) return varchar2 is&lt;br /&gt;&lt;br /&gt;         l_ret          varchar2(255);&lt;br /&gt;         l_session_id   number := v('SESSION'); -- setting directly prevents unauthorized use&lt;br /&gt;         l_value_tab    apex_application_global.vc_arr2;&lt;br /&gt;&lt;br /&gt;         cursor c_region (&lt;br /&gt;            b_session_id      in       number&lt;br /&gt;         ,  b_region_id       in       number&lt;br /&gt;         ) is &lt;br /&gt;            select item_value&lt;br /&gt;              from apex_040000.wwv_flow_data&lt;br /&gt;            where  flow_instance = b_session_id&lt;br /&gt;              and  item_id = b_region_id;&lt;br /&gt;      begin&lt;br /&gt;         for r in c_region(l_session_id,p_region_id)&lt;br /&gt;         loop&lt;br /&gt;            l_value_tab := apex_util.string_to_table(r.item_value,':');&lt;br /&gt;         end loop;&lt;br /&gt;&lt;br /&gt;         if l_value_tab.exists(1)&lt;br /&gt;         then&lt;br /&gt;            l_ret := l_value_tab(1) || '_' || l_value_tab(2) || '_' || l_value_tab(2);&lt;br /&gt;         end if;&lt;br /&gt;         &lt;br /&gt;         return l_ret;&lt;br /&gt;      end;                            &lt;br /&gt;      /&lt;br /&gt;&lt;br /&gt;      grant execute on &amp;lt;privileged schema user&amp;gt;.get_current_pagination to &amp;lt;application schema user&amp;gt;;&lt;br /&gt;&lt;/pre&gt;That taken care of, the next thing is to get that value and use it in a call to $a_report_Split.&lt;br /&gt;&lt;br /&gt;Follow these steps in your APEX builder: &lt;br /&gt;&lt;ul&gt;&lt;li&gt;Create two hidden page items called P1_REPORT_REGION_ID and P1_REPORT_SPLIT_PARAMS&lt;/li&gt;&lt;li&gt;Create a dynamic action to be called when you would like your report to be refreshed&lt;/li&gt;&lt;li&gt;Create three true actions:&lt;/li&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;li&gt;First action to set P_REPORT_REGION_ID with the id of the report region:&lt;br /&gt;Action: SetValue, Type: Javascript Expression, Javascript Expression: $('#&amp;lt;report region static id&amp;gt;').closest('div').attr('id').replace(/[^0-9]/g,'').&lt;br /&gt;(If you set this item value as a computation before header in APEX, you can skip this action)&lt;/li&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/TNer6THRkyI/AAAAAAAAAJI/25frmQDASFQ/s1600/action001.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="417" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/TNer6THRkyI/AAAAAAAAAJI/25frmQDASFQ/s640/action001.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Second action is to set P1_REPORT_SPLIT_PARAMS with values corresponding to the current report page:&lt;br /&gt;Action: SetValue, Type: PL/SQL Function Body, PL/SQL Function Body: return &amp;lt;privileged schema user&amp;gt;.get_current_pagination(:p1_report_region_id);&lt;/li&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/TNesP3ODCKI/AAAAAAAAAJM/HLoC5srwtjc/s1600/action002.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="516" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/TNesP3ODCKI/AAAAAAAAAJM/HLoC5srwtjc/s640/action002.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Third action is to call the $a_report_Split javascript function:&lt;br /&gt;Action: Execute Javascript Code, Code: $a_report_Split($v('P1_REPORT_REGION_ID'),$v('P1_REPORT_SPLIT_PARAMS'));&lt;/li&gt;&lt;ul&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TNesUloUQ2I/AAAAAAAAAJQ/dFHknZWPHWU/s1600/action003.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="297" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TNesUloUQ2I/AAAAAAAAAJQ/dFHknZWPHWU/s640/action003.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;That should be it. &lt;br /&gt;&lt;br /&gt;Naturally, I have cannot create a demo page on apex.oracle.com for this, as access to wwv_flow_data is somewhat scarce in that environment (and rightfully so).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Why Didn't He Just...&lt;/b&gt;&lt;br /&gt;If you know of a better way to do this (and better than refreshing the whole page :-)), then please let me know! And if it is possible through documented API's, that would be grand.&lt;br /&gt;&lt;br /&gt;If this is the closest I get, and any of the APEX team is reading this, then please consider this an enhancement request :-P&lt;br /&gt;&lt;br /&gt;For future reference: Oracle Application Express 4.0.1.00.03&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-4683105127393654622?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/4683105127393654622/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/11/refresh-report-region-and-pagination.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/4683105127393654622'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/4683105127393654622'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/11/refresh-report-region-and-pagination.html' title='Refresh Report Region and Pagination'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_8GtRvOur8pQ/TNer6THRkyI/AAAAAAAAAJI/25frmQDASFQ/s72-c/action001.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-4040482367881720553</id><published>2010-11-05T15:23:00.000+01:00</published><updated>2010-11-05T15:23:56.759+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='backup'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Paranoia 101 - or protecting your investment</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;In corporate application development, a versioning and backup strategy is pretty mandatory. But what about all those small personal projects? The only thing you can be really certain of when using a laptop/desktop for your pet projects, is that eventually your machine is going to die. How much work gets lost when it does?&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/TNQHfMbScxI/AAAAAAAAAJE/qYSaKSRXqnQ/s1600/inthecloud.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="239" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/TNQHfMbScxI/AAAAAAAAAJE/qYSaKSRXqnQ/s320/inthecloud.jpg" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I use SVN for version control as a rule. I also use the APEX export and splitter utility to capture my local Oracle APEX applications into subversion repositories. Sometimes I even remember to put some of my more important repositories on Amazon S3 for safe storage. The APEX export is (very) loosely based on &lt;a href="http://jes.blogs.shellprompt.net/2006/12/12/backing-up-your-applications"&gt;a blog post&lt;/a&gt; by John Scott back in 2006.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;A word of cation: This just my naive approach, use it at your own risk! Be sure to test whether a restore actually works, equally important as the actual backing up :-)&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Start of an "Automated Approach"&lt;/b&gt;&lt;br /&gt;While the manual steps described above will get you there, it is also a bit more work than I appreciate. In this post I will describe a more automated approach to ease your local APEX application backups (and anything else you put in your svn repository as well). A bit of pain to set it up, but once there, it is all plain sailing.&lt;br /&gt;&lt;br /&gt;I do my development in Windows mainly, so all OS specifics described below are set in that context. This approach is viable for your favorite nix OS also. A tweaking of the batch scripts should do it.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Subversion&lt;/b&gt;&lt;br /&gt;You need a command line tool to manipulate subversion repositories, download Subversion from &lt;a href="http://subversion.apache.org/packages.html"&gt;http://subversion.apache.org/packages.html&lt;/a&gt;. I currently use the one from &lt;a href="http://www.collab.net/downloads/subversion/"&gt;CollabNet&lt;/a&gt;. It should not matter much which one you choose. There are a number of online resources on how to install svn and create repositories (yes, even for Windows...).&lt;br /&gt;&lt;br /&gt;Include the subversion bin directory to your path environment variable. After installation you should be able to execute svn commands from a cmd window:&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;svn help&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;svnadmin help&lt;/div&gt;&lt;br /&gt;&lt;b&gt;APEX Export Utility&lt;/b&gt;&lt;br /&gt;This comes with the APEX installation files, and has the ability to extract single APEX applications (or whole instances) from a command line. An APEX splitter tool is also included, which together with a subversion repository enables you to track all the tiny changes in your applications.&lt;br /&gt;&lt;br /&gt;In Oracle APEX 4.0, it accepts more modern Oracle jdbc libraries, in APEX 3.2 you had to locate classes12.jar (predates even the jdbc libraries that comes ships with Oracle XE). You can download all jdbc libraries here: &lt;a href="http://www.oracle.com/technetwork/database/features/jdbc/index-091264.html"&gt;http://www.oracle.com/technetwork/database/features/jdbc/index-091264.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Since technetwork is constantly shifting about: If the link does not work, try to locate the Software Download Index, and search for JDBC.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Amazon S3&lt;/b&gt;&lt;br /&gt;You need to set up an Amazon account before you can use Amazon Simple Storage Service (S3). If you have ever ordered books from Amazon, you probably already have an account, but it may be necessary to confirm payment methods.&lt;br /&gt;&lt;br /&gt;Storage prices are not that high (at the moment). My reasoning is that I will suffer more if I don't backup my stuff to safe storage, than a tiny monthly bill could ever make me.&lt;br /&gt;&lt;br /&gt;In order for this to work, you have to obtain an Acceskey:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Log on to &lt;a href="http://aws.amazon.com/account/"&gt;http://aws.amazon.com/account/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Go to Security Credentials &lt;/li&gt;&lt;li&gt;Go to Access Credentials&lt;/li&gt;&lt;li&gt;If you do not have any previously generated accesskeys, then click Create a new Access Key&lt;/li&gt;&lt;li&gt;Copy both Access Key ID, and Secret Access Key&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Beware to keep this information to yourself.&lt;br /&gt;&lt;br /&gt;Create a bucket where you would like your backups to be stored, and take a note of the name.&lt;br /&gt;&lt;br /&gt;The thing with Amazon S3, is its interfaces (REST style HTTP or SOAP) and storage objects organized in buckets. This basically means that you will require a third party tool (or do some serious programming yourself) to easily use the service.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;JetS3t - Synchronize&lt;/b&gt;&lt;br /&gt;I settled for &lt;a href="http://jets3t.s3.amazonaws.com/downloads.html"&gt;JetS3t&lt;/a&gt;. I will not vouch for this tool in any way. I do not know who develops it, I do not know if it sends my credentials to other people or companies. I basically just hope and pray.&lt;br /&gt;&lt;br /&gt;When credentials potentially can be exploited in a very direct manner to get at your most precious, and even conducting illegal activities on your behalf, it is time to draw a deep breath. Maybe uploading your file manually wasn't such a bad idea anyway? As I said, I just hope and prey. I hope "someone" will scan the source code, and "someone" will scream if anomalies appears. I hope.&lt;br /&gt;&lt;br /&gt;I use a part of JetS3t called &lt;a href="http://jets3t.s3.amazonaws.com/applications/synchronize.html"&gt;Synchronize&lt;/a&gt;. It has a very easy cli that suites my need for an automated backup process. Installation is just unzipping, but you have to tweak the properties a bit for it to work.&lt;br /&gt;&lt;br /&gt;These are the changes I made in jets3t\configs\jets3t.properties-file:&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;s3service.default-bucket-location=EU&lt;/span&gt; (well, bacause it's the easiest to me)&lt;br /&gt;&lt;br /&gt;And these are the changes in jets3t\configs\synchronize.properties&lt;br /&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;accesskey=&amp;lt;your accesskey&amp;gt;&lt;/div&gt;&lt;div style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace;"&gt;secretkey=&amp;lt;your secret accesskey&amp;gt;&lt;/div&gt;&lt;br /&gt;You can also point Synchronize to an encrypted file containing your access keys using the --credentials switch.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;7-Zip&lt;/b&gt;&lt;br /&gt;To zip my svn repository dumps, I use &lt;a href="http://www.7-zip.org/"&gt;7-Zip&lt;/a&gt;. Very easy, straight forward open source zipping tool with a command line interface. Make sure you can reach 7z from a command prompt before proceeding, else you have to modify the batch scripts below and add the path.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Java&lt;/b&gt;&lt;br /&gt;Make sure you have java 1.5 or higher installed in you system, and that you can write java -version from a command prompt.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Batch scripts&lt;/b&gt;&lt;br /&gt;This is where it gets interesting.&lt;br /&gt;&lt;br /&gt;I start off by creating a directory where my backup scripts will reside. It will also serve as a staging area before files are uploaded to Amazon S3. This is the current structure I have:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;backup&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Apex (I need this directory for temporary check out/in)&lt;/li&gt;&lt;li&gt;oracle (this is where I copy the export/splitter utilities from &amp;lt;apex install dir&amp;gt;/apex/utilities/oracle)&lt;/li&gt;&lt;ul&gt;&lt;li&gt;apex&lt;/li&gt;&lt;ul&gt;&lt;li&gt;APEXExport.class&lt;/li&gt;&lt;li&gt;APEXExportSplitter.class   &lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;classes12.jar (jdbc drivers)&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;br /&gt;I then place four backup scripts directly into the backup directory.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;backup_apex_app.bat&lt;/b&gt;&lt;br /&gt;This script is the one responsible for checking out the APEX files from svn, extracting the current version of the APEX applications from the database, and checking it into svn again. It takes a parameter (%1%) containing APEX application id. All other necessary parameters will be set in the master.bat below.&lt;br /&gt;&lt;pre class="brush: js"&gt;REM ******** Start Backup APEX apps ************&lt;br /&gt;set APEX_APP_ID=%1%&lt;br /&gt;&lt;br /&gt;REM ** svn checkout&lt;br /&gt;svn checkout file:///%APEX_REPOS_PATH% %APEX_SPLITTER_DIR%&lt;br /&gt;&lt;br /&gt;REM ** Export APEX applications&lt;br /&gt;set CLASSPATH=%CLASSPATH%;%APEX_BACKUP_DIR%\classes12.jar;&lt;br /&gt;java oracle.apex.APEXExport -db %DB_JDBC_CONN_STR% -user %DB_USER% -password %DB_USER_PWD% -applicationid %APEX_APP_ID%&lt;br /&gt;java oracle.apex.APEXExportSplitter f%APEX_APP_ID%.sql&lt;br /&gt;&lt;br /&gt;REM ** Copy files to svn directory&lt;br /&gt;xcopy %APEX_BACKUP_DIR%\f%APEX_APP_ID%.sql %APEX_SPLITTER_DIR% /Y&lt;br /&gt;xcopy %APEX_BACKUP_DIR%\f%APEX_APP_ID% %APEX_SPLITTER_DIR%\f%APEX_APP_ID% /S /E /Y&lt;br /&gt;&lt;br /&gt;REM ** Remove superflous files&lt;br /&gt;rmdir %APEX_BACKUP_DIR%\f%APEX_APP_ID%\ /s /q&lt;br /&gt;del %APEX_BACKUP_DIR%\f%APEX_APP_ID%.sql&lt;br /&gt;&lt;br /&gt;REM ** Add and check in files to repository&lt;br /&gt;cd %APEX_SPLITTER_DIR%&lt;br /&gt;svn add * --force&lt;br /&gt;svn ci -m "Automated backup"&lt;br /&gt;cd %APEX_BACKUP_DIR%&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;backup_svn_repos.bat&lt;/b&gt;&lt;br /&gt;This is the script responsible for dumping the svn repository and zipping the dump file. I suppose this could have been done with HOTCOPY as well. Old habits and such... You can include filters here to extract only the project you are interested in. All necessary parameters will be set from the master.bat script below.&lt;br /&gt;&lt;pre class="brush: js"&gt;REM ******** Start dumping and zipping repository ************&lt;br /&gt;&lt;br /&gt;REM ** Remving old dumpfile&lt;br /&gt;del %APEX_BACKUP_DIR%\%APEX_REPOS_DUMPFILE%.zip&lt;br /&gt;&lt;br /&gt;REM ** Dumping svn repository&lt;br /&gt;svnadmin dump %APEX_REPOS% &amp;gt; %APEX_BACKUP_DIR%\%APEX_REPOS_DUMPFILE%.dump&lt;br /&gt;&lt;br /&gt;REM ** zipping repository dump&lt;br /&gt;7z.exe a %APEX_REPOS_DUMPFILE%.zip %APEX_REPOS_DUMPFILE%.dump&lt;br /&gt;&lt;br /&gt;REM ** Removing old dump file&lt;br /&gt;del %APEX_BACKUP_DIR%\%APEX_REPOS_DUMPFILE%.dump&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;backup_svn_to_s3.bat&lt;/b&gt;&lt;br /&gt;This script is responsible for sending the zipped repository dump to Amazon S3.&lt;br /&gt;&lt;pre class="brush: js"&gt;REM ******** Backup SVN repository to S3 ************&lt;br /&gt;&lt;br /&gt;REM ** Synchronizing svn repository dumpfile&lt;br /&gt;%JETS3T_PATH%\bin\synchronize.bat UP %BUCKET_NAME%/%BUCKET_PATH% %APEX_BACKUP_DIR%\%APEX_REPOS_DUMPFILE%.zip&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;master.bat&lt;/b&gt;&lt;br /&gt;This is the script which ties it all together. If you want to test the separate scripts, just comment out the scripts (rem) you don't want to call. Chances are there is going to be some issues with the parameters the first run, so testing in small stages is key when debugging (it produces quite a bit of output during a normal run).&lt;br /&gt;&lt;br /&gt;In this script it is necessary for you to adjust all parameters to your own environment. Example values are included in the sample below.&lt;br /&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;rem *********** Setting environment parameters *******************&lt;br /&gt;&lt;br /&gt;rem ** backup directories&lt;br /&gt;set APEX_BACKUP_DIR=&amp;lt;your backup dir, like c:\development\backup&amp;gt;&lt;br /&gt;set APEX_SPLITTER_DIR=%APEX_BACKUP_DIR%\apex&lt;br /&gt;&lt;br /&gt;rem ** path to apex application directory inside svn repository&lt;br /&gt;set APEX_REPOS=&amp;lt;the actual path of your svn repository, like c:\SVN\myapprepos&amp;gt;&lt;br /&gt;set APEX_REPOS_PATH=&amp;lt;Full path to your APEX folder inside your svn repository, note the forward slashes, like c:/SVN/myapprepos/trunc/apex&amp;gt;&lt;br /&gt;set APEX_REPOS_DUMPFILE=&amp;lt;name of your repository dump file, like svn_repos, without post-fix/file type&amp;gt;&lt;br /&gt;&lt;br /&gt;rem ** connect string format host:port:sid&lt;br /&gt;set DB_JDBC_CONN_STR=&amp;lt;jdbc connect string, like localhost:1521:orcl3&amp;gt;&lt;br /&gt;set DB_USER=&amp;lt;username, like scott&amp;gt;&lt;br /&gt;set DB_USER_PWD=&amp;lt;password, like tiger&amp;gt;&lt;br /&gt;&lt;br /&gt;rem ** S3&lt;br /&gt;set JETS3T_PATH=&amp;lt;full path to your jets3t unzip dir, like C:\development\backup\jets3t&amp;gt;&lt;br /&gt;set BUCKET_NAME=&amp;lt;name of the Amazon S3 bucket you created earlier, like mysafebackup.backup.svn&amp;gt;&lt;br /&gt;set BUCKET_PATH=&amp;lt;name of path inside bucket, like svn_repos&amp;gt;&lt;br /&gt;&lt;br /&gt;rem ************ Calling batch files ************************&lt;br /&gt;call backup_apex_app.bat &amp;lt;application id&amp;gt;&lt;br /&gt;call backup_apex_app.bat &amp;lt;other application id&amp;gt;&lt;br /&gt;call backup_svn_repos.bat&lt;br /&gt;call backup_svn_to_s3.bat&lt;br /&gt;&lt;br /&gt;pause&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Good to Go!?!&lt;/b&gt;&lt;br /&gt;The only thing remaining to fully automated backups, is to schedule a regular execution of the script through the scheduler.&lt;br /&gt;&lt;br /&gt;I know I felt a whole deal better after I started using this backup strategy :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-4040482367881720553?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/4040482367881720553/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/11/paranoia-101-or-protecting-your.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/4040482367881720553'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/4040482367881720553'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/11/paranoia-101-or-protecting-your.html' title='Paranoia 101 - or protecting your investment'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_8GtRvOur8pQ/TNQHfMbScxI/AAAAAAAAAJE/qYSaKSRXqnQ/s72-c/inthecloud.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-7943850403455250456</id><published>2010-09-03T14:11:00.000+02:00</published><updated>2010-09-03T14:11:37.340+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='apex40'/><category scheme='http://www.blogger.com/atom/ns#' term='hotkeys'/><title type='text'>Updated APEX 4.0 Builder Hotkeys Userscript</title><content type='html'>I have updated my user script to include shortcut keys for navigating to Shared Components, Edit Page and Cancel.&lt;br /&gt;&lt;br /&gt;Current shortcut keys are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;F8: Run Page&lt;/li&gt;&lt;li&gt;F9: Apply Changes&lt;/li&gt;&lt;li&gt;Alt + F9: Clicks the highlighted button&lt;/li&gt;&lt;li&gt;Shift + F9: Cancel&lt;/li&gt;&lt;li&gt;Alt + PageDown: Next&lt;/li&gt;&lt;li&gt;Alt + PageUp: Previous&lt;/li&gt;&lt;li&gt;Ctrl + Shift + S: Shared Components&lt;/li&gt;&lt;li&gt;Ctrl + Shift + E: Edit Page&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;It is tested with Google Chrome and Firefox (Greasemonkey must be installed).&lt;br /&gt;&lt;br /&gt;You can download the script here: &lt;a href="http://userscripts.org/scripts/show/81058"&gt;http://userscripts.org/scripts/show/81058&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Press the big green Install-button to install the script in your browser :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-7943850403455250456?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/7943850403455250456/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/09/updated-apex-40-builder-hotkeys.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7943850403455250456'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7943850403455250456'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/09/updated-apex-40-builder-hotkeys.html' title='Updated APEX 4.0 Builder Hotkeys Userscript'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-1938532413963394124</id><published>2010-09-03T13:18:00.000+02:00</published><updated>2010-09-03T13:18:14.518+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='chrome'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><title type='text'>Developing APEX Applications in Google Chrome</title><content type='html'>Earlier this year I read Scott Wesleys post called &lt;a href="http://triangle-circle-square.blogspot.com/2010/03/why-i-use-google-chrome-for-oracle-apex.html"&gt;Why I use Google Chrome for Oracle Apex Development&lt;/a&gt;, but it did not quite register then. Recently I started using Google Chrome as my main APEX development browser.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/TIDVyz5TsUI/AAAAAAAAAIw/it70RyCrlvY/s1600/google-chrome.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="200" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/TIDVyz5TsUI/AAAAAAAAAIw/it70RyCrlvY/s200/google-chrome.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The sole reason for starting development in Chrome was the speed, but over time I have become more familiar with it's web developer environment. Here I will sum up some of the features I use, and my experiences so far.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Invoking Chrome Developer Tools&lt;/b&gt;&lt;br /&gt;Besides the blistering speed of the browser, the native developer tools in Chrome is a great feature.&lt;br /&gt;&lt;br /&gt;There are a number of ways to start the developer toolbar&lt;br /&gt;&lt;ul&gt;&lt;li&gt;You can click the icon in the upper right corner (tedious)&lt;/li&gt;&lt;li&gt;You can right click anywhere on the page (handy)&lt;/li&gt;&lt;li&gt;You can press &amp;lt;ctrl&amp;gt; + &amp;lt;shift&amp;gt; + I (handy indeed).&lt;/li&gt;&lt;/ul&gt;You can also press &amp;lt;ctrl&amp;gt; + &amp;lt;shift&amp;gt; + J to go directly to the javascript console.&lt;br /&gt;&lt;br /&gt;The developer tools are feature rich, so I will only highlight some of the features.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Elements&lt;/b&gt;&lt;br /&gt;This is the default screen when starting developer tools. You get what you expect from an element inspector (like Firebug):&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/TIDUPI6ieuI/AAAAAAAAAIQ/_Xkvntov3hI/s1600/developer+toolbar+01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="330" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/TIDUPI6ieuI/AAAAAAAAAIQ/_Xkvntov3hI/s640/developer+toolbar+01.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Formatted HTML source code and instant view of element styles (also computed overview of all element style attributes in play)&lt;/li&gt;&lt;li&gt;Navigate through source code with keys or mouse to highlight page elements&lt;/li&gt;&lt;li&gt;Click the magnifying glass at the bototm and click page elements to inspect single elements&lt;/li&gt;&lt;li&gt;Double click element to change it (both in HTML document and element style attributes)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Resources&lt;/b&gt;&lt;br /&gt;Resources gives you an overview of the complete page including external files. It sports an easy graphical view over how much time each part takes (like the Net tab of Firebug). Click any resource to view content and headers. It also gives you a nasty red dot with (preferrably a low) number in it, if the browser encounters any structural error in the documents it has to rewrite. If you click the document, the rewrites are highlighted so you can correct the issues.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/TIDUZ8o3uWI/AAAAAAAAAIY/ZZXlxTDfe6M/s1600/developer+toolbar+02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="324" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/TIDUZ8o3uWI/AAAAAAAAAIY/ZZXlxTDfe6M/s640/developer+toolbar+02.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;You can filter the resources by clicking the buttons (like Stylesheets, Images, Scripts, etc.).&lt;br /&gt;&lt;br /&gt;One of the features I liked most about Firebug, was the ability to easely track AJAX calls (from the console or Net). Resources tab in Crome has the same ability, just click XHR-button (XHR = XML HTTP Request) to view AJAX type requests and responses.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TIDVDbn22GI/AAAAAAAAAIg/huLosdtNaGM/s1600/developer+toolbar+03.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="331" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TIDVDbn22GI/AAAAAAAAAIg/huLosdtNaGM/s640/developer+toolbar+03.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Console and Scripts&lt;/b&gt;&lt;br /&gt;The console has it's own shortcut keys, &amp;lt;ctrl&amp;gt; + &amp;lt;shift&amp;gt; + J gets you directly there. The console will display all errors encountered by the browser to start with, and let's you write javascript to interact with the page.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;ctrl&gt;&lt;shift&gt; &lt;/shift&gt;&lt;/ctrl&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/TIDXnhq3f1I/AAAAAAAAAI4/DZNjF2bjlHE/s1600/developer+toolbar+05+-+script+console01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="288" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/TIDXnhq3f1I/AAAAAAAAAI4/DZNjF2bjlHE/s640/developer+toolbar+05+-+script+console01.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;ctrl&gt;&lt;shift&gt;The Scripts tab let's you view, alter and debug javascripts&lt;/shift&gt;&lt;/ctrl&gt;&lt;br /&gt;&lt;ctrl&gt;&lt;shift&gt; &lt;/shift&gt;&lt;/ctrl&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/TIDVNV6lDsI/AAAAAAAAAIo/jLnS1pqflm8/s1600/developer+toolbar+05+-+script+console.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="276" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/TIDVNV6lDsI/AAAAAAAAAIo/jLnS1pqflm8/s640/developer+toolbar+05+-+script+console.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;ctrl&gt;&lt;shift&gt; &lt;/shift&gt;&lt;/ctrl&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Set/enable/disable breakpoints&lt;/li&gt;&lt;li&gt;Play/pause, step in/out/over, etc.&lt;/li&gt;&lt;li&gt;View and change variable values&lt;/li&gt;&lt;/ul&gt;&lt;b&gt;Other Features&lt;/b&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Timeline - Nifty feature to watch real-time javascript events as you interact with the page. Filtering options are available (Loading, Rendering, Scripting).&lt;/li&gt;&lt;li&gt; Profiles - Profile your pages (or actions on page) in view of CPU intensity or memory usage&lt;/li&gt;&lt;li&gt; Storage - The only real use I have for this is the Cookies overview.&lt;/li&gt;&lt;li&gt; Audits - This is like YSlow for Firefox, giving you advice on how to speed up your pages&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Extensions and Userscripts&lt;/b&gt;&lt;br /&gt;All the features mentioned above is integrated in the browser, in addition, there are a number of extensions you can download. I just have &lt;a href="https://chrome.google.com/extensions/detail/bfbameneiokkgbdmiekhjnmfkcnldhhm?hl=en"&gt;Web Developer&lt;/a&gt; installed, but you can browse &lt;a href="https://chrome.google.com/extensions/?hl=en"&gt;Google Chrome Extensions&lt;/a&gt; for more. You can also see &lt;a href="http://speckyboy.com/2010/01/04/creating-a-web-development-environment-using-google-chrome-extensions/"&gt;Creating a Web Development Environment using Google Chrome Extensions&lt;/a&gt; from Speckboy Design Magazine for more on related extensions.&lt;br /&gt;&lt;br /&gt;Sadly, the &lt;a href="http://builderplugin.oracleapex.info/"&gt;APEX Builder Plugin&lt;/a&gt; does not work for Chrome. I use my own userscript to get shortcut keys working in the APEX builder. Many of the Greasemonkey userscripts also works in Chrome, see &lt;a href="http://userscripts.org/"&gt;userscripts.org&lt;/a&gt; for a large amount of those.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Experiences so far&lt;/b&gt;&lt;br /&gt;I now go days without resorting to Firefox and Firebug. (In truth, that is not just Chrome's achievement. Dynamic Actions in APEX 4.0 makes rich interfaces much, much easier than it was.) I find that most of what I need, most of the time is in place within the developer toolbar of Chrome. I can also work full working days without having to restart the browser because of memory leaks, etc (browser getting sluggish, you know what I am talking about...).&lt;br /&gt;&lt;br /&gt;Oh, did I mention it is fast?&lt;br /&gt;&lt;br /&gt;For future reference: This was written based on Google Chrome version 6.0.472.53.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-1938532413963394124?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/1938532413963394124/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/09/developing-apex-applications-in-google.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1938532413963394124'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1938532413963394124'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/09/developing-apex-applications-in-google.html' title='Developing APEX Applications in Google Chrome'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_8GtRvOur8pQ/TIDVyz5TsUI/AAAAAAAAAIw/it70RyCrlvY/s72-c/google-chrome.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-4399743194624123213</id><published>2010-08-26T15:14:00.000+02:00</published><updated>2010-08-26T15:14:40.403+02:00</updated><title type='text'>Formatted Text with Dynamic Actions</title><content type='html'>This is a quick note on how to use Dynamic Actions in Oracle APEX 4.0 to display unstructured and formatted text from the database based on user selection. This was a bit more cumbersome in the previous versions of APEX.&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZke16_56I/AAAAAAAAAH4/mKAfdpEpihs/s1600/htmljoke.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZke16_56I/AAAAAAAAAH4/mKAfdpEpihs/s320/htmljoke.jpg" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Image by &lt;a href="http://www.flickr.com/photos/jesper/"&gt;Jesper Rønn-Jensen&lt;/a&gt; under &lt;a href="http://creativecommons.org/licenses/by-sa/2.0/"&gt;Creative Commons License&lt;/a&gt;.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;If you have ever let the users get at the Rich Text Editor items (HTML Editor in the previous versions), then you probably have som text with HTML-markup stored in the database. Below is a description on how to get and display the text based on browser events. The example below will display a description of a department when the user clicks department name in a SQL Report (quite stupid really, but serves the purpose).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Demo Application&lt;/b&gt;&lt;br /&gt;I have updated my demo application with the example, see it in action here:&lt;a href="http://apex.oracle.com/pls/apex/f?p=45420:5"&gt;http://apex.oracle.com/pls/apex/f?p=45420:5&lt;/a&gt;. Download the application here: &lt;a href="http://apex.oracle.com/pls/apex/f?p=45410:DOWNLOAD"&gt;http://apex.oracle.com/pls/apex/f?p=45410:DOWNLOAD&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Prepare the Report&lt;/b&gt;&lt;br /&gt;Create a region of type SQL Report based on table DEPT:&lt;br /&gt;&lt;pre class="brush: sql"&gt;select deptno, dname from dept&lt;br /&gt;&lt;/pre&gt;Modify the DNAME-column in the report:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Set Link Text to #DNAME#&lt;/li&gt;&lt;li&gt;Set Link Attributes to id="departmentName#DEPTNO#" (this will uniquely identify each anchor element, and give the ability of extracting DEPTNO from the department name)&lt;/li&gt;&lt;li&gt;Set Target to URL&lt;/li&gt;&lt;li&gt;Set URL to # (this really does nothing)&lt;/li&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZT0cLVa_I/AAAAAAAAAGw/mPcqneVcPEw/s1600/010+-+upd+report+column.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZT0cLVa_I/AAAAAAAAAGw/mPcqneVcPEw/s320/010+-+upd+report+column.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Add an item to the report region, this will eventually hold the DEPTNO of the chosen department from the report.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Right click the department report region and choose Create Page Item&lt;/li&gt;&lt;li&gt;Select Hidden&lt;/li&gt;&lt;li&gt;Give the item a name (P5_DEPTNO)&lt;/li&gt;&lt;li&gt;Leave the rest default (you might want to reconsider Source Used, if you set it to "Always, replacing any existing value in session state", and leaves Source Value blank, you ensure the item starts with no value when the page is displayed).&lt;/li&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/THZZBc7KK5I/AAAAAAAAAHw/-8VMOxxhXuY/s1600/020+-+create+item.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/THZZBc7KK5I/AAAAAAAAAHw/-8VMOxxhXuY/s320/020+-+create+item.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;Create Region to Display Text&lt;/b&gt;&lt;br /&gt;For the sake of simplicity, the text will be displayed in it's own HTML Region&lt;br /&gt;&lt;br /&gt;Right click Regions and choose Create&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Choose HTML&lt;/li&gt;&lt;li&gt;Choose HTML again&lt;/li&gt;&lt;li&gt;Give it a descriptive title: Department Description&lt;/li&gt;&lt;li&gt;Create a div to display the text, set Region Source to:&lt;/li&gt;&lt;li&gt;&amp;lt;div id="departmentDescription"&amp;gt;&amp;lt;/div&amp;gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Create the Dynamic Action&lt;/b&gt;&lt;br /&gt;The first we need to do, is to create a Dynamic Action to get the DEPTNO from the chosen department name, and stuff it into P5_DEPTNO.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;br /&gt;Right click Dynamic Actions and choose Create&lt;br /&gt;Choose Advanced&lt;br /&gt;Give it a name (onclick - Department Name)&lt;br /&gt;Set the values as seen below, this means the Dynamic Action will fire when anchors with id's starting with departmentName is clicked&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/THZUd4RqOtI/AAAAAAAAAHA/N6tS6iLul24/s1600/030+-+onclick+selector.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/THZUd4RqOtI/AAAAAAAAAHA/N6tS6iLul24/s320/030+-+onclick+selector.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Set Action to Set Value, uncheck Fire on Page Load (we only want it to fire when clicked), set the Set Type to Javascript, and Javascript Expression to&lt;br /&gt;&lt;pre class="brush: js"&gt;this.triggeringElement.id.replace('departmentName','');&lt;br /&gt;&lt;/pre&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/THZUo2fF8iI/AAAAAAAAAHI/nI2smNaa5wk/s1600/040+-+onclick+Javascript.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/THZUo2fF8iI/AAAAAAAAAHI/nI2smNaa5wk/s320/040+-+onclick+Javascript.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;This will extract the DEPTNO from the id of the element clicked (by replacing departmentName with nothing).&lt;br /&gt;&lt;br /&gt;Set Selection Type to Item(s), and transfer P5_DEPTNO to the right hand side&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZUvLxKnSI/AAAAAAAAAHQ/aAPWF1m1Kes/s1600/050+-+onclick+target+item.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZUvLxKnSI/AAAAAAAAAHQ/aAPWF1m1Kes/s320/050+-+onclick+target+item.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The final result should look something like this:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZU88af8qI/AAAAAAAAAHY/SSnivONBkAY/s1600/055+-+onclick+first+true+action.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZU88af8qI/AAAAAAAAAHY/SSnivONBkAY/s320/055+-+onclick+first+true+action.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The next step is to get and display the desired text from the database. In order to do this we must create a new True Action to the same Dynamic Action.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Right click the Dynamic Action and choose Edit&lt;/li&gt;&lt;li&gt;Click Add True Action&lt;/li&gt;&lt;li&gt;Set Action: SetValue&lt;/li&gt;&lt;li&gt;Uncheck Fire on Page Load&lt;/li&gt;&lt;li&gt;Set Set Type: PL/SQL Function Body&lt;/li&gt;&lt;li&gt;Set PL/SQL Function Body to code below:&lt;/li&gt;&lt;/ul&gt;&lt;pre class="brush: sql"&gt;declare&lt;br /&gt;&amp;nbsp;&amp;nbsp; l_ret varchar2(32000);&lt;br /&gt;begin&lt;br /&gt;&amp;nbsp;&amp;nbsp; for r in (select dep.*&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; from dept dep&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; where dep.deptno = :p5_deptno)&lt;br /&gt;&amp;nbsp;&amp;nbsp; loop&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; l_ret := 'HTML formatted return value for &amp;lt;b&amp;gt;'|| r.dname ||'&amp;lt;/b&amp;gt; located in &amp;lt;i&amp;gt;'|| upper(r.loc) ||'&amp;lt;/i&amp;gt;.';&lt;br /&gt;&amp;nbsp;&amp;nbsp; end loop;&lt;br /&gt;&amp;nbsp;&amp;nbsp; return l_ret;&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Set Page Items to Submit: P5_DEPTNO&lt;/li&gt;&lt;li&gt;Set Escape Special Characters: No&lt;/li&gt;&lt;li&gt;Set Selection Type: jQuery Selector&lt;/li&gt;&lt;li&gt;Set jQuery Selector: div[id=departmentDescription]&lt;/li&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZVJgVMN2I/AAAAAAAAAHg/Tpx7iQyF-ow/s1600/060+-+onclick+second+true+action.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/THZVJgVMN2I/AAAAAAAAAHg/Tpx7iQyF-ow/s320/060+-+onclick+second+true+action.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Finally edit the Dynamic Action, and set Event Scope: live. This is to ensure the onclick event is attached to Department Name even after PPR refresh of the report.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/THZVOivjnWI/AAAAAAAAAHo/wxm6Phs9Uc8/s1600/070+-+onclick+overview.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/THZVOivjnWI/AAAAAAAAAHo/wxm6Phs9Uc8/s320/070+-+onclick+overview.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A Word of Caution&lt;/b&gt;&lt;br /&gt;Even though APEX takes care of escaping/encoding your HTML according to JSON specification, this will bloat your return message. There seems to be the standard PL/SQL 32k limit to the JSON-message (Large texts resulting in ORA-06502).&lt;br /&gt;&lt;br /&gt;Take care what you return in your message, switching off escape special characters leaves it pretty much wide open to anything. HTML in JSON is a debated issue.&lt;br /&gt;&lt;br /&gt;In short: Keep it small and neat :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-4399743194624123213?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/4399743194624123213/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/08/formatted-text-with-dynamic-actions.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/4399743194624123213'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/4399743194624123213'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/08/formatted-text-with-dynamic-actions.html' title='Formatted Text with Dynamic Actions'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_8GtRvOur8pQ/THZke16_56I/AAAAAAAAAH4/mKAfdpEpihs/s72-c/htmljoke.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-447978773585780711</id><published>2010-08-02T23:46:00.001+02:00</published><updated>2010-08-02T23:50:30.942+02:00</updated><title type='text'>More on Modal Pop Ups</title><content type='html'>I previously wrote &lt;a href="http://monkeyonoracle.blogspot.com/2010/07/modal-pop-up-with-dynamic-actions.html"&gt;a post&lt;/a&gt; on how to use the new APEX 4.0 native features to conjure modal inline dialogs. It was followed by a brief (very brief on my part, to say the least) discussion on how to achieve the same functionality for a create button. In this post I will elaborate a bit on how to do exactly that.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TFcy-CYcymI/AAAAAAAAAFw/jV0EHSqEyL4/s1600/frontpic.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TFcy-CYcymI/AAAAAAAAAFw/jV0EHSqEyL4/s320/frontpic.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;The solution sketched out below definitely has potential for improvement, but can serve as a diving board for the interested.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Demo Application&lt;/b&gt;&lt;br /&gt;If you are curious, or just down right bored with long posts, then I have a running copy of the demo application here: &lt;a href="http://apex.oracle.com/pls/apex/f?p=45420:3"&gt;http://apex.oracle.com/pls/apex/f?p=45420:3&lt;/a&gt;. The demo application can be downloaded here: &lt;a href="http://apex.oracle.com/pls/apex/f?p=45410:DOWNLOAD"&gt;http://apex.oracle.com/pls/apex/f?p=45410:DOWNLOAD&lt;/a&gt;. To keep the examples as clean as possible, I have created a new page with the Create-button, so you have both alternatives available.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Quick Solution&lt;/b&gt;&lt;br /&gt;And what is wrong with a quick solution? Personally I feel the we programmers (me definitely included) are very good at complicating things. The rest of this post is dedicated to a more generic approach, but in my original comment I suggested this:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Create an anchor-tag (link) anywhere on the page&lt;/li&gt;&lt;li&gt;Set href attribute to the URL of your edit form page, let primary key item values remain blank (but be sure to pass them)&lt;/li&gt;&lt;li&gt;Set the id attribute to callModalDialog&lt;/li&gt;&lt;/ul&gt;In my sample application that would be: &amp;lt;a id="callModalDialog" href="f?p=&amp;amp;APP_ID.:4:&amp;amp;SESSION.::NO::P4_EMPNO:"&amp;gt;Create Employee&amp;lt;/a&amp;gt;.&lt;br /&gt;&lt;br /&gt;This would work just like the edit links in my original example, but ready to insert a new employee. Like magic :-)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Elaborate Solution&lt;/b&gt;&lt;br /&gt;The elaborate solution uses more Dynamic Actions, a bit more javascripting and a couple of dirty tricks to accomplish the same as the quick solution. The goal is to make a normal create button to have the same behavior with inline modal dialog, as the edit links does.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;The explanation below is based on the original modal dialog page in my sample application. To follow the example and repeat the steps, you need to start off with a copy of page 2 in my sample application.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;To follow the example, start by creating a region button with the following values (mostly just accept defaults):&lt;br /&gt;Button name: CREATE&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/TFcz3LPxKSI/AAAAAAAAAGA/6c9YyOBo7YE/s1600/create_button01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/TFcz3LPxKSI/AAAAAAAAAGA/6c9YyOBo7YE/s320/create_button01.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The button should take you to the create/edit page, but without the fancy modal dialog.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A Note for Later&lt;/b&gt;&lt;br /&gt;If you name your button to something else than Create (or Text Label/ALT-property for the page button in APEX), you must adjust the following JQuery selectors accordingly for it to work. You may also expect some issues when using an image button (the javascript onclick function extraction may fail).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Removing the Original onclick-Event&lt;/b&gt;&lt;br /&gt;APEX uses a javascript function to redirect the browser (also when using anchor button template). There are two things that needs to be done when the page loads; store the original create link, and remove the original button onclick event. I use JQuery and javascript native regexp capabilities to achieve this.&lt;br /&gt;&lt;br /&gt;To create a Dynamic Action which fires when the page has finished loading, do the following:&lt;br /&gt;Create a new Dynamic Action at the page level&lt;br /&gt;&lt;br /&gt;Choose Advanced&lt;br /&gt;&lt;br /&gt;Give it a sensible name ("On Page Load")&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/TFczsQlgpdI/AAAAAAAAAF4/aSk61CtS_Ek/s1600/create_onload_01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/TFczsQlgpdI/AAAAAAAAAF4/aSk61CtS_Ek/s320/create_onload_01.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Choose Event Page Load&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/TFc0PdyRoSI/AAAAAAAAAGI/OSKQM9CZhqE/s1600/create_onload_02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/TFc0PdyRoSI/AAAAAAAAAGI/OSKQM9CZhqE/s320/create_onload_02.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Choose Action Execute Javascript Code, and paste in the javascript code below:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/TFc0peHkJuI/AAAAAAAAAGY/rWyNlReFUQc/s1600/create_onload_04.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/TFc0peHkJuI/AAAAAAAAAGY/rWyNlReFUQc/s320/create_onload_04.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;pre class="brush: js"&gt;/* get original onclick event */&lt;br /&gt;var origAction = $('button[value=Create]').attr('onclick').toString();&lt;br /&gt;/* get link from original onclick event using regular expression */&lt;br /&gt;var link = origAction.match(/(redirect\((\'|\"))([^\'\)|\"\)]*)/)[3];&lt;br /&gt;/* Remove original onclick event */&lt;br /&gt;$('button[value=Create]').removeAttr('onclick');&lt;br /&gt;/* store link as title attribute of button */&lt;br /&gt;$('button[value=Create]').attr('title', link);&lt;br /&gt;&lt;/pre&gt;Looks a bit Greek? Even if I have actually included comments? If you are not familiar with JQuery, it definitely will. If you are not familiar with regular expressions, even more so. If you are not familiar with javascript at all, you are allowed to test the code, but not use it in production unless you have truly understood what it means :-) There are very good sources on the web for all the knowledge required.&lt;br /&gt;&lt;br /&gt;And why, oh, why store the link value in an element attribute definitely not meant to hold a link value!?! It will overwrite any existing title-values, and makes both setting and retrieving code hard to read. On the other hand, it will keep the value with it's element, and support more than one create button on any given page. There are more than one alternate way of doing this, but hey, feel free to bring suggestions :-)&lt;br /&gt;&lt;br /&gt;Anyway, click Create and you are done.&lt;br /&gt;&lt;br /&gt;If you run your page now, clicking the Create button will not do anything (the onclick event was removed, but not replaced).&lt;br /&gt;&lt;br /&gt;That was the hard part!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Calling the Dialog&lt;/b&gt;&lt;br /&gt;The edit dialog Dynamic Action is already in place, all that has to be done is to adapt the existing Dynamic Action to include the new create button.&lt;br /&gt;Edit the Modal Dialog Dynamic Action&lt;br /&gt;Include: button[value=Create] as the JQuery Selector expression (total expression will now be: a[id^=callModalDialog],button[value=Create])&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TFc1FapbfdI/AAAAAAAAAGg/4tzA9qQHECE/s1600/edit_modal_01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TFc1FapbfdI/AAAAAAAAAGg/4tzA9qQHECE/s320/edit_modal_01.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Edit the True Action Javascript Expression to be:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/TFc1LmovikI/AAAAAAAAAGo/wQh4QtUaRK8/s1600/edit_modal_02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/TFc1LmovikI/AAAAAAAAAGo/wQh4QtUaRK8/s320/edit_modal_02.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;pre class="brush: js"&gt;/* prevent default behaviour on click */&lt;br /&gt;var e = this.browserEvent;&lt;br /&gt;e.preventDefault();&lt;br /&gt;/* Find page link */&lt;br /&gt;var link;&lt;br /&gt;if (this.triggeringElement.tagName=='A') {&lt;br /&gt;   link = this.triggeringElement.href;&lt;br /&gt;} else if (this.triggeringElement.tagName=='BUTTON') {&lt;br /&gt;   link = this.triggeringElement.title;&lt;br /&gt;}&lt;br /&gt;/* Trigger JQuery UI dialog */&lt;br /&gt;var horizontalPadding = 30;&lt;br /&gt;var verticalPadding = 30;&lt;br /&gt;$('&amp;lt;iframe id="modalDialog" src="' + link + '" /&amp;gt;').dialog({&lt;br /&gt;   title: "Edit Employee",&lt;br /&gt;   autoOpen: true,&lt;br /&gt;   width: 700,&lt;br /&gt;   height: 300,&lt;br /&gt;   modal: true,&lt;br /&gt;   close: function(event, ui) {apex.event.trigger('#P3_AFTER_MODAL','select',''); $(this).remove();},&lt;br /&gt;   overlay: {&lt;br /&gt;       opacity: 0.5,&lt;br /&gt;       background: "black"}&lt;br /&gt;}).width(700 - horizontalPadding).height(300 - verticalPadding);&lt;br /&gt;return false;            &lt;br /&gt;&lt;/pre&gt;The difference from the original javascript code is the extra bit extracting the link to use, which differs from a normal anchor element, and our button with the special id attribute.&lt;br /&gt;Click Apply and you are done.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Rest?&lt;/b&gt;&lt;br /&gt;Is the same as detailed in my last post. I have updated the dialog page in the sample application to include Create and Delete buttons, but that is it.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Quick or Slow?&lt;/b&gt;&lt;br /&gt;Quick sound promising! Simplicity rules! There are some draw-backs, of course. Like where to put the link to find it later (a Display Only item properly named perhaps?)? Like the need to style a link as a button? Like the need to place the link in a region template position? It all adds up.&lt;br /&gt;&lt;br /&gt;The generic (more elaborate) approach will work with any create button (well, not with the anchor template). Drop the Dynamic Actions onto the page, and it will work. On the other hand, the generic approach involves more code, more code is harder to maintain and more prone to breaking. Generic code requires a delicate hand (ie takes time), and is generally harder to read than code created for a specific task. This is all in the eye of the beholder, but on more than one occasion I have had the pleasure of revisiting my own old code and though: I didn't need to do that... I digress, I know. Besides, that is too great a topic to just be delegated to a digression :-)&lt;br /&gt;&lt;br /&gt;So, should you use the quick link (pun intended :-))? My answer (being a consultant) is a definitive: It depends!&lt;br /&gt;&lt;br /&gt;Enjoy :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-447978773585780711?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/447978773585780711/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/08/more-on-modal-pop-ups.html#comment-form' title='34 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/447978773585780711'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/447978773585780711'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/08/more-on-modal-pop-ups.html' title='More on Modal Pop Ups'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_8GtRvOur8pQ/TFcy-CYcymI/AAAAAAAAAFw/jV0EHSqEyL4/s72-c/frontpic.jpg' height='72' width='72'/><thr:total>34</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-6446570806831192609</id><published>2010-07-16T12:42:00.000+02:00</published><updated>2010-07-16T12:42:22.892+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='off topic'/><category scheme='http://www.blogger.com/atom/ns#' term='hotkeys'/><title type='text'>Quick Notes</title><content type='html'>Just a bit of housekeeping. &lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/TEA1ODMCoUI/AAAAAAAAAFg/mPhUaad__Nw/s1600/housekeeping.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/TEA1ODMCoUI/AAAAAAAAAFg/mPhUaad__Nw/s320/housekeeping.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;b&gt;APEX 4.0 Builder Hotkeys&lt;/b&gt;&lt;br /&gt;This is just a quick note on the APEX 4.0 Builder Hotkeys. I have updated the script because not all previous/next buttons were included in the last version. Alt + PageUp/PageDown should now give the right response.&lt;br /&gt;&lt;br /&gt;You can download and install the script here: &lt;a href="http://userscripts.org/scripts/show/81058"&gt;http://userscripts.org/scripts/show/81058&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Installation instructions can be found here: &lt;a href="http://monkeyonoracle.blogspot.com/2010/07/apex-40-builder-hotkeys.html"&gt;http://monkeyonoracle.blogspot.com/2010/07/apex-40-builder-hotkeys.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Ye Olde Sample App&lt;/b&gt;&lt;br /&gt;...is still working! I just went through the Application Upgrade and changed the theme while at it. I even updated the Google Maps example. ("Updated" sound so much better than "fixed a bug". Quick solutions will make you do that...) This application will probably not be updated anytime soon. There are a few more elaborate examples I had in mind, but then there is this "time"-thing all over again. Exploring APEX 4.0 is at the top of my spare time roster for now.&lt;br /&gt;&lt;br /&gt;The application will be left alone for the time being, and can be found here: &lt;a href="http://apex.oracle.com/pls/apex/f?p=28990:1"&gt;http://apex.oracle.com/pls/apex/f?p=28990:1&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Summer Vacation&lt;/b&gt;&lt;br /&gt;Finally, my last day of work before I start my summer vacation. One of the nicer benefits as a worker in Norway, is the length of paid vacation established by national law. I guess this is not so normal for Americans.&lt;br /&gt;&lt;br /&gt;True story from the ODTUG Kaleidoscope this year. An American guy comes up to me with his friend, and after introductions the following conversation takes place:&lt;br /&gt;Him: So, you're from Norway?&lt;br /&gt;Me: Yep!&lt;br /&gt;Him: Could you tell my colleague here how much paid vacation you guys have?&lt;br /&gt;Me: Five weeks!&lt;br /&gt;Friend: ... (going through the whole stunned jaw dropping improvised look)&lt;br /&gt;&lt;br /&gt;Cheers :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-6446570806831192609?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/6446570806831192609/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/quick-notes.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/6446570806831192609'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/6446570806831192609'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/quick-notes.html' title='Quick Notes'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_8GtRvOur8pQ/TEA1ODMCoUI/AAAAAAAAAFg/mPhUaad__Nw/s72-c/housekeeping.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-2773088934311715780</id><published>2010-07-15T14:21:00.000+02:00</published><updated>2010-07-15T14:21:23.673+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dynamic actions'/><category scheme='http://www.blogger.com/atom/ns#' term='popup'/><category scheme='http://www.blogger.com/atom/ns#' term='apex40'/><category scheme='http://www.blogger.com/atom/ns#' term='dialog'/><title type='text'>Modal Pop Up with Dynamic Actions</title><content type='html'>You have all seen it in the more "modern" web applications, pop-up windows displayed inline, and not in a separate window. With JQuery embedded into Oracle APEX 4.0, and JQuery UI equally at your disposal (at least in the new themes I have tested), creating this type of dialogs is well withing reach. The JQuery UI dialog is already in use in the application templates (the tool tip dialogs).&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TD73LVP8nxI/AAAAAAAAAFY/gisjwPM8nDo/s1600/windows.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TD73LVP8nxI/AAAAAAAAAFY/gisjwPM8nDo/s320/windows.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I wanted to use Dynamic Actions to create a modal pop-up dialog of APEX-pages, and this is what I came up with. It is not as streamlined as I would have liked it to be, but a big leap in the right direction nonetheless.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Demo Application&lt;/b&gt;&lt;br /&gt;I have created a demo application which you can test here: &lt;a href="http://apex.oracle.com/pls/apex/f?p=45420:2"&gt;http://apex.oracle.com/pls/apex/f?p=45420:2&lt;/a&gt;. You can also download the application here: &lt;a href="http://apex.oracle.com/pls/apex/f?p=45410:DOWNLOAD"&gt;http://apex.oracle.com/pls/apex/f?p=45410:DOWNLOAD&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Identifying Triggering Elements&lt;/b&gt;&lt;br /&gt;The trick with the Dynamic Actions is again to identify which elements you would like to use to trigger the dialog. In my example I have a plain SQL-report, but I have edited the primary key column to display an edit icon, and under the Column Link properties, I have set Link Attributes to include id="callModalDialog#EMPNO#". This gives me the possibility to identify the links using a JQuery selector expression.&lt;br /&gt;&lt;br /&gt;I created a Dynamic Action on the click event, with Selection Type JQuery Selector with value a[id^=callModalDialog]. This will identfy all items with attribute id starting with callModalDialog, which, incidentally, is the same id I used on my edit links in the report.&lt;br /&gt;&lt;br /&gt;To ensure that the event triggers after refresh of the report, I set the Event Scope to live under Advanced (as opposed to bind, which is normal). This saves me the effort of re-binding the click-events when the report is refreshed.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/TD7zqWxGPxI/AAAAAAAAAEw/ucBkO1afRO0/s1600/DynAction01.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/TD7zqWxGPxI/AAAAAAAAAEw/ucBkO1afRO0/s320/DynAction01.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Calling the Dialog&lt;/b&gt;&lt;br /&gt;When the user clicks the edit links, I want to do two things: suppress the default browser behavior, and call the modal dialog. I achieve this by adding a true Action of type Execute Javascript Code.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/TD7z2tPMERI/AAAAAAAAAE4/fH_3tInrwG4/s1600/DynAction02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/TD7z2tPMERI/AAAAAAAAAE4/fH_3tInrwG4/s320/DynAction02.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;This contains the javascript code to do both the things I want to do:&lt;br /&gt;&lt;pre class="brush: js"&gt;/* prevent default behavior on click */&lt;br /&gt;var e = this.browserEvent;&lt;br /&gt;e.preventDefault();&lt;br /&gt;/* Trigger JQuery UI dialog */&lt;br /&gt;var horizontalPadding = 30;&lt;br /&gt;var verticalPadding = 30;&lt;br /&gt;$('&amp;lt;iframe id="modalDialog" src="' + this.triggeringElement.href + '" /&amp;gt;').dialog({&lt;br /&gt;   title: "Edit Employee",&lt;br /&gt;   autoOpen: true,&lt;br /&gt;   width: 700,&lt;br /&gt;   height: 300,&lt;br /&gt;   modal: true,&lt;br /&gt;   close: function(event, ui) {apex.event.trigger('#P2_AFTER_MODAL','select',''); $(this).remove();},&lt;br /&gt;   overlay: {&lt;br /&gt;       opacity: 0.5,&lt;br /&gt;       background: "black"}&lt;br /&gt;}).width(700 - horizontalPadding).height(300 - verticalPadding);&lt;br /&gt;return false;            &lt;br /&gt;&lt;/pre&gt;Some explanation for this is in order. The first part is just to prevent the default browser behavior (user click a ink, and the browser naturally wants to open that page). The second part is by far more complex, and contains all the code needed to start the JQuery UI dialog.&lt;br /&gt;&lt;br /&gt;The edit link points to an APEX application page, and the code above invokes the page in an IFRAME, and uses the JQuery UI dialog to show the IFRAME inline in a modal dialog. I extract the link from the triggeringElement, set the title (this could also have been extracted from the triggeringElement), set various attributes for the dialog and IFRAME. In short the code above says open the HREF of the triggeringElement in an IFRAME, show the IFRAME in a modal dialog and open the dialog immediately.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Returning from the Dialog&lt;/b&gt;&lt;br /&gt;If you notice the code above, the close configuration for the dialog looks quite complex. The reason for this is that I want to execute a Dynamic Action when the dialog is closed. There are two problems with this; (as far as I know) I cannot execute a Dynamic Action directly from Javascript, and I have nowhere to attach the Dynamic Event anyways. Write som javascript function you say? Yes I could do that, but the Dynamic Action is too good to pass up. Let us say that I want to refresh a report, set some item values and execute some PL/SQL when returning, with Dynamic Actions that is a walk in the park.&lt;br /&gt;&lt;br /&gt;This is where I cheat :-)&lt;br /&gt;&lt;br /&gt;The dialog does not exist when the page renders, so I have nowhere to attach my Dynamic Action to execute when returning from the dialog. My (kludgy) solution to this was to create a hidden item in the report region, and attach the Dynamic Action to the item. This way I can see all that happens in the Builder (easier to maintain), and if I give it sensible names, I can even discern what it does. In my case I created an item called P2_AFTER_MODAL, and attached a Dynamic Action called Refresh Report.&lt;br /&gt;&lt;br /&gt;So, now I have defined what to do after returning, but how to trigger the event? And I cheat again... I attach the Dynamic Event to P2_AFTER_MODAL with the select event. The select event on a hidden item does not get triggered very easily. In effect this gives me a way to control that the Dynamic Action only executes when I programmatically tells it to. That is exactly what I tell it to when closing the dialog, using the APEX javascript API apex.event.trigger.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/TD70GN-3urI/AAAAAAAAAFA/_sp2FqI-NfA/s1600/DynAction03.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/TD70GN-3urI/AAAAAAAAAFA/_sp2FqI-NfA/s320/DynAction03.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I also destroy the dialog after use, so I can create a new on the fly when the user wants to edit another employee, $(this).remove(); takes care of that (removing the IFRAME in effect, I could not get the dialog.destroy-method to work properly).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Closing the Dialog&lt;/b&gt;&lt;br /&gt;When creating an APEX page that is to be a pop-up window, you need to take special care on the branching. In this case, you want to take care to close the dialog, both on cancel and after processing.&lt;br /&gt;&lt;br /&gt;To handle cancel, let the cancel button redirect to URL, and set the URL target to: javascript:parent.$('#modalDialog').dialog('close');.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/TD70RVgdOPI/AAAAAAAAAFI/ucha2s2WLfE/s1600/Cancel.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/TD70RVgdOPI/AAAAAAAAAFI/ucha2s2WLfE/s320/Cancel.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Notice the use of parent, the dialog is spawned by the original web page, and the dialog and its method belongs to the parent.&lt;br /&gt;&lt;br /&gt;When branching after processing you have to be a bit more creative, create an unconditional branch to PL/SQL Procedure that runs the following code:&lt;br /&gt;&lt;pre class="brush: sql"&gt;BEGIN&lt;br /&gt;--closes this popup window&lt;br /&gt;htp.p('&amp;lt;body&amp;gt;');&lt;br /&gt;htp.p('&amp;lt;script type="text/javascript"&amp;gt;');&lt;br /&gt;htp.p('parent.$(''#modalDialog'').dialog(''close'');');&lt;br /&gt;htp.p('&amp;lt;/script&amp;gt;');&lt;br /&gt;htp.p('&amp;lt;/body&amp;gt;');&lt;br /&gt;END;&lt;br /&gt;&lt;/pre&gt;This is in effect the same as the URL target behind the cancel button, but rendered by htp.p in PL/SQL.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/TD70aWZVHEI/AAAAAAAAAFQ/tBaARUbL-FU/s1600/branch.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/TD70aWZVHEI/AAAAAAAAAFQ/tBaARUbL-FU/s320/branch.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Layout of the Dialog Page&lt;/b&gt;&lt;br /&gt;In the sample application have have copied the Printer Friendly template, called it Modal Dialog, and used that as a page template for the dialog page. The modifications is basically just removing the navbar, and overriding the default "min-height" and "min-width" css attributes by explicitly setting style-attributes for the body-tag and div with id="body". This will vary, depending on the theme you are using. The goal is to strip the dialog of all unnecessary layout clutter.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Some Notes&lt;/b&gt;&lt;br /&gt;I have created a few modal dialogs in the previous versions of APEX, and the javascripting involved is a pain. Maintaining the code even more painful. Even with the simple trick (or cheat, if you will) of attaching the Dynamic Action to an item in a declarative way, helps me getting control over the classic "what happens where, when and why".&lt;br /&gt;&lt;br /&gt;Even though the basics is demonstrated in the sample application, there is a bit of work left. When you return from the dialog and refresh the report, you might want to take care to maintain the pagination, and if the user closes the dialog with cancel or window close, you might not want to refresh the report, etc., etc.&lt;br /&gt;&lt;br /&gt;Enjoy :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-2773088934311715780?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/2773088934311715780/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/modal-pop-up-with-dynamic-actions.html#comment-form' title='13 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/2773088934311715780'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/2773088934311715780'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/modal-pop-up-with-dynamic-actions.html' title='Modal Pop Up with Dynamic Actions'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_8GtRvOur8pQ/TD73LVP8nxI/AAAAAAAAAFY/gisjwPM8nDo/s72-c/windows.jpg' height='72' width='72'/><thr:total>13</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-3975957873953415904</id><published>2010-07-09T00:11:00.001+02:00</published><updated>2010-07-09T00:13:08.201+02:00</updated><title type='text'>APEX 4.0 Builder Hotkeys</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDZMSIZF36I/AAAAAAAAAEo/--TEHRz_bEI/s1600/speed.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDZMSIZF36I/AAAAAAAAAEo/--TEHRz_bEI/s320/speed.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;Until recently I only developed in Oracle APEX using Mozilla Firefox. Partly because of the add-ons (like Firebug), but most of all because of &lt;a href="http://www.inside-oracle-apex.com/"&gt;Patrick Wolf&lt;/a&gt;'s &lt;a href="http://builderplugin.oracleapex.info/"&gt;Oracle APEX Builder Plugin&lt;/a&gt;. It can be really annoying at times to scroll and click to apply changes.&lt;br /&gt;&lt;br /&gt;After upgrading to APEX 4.0, the APEX Builder Plugin stopped working. I tried a quick and dirty approach, and updated the config files to look for button-elements instead of input-elements, but that did not work.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Enter Google Chrome&lt;/b&gt;&lt;br /&gt;I've been using this browser on and off for some time, and it is incredibly fast compared to Firefox (even without add-ons, I checked). I did a bit of research (sounds better than a quick google search :-)), and found that Chrome supports &lt;a href="http://www.greasespot.net/"&gt;Greasemonkey&lt;/a&gt; scripts natively. How nice! If only I had a Greasemonkey script that worked, that is...&lt;br /&gt;&lt;br /&gt;So, I had to make my own. It's an absolute first try, so don't hold your breath. I have probably made any number of rookie mistakes, feel free to correct me :-)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Greasemonkey?&lt;/b&gt;&lt;br /&gt;&lt;a href="http://www.greasespot.net/"&gt;Greasemonkey&lt;/a&gt; is a Mozilla Firefox add-on which can run scripts after page load, and do pretty much everything with it. Greasemonkey scripts are nothing but javascript with some "special" declarations at the beginning of the file, but runs in it's own sandbox.&lt;br /&gt;&lt;br /&gt;Google Chrome understands and runs Greasemonkey scripts as add-ons (no install of Greasemonkey necessary). Chrome was my target after all.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;APEX 4.0 Builder Hotkeys&lt;/b&gt;&lt;br /&gt;This is nothing like the more sophisticated Builder Plugin from Patrick Wolf, I have only aimed at the dearly missed hotkeys (or shortcut keys if you will).&lt;br /&gt;&lt;br /&gt;Hotkeys implemented so far:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;F8 - Run page&lt;/li&gt;&lt;li&gt;F9 - Apply Changes&lt;/li&gt;&lt;li&gt;Alt + F9 - Click the orange button&lt;/li&gt;&lt;li&gt;Alt + PageUp - Click button named "&amp;lt;" or "&amp;lt; Previous"&lt;/li&gt;&lt;li&gt;Alt + PageDown - Click button named "&amp;gt;" or "Next &amp;gt;"&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Install&lt;/b&gt;&lt;br /&gt;Installation instructions for both Chrome and Firefox:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Point browser to &lt;a href="http://userscripts.org/scripts/show/81058"&gt;http://userscripts.org/scripts/show/81058&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Click green Install-button to the right &lt;/li&gt;&lt;li&gt;Click Install in the dialog (In Chrome: Click Continue at the bottom of the page, and then Install in the dialog)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Firefox users have to install Greasemonkey before installing the script above. I have hosted the script at &lt;a href="http://userscripts.org/"&gt;userscripts.org&lt;/a&gt;. Have a look around while you are there, the number of scripts (and the crazy things people do to web pages) is just staggering.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Uninstall&lt;/b&gt;&lt;br /&gt;Chrome:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Point the browser to chrome://extensions/&lt;/li&gt;&lt;li&gt;Click Uninstall under the "Oracle APEX 4.0 Builder Hotkeys" extension.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Firefox:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Right click the little monkey in the lower right corner of the window&lt;/li&gt;&lt;li&gt;Choose Manage User Scripts&lt;/li&gt;&lt;li&gt;Click "Oracle APEX 4.0 Builder Hotkeys" on the left&lt;/li&gt;&lt;li&gt;Check "Also uninstall associated preferences"&lt;/li&gt;&lt;li&gt;Click Uninstall&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Some Notes&lt;/b&gt;&lt;br /&gt;I had to revert to JQuery 1.3.2 to make it play nice with Greasemonkey. I know there are ways make it work for 1.4.x, but I am not using JQuery to it's full potential (only simple selectors and click-events). I didn't even bother trying. I also had to include the whole script to make it work with Chrome (no external references).&lt;br /&gt;&lt;br /&gt;There might be some problems with namespacing and JQuery conflicts, but I have encountered none so far. &lt;br /&gt;&lt;br /&gt;Feel free to modify the scripts, or to make enhancement requests (hey, I might even do something about them. It could happen! Really!)&lt;br /&gt;&lt;br /&gt;So, will I retire Firefox now? Nope, Firebug, YSlow, WebDeveloper, etc. are all good reasons not to. I am not that comfortable with the Chrome developer tool.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-3975957873953415904?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/3975957873953415904/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/apex-40-builder-hotkeys.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3975957873953415904'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3975957873953415904'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/apex-40-builder-hotkeys.html' title='APEX 4.0 Builder Hotkeys'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_8GtRvOur8pQ/TDZMSIZF36I/AAAAAAAAAEo/--TEHRz_bEI/s72-c/speed.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-3275786680831229551</id><published>2010-07-07T09:53:00.008+02:00</published><updated>2010-07-08T11:56:10.132+02:00</updated><title type='text'>Dynamic Actions</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/TDQw4MySsfI/AAAAAAAAAEA/HSvQPJfFJBw/s1600/intropic.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/TDQw4MySsfI/AAAAAAAAAEA/HSvQPJfFJBw/s320/intropic.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;One of the killer features of Apex 4.0 are Dynamic Actions. What a fantastic way to declaratively include JQuery and Ajax events into your application! I never really enjoyed placing javascript calls all over the place to achieve the rich functionality in the earlier versions of Apex. Too much work, and too hard to maintain.&lt;br /&gt;&lt;br /&gt;Now you have Dynamic Actions, and all the little knobs and wheels show up in the page editor as any other kind of objects. The Apex-team has done a marvelous job of integrating JQuery into the framework.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;How Dynamic are Dynamic Actions?&lt;/b&gt;&lt;br /&gt;As I upgrade applications I want to get rid of all the little Javascript function calls that are all over, and replace them with Dynamic Actions. As I go along, there are a few challenges, but no show stoppers (yet). Below is a description on one of the hurdles (well, more like a small bump) passed along the way.&lt;br /&gt;&lt;br /&gt;I find that Dynamic Actions are extremely versatile, but you should have some knowledge of JQuery selectors to realise it's full potential.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Invoking Dynamic Actions from Report Columns&lt;/b&gt;&lt;br /&gt;In the editor there are no possibility of attaching dynamic actions to tabular forms columns. I wanted to invoke an Ajax-call to commit changes when the user changed a select list in a tabular form, here is a way you can do the same.&lt;br /&gt;&lt;br /&gt;If you just want to inspect the code for yourself, I have created a small application you can download from apex.oracle.com: &lt;a href="http://apex.oracle.com/pls/apex/p?n=1070334102765726226"&gt;http://apex.oracle.com/pls/apex/p?n=1070334102765726226&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;First off, create a tabular form, in this example I have a select that uses the APEX_ITEM-api to create application items:&lt;br /&gt;&lt;pre class="brush: sql"&gt;select apex_item.hidden(1, emp.empno) empno&lt;br /&gt;,      apex_item.display_and_save(2, emp.ename) ename&lt;br /&gt;,      apex_item.select_list_from_query(3, emp.deptno, 'select dname d, deptno r from dept order by dname', p_item_id =&amp;gt; 'empno'||empno) deptno       &lt;br /&gt;from   emp&lt;br /&gt;&lt;/pre&gt;Next, create two hidden items, one for the primary key (P1_EMPNO), and one for the changed column value (P1_DEPTNO).&lt;br /&gt;Next, create a Dynamic Action on the page level to invoke the update statement:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;br /&gt;Right click Dynamic Actions on the page level, and choose Create &lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/TDQpl55HuPI/AAAAAAAAACw/JODT4mfvMpQ/s1600/001.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/TDQpl55HuPI/AAAAAAAAACw/JODT4mfvMpQ/s320/001.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Choose Advanced&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDQp5Dyrv6I/AAAAAAAAAC4/7kHxlegzoHU/s1600/002.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDQp5Dyrv6I/AAAAAAAAAC4/7kHxlegzoHU/s320/002.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Give the Dynamic Action a name&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDQp-SheoHI/AAAAAAAAADA/cXDnUL9WKA8/s1600/003.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDQp-SheoHI/AAAAAAAAADA/cXDnUL9WKA8/s320/003.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Set the event to trigger on Change, set Selection Type to JQuery Selector and set JQuery Selector to "select[id^=empno]". This is where we set up the Dynamic Action to fire on every change to all items where the item ID starts with "empno". If you notice the select statement above, I explicitly set the item ID when creating the select lists.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_8GtRvOur8pQ/TDQqDiHqALI/AAAAAAAAADI/1z1A0IV4jG8/s1600/004.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_8GtRvOur8pQ/TDQqDiHqALI/AAAAAAAAADI/1z1A0IV4jG8/s320/004.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;Now I have set up when the event is supposed to trigger, next I define what is actually going to happen once the Action is triggered.&lt;br /&gt;&lt;br /&gt;Set the Action to Execute PL/SQL Code, and the PL/SQL Code to the update statement below.&lt;br /&gt;&lt;pre class="brush: sql"&gt;update emp&lt;br /&gt;   set deptno = :P1_DEPTNO&lt;br /&gt; where empno = :P1_EMPNO;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In addition you must set Page Items to Submit with the request (P1_DEPTNO and P1_EMPNO):&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDQqKbGq9HI/AAAAAAAAADQ/Y7Vrq9IUKWU/s1600/005.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDQqKbGq9HI/AAAAAAAAADQ/Y7Vrq9IUKWU/s320/005.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;Click create and you are almost done.&lt;br /&gt;&lt;br /&gt;Now the Dynamic Action is defined in essence, but there is one piece missing: When the user changes the values of the select list, the new DEPTNO-value and the current EMPNO must be reflected in P1_DEPTNO and P1_EMPNO before the PL/SQL-Code (in this case the update statement) is invoked. This is where the pure genious of the Dynamic Actions come into play; Apex allows you to create additional Actions to execute in the same event.&lt;br /&gt;&lt;br /&gt;To set the values of P1_DEPTNO and P1_EMPNO, do the following:&lt;br /&gt;&lt;br /&gt;Open the editor for the Dynamic Action&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;Under True Actions region, click Add True Action&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/TDQqThanm3I/AAAAAAAAADY/nMHbFLslPaQ/s1600/006.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/TDQqThanm3I/AAAAAAAAADY/nMHbFLslPaQ/s320/006.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Set the sequence to something lower than the PL/SQL-action (normally this means setting it below 10)&lt;br /&gt;Set the Action to Set Value&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;Make sure the Fire on Page Load is unchecked&lt;br /&gt;Set Set Type to Javascript Expression &lt;br /&gt;Set Javascript Expression to this.triggeringElement.value, which basically means get the value of the triggering element&lt;br /&gt;Under Affected Elements, set Selection Type to Item(s) and Item(s) to P1_DEPTNO&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/TDWd9N-A3EI/AAAAAAAAAEQ/P4BRsbB8Nms/s1600/upd_deptno.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/TDWd9N-A3EI/AAAAAAAAAEQ/P4BRsbB8Nms/s320/upd_deptno.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;Now we have to do the same for P1_EMPNO, this almost the same as for P1_DEPTNO, but we have to get the EMPNO-value from the ID of the triggering element. To achieve this, Click Add True Action, and set it up as seen below:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/TDWeZX_TN5I/AAAAAAAAAEY/6erkEAEyiBk/s1600/upd_empno.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/TDWeZX_TN5I/AAAAAAAAAEY/6erkEAEyiBk/s320/upd_empno.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The Javascript code gets the ID of the triggering element, and strips off the leading empno-string. The remainder is the actual EMPNO of the employee affected.&lt;br /&gt;&lt;br /&gt;When you return to the Dynamic Actions editor, this is what the True  Actions should look like:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDWgYa09FWI/AAAAAAAAAEg/49qgy0rRga0/s1600/upd_true_actions.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/TDWgYa09FWI/AAAAAAAAAEg/49qgy0rRga0/s320/upd_true_actions.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Run the page to check that it works.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A Word of Caution&lt;/b&gt;&lt;br /&gt;This was a special and simplified case, normally you should use the native tabular forms functionality in Apex. There are several reasons for this. There is no MD5-checking here (data has been changed by another user), and every little change will result in a round trip to the server (no batch submit and no way for user to cancel changes).&lt;br /&gt;&lt;br /&gt;Either way, Dynamic Actions rock!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-3275786680831229551?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/3275786680831229551/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/dynamic-actions.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3275786680831229551'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3275786680831229551'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/dynamic-actions.html' title='Dynamic Actions'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_8GtRvOur8pQ/TDQw4MySsfI/AAAAAAAAAEA/HSvQPJfFJBw/s72-c/intropic.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-6514084903590288252</id><published>2010-07-05T15:26:00.000+02:00</published><updated>2010-07-05T15:26:03.300+02:00</updated><title type='text'>ODTUG Kaleidoscope 2010</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/TDHc8E4komI/AAAAAAAAACg/uGGYc6h8gIw/s1600/lincolnmemorial.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/TDHc8E4komI/AAAAAAAAACg/uGGYc6h8gIw/s320/lincolnmemorial.gif" /&gt;&lt;/a&gt;&lt;/div&gt;I attended the &lt;a href="http://www.odtugkaleidoscope.com/"&gt;ODTUG Kaleidoscope &lt;/a&gt;this year. First off; this conference is truly a developers conference. The amount of knowledge shared (both within and outside sessions) is immense. The sheer number of profiles in Oracle development community speaks for itself. In short, if you are a developer in Oracle products, this is the conference for you.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Goal&lt;/b&gt;&lt;br /&gt;This year my primary focus was middle tier development, and ADF in particular. Being a consultant, you jump when customers says toad. While I remain a fan of APEX, it is part of my job to keep abreast with the alternatives.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;ADF&lt;/b&gt;&lt;br /&gt;The Oracle Application Development Framework (ADF) has been pushed by Oracle quite hard for a number of years. The market adoption has been a bit on the slow side (to say the least). That being said, I think it is one of the best frameworks out there based on Java to develop rich internet applications.&lt;br /&gt;&lt;br /&gt;I think ADF sort of lives in a limbo; Traditional Java developers will not touch it (it's Oracle), and traditional (database) developers will not touch it (it's Java). It is not as simple as that, obviously, and it is being used out there, just not to the extent I expected.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;ODTUG and ADF&lt;/b&gt;&lt;br /&gt;I think Kaleidoscope reflects the limbo-effect, both in content and interest. Java developers obviously has other arenas, database developers are, well, database developers. I get that it is hard to combine the two, but look at the adoption of the conference by Essbase and Hyperion developers! They have really swarmed to the conference.&lt;br /&gt;&lt;br /&gt;So, what did I get out of it? Related to ADF, not as much as I would have liked. There still seems to be considerably technical issues if you move out of the pure ADF sphere (like the SOA-suite). ADF on the other hand has matured considerably, and creating a slick looking, efficient application requires much less effort than it was in my last ADF project on 10g.&lt;br /&gt;&lt;br /&gt;I still miss more to-the-point sessions on how to attack large implementations (like &lt;a href="http://andrejusb.blogspot.com/"&gt;Andrejus Baranovskis&lt;/a&gt;).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Meat&lt;/b&gt;&lt;br /&gt;What really gave me value was the sessions by the big guns; &lt;a href="http://carymillsap.blogspot.com/"&gt;Cary Millsap&lt;/a&gt;, &lt;a href="http://jonathanlewis.wordpress.com/"&gt;Jonathan Lewis&lt;/a&gt; and &lt;a href="http://www.stevenfeuerstein.com/"&gt;Steven Feuerstein&lt;/a&gt;. Worth the trip alone to hear any of those speaking :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-6514084903590288252?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/6514084903590288252/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/odtug-kaleidoscope-2010.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/6514084903590288252'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/6514084903590288252'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/07/odtug-kaleidoscope-2010.html' title='ODTUG Kaleidoscope 2010'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_8GtRvOur8pQ/TDHc8E4komI/AAAAAAAAACg/uGGYc6h8gIw/s72-c/lincolnmemorial.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-8942894265175354592</id><published>2010-03-29T15:51:00.001+02:00</published><updated>2010-03-29T15:51:52.197+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ckeditor'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><title type='text'>CKEditor Image Browser - APEX Style</title><content type='html'>FCKEditor has been used with Oracle APEX for a long while, in APEX terms it is known as HTML Editor Standard and HTML Editor Minimal items. In APEX 4.0 it looks like the new CKEditor is included, called Rich Text Editor item. It actually looks like you are given a choice between the old FCKEditor and the new CKEditor, which is a nice touch for those that have customized FCKEditor in earlier versions.&lt;br /&gt;&lt;br /&gt;If you already have FCKEditor configured with image browsing in you current install, I would not sweat through integrating CKEditor unless you have good reason. Editors are known to change, and this particular will be included in APEX 4.0. &lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;/div&gt;&lt;br /&gt;The problem with CKEditor native image browser is the default parameters it includes when launching a pop-up window. Additional parameters does not combine very well with APEX, to say the least. In this post I will give an example on how to adapt the native CKEditor image editor to include your own APEX image browser. This is similar to the earlier article by Carsten Czarski found &lt;a href="http://www.oracle.com/global/de/community/tipps/images-fckeditor/index.html"&gt;here&lt;/a&gt; in German, or a translated version &lt;a href="http://translate.google.com/translate?u=http%3A%2F%2Fwww.oracle.com%2Fglobal%2Fde%2Fcommunity%2Ftipps%2Fimages-fckeditor%2Findex.html&amp;amp;langpair=de%7Cen&amp;amp;hl=de&amp;amp;ie=UTF-8&amp;amp;oe=UTF-8&amp;amp;prev=%2Flanguage_tools"&gt;here&lt;/a&gt;. I will not detail how to create your own image browser, but focus on how to change the CKEditor.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;Beware: I am not a Javascript expert. I am not a CKEditor expert. This is basically just a documentation of my experience of adapting CKEditor.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Prerequisites&lt;/b&gt;&lt;br /&gt;To follow the example you should:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://ckeditor.com/download"&gt;Download&lt;/a&gt; and extract ckeditor_3.2.zip to a folder named ckeditor&lt;/li&gt;&lt;li&gt;&lt;a href="http://svn.fckeditor.net/CKPackager/trunk/bin/"&gt;Download&lt;/a&gt; ckpackager.exe and place it under the ckeditor directory&lt;/li&gt;&lt;li&gt;Map the ckeditor directory in your webserver so it can be reached with a web browser. I will use "/js/ckeditor" in this example.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;CKEditor comes complete with examples, source code and documentation (albeit a bit lacking). CKPackager is not as streamlined, but browsing around the &lt;a href="http://svn.fckeditor.net/CKPackager/trunk/"&gt;subversion repository&lt;/a&gt; you get the general idea. In short CKPackager takes care of putting together ckeditor.js and ckeditor_basic.js after you have fiddled with the source code. I chose the exe-version of CKPackager as I do my development in Windows, but there is a jar-file available as well.&lt;br /&gt;&lt;br /&gt;Create an APEX page to serve as an image browser/picker (it does not have to do anything yet, we'll get to that).&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Give the page an alias: IMAGE_PICKER&lt;/li&gt;&lt;li&gt;Create an APEX page with a text area item&lt;/li&gt;&lt;li&gt;Put the following code in the HTML-header of the APEX page:&lt;/li&gt;&lt;/ul&gt;&lt;pre class="brush: js"&gt;&amp;lt;script type="text/javascript" src="/js/ckeditor/ckeditor.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;br /&gt;   &amp;lt;link href="/js/ckeditor/bouvetckeditor.css" rel="stylesheet" type="text/css" /&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Put the following code in the Post Element Text of the text area item:&lt;/li&gt;&lt;/ul&gt;&lt;pre class="brush: js"&gt;&amp;lt;script type="text/javascript"&amp;gt;&lt;br /&gt;   //&amp;lt;![CDATA[&lt;br /&gt;      // Replace the &amp;lt;textarea id="editor"&amp;gt; with an CKEditor&lt;br /&gt;      // instance, using default configurations.&lt;br /&gt;      CKEDITOR.replace( 'P12_TEXT',&lt;br /&gt;                        {filebrowserImageBrowseUrl : 'f?p=&amp;amp;APP_ID.:IMAGE_PICKER:&amp;amp;SESSION.::NO::',&lt;br /&gt;                        width: '500'&lt;br /&gt;                        });&lt;br /&gt;   //]]&amp;gt;&lt;br /&gt;   &amp;lt;/script&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Replace "/js/ckeditor" to match your own configuration&lt;/li&gt;&lt;li&gt;Replace "P12_TEXT" to match the item name of your text area item.&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;The Problematic URL&lt;/b&gt;&lt;br /&gt;You should now be able to run the page described above, and have CKEditor magically appear where your text area item was supposed to be. When you click the image button in the CKEditor toolbar, the image dialog appears, but when you click the "Browse Server" button, the fun begins. The pop-up window spawns with an URL that has three extra parameters attached.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;CKEditor which contains the item name&lt;/li&gt;&lt;li&gt;CKEditorFuncNum which contains som mystic numeric ID&lt;/li&gt;&lt;li&gt;langCode which contains the language code set for the CKEditor instance&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;APEX will blow up when this URL is tried (as f has no parameters matching the three). Actually APEX won't blow, because f will never be reached as mod_plsql won't recognize any procedure with the signature required. You get the idea.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Changing CKEditor Source Code&lt;/b&gt;&lt;br /&gt;To adapt the URL to APEX style, you must get your hands dirty with Javascript. Actually I think the code of CKEditor looks quite clean, it's just so much of it :-) The source code is located in the "_source" directory, open the file "ckeditor/_source/plugins/filebrowser/plugin.js" in your favourite Javascript editor (or &lt;a href="http://www.textpad.com/"&gt;TextPad&lt;/a&gt; in my case).&lt;br /&gt;&lt;br /&gt;In this example I will keep the original parameters (and parameter names), but if you want to change them, they are located in the browseServer function.&lt;br /&gt;&lt;br /&gt;The addQueryString function accepts the URL and parameters, and builds the complete URL. Change the function to look something like this:&lt;br /&gt;&lt;pre class="brush: js"&gt;function addQueryString( url, params )&lt;br /&gt;        {&lt;br /&gt;                var paramString = [];&lt;br /&gt;                var queryString = [];&lt;br /&gt;&lt;br /&gt;                if ( !params )&lt;br /&gt;                        return url;&lt;br /&gt;                else&lt;br /&gt;                {&lt;br /&gt;                        for ( var i in params ) {&lt;br /&gt;                                paramString.push( i );&lt;br /&gt;                                queryString.push( encodeURIComponent( params[ i ] ) );&lt;br /&gt;                        }&lt;br /&gt;                }&lt;br /&gt;&lt;br /&gt;                return url + paramString.join( "," ) + ":" + queryString.join( "," );&lt;br /&gt;        }&lt;br /&gt;&lt;/pre&gt;The function will now add the parameter names before the colon symbol, and the parameter values after. All in the same order, all separated by a comma. APEX style :-)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Re-Packing CKEditor&lt;/b&gt;&lt;br /&gt;Changing the code is not enough, your changes have not reached ckeditor.js yet. This is what we need the CKPackager for. It collects the source, and builds a new ckeditor.js based on the current source.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Start a command line window and browse to the ckeditor directory&lt;/li&gt;&lt;li&gt;Execute CKPackager  like this: "CKPackager.exe ckeditor.pack -v" (or: "java -jar CKPackager.jar ckeditor.pack -v")&lt;/li&gt;&lt;li&gt;A new version of ckeditor.js and ckeditor_basic.js will be generated for you&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;ckeditor.pack contains reference to the files to be included into the ckeditor.js and ckeditor_basic.js files. The switch -v will give a verbose execution.&lt;br /&gt;&lt;br /&gt;Reload the APEX page containing the CKEditor item (a proper reload to ensure the new version of the ckeditor.js file is loaded), and this time the image picker APEX page should pop-up, so to speak.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Returning From APEX Pop-up Window&lt;/b&gt;&lt;br /&gt;The only thing missing now is the value to return to the CKEditor image dialog. This requires som additional code in your Apex application:&lt;br /&gt;&lt;br /&gt;First create three application level items (unrestricted) named:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;CKEDITOR&lt;/li&gt;&lt;li&gt;CKEDITORFUNCNUM&lt;/li&gt;&lt;li&gt;LANGCODE&lt;/li&gt;&lt;/ul&gt;These items will contain the parameters recieved from CKEditor.&lt;br /&gt;&lt;br /&gt;Next, modify your image picker APEX page:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Create a new submit button on the page, and name it ADD_IMAGE, accept the rest as defaults.&lt;/li&gt;&lt;li&gt;Create a new After Submit Page Process, type PL/SQL, and call it close_popup&lt;/li&gt;&lt;li&gt;Paste the code below into the PL/SQL Process code area:&lt;/li&gt;&lt;/ul&gt;&lt;pre class="brush: sql"&gt;declare&lt;br /&gt;      l_file_url varchar2(4000);&lt;br /&gt;   begin&lt;br /&gt;      l_file_url := '/path/of/image/imagename.jpg';&lt;br /&gt;      htp.p('&amp;lt;body&amp;gt;');&lt;br /&gt;      htp.p('&amp;lt;script type="text/javascript"&amp;gt;');&lt;br /&gt;      htp.p('window.opener.CKEDITOR.tools.callFunction( '||:ckeditorfuncnum||', '''||l_file_url||''');');&lt;br /&gt;      htp.p('window.close();');&lt;br /&gt;      htp.p('&amp;lt;/script&amp;gt;');&lt;br /&gt;      htp.p('&amp;lt;/body&amp;gt;');&lt;br /&gt;   end;&lt;br /&gt;&lt;/pre&gt;This bit of code will call the appropriate CKEditor return function, and close the pop-up window. Modify the code to suite your needs, but beware &lt;a href="http://forums.oracle.com/forums/thread.jspa?threadID=646784"&gt;not to put any code below the last htp.p-call&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Testing the Code&lt;/b&gt;&lt;br /&gt;To test the code so far: &lt;br /&gt;&lt;ul&gt;&lt;li&gt;Run the page containing the CKEditor item.&lt;/li&gt;&lt;li&gt;Click the image button in the CKEditor toolbar&lt;/li&gt;&lt;li&gt;Click the Browse Server button&lt;/li&gt;&lt;li&gt;A pop-up window with your image picker APEX page should now appear&lt;/li&gt;&lt;li&gt;Click the Add Images button on your APEX page, and the CKEditor image dialog URL should now reflect the URL set from your APEX image picker page&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Modifying CKEditor Image Browser Parameters&lt;/b&gt;&lt;br /&gt;So far you have a complete working example with CKEditor image browser and Oracle APEX, but what if you want to modify the parameters themselves? Earlier you modified the function assembling the complete URL based on the defatult parameters, to change the parameters you must modify the browseServer-function in the same file as before ("ckeditor/_source/plugins/filebrowser/plugin.js"). The only parameter the CKEditor really needs when returning image source, is the image source (duh) and CKEditorFuncNum.&lt;br /&gt;&lt;br /&gt;Here is an example of the browseServer-function when parameters CKEditor and langCode is removed, and the current APEX-page id added as a parameter called "callingPage":&lt;br /&gt;&lt;pre class="brush: js"&gt;function browseServer( evt )&lt;br /&gt;   {&lt;br /&gt;      var dialog = this.getDialog();&lt;br /&gt;      var editor = dialog.getParentEditor();&lt;br /&gt;&lt;br /&gt;      editor._.filebrowserSe = this;&lt;br /&gt;&lt;br /&gt;      var width = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowWidth' ]&lt;br /&gt;          || editor.config.filebrowserWindowWidth || '80%';&lt;br /&gt;      var height = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowHeight' ]&lt;br /&gt;          || editor.config.filebrowserWindowHeight || '70%';&lt;br /&gt;&lt;br /&gt;      var params = this.filebrowser.params || {};&lt;br /&gt;      params.CKEditorFuncNum = editor._.filebrowserFn;&lt;br /&gt;      &lt;br /&gt;      // New parameter to get APEX pageId&lt;br /&gt;      params.callingPage = $v('pFlowStepId');&lt;br /&gt;&lt;br /&gt;   var url = addQueryString( this.filebrowser.url, params );&lt;br /&gt;      editor.popup( url, width, height );&lt;br /&gt;   }&lt;br /&gt;&lt;/pre&gt;To generate a new version of CKEditor, use the CKPackager as described earlier in the post. The parameters in the URL uses the same (APEX friendly) construct as before (pName,pName:pValue,pValue), but the number and names of the parameters have changed. The new pop-up URL should now look something like this: "http://&amp;lt;yourserver&amp;gt;/pls/apex/f?p=&amp;lt;app_id&amp;gt;:IMAGE_PICKER:&amp;lt;sessionid&amp;gt;::NO::CKEditorFuncNum,callingPage:&amp;lt;X&amp;gt;,&amp;lt;pageid&amp;gt;".&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A Word of Caution&lt;/b&gt;&lt;br /&gt;When hacking the source like this, and not making your own plugin, guess what happens when you need to upgrade to the next version of CKEditor? When you push the toothbrush too far back, you have to start all over again.&lt;br /&gt;&lt;br /&gt;This hack is in the filebrowser-plugin, which in turn is used by the Flash- and Link-dialogs, so pay attention to changes there as well.&lt;br /&gt;&lt;br /&gt;Not as sleek as I would like it to be, but if moving to  CKEditor is on your list, this is a way to achieve just that.&lt;br /&gt;&lt;br /&gt;As always, use it at your own risk!&lt;br /&gt;&lt;br /&gt;Enjoy :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-8942894265175354592?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/8942894265175354592/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/03/ckeditor-image-browser-apex-style.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/8942894265175354592'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/8942894265175354592'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/03/ckeditor-image-browser-apex-style.html' title='CKEditor Image Browser - APEX Style'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-1533033544688537738</id><published>2010-03-02T12:35:00.000+01:00</published><updated>2010-03-02T12:35:43.381+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='text index'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><category scheme='http://www.blogger.com/atom/ns#' term='docx'/><title type='text'>DOCX Part II: How to Index Document Content With Oracle Text</title><content type='html'>Here I will demonstrate how to index content of CLOB data using Oracle Text. Other than the database objects used in the example, the post addresses the use of Oracle Text in a general way, and will work for any CLOB column. There is no ground breaking code here, but it completes the example on how to handle Microsoft Open Office XML Format (DOCX) documents. The examples and content here are based on &lt;a href="http://monkeyonoracle.blogspot.com/2010/03/docx-part-i-how-to-extract-document.html"&gt;my previous post&lt;/a&gt;.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/S4zuzgUb6oI/AAAAAAAAACQ/zpzENrehA5k/s1600-h/lupa.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/S4zuzgUb6oI/AAAAAAAAACQ/zpzENrehA5k/s320/lupa.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The code here is tested with Oracle database release 11.1.0.6, but should work with releases down to 10g. When I say "tested", remember I am a developer, so it basically means it does compile and it did give the expected result on &lt;i&gt;one&lt;/i&gt; run trough...&lt;br /&gt;&lt;br /&gt;&lt;b&gt;See it in Action&lt;/b&gt;&lt;br /&gt;I have included a search page in my demo application. Upload a DOCX document, and try it for yourself: &lt;a href="http://apex.oracle.com/pls/apex/f?p=28990:20"&gt;Go to demo application&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;About Oracle Text&lt;/b&gt;&lt;br /&gt;Oracle Text (formerly Oracle interMedia) is Oracles full-text retrieval technology, and part of Standard, Enterprise and even XE editions of the database. See &lt;a href="http://www.oracle.com/technology/products/text/index.html"&gt;Oracle Text on oracle.com&lt;/a&gt; for more information.&lt;br /&gt;&lt;br /&gt;In my case I will use it to index text content stored in a CLOB column, and utilize it's search capabilities to enable users to retrieve relevant results based on search phrases.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Indexing CLOBs&lt;/b&gt;&lt;br /&gt;Or BLOBs, or BFILEs, for that matter. It is very easy to get up and running with a text index. The following code is all that is needed to get a text index up and running against the TEXT_CONTENT column of the SAMPLES_DOCX table:&lt;br /&gt;&lt;pre class="brush: sql"&gt;create index samples_docx_ctx on samples_docx&lt;br /&gt;(text_content)&lt;br /&gt;indextype is ctxsys.context&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;That's it! If you want to get fancy, there are a number of options and ways to enhance the indexing, look at &lt;a href="http://download.oracle.com/docs/cd/B28359_01/text.111/b28303/ind.htm"&gt;the documentation&lt;/a&gt; for more information on the subject (lexers, sections, stopword lists, etc.).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Searching with Oracle Text&lt;/b&gt;&lt;br /&gt;With the text index in place, the next step is to use the index to retrieve search results. In my example I will use a technique called "Progressive Relaxation" as described by Roger Ford &lt;a href="http://www.oracle.com/technology/products/text/htdocs/prog_relax.html"&gt;here&lt;/a&gt;. It uses a query template to implement a progressive relaxation of the search tokens.&lt;br /&gt;&lt;br /&gt;The following script creates a package to handle and use the new text index:&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package samples_docx_search_p&lt;br /&gt;as&lt;br /&gt;   -- result types&lt;br /&gt;   type t_result_rec is record (&lt;br /&gt;      score         number,&lt;br /&gt;      filename      samples_docx.filename%type,&lt;br /&gt;      snippet       varchar2(4000));&lt;br /&gt;   type t_result_tab is table of t_result_rec;&lt;br /&gt;   -- synchronize search index&lt;br /&gt;   procedure sync_search_index (&lt;br /&gt;      p_ctx_index_name     in          varchar2 default 'SAMPLES_DOCX_CTX'&lt;br /&gt;   );&lt;br /&gt;   -- search docx content&lt;br /&gt;   function search_text (&lt;br /&gt;      p_tokens    in          varchar2&lt;br /&gt;   ) return t_result_tab pipelined;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create or replace package body samples_docx_search_p&lt;br /&gt;as&lt;br /&gt;   -- synchronize search index&lt;br /&gt;   procedure sync_search_index (&lt;br /&gt;      p_ctx_index_name     in          varchar2 default 'SAMPLES_DOCX_CTX'&lt;br /&gt;   ) is&lt;br /&gt;   begin&lt;br /&gt;      ctx_ddl.sync_index(p_ctx_index_name, '2M');&lt;br /&gt;   end;&lt;br /&gt;   -- search docx content&lt;br /&gt;   function search_text (&lt;br /&gt;      p_tokens    in          varchar2&lt;br /&gt;   ) return t_result_tab pipelined&lt;br /&gt;   as &lt;br /&gt;      l_max_rows         integer := 10;&lt;br /&gt;      l_counter          integer := 0;&lt;br /&gt;      l_ret_rec          t_result_rec;&lt;br /&gt;      l_tokens           varchar2(4000) := lower(p_tokens); &lt;br /&gt;      l_query_template   varchar2(32000):='&amp;lt;query&amp;gt;'&lt;br /&gt;                              ||chr(10)||'   &amp;lt;textquery&amp;gt; heregoesthetokens'&lt;br /&gt;                              ||chr(10)||'     &amp;lt;progression&amp;gt;'&lt;br /&gt;                              ||chr(10)||'       &amp;lt;seq&amp;gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "{", "}", " "))&amp;lt;/rewrite&amp;gt;&amp;lt;/seq&amp;gt;'&lt;br /&gt;                              ||chr(10)||'       &amp;lt;seq&amp;gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "{", "}", "AND"))&amp;lt;/rewrite&amp;gt;/seq&amp;gt;'&lt;br /&gt;                              ||chr(10)||'       &amp;lt;seq&amp;gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "?{", "}", "AND"))&amp;lt;/rewrite&amp;gt;&amp;lt;/seq&amp;gt;'&lt;br /&gt;                              ||chr(10)||'       &amp;lt;seq&amp;gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "{", "}", "OR"))&amp;lt;/rewrite&amp;gt;&amp;lt;/seq&amp;gt;'&lt;br /&gt;                              ||chr(10)||'       &amp;lt;seq&amp;gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "?{", "}", "OR"))&amp;lt;/rewrite&amp;gt;&amp;lt;/seq&amp;gt;'&lt;br /&gt;                              ||chr(10)||'     &amp;lt;/progression&amp;gt;'&lt;br /&gt;                              ||chr(10)||'   &amp;lt;/textquery&amp;gt;'&lt;br /&gt;                              ||chr(10)||'&amp;lt;/query&amp;gt;';&lt;br /&gt;      l_query            varchar2(32000);&lt;br /&gt;   begin&lt;br /&gt;      if l_tokens is null&lt;br /&gt;      then&lt;br /&gt;         return;&lt;br /&gt;      end if;&lt;br /&gt;      -- create query temlpate based on tokens&lt;br /&gt;      l_query_template := replace(l_query_template, 'heregoesthetokens', l_tokens);&lt;br /&gt;      -- restructure tokens for use with snippet&lt;br /&gt;      l_tokens := replace(l_tokens, ' ', ' | ');&lt;br /&gt;      for c in (select rowid&lt;br /&gt;                     , filename &lt;br /&gt;                  from samples_docx &lt;br /&gt;                 where contains (text_content, l_query_template, 1) &amp;gt; 0)&lt;br /&gt;      loop&lt;br /&gt;         -- crude custom score, just listing the results as they come&lt;br /&gt;         l_ret_rec.score := l_counter;&lt;br /&gt;         l_ret_rec.filename := c.filename;&lt;br /&gt;         -- create snippet (with tokenbased highlight) of content to return to the user&lt;br /&gt;         ctx_doc.set_key_type('ROWID');&lt;br /&gt;         l_ret_rec.snippet :=  ctx_doc.snippet('SAMPLES_DOCX_CTX', c.rowid, l_tokens, '&amp;lt;b&amp;gt;', '&amp;lt;/b&amp;gt;', false);&lt;br /&gt;         l_counter := l_counter + 1;&lt;br /&gt;         -- return row&lt;br /&gt;         pipe row (l_ret_rec);&lt;br /&gt;         -- exit when max number of rows exceeded&lt;br /&gt;         exit when l_counter &amp;gt;= l_max_rows;&lt;br /&gt;      end loop;&lt;br /&gt;   end;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;Some parts of the code are more cryptic than others.&lt;br /&gt;&lt;br /&gt;First up is the "SYNC_SEARCH_INDEX" procedure. It's sole purpose is to synchronize the search index with the TEXT_CONTENT column. When you perform DML on the TEXT_CONTENT column, changes to the text index does not automatically propagate to the search index (unless the index is explicitly told to do so). So, in this example, if you insert a new row, the TEXT_CONTENT will not show up through Oracle Text searches until you have told it to synchronize the index. Indexing can be a resource demanding operation, so it makes sense to separate table DML from the indexing job.&lt;br /&gt;&lt;br /&gt;In short: &lt;i&gt;Make sure you synchronize the text index after DML on the indexed column has been performed&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;Next is the "SEARCH_TEXT" function, particularly the query template. In the past I have used Oracle Text in its most simple form, but query templates adds a new dimension to controlling your searches. Basically you define how you want to use the search tokens (search phrase) to find matches in the text index. It is written in XML-form, and the &amp;lt;seq&amp;gt;-tag states the order of which you want to process your criteria. For more information on query templates, take a look at &lt;a href="http://download.oracle.com/docs/cd/B28359_01/text.111/b28303/query.htm"&gt;the documentation&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;A short explanation on the query template:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "{", "}", " "))&amp;lt;/rewrite&amp;gt;: Matches when whole phrase (tokens in order) is present within document&lt;/li&gt;&lt;li&gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "{", "}", "AND"))&amp;lt;/rewrite&amp;gt;: Matches when each token is present within document&lt;/li&gt;&lt;li&gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "?{", "}", "AND"))&amp;lt;/rewrite&amp;gt;: Matches when each token is present within docoument, allowing typos (fussy search)&lt;/li&gt;&lt;li&gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "{", "}", "OR"))&amp;lt;/rewrite&amp;gt;: Matches when any of the tokens are present within document&lt;/li&gt;&lt;li&gt;&amp;lt;rewrite&amp;gt;transform((TOKENS, "?{", "}", "OR"))&amp;lt;/rewrite&amp;gt;: Matches when any of the tokens are present within document, allowing typos (fussy search)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Although it will return matches according to the sequence, the "score" within the sequence will be random (as far as I could see at least), so the "score" column in the example above needs a bit of work. I also learned the hard way that structure of the XML in the query template is not validated, and it swallows typos without a word.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Test the Code&lt;/b&gt;&lt;br /&gt;You can test the code directly from SQL*Plus or your favorite weapon of choice. The following code will search for "test":&lt;br /&gt;&lt;pre class="brush: sql"&gt;select filename&lt;br /&gt;,      snippet&lt;br /&gt;from   table(samples_docx_search_p.search_text('test'))&lt;br /&gt;order  by score&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Wildcards&lt;/b&gt;&lt;br /&gt;My query template does not support searching for parts of words, there are some reasons for this behavior.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Wildcard searches requires more resources&lt;/li&gt;&lt;li&gt;The results returned will be unpredictable. Imagine searching for "ora*", both "oracle" and "orange" will match.&lt;/li&gt;&lt;li&gt;Wildcard searches are more suitable for look-ups in structured data&lt;/li&gt;&lt;/ul&gt;What did I mean by that last statement? As I see it, users today are using a wide range of search engines and site searches every day, and wildcard matching are in most cases available only through the "advanced search"-link (that is rarely used, if ever). Let alone that people do not expect this kind of behavior anymore. If you want to help the user, add ajax autocomplete to your search field, and use the Oracle Text index. In the traditional back office application, it is another matter (look-ups and such).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Create a Search Page in Oracle APEX&lt;/b&gt;&lt;br /&gt;I will not do a detailed explanation here, but creating a report based on a query will do the trick. If you choose "Yes" for "Enable search" in the wizard it will save you some work by creating the search dialog on the page. Clean up the query in the report region so that tour query looks something like this:&lt;br /&gt;&lt;pre class="brush: sql"&gt;select filename&lt;br /&gt;,      snippet&lt;br /&gt;from   table(samples_docx_search_p.search_text(:PXX_REPORT_SEARCH))&lt;br /&gt;order  by score&lt;br /&gt;&lt;/pre&gt;When keeping the order by, I assume that you will let the score column determine the result order. For good measure you can create a row template for the result representation in the APEX report so you can have heading and snippet spread over two rows.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update Sample Upload Page&lt;/b&gt;&lt;br /&gt;If you created the upload page described in my previous post, include a new page process (After Submit, and after the "extract_text"-process). The process should be of PL/SQL-type, and the code should look something like this:&lt;br /&gt;&lt;pre class="brush: sql"&gt;samples_docx_search_p.sync_search_index;&lt;br /&gt;&lt;/pre&gt;It does not necessary have to be a page process, but it is important to synchronize the text index after DML.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;How to Handle DOC and DOCX&lt;/b&gt;&lt;br /&gt;...And PDF, XLS, PPT, etc. Well, the DOCX is the exception here. Oracle Text does not understand the format yet (unless you are running on release 11.1.0.7 or higher). If you create an Oracle Text index on a BLOB column containing other file types which it does understand, they will be indexed automagically. If you want to index the content of a DOCX document in the same index, use the technique described in my last post, convert the output (plain text) into a BLOB, and insert the BLOB into the table with the other documents. Or said in a different way: you except and handle DOCX documents differently than the known document types.&lt;br /&gt;&lt;br /&gt;Querying an Oracle Text index works the same for BLOB and CLOB columns.&lt;br /&gt;&lt;br /&gt;There is definitely room for improvement in the code, but it serves well enough for demonstration purposes.&lt;br /&gt;&lt;br /&gt;Enjoy :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-1533033544688537738?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/1533033544688537738/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/03/docx-part-ii-how-to-index-document.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1533033544688537738'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1533033544688537738'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/03/docx-part-ii-how-to-index-document.html' title='DOCX Part II: How to Index Document Content With Oracle Text'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_8GtRvOur8pQ/S4zuzgUb6oI/AAAAAAAAACQ/zpzENrehA5k/s72-c/lupa.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-396674855393371105</id><published>2010-03-01T15:48:00.000+01:00</published><updated>2010-03-01T15:48:33.098+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plsql'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><category scheme='http://www.blogger.com/atom/ns#' term='docx'/><title type='text'>DOCX Part I: How to Extract Document Content as Plain Text</title><content type='html'>In this post I will demonstrate how to extract plain text from Microsoft Open Office XML Format (DOCX) documents stored as a BLOB data type in an Oracle database. It is part one of two posts concerning how to extract and index unstructured content stored in DOCX files. I found snippets of solutions several places, but no complete copy-paste solution, so perhaps this will save some of you a bit of work.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/_8GtRvOur8pQ/S4vHvKHjyvI/AAAAAAAAACI/pa5eiB_fIVk/s1600-h/text.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_8GtRvOur8pQ/S4vHvKHjyvI/AAAAAAAAACI/pa5eiB_fIVk/s320/text.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The solution is tested on Oracle database Enterprise Edition release 11.1.0.6 with Apex 3.2.1 running on Windows XP, but should work down to 10g Standard Edition (not Oracle XE, since part of the code is in Java).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;See it In Action&lt;/b&gt;&lt;br /&gt;I have updated my demo application with a couple of pages so you can try it out yourselves: &lt;a href="http://apex.oracle.com/pls/apex/f?p=28990:20"&gt;Go to demo application&lt;/a&gt;. There is a limited space available at apex.oracle.com, so please take care when uploading (and it is very easy to test in your own environment).&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;When will You Ever Need This?&lt;/b&gt;&lt;br /&gt;If you have an Oracle database version prior to 11.1.0.7, Oracle Text is not able to index DOCX documents (as it does DOC documents). From version 11.1.0.7 and on, Oracle uses technology from Stellent, and DOCX is indexed as the other formats. So if you want to index DOCX text content in Oracle Text in a version prior to 11.1.0.7, then this could be a way to do it.&lt;br /&gt;&lt;br /&gt;Oracle is working on a back-port to 10.x, but I have no status when (if) this will be available. Microsoft Office 2007 has been around since.. Take a wild guess! So these things obviously takes some time.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;About Microsoft Open Office XML Format&lt;/b&gt;&lt;br /&gt;As seen from the eyes of a PL/SQL developer, that is. First of all, XML sounds promising, you can do a ton of things with a valid XML in an Oracle database. You get the first nasty surprise when opening a DOCX-document in your favorite text editor; it is a zip archive! The next is when you realize that utl_compress can't help you uncompressing it either.&lt;br /&gt;&lt;br /&gt;So Google next, and realizing this cannot be easily done in a pure PL/SQL solution, Google yields &lt;a href="http://www.infoq.com/articles/cracking-office-2007-with-java"&gt;this gem from Ted Neward&lt;/a&gt;. It is a DOCX (Open XML) walk through as seen from the eyes of a Java developer. Very educational.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;How to Unzip in PL/SQL&lt;/b&gt;&lt;br /&gt;You probably can, but that would probably also involve a lot of work. The easy way is to take hold of a Java method that already does what you want. After searching the net I came up with &lt;a href="http://www.oooforum.org/forum/viewtopic.phtml?t=24711"&gt;this post from peterv6i&lt;/a&gt; which does exactly what I want (and more, it can also add files (BLOB) to an existing zip archive).&lt;br /&gt;&lt;br /&gt;My short version of this (as I only need to extract content), follows below. The script creates a java class with a method to extract a file from a zip archive, and a PL/SQL wrapper to the getFileFromZip method. The database user must have JAVA_DEPLOY and JAVAUSERPRIV (I think) roles.&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace java source named "ZIPImpl" &lt;br /&gt;AS &lt;br /&gt;import java.io.*; &lt;br /&gt;import java.util.zip.*; &lt;br /&gt;import java.sql.*; &lt;br /&gt;import oracle.sql.*; &lt;br /&gt;public class ZIPImpl &lt;br /&gt;{ &lt;br /&gt;public static void getFileFromZip(oracle.sql.BLOB srcBlob, oracle.sql.BLOB dstBlob[], java.lang.String name) { &lt;br /&gt;try { &lt;br /&gt;   OutputStream outBuffer = dstBlob[0].getBinaryOutputStream(); &lt;br /&gt;   InputStream inBuffer = srcBlob.getBinaryStream(); &lt;br /&gt;   ZipInputStream zip = new ZipInputStream(inBuffer); &lt;br /&gt;   ZipEntry entry; &lt;br /&gt;   byte[] tmpBuffer = new byte[2048]; &lt;br /&gt;   while((entry = zip.getNextEntry()) != null) { &lt;br /&gt;      if (entry.getName().compareTo(name)==0) { &lt;br /&gt;         int n; &lt;br /&gt;         while ((n = zip.read(tmpBuffer)) &amp;gt;= 0) &lt;br /&gt;           outBuffer.write(tmpBuffer, 0, n); &lt;br /&gt;      } &lt;br /&gt;   } &lt;br /&gt;   outBuffer.close(); &lt;br /&gt; } &lt;br /&gt; catch (SQLException e) { &lt;br /&gt;   System.err.println(e); &lt;br /&gt; } &lt;br /&gt; catch (IOException e) { &lt;br /&gt;   System.err.println(e); &lt;br /&gt; } &lt;br /&gt;} &lt;br /&gt;}; &lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;alter java source "ZIPImpl" compile &lt;br /&gt;/      &lt;br /&gt;&lt;br /&gt;create or replace package zip as &lt;br /&gt;   procedure unzip( &lt;br /&gt;      p_src       in          blob&lt;br /&gt;   ,  p_dst       in out      blob&lt;br /&gt;   ,  p_filename  in          varchar2); &lt;br /&gt;end; &lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create or replace package body zip as &lt;br /&gt;   procedure unzip( &lt;br /&gt;      p_src       in          blob&lt;br /&gt;   ,  p_dst       in out      blob&lt;br /&gt;   ,  p_filename  in          varchar2) &lt;br /&gt;   as language java &lt;br /&gt;   name 'ZIPImpl.getFileFromZip(oracle.sql.BLOB, oracle.sql.BLOB[], java.lang.String)'; &lt;br /&gt;end; &lt;br /&gt;/ &lt;br /&gt;&lt;/pre&gt;&lt;b&gt;How to Extract Plain Text Content from DOCX&lt;/b&gt;&lt;br /&gt;So, now you have a procedure to extract a file from a zip archive, next is to attack the content of the file. The DOCX file consists of several files, but the text content of the document resides in "word/document.xml". As the file is an XML document, I will perform an XML/XSL transformation of the file to be left with only plain text.&lt;br /&gt;&lt;br /&gt;The script below creates a table SAMPLES_DOCX which will hold the original DOCX file stored as a BLOB, and a CLOB containing the plain text. It also creates a package with three procedures:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Store the DOCX (as uploaded into APEX_APPLICATION_FILES)&lt;/li&gt;&lt;li&gt;Download the original file (strictly not necessary for this exercise, but nice to have all the same)&lt;/li&gt;&lt;li&gt;Extract and store plain text from DOCX&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The XSL used to transform the document.xml file is an (extremely) abbreviated version of the XSL &lt;a href="http://forums.oracle.com/forums/thread.jspa?messageID=3368284"&gt;posted here in the Oracle Technical Forums&lt;/a&gt;.&lt;br /&gt;&lt;pre class="brush: sql"&gt;create table samples_docx (&lt;br /&gt;   filename       varchar2(255) not null&lt;br /&gt;,  mime_type      varchar2(255)&lt;br /&gt;,  orig_file      blob&lt;br /&gt;,  text_content   clob)&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;alter table samples_docx add constraint samples_docx_pk primary key (filename)&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create or replace package sample_docx_p as&lt;br /&gt;   -- get file from apex_application_files and store it in samples_docx table&lt;br /&gt;   procedure store_docx (&lt;br /&gt;      p_file_name    in       varchar2);&lt;br /&gt;   -- download procedure for original docx file&lt;br /&gt;   procedure retrieve_docx (&lt;br /&gt;      p_file_name    in       varchar2);&lt;br /&gt;   -- extract plain text from docx file (this is the meat)&lt;br /&gt;   procedure store_text (&lt;br /&gt;      p_file_name    in       varchar2);&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create or replace package body sample_docx_p as&lt;br /&gt;   -- get file from apex_application_files and store it in samples_docx table&lt;br /&gt;   procedure store_docx (&lt;br /&gt;      p_file_name    in       varchar2)&lt;br /&gt;   is&lt;br /&gt;      l_file         blob;&lt;br /&gt;      l_mime_type    apex_application_files.mime_type%type;&lt;br /&gt;      l_name         apex_application_files.name%type;&lt;br /&gt;   begin&lt;br /&gt;      -- get file from apex files&lt;br /&gt;      select  name&lt;br /&gt;            , mime_type&lt;br /&gt;            , blob_content&lt;br /&gt;       into   l_name&lt;br /&gt;            , l_mime_type&lt;br /&gt;            , l_file&lt;br /&gt;       from   apex_application_files&lt;br /&gt;      where   name = p_file_name;&lt;br /&gt;      -- insert record into samples table&lt;br /&gt;      insert into samples_docx ( filename&lt;br /&gt;                               , mime_type&lt;br /&gt;                               , orig_file)&lt;br /&gt;       values   (   l_name&lt;br /&gt;                  , l_mime_type&lt;br /&gt;                  , l_file);&lt;br /&gt;      -- delete file from apex files when done&lt;br /&gt;      delete from   apex_application_files&lt;br /&gt;           where   name = p_file_name;&lt;br /&gt;   end store_docx;&lt;br /&gt;   -- download procedure for original docx file&lt;br /&gt;   procedure retrieve_docx (&lt;br /&gt;      p_file_name    in       varchar2)&lt;br /&gt;   is&lt;br /&gt;      l_file         blob;&lt;br /&gt;      l_mime_type    apex_application_files.mime_type%type;&lt;br /&gt;      l_name         apex_application_files.name%type;&lt;br /&gt;      l_size         number;&lt;br /&gt;   begin&lt;br /&gt;      -- get file from apex files&lt;br /&gt;      select  filename&lt;br /&gt;            , mime_type&lt;br /&gt;            , orig_file&lt;br /&gt;            , dbms_lob.getlength(orig_file)&lt;br /&gt;       into   l_name&lt;br /&gt;            , l_mime_type&lt;br /&gt;            , l_file&lt;br /&gt;            , l_size&lt;br /&gt;       from samples_docx   &lt;br /&gt;      where   filename = p_file_name;&lt;br /&gt;      -- return file&lt;br /&gt;      owa_util.mime_header( nvl(l_mime_type,'application/octet'), false);&lt;br /&gt;      htp.p('Content-length: ' || l_size);&lt;br /&gt;      htp.p('Content-Disposition:  attachment; filename="'||replace(replace(substr(l_name,instr(l_name,'/')+1),chr(10),null),chr(13),null)|| '"');&lt;br /&gt;      owa_util.http_header_close;&lt;br /&gt;      wpg_docload.download_file(l_file);&lt;br /&gt;   end retrieve_docx;&lt;br /&gt;   -- perform xsl tranformation of document.xml&lt;br /&gt;   function get_text (&lt;br /&gt;      p_docx_xml     in       xmltype&lt;br /&gt;   ) return clob&lt;br /&gt;   is&lt;br /&gt;   l_clob   clob;&lt;br /&gt;   -- xsl monkeyed from http://forums.oracle.com/forums/thread.jspa?messageID=3368284&lt;br /&gt;   -- abbreviated quite a bit, check out original posting by "user304344" for the original&lt;br /&gt;   l_xsl    xmltype := xmltype('&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" '&lt;br /&gt;                     ||chr(10)||'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"'&lt;br /&gt;                     ||chr(10)||'xmlns:v="urn:schemas-microsoft-com:vml"'&lt;br /&gt;                     ||chr(10)||'exclude-result-prefixes="w v"&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:output method="text" indent="no" encoding="UTF-8" version="1.0"/&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;!-- document root --&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:template match="/"&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;!-- root element in document --&amp;gt; '&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:apply-templates select="w:document"/&amp;gt; '&lt;br /&gt;                     ||chr(10)||'&amp;lt;/xsl:template&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;!-- ****************************start document**************************** --&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:template match="w:document"&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:for-each select="//w:p"&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:apply-templates select="*/w:t"/&amp;gt; '&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:text&amp;gt;|¤¤&amp;lt;/xsl:text&amp;gt; '&lt;br /&gt;                     ||chr(10)||'&amp;lt;/xsl:for-each&amp;gt; '&lt;br /&gt;                     ||chr(10)||'&amp;lt;/xsl:template&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;!-- get all text nodes within a para --&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:template match="*/w:t"&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;xsl:value-of select="."/&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;/xsl:template&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;!-- **************************** end document**************************** --&amp;gt;'&lt;br /&gt;                     ||chr(10)||'&amp;lt;/xsl:stylesheet&amp;gt;');&lt;br /&gt;   begin&lt;br /&gt;      -- "|¤¤" is just a hack to get linebreaks, should be an easier way to achieve this&lt;br /&gt;      select replace(xmltransform(p_docx_xml, l_xsl).GetClobVal(), '|¤¤', chr(10))&lt;br /&gt;        into l_clob&lt;br /&gt;        from dual;&lt;br /&gt;      return l_clob;&lt;br /&gt;   end;   &lt;br /&gt;   -- extract plain text from docx file (this is the meat)&lt;br /&gt;   procedure store_text (&lt;br /&gt;      p_file_name    in       varchar2)&lt;br /&gt;   is&lt;br /&gt;      l_docx            blob;&lt;br /&gt;      l_docx_unzip      blob;&lt;br /&gt;      l_doc             clob;&lt;br /&gt;      l_dest_offset     integer := 1;&lt;br /&gt;      l_src_offset      integer := 1;&lt;br /&gt;      l_lang_context    integer := dbms_lob.default_lang_ctx;&lt;br /&gt;      l_warning         integer;&lt;br /&gt;   begin&lt;br /&gt;      -- get original file&lt;br /&gt;      select orig_file&lt;br /&gt;        into l_docx&lt;br /&gt;        from samples_docx&lt;br /&gt;       where filename = p_file_name;&lt;br /&gt;      -- create lob locators&lt;br /&gt;      dbms_lob.createtemporary(l_docx_unzip,false);&lt;br /&gt;      dbms_lob.createtemporary(l_doc,false);&lt;br /&gt;      -- use java to unzip the docx file and retrieve document.xml&lt;br /&gt;      zip.unzip(l_docx, l_docx_unzip, 'word/document.xml');&lt;br /&gt;      -- convert blob to clob&lt;br /&gt;      dbms_lob.converttoclob&lt;br /&gt;               ( dest_lob =&amp;gt; l_doc&lt;br /&gt;               , src_blob =&amp;gt; l_docx_unzip&lt;br /&gt;               , amount =&amp;gt; dbms_lob.lobmaxsize&lt;br /&gt;               , dest_offset =&amp;gt; l_dest_offset&lt;br /&gt;               , src_offset =&amp;gt; l_src_offset&lt;br /&gt;               , blob_csid =&amp;gt; nls_charset_id('AL32UTF8') --in my case, it is stored as UTF8&lt;br /&gt;               , lang_context =&amp;gt; l_lang_context&lt;br /&gt;               , warning =&amp;gt; l_warning&lt;br /&gt;               );&lt;br /&gt;      -- transform clob via xsl to get clean text&lt;br /&gt;      l_doc := get_text(xmltype(l_doc));&lt;br /&gt;      -- update the column containing document text&lt;br /&gt;      update samples_docx&lt;br /&gt;         set text_content = l_doc&lt;br /&gt;       where filename = p_file_name;&lt;br /&gt;      -- clean lob locators, should be repeated in exception block&lt;br /&gt;      dbms_lob.freetemporary(l_docx_unzip);&lt;br /&gt;      dbms_lob.freetemporary(l_doc);&lt;br /&gt;   end;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;Bringing it Together in Oracle APEX&lt;/b&gt;&lt;br /&gt;To test the code, you can create a simple upload page in APEX:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Create new page&lt;/li&gt;&lt;li&gt;Form&lt;/li&gt;&lt;li&gt;Form on a procedure&lt;/li&gt;&lt;li&gt;&amp;lt;schema name&amp;gt; where you installed the application&lt;/li&gt;&lt;li&gt;Choose "sample_docx_p.store_docx" as stored procedure name&lt;/li&gt;&lt;li&gt;Accept defaults (or change to your liking)&lt;/li&gt;&lt;li&gt;Choose desired tab options&lt;/li&gt;&lt;li&gt;Leave Invoking Page/Button Label blank if you do not want an invoking page&lt;/li&gt;&lt;li&gt;Choose branch pages (can be the same as the one you are creating)&lt;/li&gt;&lt;li&gt;Accept defaults&lt;/li&gt;&lt;li&gt;Click Finish&lt;/li&gt;&lt;li&gt;Click Edit Page&lt;/li&gt;&lt;li&gt;Choose PXX_FILE_NAME, and change "Display as" from "Text item" to "File Browse..."&lt;/li&gt;&lt;li&gt;Apply changes&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;To add post processing to extract DOCX file content:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Click Create under Processes&lt;/li&gt;&lt;li&gt;Choose PL/SQL&lt;/li&gt;&lt;li&gt;Give the process a name like "extract_text"&lt;/li&gt;&lt;li&gt;In the "Enter PL/SQL Page Process" dialog, paste "sample_docx_p.store_text(:PXX_FILE_NAME);" (XX being your page number)&lt;/li&gt;&lt;li&gt;Create process (and ensure the process comes after "Run Stored Procedure" process created by the page wizard&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;The last process could be implemented as a trigger on the samples_docx table.&lt;br /&gt;&lt;br /&gt;Run the page and test by uploading a DOCX document (or test it in my demo application).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Building More&lt;/b&gt;&lt;br /&gt;If you have a need to manipulate DOCX documents, this could probably be achieved by using the complete "ZIPImpl" as posted in &lt;a href="http://www.oooforum.org/forum/viewtopic.phtml?t=24711"&gt;this article&lt;/a&gt;, and using substitution variables in the same manner as I have described with RTF documents in an earlier post.&lt;br /&gt;&lt;br /&gt;The zip/unzip may also come in handy when handling other office files (like Open Office ODF).&lt;br /&gt;&lt;br /&gt;This is only a proof of concept, and the code is not production ready. Really! I'm not just saying that out of habit.&lt;br /&gt;&lt;br /&gt;The next (and final) part of the DOCX "series" will address how to index the plain text content using Oracle Text.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-396674855393371105?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/396674855393371105/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/03/docx-part-i-how-to-extract-document.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/396674855393371105'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/396674855393371105'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/03/docx-part-i-how-to-extract-document.html' title='DOCX Part I: How to Extract Document Content as Plain Text'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_8GtRvOur8pQ/S4vHvKHjyvI/AAAAAAAAACI/pa5eiB_fIVk/s72-c/text.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-1350842315998246835</id><published>2010-02-04T10:33:00.001+01:00</published><updated>2010-02-04T13:06:35.340+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='apache'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Nice URL in a Public Facing Oracle APEX Application</title><content type='html'>&lt;i&gt;UPDATE: Try out Morten Bråtens excellent post on &lt;a href="http://ora-00001.blogspot.com/2009/07/creating-rest-web-service-with-plsql.html"&gt;creating a REST web service with PL/SQL&lt;/a&gt; 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.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Why Do You Need Nice URLs?&lt;/b&gt;&lt;br /&gt;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 &lt;b&gt;YOU&lt;/b&gt; need this for &lt;b&gt;YOUR&lt;/b&gt; 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.&lt;br /&gt;&lt;br /&gt;For an intranet environment I can see no good reasons to do this. None.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Alternatives&lt;/b&gt;&lt;br /&gt;There are already some designs out there to help you on your way. Patrick Wolf has &lt;a href="http://www.inside-oracle-apex.com/nicer-url-for-an-oracle-apex-application/"&gt;a list of the more popular ones&lt;/a&gt;. Kris Rice's post &lt;a href="http://krisrice.blogspot.com/2007/02/better-apex-urls.html"&gt;Better APEX URLs&lt;/a&gt; also uses a map file. The &amp;lt;iframe&amp;gt; solution is also worth considering, depending on your need. There are also some &lt;a href="http://forums.oracle.com/forums/thread.jspa?threadID=299891&amp;amp;start=15&amp;amp;tstart=0"&gt;very creative Apache rewrite solutions&lt;/a&gt; described in the OTN APEX-forum.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;My Attempt&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;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.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;This is how it works:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;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&lt;/li&gt;&lt;li&gt;You can manually create mapping between APEX URL and a nice URL to fully control both APEX and nice URL&lt;/li&gt;&lt;li&gt;The APEX to nice URL mapping will be written to an Apache map file&lt;/li&gt;&lt;li&gt;Apache checks the content of the map file for a potential rewrite&lt;/li&gt;&lt;li&gt;If the map file changes, Apache will cache the new version automagically&lt;/li&gt;&lt;/ul&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Database Code&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;pre class="brush: sql"&gt;-- change path to suite your environment&lt;br /&gt;create or replace directory APACHECONF as 'C:\Oracle\OraXE\Apache\conf'&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create table app_page_type (&lt;br /&gt;   page_id              number&lt;br /&gt;,  source_table         varchar2(32)&lt;br /&gt;,  source_table_pk_col  varchar2(32)&lt;br /&gt;,  url_path             varchar2(255)&lt;br /&gt;,  pk_page_parameter    varchar2(255)&lt;br /&gt;,  name_column          varchar2(32)&lt;br /&gt;)&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create unique index app_page_type_uk&lt;br /&gt;   on app_page_type (page_id, source_table)&lt;br /&gt;/   &lt;br /&gt;&lt;br /&gt;create table app_page_url_mapping&lt;br /&gt;(&lt;br /&gt;   page_id             number not null&lt;br /&gt; , source_table        varchar2(32)&lt;br /&gt; , source_table_id     number&lt;br /&gt; , apex_url            varchar2(4000) not null&lt;br /&gt; , nice_url            varchar2(4000) not null&lt;br /&gt; , nice_title          varchar2(255) &lt;br /&gt;)&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create unique index app_page_url_mapping_uk&lt;br /&gt;   on app_page_url_mapping (page_id, source_table, source_table_id)&lt;br /&gt;/   &lt;br /&gt;&lt;br /&gt;create or replace package app_page_p&lt;br /&gt;as&lt;br /&gt;   -- create url-friendly construct, 255 chars long&lt;br /&gt;   -- based on a name.&lt;br /&gt;   function get_nice_name (&lt;br /&gt;      p_name               in     varchar2&lt;br /&gt;   ,  p_source_table_id    in     varchar2 default null&lt;br /&gt;   ,  p_url_end            in     varchar2 default '.html'&lt;br /&gt;   ) return varchar2;&lt;br /&gt;   -- get nice url based on source table, can be used&lt;br /&gt;   -- directly from APEX application&lt;br /&gt;   function get_nice_url (&lt;br /&gt;      p_page_id            in    number&lt;br /&gt;   ,  p_source_table       in    varchar2&lt;br /&gt;   ,  p_source_table_id    in    varchar2&lt;br /&gt;   ) return varchar2;&lt;br /&gt;   -- get nice url from APEX page_id, will only return&lt;br /&gt;   -- mappings without source table defined&lt;br /&gt;   function get_nice_url (&lt;br /&gt;      p_page_id            in    number&lt;br /&gt;   ) return varchar2;&lt;br /&gt;   -- create mapping based on page type and source table&lt;br /&gt;   procedure create_mapping (&lt;br /&gt;      p_source_table       in       varchar2&lt;br /&gt;   ,  p_source_table_id    in       varchar2&lt;br /&gt;   ,  p_name               in       varchar2 default null);&lt;br /&gt;   -- create complete manual mapping  &lt;br /&gt;   procedure create_manual_mapping (&lt;br /&gt;      p_page_id            in       number&lt;br /&gt;   ,  p_apex_url           in       varchar2&lt;br /&gt;   ,  p_nice_url           in       varchar2&lt;br /&gt;   ,  p_nice_title         in       varchar2 default null);&lt;br /&gt;   --remove mapping when source is removed  &lt;br /&gt;   procedure remove_mapping (&lt;br /&gt;      p_source_table       in       varchar2&lt;br /&gt;   ,  p_source_table_id    in       number&lt;br /&gt;   );&lt;br /&gt;   -- remove manual mapping to page_id&lt;br /&gt;   procedure remove_manual_mapping (&lt;br /&gt;      p_page_id            in       number&lt;br /&gt;   );&lt;br /&gt;   -- synchronize map file with mapping table&lt;br /&gt;   procedure synchronize_mapfile;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;          &lt;br /&gt;create or replace package body app_page_p&lt;br /&gt;as&lt;br /&gt;   -- global variables&lt;br /&gt;   -- change variables to suite your environment&lt;br /&gt;   g_app_id       number        := 1000; --APEX app_id for public application&lt;br /&gt;   g_app_name     varchar2(255) := 'My test app'; --descriptive name of application&lt;br /&gt;   g_dad_path     varchar2(255) := '/pls/apex'; --dad or location&lt;br /&gt;   g_map_file     varchar2(255) := 'map.txt'; --name of map file&lt;br /&gt;   g_tmp_map_file varchar2(255) := 'map_tmp.txt'; --name of temporary map file&lt;br /&gt;   g_map_dir      varchar2(255) := 'APACHECONF'; --name of directory&lt;br /&gt;   -- get nice url from source_table&lt;br /&gt;   function get_nice_url (&lt;br /&gt;      p_page_id            in    number&lt;br /&gt;   ,  p_source_table       in    varchar2&lt;br /&gt;   ,  p_source_table_id    in    varchar2&lt;br /&gt;   ) return varchar2 is&lt;br /&gt;      l_ret    varchar2(255);&lt;br /&gt;   begin&lt;br /&gt;      select tab.nice_url&lt;br /&gt;        into l_ret&lt;br /&gt;        from app_page_url_mapping tab&lt;br /&gt;       where tab.page_id = p_page_id&lt;br /&gt;         and tab.source_table = p_source_table&lt;br /&gt;         and tab.source_table_id = p_source_table_id;&lt;br /&gt;      return l_ret;&lt;br /&gt;   exception&lt;br /&gt;      when no_data_found&lt;br /&gt;      then&lt;br /&gt;         return null;&lt;br /&gt;   end;&lt;br /&gt;   -- get nice url from page_id, manually created&lt;br /&gt;   function get_nice_url (&lt;br /&gt;      p_page_id            in    number&lt;br /&gt;   ) return varchar2 is&lt;br /&gt;      l_ret    varchar2(255);&lt;br /&gt;   begin&lt;br /&gt;      for r in (select tab.nice_url&lt;br /&gt;                  from app_page_url_mapping tab&lt;br /&gt;                 where tab.page_id = p_page_id&lt;br /&gt;                   and tab.source_table_id is null)&lt;br /&gt;      loop&lt;br /&gt;         l_ret := r.nice_url;&lt;br /&gt;         exit;&lt;br /&gt;      end loop;&lt;br /&gt;      return l_ret;&lt;br /&gt;   exception&lt;br /&gt;      when no_data_found&lt;br /&gt;      then&lt;br /&gt;         return null;&lt;br /&gt;   end;&lt;br /&gt;   -- create url-friendly construct, 255 chars long&lt;br /&gt;   function get_nice_name (&lt;br /&gt;      p_name               in     varchar2&lt;br /&gt;   ,  p_source_table_id    in     varchar2 default null&lt;br /&gt;   ,  p_url_end            in     varchar2 default '.html'&lt;br /&gt;   ) return varchar2 is&lt;br /&gt;      l_ret    varchar2(4000) := p_name;&lt;br /&gt;      l_sub    number;&lt;br /&gt;   begin&lt;br /&gt;      -- special national translation (excerpt)&lt;br /&gt;      l_ret := translate(lower(l_ret), 'æøå ', 'aoa_');&lt;br /&gt;      l_ret := regexp_replace(l_ret, '([^[:alnum:]|_])', '', 1, 0, 'i');&lt;br /&gt;      l_sub := nvl(length(to_char(p_source_table_id)||p_url_end),0);&lt;br /&gt;      l_ret := substr(l_ret, 1, 255-l_sub);&lt;br /&gt;      l_ret := l_ret||p_source_table_id||p_url_end;&lt;br /&gt;      return l_ret;&lt;br /&gt;   end;&lt;br /&gt;   -- lookup column value for source_table and source_table_id  &lt;br /&gt;   function get_source_name (&lt;br /&gt;      p_source_table       in       varchar2&lt;br /&gt;   ,  p_source_table_id    in       number&lt;br /&gt;   ,  p_source_pk_col      in       varchar2&lt;br /&gt;   ,  p_source_name_col    in       varchar2&lt;br /&gt;   ) return varchar2&lt;br /&gt;   is&lt;br /&gt;      l_sql    varchar2(4000) :=   'select '||p_source_name_col||' pk_col'||chr(10)&lt;br /&gt;                                 ||'  from '||p_source_table||chr(10)&lt;br /&gt;                                 ||' where '||p_source_pk_col||' = :1';&lt;br /&gt;      l_ret    varchar2(4000);&lt;br /&gt;   begin&lt;br /&gt;      execute immediate l_sql into l_ret using in p_source_table_id;&lt;br /&gt;      return l_ret;&lt;br /&gt;   end;&lt;br /&gt;   -- create the actual mapping&lt;br /&gt;   procedure create_mapping (&lt;br /&gt;      p_source_table       in       varchar2&lt;br /&gt;   ,  p_source_table_id    in       varchar2&lt;br /&gt;   ,  p_name               in       varchar2 default null&lt;br /&gt;   ) is&lt;br /&gt;      l_orig_name     varchar2(4000);&lt;br /&gt;      l_nice_name     varchar2(255);&lt;br /&gt;      l_apex_url      varchar2(4000);&lt;br /&gt;      l_nice_url      varchar2(4000); &lt;br /&gt;      l_nice_title    varchar2(4000);&lt;br /&gt;   begin&lt;br /&gt;      -- delete previous entries&lt;br /&gt;      delete from app_page_url_mapping&lt;br /&gt;       where source_table = p_source_table&lt;br /&gt;         and source_table_id = p_source_table_id;  &lt;br /&gt;      for r in (select pt.*&lt;br /&gt;                  from app_page_type pt&lt;br /&gt;                 where pt.source_table = p_source_table)&lt;br /&gt;      loop&lt;br /&gt;         if p_name is null&lt;br /&gt;         then&lt;br /&gt;            l_orig_name := get_source_name(r.source_table, p_source_table_id, r.source_table_pk_col, r.name_column);&lt;br /&gt;         else&lt;br /&gt;            l_orig_name := p_name;&lt;br /&gt;         end if; &lt;br /&gt;         l_nice_name := get_nice_name(l_orig_name, p_source_table_id, '.html');&lt;br /&gt;         l_apex_url  := g_dad_path||'/f?p='||g_app_id||':'||r.page_id||':0::::'||r.pk_page_parameter||':'||p_source_table_id;&lt;br /&gt;         l_nice_url  := r.url_path||'/'||l_nice_name;&lt;br /&gt;         l_nice_title := substr(g_app_name||' - '||l_orig_name, 1, 255);&lt;br /&gt;         -- insert new entry&lt;br /&gt;         insert into app_page_url_mapping (&lt;br /&gt;            page_id&lt;br /&gt;         ,  source_table&lt;br /&gt;         ,  source_table_id&lt;br /&gt;         ,  apex_url&lt;br /&gt;         ,  nice_url&lt;br /&gt;         ,  nice_title)&lt;br /&gt;         values (&lt;br /&gt;            r.page_id&lt;br /&gt;         ,  r.source_table&lt;br /&gt;         ,  p_source_table_id&lt;br /&gt;         ,  l_apex_url&lt;br /&gt;         ,  l_nice_url&lt;br /&gt;         ,  l_nice_title);&lt;br /&gt;      end loop; &lt;br /&gt;      -- synchronize url-mapping file&lt;br /&gt;      synchronize_mapfile;      &lt;br /&gt;   end;&lt;br /&gt;   -- create manual mapping  &lt;br /&gt;   procedure create_manual_mapping (&lt;br /&gt;      p_page_id            in       number&lt;br /&gt;   ,  p_apex_url           in       varchar2&lt;br /&gt;   ,  p_nice_url           in       varchar2&lt;br /&gt;   ,  p_nice_title         in       varchar2 default null  &lt;br /&gt;   ) is&lt;br /&gt;   begin&lt;br /&gt;      -- educated guess on delete&lt;br /&gt;      delete from app_page_url_mapping&lt;br /&gt;       where page_id = p_page_id&lt;br /&gt;         and source_table is null;&lt;br /&gt;      -- insert new entry&lt;br /&gt;      insert into app_page_url_mapping (&lt;br /&gt;         page_id&lt;br /&gt;      ,  apex_url&lt;br /&gt;      ,  nice_url&lt;br /&gt;      ,  nice_title)&lt;br /&gt;      values (&lt;br /&gt;         p_page_id&lt;br /&gt;      ,  p_apex_url&lt;br /&gt;      ,  p_nice_url&lt;br /&gt;      ,  p_nice_title);&lt;br /&gt;      -- synchronize map file&lt;br /&gt;      synchronize_mapfile;&lt;br /&gt;   end;&lt;br /&gt;   procedure remove_mapping (&lt;br /&gt;      p_source_table       in       varchar2&lt;br /&gt;   ,  p_source_table_id    in       number&lt;br /&gt;   ) is&lt;br /&gt;   begin&lt;br /&gt;      delete from app_page_url_mapping&lt;br /&gt;       where source_table = p_source_table&lt;br /&gt;         and source_table_id = p_source_table_id;&lt;br /&gt;      synchronize_mapfile;&lt;br /&gt;   end;&lt;br /&gt;   procedure remove_manual_mapping (&lt;br /&gt;      p_page_id            in       number&lt;br /&gt;   ) is&lt;br /&gt;   begin&lt;br /&gt;      delete from app_page_url_mapping&lt;br /&gt;       where page_id = p_page_id&lt;br /&gt;         and source_table is null;&lt;br /&gt;      synchronize_mapfile;&lt;br /&gt;   end;&lt;br /&gt;   -- normal write to file procedure&lt;br /&gt;   procedure write_to_file (&lt;br /&gt;      p_file_name       in          varchar2&lt;br /&gt;   ,  p_directory       in          varchar2&lt;br /&gt;   ,  p_content         in          clob&lt;br /&gt;   ) is &lt;br /&gt;      l_file          utl_file.file_type;&lt;br /&gt;      l_buffer        raw(32000);&lt;br /&gt;      l_amount        binary_integer := 32000;&lt;br /&gt;      l_pos           integer := 1;&lt;br /&gt;      l_blob          blob;&lt;br /&gt;      l_blob_left     number;&lt;br /&gt;      l_blob_length   number;&lt;br /&gt;      l_file_content  blob;&lt;br /&gt;      l_src_offset    integer := 1;&lt;br /&gt;      l_dest_offset   integer := 1;&lt;br /&gt;      l_lang_ctx      integer := dbms_lob.default_lang_ctx;&lt;br /&gt;      l_warn          integer;&lt;br /&gt;   begin&lt;br /&gt;      -- converting from clob to blob&lt;br /&gt;      dbms_lob.createtemporary(l_file_content, false);&lt;br /&gt;      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);&lt;br /&gt;      l_blob_length := dbms_lob.getlength(l_file_content);&lt;br /&gt;      l_blob_left := l_blob_length;&lt;br /&gt;      -- open the destination file.&lt;br /&gt;      l_file := utl_file.fopen(p_directory,p_file_name,'WB', 32760);&lt;br /&gt;      -- if small enough for a single write&lt;br /&gt;      if l_blob_length &amp;lt; 32760 then&lt;br /&gt;         utl_file.put_raw(l_file,l_file_content);&lt;br /&gt;         utl_file.fflush(l_file);&lt;br /&gt;      else -- write in pieces&lt;br /&gt;         l_pos := 1;&lt;br /&gt;         while l_pos &amp;lt; l_blob_length&lt;br /&gt;         loop&lt;br /&gt;            dbms_lob.read(l_file_content,l_amount,l_pos,l_buffer);&lt;br /&gt;            utl_file.put_raw(l_file,l_buffer);&lt;br /&gt;            utl_file.fflush(l_file);&lt;br /&gt;            -- set the start position for the next cut&lt;br /&gt;            l_pos := l_pos + l_amount;&lt;br /&gt;            -- set the end position if less than 32000 bytes&lt;br /&gt;            l_blob_left := l_blob_left - l_amount;&lt;br /&gt;            if l_blob_left &amp;lt; 32000 then&lt;br /&gt;               l_amount := l_blob_left;&lt;br /&gt;            end if;&lt;br /&gt;         end loop;&lt;br /&gt;      end if;&lt;br /&gt;      utl_file.fclose(l_file);&lt;br /&gt;      dbms_lob.freetemporary(l_file_content);&lt;br /&gt;   exception&lt;br /&gt;     when others then&lt;br /&gt;       dbms_lob.freetemporary(l_file_content);&lt;br /&gt;       -- close the file if something goes wrong.&lt;br /&gt;       if utl_file.is_open(l_file) then&lt;br /&gt;         utl_file.fclose(l_file);&lt;br /&gt;       end if;&lt;br /&gt;       raise;&lt;br /&gt;   end;&lt;br /&gt;   -- synchronize map file&lt;br /&gt;   procedure synchronize_mapfile&lt;br /&gt;   is&lt;br /&gt;      l_map    clob;&lt;br /&gt;   begin&lt;br /&gt;      l_map :=          '#       Generated file, manual changes will suddenly disappear'||chr(10);&lt;br /&gt;      l_map := l_map || '#       Last generated: '||to_char(sysdate, 'dd.mm.yyyy hh24:mi:ss')||chr(10);&lt;br /&gt;      for r in (select *&lt;br /&gt;                  from app_page_url_mapping)&lt;br /&gt;      loop&lt;br /&gt;         l_map := l_map||r.nice_url||' '||r.apex_url||chr(10);&lt;br /&gt;      end loop;&lt;br /&gt;      write_to_file(g_tmp_map_file, g_map_dir, l_map);&lt;br /&gt;      -- if everything goes well, we rename and overwrite&lt;br /&gt;      utl_file.frename(g_map_dir, g_tmp_map_file, g_map_dir, g_map_file, true); &lt;br /&gt;   end; &lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Configuring Apache&lt;/b&gt;&lt;br /&gt;Apache will handle the actual rewrites based on the generated map file. Include the following in your httpd.conf (after backing it up)&lt;br /&gt;&lt;pre&gt;## Rewrite URLs that exists in map.txt&lt;br /&gt;  RewriteMap nicetoapex txt:c:/oracle/oraxe/apache/conf/map.txt&lt;br /&gt;  RewriteCond %{REQUEST_URI} /(([^/]+)(/.*)*)$&lt;br /&gt;  RewriteCond %2 =articles.html [OR]&lt;br /&gt;  RewriteCond %2 =article [OR]&lt;br /&gt;  RewriteCond %2 =albums.html [OR]&lt;br /&gt;  RewriteCond %2 =album&lt;br /&gt;  RewriteCond ${nicetoapex:%{REQUEST_URI}|NOT_FOUND} !NOT_FOUND&lt;br /&gt;  RewriteRule ^(/.*) ${nicetoapex:$1}%{QUERY_STRING} [P]&lt;br /&gt;&lt;/pre&gt;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'.&lt;br /&gt;&lt;br /&gt;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...&lt;br /&gt;&lt;br /&gt;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...&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre&gt;## Handle calls to wwv_flow.accept&lt;br /&gt;  RewriteCond %{REQUEST_URI} ^/wwv_flow.accept&lt;br /&gt;  RewriteRule ^/(.*) /pls/apex/$1 [P]&lt;br /&gt;&lt;/pre&gt;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).&lt;br /&gt;&lt;br /&gt;For good measure you can also include a rewrite to handle the default "start page" of your site with the following:&lt;br /&gt;&lt;pre&gt;## Redirect server home page to Apex application&lt;br /&gt;  RewriteRule ^/$ http://localhost:8080/pls/apex/f?p=1000:1:0 [P,L]&lt;br /&gt;  RewriteRule ^/index.html$ http://localhost:8080/pls/apex/f?p=1000:1:0 [P,L]&lt;br /&gt;&lt;/pre&gt;Restart Apache for every change of httpd.conf for the changes to take effect.  &lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Mapping URLs From Table Content&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;pre class="brush: sql"&gt;insert into app_page_type (&lt;br /&gt;     page_id&lt;br /&gt;   , source_table&lt;br /&gt;   , source_table_pk_col&lt;br /&gt;   , url_path&lt;br /&gt;   , pk_page_parameter&lt;br /&gt;   , name_column)&lt;br /&gt;  values   (&lt;br /&gt;     3                -- APEX page_id&lt;br /&gt;   , 'ARTICLES'       -- Source Table&lt;br /&gt;   , 'ARTICLES_ID'    -- Source table primary key&lt;br /&gt;   , '/article'       -- Desired URL-prefix&lt;br /&gt;   , 'P3_ARTICLE_ID'  -- APEX page item id that holds the primary key&lt;br /&gt;   , 'TITLE')         -- Source table column that content title&lt;br /&gt;/&lt;br /&gt;commit&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;When I insert/update a row in the ARTICLES-table with ARTICLE_ID=0, I can generate a nice URL with the following statements:&lt;br /&gt;&lt;pre class="brush: sql"&gt;begin&lt;br /&gt;   app_page_p.create_mapping( 'ARTICLES' -- source table name&lt;br /&gt;                            , 0);        -- source table id&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;commit&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;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:&lt;br /&gt;&lt;pre&gt;#       Generated file, manual changes will suddenly disappear&lt;br /&gt;#       Last generated: 03.02.2010 22:19:22&lt;br /&gt;/article/apex_40__it_is_finally_here0.html /pls/apex/f?p=1000:3:0::::P3_ARTICLE_ID:0&lt;br /&gt;&lt;/pre&gt;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://&amp;lt;yoursite&amp;gt;/article/apex_40__it_is_finally_here0.html&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Manually Mappig URLs&lt;/b&gt;&lt;br /&gt;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):&lt;br /&gt;&lt;pre class="brush: sql"&gt;begin&lt;br /&gt;   app_page_p.create_manual_mapping(2                          -- APEX page_id&lt;br /&gt;                                   , '/pls/apex/f?p=1000:2:0'  -- APEX URL&lt;br /&gt;                                   , '/articles.html'          -- Nice URL&lt;br /&gt;                                   , 'Articles');              -- Nice name&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;commit&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;If you ran this after the previous example, your map file should look like this:&lt;br /&gt;&lt;pre class="brush: sql"&gt;#       Generated file, manual changes will suddenly disappear&lt;br /&gt;#       Last generated: 03.02.2010 22:21:01&lt;br /&gt;/articles.html /pls/apex/f?p=1000:2:0&lt;br /&gt;/article/apex_40__it_is_finally_here0.html /pls/apex/f?p=1000:3:0::::P3_ARTICLE_ID:0&lt;br /&gt;&lt;/pre&gt;And likewise you should be able to see http://&amp;lt;yourhostname&amp;gt;/articles.html in a browser.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Pitfalls, Improvements and Considerations&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;The second observation is the lack of support for more than one primary key column. The API must be modified to support this.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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. &lt;br /&gt;&lt;br /&gt;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 &lt;a href="http://www.orafaq.com/scripts/plsql/ftpclient.txt"&gt;http://www.orafaq.com/scripts/plsql/ftpclient.txt&lt;/a&gt; for a plain PL/SQL FTP-solution, or be creative with an external procedure.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A Small Bonus&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;In Conclusion&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;Oh, and did I mention you use this code at your own risk? Well, you do!&lt;br /&gt;&lt;br /&gt;Enjoy :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-1350842315998246835?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/1350842315998246835/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/02/nice-url-in-public-facing-oracle-apex.html#comment-form' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1350842315998246835'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1350842315998246835'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/02/nice-url-in-public-facing-oracle-apex.html' title='Nice URL in a Public Facing Oracle APEX Application'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-1735268564131832348</id><published>2010-01-07T15:52:00.000+01:00</published><updated>2010-01-07T15:52:33.707+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='spatial'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><category scheme='http://www.blogger.com/atom/ns#' term='google maps'/><title type='text'>Oracle Spatial, APEX and Google Maps</title><content type='html'>In this post I will demonstrate how to use Oracle Spatial to store shapes, and utilize Oracle APEX and Google Maps API to visualize and manipulate the shapes.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_8GtRvOur8pQ/S0X0uNeG55I/AAAAAAAAAB8/lWfs8Nx6AD4/s1600-h/ApexMap.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_8GtRvOur8pQ/S0X0uNeG55I/AAAAAAAAAB8/lWfs8Nx6AD4/s320/ApexMap.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;The code is tested for Oracle 11g, but should work equally well for Oracle XE (no advanced Spatial operations), although I have noticed that Oracle XE is a bit more picky when it comes to valid shapes.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Demo Application&lt;/b&gt;&lt;br /&gt;If there is too much text for you to read through, check out &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:11"&gt;my demo Application&lt;/a&gt; on apex.oracle.com to see it in action :-)&lt;br /&gt;&lt;br /&gt;The demo application is tested on Firefox ONLY, so expect issues when using other browsers!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Database objects&lt;/b&gt;&lt;br /&gt;The code below will create a simple table with one column containing a sdo_geometry column called shapes, and a supporting package to mediate between APEX/Google Maps and the database table:&lt;br /&gt;&lt;pre class="brush: sql"&gt;create table sample_polygon&lt;br /&gt;(&lt;br /&gt;   sample_polygon_id     number not null&lt;br /&gt; , name                  varchar2 (80) not null&lt;br /&gt; , description           varchar2 (4000)&lt;br /&gt; , zoom_level            number&lt;br /&gt; , shape                 mdsys.sdo_geometry&lt;br /&gt;)&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;alter table sample_polygon add (&lt;br /&gt;  constraint sample_polygon_pk&lt;br /&gt; primary key&lt;br /&gt; (sample_polygon_id))&lt;br /&gt;/ &lt;br /&gt;&lt;br /&gt;create sequence sample_polygon_pk_seq&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create or replace trigger trg_sample_polygon_bi&lt;br /&gt;   before insert&lt;br /&gt;   on sample_polygon&lt;br /&gt;   referencing new as new old as old&lt;br /&gt;   for each row&lt;br /&gt;declare&lt;br /&gt;   l_new_id       number;&lt;br /&gt;begin&lt;br /&gt;   if :new.sample_polygon_id is null&lt;br /&gt;   then&lt;br /&gt;      select   sample_polygon_pk_seq.nextval&lt;br /&gt;        into   l_new_id&lt;br /&gt;        from   dual;&lt;br /&gt;      :new.sample_polygon_id   := l_new_id;&lt;br /&gt;   end if;&lt;br /&gt;exception&lt;br /&gt;   when others&lt;br /&gt;   then&lt;br /&gt;      raise;&lt;br /&gt;end trg_sample_polygon_bi;&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create or replace package sample_polygon_p&lt;br /&gt;as&lt;br /&gt;   -- return string with coordinates&lt;br /&gt;   function get_ordinates (&lt;br /&gt;      p_shape                 in    sample_polygon.shape%type&lt;br /&gt;   ) return varchar2;&lt;br /&gt;   -- update table with shape&lt;br /&gt;   procedure create_geometry (&lt;br /&gt;       p_sample_polygon_id    in    sample_polygon.sample_polygon_id%type&lt;br /&gt;   ,   p_name                 in    sample_polygon.name%type&lt;br /&gt;   ,   p_description          in    sample_polygon.description%type&lt;br /&gt;   ,   p_zoom_level           in    sample_polygon.zoom_level%type&lt;br /&gt;   ,   p_shape                in    varchar2);&lt;br /&gt;end;   &lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create or replace package body sample_polygon_p&lt;br /&gt;as&lt;br /&gt;   function get_ordinates (&lt;br /&gt;      p_shape                 in    sample_polygon.shape%type&lt;br /&gt;   ) return varchar2 is       &lt;br /&gt;      l_poly   mdsys.sdo_ordinate_array;&lt;br /&gt;      l_coords varchar2(4000);&lt;br /&gt;      l_point  varchar2(20);&lt;br /&gt;   begin&lt;br /&gt;      l_poly := p_shape.sdo_ordinates;&lt;br /&gt;      if l_poly.exists(1)&lt;br /&gt;      then&lt;br /&gt;         for i in l_poly.first..l_poly.last&lt;br /&gt;         loop&lt;br /&gt;            l_coords := l_coords ||','||replace(l_poly(i), ',', '.'); &lt;br /&gt;         end loop;&lt;br /&gt;         l_coords := substr(l_coords, 2, length(l_coords));&lt;br /&gt;      end if;&lt;br /&gt;      return l_coords;&lt;br /&gt;   end;&lt;br /&gt;   procedure create_geometry (&lt;br /&gt;       p_sample_polygon_id    in    sample_polygon.sample_polygon_id%type&lt;br /&gt;   ,   p_name                 in    sample_polygon.name%type&lt;br /&gt;   ,   p_description          in    sample_polygon.description%type&lt;br /&gt;   ,   p_zoom_level           in    sample_polygon.zoom_level%type&lt;br /&gt;   ,   p_shape                in    varchar2&lt;br /&gt;   ) is&lt;br /&gt;      l_sql       varchar2(32000);&lt;br /&gt;      l_sample_polygon_id     sample_polygon.sample_polygon_id%type := p_sample_polygon_id;&lt;br /&gt;   begin&lt;br /&gt;      if l_sample_polygon_id is null&lt;br /&gt;      then&lt;br /&gt;         insert into sample_polygon&lt;br /&gt;         (  name&lt;br /&gt;         ,  description&lt;br /&gt;         ,  zoom_level&lt;br /&gt;         ) values&lt;br /&gt;         (  p_name&lt;br /&gt;         ,  p_description&lt;br /&gt;         ,  p_zoom_level) returning sample_polygon_id into l_sample_polygon_id;&lt;br /&gt;      else&lt;br /&gt;         update sample_polygon&lt;br /&gt;         set    name = p_name&lt;br /&gt;         ,      description = p_description&lt;br /&gt;         ,      zoom_level = p_zoom_level&lt;br /&gt;         where  sample_polygon_id = l_sample_polygon_id;&lt;br /&gt;      end if;&lt;br /&gt;      -- 2003: Two dimentional polygon&lt;br /&gt;      -- 4326: SRID&lt;br /&gt;      -- 1,1003,1: one polygon (exterior polygon ring)&lt;br /&gt;      l_sql := 'update sample_polygon'||chr(10)||&lt;br /&gt;               'set    shape = sdo_geometry (2003'||chr(10)||&lt;br /&gt;               '                            , ''4326'''||chr(10)||&lt;br /&gt;               '                            , null'||chr(10)||&lt;br /&gt;               '                            , sdo_elem_info_array(1,1003,1)'||chr(10)||&lt;br /&gt;               '                            , sdo_ordinate_array( '|| p_shape ||')'||chr(10)||&lt;br /&gt;               '                            )'||chr(10)||&lt;br /&gt;               'where  sample_polygon_id = :1'; &lt;br /&gt;      execute immediate l_sql using in l_sample_polygon_id;&lt;br /&gt;   end;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;You must have Oracle Spatial installed in your database (is part of default installation, including XE), and schema must have appropriate rights to invoke Spatial functions. If the above script delivers no error-messages you should be OK.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Why Spatial?&lt;/b&gt;&lt;br /&gt;Well, why not? You already paid for it in you license, and quite frankly I can see no easier way to store your shapes. There are also a number of valuable functions that comes with Spatial, such as:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Location inside shape&lt;/li&gt;&lt;li&gt;Distance to nearest part of shape from location&lt;/li&gt;&lt;li&gt;Conversion between coordinate types&lt;/li&gt;&lt;li&gt;The ability to view shapes in other client tools like ESRI&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I have very limited experience with Oracle Spatial, and the syntax for creating shapes as an SDO_GEOMETRY-data type was (is) a bit daunting.&lt;br /&gt;&lt;br /&gt;Lessons learned are:&lt;br /&gt;Take a close look at which coordinate system you are storing the points in. I have used the most common 4326, but if I understood correctly Google actually uses 3857 or 900913. This requires some further investigation.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Google Maps API&lt;/b&gt;&lt;br /&gt;There are already some examples on how to integrate Oracle APEX and Google Maps, and likewise, there are many examples on how to interact with Google Maps using the Google Maps API. I will list some of the resources on the subject:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.oracle.com/technology/products/database/application_express/pdf/Integrating_Application_Express_with_Google_Maps.pdf"&gt;Official Oracle APEX/Google Maps integration&lt;/a&gt; written by Jason Straub&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.inside-oracle-apex.com/integration-of-google-maps-oracle-maps-into-apex/"&gt;Collection of map integrations with Oracle APEX&lt;/a&gt; by Patrick Wolf&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.birdtheme.org/useful/googletool.html"&gt;Google Maps API Tool&lt;/a&gt; by Kjell Scharning &lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.blogger.com/goog_1262874776339"&gt;Official Google Maps API Reference&lt;br /&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;a href="http://code.google.com/apis/maps/"&gt;&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;The first thing to do when trying out this code, is to &lt;a href="http://code.google.com/apis/maps/signup.html"&gt;sign up and get a Google Maps API Key&lt;/a&gt;!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A tiny bit of Javascript&lt;/b&gt;&lt;br /&gt;The meaning of "tiny" being somewhat stretched here. Even if you are not familiar with Javascript, it is surprisingly easy to pick up. This script might not be the best place to start, but I encourage you to look at the source code from my demo application in detail to see what it does.&lt;br /&gt;&lt;br /&gt;I will not do a full listing here, but highlight some of the more important functions and give a brief top-down view of what goes on.&lt;br /&gt;&lt;br /&gt;On page rendering:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Javascript function buildMap is called&lt;/li&gt;&lt;li&gt;Invokes Google Maps API&lt;/li&gt;&lt;li&gt;Draws a map&lt;/li&gt;&lt;li&gt;Adds onClick-listener to the map to enable the user to create shapes&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;On click in map:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Draws a new marker on the map&lt;/li&gt;&lt;li&gt;Adds a new point to points array (this is what gets stored in the database)&lt;/li&gt;&lt;li&gt;Redraws the polygon on the map&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;On click on a marker in the map&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Removes the marker&lt;/li&gt;&lt;li&gt;Removes the point from the points array&lt;/li&gt;&lt;li&gt;Redraws the map&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Edit mode:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Redraws map to ensure completeness of polygon&lt;/li&gt;&lt;li&gt;Removes markers&lt;/li&gt;&lt;li&gt;Makes polygon editable&lt;/li&gt;&lt;li&gt;Removes onClick-listener on Map (Adding new points/markers in the map is no longer possible)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Exit edit mode:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Repopulates points array from polygon&lt;/li&gt;&lt;li&gt;Makes polygon uneditable (is that even a word?)&lt;/li&gt;&lt;li&gt;Redraws polygon on map with markers&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Create new shape:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Removes all overlays from map (markers and polygon)&lt;/li&gt;&lt;li&gt;Resets all arrays and variables&lt;/li&gt;&lt;li&gt;Reattaches onClick-listener to map&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;For more details, you have to dig into the source code (view source in the demo application).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Merging Oracle Spatial and Google Maps&lt;/b&gt;&lt;br /&gt;This not advanced mathematics, I have two functions in the database:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;One which extracts ordinates (as they are called, don't ask me why) from the stored SDO_GEOMETRY-object, and return the ordinates as a comma-separated string&lt;/li&gt;&lt;li&gt;One which accepts a shape as a comma-separated string and creates a SDO_GEOMETRY-object&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;I also have two javascript functions:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;One which accepts a string of comma separated coordinates, splits it into an array og "Google points"&lt;/li&gt;&lt;li&gt;One which accepts an array of "Google points", and returns a comma separated string (well, not exactly, it "returns" by setting an APEX item...)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Bringing it All Together&lt;/b&gt;&lt;br /&gt;Oracle Apex is the glue between Google Maps and Oracle Spatial, but the actual coding in APEX to achieve this is quite little.&lt;br /&gt;&lt;br /&gt;In my demo application I have:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Region containing a DIV and all necessary Javascrips to show and interact with Google Maps&lt;/li&gt;&lt;li&gt;Region based on a stored procedure to store shapes&lt;/li&gt;&lt;li&gt;Region containing a report of stored shapes&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;That's it, enjoy :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-1735268564131832348?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/1735268564131832348/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/01/oracle-spatial-apex-and-google-maps.html#comment-form' title='8 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1735268564131832348'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1735268564131832348'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2010/01/oracle-spatial-apex-and-google-maps.html' title='Oracle Spatial, APEX and Google Maps'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_8GtRvOur8pQ/S0X0uNeG55I/AAAAAAAAAB8/lWfs8Nx6AD4/s72-c/ApexMap.jpg' height='72' width='72'/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-1754326086570902130</id><published>2009-11-13T13:57:00.001+01:00</published><updated>2009-11-13T13:59:04.101+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Interactive Reports Not Working Properly on New Install</title><content type='html'>This is just a quick note to self, but may be useful to others experiencing similar problems. I would not recommend the Fix Two solution for production environments before conducting further research though...&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Preface&lt;/b&gt;&lt;br /&gt;Recently I installed Oracle Apex 3.2 in an existing database. Plain vanilla install on an Oracle 9.2.0.5 database with an Oracle iAS 9.2.0.3 in front. Installation went smooth, demo application works. &lt;i&gt;Shift.&lt;/i&gt; Different site, same plain install, but this time on a 10.2.0.4 database, and 10.1.2 application server.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Demo Effect&lt;/b&gt;&lt;br /&gt;What is the first thing you show off when demonstrating Apex? The interactive reports, that is a no-brainer :-) But at both of these installations, something goes wrong. When clicking a report column, I just get the spinning wheel, no other response. What is amiss?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Dissecting the Problem&lt;/b&gt;&lt;br /&gt;At the first location, I had no time and no Firebug. At the second location I had both, and two failed installations creating some sort of consistency.&lt;br /&gt;&lt;br /&gt;In the Firebug console, I can see a javascript error pops up when a column is clicked. Sometimes it throws string not terminated error, sometimes some other cryptic message, but always the same javascript function. Examining the response in Firebug shows something odd; the response is cut short. Depending on the distinct values of the column I clicked, the response might be cut inside a string (string not terminated error), or in-between. When clicking numeric columns, it works. Hm... Special characters? NLS?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Fix One&lt;/b&gt;&lt;br /&gt;Patching the Apex installation to 3.2.1 worked for the installation on the 10g system. IR's started working when the patch was applied.&lt;br /&gt;&lt;br /&gt;One down, one to go...&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Fix Two (The Dirty Fix)&lt;/b&gt;&lt;br /&gt;Examining dads.conf for the 9i installation, I see previously configured dads has a different setting of PlsqlNLSLanguage. Both installs go against databases with NLS_LANG=AMERICAN_AMERICA.WE8MSWIN1252 (don't ask me why, seems like a popular choice for older systems in Norway).&lt;br /&gt;&lt;br /&gt;Changing from PlsqlNLSLanguage from AMERICAN_AMERICA.ALUTF8 to AMERICAN_AMERICA.WE8MSWIN1252 did the trick, IR's are now working as expected. I have not noticed anything else breaking (yet), but I am not at all comfortable with the workaround.&lt;br /&gt;&lt;br /&gt;The documentation clearly states:&lt;br /&gt;&lt;i&gt;"The character set portion of the PlsqlNLSLanguage value must be set to AL32UTF8, regardless of whether or not the database character set is AL32UTF8."&lt;/i&gt;&lt;br /&gt;or for older versions of iAS:&lt;br /&gt;&lt;i&gt;"The character set portion of the nls_lang value must always be set to AL32UTF8, regardless of whether or not the database character set is AL32UTF8."&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Luckily, the 9i installation will &lt;b&gt;not&lt;/b&gt; go to production in it's current state. Phew...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-1754326086570902130?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/1754326086570902130/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/11/interactive-reports-not-working.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1754326086570902130'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/1754326086570902130'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/11/interactive-reports-not-working.html' title='Interactive Reports Not Working Properly on New Install'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-2427428703657610303</id><published>2009-11-03T13:51:00.001+01:00</published><updated>2009-11-03T13:53:41.596+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plsql'/><category scheme='http://www.blogger.com/atom/ns#' term='utl_smtp'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>PL/SQL and Gmail (or UTL_SMTP with SSL)</title><content type='html'>In this post I will describe how to send mail from an Oracle database using UTL_SMTP over SSL using &lt;a href="http://www.stunnel.org/"&gt;Stunnel&lt;/a&gt;. I conducted the test on Windows XP with Oracle database 11gR1, but it should work for nix-operating systems and database versions 9.2 and up. To be quite frank, this is nothing new, but it might be of use anyway.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Preface&lt;/b&gt;&lt;br /&gt;I wanted to send emails from my database when some data changes. It was not a corporate solution with access to an internal smtp-host. A simple, accessible, ISP agnostic smtp-server would do. In my case, Gmail fitted the bill, only problem was that Gmail required SSL, which UTL_SMTP does not support. I am up for a challenge (meaning: I am good at complicat&lt;b&gt;ing&lt;/b&gt; (ing, not ed :-)) things), so here goes...&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Stunnel&lt;/b&gt;&lt;br /&gt;Since UTL_SMTP does not support SSL, I will use a third party tool to "wrap" my connection. There are probably any number of tools which can do this, but Stunnel is quite often referred to, and very easy to install and configure. For nix systems, I suggest checking the Examples-page on &lt;a href="http://stunnel.org/"&gt;stunnel.org&lt;/a&gt;, this is a Windows-specific explanation. This part of the post is based on &lt;a href="http://ez.no/developer/forum/install_configuration/sending_mail_via_gmail_or_google_apps_smtp"&gt;a thread on ez.no&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Installing and configuring Stunnel&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Go to stunnel.org and download the latest Windows binaries&lt;/li&gt;&lt;li&gt;Install Stunnel (take note of the installation path), in my example it is c:\stunnel&lt;/li&gt;&lt;li&gt;Edit the file stunnel.conf located in installation folder to (just backup the original, and replace all the original text with the text below):&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;; Use it for client mode&lt;br /&gt;client = yes&lt;br /&gt;&lt;br /&gt;[ssmtp]&lt;br /&gt;accept  = 1925&lt;br /&gt;connect = smtp.gmail.com:465&lt;br /&gt;&lt;/pre&gt;Here I use port 1925 on my localhost (unused as far as I know) to connect to smtp.gmail.com.&lt;br /&gt;&lt;br /&gt;Start Stunnel.exe, and test the configuration:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Start cmd&lt;/li&gt;&lt;li&gt;Write: telnet localhost 1925&lt;/li&gt;&lt;li&gt;You should then see something like "220 mx.google.com ESMTP 5sm18031572eyh.34"&lt;/li&gt;&lt;li&gt;Write: quit&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Troubleshooting: If you cannot reach smtp.gmail.com, there can be any number of things gone wrong.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Try a normal ping to smtp.gmail.com&lt;/li&gt;&lt;li&gt;Check to see if stunnel.exe is excepted properly in all firewalls (Windows native and other software firewalls)&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Once stunnel is working, and if you are familiar with UTL_SMTP, don't bother reading on. This is the same as UTL_SMTP with any other smtp-host requiring authentication.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Setting up ACL (11g only)&lt;/b&gt;&lt;br /&gt;This is more or less monkeyed from &lt;a href="http://www.oracle.com/technology/pub/articles/oracle-database-11g-top-features/11g-security.html"&gt;Arup Nandas 11g series&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;To create an access control list for your application user, and enabling it to connect to localhost on port 1925, do the following:&lt;br /&gt;&lt;pre class="brush: sql"&gt;-- create acl&lt;br /&gt;begin&lt;br /&gt;        dbms_network_acl_admin.create_acl (&lt;br /&gt;                acl             =&amp;gt; 'gmail.xml',&lt;br /&gt;                description     =&amp;gt; 'Normal Access',&lt;br /&gt;                principal       =&amp;gt; 'CONNECT',&lt;br /&gt;                is_grant        =&amp;gt; TRUE,&lt;br /&gt;                privilege       =&amp;gt; 'connect',&lt;br /&gt;                start_date      =&amp;gt; null,&lt;br /&gt;                end_date        =&amp;gt; null&lt;br /&gt;        );&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;-- add priviliege to acl&lt;br /&gt;begin&lt;br /&gt;  dbms_network_acl_admin.add_privilege ( &lt;br /&gt;  acl       =&amp;gt; 'gmail.xml',&lt;br /&gt;  principal    =&amp;gt; '&amp;lt;YOUR SCHEMA USER&amp;gt;',&lt;br /&gt;  is_grant    =&amp;gt; TRUE, &lt;br /&gt;  privilege    =&amp;gt; 'connect', &lt;br /&gt;  start_date    =&amp;gt; null, &lt;br /&gt;  end_date    =&amp;gt; null); &lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;-- assign host, port to acl&lt;br /&gt;begin&lt;br /&gt;  dbms_network_acl_admin.assign_acl (&lt;br /&gt;  acl =&amp;gt; 'gmail.xml',&lt;br /&gt;  host =&amp;gt; 'localhost',&lt;br /&gt;  lower_port =&amp;gt; 1925,&lt;br /&gt;  upper_port =&amp;gt; 1925);&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;And you are ready to use UTL_SMTP against smtp.gmail.com.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Wrapping UTL_SMTP&lt;/b&gt;&lt;br /&gt;I have created a small test-package based on &lt;a href="http://www.oracle.com/technology/sample_code/tech/pl_sql/htdocs/maildemo_sql.txt"&gt;the old UTL_MAIL example&lt;/a&gt; from Oracle. Your schema user must have execute privileges on UTL_SMTP and UTL_ENCODE for this to work:&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace package apex_mail_p&lt;br /&gt;is&lt;br /&gt;   g_smtp_host      varchar2 (256)     := 'localhost';&lt;br /&gt;   g_smtp_port      pls_integer        := 1925;&lt;br /&gt;   g_smtp_domain    varchar2 (256)     := 'gmail.com';&lt;br /&gt;   g_mailer_id constant varchar2 (256) := 'Mailer by Oracle UTL_SMTP';&lt;br /&gt;   -- send mail using UTL_SMTP&lt;br /&gt;   procedure mail (&lt;br /&gt;      p_sender in varchar2&lt;br /&gt;    , p_recipient in varchar2&lt;br /&gt;    , p_subject in varchar2&lt;br /&gt;    , p_message in varchar2&lt;br /&gt;   );&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;create or replace package body apex_mail_p&lt;br /&gt;is&lt;br /&gt;   -- Write a MIME header&lt;br /&gt;   procedure write_mime_header (&lt;br /&gt;      p_conn in out nocopy utl_smtp.connection&lt;br /&gt;    , p_name in varchar2&lt;br /&gt;    , p_value in varchar2&lt;br /&gt;   )&lt;br /&gt;   is&lt;br /&gt;   begin&lt;br /&gt;      utl_smtp.write_data ( p_conn&lt;br /&gt;                          , p_name || ': ' || p_value || utl_tcp.crlf&lt;br /&gt;      );&lt;br /&gt;   end;&lt;br /&gt;   procedure mail (&lt;br /&gt;      p_sender in varchar2&lt;br /&gt;    , p_recipient in varchar2&lt;br /&gt;    , p_subject in varchar2&lt;br /&gt;    , p_message in varchar2&lt;br /&gt;   )&lt;br /&gt;   is&lt;br /&gt;      l_conn           utl_smtp.connection;&lt;br /&gt;      nls_charset    varchar2(255);&lt;br /&gt;   begin&lt;br /&gt;      -- get characterset&lt;br /&gt;      select value&lt;br /&gt;      into   nls_charset&lt;br /&gt;      from   nls_database_parameters&lt;br /&gt;      where  parameter = 'NLS_CHARACTERSET';&lt;br /&gt;      -- establish connection and autheticate&lt;br /&gt;      l_conn   := utl_smtp.open_connection (g_smtp_host, g_smtp_port);&lt;br /&gt;      utl_smtp.ehlo(l_conn, g_smtp_domain);  &lt;br /&gt;      utl_smtp.command(l_conn, 'auth login');&lt;br /&gt;      utl_smtp.command(l_conn,utl_encode.text_encode('&amp;lt;your gmail account including @gmail.com&amp;gt;', nls_charset, 1));&lt;br /&gt;      utl_smtp.command(l_conn, utl_encode.text_encode('&amp;lt;your gmail account password&amp;gt;', nls_charset, 1));&lt;br /&gt;      -- set from/recipient&lt;br /&gt;      utl_smtp.command(l_conn, 'MAIL FROM: &amp;lt;'||p_sender||'&amp;gt;');&lt;br /&gt;      utl_smtp.command(l_conn, 'RCPT TO: &amp;lt;'||p_recipient||'&amp;gt;');&lt;br /&gt;      -- write mime headers&lt;br /&gt;      utl_smtp.open_data (l_conn);&lt;br /&gt;      write_mime_header (l_conn, 'From', p_sender);&lt;br /&gt;      write_mime_header (l_conn, 'To', p_recipient);&lt;br /&gt;      write_mime_header (l_conn, 'Subject', p_subject);&lt;br /&gt;      write_mime_header (l_conn, 'Content-Type', 'text/plain');&lt;br /&gt;      write_mime_header (l_conn, 'X-Mailer', g_mailer_id);&lt;br /&gt;      utl_smtp.write_data (l_conn, utl_tcp.crlf);&lt;br /&gt;      -- write message body&lt;br /&gt;      utl_smtp.write_data (l_conn, p_message);&lt;br /&gt;      utl_smtp.close_data (l_conn);&lt;br /&gt;      -- end connection&lt;br /&gt;      utl_smtp.quit (l_conn);&lt;br /&gt;   exception&lt;br /&gt;      when others&lt;br /&gt;      then&lt;br /&gt;         begin&lt;br /&gt;           utl_smtp.quit(l_conn);&lt;br /&gt;         exception&lt;br /&gt;           when others then&lt;br /&gt;             null;&lt;br /&gt;         end;&lt;br /&gt;         raise_application_error(-20000,'Failed to send mail due to the following error: ' || sqlerrm);   &lt;br /&gt;   end;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;This is &lt;b&gt;NOT&lt;/b&gt; production-ready code: First of all, you do not want your credentials in the open, at least obfuscate the package body.&lt;br /&gt;&lt;br /&gt;Some notes on the package:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Parameters sender and recipient must contain e-mail addresses only, use the get_address function in the original Oracle example for more sophisticated use (you can also look at how to add attachments if you have the need).&lt;/li&gt;&lt;li&gt;I had some trouble encoding my account name and password. My initial thought was to use utl_raw.cast_to_raw and utl_encode.base64_encode, but this did not work, so I ended up using utl_encode.encode_text&lt;/li&gt;&lt;li&gt;Mime-type is set to "text/plain", set it to "text-html; charset=&amp;lt;something appropriate&amp;gt;" to enhance visual layout&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;Sending an E-mail&lt;/b&gt;&lt;br /&gt;To test it all, try:&lt;br /&gt;&lt;pre class="brush: sql"&gt;begin&lt;br /&gt;   apex_mail_p.mail('&amp;lt;your gmail address&amp;gt;', '&amp;lt;recipient address&amp;gt;', '&amp;lt;Subject&amp;gt;', '&amp;lt;message body&amp;gt;');&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;And you are done!&lt;br /&gt;&lt;br /&gt;Well, if you don't get any error messages, that is. If you encounter any exceptions, first of all check your Gmail credentials. Next, check where (in the PL/SQL-code) it fails, and use your favorite search engine to do a combined search on smtp command sent and smtp-error received. Chances are others have worked through the same problems (even if they are not Oracle-related). Last resort is to use telnet and manually type the commands, a bit cumbersome but gives full control.&lt;br /&gt;&lt;br /&gt;Happy coding :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-2427428703657610303?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/2427428703657610303/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/11/plsql-and-gmail-or-utlsmtp-with-ssl.html#comment-form' title='22 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/2427428703657610303'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/2427428703657610303'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/11/plsql-and-gmail-or-utlsmtp-with-ssl.html' title='PL/SQL and Gmail (or UTL_SMTP with SSL)'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>22</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-4155627233907295261</id><published>2009-10-15T08:12:00.000+02:00</published><updated>2009-10-15T08:12:34.174+02:00</updated><title type='text'>Oracle Apex, Dead on Arrival?</title><content type='html'>&lt;i&gt;Warning: Rant follows! The views and opinions expressed here are explicitly my own. Subjective, prejudiced and factless content ahead, best consumed with a pinch of salt (and some Tequila if you are in the mood).&lt;/i&gt;&lt;br /&gt;&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;br /&gt;Here in Norway, the Oracle community is just starting to open its eyes to Apex. I have been lobbying Apex internally in my company for quite some time now. Given the latest releases, this should have made my job easier (and the coming version 4.0 even more so). Apex has matured in many ways, but most noticeably in terms of functionality and community adoption. So what is driving me up the wall?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The long, long time ago&lt;/b&gt;&lt;br /&gt;The major part of the customers I have worked with, started their relationship with Oracle by buying an Oracle database. To a greater or lesser degree, most have expanded their use of Oracle products. Having worked most of my time with rich back office applications in various domains and a number of development tools, I have witnessed the investments made by customers into their applications (both in terms of time and money).&lt;br /&gt;&lt;br /&gt;Changing client technologies combined with a consistently performant database in the cellar, has made some pretty impressive database applications. Whole business processes have been modeled, implemented and evolved (and continue to evolve) in the database for quite some time now, and the companies have gained in-house database competence as a natural result.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Enter APEX&lt;/b&gt;&lt;br /&gt;Along comes Apex, a PL/SQL based RAD tool , which actually seems to live up to the "RAD"-label and has the ability to flip code around to satisfy the most exotic&amp;nbsp; business needs. At the same time also embracing both KISS and the fat database paradigm.&lt;br /&gt;&lt;br /&gt;In particular, companies heavily invested in database applications, often in combination with Oracle Forms clients, should see the benefits of Apex. Reuse of code, reuse of competence, and getting their pennies worth of already invested time and money. So what is stopping them?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Case and the Choice&lt;/b&gt;&lt;br /&gt;Based on one need or another, the business wants to expand their existing applications; what client tools are available in the Oracle sphere? There are several options, but most prominent (for the time being, anyway) is ADF and Apex. What to choose depends on a number of things, but I believe the most important in this phase is not to apply your silver bullet “just because”. &lt;br /&gt;&lt;br /&gt;This is a topic in itself, so I will short circuit the discussion, and narrow it down to this:&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;A company wants to implement some back office system based on an existing database application. The application is of "medium" complexity, consisting of registration forms, case evaluation support, reporting and some integrating with other systems. About 10 concurrent users. The IT department has most of its skills in PL/SQL and SQL.&lt;br /&gt;&lt;/i&gt;&lt;br /&gt;&lt;/blockquote&gt;You cannot build a system based on that information, I agree. You cannot make a valid recommendation on a single tool based on that information, I agree. But this is the same amount of information available to Oracle sales representatives as well, and (in Norway at least) the answer is ADF nine out of ten times (actually, I have never heard Apex recommended, but I am giving the benefit of the doubt here).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;What do they know that I do not?&lt;/b&gt;&lt;br /&gt;How can they consistently answer ADF? Is Apex going to die soon? If I were a customer, I would shy away from Apex hearing this from Oracle itself. Heck, &lt;i&gt;I&lt;/i&gt; might even shy away from Apex, I have to make a living too.&lt;br /&gt;&lt;br /&gt;In many cases, I am convinced Apex is not just a viable choice, but also &lt;i&gt;the best choice&lt;/i&gt; for the customer. But how can I justify that to the customer that Apex is the way to go, when the software vendor actually says something else? If I was a customer, I would be reluctant to use Apex, to say the least.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The road ahead&lt;/b&gt;&lt;br /&gt;I do not know where to take it from here, hence the rant. That is the long and the short of it.&lt;br /&gt;&lt;br /&gt;Any comments or suggestions would be greatly appreciated.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-4155627233907295261?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/4155627233907295261/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/10/oracle-apex-dead-on-arrival.html#comment-form' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/4155627233907295261'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/4155627233907295261'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/10/oracle-apex-dead-on-arrival.html' title='Oracle Apex, Dead on Arrival?'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-8261629405296488199</id><published>2009-10-12T14:27:00.000+02:00</published><updated>2009-10-12T14:27:59.084+02:00</updated><title type='text'>Protecting Apache Directories Using Apex Authentication Cookie</title><content type='html'>Regarding my previous post on &lt;a href="http://monkeyonoracle.blogspot.com/2009/10/storing-images-outside-oracle-xe-with.html"&gt;how to place your images outside Oracle XE&lt;/a&gt;, you might want to ensure that only logged in users have access to the image folders. The technique described here applies for Oracle Apex applications running behind an Apache, there are probably similar configurations available for other web servers. It is tested for &lt;a href="http://httpd.apache.org/"&gt;Apache 2.2&lt;/a&gt;, but will most likely work for versions down to 1.3.&lt;br /&gt;&lt;br /&gt;There is security, and then there is Security with a big, fat, capital S. This falls in the category below the big S. I do not know how to circumvent this simple trick to protect your folders, but there are probably those that can. If you just want to keep out the general public from accessing content from your web server, this is a very simple, unobtrusive way of doing it.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Configure Cookie Attributes in Apex&lt;/b&gt;&lt;br /&gt;When you log on to an application in Apex with native (and unedited) Apex authentication scheme, you will get a cookie named something like this:&lt;br /&gt;&lt;span style="font-family: &amp;quot;Courier New&amp;quot;,Courier,monospace; font-size: x-small;"&gt;WWV_CUSTOM-F_&amp;lt;some_id_here&amp;gt;_&amp;lt;APP_ID&amp;gt;&lt;/span&gt;&lt;br /&gt;It contains some mystic id. The attributes (not value) of this session cookie can easely be changed for the Apex application.&lt;br /&gt;&lt;br /&gt;In the builder&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Go to Shared Components for your application&lt;/li&gt;&lt;li&gt;Go to Authentication Schemes&lt;/li&gt;&lt;li&gt;Click your current Authentication Scheme&lt;/li&gt;&lt;li&gt;Scroll down to Cookie Attributes&lt;/li&gt;&lt;li&gt;Fill in the Cookie Name and Cookie Path values (see example below)&lt;/li&gt;&lt;/ul&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_8GtRvOur8pQ/StMcs-gTS2I/AAAAAAAAABA/qJm0VfeI798/s1600-h/cookie.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/StMcs-gTS2I/AAAAAAAAABA/qJm0VfeI798/s320/cookie.jpg" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;In my example, I named my cookie TTR_IMAGES, and the Path value ensures that the cookie is valid through my entire domain.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Configure Directory in Apache&lt;br /&gt;&lt;/b&gt;This means getting your hands dirty with &lt;a href="http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html"&gt;mod_rewrite&lt;/a&gt;. In my application I have an alias called "ttrimg" that maps to a physical directory where my image files are stored. Since I only want this directory open for users with valid Apex sessions, I include the rewrite rule in the &lt;a href="http://httpd.apache.org/docs/2.2/mod/core.html#directory"&gt;Directory directive&lt;/a&gt; (as opposed to under VirtualHost). In my simple world, I imagine it must be better to evaluate the condition for this directory only, and not every request. Then again, my knowledge of Apache is a bit limited to say the least (there was some reference to re-injecting URL for server processing when substituting).&lt;br /&gt;&lt;br /&gt;To achieve redirect based on TTR_IMAGES-cookie, include the following in httpd.conf (remember to back it up first!):&lt;br /&gt;&lt;pre&gt;Alias /ttrimg "C:/www/ttr/images"&lt;br /&gt;  &amp;lt;Directory "C:/www/ttr/images"&amp;gt;&lt;br /&gt;     ## Allow access if Apex session cookie is set and value not like -1&lt;br /&gt;     RewriteEngine On&lt;br /&gt;     RewriteCond %{HTTP_cookie} !TTR_IMAGES=([a-zA-Z0-9]{1})&lt;br /&gt;     RewriteRule /(.*) /403.html [R=403,L] &lt;br /&gt;&lt;br /&gt;     Options Indexes FollowSymLinks MultiViews ExecCGI&lt;br /&gt;     AllowOverride All&lt;br /&gt;     Order allow,deny&lt;br /&gt;     Allow from all&lt;br /&gt;  &amp;lt;/Directory&amp;gt;&lt;br /&gt;&lt;/pre&gt;Some explanation is in order (well, at least I do try! :-)):&lt;br /&gt;&lt;ul&gt;&lt;li&gt;You must explicitly set RewriteEngine On for your Directory directive&lt;/li&gt;&lt;li&gt;%{HTTP_cookie} contains all cookies for your domain/path&lt;/li&gt;&lt;li&gt;TTR_IMAGES=([a-zA-Z0-9]{1} means that cookie TTR_IMAGES must have value starting with a letter or digit. When you log out of the Apex application the cookie gets value -1.&lt;/li&gt;&lt;li&gt;The exclamation mark in front of the last expression negates the result, meaning if it does not match TTR_IMAGES=([a-zA-Z0-9]{1}, the rewrite rule will be applied&lt;/li&gt;&lt;/ul&gt;RewriteBase is not necessary with Apache 2.2.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Secure?&lt;/b&gt;&lt;br /&gt;For my particular need; secure enough. Only you know your own needs (well, hopefully...).&lt;br /&gt;&lt;br /&gt;Unencrypted cookies on an unsecured channel, can be prone to any number of things. Let me throw this at you: cookie hijacking (packet sniffing), cross site scripting cookie theft (send cookie to third party), cookie poisoning (tampering with cookie values)... Feel safer now?&lt;br /&gt;&lt;br /&gt;Oh well, as I stated earlier: Safe enough for my particular application.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-8261629405296488199?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/8261629405296488199/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/10/protecting-apache-directories-using.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/8261629405296488199'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/8261629405296488199'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/10/protecting-apache-directories-using.html' title='Protecting Apache Directories Using Apex Authentication Cookie'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_8GtRvOur8pQ/StMcs-gTS2I/AAAAAAAAABA/qJm0VfeI798/s72-c/cookie.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-7494244496915226570</id><published>2009-10-07T20:35:00.002+02:00</published><updated>2009-10-08T08:24:46.722+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xe'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Storing images outside Oracle XE with Apex</title><content type='html'>Although Oracle XE gives great value for money, 4Gb is not that much in this multimedia age. In this post I will describe how to store, resize and fetch images using Oracle XE and Apex, without having to worry about images eating of the precious 4Gb storage limit. If you don't run XE, you can use Ordimage data type and methods in the database to achieve the same result.&lt;br /&gt;&lt;br /&gt;Parts of the solution is OS specific (due to third party image processing applications), and in this case based on a Windows operating system. Adapting the implementation for your favorite nix system should not be that different.&lt;br /&gt;&lt;br /&gt;I will try to be structured in my explanation, but I am a developer after all... My pedagogical skills (or lack there of) lead me to explain this from the bottom up, breaking it up in uneven pieces:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Resize images with &lt;a href="http://www.irfanview.com/"&gt;IrfanView&lt;/a&gt; using CLI&lt;br /&gt;&lt;/li&gt;&lt;li&gt; Create OS script for resizing images&lt;/li&gt;&lt;li&gt; Create PL/SQL procedure to execute OS script from XE&lt;/li&gt;&lt;li&gt; Create PL/SQL procedure to write blob to OS file system&lt;/li&gt;&lt;li&gt; Create PL/SQL procedure for custom upload from APEX&lt;/li&gt;&lt;li&gt; Create Apex page to upload image&lt;/li&gt;&lt;/ul&gt;You can download the full source code from my &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:11"&gt;demo application&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Pre-requisites&lt;/span&gt;&lt;br /&gt;To follow the example, you must have the following installed and working correctly:&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Oracle XE&lt;/li&gt;&lt;li&gt; Oracle Apex 3.2&lt;/li&gt;&lt;li&gt; IrfanView v4.1 or later&lt;/li&gt;&lt;li&gt; Apache HTTP Server running as reverse proxy in front of EPG (if you want to view the images after uploading them...)&lt;/li&gt;&lt;/ul&gt;Some notes on the configuration: The process is only tested for Oracle Apex 3.2, but I see no reason why it should not work for older versions. You can change from Apache to your favorite web server, but some of the steps in this post will be different. This also applies to IrfanView, any other image processing application with command line interface will probably do the job just as well.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Resizing images with IrfanView&lt;/span&gt;&lt;br /&gt;As mentioned earlier, I will use &lt;a href="http://www.irfanview.com/"&gt;IrfanView &lt;/a&gt;in this example, it sports a CLI which is fast, and cover my needs. The CLI is also documented with examples, that helps... I am sure there are alternatives to IrfanView for Linux which has the same features, but I have not looked for any (yet).&lt;br /&gt;&lt;br /&gt;The syntax for resizing images are quite straight forward:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&amp;lt;installDir&amp;gt;\i_view32.exe &amp;lt;originalImage&amp;gt; /resize=(x,y)  /convert=&amp;lt;outputImage&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;ul&gt;&lt;li&gt; instalDir is full path to where IrfanView was installed&lt;/li&gt;&lt;li&gt; originalImage is full path and file name of image to be resized&lt;/li&gt;&lt;li&gt; outputImage is full path and file name of resized image&lt;/li&gt;&lt;li&gt; /resize=(x,y) - set pixels for resized image&lt;/li&gt;&lt;/ul&gt;I also add some other switches for good measure:&lt;br /&gt;&lt;ul&gt;&lt;li&gt; /aspectratio - keep aspect ratio&lt;/li&gt;&lt;li&gt; /jpgq=90 - drop quality to 90% of original&lt;/li&gt;&lt;li&gt; /resample - for better quality&lt;/li&gt;&lt;/ul&gt;The command line now becomes:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;&amp;lt;installDir&amp;gt;\i_view32.exe &amp;lt;originalImage&amp;gt; /resize=(1024,768) /aspectratio /jpgq=90 /resample /convert=&amp;lt;outputImage&amp;gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;Try it out on an existing image to make sure it works.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Creating OS script for resizing images&lt;/span&gt;&lt;br /&gt;Next up is packing the command lines into a .bat-file. The reason for this is twofold. First, I want to simplify the call from the database which will be executing the script later on. Second, I will resize each image more than once to get both thumbnails and "web friendly" versions of the images. Digital cameras today have an extraordinary amount of pixels, and way to much just for displaying it on a web page.&lt;br /&gt;&lt;br /&gt;I will also create a folder structure to store the images in, this is the folder that will be used by Apache to serve the images. I will work with the following directory structure:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;c:\www\ttr\images\orig&lt;/li&gt;&lt;li&gt;c:\www\ttr\images\thumb&lt;/li&gt;&lt;li&gt;c:\www\ttr\images\web&lt;/li&gt;&lt;/ul&gt;Folder "orig" will contain the original images, web contains images resized to a web-friendly 1024x768, and thumbs will contain 150x150 images.&lt;br /&gt;&lt;br /&gt;Create a script that looks like this:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;set PATH=%PATH%;C:\WINDOWS\system32;&lt;br /&gt;echo Start %date% %time% &amp;gt;&amp;gt; C:\Oracle\OraXE\images\test.txt&lt;br /&gt;C:\Programfiler\IrfanView\i_view32.exe C:\www\ttr\images\orig\%1 /resize=(1024,768) /aspectratio /resample /jpgq=90 /convert=C:\www\ttr\images\web\%1&lt;br /&gt;C:\Programfiler\IrfanView\i_view32.exe C:\www\ttr\images\orig\%1 /resize=(150,150) /aspectratio /resample /jpgq=90 /convert=C:\www\ttr\images\thumb\%1&lt;br /&gt;echo End %date% %time% &amp;gt;&amp;gt; C:\Oracle\OraXE\images\test.txt&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and save it as resize.bat in the images folder. Remember to replace the physical path names to your own structure. I have added setting of PATH environment variable to ensure the script can locate any additional files in the system32 folder, and echo Start/End to log how long time the conversion takes.&lt;br /&gt;&lt;br /&gt;Place an image in the c:\www\ttr\images\orig folder, and run resize.bat to check if it works.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Execute OS-script from XE&lt;/span&gt;&lt;br /&gt;I will cheat! In fact, I will cheat twice while I am at it.&lt;br /&gt;&lt;br /&gt;First of all I will be using dbms_scheduler to execute host commands. In order to avoid any hassle setting up the OracleXEClrAgent and user rights, I will create a procedure in SYS schema to create and execute the job. Not using invokers rights here avoids the whole user rights shebang. &lt;span style="font-style: italic;"&gt;See how easy it is shooting yourself in the foot?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Second, I will leave the script wide open any kind of host script. As an afterthought, I will grant schema user anything to get it going too! Feel comfortable putting this in production? If you do, go take a cold shower!&lt;br /&gt;&lt;br /&gt;Log in as SYS and create a procedure to run arbitrary host scripts (&lt;span style="font-style: italic;"&gt;...and such a big gun too...&lt;/span&gt;):&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;create or replace procedure resize_image (&lt;br /&gt;p_script_name     in          varchar2&lt;br /&gt;,  p_image_name      in          varchar2&lt;br /&gt;,  p_directory       in          varchar2&lt;br /&gt;) as&lt;br /&gt;begin&lt;br /&gt;dbms_scheduler.create_job(&lt;br /&gt;   'imgres'&lt;br /&gt; , job_action=&amp;gt;'C:\WINDOWS\system32\cmd.exe'&lt;br /&gt; , number_of_arguments=&amp;gt;4&lt;br /&gt; , job_type=&amp;gt;'executable'&lt;br /&gt; , enabled=&amp;gt;false);&lt;br /&gt;dbms_scheduler.set_job_argument_value('imgres',1,'/q');&lt;br /&gt;dbms_scheduler.set_job_argument_value('imgres',2,'/c');&lt;br /&gt;dbms_scheduler.set_job_argument_value('imgres',3,p_directory||p_script_name);&lt;br /&gt;dbms_scheduler.set_job_argument_value('imgres',4,p_image_name);&lt;br /&gt;dbms_scheduler.enable('imgres');&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;grant execute on sys.resize_image to &amp;lt;app_schema&amp;gt;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To test the procedure, make sure there web and thumb OS-directories are empty, and an image is placed in the orig directory.&lt;br /&gt;&lt;br /&gt;Log in as your &amp;lt;app_schema&amp;gt; user and execute the following script in SQL*Plus:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;begin&lt;br /&gt;sys.resize_image('imgres.bat', '&amp;lt;myImageFile&amp;gt;', 'C:\www\ttr\images\');&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If it comes up with a user rights error, try granting "create job", "create external job" and/or "manage scheduler" to your schema user. If it still does not work, check dbms_scheduler_job_log for any error messages. If you are unable to correct your job,search the web, there are plenty of people who has run into the same issue.&lt;br /&gt;&lt;br /&gt;When this piece works, the rest is a breeze.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Writing BLOB to OS-file&lt;/span&gt;&lt;br /&gt;There a ton of examples on how to do this, see Dr. Tim Hall's &lt;a href="http://www.oracle-base.com/dba/miscellaneous/ftp.pkb"&gt;ftp-package&lt;/a&gt;, or the &lt;a href="http://www.psoug.org/reference/utl_file.html"&gt;Extract BLOB Demo&lt;/a&gt; of Morgan's Library.&lt;br /&gt;My version is in between those two, but will do the job:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;create or replace directory IMAGES as 'C:\www\ttr\images\orig\'&lt;br /&gt;/&lt;br /&gt;&lt;br /&gt;create or replace procedure write_to_file (&lt;br /&gt;p_file_name       in          varchar2&lt;br /&gt;,  p_directory       in          varchar2&lt;br /&gt;,  p_content         in          blob&lt;br /&gt;) is&lt;br /&gt;l_file          utl_file.file_type;&lt;br /&gt;l_buffer        raw(32000);&lt;br /&gt;l_amount        binary_integer := 32000;&lt;br /&gt;l_pos           integer := 1;&lt;br /&gt;l_blob          blob;&lt;br /&gt;l_blob_left     number;&lt;br /&gt;l_blob_length   number;&lt;br /&gt;begin&lt;br /&gt;l_blob_length := dbms_lob.getlength(p_content);&lt;br /&gt;l_blob_left := l_blob_length;&lt;br /&gt;-- open the destination file.&lt;br /&gt;l_file := utl_file.fopen(p_directory,p_file_name,'WB', 32760);&lt;br /&gt;-- read chunks of the blob and write them to the file&lt;br /&gt;-- until complete.&lt;br /&gt;-- if small enough for a single write&lt;br /&gt;if l_blob_length &amp;lt; 32760 then&lt;br /&gt;utl_file.put_raw(l_file,p_content);&lt;br /&gt;utl_file.fflush(l_file);&lt;br /&gt;else -- write in pieces&lt;br /&gt;l_pos := 1;&lt;br /&gt;while l_pos &amp;lt; l_blob_length&lt;br /&gt;loop&lt;br /&gt;  dbms_lob.read(p_content,l_amount,l_pos,l_buffer);&lt;br /&gt;  utl_file.put_raw(l_file,l_buffer);&lt;br /&gt;  utl_file.fflush(l_file);&lt;br /&gt;  -- set the start position for the next cut&lt;br /&gt;  l_pos := l_pos + l_amount;&lt;br /&gt;  -- set the end position if less than 32000 bytes&lt;br /&gt;  l_blob_left := l_blob_left - l_amount;&lt;br /&gt;  if l_blob_left &amp;lt; 32000 then&lt;br /&gt;     l_amount := l_blob_left;&lt;br /&gt;  end if;&lt;br /&gt;end loop;&lt;br /&gt;end if;&lt;br /&gt;utl_file.fclose(l_file);&lt;br /&gt;exception&lt;br /&gt;when others then&lt;br /&gt;-- close the file if something goes wrong.&lt;br /&gt;if utl_file.is_open(l_file) then&lt;br /&gt;utl_file.fclose(l_file);&lt;br /&gt;end if;&lt;br /&gt;raise;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To test the procedure, you can run the following as schema user:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;declare&lt;br /&gt;l_file         blob;&lt;br /&gt;l_content      clob := 'This is soon to be a blob';&lt;br /&gt;l_src_offset   integer := 1;&lt;br /&gt;l_dest_offset  integer := 1;&lt;br /&gt;l_lang_ctx     integer := dbms_lob.default_lang_ctx;&lt;br /&gt;l_warn         integer;&lt;br /&gt;begin&lt;br /&gt;dbms_lob.createtemporary(l_file, false);&lt;br /&gt;dbms_lob.converttoblob(l_file, l_content, dbms_lob.getlength(l_content), l_dest_offset, l_src_offset, 1, l_lang_ctx, l_warn);&lt;br /&gt;write_to_file('testfile.txt', 'IMAGES', l_file);&lt;br /&gt;dbms_lob.freetemporary(l_file);&lt;br /&gt;exception&lt;br /&gt;when others&lt;br /&gt;then&lt;br /&gt; dbms_lob.freetemporary(l_file);&lt;br /&gt; raise;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;After running this, you should see a file called "testfile.txt" in the os-directory where the images will be placed later.&lt;br /&gt;&lt;br /&gt;Note to self: this would probably be more elegant using BFILE.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Creating a custom upload procedure&lt;/span&gt;&lt;br /&gt;Apex is goodhearted enough to take care of all the tedious bits of code to bring the image from your client into the database. If you want to do something more with it, you must create it yourself. Luckily it is not that hard. There are also some good examples of how to do this out there, including &lt;a href="http://download.oracle.com/docs/cd/E14373_01/appdev.32/e13363/up_dn_files.htm"&gt;one from Oracle in the official documentation&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Here I will bring the pieces together, the procedure below calls on both write_to_file-procedure and sys.image_resize-procedure after inserting the image in a custom table. My table is called MY_IMAGE, take care to change this and other bits to your implementation.&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;create or replace procedure store_image (&lt;br /&gt;p_file_name in varchar2&lt;br /&gt;, p_description in varchar2&lt;br /&gt;)&lt;br /&gt;is&lt;br /&gt;l_image_id     my_image.my_image_id%type;&lt;br /&gt;l_file         blob;&lt;br /&gt;l_mime_type    apex_application_files.mime_type%type;&lt;br /&gt;l_name         apex_application_files.name%type;&lt;br /&gt;l_file_ext     varchar2(255) := regexp_substr(p_file_name, '\..*$');&lt;br /&gt;begin&lt;br /&gt;-- get file from apex files&lt;br /&gt;select   name&lt;br /&gt;      , mime_type&lt;br /&gt;      , blob_content&lt;br /&gt; into   l_name&lt;br /&gt;      , l_mime_type&lt;br /&gt;      , l_file&lt;br /&gt; from   apex_application_files&lt;br /&gt;where   name = p_file_name;&lt;br /&gt;-- insert record into images table&lt;br /&gt;insert into my_image (   filename&lt;br /&gt;                       , mime_type&lt;br /&gt;                       , description)&lt;br /&gt; values   (   l_name&lt;br /&gt;            , l_mime_type&lt;br /&gt;            , p_description)&lt;br /&gt;returning my_image_id into l_image_id;&lt;br /&gt;-- insert file to os, use table pk as file name&lt;br /&gt;write_to_file(l_image_id||l_file_ext, 'IMAGES', l_file);    &lt;br /&gt;-- resize image, could check for mime-type here&lt;br /&gt;sys.resize_image('imgres.bat', l_image_id||l_file_ext, 'C:\www\ttr\images\');&lt;br /&gt;-- delete file from apex files when done&lt;br /&gt;delete from   apex_application_files&lt;br /&gt;     where   name = p_file_name;&lt;br /&gt;end store_image;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In order to test this, you need to create an Apex page with a file upload form region. In the following example, make sure your Apex application uses the same parsing schema as above, or have been granted appropriate rights to execute them.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Create a new empty page&lt;/li&gt;&lt;li&gt;Create a form based on a procedure&lt;/li&gt;&lt;li&gt;Choose procedure STORE_IMAGE&lt;/li&gt;&lt;li&gt;Display PXX_FILE_NAME as a Browse item and PXX_DESCRIPTION as textarea&lt;/li&gt;&lt;li&gt;Next-Next-Create (or something close to it)&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;And you are done.&lt;br /&gt;&lt;br /&gt;When you run the page, choose an image, click Submit, three versions of the image should now appear in the three images folders (original, web friendly and thumbnail).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Viewing the images&lt;/span&gt;&lt;br /&gt;For this, you need the Apache webserver. First of all, you must edit httpd.conf (AFTER backing it up first, of course!), include the lines:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Alias /ttrimg "C:/www/ttr/images"&lt;br /&gt;&amp;lt;Directory "C:/www/ttr/images"&amp;gt;&lt;br /&gt;  Options Indexes FollowSymLinks MultiViews ExecCGI&lt;br /&gt;  AllowOverride All&lt;br /&gt;  Order allow,deny&lt;br /&gt;  Allow from all&lt;br /&gt;&amp;lt;/Directory&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;somewhere at the bottom of the file. If you use VirtualHost directives, be sure to include it inside the directive.&lt;br /&gt;&lt;br /&gt;This is just a suggestion, options and access rights must be adapted to your needs (the Alias above is very public), the same goes for the location of the images directory. You must restart the Apache for the changes to take effect.&lt;br /&gt;&lt;br /&gt;The images can now be reached with the following URL construct:&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Original image:&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family:courier new;"&gt;http://&amp;lt;yourserver&amp;gt;:&amp;lt;port&amp;gt;/ttrimg/orig/&amp;lt;my_image.file_name&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt; Web friendly image:&lt;br /&gt;&lt;span style=";font-family:courier new;font-size:85%;"  &gt;http://&amp;lt;yourserver&amp;gt;:&amp;lt;port&amp;gt;/ttrimg/web/&amp;lt;my_image.file_name&amp;gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt; Thumbnail:&lt;br /&gt;&lt;span style=";font-family:courier new;font-size:85%;"  &gt;http://&amp;lt;yourserver&amp;gt;:&amp;lt;port&amp;gt;/ttrimg/thumb/&amp;lt;my_image.file_name&amp;gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Cool?&lt;/span&gt;&lt;br /&gt;Well, isn't it?!? Not breaking native Apex upload functionality, and not eating of the precious 4Gb. Me like :-)&lt;br /&gt;&lt;br /&gt;The whole source code including create user, grants, .bat, apex app, etc. can be downloaded from my &lt;a style="font-weight: bold;" href="http://apex.oracle.com/pls/otn/f?p=28990:11"&gt;demo application&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;PS:&lt;/span&gt;&lt;br /&gt;This was just an example, there are things I would do before moving on:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Waiting for the image to be resized: Scheduler jobs are by nature asynchronous (unless you use the "use_current_session"-thingy), so in order to view your images immediately after upload, you must code your own "pause"-procedure (and probably check dbms_scheduler_job_log)&lt;/li&gt;&lt;li&gt;The naming of the OS-files does not say much, there are room for improvement here. This also means my_image-table must be updated accordingly. BFILE again?&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Directory structure should be reconsidered if you expect a large amount of images, sub folders can be useful&lt;/li&gt;&lt;li&gt;Extract and retrieve EXIF information back into the database?&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-7494244496915226570?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/7494244496915226570/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/10/storing-images-outside-oracle-xe-with.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7494244496915226570'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7494244496915226570'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/10/storing-images-outside-oracle-xe-with.html' title='Storing images outside Oracle XE with Apex'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-7638880918905480941</id><published>2009-10-04T23:15:00.014+02:00</published><updated>2009-10-05T00:13:43.336+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='fckeditor'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Interacting with HTML Editor Items in Oracle Apex</title><content type='html'>Oracle Apex ships with a rich text editor to enhance text area items; "HTML Editor Standard" and "HTML Editor Minimal". As you may know, this is actually &lt;a href="http://ckeditor.com/download"&gt;FCKeditor&lt;/a&gt; (renamed CKEditor in the last release). This post is a short description on how to interact with FCKeditor API.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;About the FCKeditor API&lt;/span&gt;&lt;br /&gt;The API is documented on &lt;a href="http://docs.fckeditor.net/FCKeditor_2.x/Developers_Guide/JavaScript_API"&gt;DKSource Docs&lt;/a&gt;. In my case, I only needed to paste something into the editor instance, but there are a number of things you can do. Basicly there are methods you can call to get/set values and properties from the editor instance, and there are events you can listen to, and override default behaviour.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;My case&lt;/span&gt;&lt;br /&gt;During creation and editing of articles, I wanted to be able to add images into the editor by the simplest means possible. And the images (when the article was displayed) should open in &lt;a href="http://www.huddletogether.com/projects/lightbox2/"&gt;Lightbox2&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;So, I had:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;A table of images (ordimage data type)&lt;/li&gt;&lt;li&gt;An application process to serve both thumbnails and whole images&lt;/li&gt;&lt;li&gt;A report with thumbnails&lt;/li&gt;&lt;li&gt;A page item of type HTML Editor Standard&lt;/li&gt;&lt;li&gt;Lightbox2&lt;/li&gt;&lt;/ul&gt;A couple of notes here. The application process was based on &lt;a href="http://www.oracle.com/global/de/community/tipps/ordimage/index.html"&gt;an article by Carsten Czarsky&lt;/a&gt; on the &lt;a href="http://www.oracle.com/global/de/community/index.html"&gt;German Apex community&lt;/a&gt; pages. There are more gems hidden among these community pages, but being written in German they don't show up in my web searches very often. &lt;a href="http://translate.google.com/"&gt;Google Translate&lt;/a&gt; to the rescue, my German is not good to say the least... Just remember to keep the original page close by when copying code, as the code examples gets translated too :-)&lt;br /&gt;&lt;br /&gt;Integrating Lightbox2 into Apex &lt;a href="http://deneskubicek.blogspot.com/2009/06/use-lightbox-2-in-apex.html"&gt;was described by Denes Kubicek&lt;/a&gt; (creator of &lt;a href="http://htmldb.oracle.com/pls/otn/f?p=31517:1"&gt;the mother of all Apex sample apps&lt;/a&gt;), I have used a &lt;a href="http://seb.box.re/2006/11/22/lightbox-2-auto-resizing-enhancement"&gt;slightly modified version by Sébastien Grosjean&lt;/a&gt; (auto resize to browser window), but integrating with Apex is the same as the unmodified version.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Communicating with the FCKeditor&lt;/span&gt;&lt;br /&gt;First of all I created a small javascript function in the html header. The function was to accept a string to be pasted into the editor.&lt;br /&gt;&lt;pre class="brush: js"&gt;&lt;br /&gt;function f_pasteEditor(htmlSnippet)&lt;br /&gt;{&lt;br /&gt;var oEditor = FCKeditorAPI.GetInstance('P12_TEXT');&lt;br /&gt;oEditor.InsertHtml(htmlSnippet);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;P12_TEXT being the HTML Editor Standard item.&lt;br /&gt;&lt;br /&gt;Next I created a report column, and edited the column link properties:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Link Text: Paste text&lt;/li&gt;&lt;li&gt;Target: URL&lt;/li&gt;&lt;li&gt;URL: javascript:f_pasteEditor('SomeTextToBePasted');&lt;/li&gt;&lt;/ul&gt;It actually works! No great accomplishment perhaps, but a step in the right direction :-)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Setting up the URL&lt;/span&gt;&lt;br /&gt;...or The Great Escape. In my case, the pasted text would have a very specific syntax:&lt;br /&gt;&lt;span style=";font-family:courier new;font-size:85%;"  &gt;&amp;lt;a href="imgprocess" rel="lightbox"&amp;gt;&amp;lt;img src="imgprocess" title="image name"&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;&lt;/span&gt;and imgprocess is a call to an application process which looks like this:&lt;br /&gt;&lt;span style=";font-family:courier new;font-size:85%;"  &gt;f?p=&amp;amp;APP_ID.:&amp;amp;APP_PAGE_ID.:&amp;amp;SESSION.:APPLICATION_PROCESS=getImage:::P_IMAGE_ID,P_THUMBNAIL:#IMAGE_ID#,Y&lt;br /&gt;&lt;/span&gt;Look at the URL in the link target above, what could possibly go wrong here? (or: what went wrong for me!)&lt;br /&gt;&lt;ul&gt;&lt;li&gt;If the name of the picture contains a single quote character, it will blow up. Single quotes has a meaning in javascript. Replacing it with unicode escape character "\u0027" will solve the issue.&lt;/li&gt;&lt;li&gt;Double quotes has a meaning in HTML, putting double quotes into target URL will have an impact. Replacing it with unicode escape character "\u0022" or (if the output is HTML) "&amp;amp;quot;" will solve the issue.&lt;/li&gt;&lt;li&gt;Apex substitution variables gets, er.. substituted! My resulting link in FCKeditor was supposed to contain substitution variables, when they are substituted in the report, that is a bit of a problem.&lt;/li&gt;&lt;li&gt;The Apex report would simply blank the column containing the URL (hm... this was probably a fault entirely created by me, but helps to understand my solution ;-))&lt;/li&gt;&lt;/ul&gt;I solved this the quick and dirty way, by extending the javascript function, and hardcoding the link there:&lt;br /&gt;&lt;pre class="brush: js"&gt;&lt;br /&gt;function f_pasteImg(p_item_name, p_image_id, p_alt)&lt;br /&gt;{&lt;br /&gt;   var htmlSnippet = '&amp;lt;a href=\&amp;quot;f?p=&amp;A'+'PP_ID.:&amp;AP'+'P_PAGE_ID.:&amp;S'+'ESSION.:APPLICATION_PROCESS=getImage:::P_IMAGE_ID,P_THUMBNAIL:'+p_image_id+',N\&amp;quot; rel=\&amp;quot;lightbox[myPicts]\&amp;quot;&amp;gt;&amp;lt;img align=&amp;quot;left&amp;quot; alt=\&amp;quot;'+ p_alt +'\&amp;quot; src=\&amp;quot;f?p=&amp;AP'+'P_ID.:&amp;A'+'PP_PAGE_ID.:&amp;S'+'ESSION.:APPLICATION_PROCESS=getImage:::P_IMAGE_ID,P_THUMBNAIL:'+p_image_id+',Y\&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;';&lt;br /&gt;   var oEditor = FCKeditorAPI.GetInstance(p_item_name);&lt;br /&gt;   oEditor.InsertHtml(htmlSnippet);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note how the Apex substitution variables are divided with "'+'" so the Apex engine won't replace it, and how the double quote are escaped by "\". Quotes in the alt-text are replaced with it's HTML/unicode counterparts in the SQL-query. I also cheat a bit by aligning the picture to the left, but this can easily be changed with FCKeditor later.The images will not show up in the preview window, because of the unsubstituted substitution variables (phew!).&lt;br /&gt;&lt;br /&gt;As long as the images are served through an application process (to apply security) and not through a public procedure, this will be the result. But when the article is displayed, and session variables properly substituted, the thumbnails are properly displayed&lt;br /&gt;&lt;br /&gt;So now my articles shows clickable thumbnails which displays the full picture in Lighbox2 :-)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The result?&lt;/span&gt;&lt;br /&gt;Despite my shaky implementation, the users are left with a simple way to include pictures into their articles. That was pretty much the point. The users doesn't need to know it's held together with chewing gum and a piece of string.&lt;br /&gt;&lt;br /&gt;As for me, I start to realize that javascript can do pretty much everything, and that sometimes it is a pain to get it to do anything...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-7638880918905480941?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/7638880918905480941/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/10/interacting-with-html-editor-items-in.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7638880918905480941'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/7638880918905480941'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/10/interacting-with-html-editor-items-in.html' title='Interacting with HTML Editor Items in Oracle Apex'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-3173936800831773818</id><published>2009-07-09T21:32:00.008+02:00</published><updated>2009-07-09T23:38:12.169+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plsql'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='rtf'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Dynamic RTF Documents Revisited</title><content type='html'>Yes, yes, this will be my last post on RTF for a while, but it's been a fun ride so far.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The (hi)story&lt;/span&gt;&lt;br /&gt;In my posting &lt;a href="http://monkeyonoracle.blogspot.com/2009/05/rtf-documents-in-oracle-database.html"&gt;RTF Documents in an Oracle Database&lt;/a&gt; I showed how to go about&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Creating RTF templates with substitution variables&lt;/li&gt;&lt;li&gt;Generating RTF documents with substitution variables filled in run-time&lt;/li&gt;&lt;li&gt;Data model and code to support the functionality in Oracle Apex&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;In the posting &lt;a href="http://monkeyonoracle.blogspot.com/2009/06/converting-rtf-to-pdf-in-oracle.html"&gt;Converting RTF to PDF in an Oracle Database&lt;/a&gt; I,... Well the title is pretty self explanatory I guess :-)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Too simple!&lt;/span&gt;&lt;br /&gt;Well, the first version of the code I showcased was a bit on the simple side, but it was just meant to give a hint of the possibilities. Anyway, I challenged myself to make a more complex RTF-component, so I set about wasting my time on coding a PL/SQL package that can return a RTF-table based on a query. With heavy use of &lt;span style="font-style: italic;"&gt;dbms_sql&lt;/span&gt; the code can:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Describe and store query columns&lt;br /&gt;This is used when defining the substitution variable. The columns are stored as rows in a table and have attributes such as title, text alignment, width.&lt;/li&gt;&lt;li&gt;Running the query and return a RTF-table&lt;br /&gt;This is just a more advanced version of the simple substitution variables, but in stead of returning one column, one row as plain text, it now returns a RTF-table (but still as text).&lt;/li&gt;&lt;/ul&gt;In order for it to work, the query must have column aliases that can be resolved by &lt;span style="font-style: italic;"&gt;dbms_sql.describe_columns&lt;/span&gt; into unique column names, and the columns must be of type varchar2 (yes, it is quite possible to return other data types, but in most cases text will suffice and I am lazy).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Sample Application&lt;/span&gt;&lt;br /&gt;I have updated the sample application to show the function. I have included some images here to explain some of the elements. First a screen shot of the listing of all substitution variables in the uploaded template:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_8GtRvOur8pQ/SlZaW8jYWUI/AAAAAAAAAAQ/Gc4XyUG8eQg/s1600-h/substTemplate03.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 127px;" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/SlZaW8jYWUI/AAAAAAAAAAQ/Gc4XyUG8eQg/s320/substTemplate03.jpg" alt="" id="BLOGGER_PHOTO_ID_5356568157061929282" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And a screen shot of the substitution variable attributes:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_8GtRvOur8pQ/SlZajYjSjfI/AAAAAAAAAAY/dYOHb4ZXx5A/s1600-h/substAttrs02.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 282px; height: 320px;" src="http://4.bp.blogspot.com/_8GtRvOur8pQ/SlZajYjSjfI/AAAAAAAAAAY/dYOHb4ZXx5A/s320/substAttrs02.jpg" alt="" id="BLOGGER_PHOTO_ID_5356568370736172530" border="0" /&gt;&lt;/a&gt;You can test it in &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:2"&gt;my sample application&lt;/a&gt;, you can also download all necessary sample code there.&lt;br /&gt;&lt;br /&gt;Now I soon leave for my summer holiday, five (!) weeks of leisure with my family :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-3173936800831773818?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/3173936800831773818/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/07/dynamic-rtf-documents-revisited.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3173936800831773818'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3173936800831773818'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/07/dynamic-rtf-documents-revisited.html' title='Dynamic RTF Documents Revisited'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_8GtRvOur8pQ/SlZaW8jYWUI/AAAAAAAAAAQ/Gc4XyUG8eQg/s72-c/substTemplate03.jpg' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-5042510579041286039</id><published>2009-07-07T15:20:00.005+02:00</published><updated>2009-07-07T15:59:25.401+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>XSL Transformation in Oracle Apex</title><content type='html'>I recently explored some of the XML capabilities of the Oracle database, and I must say it has matured quite a bit since I last looked at it (Oracle 8i).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Using XMLTRANSFORM-function&lt;/span&gt;&lt;br /&gt;It is a powerful function which acts as a full XSLT processor. The syntax is simple:&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;&lt;/span&gt;&lt;blockquote&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-family: courier new;"&gt;xmltransform([xml_document],[xsl_document])&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;Both input parameters must be of type xmltype. By having XML/XSL stored as xmltype, or casting it to xmltype with the corresponding function, you in effect ensure the structure of the xml-documents are intact.&lt;br /&gt;&lt;br /&gt;Read the &lt;a href="http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/functions229.htm"&gt;documentation&lt;/a&gt; for more information. I know it can be boring, but also quite giving (from time to time, at least...). While you are at it, why not click "Previous" or "Next" on the documentation pages to view the other XML-functions?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Sample application&lt;/span&gt;&lt;br /&gt;Well, "application" is just a bit overstated, what I do have is an Oracle Apex application page. I have created a quick and easy front end to the XSL transformation. Just paste your XML and XSL into the corresponding text areas, and click button Transform to view the result.&lt;br /&gt;&lt;br /&gt;If the transformation fails, the error message will be returned in the display area.&lt;br /&gt;&lt;br /&gt;Try out the transformation, and get a detailed description of the code involved &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:10"&gt;in my sample application&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-5042510579041286039?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/5042510579041286039/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/07/xsl-transformation-in-oracle-apex.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/5042510579041286039'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/5042510579041286039'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/07/xsl-transformation-in-oracle-apex.html' title='XSL Transformation in Oracle Apex'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-9039974760836446071</id><published>2009-06-26T13:42:00.014+02:00</published><updated>2009-07-03T17:47:56.682+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='pdf'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='rtf'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Converting RTF to PDF in an Oracle Database</title><content type='html'>There are a number of commercial products out there which converts RTF to PDF, but there is also a free alternative called Ted. Actually, calling this a converter is a gross understatement, it is a fully fledged RTF editor which also can convert RTF to PDF.&lt;br /&gt;&lt;br /&gt;I will describe how to install Ted, and how to use it with RTF-documents stored in the database. This combined with &lt;a href="http://monkeyonoracle.blogspot.com/2009/05/rtf-documents-in-oracle-database.html"&gt;generating dynamic RTF-documents&lt;/a&gt; can give a rich document solution for your Oracle Apex applications.&lt;br /&gt;&lt;br /&gt;This is a Linux/Unix specific solution. So if you are stuck with some other operating system, go do something about it ;-)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Downloading and Installing Ted&lt;/span&gt;&lt;br /&gt;Ted runs on Unix/Linux, and you can download the software &lt;a href="http://www.nllgg.nl/Ted/"&gt;here&lt;/a&gt;. Ted is released under &lt;a href="http://www.fsf.org/licensing/licenses/gpl.html"&gt;GNU Public License&lt;/a&gt;, and restrictions may apply.&lt;br /&gt;&lt;br /&gt;The easiest way of installing Ted is to use RPM package file.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Download RPM package file to /tmp&lt;/li&gt;&lt;li&gt;Install RPM-package as user root (or system user with sufficient privileges)&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;rpm -ivh &lt;/span&gt;&lt;packagename style="font-family: courier new;"&gt;.rpm&lt;/packagename&gt;&lt;/li&gt;&lt;/ul&gt;If you get an error stating that packages are missing, do not despair, simply &lt;a href="http://rpm.pbone.net/"&gt;download&lt;/a&gt; and install the package indicated (or get it from your installation CD). The installation is pretty much the same as described in the bullets above.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Preparing os for RTF conversion&lt;/span&gt;&lt;br /&gt;Download Ted the &lt;a href="ftp://ftp.nluug.nl/pub/editors/ted/rtf2pdf.sh"&gt;rtf2pdf.sh&lt;/a&gt; script which converts rtf-files to pdf-files.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Create an os-directory:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;mkdir /u01/app/ted&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Copy the Ted conversion script to the directory&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;cp /u01/stage/ted/rtf2pdf.sh /u01/app/ted/.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Grant execute privileges on the script&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;chmod +x rtf2pdf.sh&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;I have done the above operations with user oracle of group oinstall, and in this demonstration user oracle will be used to execute os-operations. This is probably not the best production solution, but will serve for my demonstration purposes.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Creating directory in the Oracle database&lt;br /&gt;&lt;/span&gt;Defining a directory in Oracle is quite easy:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Connect as user SYS&lt;/li&gt;&lt;li&gt;Create directory:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;create directory RTF2PDF as '/u01/app/ted';&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Grant privileges to RTF2PFDF&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;grant all on directory RTF2PDF to &amp;lt;schema_user&amp;gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;How to execute os-commands from the Oracle database&lt;/span&gt;&lt;br /&gt;There are basicly three alternatives; dbms_scheduler, Java and C. The three alternatives and use of dbms_scheduler is described by Steven Feuerstein &lt;a href="http://apex.oracle.com/pls/otn/f?p=2853:4:660353051264551::NO::P4_QA_ID:16282"&gt;here&lt;/a&gt;. He also has &lt;a href="http://www.toadworld.com/BLOGS/tabid/67/EntryID/356/Default.aspx"&gt;a good description of the Java and C approaches&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;dbms_scheduler is the superior alternative to me, it's easy to use, and as opposed to Java, is compatible with Oracle XE (even though there are &lt;a href="http://forums.oracle.com/forums/thread.jspa?threadID=555102"&gt;some issues&lt;/a&gt; with this release). The only beef with dbms_scheduler are the implicit commits, I have not investigated when or why any further as I have barely scratched the surface of dbms_scheduler. I'll get around to it some rainy day.&lt;br /&gt;&lt;br /&gt;What actually made me revisit dbms_scheduler was &lt;a href="http://apex-at-work.blogspot.com/2009/06/dbmsscheduler-examples.html"&gt;this recent blog post&lt;/a&gt; by Tobias Arnold, following feeds like &lt;a href="http://orana.info/"&gt;orna.info&lt;/a&gt; and &lt;a href="http://www.apexblogs.info/"&gt;apexblogs.info&lt;/a&gt; has proven invaluable for a monkey like me :-)&lt;br /&gt;&lt;br /&gt;Dr. Tim Hall has an excellent site called &lt;a href="http://www.oracle-base.com/index.php"&gt;oracle-base.com&lt;/a&gt; with loads of examples and articles on the Oracle database. My dbms_scheduler code and setup is based on his article &lt;a href="http://www.oracle-base.com/articles/11g/SchedulerEnhancements_11gR1.php"&gt;Scheduler Enhancements in Oracle Database 11g Release 1&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Preparing for use of dbms_scheduler&lt;/span&gt;&lt;br /&gt;Before your schema user can take advantage of dbms_scheduler and execute host commands, there are a few steps that must be done:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Connect as user sys&lt;/li&gt;&lt;li&gt;Grant create job to schema user:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;grant create job to &amp;lt;schema_user&amp;gt;;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Grant the ability to execute host commands/external jobs to schema user:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;grant create external job to &amp;lt;schema_user&amp;gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Create credential (this is new in 11g, and saves a lot of work):&lt;/li&gt;&lt;/ul&gt;&lt;pre class="brush: sql"&gt;begin&lt;br /&gt;-- basic credential.&lt;br /&gt;dbms_scheduler.create_credential(&lt;br /&gt;credential_name =&gt; 'LOCALORACLE',&lt;br /&gt;username        =&gt; '&lt;host_user&gt;',&lt;br /&gt;password        =&gt; '&lt;host_user_password&gt;');&lt;br /&gt;end;&lt;br /&gt;&lt;/pre&gt;&lt;ul&gt;&lt;li&gt;Grant credential to schema user:&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;grant execute on LOCALORACLE to &amp;lt;schema_user&amp;gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;The os-user specified must have execution privileges on rtf2pdf.sh, and r+w on the directory where the script resides for this example.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;PL/SQL code to invoke conversion&lt;/span&gt;&lt;br /&gt;I have written a sample package for converting a rtf-document to a pdf-document. Sadly, I can't use Oracle's hosted environment on apex.oracle.com to showcase the functionality. I do not have the privileges to perform the required steps described above, and rightly so. Feel free to download and test it yourself. I have uploaded the package to &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:1"&gt;my sample application&lt;/a&gt; at apex.oracle.com, you can download the sample code &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:9"&gt;&lt;span style="font-weight: bold;"&gt;here&lt;/span&gt;&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The code is for demonstration purposes only, so my usual disclaimer apply: I take no responsibility what so ever for what might happen when you use it and there is no warranty of any kind expressed or implied. Phew... I still hope for world domination when you compile the package though ;-)&lt;br /&gt;&lt;br /&gt;The package contains procedures/functions for file handling and invoking rtf2pdf.sh, but can easily be adapted for a more generic use.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Converting RTF to PDF&lt;/span&gt;&lt;br /&gt;Lets say you have a table called RTF_DOCUMENTS that looks like this:&lt;br /&gt;&lt;pre class="brush: sql"&gt;create table rtf_document&lt;br /&gt;(&lt;br /&gt;rtf_document_id   number not null,&lt;br /&gt;file_name         varchar2 (255) not null,&lt;br /&gt;rtf_file          clob,&lt;br /&gt;pdf_file          blob,&lt;br /&gt;constraint rtf_document_pk primary key (rtf_document_id)&lt;br /&gt;);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And you want to create a procedure to convert the contents of column RTF_FILE to a pdf-document in column PDF_DOCUMENT, you can use the following procedure:&lt;br /&gt;&lt;pre class="brush: sql"&gt;create or replace procedure convert_rtf_doc (&lt;br /&gt;p_rtf_document_id    in       rtf_document.rtf_document_id%type&lt;br /&gt;,  p_dir_name           in       varchar2 default 'RTF2PDF'&lt;br /&gt;) is&lt;br /&gt;cursor c_doc&lt;br /&gt;is&lt;br /&gt;select rtdo.rtf_document_id&lt;br /&gt;    , rtdo.file_name&lt;br /&gt;    , rtdo.rtf_file&lt;br /&gt;    , rtdo.pdf_file&lt;br /&gt;from rtf_document rtdo&lt;br /&gt;where rtdo.rtf_document_id = p_rtf_document_id;&lt;br /&gt;l_pdf_file        blob;&lt;br /&gt;begin&lt;br /&gt;for r_doc in c_doc&lt;br /&gt;loop&lt;br /&gt;l_pdf_file := rtf2pdf.rtf2pdf(p_rtf_file_name =&gt; r_doc.file_name&lt;br /&gt;                            ,p_rtf_file =&gt; r_doc.rtf_file&lt;br /&gt;                            ,p_dir_name =&gt; p_dir_name);&lt;br /&gt;update rtf_document rtdo&lt;br /&gt;  set rtdo.pdf_file = l_pdf_file&lt;br /&gt;where rtdo.rtf_document_id = p_rtf_document_id;&lt;br /&gt;end loop;&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Beware of the implicit commit by dbms_scheduler! A select for update in c_doc cursor would result in "fetch out of sequence"-error.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Alternatives to Ted&lt;/span&gt;&lt;br /&gt;There are always alternatives, this is not different. There are some Open Source projects out there, and this guy (that's sexist, but I still assume cfSearching is a he) has done &lt;a href="http://cfsearching.blogspot.com/2009/04/coldfusion-in-search-of-wordrtf-to-pdf.html"&gt;some research on the topic&lt;/a&gt;. His angle was different from mine though, and he went the ooxml-way, which is not a stupid idea.&lt;br /&gt;&lt;br /&gt;Open Office could be an alternative, and has a more active community surrounding it. The CLI surrounding the OO-libraries in server mode will probably do the job.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;In conclusion&lt;/span&gt;&lt;br /&gt;In all honesty, I have not yet investigated to which extent the rendering of pdf-files is correct based on the more complex elements of rtf. This, of course, I should have done first. For me the journey is the most fun when (as in this case) there are no expectations attached to the end solution.&lt;br /&gt;&lt;br /&gt;Tightening the PL/SQl-code referenced here for production goes without saying!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-9039974760836446071?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/9039974760836446071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/06/converting-rtf-to-pdf-in-oracle.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/9039974760836446071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/9039974760836446071'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/06/converting-rtf-to-pdf-in-oracle.html' title='Converting RTF to PDF in an Oracle Database'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-6852328605475221614</id><published>2009-06-21T21:40:00.011+02:00</published><updated>2009-06-23T08:37:14.076+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plsql'/><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Generating Table Collection API for Oracle Apex</title><content type='html'>...or "The Art of Laziness". The lazy programmer will always strive to get more for less, which is a good thing.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Why?&lt;/span&gt;&lt;br /&gt;After looking at the collection API's i Oracle Apex, I could see their usefulness, but also get an idea on how labor intensive the operations were (for the programmer, not the database). I quickly got bored of the c001, c002, etc. coding, so for some time now I have been working on an API generator for creating and manipulating collections based on tables.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;What is generated?&lt;/span&gt;&lt;br /&gt;You get two packages and a view.&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;The table API-package:&lt;/span&gt; This is very similar to the API packages generated by Apex, but with some changes to the cursor fetching table rows (all now share a global cursor) and the updates in my version does not attempt to update primary keys. Quite frankly, I could get by with the API's from Apex, but I have a bit of control freakishness about me, and it puts me in a better position to further customize the API when needed. I already have some ideas fro the latter part.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;The collection API package:&lt;/span&gt; This is the package that wraps both the Apex API's, and the table operations. Here you will find create, drop, ins/upd/del collection procedures, all with parameters that match the table, both in name and data type. You also get an apply-procedure to handle the actual dml operations against the actual database table. The package supports "overloading" of collections, so you can have more then one collection based on the same table at the same time. It also handles MD5 if you want it to&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;The view:&lt;/span&gt; The view wraps the htmld_collections-view, and casts data types and column names from c### varchar2 format to their actual column names and data types from the database table it was founded on.&lt;/li&gt;&lt;/ul&gt;Statements for calling the collection API is also generated and included with the view-script.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Installation&lt;/span&gt;&lt;br /&gt;Just download the generator package, and compile it into desired schema. Schema must have access to the following views: all_tab_columns, all_cons_columns and all_constraints. I deliberately left out any supporting tables (which certainly could have helped in some areas) to simplify things. This means that if you want to modify some parameters, you will have to do it in the package.&lt;br /&gt;&lt;br /&gt;You can download the generator package &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:8"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Generating code&lt;/span&gt;&lt;br /&gt;Well, generating the code is quite easy. It is a "fire-and-forget" kind of operation; the packages and view will be generated in a PL/SQL-collection and can be queried and executed at will.&lt;br /&gt;&lt;br /&gt;The following section shows how to generate code for an EMP-table located in schema NMS:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;begin&lt;br /&gt;gen_coll_p.gen_coll_package (p_owner        =&gt; 'NMS',&lt;br /&gt;                          p_table_name   =&gt; 'EMP',&lt;br /&gt;                          p_shortname    =&gt; 'EMP',&lt;br /&gt;                          p_gen_type     =&gt; 'ALL');&lt;br /&gt;end;&lt;br /&gt;/&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;p_gen_type indicates what part(s) of code to generate, the available values are:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;TAB_API_SPEC for table API package specification&lt;/li&gt;&lt;li&gt;TAB_API_BODY for table API package body&lt;/li&gt;&lt;li&gt;COLL_SPEC for table/collection API package specification&lt;/li&gt;&lt;li&gt;COLL_BODY for table/collection API package body&lt;/li&gt;&lt;li&gt;COLL_VIEW for table/collection wrapper view&lt;/li&gt;&lt;li&gt;ALL for all of the above&lt;/li&gt;&lt;/ul&gt;Viewing the generated code:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;select text&lt;br /&gt;from table (gen_coll_p.get_code)&lt;br /&gt;order by line&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Compiling generated code:&lt;/span&gt;&lt;br /&gt;This part I have put NO amount of work in, after selecting the generated code, you are on your own :-) Well, it's not really that hard to select the code in SQL Developer or TOAD using the query above and copy/paste the result into a new window and choose "Run Script". Or lacking any "sophisticated" tools, you can even revert to spooling and running the result in SQL*Plus . I am sure you will find a way :-)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Using the collection API&lt;/span&gt;&lt;br /&gt;Te following examples shows how to use the API from an Oracle Apex application. The API in these examples are generated on the EMP table, and p_shortname parameter during generation is set to EMP as well.&lt;br /&gt;&lt;br /&gt;Creating a new collection:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;apex_emp_coll_p.create_coll( p_coll_name =&gt; 'EMP_COLL'&lt;br /&gt;                           , p_include_md5 =&gt; 'Y'&lt;br /&gt;                           , p_where =&gt; 'where upper(ename) like ''S%''');&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Viewing the contents of the collection:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;select seq_id,&lt;br /&gt;    empno,&lt;br /&gt;    ename,&lt;br /&gt;    job,&lt;br /&gt;    mgr,&lt;br /&gt;    hiredate,&lt;br /&gt;    sal,&lt;br /&gt;    comm,&lt;br /&gt;    deptno&lt;br /&gt;from apex_emp_coll_v&lt;br /&gt;where apex_emp_coll_v.collection_name = 'EMP_COLL'&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And please feel free to replace 'EMP_COLL' with a bind variable :-)&lt;br /&gt;&lt;br /&gt;Inserting a collection member:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;apex_emp_coll_p.ins(p_coll_name =&gt; 'EMP_COLL'&lt;br /&gt;                   ,p_empno     =&gt; :p_empno&lt;br /&gt;                   ,p_ename     =&gt; :p_ename&lt;br /&gt;                   ,p_job       =&gt; :p_job&lt;br /&gt;                   ,p_mgr       =&gt; :p_mgr&lt;br /&gt;                   ,p_hiredate  =&gt; :p_hiredate&lt;br /&gt;                   ,p_sal       =&gt; :p_sal&lt;br /&gt;                   ,p_comm      =&gt; :p_comm&lt;br /&gt;                   ,p_deptno    =&gt; :p_deptno);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Updating a collection member:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;apex_emp_coll_p.upd(p_coll_name =&gt; 'EMP_COLL'&lt;br /&gt;                   ,p_seq_id    =&gt; :p_seq_id&lt;br /&gt;                   ,p_empno     =&gt; :p_empno&lt;br /&gt;                   ,p_ename     =&gt; :p_ename&lt;br /&gt;                   ,p_job       =&gt; :p_job&lt;br /&gt;                   ,p_mgr       =&gt; :p_mgr&lt;br /&gt;                   ,p_hiredate  =&gt; :p_hiredate&lt;br /&gt;                   ,p_sal       =&gt; :p_sal&lt;br /&gt;                   ,p_comm      =&gt; :p_comm&lt;br /&gt;                   ,p_deptno    =&gt; :p_deptno);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Deleting a collection member:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;apex_emp_coll_p.del(p_coll_name =&gt; 'EMP_COLL'&lt;br /&gt;                   ,p_seq_id    =&gt; :p_seq_id);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Propagating changes to the database table:&lt;br /&gt;&lt;pre class="brush: sql"&gt;&lt;br /&gt;apex_emp_coll_p.apply_changes(p_coll_name =&gt; 'EMP_COLL',&lt;br /&gt;                              p_refresh   =&gt; 'Y');&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;See it in action&lt;br /&gt;&lt;/span&gt;I have made some sample pages to showcase the generator and using the generated code. See &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:8"&gt;http://apex.oracle.com/pls/otn/f?p=28990:8&lt;/a&gt; for the code generator, and &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:4"&gt;http://apex.oracle.com/pls/otn/f?p=28990:4&lt;/a&gt; for viewing the generated code in action.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Some notes on the generator&lt;/span&gt;&lt;br /&gt;It did not turn out as smooth as I had hoped. I would really have liked to include an "instead of"-trigger to the view, but this cannot be done without granting rights on WWV_FLOW_COLLECTIONS which I would rather not do. Even though the "instead of"-trigger uses the documented Apex API's, it does not change the "ORA-01031: insufficient privileges" from cropping up. This pretty much rules out the automatic DML process, which is a shame.&lt;br /&gt;&lt;br /&gt;When viewing the code in the generator itself, please remember that it has been evolving over time (and in my spare time too :-)), and the code reflects that fact.&lt;br /&gt;&lt;br /&gt;I have some plans for features to include in the future, but as of now I do not know what and in what order they will be implemented, so I will let it lie.&lt;br /&gt;&lt;br /&gt;Please leave a note if you find this useful or have any comments/questions, etc. :-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-6852328605475221614?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/6852328605475221614/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/06/generating-table-collection-api-for.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/6852328605475221614'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/6852328605475221614'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/06/generating-table-collection-api-for.html' title='Generating Table Collection API for Oracle Apex'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-8810215546387047294</id><published>2009-05-26T08:37:00.007+02:00</published><updated>2009-06-07T23:14:24.796+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='rtf'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>RTF Documents in an Oracle Database</title><content type='html'>In this article I will outline how to use RTF templates to create documents with dynamic content.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Why RTF?&lt;/span&gt;&lt;br /&gt;I'm lazy. RTF templates can easily be made of any user with some knowledge of MS Word, I don't have to lift a finger. Most users are comfortable with Word, and like the "freedom" it gives.&lt;br /&gt;&lt;br /&gt;It's a proprietary standard, yes, but it's also &lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=DD422B8D-FF06-4207-B476-6B5396A18A2B&amp;amp;displaylang=en"&gt;documented&lt;/a&gt;, that helps... It comes in different versions, with subtle differences.&lt;br /&gt;&lt;br /&gt;Is all rosy? No, there are pitfalls. In order to create templates with reasonable content and size, a structured approach i necessary. In a public application, I would not recommend RTF as the preferred format, but for an internal application in a controlled environment the approach can be quite effective.&lt;br /&gt;&lt;br /&gt;In the past, I have used RTF in various projects, and found that it is very easy to develop and maintain fairly complex reports.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Creating a template&lt;/span&gt;&lt;br /&gt;This example assumes working in M$ Word. First of all, create a document layout (with content) that you would like to use. When all is done, copy all content, click "Create new document" and choose "Empty document", paste all the content from the original document in one go and save as RTF.&lt;br /&gt;&lt;br /&gt;You now have a clean RTF document, if you want to, you can examine the result in your text-editor of choice. Some elements are more cryptic than others, but you should get the general idea. For a more thorough investigation (depending on your temper), you can create RTFs from scratch. There are a number of sources online, check out the three tutorials located &lt;a href="http://www.pindari.com/"&gt;here&lt;/a&gt;, or this &lt;a href="http://search.cpan.org/%7Esburke/RTF-Writer/lib/RTF/Cookbook.pod"&gt;cookbook&lt;/a&gt;. If you are a bit more lazy (but still won't use MS Word), there are a number of tools that can do the job for you, including OpenOffice. &lt;a href="http://en.wikipedia.org/wiki/Rich_Text_Format"&gt;Wiki&lt;/a&gt; is a good starting point, or use your favourite internet search engine...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Identifying dynamic elements&lt;/span&gt;&lt;br /&gt;Some parts of the document are clearly candidates for dynamic substitution, identify these and tag them with descriptive names, I use §¤mySubstitutionVariable¤§ with "§¤" and "¤§" surrounding a descriptive name.&lt;br /&gt;&lt;br /&gt;The substitution variable can contain any RTF-element, including tables and graphics. I concentrate on simple text strings in this article.&lt;br /&gt;&lt;br /&gt;When you are satisfied with your substitutions, create a RTF template as described above.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Simple test application&lt;/span&gt;&lt;br /&gt;Next you want to put the document in the database. I have a simple data model to support both RTF-templates and the substitution variables. It consists of three tables; one containing the RTF-template, one containing substitution variables and the last to bind them together.&lt;br /&gt;&lt;br /&gt;I have created a package called rtf_p which pretty much handles all the fun, you can see it in action and download the source database objects &lt;a href="http://apex.oracle.com/pls/otn/f?p=28990:1"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Remember that this is for demonstration purposes only, use it as you will, but I take no responsibility for what might happen when you do (your desktop melts, I achieve world domination, etc.).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Ready for production, or...?&lt;/span&gt;&lt;br /&gt;So, the algorithm did not satisfy you? Well, it was not supposed to :-) Next you would want to implement some common function basis; which customer am I working with, what order number, etc. Using Apex collections is an easy way of achieving this. Create a collection (perhaps called "report"), and create rows of common use like "CUST_ID" or whatever you need and populate it before creating the finished document. Use the collection in your queries. If there are some substitution variables that are "always" included in a document; fill them with one query to improve performance.&lt;br /&gt;&lt;br /&gt;The download part of rtf_p is not protected in my example (quite the contrary in fact), this you should rectify in a production environment; be sure the user is authenticated.&lt;br /&gt;&lt;br /&gt;Depending on the need (you never know which way the customer jumps...), you might implement functionality for storing the finished document, or substituting more advanced RTF objects (like tables). You should try hard (well, harder than I did) to assist the users for filling substitution variables and such in the application (or: do you think it's fun to write the advanced "I wan't the last name first!"-type of queries?).&lt;br /&gt;&lt;br /&gt;As always; the sky is the limit! &lt;span style="font-size:78%;"&gt;(...and time, money, social life, quality of coffee, mood, etc...)&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-8810215546387047294?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/8810215546387047294/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/05/rtf-documents-in-oracle-database.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/8810215546387047294'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/8810215546387047294'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/05/rtf-documents-in-oracle-database.html' title='RTF Documents in an Oracle Database'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-3273160219325579459</id><published>2009-05-24T22:57:00.006+02:00</published><updated>2009-06-07T23:15:23.793+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='apex'/><category scheme='http://www.blogger.com/atom/ns#' term='reports'/><category scheme='http://www.blogger.com/atom/ns#' term='oracle'/><title type='text'>Producing documents with Oracle Apex</title><content type='html'>While in a start-up of a project, I delved into the whole document administration and generation world from an Apex perspective. I'm not talking about the typical accounting reports, but more like formal letters and such to send to customers.&lt;br /&gt;&lt;br /&gt;My requirements list goes like this:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Produce nice looking documents with some advanced elements (graphics, fonts, tables, header/footer, etc.), preferably in PDF&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Easy to develop and maintain, both static and dynamic content&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Low or no license cost&lt;/li&gt;&lt;/ul&gt;First off, Oracle Apex does not produce PDFs on its own. If you want to use the native Apex functionality for producing reports in PDF, you have to connect it to a print server. You can however generate PDFs with tools that does not integrate as well with Apex, and the list of possible solutions increases.&lt;br /&gt;&lt;br /&gt;Here are the alternatives I dug out on short notice. Now, this is by no means an exhausting list, so feel free to guide me to other solutions.&lt;br /&gt;&lt;br /&gt;&lt;a style="font-weight: bold;" href="http://xmlgraphics.apache.org/fop/"&gt;Apache FOP&lt;/a&gt;: Open source XSL:FO implementation. Pure Java solution, can be used "as-is" with command line interface, or embedded in a servlet running in a Java container (eg. &lt;a href="http://tomcat.apache.org/"&gt;Tomcat&lt;/a&gt;). Requires skills of XSL:FO (or a &lt;a href="http://www.java4less.com/fopdesigner/fodesigner.php"&gt;commercial tool&lt;/a&gt; that produces this format) to customize output. Apex ships with pre-configured implementation for running in OC4J, so it's very easy to integrate.&lt;br /&gt;&lt;br /&gt;&lt;a style="font-weight: bold;" href="http://cocoon.apache.org/"&gt;Apache Cocoon&lt;/a&gt;: Spring-based open source Java framework that can do a lot more than producing PDF. If I understood correctly, it uses Apache fop libraries to generate PDF. Has to run in a Java container (eg. &lt;a href="http://tomcat.apache.org/"&gt;Tomcat&lt;/a&gt;).  Very easy to integrate with Apex, &lt;a href="http://carlback.blogspot.com/2007/03/apex-cocoon-pdf-and-more.html"&gt;Carl's&lt;/a&gt; (may he rest in peace) example proves the point. Requires skills of XSL:FO to customize output.&lt;br /&gt;&lt;br /&gt;&lt;a style="font-weight: bold;" href="http://www.plpdf.com/"&gt;PLPDF&lt;/a&gt;: A pure PL/SQL alternative, and thus requires PL/SQL skills to define documents. I have tested the api, and the amount of code required to produce a document is not overwhelming. I really miss a GUI for the tool though. Cheap license (per database). Keeping it in the database appeals to me, and it comes recommended from some prominent members of the Apex community. Easy (but not without programming) integration with Apex.&lt;br /&gt;&lt;br /&gt;&lt;a style="font-weight: bold;" href="http://jasperforge.org/plugins/project/project_home.php?group_id=102"&gt;JasperReports&lt;/a&gt;: Open source Java framework for report generation. Can be embedded in a servlet running in a Java container (yes, yes, &lt;a href="http://tomcat.apache.org/"&gt;Tomcat&lt;/a&gt; again...). Requires adaption of both JR and Apex for integration. There are some examples of integrating the two on forums.oracle.com. As opposed to the Apache fop-based tools which integrate quite easely into Apex, both the queries and layout have to be configured in JR. Customizing and layout of reports are supposedly quite easy with &lt;a href="http://jasperforge.org/plugins/project/project_home.php?group_id=83"&gt;iReport&lt;/a&gt;. I have seen som fairly exotic documents generated with this tool, so advanced layout is no show-stopper. JR will require skills in a separate product from Apex. &lt;a style="font-weight: bold;" href="http://sourceforge.net/project/showfiles.php?group_id=234934"&gt;ReportChunker&lt;/a&gt; (based on JasperReports) comes pre-packed as a war, this might be an easy way of integrating, but I have no hands-on experience with it.&lt;br /&gt;&lt;br /&gt;&lt;a style="font-weight: bold;" href="http://www.oracle.com/technology/products/xml-publisher/index.html"&gt;Oracle BI Publisher&lt;/a&gt;: King of document design and Apex integration. If you already have a license for Oracle EBS/BI Publisher, or are going to produce painfully many advanced reports (and have an arm and a leg to spare), this is the way to go. It is really, really not cheap, but depending on how many hours you plan spending on developing documents and customizing them, I imagine there must be a break-even limit somewhere. I imagine... Requires skills in a separate product from Apex.&lt;br /&gt;&lt;br /&gt;Any more out there? Of course there are, but as the project headed for RTF, I did not dig any further.&lt;br /&gt;&lt;br /&gt;The monkey-side of me is still baffled by the fact that there still are no point-and-click solution for this, coding is so much, well, work... And quite a bit of fun ;-)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-3273160219325579459?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/3273160219325579459/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/05/producing-documents-with-oracle-apex.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3273160219325579459'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3273160219325579459'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/05/producing-documents-with-oracle-apex.html' title='Producing documents with Oracle Apex'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8575920265455783910.post-3993401193221251368</id><published>2009-05-24T22:46:00.006+02:00</published><updated>2009-06-07T23:16:05.228+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='off topic'/><title type='text'>Monkey see, monkey do</title><content type='html'>So, what could possibly possess a "normal" person to start a blog at this time? Everyone knows there are far more blogs than readers out there.&lt;br /&gt;&lt;br /&gt;I think the reason is simply that I am a monkey! Well, at least a biped of sorts. You have to hand it to the monkeys, they have no restrictions what so ever about "copying" other peoples (or bipeds, whatever) work. In that sense, I am a monkey (probably more than just that sense, but I won't go there now). I sift through the internet to find an example matching my current task. I am lazy, I am good at searching the net, and I am good at adopting code. At least good enough to make a living of it.&lt;br /&gt;&lt;br /&gt;And the blog? It's time for this monkey to give something back. Even if people don't read blogs like they used to (everybody busy writing their own and all), the text becomes public and just a few keystrokes away in your favorite search engine. Even my own work for myself, which is not such a stupid idea for someone like me.&lt;br /&gt;&lt;br /&gt;What really prompted me to do this was this thing called Oracle Application Express (Apex). Working with the product (which is great!) proved significantly more easy than I am used to. Why? The community surrounding the product! Let's face it, Apex does not get the support from sales that it deserves, so the community surrounding it has risen to the occasion. This is what we expect to see from the Java community, but seldom see in the more closed world of Oracle (even if that has changed these past few years). I like the "make each other better"-approach!&lt;br /&gt;&lt;br /&gt;So, this blog will ramble on about this Apex thingy and technology surrounding it.&lt;br /&gt;&lt;br /&gt;Think you could learn something from a monkey? Happy reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8575920265455783910-3993401193221251368?l=monkeyonoracle.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://monkeyonoracle.blogspot.com/feeds/3993401193221251368/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/05/monkey-see-monkey-do.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3993401193221251368'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8575920265455783910/posts/default/3993401193221251368'/><link rel='alternate' type='text/html' href='http://monkeyonoracle.blogspot.com/2009/05/monkey-see-monkey-do.html' title='Monkey see, monkey do'/><author><name>Håvard Kristiansen</name><uri>http://www.blogger.com/profile/13932482145147467490</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_8GtRvOur8pQ/S0XwES-MewI/AAAAAAAAABc/5Ez0Fhw7XhY/S220/DSC00174t.jpg'/></author><thr:total>2</thr:total></entry></feed>
