tag:blogger.com,1999:blog-205670722024-03-13T22:30:01.763+01:00Roels BlogAnonymoushttp://www.blogger.com/profile/03932233704705635918noreply@blogger.comBlogger354125tag:blogger.com,1999:blog-20567072.post-29282183138556405112023-05-22T09:34:00.000+02:002023-05-22T09:34:23.114+02:00APEX (Multi) Application SettingsMost applications need some sort of "configuration" table. A simple key-value store where you can put values that might change on a different environment, like URLs, minimal password length, API keys, etc. And very conveniently APEX has this implemented as "<b><i>Application Settings</i></b>": A simple table containing a key and a value - and a few other properties like "valid values". In your code you can reference these values using either the <span style="font-family: courier;">apex_application_settings</span> view - that contains all Application Settings in your Workspace - or the <span style="font-family: courier;">apex_app_setting</span> API - that you can use to get or set a value based on the Application Settings Name.<div><br /></div><div>That's all great. Until I ran into this situation: Our "application" consists of 2 APEX applications, one Internal (INT), backoffice kind of application and one external (EXT) facing one. And now I needed to set the Application Setting of the EXT application from the INT application. While I can see the current values of the EXT Application Settings - as the <span style="font-family: courier;">apex_application_settings </span>view shows me all settings in my Workspace, I cannot simply set it using the <span style="font-family: courier;">apex_app_setting </span>API as "<span face=""Oracle Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif" style="background-color: #fcfbfa; color: #1a1816; font-size: 16px; orphans: 2; widows: 2;"><i>This procedure changes the application setting value in the <b><u>current</u></b> application</i>". So how can I use that to set that API to set the value in <u><b>another</b></u> application?</span></div><div><span style="background-color: #fcfbfa; orphans: 2; widows: 2;"><span face="Oracle Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Arial, sans-serif" style="color: #1a1816;">My first idea was to create a Form Page with a Process calling that API in the EXT app and just link to that Form Page from my INT app. That could have worked if both applications would have used the same Authentication cookie. Which of course they didn't!</span></span></div><div><span style="background-color: #fcfbfa; orphans: 2; widows: 2;"><span face="Oracle Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Arial, sans-serif" style="color: #1a1816;"><br /></span></span></div><div><span style="background-color: #fcfbfa; orphans: 2; widows: 2;"><span face="Oracle Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Arial, sans-serif" style="color: #1a1816;">So as the API can only set a value in the <b><i>current</i></b> application .... I had to switch the <i>current</i> application during a Form process that does the update. And there the <span style="font-family: courier;">apex_session</span> package comes to the rescue!</span></span></div><div><span style="background-color: #fcfbfa; orphans: 2; widows: 2;"><span face="Oracle Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Arial, sans-serif" style="color: #1a1816;">So upon update, when the Application Setting is not a setting of the current application, I have to detach from the current (APEX) session and create a new session for the other (EXT) application. Then I can set the value using the <span style="font-family: courier;">apex_app_setting</span> API. Once done, I can delete that APEX session and attach back to my original session. So the next processes in my APEX page (like "Close Dialog") will still work. </span></span></div><div><span style="background-color: #fcfbfa; orphans: 2; widows: 2;"><span face="Oracle Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica Neue, Arial, sans-serif" style="color: #1a1816;"><br /></span></span></div><pre name="code">begin <br /> if :P235_APPLICATION_ID = :APP_ID then <br /> apex_app_setting.set_value( :P235_NAME, :P235_VALUE );<br /> else<br /> -- Switch to other / new session in the other App<br /> apex_session.detach;<br /> apex_session.create_session (p_app_id => :P235_APPLICATION_ID, p_page_id => 0, p_username => :APP_USER);<br /> apex_app_setting.set_value( p_name => :P235_NAME, p_value => :P235_VALUE );<br /> apex_session.delete_session;<br /> -- Return to the original session<br /> apex_session.attach( p_app_id => :APP_ID, p_page_id => :APP_PAGE_ID, p_session_id => :APP_SESSiON );<br /> end if; <br />end;</pre><div><br /></div><div>Simple. Elegant. And just using the APEX API's! </div><span face="-webkit-standard"></span>Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-52115383736450732142021-02-07T15:25:00.000+01:002021-02-07T15:25:10.010+01:00Stop using validations for checking constraints !<p> If you run your APEX application - like a Form based on the EMP table - and test if you can change the value of Department to something else then the standard values of 10, 20, 30 or 40, you'll get a nice error message like this:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-EXlXLJELSq8/YB1A3IbOj6I/AAAAAAAAE0g/A3wQnxyXrxwaWHmrLIZ41ej9QoIQ_zdpACLcBGAsYHQ/s1418/Screenshot%2B2021-02-05%2Bat%2B13.56.41%2B.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="308" data-original-width="1418" height="141" src="https://1.bp.blogspot.com/-EXlXLJELSq8/YB1A3IbOj6I/AAAAAAAAE0g/A3wQnxyXrxwaWHmrLIZ41ej9QoIQ_zdpACLcBGAsYHQ/w640-h141/Screenshot%2B2021-02-05%2Bat%2B13.56.41%2B.png" width="640" /></a></div><p>But it isn't really nice, is it? So what do a lot of developers do? They create a validation (just) in order to show a nicer, better worded, error message like "This is not a valid department". And what you then just did is writing code twice : Once in the database as a (foreign key) check constraint and once as a sql statement in your validation. And we all know : writing code twice is usually not a good idea - and executing the same query twice is not enhancing your performance!</p><p>So how can we transform that ugly error message into something nice? By combining two APEX features: the Error Handling Function and the Text Messages!</p><p>Start with copying the <a href="https://docs.oracle.com/en/database/oracle/application-express/20.2/aeapi/Example-of-an-Error-Handling-Function.html#GUID-2CD75881-1A59-4787-B04B-9AAEC14E1A82" target="_blank">example of an Error Handling Function</a> from the APEX documentation. Create this function in your database schema and define this as the Error Handling Function for your application:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-cqTl6ON5SHU/YB1HlWNN3QI/AAAAAAAAE0s/vfGDFhp0dq0TdHuKukROAGtNct6hMC-GgCLcBGAsYHQ/s1884/Screenshot%2B2021-02-05%2Bat%2B14.26.03%2B.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="642" data-original-width="1884" height="218" src="https://1.bp.blogspot.com/-cqTl6ON5SHU/YB1HlWNN3QI/AAAAAAAAE0s/vfGDFhp0dq0TdHuKukROAGtNct6hMC-GgCLcBGAsYHQ/w640-h218/Screenshot%2B2021-02-05%2Bat%2B14.26.03%2B.png" width="640" /></a></div><div><br /></div><div>When you put this in place, the message gets a bit better - the ORA-02291 is removed - but still not that you would like to show to your customer.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-SNe7fS8OiF8/YB1ISeE7VdI/AAAAAAAAE00/U6z-KE3F_sIa0uOGWzTIVMmflhOwPLE-ACLcBGAsYHQ/s1410/Screenshot%2B2021-02-05%2Bat%2B14.29.23%2B.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="318" data-original-width="1410" height="144" src="https://1.bp.blogspot.com/-SNe7fS8OiF8/YB1ISeE7VdI/AAAAAAAAE00/U6z-KE3F_sIa0uOGWzTIVMmflhOwPLE-ACLcBGAsYHQ/w640-h144/Screenshot%2B2021-02-05%2Bat%2B14.29.23%2B.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">Now lets add a few lines to that Error Handling Function in the part that handles the contraint violations. The original code is this:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div><div><span style="font-family: courier; font-size: x-small;">if p_error.ora_sqlcode in (-1, -2091, -2290, -2291, -2292) then</span></div><div><span style="font-family: courier; font-size: x-small;"> l_constraint_name := apex_error.extract_constraint_name (</span></div><div><span style="font-family: courier; font-size: x-small;"> p_error => p_error );</span></div><div><span style="font-family: courier; font-size: small;"> begin</span></div><div><span style="font-family: courier; font-size: x-small;"> select message</span></div><div><span style="font-family: courier; font-size: x-small;"> into l_result.message</span></div><div><span style="font-family: courier; font-size: x-small;"> from constraint_lookup</span></div><div><span style="font-family: courier; font-size: x-small;"> where constraint_name = l_constraint_name;</span></div><div><span style="font-family: courier; font-size: x-small;"> exception when no_data_found </span></div><div><span style="font-family: courier; font-size: x-small;"> then null; -- not every constraint has to be in our lookup table</span></div><div><span style="font-family: courier; font-size: x-small;"> end;</span></div><div><span style="font-family: courier; font-size: small;">end if;</span></div></div><div><br /></div><div>Now change the select from the constraint_lookup table (which we don't have anyway) into:</div><div><br /></div><div><span style="font-family: courier; font-size: x-small;">if p_error.ora_sqlcode in (-1, -2091, -2290, -2291, -2292) then</span></div><div><span style="font-family: courier; font-size: x-small;"> l_constraint_name := apex_error.extract_constraint_name (</span></div><div><span style="font-family: courier; font-size: x-small;"> p_error => p_error );</span></div><div><span style="font-family: courier; font-size: small;"> l_result.message := apex_lang.message( l_constraint_name );</span></div><div><span style="font-family: courier; font-size: x-small;"> if l_result.message = l_constraint_name then</span></div><div><span style="font-family: courier; font-size: x-small;"> apex_lang.create_message( p_application_id => v('APP_ID')</span></div><div><span style="font-family: courier; font-size: x-small;"> , p_name => l_constraint_name</span></div><div><span style="font-family: courier; font-size: x-small;"> , p_language => apex_util.get_preference</span></div><div><span style="font-family: courier; font-size: x-small;"> ('FSP_LANGUAGE_PREFERENCE')</span></div><div><span style="font-family: courier; font-size: x-small;"> , p_message_text => 'TODO'</span></div><div><span style="font-family: courier; font-size: x-small;"> );</span></div><div><span style="font-family: courier; font-size: x-small;"> commit;</span></div><div><span style="font-family: courier; font-size: x-small;"> end if;</span></div><div><span style="font-family: courier; font-size: small;">end if;</span></div><div><br /></div><div>So the first step is to "translate" the message using the constraint name ( e.g. EMP_DEPT_FK) as a parameter. If that value is not found the apex_lang.message will simply return the parameter value. So if that happens, the procedure apex_lang.create_message is called to create a Text Message entry with a default text "TODO'.</div><div>When we run our test case again, the first time we get an error that is only showing the constraint name as a message.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-HAWilvnVooI/YB_0-W1j1JI/AAAAAAAAE1M/-0O__fpqQ_sZWOtqae_I17wJBQAfw-wCACLcBGAsYHQ/s1406/Screenshot%2B2021-02-07%2Bat%2B15.10.25%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="278" data-original-width="1406" height="126" src="https://1.bp.blogspot.com/-HAWilvnVooI/YB_0-W1j1JI/AAAAAAAAE1M/-0O__fpqQ_sZWOtqae_I17wJBQAfw-wCACLcBGAsYHQ/w640-h126/Screenshot%2B2021-02-07%2Bat%2B15.10.25%2B.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div>If we immediately press the Save button again, we get a different message.<div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-Rr-q3qLUKEs/YB_1bbAa5HI/AAAAAAAAE1U/YB5cDGhzuaULC5wh5GoZ0oJPfdmHmcC1gCLcBGAsYHQ/s1402/Screenshot%2B2021-02-07%2Bat%2B15.10.36%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="270" data-original-width="1402" height="124" src="https://1.bp.blogspot.com/-Rr-q3qLUKEs/YB_1bbAa5HI/AAAAAAAAE1U/YB5cDGhzuaULC5wh5GoZ0oJPfdmHmcC1gCLcBGAsYHQ/w640-h124/Screenshot%2B2021-02-07%2Bat%2B15.10.36%2B.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: left;">Now head over to the APEX Designer, Shared Components, Text Messages and look for the messages you still need to change:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-r6mzjo5uzRU/YB_2suu7KpI/AAAAAAAAE1g/NxfM7n9ZCA0upUQf6-6dpcNI9s5uCaKmQCLcBGAsYHQ/s1134/Screenshot%2B2021-02-07%2Bat%2B15.16.46%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="546" data-original-width="1134" height="308" src="https://1.bp.blogspot.com/-r6mzjo5uzRU/YB_2suu7KpI/AAAAAAAAE1g/NxfM7n9ZCA0upUQf6-6dpcNI9s5uCaKmQCLcBGAsYHQ/w640-h308/Screenshot%2B2021-02-07%2Bat%2B15.16.46%2B.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: left;">And after adding a meaningful message, run your test again.</div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-Oxm60Vds8TI/YB_3AZ3LmBI/AAAAAAAAE1o/fHPZm2Qeos0bZvb5OG9mLIkD7SSfva_8gCLcBGAsYHQ/s1420/Screenshot%2B2021-02-07%2Bat%2B15.17.53%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="276" data-original-width="1420" height="124" src="https://1.bp.blogspot.com/-Oxm60Vds8TI/YB_3AZ3LmBI/AAAAAAAAE1o/fHPZm2Qeos0bZvb5OG9mLIkD7SSfva_8gCLcBGAsYHQ/w640-h124/Screenshot%2B2021-02-07%2Bat%2B15.17.53%2B.png" width="640" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: left;">So without writing any additional validation code on your pages, you can show a nice, user-friendly error message by just using the power of APEX and the Oracle Database.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Of course this is just a simple example of what you can do with an Error Handling Function: just to name an example or two, you can also send an email from that function or do a REST call from one database to another.</div><br /><div class="separator" style="clear: both; text-align: left;"><br /></div><br /><div class="separator" style="clear: both; text-align: left;"><br /></div><br /><div><br /><div><br /></div></div>Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0Deventer, Netherlands52.2660751 6.155216523.955841263821156 -29.0010335 80.576308936178847 41.3114665tag:blogger.com,1999:blog-20567072.post-27128529378690555562020-05-10T17:37:00.001+02:002020-05-10T17:37:32.380+02:00Using Static Files in APEX has never been easier !Almost every APEX Developer knows that JavaScript and CSS belongs in separate files and (in 99% of the use cases) not somewhere in your Page properties. If you have this code in separate files it is easier to use them in a Version Control System (SVN or Git) - if the files are outside of APEX. And the code you store in these files can be reused, in contrast to the stuff you store on Page level.<div>In a lot of environments it is harder to use external files, because deployment of these files to the appropriate location on a webserver requires special privileges that not everybody has. In those cases storing these files as Static Application Files or Static Workspace Files might be a better solution.</div><div>It makes deployment easier, because these files will be exported and imported as part of the application. You can't accidentally forget about them. But working with these files is quite a pain. When you need to edit something you have to download that file, make the changes, upload it again and refresh the page to see the result (either positive or negative). One tool that overcomes that repeated cycle in <a href="https://github.com/OraOpenSource/apex-nitro" target="_blank">APEX Nitro</a>. Nitro uses JavaScript (node) behind to seems to facilitate the upload / refresh part of the development cycle. Pretty cool. But it is another tool that runs outside your browser and it requires the installation of node. No big deal but still.</div><div><br /></div><div>Just recently I stumbled upon a tweet of the guys from <a href="https://www.foex.at/home/" target="_blank">FOEX</a> telling about a new initiative <a href="https://github.com/foex-open-source/apex-builder-extension-by-fos" target="_blank">FOEX Open Source (a.k.a. FOS)</a>. And they started this initiative by releasing a browser extension - for Firefox and Chrome - that facilities editing of Static JavaScript and CSS Files straight in the browser. Big advantage: No more uploading, changing, uploading cycle anymore. Just change, save and refresh!</div><div style="text-align: center;"><a href="https://1.bp.blogspot.com/-zkbhT_QN40g/XrgUmfEPBtI/AAAAAAAAEpc/W1L88W_Wzyg8kxZr6B6WPvLUD7OfCnqQwCK4BGAsYHg/Screenshot%2B2020-05-10%2Bat%2B11.21.27%2B.png"><img border="0" data-original-height="592" data-original-width="1058" height="358" src="https://1.bp.blogspot.com/-zkbhT_QN40g/XrgUmfEPBtI/AAAAAAAAEpc/W1L88W_Wzyg8kxZr6B6WPvLUD7OfCnqQwCK4BGAsYHg/w640-h358/Screenshot%2B2020-05-10%2Bat%2B11.21.27%2B.png" style="margin-right: 10px;" width="640" /></a><br /></div><div>When you install the browser extension, a little Select List appears in the page where you can see your Static Application (or Workspace) Files. You can just select one of your files there and a built-in editor appears with all the nice Visual Studio Code features we all know and love.</div><div>So you have syntax highlighting and can edit multiple files at the same time in different tabs - either behind each other or next to each other (left to right or top to bottom).</div><div><div>And it's not just that you van Edit and Save the file, there is also a "Minify" option. So you can even do that directly from with APEX! That is really cool.</div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-91MlArOeJsA/XrgWCFYwmuI/AAAAAAAAEqU/xK3H-w_jHfAO_Yc3Ej6hw0kApyNI7mR4wCK4BGAsYHg/Screenshot%2B2020-05-10%2Bat%2B11.22.57%2B.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1082" data-original-width="1438" height="482" src="https://1.bp.blogspot.com/-91MlArOeJsA/XrgWCFYwmuI/AAAAAAAAEqU/xK3H-w_jHfAO_Yc3Ej6hw0kApyNI7mR4wCK4BGAsYHg/w640-h482/Screenshot%2B2020-05-10%2Bat%2B11.22.57%2B.png" width="640" /></a>
</div><div class="separator" style="clear: both; text-align: center;"><div style="text-align: left;">And to top that all off, you can also create (or edit) Less files and compile these to CSS (and minify them) in one go. </div></div><div style="text-align: center;"><img border="0" data-original-height="1088" data-original-width="1424" height="488" src="https://1.bp.blogspot.com/-mBrDl1zITgo/XrgWOx6puiI/AAAAAAAAEqw/bVi9TmjiWKwswzyoghvUIRnJd6s8K9-GQCK4BGAsYHg/w640-h488/Screenshot%2B2020-05-10%2Bat%2B11.26.03%2B.png" style="margin-left: 10px; text-align: left;" width="640" /></div><div style="text-align: left;"><span style="text-align: left;">So no more excuses for not using files anymore! It will never get easier than this ....</span></div><div><div style="text-align: left;">You can download the extension from the webstores for <a href="https://chrome.google.com/webstore/detail/apex-builder-extension-by/jhmmfmhnhnnfnejfphieclbibmoaapid" target="_blank">Chrome</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/apex-builder-extension-by-fos/" target="_blank">Firefox</a>.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div style="text-align: center;"><br /></div><div style="text-align: center;"><br /></div></div>Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com1tag:blogger.com,1999:blog-20567072.post-18047317831636661902019-03-12T15:48:00.001+01:002019-03-12T15:48:13.798+01:00Filtering in the APEX Interactive GridRemember Oracle Forms?<br />
<br />
One of the nice features of Forms was the use of GLOBAL items. More or less comparable to Application Items in APEX. These GLOBALS where often used to pre-query data. For example you queried Employee 200 in Form A, then opened Form B and on opening that Form the Employee field is filled with that (GLOBAL) value of 200 and the query was executed. So without additional keys strokes or entering data, when switching to another Form a user would immediately see the data in the same context. And they loved that.<br />
<br />
In APEX you can create a similar experience using Application Items (or an Item on the Global Page) for Classic Reports (by setting a Default Value to a Search Item) and Interactive Reports (using the <a href="https://docs.oracle.com/en/database/oracle/application-express/18.2/aeapi/ADD_FILTER-Procedure-Signature-1.html#GUID-3BEF6280-0BEE-47B2-9067-B607E0D822A0" target="_blank">APEX_IR.ADD_FILTER</a> procedure). But what about the Interactive Grid? There is no APEX_IG package ... so the first thing we have to figure out is how can we set a filter programmatically?<br />
<br />
Start with creating an Interactive Grid based upon the good old Employees table - and add a Static ID to the Grid region : "emp". We will use the "<i>addFilter</i>" method to do the trick.<br />
So, run the Page, open the Developer Toolbar of your browser and type into the console:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">apex.region("emp").call("addFilter", </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> { type : "column" </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> , columnType : "column" </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> , columnName : "LAST_NAME" </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> , operator : "EQ" </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> , value : "King" </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> }); </span><br />
<br />
This will add a filter to your report.<br />
Now you can add this snippet of JavaScript to a Dynamic Action that runs on Page Load. Refresh the Page and the filter is automagically applied. Woohoo. Refresh the Page again and the filter is applied. Twice. Bummer.<br />
So before we apply a filter on a column, we need to delete existing filters on that column first. There is another method "<i>deleteFilter</i>", but instead of a columnName, that needs a columnId as a parameter. So how do we get the colunnId? A third method comes to the rescue : "<i>getFilters</i>".<br />
Calling <span style="font-family: "courier new" , "courier" , monospace;">apex.region("emp").call("getFilters")</span>will return an array of filter objects. Alas columnName is not one of the elements. Thus we have to figure out what the ID is of the column we're using in our filter (<span style="font-family: "courier new" , "courier" , monospace;">LAST_NAME</span> in our example). For that information we have to dig into the model:<br />
<span style="font-family: "courier new" , "courier" , monospace;">apex.region("emp").call("getViews").grid.modelColumns["LAST_NAME"].id;</span><span style="font-family: "courier new" , "courier" , monospace;"> </span>returns that id (eg 34282803428196769095).<br />
We can use this Id to loop through our filter objects and delete the filter that matches that Id:<br />
<span style="font-family: "courier new" , "courier" , monospace;">var vRegion = </span><span style="font-family: "courier new" , "courier" , monospace;">apex.region("emp");</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">var vFilters = </span><span style="font-family: "courier new" , "courier" , monospace;">vRegion</span><span style="font-family: "courier new" , "courier" , monospace;">.call( "getFilters" );</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">var vFilterColId = vRegion.call("getViews").grid.modelColumns["LAST_NAME"].id;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">for ( var i in vFilters ){</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> if ( vFilters[i].columnId == vFilterColId ){</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> vRegion.call( "deleteFilter", vFilters[i].id );</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> } </span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> } </span><span style="font-family: "courier new" , "courier" , monospace;"> </span><br />
You can see the addFilter and deleteFilter in action <a href="https://apex.oracle.com/pls/apex/f?p=131155:5" target="_blank">here on apex.oracle.com</a>.<br />
<br />
Now you can use this code to fire an "autoFilter" when a certain Item (on Page 0) has a value and to delete a filter when that Item has no value. You have to make sure to set that Item to a value whenever it makes sense. For instance when opening a detail page, after querying or clicking on a button or link. You also have to create a way to unset that Item ... most likely when clicking on the "delete Filter" icon (that has a jQuery selector of "<i>button.a-IG-button--remove</i>").<br />
<br />
There's a little plugin (search for manageFilter on apex.world, or <a href="https://github.com/APEXGru/manageFilter">go to GitHub</a>) that can contains this code and can help you started using this stuff.<br />
<br />
But be aware ... the methods mentioned above and that are used in the plugin are <b>NOT DOCUMENTED</b>. Looking in the widget.interactiveGrid.js file you will see :<br />
<div style="background-color: #0c1021; color: #f8f8f8; font-family: menlo, monaco, "courier new", monospace; font-size: 14px; line-height: 21px; white-space: pre;">
<div>
<span style="color: #aeaeae;">/*** FOR INTERNAL USE ONLY: Subject to change in the future, please do not use! ***/</span></div>
<br /></div>
So although it might work now, there is zero guarantee it will work after your next upgrade!Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com1tag:blogger.com,1999:blog-20567072.post-54383862504476088622019-02-18T21:03:00.000+01:002019-02-19T08:33:08.333+01:00Caution when using Suppress Change Event on an Interactive Grid Column<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-2BQyRTeiMcM/XGqlNh-z6hI/AAAAAAAAEbM/7-s4iJi0lhosz2W_y1HlpK8tYqF2Jr0pwCLcBGAs/s1600/Screenshot%2B2019-02-18%2Bat%2B13.28.05%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="112" data-original-width="418" height="106" src="https://1.bp.blogspot.com/-2BQyRTeiMcM/XGqlNh-z6hI/AAAAAAAAEbM/7-s4iJi0lhosz2W_y1HlpK8tYqF2Jr0pwCLcBGAs/s400/Screenshot%2B2019-02-18%2Bat%2B13.28.05%2B.png" width="400" /></a></div>
There are some, maybe rare, use case when you want to set the "<i>Suppress Change Event</i>" switch of a Dynamic Action to "<i>Yes</i>". Usually that's only necessary when the Dynamic Action is fired <i>On Change</i> and executes a <i>Set Value</i> with the same field as a target. There might be a chain of events leading to this or just one. If you forget to set it you'll end up in an endless loop and your browser will choke.<br />
That works all fine and as expected with regular Page Items.<br />
<br />
However ... when you apply this to a column in an Interactive Grid something unexpected happens. When you set the <i>Suppress Change Event</i> of a Dynamic Action that fires on change of a column to <i>Yes</i>, the value of that item seems empty when you leave that field (column)! If you navigate back into that field, the (new) value does show up...<br />
<br />
You can call it a feature, you can call it a bug, but bottom line it is unexpected and undesired behaviour. The solution is to call the Grid method <i>SetActiveRecordValue</i> after you set the value.<br />
Thus, next to the <i>Set Value </i>step, you need an <i>Execute JavaScript</i> step with code like this:<br />
<br />
<div style="background-color: #0c1021; font-family: menlo, monaco, "courier new", monospace; font-size: 14px; line-height: 21px; white-space: pre;">
<div style="font-family: Menlo, Monaco, "Courier New", monospace; line-height: 21px;">
<div style="color: #f8f8f8;">
<span style="color: #fbde2d;">var</span> grid$ <span style="color: #fbde2d;">=</span> apex.<span style="color: #ff6400;">region</span>( <span style="color: #61ce3c;">"myGrid"</span> ).<span style="color: #ff6400;">call</span>( <span style="color: #61ce3c;">"getCurrentView"</span> ).view$;</div>
<div style="color: #f8f8f8;">
grid$.<span style="color: #ff6400;">grid</span>(<span style="color: #61ce3c;">"lockActive"</span>);</div>
<div style="color: #f8f8f8;">
<span style="color: #8da6ce;">setTimeout</span>( <span style="color: #fbde2d;">function</span>() {</div>
<div style="color: #f8f8f8;">
<span style="color: #aeaeae;">// the grid model is not updated so call setActiveRecordValue</span></div>
<div>
<span style="color: #aeaeae;"><span style="caret-color: rgb(174, 174, 174);"> </span></span><span style="color: #f8f8f8;"> grid$.</span><span style="color: #ff6400;">grid</span><span style="color: #f8f8f8;">( </span><span style="color: #61ce3c;">"setActiveRecordValue"</span><span style="color: #f8f8f8;">, </span><span style="color: #61ce3c;">"SAL"</span><span style="color: #f8f8f8;"> )</span></div>
<div style="color: #f8f8f8;">
.<span style="color: #ff6400;">grid</span>( <span style="color: #61ce3c;">"unlockActive"</span> );</div>
<div style="color: #f8f8f8;">
}, <span style="color: #d8fa3c;">100</span> );</div>
</div>
</div>
<br />
Of course you should not code this JavaScript directly into the Dynamic Action but create either a JavaScript function that accepts two parameters (region and column) or - in my opinion the preferred solution - create a (simple) plugin (with two attributes) that executes this piece of JavaScript.<br />
<br />
<i><span style="font-size: x-small;">(This is the behaviour currently on APEX 18.2)</span></i>Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-82448127200931167752018-12-18T13:35:00.001+01:002018-12-18T14:45:02.711+01:00Adding items to your Interactive Grid Toolbar<style>code { font-size : 14px; }</style>
The APEX Interactive Grid uses the Toolbar widget to create the default Toolbar showing the Search box, Actions menu, Save button etc. And since quite a while there is a nice Plugin "<i><a href="https://github.com/mgoricki/apex-plugin-extend-ig-toolbar" target="_blank">Extend IG Toolbar</a></i>" by Marko Goricki that makes it very easy to add additional buttons to the Toolbar.<span class="fullpost"></span><br />
<div>
<br /></div>
<div>
But what if you need more than a button? </div>
<div>
<br /></div>
<div>
Inspecting the contents of widget.toolbar.js, you can easily spot there can be added more to the Toolbar than just a button:</div>
<div>
<div style="background-color: #0c1021; color: #f8f8f8; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height: 21px; white-space: pre;">
<div>
<span style="color: #aeaeae;">The type of control, available values: </span><br />
<span style="color: #aeaeae;">"STATIC", "TEXT", "SELECT", "BUTTON", "MENU", "RADIO_GROUP", "TOGGLE".</span></div>
</div>
<br />
The first example will show a way to easily switch from one filter to another. Of course we could use the standard functionality and create two different Report views, but using a Radio Group on the Toolbar gives a more "Tab" like user experience.<br />
<br />
So how can we create a Radio Group that looks like a switch in the Toolbar?<br />
In the Javascript Code property of the Interactive Grid add the code below:<br />
<br />
<code>
function (options) {<br />
var tgc = options.toolbarData.toolbarFind("actions3").controls;<br />
// Add a Radio Group to the Toolbar<br />
// and define the associated action (that does the actual work)<br />
tgc.push(<br />
{<br />
type: "RADIO_GROUP"<br />
, action: "set-source"<br />
, id: "P855_SOURCE_SWITCH"<br />
});<br />
// Define the action : The options and what to do on change<br />
options.initActions = function (actions) {<br />
actions.add(<br />
{<br />
name: "set-source"<br />
, choices: [<br />
{ label: "JOP", value: "J" }<br />
, { label: "Retouren", value: "R" }<br />
]<br />
, action: function (evt, elm) { // Show the current selection<br />
$("input:radio[name=report_ig_toolbar_set-source]").removeClass("current");<br />
$("input:radio[name=report_ig_toolbar_set-source][value=" + elm.value + "]").addClass("current");<br />
// Set the value of the Page Item to the selected value<br />
$s("P855_SOURCE", elm.value);<br />
}<br />
});<br />
};<br />
return options;<br />
}<br />
</code>
<br />
And in our IG query we would reference P855_SOURCE (and use it in Page Items to Submit).<br />
It will look like this (and work like a charm):<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-WRIm27QscS4/XBjZph7E57I/AAAAAAAAEZU/_TLEnJj81QoTl-uuXYW96X0efVWKEMVFQCLcBGAs/s1600/radiogroup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="70" data-original-width="584" height="75" src="https://2.bp.blogspot.com/-WRIm27QscS4/XBjZph7E57I/AAAAAAAAEZU/_TLEnJj81QoTl-uuXYW96X0efVWKEMVFQCLcBGAs/s640/radiogroup.png" width="640" /></a></div>
<br />
Likewise you can add a Select list with a label:</div>
<br />
<code>
<span style="font-family: monospace;">function (options) {</span><br />
<span style="font-family: monospace;"> var tgc = options.toolbarData.toolbarFind("actions3").controls;</span><br />
<span style="font-family: monospace;"> // Add Static text (as a label)</span><br />
<span style="font-family: monospace;"> tgc.push({</span><br />
<span style="font-family: monospace;"> type: "STATIC"</span><br />
<span style="font-family: monospace;"> , label: "Aantal maanden"</span><br />
<span style="font-family: monospace;"> });</span><br />
<span style="font-family: monospace;"> // Add Select list</span><br />
<span style="font-family: monospace;"> tgc.push({</span><br />
<span style="font-family: monospace;"> type: "SELECT"</span><br />
<span style="font-family: monospace;"> , action: "set-months"</span><br />
<span style="font-family: monospace;"> , id: "P852_TOOLBAR_MONTHS"</span><br />
<span style="font-family: monospace;"> });</span><br />
<span style="font-family: monospace;"> // Add the options and the actual action§ </span><br />
<span style="font-family: monospace;"> options.initActions = function (actions) {</span><br />
<span style="font-family: monospace;"> actions.add({</span><br />
<span style="font-family: monospace;"> name: "set-months",</span><br />
<span style="font-family: monospace;"> choices: [{ label: "1", value: "1" }</span><br />
<span style="font-family: monospace;"> , { label: "2", value: "2" }</span><br />
<span style="font-family: monospace;"> , { label: "3", value: "3" }</span><br />
<span style="font-family: monospace;"> , { label: "4", value: "4" }</span><br />
<span style="font-family: monospace;"> , { label: "5", value: "5" }</span><br />
<span style="font-family: monospace;"> , { label: "6", value: "6" }</span><br />
<span style="font-family: monospace;"> , { label: "7", value: "7" }</span><br />
<span style="font-family: monospace;"> , { label: "8", value: "8" }</span><br />
<span style="font-family: monospace;"> , { label: "9", value: "9" }</span><br />
<span style="font-family: monospace;"> , { label: "10", value: "10" }</span><br />
<span style="font-family: monospace;"> , { label: "11", value: "11" }</span><br />
<span style="font-family: monospace;"> , { label: "12", value: "12" }</span><br />
<span style="font-family: monospace;"> ],</span><br />
<span style="font-family: monospace;"> action: function (evt, sel) {</span><br />
<span style="font-family: monospace;"> // Set the Page Item to the selected value </span><br />
<span style="font-family: monospace;"> var v = sel.options[sel.selectedIndex].value;</span><br />
<span style="font-family: monospace;"> $s("P852_MONTHS", v);</span><br />
<span style="font-family: monospace;"> }</span><br />
<span style="font-family: monospace;"> });</span><br />
<span style="font-family: monospace;"> };</span><br />
<span style="font-family: monospace;"> return options;</span><br />
<span style="font-family: monospace;">}</span><br />
<br />
</code>
The result is looks like shown below. This gives the end user the opportunity to quickly select a number of months he would like to see in the report.<br />
<br />
<a href="https://1.bp.blogspot.com/-LPWyOC_fpYU/XBjbTFFWHYI/AAAAAAAAEZg/AKg3EO-EALU5HzDlXgnLq92cH0r-DKNSgCLcBGAs/s1600/select.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="64" data-original-width="638" height="64" src="https://1.bp.blogspot.com/-LPWyOC_fpYU/XBjbTFFWHYI/AAAAAAAAEZg/AKg3EO-EALU5HzDlXgnLq92cH0r-DKNSgCLcBGAs/s640/select.png" width="640" /></a>
<br />
Similar, you can also add regular text fields. In this example I needed two dates (to and from) that are used in the query and another (number) field. Without this option these fields would appear in a (filter) region above the report and that wouldn't look that good (and would consume valuable screen estate).<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-PT5uo7fZv7I/XBjcorGkc_I/AAAAAAAAEZs/UVEIKLcqZIc8C7gjjIkOF2hGxxPateHVwCLcBGAs/s1600/preview.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="71" data-original-width="1468" height="30" src="https://1.bp.blogspot.com/-PT5uo7fZv7I/XBjcorGkc_I/AAAAAAAAEZs/UVEIKLcqZIc8C7gjjIkOF2hGxxPateHVwCLcBGAs/s640/preview.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
As this is more standard than creating a (specific) select list, I made it easy for you and created a plugin that can add regular input fields and static text to the toolbar. </div>
<br />
You can <a href="https://apex.oracle.com/pls/apex/f?p=131155:3" target="_blank">see a demo here</a> and <a href="https://github.com/APEXGru/ExtendToolbarInputFIeld" target="_blank">download it here</a>.
Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-38251351753436161762018-12-11T09:56:00.001+01:002018-12-11T14:56:03.971+01:00Startup your ORDS container after your Oracle DB container is readyIf you are using multiple containers to set up your Oracle Development (or Test or Production) environment - as I described <a href="https://roelhartman.blogspot.com/2017/10/dockerize-your-apex-development.html" target="_blank">here</a> - you probably ran into the issue that your ORDS container was started before the Database was started up. This results in an ORDS container that is not working - at least not as you need to.<br />
<div>
<br />
<div>
Using <a href="https://docs.docker.com/compose/" target="_blank">Docker Compose</a> you can specify a depends_on option, for instance in the ORDS container configuration you specify (where "oracle" refers to the container that holds the Oracle database) :</div>
<div style="background-color: #0c1021; color: #f8f8f8; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height: 21px; white-space: pre;">
<span style="color: #ff6400;">depends_on</span> :
- <span style="color: #61ce3c;">oracle</span>
</div>
but this only means the ORDS container will start after the "oracle" container has been started. That does not mean the Oracle database inside the container is started and available!<br />
<br />
So how do we tell the ORDS to configure itself in one container to wait for the database in another container is ready to use?<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-9Yzc-C_BfXY/XA91hWpJEbI/AAAAAAAAEYw/n0UWDZLaGLQt_FKfJnfNeX7TE7Pib4iXwCLcBGAs/s1600/wait-for-it.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="644" data-original-width="625" height="320" src="https://3.bp.blogspot.com/-9Yzc-C_BfXY/XA91hWpJEbI/AAAAAAAAEYw/n0UWDZLaGLQt_FKfJnfNeX7TE7Pib4iXwCLcBGAs/s320/wait-for-it.jpg" width="310" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<b><u>Step 1:</u></b></div>
<div class="separator" style="clear: both; text-align: left;">
Start the Oracle database (container), connect to it and enable a port - in this example 8080 - in the Oracle database </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "courier new" , "courier" , monospace;">begin </span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "courier new" , "courier" , monospace;"> dbms_xdb_config.sethttpport( 8080 ); </span></div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "courier new" , "courier" , monospace;">end;</span></div>
<div class="separator" style="clear: both;">
</div>
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "courier new" , "courier" , monospace;">/</span></div>
<div style="font-family: "Helvetica Neue"; font-size: 12px; font-stretch: normal; line-height: normal;">
<br /></div>
This port will become available when the database is up and running.<br />
<br />
<b><u>Step 2:</u></b><br />
Get the "wait-for-it" shell script from <a href="https://github.com/vishnubob/wait-for-it" target="_blank">https://github.com/vishnubob/wait-for-it</a>. In your ORDS Dockerfile copy this in the root of your container and change the Entrypoint from just <span style="font-family: "courier new" , "courier" , monospace;">/config_ords_and_run_catalina.sh</span> to a call to <span style="font-family: "courier new" , "courier" , monospace;">/wait-for-it.sh</span> specifying the Oracle port from step 1. You don't need to use port forwarding as both containers run on the same Docker network and can access each others ports. The example below will wait for max 2 minutes for the Oracle database to start up (if it isn't ready, the rest of the script will not run as it won't make any sense).<br />
<div style="background-color: #0c1021; color: #f8f8f8; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height: 21px; white-space: pre;">
<span style="color: #fbde2d;">COPY</span> ./wait-for-it.sh /
<span style="color: #fbde2d;">RUN</span> chmod +x /*.sh
<span style="color: #fbde2d;">ENTRYPOINT</span> [<span style="color: #61ce3c;">"/wait-for-it.sh"</span>,<span style="color: #61ce3c;">"oracle:8080"</span>,<span style="color: #61ce3c;">"--timeout=120"</span>,<span style="color: #61ce3c;">"-s"</span>,<span style="color: #61ce3c;">"--"</span>,<span style="color: #61ce3c;">"/config_ords_and_run_catalina.sh"</span>]
</div>
</div>
After recreating the image with these changes to the Dockerfile and using Docker Compose to start it all up, the logging of the ORDS container will show the following before proceeding with the configuration:<br />
<br />
<div style="font-stretch: normal; line-height: normal;">
<span style="font-family: "courier new" , "courier" , monospace;">wait-for-it.sh: waiting 120 seconds for oracle:8080</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">wait-for-it.sh: oracle:8080 is available after 42 seconds</span></div>
<div>
<br /></div>
<div>
And that is exactly what we want! Now we defined a proper dependency between our database to be available and our ORDS service.<br />
<br />
<i>Note: This will only work properly if your database is already created. If you want this to work when the database has to be created first, you need to add step 1 into your database creation script and need to set the timeout parameter in step 3 to a higher value.</i></div>
Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-1377659377549740672018-12-07T14:32:00.005+01:002018-12-07T14:32:52.622+01:00Some conditions are more equal than others ... Imagine you have a nice APEX application, but on a certain day your client asks you to remove a column from the application. So after a thorough dependency analysis it appears that that column is only used in two pages : One Interactive Report and one Form. <span class="fullpost"></span><br />
<div>
<br /></div>
<div>
So you go ahead and start with the hard work and delete the column from the table. Now you only have to fix the application, right?</div>
<div>
So - just in case of <i>"you never know whether the client will change his mind again" </i>- you decide to set the "Server Side Condition" of the column in both the Report and the Form to "Never". That should do the trick!</div>
<div>
<br /></div>
<div>
Of course you do test, and fire up the report. Works as expected, the column is not shown on the report anymore. Then you click the little pencil link to open the Form. </div>
<div>
<br /></div>
<div>
And an error pops up:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/--uG-OhDq0mU/XApz6EFDX3I/AAAAAAAAEYU/Hd5FBmfVI2k-w_WqFBo3wT7N3l7FXl0KgCLcBGAs/s1600/Screenshot%2B2018-12-07%2Bat%2B14.21.15%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="461" data-original-width="709" height="260" src="https://4.bp.blogspot.com/--uG-OhDq0mU/XApz6EFDX3I/AAAAAAAAEYU/Hd5FBmfVI2k-w_WqFBo3wT7N3l7FXl0KgCLcBGAs/s400/Screenshot%2B2018-12-07%2Bat%2B14.21.15%2B.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
WTF? I was pretty sure I set the Condition to Never! So why is it still trying to fetch that column?</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Luckily there are more ways to not show an Item on a Page, so I created a Build Option "Exclude". </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-ZWOcF813Zms/XAp1hdpJ44I/AAAAAAAAEYg/A6zDcS6Eg7wKqK0h0AA1Wtho9z3gRpMwACLcBGAs/s1600/Screenshot%2B2018-12-07%2Bat%2B14.28.24%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="168" data-original-width="646" height="103" src="https://1.bp.blogspot.com/-ZWOcF813Zms/XAp1hdpJ44I/AAAAAAAAEYg/A6zDcS6Eg7wKqK0h0AA1Wtho9z3gRpMwACLcBGAs/s400/Screenshot%2B2018-12-07%2Bat%2B14.28.24%2B.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
And when I used that Build Option to exclude the column from the Form, it just works as expected.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
So lessons learned :</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ol>
<li>Not every condition that's available to show or hide items on your Page will exactly work the same.</li>
<li>Be careful with <i>"Condtion = Never"</i>, make "<i>Build Options</i>" great again! </li>
</ol>
Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-61170959803935968842018-06-18T21:31:00.001+02:002018-06-18T21:31:27.153+02:00Kscope18 flashbackAfter a few nights of good sleep (that means more than the average 4 to 5 hours at Kscope), it is time to sit back and evaluate what happened last week.
I arrived on Thursday night before the event, just in time to catch Danny Bryant at the bar for "just one last beer" (I don't know how often I heard, and used, that phrase last week).<br />
The whole Friday was filled with an all day Board Meeting, mainly discussing the future of ODTUG, a reception and a fine dinner. Since a couple of years, Saturday is the Community Service Day. This year, an impressive number of people showed up and were transferred to "Up Orlando", to make its easier for them to help the people that really need some help. All kinds of different activities were done, like cleaning, filling shelves, painting chairs etc.<br />
After that there is another tradition: bag stuffing. All those (about 1500) bags didn't get delivered with all those goodies in there! A number of volunteers created the most efficient human machinery to fill those bags. An hour or so at the poolside to wind down, before registration opened and the people started to drop in in huge numbers. The evening was concluded with an unscheduled "Spirit Tasting" event - very risky at the start of the conference!
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-dWCDlYsxxuU/WygBSehhEpI/AAAAAAAAERo/V1NTjcC2I0UXAo3nYpDrBGVuPsN-UbPgQCLcBGAs/s1600/ironman.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="1600" data-original-width="933" height="320" src="https://1.bp.blogspot.com/-dWCDlYsxxuU/WygBSehhEpI/AAAAAAAAERo/V1NTjcC2I0UXAo3nYpDrBGVuPsN-UbPgQCLcBGAs/s320/ironman.png" width="187" /></a></div>
The Sunday started off after breakfast with a joint Database/APEX symposium session by Mike Hitchwa on the Oracle RAD stack: REST. APEX. Database. You don't need more than that to create stunning, reliable applications in the most efficient way! Most other sessions that day I had to skip due to "Board Duties" like the rehearsal of Monday's General Session, but I returned just in time to hear Joel's "State of the Union". A great APEX future is ahead of us! An APEX MOOC, a new Certification (with NO WEBSHEETS QUESTIONS!) and APEX Office Hours. And of course rewarding Juergen Schuster for all his work for the APEX Community!
Next was the Welcome Reception, dressed up as your favourite character. I was glad more people than the Board and the Conference Committee came dressed up! I got quite some attention and remarks wearing my Ironman costume (it took a few drinks to feel at least somewhat comfortable wearing it) ... even in the days after!
<br />
Apparently some things can't be unseen ;-)<br />
<br />
Monday started off with the General session - with the clips we made in January as an attempt to make it somewhat funny. If we succeeded or not ... I leave that up to you. The videos will appear on YouTube soonish I heard.<br />
In between the meetings, interviews etc I managed to catch at least two full sessions, one of it was "APEX as a Progressive Web App (PWA)" by Vincent Morneau. Really interesting stuff, maybe not for the next couple of weeks or months, but keep an eye on it for the future!<br />
<a href="https://3.bp.blogspot.com/-XbsyG31ZVRs/WygEgJ0yDwI/AAAAAAAAER0/FRi-xgXvqjA4i4214ZCSTaGV1ZiitMpgACLcBGAs/s1600/mRainey_2018-Jun-12.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="1361" data-original-width="1600" height="272" src="https://3.bp.blogspot.com/-XbsyG31ZVRs/WygEgJ0yDwI/AAAAAAAAER0/FRi-xgXvqjA4i4214ZCSTaGV1ZiitMpgACLcBGAs/s320/mRainey_2018-Jun-12.jpg" width="320" /></a>After Happy Hour it was "Community Nights". Database/BI Trivia, EPM Lip Sync Battle and APEX Show & Tell. I went from one to the other and back to catch as much of the community vibe I could. After this there was another huge Happy Hour sponsored by some if the vendors in the hotel lobby bar (again "ok, just one more...").<br />
<br />
Tuesday started early with the 5K "Fun Run" in the sauna named Florida. The temperature is not the <br />
problem here, but the 98% humidity makes running longer than 10 minutes really hard. But nevertheless about a 100 people showed up early o' clock to torture themselves (and feel good afterwards).<br />
After two interesting sessions, the ACE Briefing and one session by myself, it was time for the ACE Dinner. This turns more and more into a reunion. People you haven't even spotted during the first few days turn up here. And a lot of new ACE's were announced, amongst who quite a number of APEX developers! A recognition for the community!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-cLOZHjJZWs8/WygGoIse31I/AAAAAAAAESA/41JkAi1t4PUN0QJcSvv2UVa9diT5wZamwCLcBGAs/s1600/coachjdave_2018-Jun-14.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="240" src="https://2.bp.blogspot.com/-cLOZHjJZWs8/WygGoIse31I/AAAAAAAAESA/41JkAi1t4PUN0QJcSvv2UVa9diT5wZamwCLcBGAs/s320/coachjdave_2018-Jun-14.jpg" width="320" /></a></div>
Wednesday I managed to catch two more sessions (about Additional Templating Engines and Docker) before I had to head up for the Leadership Graduation. For the people that are unaware of this: every year ODTUG invites a group of people into the Leadership Program. They work their way through a curriculum ending with a presentation of their end result to the Board. This year's group did very well with a very useful and elaborate project.<br />
And after that it was time to get dressed up for the Wednesday event party at Andretti's. I guess the karts were everyones favourite, although I also spotted lines for the Zombie shooting and some other attractions. About half of the group went to the after party in Mangos, the other half created their own (just little more quiet) afterparty in the lobby bar - and that's were I was for just one more drink ;-)<br />
<br />
Thursday, already the last day of the conference. A lot of people had quite a hard time to get up and attend the sessions, luckily we started rather late with the <i>"#LetsWreckThisTogether APEX Talks"</i>. And as usual they were great. All grouped around one subject: <b>JavaScript</b>. And as I am most certainly not an expert in this matter, I picked up some interesting facts and ideas here and there!<br />
And after the Closing Session it was time to end this year's show. I could finally spend another two hours at the pool before heading to the airport for the (long) trip home.<br />
<br />
Although Kscope is a totally different experience when you are on the Board (not necessarily worse, just different), I had a blast - just like all the previous years.<br />
<br />
I hope to see you all back in Seattle at Kscope19!<br />
<br />
<br />Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-84982313590787602872018-02-05T16:44:00.001+01:002018-02-06T09:01:55.862+01:00apex_application.g_f0x array processing in Oracle 12If you created your own "updatable reports" or your custom version of tabular forms in Oracle Application Express, you'll end up with a query that looks similar to this one:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-WdEh0HdWKdg/Wnh6nDYKwUI/AAAAAAAAEOQ/u1G2ud7Ks94EZBreGG-MqMlAQHcrG6buQCLcBGAs/s1600/Screen%2BShot%2B2018-02-05%2Bat%2B16.38.51%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="507" data-original-width="1024" height="315" src="https://2.bp.blogspot.com/-WdEh0HdWKdg/Wnh6nDYKwUI/AAAAAAAAEOQ/u1G2ud7Ks94EZBreGG-MqMlAQHcrG6buQCLcBGAs/s640/Screen%2BShot%2B2018-02-05%2Bat%2B16.38.51%2B.png" width="640" /></a></div>
then you disable the "<span style="font-family: "courier new" , "courier" , monospace;">Escape special characters</span>" property and the result is an updatable multirecord form.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-MVybmKtIx_s/Wnhp1-uxtvI/AAAAAAAAEOE/hqH6PO3ZKUUiYK-_pU-sgyiib4n1tJoXgCLcBGAs/s1600/Screen%2BShot%2B2018-02-05%2Bat%2B15.27.05%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="692" data-original-width="578" height="320" src="https://4.bp.blogspot.com/-MVybmKtIx_s/Wnhp1-uxtvI/AAAAAAAAEOE/hqH6PO3ZKUUiYK-_pU-sgyiib4n1tJoXgCLcBGAs/s320/Screen%2BShot%2B2018-02-05%2Bat%2B15.27.05%2B.png" width="266" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
That was easy, right? But now we need to process the changes in the Ename column when the form is submitted, but only if the checkbox is checked. All the columns are submitted as separated arrays, named apex_application.g_f0x - where the "x" is the value of the "p_idx" parameter you specified in the apex_item calls. So we have apex_application.g_f01, g_f02 and g_f03.</div>
<div class="separator" style="clear: both; text-align: left;">
But then you discover APEX has the oddity that the "checkbox" array only contains values for the checked rows. Thus if you just check "Jones", the length of g_f02 is 1 and it contains only the empno of Jones - while the other two arrays will contain all (14) rows.</div>
<div class="separator" style="clear: both; text-align: left;">
So for processing you need to work through g_f02 and find the corresponding index in the g_f01 array. And then you need to use that index to find the corresponding value in the g_f03 array. Check the code example below...</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-2wtVxfa6lsU/Wnh615j6qcI/AAAAAAAAEOU/ciuRSCbWDzA9lUt-l8N0vD1zUiSz6i4VACLcBGAs/s1600/Screen%2BShot%2B2018-02-05%2Bat%2B16.39.47%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1352" data-original-width="1462" height="588" src="https://3.bp.blogspot.com/-2wtVxfa6lsU/Wnh615j6qcI/AAAAAAAAEOU/ciuRSCbWDzA9lUt-l8N0vD1zUiSz6i4VACLcBGAs/s640/Screen%2BShot%2B2018-02-05%2Bat%2B16.39.47%2B.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
To me this looks quite complex - even for a simple example as this one. But hey, that's the way it is. Everybody is doing it this way, so it must be ok.</div>
<br />
Until Oracle Database 12.1 came along .... and you have read this: <a href="http://stevenfeuersteinonplsql.blogspot.nl/2016/02/use-table-operator-with-associative.html" target="_blank">http://stevenfeuersteinonplsql.blogspot.nl/2016/02/use-table-operator-with-associative.html</a><br />
What is explained in that post, can also be applied to apex_application.g_f0x arrays! So knowing that, we can rewrite the code above using a way more elegant cursor :<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-Hz4xMeg6PJ8/Wnh7CNmihTI/AAAAAAAAEOY/l-Su1pWOBXQ662oMhBPAgWzN-H5oCL1EwCLcBGAs/s1600/Screen%2BShot%2B2018-02-05%2Bat%2B16.40.41%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="996" data-original-width="980" height="640" src="https://1.bp.blogspot.com/-Hz4xMeg6PJ8/Wnh7CNmihTI/AAAAAAAAEOY/l-Su1pWOBXQ662oMhBPAgWzN-H5oCL1EwCLcBGAs/s640/Screen%2BShot%2B2018-02-05%2Bat%2B16.40.41%2B.png" width="627" /></a></div>
Just use that cursor in your loop to process the changes. Although you still have to figure out the join between the different arrays, it is now all done within one SQL statement. Simple, elegant, effective. And probably faster too if you have a larger result set.<br />
<br />
BTW, as you can read <a href="https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:9532864900346799695" target="_blank">here</a>, you can't use this PL/SQL table() construct inside a DML statement. So you can't use it directly in an insert, update, delete or merge command as that will result in an "<i>ORA-00902 - Invalid datatype</i>" error. It would be really, really nice if that limitation is lifted in a future version!Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-41358483666180116012017-10-31T13:13:00.003+01:002017-10-31T13:13:37.290+01:00Using multiple Authentication Schemes for your APEX applicationRecently someone asked me how he could implement multiple authentication schemes for his APEX application. He would like to use (some kind of) Single Sign-on authentication and - as an alternative - an Application Express Authentication. The problem is ... you can only define one Authentication Scheme being "Current" for an application! So how can we solve this issue?<br />
<br />
First, we need te be aware that multiple applications can share their authentication by using the same cookie. Thus if you specify "MYCOOKIE" as the Cookie Name in Application A as well as in Application B, you can switch from A to B and back without the need of logging in again. It doesn't matter what Authentication Scheme Type you are using!<br />
<br />
Knowing this, we are halfway our solution. We need two Applications. One - the "real" application - using the Application Express Authentication, let's name this one "LAUNCHPAD". And another one using the Single Sign-on Authentication, named "SSO", with just one page named "HOME". Both using the same Cookie Name as described above.<br />
On the login page of LAUNCHPAD we also define a button "Connect using SSO". This will issue a redirect to the page "SSO:HOME". Because we're not logged in yet, this will try to authenticate using the Single Sign-on Authentication. On the "SSO:HOME" page we just define one before header process that does a redirect to the "LAUNCHPAD:HOME" page. When the "SSO:HOME" page is being rendered, we are logged in, the cookie is set and we can safely redirect to the other application sharing the same cookie. Mission completed!<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-S9QXq5qKjM4/Wfho1ezvUwI/AAAAAAAAEL4/rC-Zy5QDRX42Wr0Bo4ZhTBAUfNhkevapwCLcBGAs/s1600/Screen%2BShot%2B2017-10-31%2Bat%2B13.11.42%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="978" data-original-width="1418" height="440" src="https://2.bp.blogspot.com/-S9QXq5qKjM4/Wfho1ezvUwI/AAAAAAAAEL4/rC-Zy5QDRX42Wr0Bo4ZhTBAUfNhkevapwCLcBGAs/s640/Screen%2BShot%2B2017-10-31%2Bat%2B13.11.42%2B.png" width="640" /></a></div>
Using an After Authentication Application Process in the SSO app, we can even do some additional checks based on the username returned by the SSO function (e.g. 'Proxy-Remote-User') - eventually redirecting to "LAUNCHPAD:LOGIN_DESKTOP" is something is wrong (thus showing the option to log in using a username and password).Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-51891052109686787412017-10-11T12:01:00.000+02:002017-10-11T12:07:51.271+02:00Dockerize your APEX development environment<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-k78s50cSmyc/Wd3Cwi9mJLI/AAAAAAAAEKY/jckCWLapFl0vqBlpGtqt1WdXeRYZM-ePwCLcBGAs/s1600/Whale%2BLogo332_5.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="201" data-original-width="269" src="https://3.bp.blogspot.com/-k78s50cSmyc/Wd3Cwi9mJLI/AAAAAAAAEKY/jckCWLapFl0vqBlpGtqt1WdXeRYZM-ePwCLcBGAs/s1600/Whale%2BLogo332_5.png" /></a></div>
<span class="fullpost"></span>Nowadays Docker is everywhere. It is one of the main components of Continuous Integration / Continuous Development environments. That alone indicates Docker has to be seen more as a Software Delivery Platform than as a replacement of a virtual machine.<br />
<br />
However ...<br />
<br />
If you are running an Oracle database using Docker on your local machine to develop some APEX application, you will probably not move that container is a whole to test and production environments. Because in that case you would not only deliver a new APEX application to the production environment - which is a good thing - but also overwrite the data in production with the data from your development environment. And that won't make your users very excited.<br />
So in this set up you will be using Docker as a replacement of a Virtual Machine and not as a Delivery Platform.<br />
And that's exactly the way Martin is using it as he described in <a href="http://www.talkapex.com/2017/10/how-to-setup-oracle-db-12-2-docker-container/" target="_blank">this recent blog post</a>. It is an ideal way to get up and running with an Oracle database in a Docker container in a very short time (you have to download 3.5 Gb, so dependent on your connection that might take some time). This works fine for a short PoC. But what if you want to be <b><u>sure</u></b> your container lives a bit longer than a week or two? Because, if your container gets blown away for some sort of reason, you will lose all your data. The original image is still there, but that contains just the starting situation. A.k.a. an empty database. And - from recent experience - I can tell you that containers can get destroyed (in this case by an upgrade of the Docker software). The whole concept of containers is that they are <b><u>ephemeral</u></b>, lasting for a short period of time.<br />
<br />
But luckily we can set up a Docker container in a way we are sure our data is safe, even when the container is destroyed. We can use volume mapping for that. So we don't need to download the complete installed database from the link Martin mentioned in his blog, but we need the Docker build files and the required Oracle database software and build the image ourselves. Exactly as Maria describes in <a href="https://sqlmaria.com/2017/04/27/oracle-database-12c-now-available-on-docker/" target="_blank">her blogpost</a>.<br />
In my setup, once the image was created, I started the container by issuing this command:<br />
<div style="background-color: #0c1021; color: #f8f8f8; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height: 21px; white-space: pre;">docker run --name oracle -p 1521:1521 -p 5500:5500 <span style="color: #d8fa3c;">\</span>
-v /Users/Roel/docker/database:/opt/oracle/oradata <span style="color: #d8fa3c;">\</span>
oracle/database:12.2.0.1-ee
</div>
The advantage is all my data is stored outside my container, on my local machine. The container itself is static - there will be no changes there. So if the container blows up, I can just spin up a new one using the same data that is still stored on my disk. And it seems a bit faster as well ....<br />
<br />
But if we want to develop APEX applications, we need to have ORDS installed somewhere as well. Of course we could just run ORDS locally connecting to the (forwarded) port 1521. But it would be way nicer if ORDS would run in a Docker container as well!<br />
Before we start that second container, we have to make sure both containers are able to connect to each other. It is possible using the <span style="font-family: "courier new" , "courier" , monospace;">--link</span> switch, but that is deprecated. The new way of connecting containers together, is to create a Docker network:<br />
<div style="background-color: #0c1021; color: #f8f8f8; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 14px; line-height: 21px; white-space: pre;">docker network create my_network</div>
Then we add our running database container, named "oracle", to that network:<br />
<div style="background-color: #0c1021; color: #f8f8f8; font-family: menlo, monaco, "courier new", monospace; font-size: 14px; line-height: 21px; white-space: pre;">docker network connect my_network oracle</div>
And now we can go looking for a Docker image for our ORDS. If you issue the command:<br />
<div style="background-color: #0c1021; color: #f8f8f8; font-family: menlo, monaco, "courier new", monospace; font-size: 14px; line-height: 21px; white-space: pre;">docker search ords</div>
you'll get a few hits. As we already have a database running, we need an image just containing ORDS and fire that up - in this case the image lucassampsouza/ords_apex:3.0.9.<br />
<div style="background-color: #0c1021; color: #f8f8f8; font-family: menlo, monaco, "courier new", monospace; font-size: 14px; line-height: 21px; white-space: pre;">docker run -t -i \
--name ords \
--network=my_network \
-e DATABASE_HOSTNAME="oracle" \
-e DATABASE_PORT="1521" \
-e DATABASE_SERVICENAME="ORCLPDB1" \
-e DATABASE_PUBLIC_USER_PASS=oracle \
-e APEX_LISTENER_PASS=oracle \
-e APEX_REST_PASS=oracle \
-e ORDS_PASS=oracle \
--volume /Users/Roel/docker/apex/images:/usr/local/tomcat/webapps/i \
-p 8080:8080 \
lucassampsouza/ords_apex:3.0.9</div>
It is version 3.0.9, but of course you can upgrade it yourself - or just wait for a newer version.<br />
Some remarks about this command:<br />
<ul>
<li>The <span style="font-family: "courier new" , "courier" , monospace;">--network</span> switch adds this new container to the network immediately. </li>
<li>I can reference the hostname of the database - normally the name or IP-address of the machine where the database is running on - by the name of the container, "oracle".</li>
<li>I defined (another) volume mapping for this container in a way my APEX images directory is located on my local machine. Thus I can easily patch or update APEX without touching the container. If this container gets blown away ... I just fire up a new one with this same command and I am ready to go.</li>
</ul>
<div>
So now we have two containers running next to each other. Nice. But now I need a solution for printing as well. So I asked Dimitri whether he has a Docker image of his <a href="https://www.apexofficeprint.com/index.html" target="_blank">APEX Office Print</a> (AOP) solution. And he was kind enough to make one available for me. So very similar to the ORDS one, I started this one up, attached to that same network - and also with another volume mapping to a directory that holds my license key:</div>
<div style="background-color: #0c1021; color: #f8f8f8; font-family: menlo, monaco, "courier new", monospace; font-size: 14px; line-height: 21px; white-space: pre;">docker run -d \
--name apexofficeprint \
--network=my_network \
-p 8010:8010 \
-v /Users/Roel/docker/apexofficeprint/:/apexofficeprintstartup/ \
apexrnd/apexofficeprint \
-s /apexofficeprintstartup/</div>
So now I only needed two more steps to make it working. First define the AOP URL in the component settings of AOP in APEX: <span style="font-family: "courier new" , "courier" , monospace;">http://apexofficeprint:8010/</span>.(again notice the use of the container name in that URL). And finally open up the ACL for the APEX owner, so it is possible to connect from within the database to the AOP container:<br />
<div style="background-color: #0c1021; color: #f8f8f8; font-family: menlo, monaco, "courier new", monospace; font-size: 14px; line-height: 21px; white-space: pre;">begin
dbms_network_acl_admin.append_host_ace (
host => 'apexofficeprint',
lower_port => 8010,
upper_port => 8010,
ace => xs$ace_type(privilege_list => xs$name_list('http'),
principal_name => 'APEX_050100',
principal_type => xs_acl.ptype_db));
end;
</div>
So this complete configuration is totally ephemeral (love using that word again) as all important data is stored on my local disk. And I can easily burn or replace containers - for instance when a new version of the ORDS or AOP image comes available.<br />
To end up, this is how it looks as a picture: <br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-grFDQHw2nGk/Wd3q2antCvI/AAAAAAAAEK0/D3eig-rk3mEcZmK-TeoDl0pr9qJm48kwgCLcBGAs/s1600/Screen%2BShot%2B2017-10-11%2Bat%2B11.53.17%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="954" data-original-width="1394" height="437" src="https://1.bp.blogspot.com/-grFDQHw2nGk/Wd3q2antCvI/AAAAAAAAEK0/D3eig-rk3mEcZmK-TeoDl0pr9qJm48kwgCLcBGAs/s640/Screen%2BShot%2B2017-10-11%2Bat%2B11.53.17%2B.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
Now go and try it yourself!</div>
<br />Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-75708894438954659862017-07-06T13:34:00.005+02:002017-07-11T14:02:58.714+02:00Refresh selected row(s) in an Interactive GridIn my previous post I blogged about <a href="https://roelhartman.blogspot.nl/2017/07/push-changed-rows-to-interactive-grid.html" target="_blank">pushing changed rows from the dabatase into an Interactive Grid</a>. The use case I'll cover right here is probably more common - and therefore more useful!<span class="fullpost"></span><br />
<div>
<br /></div>
<div>
Until we had the IG, we showed the data in a report (Interactive or Classic). Changes to the data where made by popping up a form page, making changes, saving and refreshing the report upon closing the dialog. Or by clicking an icon / button / link in your report that makes some changes to the data (like changing a status) and ... refresh the report. </div>
<div>
That all works fine, but the downsides are:</div>
<div>
<ol>
<li>The whole dataset is returned from the server to the client - again and again. And if your pagination size is large, that does lead to more and more network traffic, more interpretation by the browser and more waiting time for the end user.</li>
<li>The "current record" might be out of focus after the refresh, especially by larger pagination sizes, as the first rows will be shown. Or (even worse) while you were working on page 3 of your report, it gets refreshed and your are back on square 1. There are some work arounds (like keeping the offset before refresh and returning to that position after refresh) and plugins that should keep the user on the same page.</li>
</ol>
But since Interactive Grid we have the option to refresh just one or more records! And to use that feature to update the currently selected row(s), you only need two lines of JavaScript:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">var myGrid = apex.region( <regionid> )</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> .widget()</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> .interactiveGrid("getViews")</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> .grid;</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">myGrid.model.fetchRecords( myGrid.getSelectedRecords() );
</span><br />
<span style="font-family: "courier new" , "courier" , monospace; "><br /></span>
The first line defines the IG object, the second line is using the <span style="font-family: "courier new" , "courier" , monospace; ">getSelectedRecords</span><span style="font-family: inherit;"> method to get the currently selected record(s) and passes these on to the </span><span style="font-family: "courier new" , "courier" , monospace; ">fetchRecords</span><span style="font-family: inherit;"> method. The effect is awesome, as the page stays exactly what and where it was - so no scrolling etc - and you see only the data change. Even the "current" selection stays in place.<br />You only need to think about what should happen when the record is deleted (from the database or from the result set of your query) by your action....</span><br />
<span style="font-family: inherit;"><br /></span>
And to make it even easier to use - and eventually adapt for future enhancements, you should create a simple Dynamic Action plugin. Or <a href="https://drive.google.com/open?id=0B8Su3_nLqAoeYTE3LUNuZVVHRlk" target="_blank">download it from here</a>.<br />
<div>
<br /></div>
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-25908947831340457482017-07-03T16:30:00.000+02:002017-07-04T09:40:33.211+02:00Push changed rows to an Interactive GridFor pushing changes from the database to the end user, the regular solution is using websockets. A change in a record is detected - using a trigger or using the CQN (Change Query Notification) feature - and a notification is send to a websocket server. That websocket server broadcasts the notification over a channel to all browsers that are tuned in to that websocket channel. Then the browser reacts to that notification, usually showing an alert or refreshing a report. This trick is described on multiple sites, just Google for "<i>oracle apex websockets</i>" or similar.<span class="fullpost"></span><br />
<div>
<br /></div>
<div>
So back in the old days, we used that notification in the browser to refresh the (interactive) report. But along comes the <b><i>Interactive Grid (IG)</i></b>. While he full-refresh mechanism still works for IG, an IG has also the option to refresh just one row. </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-pyZDJnGS7C0/WVpL7ldpreI/AAAAAAAAEH8/m3cGtz6B1D0RQLZ39-qBLul02KqwWE1fQCLcBGAs/s1600/Screen%2BShot%2B2017-07-03%2Bat%2B15.51.01%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="736" data-original-width="660" height="320" src="https://1.bp.blogspot.com/-pyZDJnGS7C0/WVpL7ldpreI/AAAAAAAAEH8/m3cGtz6B1D0RQLZ39-qBLul02KqwWE1fQCLcBGAs/s320/Screen%2BShot%2B2017-07-03%2Bat%2B15.51.01%2B.png" width="286" /></a></div>
<div>
So wouldn't it be awesome that just the changed row(s) get refreshed upon a change in the database, instead of the whole report? Can we do it ... yes we can!</div>
<div>
<br /></div>
<div>
First in our "Execute when Page Loads" property we start listening to the websocket server when the page is loaded using:</div>
<div>
<br /></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;">window.addEventListener("load", init, false);</span></div>
<div>
<br /></div>
<div>
In the "Function and Global Variable Declaration" we add the actual code and the init function:</div>
<div>
<div class="MsoNormal" style="font-family: Calibri, sans-serif; font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<br /></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">//The websocket server is set using an Application Item </span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">var wsUri = "&WSSERVER.";<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<br /></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace; font-size: 11pt;">function init() {</span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> websocket = new WebSocket(wsUri);<span style="font-size: 11pt;"> </span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-size: 11pt;"><span style="font-family: "courier new" , "courier" , monospace;"> websocket.onopen = function(evt) { onOpen(evt) };</span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"><o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> websocket.onclose = function(evt) { onClose(evt) };<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> websocket.onmessage = function(evt) { onMessage(evt) };<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> websocket.onerror = function(evt) { onError(evt) };<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">}<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<br /></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace; font-size: 11pt;">// This is where the real magic happens</span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<o:p><span style="font-family: "courier new" , "courier" , monospace;">// onMessage is called when a message is received from the websocket server </span></o:p></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">function onMessage(msg) {<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace; font-size: 11pt;"> // msg is in JSON format, containing the ROWID of the changed record</span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> var rowid,<span class="Apple-converted-space"> </span><o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> record,<span class="Apple-converted-space"> </span><o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> records = [],</span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> json = JSON.parse( msg.data ),<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> l = json.changes.length; <span class="Apple-converted-space"> </span><o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-converted-space"> // "myGrid" is the Static ID of the IG region</span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> var myGrid = apex.region("myGrid").widget().interactiveGrid("getViews").grid;<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> for ( var i = 0; i < l; i++){ <span class="Apple-converted-space"> </span><o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> rowid = json.changes[i].rowid ;<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> // loop through all the rows, check if the rowid is present<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> // forEach callback params : record, index, key<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> myGrid.model.forEach( function(r, i, k){<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> if ( $.inArray( rowid, r ) > -1 ) {<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> record = myGrid.model.getRecord(k);<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> if ( myGrid.model.getRecordMetadata(k).updated ){<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> apex.message.alert("Record " + (i+1) + " is changed by another user and is requeried");<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> }<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> records.push( record );<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> }<o:p></o:p></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span lang="EN-US"><span style="font-family: "courier new" , "courier" , monospace;"> });<o:p></o:p></span></span></div>
<span style="font-size: 11pt;"><span style="font-family: "courier new" , "courier" , monospace;"> }</span></span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: 11pt;"> // fetch the changed records</span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> if ( records.length > 0 ) {<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> myGrid.model.fetchRecords(records); <span class="Apple-converted-space"> </span><o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> }<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">}</span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<br /></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">// Addtional helper functions below</span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">$(window).on("beforeunload", function(){<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> websocket.close();<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">}); <span class="Apple-converted-space"> </span><o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"><span class="Apple-converted-space"><br /></span></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">function onOpen(evt) {<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> apex.debug("Websocket opened on &WSSERVER.");<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">}<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<br /></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">function onClose(evt) {<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> apex.debug("Websocket closed");<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">}<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<br /></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">function onError(evt) {<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> apex.debug("Websocket Error");<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"> apex.debug(evt);<o:p></o:p></span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;">}</span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div class="MsoNormal" style="margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: inherit;">The key of the code above is that the records that are changed and are found on the current page are added to an array of records. And that array us used to fetch the records. If you inspect the network traffic that is going on, you can verify that only the changed records (in the screen shot below, three changed records) are fetched and shown on the screen.</span></div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-ZbTQlszv0vo/WVpSORupyDI/AAAAAAAAEII/a2yYGtoBaQwxK71tKwwsrvZAT3_ThsIiQCLcBGAs/s1600/network.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="200" data-original-width="768" src="https://4.bp.blogspot.com/-ZbTQlszv0vo/WVpSORupyDI/AAAAAAAAEII/a2yYGtoBaQwxK71tKwwsrvZAT3_ThsIiQCLcBGAs/s1600/network.JPG" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
The whole process works seamless for the user. If he made a change to a record that didn't get updated by someone else (or another process) than he can keep working on that, if he did make a change - and thus there is a conflict - he will get a notification. In our example the process didn't even show a progress spinner, but maybe that's because our queries are fast (it is not on EMP, but based on a view that references several tables and returns over 40,000 rows).</div>
<div class="MsoNormal" style="font-size: 11pt; margin: 0cm 0cm 0.0001pt; text-size-adjust: auto;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: inherit;">[Edit Jul 4 : I changed the code in a way that it is dependent on the DOM anymore, but only uses the model to determine whether there is a record to update]</span><span style="font-family: inherit; font-size: 11pt;">]</span></div>
</div>
Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-72960149803030325772017-04-03T18:34:00.003+02:002017-04-03T20:38:28.747+02:00A review of APEX World 2017 - Day 2<!--?xml version="1.0" encoding="UTF-8"?-->
<br />
<div>
(You can read my impression of the first day <a href="http://roelhartman.blogspot.nl/2017/04/a-review-of-apex-world-2017-day-1.html" target="_blank">here</a>)
</div>
<div>
After a short but good night sleep and an excellent breakfast, Mike Hichwa delivered his second keynote "<i>RAD Challenge: Build a Real World application in 60 minutes</i>”. Although he suffered from connection issues, Mike managed to wow the audience showing the new packaged app "Quick SQL” and the new (currently Cloud-only) “Blueprint” option to create a new application. As JSON is not code (but just a collection of value-attribute pairs), it was really a <b><u>no-code</u></b> showcase!</div>
<div>
</div>
<div>
<!--?xml version="1.0" encoding="UTF-8"?-->
<br />
<div>
Then it was parallel session time again and I went to see how Christian Rokitta would <i>"Bootstrapify Universal Theme”</i>. The message is: You don’t have to unsubscribe from the Universal Theme (and please don’t!) to create an application that looks totally different from the “regular” APEX applications.
</div>
<div>
<br /></div>
<div>
<a href="http://4.bp.blogspot.com/-Bq68mY3CgVw/WOJ5Xza0WMI/AAAAAAAAEF0/epi1q50bKmUR9xvuRG1Ua9mGqeBmKU4VgCK4B/s1600/CFFA07CB-0B87-4B0C-B47C-6AC2FC9A514B.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="300" src="https://4.bp.blogspot.com/-Bq68mY3CgVw/WOJ5Xza0WMI/AAAAAAAAEF0/epi1q50bKmUR9xvuRG1Ua9mGqeBmKU4VgCK4B/s400/CFFA07CB-0B87-4B0C-B47C-6AC2FC9A514B.png" width="400" /></a>Then I had to deliver my second presentation “<i>Docker for Dummies</i>”. It was aimed at people who might have heard of Docker but really have hardly a clue what it is - and what the benefits are. I received really good feedback on this session and quite a lot of additional questions afterwards. So it seems I did made people curious and enthousiast about Docker. Mission completed! </div>
</div>
<div>
<br /></div>
<div>
<!--?xml version="1.0" encoding="UTF-8"?-->
<br />
<div>
During another excellent lunch, we could still enjoy the beautiful weather and the Rotterdam skyline. It was good to spend some time outside in the fresh air.
</div>
<div>
<br /></div>
<div>
Then the last part of the event started. Dimitri kicked it off with “<i>Moving my APEX app to the Exadata Express Cloud. Live!</i>”. He explained the differences in (some of) the cloud offerings Oracle has and how you can move your application and data from your local instance into the Exadata Express Cloud. As Mike explained during his keynote on the first day, EECS will get better: more workspaces, really “Cloud first”, optional upgrading (after APEX 5.1), a local datacenter etc. Oh, yes, and a free subscription to the Oracle Developer Cloud Service as well!
</div>
<div>
<br /></div>
<div>
The last regular session I attended was “<i>Forms to APEX</i>” by Sergei Martens. The APEX pages he showed were impressive. So different than anything else and enriched with numerous cool features. I especially liked the “Outlook style” interface because it is so close to what a user is used to, he/she can start using that application right away without any training whatsoever. The presentation could even be better if he had shown the original Forms screens as a reference ...
</div>
<div>
<br /></div>
<div>
And finally, at the last keynote, Shakeeb could do his “<i>The Center of the Universal Theme</i>” presentation. Of course the presentation wasn’t just good looking - it had good content as well and I really like Shakeeb’s presentation style (as do a lot of other people I heard afterwards).
</div>
<div>
<br /></div>
<div>
As a “goodbye” we were offered some drinks and snacks and then it was time to go home.
</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-IIsWFV1PRAU/WOJ5e2XM6uI/AAAAAAAAEF8/p1XH4QEravo_qvOtgZ_F3RLfHxwAfPZBwCK4B/s1600/IMG_2772.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://4.bp.blogspot.com/-IIsWFV1PRAU/WOJ5e2XM6uI/AAAAAAAAEF8/p1XH4QEravo_qvOtgZ_F3RLfHxwAfPZBwCK4B/s640/IMG_2772.JPG" width="640" /></a></div>
<div>
<br /></div>
<div>
Next year APEX World 2018 it will probably be on the same wonderful location. I am already looking forward to it and i encourage everyone who is interested in APEX to attend! </div>
</div>
<span class="fullpost"></span>Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-41725562240718864552017-04-02T20:23:00.007+02:002017-04-02T21:52:54.908+02:00A review of APEX World 2017 - Day 1<!--?xml version="1.0" encoding="UTF-8"?-->
<br />
<div style="text-align: justify;">
Last week the SS Rotterdam was the beautiful location of the largest gathering of APEX Developers worldwide. With around 380 (!) attendees a new high was set. And they came from all over the world : I spotted people from The Netherlands, Belgium, Switzerland, Austria, Croatia, Germany, Denmark, Norway, UK, Ireland and the USA. And I even might have missed one or two ….
</div>
<div style="text-align: justify;">
<br /></div>
<div>
<a href="http://4.bp.blogspot.com/-nVRRtewxHj4/WOE_0E38xHI/AAAAAAAAEE8/juFx1qDSY9AtpflCXcBrMoZyif4sBbpwACK4B/s1600/90A3BEAF-C42D-4CAB-9850-4435FC891FD2.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: justify;"><img border="0" height="400" src="https://4.bp.blogspot.com/-nVRRtewxHj4/WOE_0E38xHI/AAAAAAAAEE8/juFx1qDSY9AtpflCXcBrMoZyif4sBbpwACK4B/s400/90A3BEAF-C42D-4CAB-9850-4435FC891FD2.png" width="400" /></a><br />
<div style="text-align: justify;">
The event started with a presentation by the “father of APEX”, Mike Hichwa, talking about "<i>Oracle APEX Past, Present and Future</i>”. Of course everyone is curious what the APEX future might bring: Friendly URL’s, automated testing, more JSON, concurrent APEX versions, third party Oauth 2 authentication (think Facebook, Google), APEX app diff and more, a lot more, REST capabilities. And now we have to wait for APEX 5.2 … and that might take a while! </div>
</div>
<div>
<!--?xml version="1.0" encoding="UTF-8"?-->
<br />
<div style="text-align: justify;">
After this keynote, the conference split up in three tracks. After the coffee break I returned to to big theatre where Geertjan Wielenga talked about "<i>Finally Javascript is easy, with Oracle JET</i>”. I do like JET, but I am not sure all attendees shared Geertjan’s view. They probably were more convinced that APEX is way easier than JET....
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Next on stage, the last before lunch, was Alex Nuijten talking about "<i>JET Charts in the world of APEX</i>”. It was a very nice intro into the world of JET charts and Alex showed some peculiarities you have to be aware of. For instance, if you have multiple series, make sure all your queries return the same number of rows and in the same order. Or - as Alex is promoting - release your inner SQL Ninja and use the SQL power to get a proper result in one pass!
</div>
<div style="text-align: justify;">
<br /></div>
<div>
<a href="http://1.bp.blogspot.com/-ipRzW4DF_c8/WOFAHUlwhkI/AAAAAAAAEFE/Rjgh4IkxpjU6ORkJLnTnwX6-T27d9ze4wCK4B/s1600/270D3928-5EAD-4A6B-89C1-1B98A01CE529.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em; text-align: justify;"><img border="0" height="300" src="https://1.bp.blogspot.com/-ipRzW4DF_c8/WOFAHUlwhkI/AAAAAAAAEFE/Rjgh4IkxpjU6ORkJLnTnwX6-T27d9ze4wCK4B/s400/270D3928-5EAD-4A6B-89C1-1B98A01CE529.png" width="400" /></a></div>
<div>
<div style="text-align: justify;">
After a very good lunch (the quality of the food and drinks was a lot better than I have seen at other conferences, kudos for the organisation!), it was time for my first session, "<i>A Deep Dive into APEX JET Charts</i>”. In this session I am trying to convince the audience that looking into the JET documentation is really worthwhile: with just one or two lines of JavaScript you can add a whole new user experience layer to your pages: much more interactive and dynamic! And for the JET components that are currently not exposed in the APEX Builder, you can create a plugin. Not that hard either, a few lines of PL/SQL, a couple lines of JavaScript and a SQL statement can do the trick! I hoped people liked it. I did get very positive feedback of Geertjan, who is the Product Manager of JET, that he didn’t know it was that easy to modify or incorporate JET Charts in APEX! </div>
</div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<!--?xml version="1.0" encoding="UTF-8"?-->
<br />
<div>
<a href="http://3.bp.blogspot.com/-ZUAkuVbcKFQ/WOFBDQvyegI/AAAAAAAAEFg/0pigkIK7TAsIdJJlxpJX6KtBR539C5odgCK4B/s1600/C778CDE3-A30D-4603-9F4A-6E7C642E6E73.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="300" src="https://3.bp.blogspot.com/-ZUAkuVbcKFQ/WOFBDQvyegI/AAAAAAAAEFg/0pigkIK7TAsIdJJlxpJX6KtBR539C5odgCK4B/s400/C778CDE3-A30D-4603-9F4A-6E7C642E6E73.png" width="400" /></a>After my session I went to one of the other rooms 4 decks lower to listen to Matt Nolan. He was "<i>Unlocking the power of the APEX plugin architecture</i>”. Compared to previous performances Matt did an excellent job! I don’t know if the attendees will start developing plugins en masse, but it didn’t look too complicated (to me)!</div>
<div>
<br /></div>
<div>
<!--?xml version="1.0" encoding="UTF-8"?-->
<br />
<div>
The last keynote of the day was another JET Charts session, "<i>Advanced Charts in APEX 5.1 and beyond</i>” by Hilary Farrell. Inevitably there was some overlap with Alex’ and my session, but also enough new stuff (and around ⅔ of the attendees probably had missed those sessions anyway). Hilary showed some cool examples how to chance the look of the chart or add functionality by just a few lines of Javascript. And in APEX 5.2 … we will get upgraded jQuery (3.x) and jQuery UI (1.12.x) libraries and the latest and greatest version of JET (probably 3.x).
</div>
<div>
<br /></div>
<div>
<a href="http://4.bp.blogspot.com/-VuDV9Nzbjrc/WOFA5XN7kgI/AAAAAAAAEFY/KWaPyqJXbfYDca5_V8vavS8wcAExJla-gCK4B/s1600/6867F57E-8E4B-4DF1-8C20-050FBB79DFF0.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="225" src="https://4.bp.blogspot.com/-VuDV9Nzbjrc/WOFA5XN7kgI/AAAAAAAAEFY/KWaPyqJXbfYDca5_V8vavS8wcAExJla-gCK4B/s400/6867F57E-8E4B-4DF1-8C20-050FBB79DFF0.png" width="400" /></a>Because a presentation of Uber was cancelled there was an "<i>Ask the experts panel”</i> added to the agenda. Three of the seven experts are member of the SMART4Apex coöperation, but to be honest, most questions were addressed to and answered by Mike. As SMART4Apex we delivered seven (7!) presentations, enough to fill one of the three tracks for both days...</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
After this extra session, it was time for drinks and dinner. And because the weather was absolutely fabulous, we could enjoy it all on deck while watching the sun set over Rotterdam.</div>
<div>
A great end of a fantastic first day!</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-TPQ2l3w-AV0/WOFA0x3C2hI/AAAAAAAAEFQ/rUsAzUb04_I7VxavOZeXj-9-65Xt0oa6ACK4B/s1600/3FFDAF7B-1666-4683-A490-F7290541EFF5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://1.bp.blogspot.com/-TPQ2l3w-AV0/WOFA0x3C2hI/AAAAAAAAEFQ/rUsAzUb04_I7VxavOZeXj-9-65Xt0oa6ACK4B/s640/3FFDAF7B-1666-4683-A490-F7290541EFF5.png" width="569" /></a></div>
<div>
</div>
</div>
</div>
<span class="fullpost"></span>Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-66448664439975662882017-03-21T14:31:00.003+01:002017-03-21T14:31:43.853+01:00EECS - Oracle Exadate Express Cloud Service, Step 4 : APEX !So what are my experiences using EECS doing some APEX work?<span class="fullpost"></span><br />
<div>
<br /></div>
<div>
First of all, it is not blazing fast. Probably caused by the latency (as mentioned in my previous post). It is not slow, it is just ok('ish). </div>
<div>
<br /></div>
<div>
Second, and way more important, it runs <b><u>APEX 5.0.4</u></b>! So whatever Oracle advertises about "<i>Cloud first</i>" ... it doesn't seem to apply to APEX (or APEX on EECS). So this fact alone makes this environment <u>useless</u> for demo, development and presentation purposes at this moment. I heard through the grapevine that 5.1 will be available from April 25 - but that's still a month away. And that rumour could be false.</div>
<div>
<br /></div>
<div>
Third, because you don't have access to a SYS account, you can't install something like <a href="http://www.oraopensource.com/apex-sert/" target="_blank">APEX-SERT</a>. Maybe the install scripts can be adapted for the PDB_ADMIN account - I haven't looked into that yet.</div>
<div>
<br /></div>
<div>
Four : As mentioned in an earlier post, within an EECS PDB instance you get one and only one APEX Workspace. You can't add one (nor delete). You can create as much schema's as you wish, but you have to assign them all to that same Workspace. Thus if you need more Workspaces, you either buy multiple EECS instances or look into another - more expensive - Database Cloud offering.</div>
<div>
<br /></div>
<div>
Five : The default Authentication Scheme is the new "Oracle Cloud Identity Management" scheme. If you have admin rights for the Service you can add users in that layer. If you would like to use "APEX Users" as an Authentication Scheme, you need admin rights as well to create those users. Or create your own scheme if you like that better.</div>
<div>
<br /></div>
<div>
And finally, when you buy an EECS, you get a Customer Support (CSI) number. You should be able to use this number to access My Oracle Support (MOS) to get information, ask questions etc. But the first few weeks that number was not recognised by MOS. But as I tried it again today, it seems to "know" the number and registration is in progress.</div>
<div>
<br /></div>
<div>
Reading the points above and the previous post, you might think EECS is not that good. But that all depends on what you expect to get for your money. Compared with other Oracle Cloud products, EECS is rather cheap. At some points it could do better, but especially when the service is upgraded to APEX 5.1, I will use it more for demo / development and presentations. Multiple Workspaces would be second on my wish list. </div>
<div>
<br /></div>
<div>
And also Oracle announced a free APEX development environment a while ago - comparable to apex.oracle.com, but with that major difference that you are officially allowed to develop applications in that new environment (apex.oracle.com is targeted as a playground - despite all the development that is probably happening there....). </div>
Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-8187420037562037892017-02-16T20:28:00.002+01:002017-02-16T20:28:41.041+01:00EECS - Oracle Exadate Express Cloud Service, Step 3Once you have defined your users for your Express Cloud Service, all users with the role of Database Developer or higher can access the database Service Console. From here all database related actions can be started.<span class="fullpost"></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-ZjySXKXcEbM/WKW8jGaFG0I/AAAAAAAAAV4/OgYOgAwYYFARU8IHE460O6sL-gYbXVZpwCK4B/s1600/Screen%2BShot%2B2017-02-16%2Bat%2B15.49.48%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="461" src="https://1.bp.blogspot.com/-ZjySXKXcEbM/WKW8jGaFG0I/AAAAAAAAAV4/OgYOgAwYYFARU8IHE460O6sL-gYbXVZpwCK4B/s640/Screen%2BShot%2B2017-02-16%2Bat%2B15.49.48%2B.png" width="640" /></a></div>
<div>
The upper category, Web Access, brings you to the specified part of the APEX builder - more on that in the next post. In the lower category you can create database schema's. For our goal within smart4apex, I created a schema for every developer.</div>
<div>
<a href="http://4.bp.blogspot.com/-eWUU3-b2Ibw/WKW91KuMr3I/AAAAAAAAAWA/flZ9Dws1rEkmpyI_iP9fajvU2DqI6qlTQCK4B/s1600/Screen%2BShot%2B2017-02-16%2Bat%2B15.57.02%2B.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="290" src="https://4.bp.blogspot.com/-eWUU3-b2Ibw/WKW91KuMr3I/AAAAAAAAAWA/flZ9Dws1rEkmpyI_iP9fajvU2DqI6qlTQCK4B/s400/Screen%2BShot%2B2017-02-16%2Bat%2B15.57.02%2B.png" width="400" /></a>You can define whether the schema should be accessible from within APEX and creating an separate tablespace is optional.</div>
<div>
I haven't played around with the Document Store yet, so I have to skip that part. You can also set the password of the administrator of your PDB - the most privileged user within your PDB you have access to. Franck Pachot did an excellent <a href="https://blog.dbi-services.com/exadata-express-cloud-service-pdb_admin-privileges/" target="_blank">writeup</a> about the privileges of that user. I hoped the "Manage Application Express" would bring me to the "APEX Admin" environment - a.k.a. the "INTERNAL" workspace. But in fact it's a very downsized form of that. </div>
<div>
<a href="http://1.bp.blogspot.com/-YahBqcnGBOM/WKXA8Psls8I/AAAAAAAAAWY/dV9hljj_ZjoMDw3ezSIllMBK4MMvlKhKQCK4B/s1600/Screen%2BShot%2B2017-02-16%2Bat%2B16.10.18%2B.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="160" src="https://1.bp.blogspot.com/-YahBqcnGBOM/WKXA8Psls8I/AAAAAAAAAWY/dV9hljj_ZjoMDw3ezSIllMBK4MMvlKhKQCK4B/s400/Screen%2BShot%2B2017-02-16%2Bat%2B16.10.18%2B.png" width="400" /></a>You can enable Application Archiving (using the Application Archive packaged app), enable or disable workspace-to-schema associations and manage a very small number of workspace preferences.</div>
<div>
What I had expected and missing the most here is the option to create multiple workspaces! <b><u>So one EECS instance means one and only one APEX Workspace</u></b>! I still haven't found the document that lists that limitation. That was a real surprise for me.</div>
<div>
<br /></div>
<div>
The middle part of the Service Console is the most interesting one for this post. As we would like to access our EECS database like a local one! Assuming you have your favorite tool (SQL Developer, SQL Plus or sqlcl already installed), you have to download the "Client Credentials". On downloading you have to enter a password. You'll need that again to set up the connection using SQL Developer. So fire up a a recent version - I used 4.2 - of SQL Developer and define the connection, using "Cloud PDB" as the Connection Type, point the Configuration File to the downloaded zip file and enter the previous defined password. Make the connection and voila, you are running SQL Developer connecting to a 12.2.0.0.3 database in the cloud! </div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-xXMml6uN0vQ/WKX1Wx5StrI/AAAAAAAAAW8/bNZXDuh5gxwDKkOpLCTZRHXylLEfyu0eQCK4B/s1600/Screen%2BShot%2B2017-02-16%2Bat%2B19.52.44%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="424" src="https://1.bp.blogspot.com/-xXMml6uN0vQ/WKX1Wx5StrI/AAAAAAAAAW8/bNZXDuh5gxwDKkOpLCTZRHXylLEfyu0eQCK4B/s640/Screen%2BShot%2B2017-02-16%2Bat%2B19.52.44%2B.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
And while you can do most of the regular database development stuff, there is quite a list of restrictions, limitations and issues, all listed in <a href="https://docs.oracle.com/en/cloud/paas/exadata-express-cloud/csdbk/feature-restrictions-and-limitations.html" target="_blank">this document</a>. So no multimedia, spatial, RAS, to name a few.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Connecting with sqlcl is just as simple. Drop the zip-file in a location easy to find by sqlcl (as you can see in the screenshot above, I dropped it in the sqlcl/bin directory). Fire it up with "sqlcl /nolog" and define the cloud configuration using the zip file:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-family: Courier New, Courier, monospace; font-size: x-small;">set cloudconfig /Applications/oracle/sqlcl/bin/client_credentials.zip</span></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
As you don't want to type that line each and every time, it's convenient to put that in a login.sql file in the sqlcl directory. </div>
<div class="separator" style="clear: both; text-align: left;">
The zipfile is unzipped in a new directory on your file system - it seems to create a new directory each and every time you issue that command. I hope it gets cleaned up somewhere somehow eventually ...</div>
<div class="separator" style="clear: both; text-align: left;">
<a href="http://1.bp.blogspot.com/-04CL1bJX0eY/WKX60pfX-7I/AAAAAAAAAXM/IXW8UBB3y8gd4q7drXHJLJjVEg7muKk-wCK4B/s1600/Screen%2BShot%2B2017-02-16%2Bat%2B20.16.08%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="112" src="https://1.bp.blogspot.com/-04CL1bJX0eY/WKX60pfX-7I/AAAAAAAAAXM/IXW8UBB3y8gd4q7drXHJLJjVEg7muKk-wCK4B/s640/Screen%2BShot%2B2017-02-16%2Bat%2B20.16.08%2B.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
And is it fast? It feels pretty fast - hey it is an Exadata machine - , but now and then I noticed some latency in the connection. Not too bad and expected as the server is in the US. I'm sure that will be a lot better once there will be a real European roll-out. In the Service Console there is a little diagnostics tool hidden in the menu in the upper right corner where you can measure your latency. I measured it three times and the latency was around 180ms on average. So that's not super, but not really annoying in a sqlcl / SQL Developer environment. But what would happen if I start clicking around in the APEX Builder?</div>
Anonymoushttp://www.blogger.com/profile/03932233704705635918noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-81304571858768472282017-02-16T14:29:00.000+01:002017-02-16T14:29:41.205+01:00EECS - Oracle Exadata Express Cloud Service, Step 2Once you get your services up and running, you can log into your domain - in this screenhot "smart4apex" - on the Oracle Cloud.<span class="fullpost"></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-AIatoFMgS9A/WKWX4U1vAbI/AAAAAAAAAUo/fHOcrCoRQiw6mLHli5ERwgD3H0hW4a6qwCK4B/s1600/Screen%2BShot%2B2017-02-16%2Bat%2B13.12.48%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="424" src="https://4.bp.blogspot.com/-AIatoFMgS9A/WKWX4U1vAbI/AAAAAAAAAUo/fHOcrCoRQiw6mLHli5ERwgD3H0hW4a6qwCK4B/s640/Screen%2BShot%2B2017-02-16%2Bat%2B13.12.48%2B.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
Then you'll end up in the "My Services" dashboard. As I have only one, my dashboard looks quite clean, showing just this widget:</div>
<div class="separator" style="clear: both; text-align: left;">
<a href="http://3.bp.blogspot.com/-5F9XJnoVf1w/WKWY1kVJaaI/AAAAAAAAAU0/WqSrGfHEzAUqXk_lR6ojlwwQ_ImWoIraQCK4B/s1600/Screen%2BShot%2B2017-02-16%2Bat%2B13.19.05%2B.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="346" src="https://3.bp.blogspot.com/-5F9XJnoVf1w/WKWY1kVJaaI/AAAAAAAAAU0/WqSrGfHEzAUqXk_lR6ojlwwQ_ImWoIraQCK4B/s400/Screen%2BShot%2B2017-02-16%2Bat%2B13.19.05%2B.png" width="400" /></a>Just a few remarks for the designers of this: I am very curious why there is an extra "<i>(Number of ..."</i> in the chart title. The same superflous text is also on another location on that page.<br />And, more intriguing, what would <i>"0.0323 database instances"</i> mean? 1/0.0323 = 31. So something like days? But February has 28 days...<br />It would make way more sense to just show here how many instances I had on those day (so, "1" al over the place...).</div>
<div class="separator" style="clear: both; text-align: left;">
From this widget you can drill into the "Service Details", where you can set rules and alerts and see the (historical) status of your instance. Funny thing is, I noticed this:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<a href="http://3.bp.blogspot.com/-E3LzTSveX9Y/WKWbPhA51nI/AAAAAAAAAVE/xfoBPDXfWzkyc5ax8waeXkzSEYRUDaxAgCK4B/s1600/Screen%2BShot%2B2017-02-16%2Bat%2B13.28.30%2B.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="178" src="https://3.bp.blogspot.com/-E3LzTSveX9Y/WKWbPhA51nI/AAAAAAAAAVE/xfoBPDXfWzkyc5ax8waeXkzSEYRUDaxAgCK4B/s400/Screen%2BShot%2B2017-02-16%2Bat%2B13.28.30%2B.png" width="400" /></a>These figures indicate, I have a storage limit of 23Gb. But my service is based on 20Gb. So I get 3Gb "for free"!<br />Probably that's (more or less) used by Oracle itself (SYS, APEX etc.). So if you buy 20Gb, you really get 20Gb to store your own data!</div>
<div class="separator" style="clear: both; text-align: left;">
Now it is time to add some users to this instance. As we - as smart4apex - would like to use it as a development environment, I added all colleagues as a user. </div>
<div class="separator" style="clear: both; text-align: left;">
<a href="http://2.bp.blogspot.com/-WPICCf2Wrm8/WKWfZwSPWvI/AAAAAAAAAVU/0CsHDgi2Ycc89CraIAcLIim0xhMLWLlXQCK4B/s1600/Screen%2BShot%2B2017-02-16%2Bat%2B13.45.25%2B.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="250" src="https://2.bp.blogspot.com/-WPICCf2Wrm8/WKWfZwSPWvI/AAAAAAAAAVU/0CsHDgi2Ycc89CraIAcLIim0xhMLWLlXQCK4B/s400/Screen%2BShot%2B2017-02-16%2Bat%2B13.45.25%2B.png" width="400" /></a>And i can assign Roles to every user. There is a standard list of roles defined. From the <a href="http://docs.oracle.com/en/cloud/get-started/subscriptions-cloud/mmocs/managing-user-roles.html#GUID-7E6B287F-E296-4B9B-A50A-575D66D3FA7B" target="_blank">documentation</a> I figured out that the first two are entitled to add or modify other users, but what the privileges and restrictions of the other three are, I still have to discover. What I do know is, if you just assign the "Database User" role to a user, you can log in to the My Services page, but if you try to use the Exadata Express service, you get an "Acces Denied" message. You need at least Database Developer or Database Administrator to access the APEX builder (more about that in a later post).</div>
<div class="separator" style="clear: both; text-align: left;">
A nice feature is, you can upload a csv file to create multiple users and assign roles in batch. Saves you a lot of clicks....</div>
<div class="separator" style="clear: both; text-align: left;">
And every user you create receives a nice welcome email with a temporary password and a login link. Once you use that link, and login you end up on a (rather outdated - ADF faces built) page of Oracle Identity Self Service to change your password and register three (!) challenge questions. That is a challenge in itself ;-)</div>
<div class="separator" style="clear: both; text-align: left;">
Apart from the standard Roles, you can define "Custom Roles" and assign those to users. But any information about how to use these roles in an APEX application is welcome, as I couldn't find any documentation on this subject.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
In the next post, I'll go into the database (in)side of the service and in the subsequent one, the APEX development environment of the Exadata Express Cloud Service (EECS).</div>
<div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/03932233704705635918noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-6517510076010157242017-02-13T13:55:00.000+01:002017-02-14T09:41:32.124+01:00Intermittent ORA-06502 error when running APEX 5 on 12cWe upgraded our environment from 11.2 to 12c last week. This week we noticed an error in one of our APEX pages, a rather simple form for entering data. Nothing fancy, nothing spectacular - only a spectacular error when you tried to edit a record. <span class="fullpost"></span><br />
<div>
Running the page in debug mode (even on LEVEL9) wasn't very helpful as you can see below (10 points who immediately spots the error!).</div>
<div>
<a href="http://1.bp.blogspot.com/-ggm6wJCBrrw/WKGp32KeOTI/AAAAAAAAAUU/nlQO6VAB0QsUAs2cHvdskJv7X_2f17TKwCK4B/s1600/image001.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="350" src="https://1.bp.blogspot.com/-ggm6wJCBrrw/WKGp32KeOTI/AAAAAAAAAUU/nlQO6VAB0QsUAs2cHvdskJv7X_2f17TKwCK4B/s640/image001.png" width="640" /></a></div>
<div>
The error didn't occur for all records. It seems that a long(er) Code and Description field on the page resulted in an error more frequently. Also fiddling with a couple of the checkbox fields on the page had an effect.</div>
<div>
So I asked Twitter for help. And within a few minutes I got all kinds of tips and advice. But especially one, from <a href="https://twitter.com/PeterRaganitsch" target="_blank">Peter Raganitsch</a> was spot on. He suggested, looking at the debug output above, to take a look at the #CLOSE# position in the template. .</div>
<div>
My buttons are positioned in that #CLOSE# position, so I took a closer look at those. There were three that passed the Id, Code and Description to other pages as a parameter. So ... the longer the description ... the longer the generated URL would be. And as one of the checkboxes would toggle the availability of one of the buttons, that would also have an effect on the total length of the URL's in that region position. </div>
<div>
So I changed a little code, to pass only the Id's to the other pages (and add a select statement there to (re)fetch the Code and Description. And since then ... it runs as a breeze.<br />
<br /></div>
<div>
<b><u>Very weird though this was not on issue on 11.2, but is on 12c....</u></b><br />
<b><u><br /></u></b>
So what causes this issue, what is the difference?<br />
The answer is: the <b><u>checksum</u></b>!<br />
<br />
If I create a Report and a Form on DEMO_CUSTOMERS, using the CUSTOMER_ID as the Primary Key, the URL has this checksum on 11.2 : " &cs=3Fqn7QOYkP-TDSLkssvzU7i7QL1E "<br />
On 12c however, the checksum is :" &cs=36Drf3ULxGXswIlT5btBujofcmgwdjJsr-luSfpLjSMpXP71DILliWBz31qkpRJHyPj2RS8iLce5qNkXSSeGbvQ "<br />
And that was probably enough extra length to pass a certain varchar2(<some number here>) in the APEX code that generates the page output....<br />
<br />
<br /></div>
Anonymoushttp://www.blogger.com/profile/03932233704705635918noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-43128490660200510642017-02-09T15:44:00.004+01:002017-02-09T15:44:34.611+01:00First steps using the Oracle Exadata Express Cloud ServiceThe <a href="https://docs.oracle.com/en/cloud/paas/exadata-express-cloud/index.html" target="_blank">Oracle Exadata Express Cloud Service</a> was announced at Oracle Open World 2016. This service looks like an excellent use case for Oracle Database development purposes as well as setting up a test environment. And even for small to medium sized production situations it looks very promising as you can see in the table below. Especially the cheapest option offers a lot of bang for your bucks.<span class="fullpost"></span><br />
<div>
<a href="http://4.bp.blogspot.com/-3VkJJ11V3EE/WJx2zRqAJ5I/AAAAAAAAATQ/tqXkm2cFSbwtm_DJzY36iR9ER9YwARfOgCK4B/s1600/Screen%2BShot%2B2017-02-02%2Bat%2B15.58.01%2B.png" imageanchor="1"><img border="0" height="300" src="https://4.bp.blogspot.com/-3VkJJ11V3EE/WJx2zRqAJ5I/AAAAAAAAATQ/tqXkm2cFSbwtm_DJzY36iR9ER9YwARfOgCK4B/s640/Screen%2BShot%2B2017-02-02%2Bat%2B15.58.01%2B.png" width="640" /></a></div>
<div>
But alas, this service was not as widely available as I expected. The first months it was US-only and you could only figure out by ordering the service and then you would find out on the last page that you should "contact your Oracle sales rep". I sincerely doubt whether he or she could provide you with that service, so I just waited until it would be more generally available.</div>
<div>
And since a few weeks the Oracle Exadata Express Cloud Service is also available in Europe - I don't know about other parts of the world. A missed chance though that Oracle didn't announce that properly. You can only find out by trial-and-error. And to be clear, your data will still reside on US soil...</div>
<div>
<a href="http://1.bp.blogspot.com/-FWJE1waNzaI/WJx43lbQUnI/AAAAAAAAATg/3a21V7rM4OcJKLLHSKsdY1PfGw3s76MhQCK4B/s1600/Screen%2BShot%2B2017-02-02%2Bat%2B15.59.31%2B.png" imageanchor="1"><img border="0" height="296" src="https://1.bp.blogspot.com/-FWJE1waNzaI/WJx43lbQUnI/AAAAAAAAATg/3a21V7rM4OcJKLLHSKsdY1PfGw3s76MhQCK4B/s640/Screen%2BShot%2B2017-02-02%2Bat%2B15.59.31%2B.png" width="640" /></a></div>
<div>
<br /></div>
<div>
After entering my credit card number I made my purchase irreversible and I immediately received an order confirmation. As I expected these cloud things go very fast I started waiting for the mail that would say that my service was ready. Not on that same day. Not on the next day. But two days later, I finally received my "<i>Welcome to Oracle Cloud</i>" email. If any webshop that ships physical items would move that slow, they would be out of business pretty soon. </div>
<div>
But now I was ready to go! First I had to create my "service instance". No clue what Oracle did the last two days, but effectively I only had access to a console where I could create such an instance. Why doesn't Oracle create that instance immediately? Because in the end what I ordered was a service instance - not the option to create one...</div>
<div>
<br /></div>
<div>
Nevertheless, with a few clicks I had requested my instance as seen in the screen below (notice the little missing icons / images on the upper left and right buttons...).</div>
<div>
<a href="http://1.bp.blogspot.com/-0Cbn1UTESdo/WJx8Fqs8RiI/AAAAAAAAATs/Q-76DcqzwMEVQwPCq2Gs1vyaZwT1bNmJQCK4B/s1600/Screen%2BShot%2B2017-02-04%2Bat%2B11.14.52%2B.png" imageanchor="1"><img border="0" height="302" src="https://1.bp.blogspot.com/-0Cbn1UTESdo/WJx8Fqs8RiI/AAAAAAAAATs/Q-76DcqzwMEVQwPCq2Gs1vyaZwT1bNmJQCK4B/s640/Screen%2BShot%2B2017-02-04%2Bat%2B11.14.52%2B.png" width="640" /></a></div>
<div>
The only thing that had to be done was firing up a 20Gb Pluggable Database (PDB). On my VM that would take a few minutes. So on an Exadata beast ... a minute or maybe two?</div>
<div>
<br /></div>
<div>
Alas, no. For some reason it took 4 (four!) full days. All those days I logged in to see whether there was any progress and all I saw was:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://4.bp.blogspot.com/-OcIW1kC6HsA/WJx80AN5feI/AAAAAAAAAT4/qY70JN6qPD8bJ19v76A3ZHMxEnLBVZtaACK4B/s1600/Screen%2BShot%2B2017-02-06%2Bat%2B16.14.51%2B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="144" src="https://4.bp.blogspot.com/-OcIW1kC6HsA/WJx80AN5feI/AAAAAAAAAT4/qY70JN6qPD8bJ19v76A3ZHMxEnLBVZtaACK4B/s320/Screen%2BShot%2B2017-02-06%2Bat%2B16.14.51%2B.png" width="320" /></a></div>
<div>
After the Oracle internal issues were solved - the PDB was created super fast, but the registration process failed somewhere - I was finally up and running. </div>
<div>
<br /></div>
<div>
It took a while, but I hope it was worth waiting for ....</div>
Anonymoushttp://www.blogger.com/profile/03932233704705635918noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-62582569535544188532016-10-13T15:59:00.001+02:002016-10-13T15:59:33.291+02:00OTN Nordic ACE Tour : A Tale of Three Cities<div style="text-align: center;">
<b><u><i><span style="font-size: large;">3 days - 3 cities - 6 presentations (4 different ones)</span></i></u></b></div>
<br />
My tour started on Monday afternoon when I left my house around 1.30PM to get on the bus to the train station. The 2PM train left exactly on time for the Amsterdam airport. I arrived at Schiphol around 3:30PM, perfectly on time to catch the 5PM flight to Copenhagen.<span class="fullpost"></span><br />
The flight to Copenhagen was very uneventful. From the airport I took the train / metro into the city. I already received an email message from one of my co-travellers, Martin Widlake, who was sitting al alone in a bar downtown. The poor guy. So I rescued him and was briefly after that joined by Sten Vesterli - another ACE Director from the organising country. After just one beer we head over to the restaurant where we met the rest of the board of the Danish Oracle User Group and the other two co-travellers, John King and Ludovico Caldera, for a very nice dinner.<br />
As the city was crowded due to another - slightly larger - conference all hotel rooms were either booked or extremely expensive or far far away, the speakers were invited to stay the night at one of the board member's houses. I had the honour to stay at Sten's place and had a good night sleep.<br />
<br />
The next morning we had a short drive to the Oracle office were breakfast was served and about 70 attendees showed up for the sessions. My two sessions - about JavaScript and APEX 5.1 - were well attended and went smooth (in my opinion). The lunch was excellent and the Miracle beer during the afternoon "coffee" break was very welcome. When the last speaker was done we went straight to the airport for our flight to Oslo. We killed the time by talking and drinking just one beer.<br />
In Oslo we took the high speed train into town, walked a few 100 metres to our hotel and were welcomed by Øyvind. After dropping off our bags we quickly went to something called the "Beer Palace" at a very short distance of the hotel. But before we enjoyed the local drinks we went to the burger joint next door to grab a bite (we took it back into the bar to be exact). Not much later Ann-Sofie (another OUGN Board member) joined us as well. And after a drink or two we called it a night.<br />
<br />
The venue in Oslo was very close by, so after an excellent breakfast we walked down there in just 5 minutes. I estimate about 50 attendees here. All 4 presenters had to deliver 3 presentations each - so at the end of the day we were quite "done". We declined the invitation to "have just one beer" (sorry Bjørn) and went to the train station and straight to the airport. While waiting for our next flight to Helsinki we grabbed a beer and a sandwich.<br />
The flight to Helsinki took just over an hour, but included a time zone shift. When we arrived at the airport we took a shared cab that brought us to our hotel downtown were we arrived around 10:30PM. And the bar would close at 11PM. So a quick check-in, drop off the bags and use the remaining bar time properly.<br />
<br />
<a href="https://4.bp.blogspot.com/-kyNsfI9EyRw/V_-RytCP5uI/AAAAAAAAEBI/nzXevDgYYCI7jZXP5ZFWqIiY-DyfwxJiACLcB/s1600/RoelH_2016-Oct-13.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="200" src="https://4.bp.blogspot.com/-kyNsfI9EyRw/V_-RytCP5uI/AAAAAAAAEBI/nzXevDgYYCI7jZXP5ZFWqIiY-DyfwxJiACLcB/s200/RoelH_2016-Oct-13.jpg" width="150" /></a>For the next morning Heli had invited us for a tour through downtown Helsinki. So after breakfast we walked around enjoying the cool air (just above freezing) of Finland! This is were the local "Glögi" was more than welcome!<br />
After returning safely back to the hotel, grabbed our stuff, checked out and walked 30 meteres to the Accenture office where the OUGF event was held. The event started with an excellent lunch, after which myself and Martin did our talks. And there is where our paths split. While Martin and I left for the airport to catch our 6PM'ish flights back home, John and Ludo stil had to do their presentations and will later catch their flights to the last stop, Stockholm.<br />
<br />
As this was my very first ACED tour, I have to say it was quite an experience. It feels like: get into a taxi, onto a plane, into a train, find the hotel, do a presentation and then start this sequence all over again. So busy, very busy, But with the good company of Martin, John and Ludo it actually quite enjoyable!<br />
Thanks to the local user groups for inviting me and thanks for the OTN ACE Program for the support! It was a blast.<br />
<i>(now in the lounge waiting for my flight back home....)</i>Anonymoushttp://www.blogger.com/profile/03932233704705635918noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-65838758322950588472016-10-03T20:52:00.000+02:002016-10-03T20:52:35.853+02:00APEX 5.1 NF : Show Custom Application Icons in the APEX BuilderIt just isn't fair. If you install a Packaged Application - and you should because there is a lot to learn from those apps - in your APEX Workspace they all have a cool looking icon. And your own applications, just show up with two characters in a (random?) coloured box. Why can't I have a nice looking icon for my application?<br />
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-n-qx19BPBMQ/V_Kd13-Z6yI/AAAAAAAAD_o/ox9pSxDi9UQjlU3qkuOCGqASyI5oLHotQCLcB/s1600/screenshot1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="335" src="https://1.bp.blogspot.com/-n-qx19BPBMQ/V_Kd13-Z6yI/AAAAAAAAD_o/ox9pSxDi9UQjlU3qkuOCGqASyI5oLHotQCLcB/s640/screenshot1.png" width="640" /></a></div>
<div>
<br />
In APEX 5.1 you can!</div>
<div>
Upload an image as a <u>Static Application File</u>. According to the help it should by 64 x 64 pixels, but I noticed it can be larger as well. It should be square though to show up nicely. Don't put it in a (sub)directory, it should be placed in the 'root' folder.</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/-O7yYWXxghEA/V_KfZvfvakI/AAAAAAAAD_0/2JGLBFI0nOoxS9jHOCVpDk_N-zUemYYIACLcB/s1600/screenshot2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="384" src="https://3.bp.blogspot.com/-O7yYWXxghEA/V_KfZvfvakI/AAAAAAAAD_0/2JGLBFI0nOoxS9jHOCVpDk_N-zUemYYIACLcB/s640/screenshot2.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Then edit the Application Definition (either via Shared Components > Application Definition Attributes or by clicking on the "Edit Application Attributes" on the Application Builder home screen. Then scroll down until you see the "Icon File Name" property. Enter the name of the uploaded file there - but don't reference #APP_IMAGES# as you're used to! </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-yEcf3gBT9pQ/V_Kn68yTMjI/AAAAAAAAEAU/FjIN9dyRcYARLgRXcmRTSRWDhY8l64hPACLcB/s1600/screenshot3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="310" src="https://4.bp.blogspot.com/-yEcf3gBT9pQ/V_Kn68yTMjI/AAAAAAAAEAU/FjIN9dyRcYARLgRXcmRTSRWDhY8l64hPACLcB/s640/screenshot3.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Now switch back to the APEX App Builder Home screen ... and your application is there with your custom application icon!</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-OJjDxUlfowQ/V_KoWgdcE9I/AAAAAAAAEAY/AC9gWIJ94S81-OUlMWi6b7f3Tu0Mr9dtgCLcB/s1600/screenshot4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="330" src="https://1.bp.blogspot.com/-OJjDxUlfowQ/V_KoWgdcE9I/AAAAAAAAEAY/AC9gWIJ94S81-OUlMWi6b7f3Tu0Mr9dtgCLcB/s640/screenshot4.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div>
So in 5.1 there's no difference anymore in the presentation of Packaged Applications or your own... they can all look good!<br />
<span class="fullpost"></span><br />
<div>
<br /></div>
</div>
</div>
Roelhttp://www.blogger.com/profile/03592016581410637420noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-13401771495321861662016-10-03T12:00:00.000+02:002016-10-03T12:00:15.581+02:00It's happening again ... running for the ODTUG Board of Directors 😉For the third time in a row I'll be running for ODTUG's Board of Directors. But after ending as a runner up twice, I am sure I'm going to make it this time!<b><i><u> But not without your help!</u></i></b><span class="fullpost"></span><br />
<div>
<b><i><u><a href="https://www.blogger.com/"></a><span id="goog_1792848467"></span><span id="goog_1792848468"></span><br /></u></i></b></div>
<div>
My campaign statement this year is:</div>
<div>
<br /></div>
<div>
<i>I have been attending and presenting at Kscope conferences since 2007. This not only resulted in a vast amount of knowledge, but also - and even more important - a huge number of friends from all over the globe. I want to see ODTUG grow and spread this community feeling even more! </i></div>
<div>
<i><br /></i></div>
<div>
<i>My experience as an attendee, presenter and content lead has provided the basic foundation to be a director. Next to that, my personality and (global) network will be beneficial to the whole board and organization. </i></div>
<div>
<i><br /></i></div>
<div>
<i>Since March I have served on the Board of Directors in a limited term for a Director who stepped down due to a career change. This has allowed me to have unique insight of all the things that are going on in and around the ODTUG organization. As the train was already rolling full steam ahead, I tried to lend a helping hand wherever I could. </i></div>
<div>
<i><br /></i></div>
<div>
<i>Now, I seek your support as a full term elected member of the board so that I may become even more involved by heading a standing committee and serving a full two year term. I am really looking forward to making a difference and helping the organization continue to move forward. I feel as if these last six months were a sort of internship that can give me a jump start for the next term! </i></div>
<div>
<i><br /></i></div>
<div>
<i>So don’t hesitate and vote for Roel! </i></div>
<div>
<i><br /></i></div>
<div>
See <a href="http://www.roelforodtug.com/" target="_blank">http://www.roelforodtug.com</a> for more details. You can even sign up as a supporter!</div>
<div>
But even more important ... if you are eligible to vote: Please do so! </div>
<div>
You can vote from October 4 to 25.</div>
Anonymoushttp://www.blogger.com/profile/03932233704705635918noreply@blogger.com0tag:blogger.com,1999:blog-20567072.post-56252261557700375032016-09-29T15:32:00.001+02:002016-09-29T15:32:45.357+02:00OTN Nordic ACE TourIn 1.5 week I'll be travelling to and through the Nordics as one of the speakers during the <a href="http://www.nordicacetour.com/" target="_blank">OTN Nordic ACE Tour</a>. It will be quite a busy schedule!<br />
<br />
On Monday afternoon I'll be flying to Copenhagen and will meet my fellow travellers for the week during a dinner hosted by the Danish Oracle User Group. On Tuesday I have two sessions during the event: One about <i>APEX JavaScript API's</i> and one about <i>APEX 5.1</i>. At the end of the day it's off to the airport to catch the flight to Oslo around 7PM. I foresee an airport dinner here....<br />
<br />
Wednesday in Oslo I have even three sessions: About the <i>Universal Theme</i>, <i>APEX 5.1 </i>again and <i>APEX and JET</i>. At the end of the day it's off to the airport again to catch the flight to Helsinki around 7PM. Probably another airport dinner here ....<br />
<br />
Thursday morning <a href="https://helifromfinland.wordpress.com/" target="_blank">Heli</a> will take us for a short (but brisk) walk through Helsinki, before the Finnish event starts around noon. Here I have only one session, about<i> APEX 5.1</i> again. And again ... back to the airport to catch the flight back to Amsterdam around 7PM. Airport dinner anyone?<br />
<br />
So it'll be a very busy few days, but fun as well!<br />
And of course thanks to OTN for supporting this trip.Anonymoushttp://www.blogger.com/profile/03932233704705635918noreply@blogger.com0