<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="https://cloud.jaris.fi/ords/r/jaris/blog/pgm?request=application_process%3Drss.xsl" media="screen"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <atom:link rel="self" href="https://cloud.jaris.fi/ords/r/jaris/blog/pgm?request=application_process%3Drss.xml" type="application/rss+xml"/>
    <title>Jari&apos;s APEX Blog</title>
    <link>https://cloud.jaris.fi/ords/r/jaris/blog/home</link>
    <description>About Oracle Application Express (APEX) and Related Stuff</description>
    <language>en</language>
    <item>
      <title>Authenticate APEX workspace with Oracle cloud identity domain</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Tips and Tricks</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=20250309143806936499</link>
      <description>Integrating Oracle Cloud Identity Domain with your APEX environment offers a streamlined and secure user management experience, enabling seamless single sign-on capabilities. This blog post provides a step-by-step guide to authenticating your Oracle APEX services, ensuring you harness advanced identity management features while enhancing security and user experience.</description>
      <content:encoded><![CDATA[<div class="ck-content"><p>By integrating Oracle Cloud Identity Domain into your APEX environment, you create a robust authentication mechanism that not only streamlines user management but also enhances security. This seamless integration allows organizations to leverage advanced identity management features, enabling secure single sign-on (SSO) capabilities while simplifying user experience. In this blog post, we will explore the steps to authenticate your Oracle APEX development and administration services with Oracle Cloud Identity Domain.</p><p>Access your Oracle Cloud tenancy and go to <i>Identity &amp; Security → Domains</i>. Choose the Default domain and make a note of the <i>Domain URL</i>. You'll need this information later when setting up the APEX authentication scheme.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/domain_overview.png"></p><p>First, we need to create an integrated application to authenticate APEX workspaces. Navigate to the <i>Integrated Applications</i> section and click the "<i>Add Application</i>" button.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/integrated_applications.png"></p><p>Select <i>Confidential Application</i> and click “<i>Launch workflow</i>” button.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/add_application.png"></p><p>Enter <i>Name</i> for application. Optionally add also <i>Description</i> and upload <i>Application Icon</i>.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/add_application_details.png"></p><p>Enter to <i>Application URL</i> “https://<i>&lt;your ORDS sever host&gt;</i>/ords/r/apex/workspace-sign-in/select-workspace” and click "<i>Next</i>" button.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/add_application_details_2.png"></p><p>Choose the option to <i>Configure this application as client now</i> and input the required details.</p><ul><li>Make sure to check the <i><strong>Authorization code</strong></i> checkbox.</li><li>For the <i><strong>Redirect URL</strong></i>, enter: <i>https://&lt;your ORDS server host&gt;/ords/apex_authentication.callback</i>.</li><li>For the <i><strong>Post-logout redirect URL</strong></i>, use: <i>https://&lt;your ORDS server host&gt;/ords/apex</i>.</li></ul><p>After that, click the "<i>Next</i>" button, and on the following page of the wizard, click the “<i>Finish</i>” button.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/configure_oauth.png"></p><p>Once the application is created, click the "<i>Activate</i>" button.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/activate_application.png"></p><p>Scroll down to <i>Resource server configuration</i> and find the <i><strong>Client ID</strong></i> and make a note of it. Then, click on <i>Show Secret</i> to reveal the <i><strong>Client Secret</strong></i> and jot it down. You will need these details later when setting up the APEX authentication scheme.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/resource_server_configuration.png"></p><p>Before configuring authentication schemes in the APEX development environment, log in to the database as an admin user and check the current value of <i><strong>APEX_BUILDER_AUTHENTICATION</strong></i> from the <i>APEX_INSTANCE_PARAMETERS</i> view. Make a note of this value in case you need to revert the authentication scheme to its original state.</p><pre><code class="language-sql">select
 name
, value
from APEX_INSTANCE_PARAMETERS
where 1 = 1
 and name = ‘APEX_BUILDER_AUTHENTICATION’
;</code></pre><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/apex_builder_authentication.png"></p><p>To authorize access to the APEX development environment and administration services, you must create users in APEX workspaces, even when using external authentication.</p><p>APEX documentation says:</p><blockquote><p><a href="https://docs.oracle.com/en/database/oracle/apex/24.2/aeadm/configuring-authentication-schemes-for-instance.html&#35;GUID-A1EFFB66-8D37-437C-9183-A230213CBDCC" target="_blank">Even for external authentication schemes (such as HTTP Header Variable), make sure that users exist as developers or administrators in your workspace. Otherwise, APEX will not be able to verify in which workspace a user is allowed to work.</a></p></blockquote><p>Log in to APEX Administration Services and go to <i>Manage Workspaces → Manage Developers and Users</i>. Create a new user in the <strong>INTERNAL</strong> workspace, using the same username as your Oracle Cloud Identity Domain account. Ensure that the <i>User is an administrator</i> option is set to <i>Yes</i>.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/new_apex_admin_user.png"></p><p>You can also create your user in other workspaces as needed or update your existing users username to match your Oracle Cloud username.</p><p>Next navigate <i>Manage Instance → Security</i> and scroll down and edit <i>Social Sign-In</i> authorization scheme.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/security_settings.png"></p><p>Enter needed information:</p><ul><li><i><strong>Client ID</strong></i>: &lt;Identity Domain application client id noted down earlier&gt;</li><li><i><strong>Client Secret</strong></i>: &lt;Identity Domain application client secret noted down earlier&gt;</li><li><i><strong>Confirm Client Secret</strong></i>: &lt;Identity Domain application client secret noted down earlier&gt;</li><li><i><strong>Authentication Provider</strong></i>: OpenID Connection Provider</li><li><i><strong>Discovery UR</strong></i>L: &lt;Domain URL you noted down earlier&gt;/.well-known/openid-configuration</li><li><i><strong>Scope</strong></i>: profile</li><li><i><strong>Username Attribute</strong></i>: sub</li><li><i><strong>Verify Username</strong></i>: Yes</li></ul><p>Then click "<i>Apply Changes</i>".</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/edit_authentication.png"></p><p>Edit again <i>Social Sign-In</i> authorization scheme and click <i>Make Current Scheme</i>.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/make_current_authorization_scheme.png"></p><p>Click “<i>OK</i>” to confirm authorization scheme change.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/confirm_authenticate_scheme_change.png"></p><p>Log out of the APEX Administration Service, then return to your APEX instance login URL.</p><p>Now you should see Oracle cloud login screen.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/oci_login.png"></p><p>Log in to see a list of workspaces you are authorized to access.</p><p><img src="https://static.jaris.fi/b/v1/i/P20250309143806936499/workspace_select.png"></p><p>If an issue arises, you can restore the APEX instance authentication scheme by logging into the database as an admin user and executing the following command. Replace <i>DB</i> with the value you obtained earlier from the <i>APEX_INSTANCE_PARAMETERS</i> view query.</p><pre><code class="language-sql">begin
 apex_instance_admin.set_parameter( 'APEX_BUILDER_AUTHENTICATION', 'DB' );
end;
/</code></pre></div>]]></content:encoded>
      <pubDate>Sun, 09 Mar 2025 17:37:00 GMT</pubDate>
      <guid isPermaLink="false">20250309143806936499</guid>
    </item>
    <item>
      <title>Report refresh interval using dynamic action</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Tips and Tricks</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=20231119172012444607</link>
      <description>How refresh classic or interactive report without writing any line of JavaScript.</description>
      <content:encoded><![CDATA[<div class="ck-content"><p>Here is how you can easily set report refresh interval just using dynamic action without writing any line of JavaScript.</p><p>You need set report attribute Lazy Loading to on.</p><p><img src="https://static.jaris.fi/b/v1/i/P20231119172012444607/report_attributes.png" alt="Report Lazy Loading attribute"></p><p>Create dynamic action:</p><ul><li>Type: Debounce</li><li>Time: &lt; enter time in milliseconds how often report is refreshed, e.g. 10000 for every 10 seconds &gt;</li><li>Event: After Refresh</li><li>Selection: Type: Region</li><li>Region: &lt; select report region to refresh &gt;</li></ul><p><img src="https://static.jaris.fi/b/v1/i/P20231119172012444607/dynamic_action.png" alt="Dynamic action"></p><p>Create true action</p><ul><li>Action: Refresh</li><li>Selection Type: Triggering Element</li><li>Fire on Initialization: Off</li></ul><p><img src="https://static.jaris.fi/b/v1/i/P20231119172012444607/dynamic_action_true_action.png" alt="Dynamic action true action"></p><p>See <a href="https://apex.oracle.com/pls/apex/f?p=39006:76" target="_blank">working example</a>.</p><p>If you don't want use report Lazy loading, you need write little bit JavaScript and define dynamic action bit differently:</p><ul><li>Type: Debounce</li><li>Time: &lt; enter time in milliseconds how often report is refreshed, e.g. 10000 for every 10 seconds &gt;</li><li>Event: Custom</li><li>Custom Event: custom-event-refresh</li><li>Selection: Type: Region</li><li>Region: &lt; select report region to refresh &gt;</li></ul><p><img src="https://static.jaris.fi/b/v1/i/P20231119172012444607/dynamic_action_alternative.png" alt="Dynamic action alternative"></p><p>Create true action:</p><ul><li>Action: Refresh</li><li>Selection Type: Triggering Element</li><li>Fire on Initialization: Off</li></ul><p><img src="https://static.jaris.fi/b/v1/i/P20231119172012444607/dynamic_action_true_action.png" alt="Dynamic action first true action"></p><p>Create another true action:</p><ul><li>Action: Execute JavaScript Code</li><li>Selection Type: Triggering Element</li><li>Fire on Initialization: On</li><li>Code:</li></ul><pre><code class="language-js">this.affectedElements.trigger( "custom-event-refresh" );</code></pre><p><img src="https://static.jaris.fi/b/v1/i/P20231119172012444607/dynamic_action_another_true_action.png" alt="Dynamic action second true action"></p></div>]]></content:encoded>
      <pubDate>Sun, 19 Nov 2023 18:35:00 GMT</pubDate>
      <guid isPermaLink="false">20231119172012444607</guid>
    </item>
    <item>
      <title>Drag &amp; Drop Interactive Grid rows</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Tips and Tricks</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=20230819081331172765</link>
      <description>Arrange the Interactive Grid rows by drag &amp; drop with the mouse.</description>
      <content:encoded><![CDATA[<div class="ck-content"><p>Here's an example that lets you drag and drop the rows of an interactive grid to arrange the records the way you want. A table used in an interactive grid must have a column that stores the sort order. If your table doesn't already have a column that can be used for this, edit the table and add a new column.</p><pre><code class="language-sql">alter table &lt;your table name&gt; add (display_seq number);
update &lt;your table name&gt; set display_seq = rownum;
alter table &lt;your table name&gt; modify display_seq not null;</code></pre><p>First, create an editable interactive grid and run the page. Sort the interactive grid in ascending order by display sequence column.</p><p><img src="https://static.jaris.fi/b/v1/i/P20230819081331172765/sort_ig.png"></p><p>Then hide the display sequence column from the report.</p><p><img src="https://static.jaris.fi/b/v1/i/P20230819081331172765/hide_display_sequence_column.png"></p><p>Save the report.</p><p><img src="https://static.jaris.fi/b/v1/i/P20230819081331172765/save_report_default.png"></p><p>Create new column to the interactive grid .</p><p><img src="https://static.jaris.fi/b/v1/i/P20230819081331172765/add_ig_column.png"></p><p>Enter a column name. Change the type to “HTML expression”. Add to the HTML Expression:</p><pre><code class="language-html language-sql">&lt;span aria-hidden="true" class="fa fa-sort z-sortable--handle"&gt;&lt;/span&gt;</code></pre><p>Change the Source Type to None. Add a Column Initialization JavaScript Function:</p><pre><code class="language-js">sortableIG.initHandleColumn</code></pre><p><img src="https://static.jaris.fi/b/v1/i/P20230819081331172765/drag_handle_column.png"></p><p>Edit the other interactive grid columns and disable Sorting for all columns except the display sequence column.</p><p><img src="https://static.jaris.fi/b/v1/i/P20230819081331172765/sort_off_from_columns.png"></p><p>Edit the display sequence column and add a Column Initialization JavaScript Function:</p><pre><code class="language-js">sortableIG.initSequenceColumn</code></pre><p><img src="https://static.jaris.fi/b/v1/i/P20230819081331172765/sequence_column.png"></p><p>Add to the interactive grid region Initialization JavaScript Function:</p><pre><code class="language-js">sortableIG.initRegion</code></pre><p><img src="https://static.jaris.fi/b/v1/i/P20230819081331172765/region_initialization_javascript_function.png">&nbsp;</p><p>Add to the page JavaScript Function and Global Variable Declaration:</p><pre><code class="language-js">// create a namespace for sortable IG
var sortableIG = {};

( function( $, region, sortableIG ) {

  // Set defaults
  sortableIG.options = {
    region: {
      defaultModelOptions: {
        sequenceStep: 10
      }
      ,defaultGridViewOptions: {
        reorderColumns: false
      }
    }
    ,handleColumn: {
      defaultGridColumnOptions: {
        noHeaderActivate: true
      }
      ,layout: {
        columnAlignment: "center"
        ,noStretch: true
      }
    }
    ,sequenceColumn: {
      features: {
      	sort: true
        ,canHide: false
        ,aggregate: false
        ,compute: false
        ,controlBreak: false
        ,groupBy: false
        ,highlight: false
        ,pivot: false
      }
    }
  }

  // sortable IG handle column Initialization code
  // call function in IG column Advanced: Column JavaScript Initialization Code
  sortableIG.initHandleColumn = function( options ) {

    return $.extend( true, options, sortableIG.options.handleColumn );

  }

  // sortable IG sequence column Initialization code
  // call function in IG column Advanced: Column JavaScript Initialization Code
  sortableIG.initSequenceColumn = function( options ) {

		// get sequence column name and store it for region Initialization JavaScript Function
    sortableIG.options.region.defaultModelOptions.sequenceField = options.name;

    return $.extend( true, options, sortableIG.options.sequenceColumn );

  }

  // sortable IG initialization code
  // call function in IG region Advanced: JavaScript Initialization Code
  sortableIG.initRegion = function( options ) {

    $( $x( options.regionStaticId ) ).on( "interactivegridviewchange interactivegridreportsettingschange", function( _event, ui ) {

      var view          = region( this.id ).call( "getViews", "grid" )
        , sortableArea$ = $( $x( this.id ) )
        , helperBgClass = "u-color-29-bg"
      ;

      // https://api.jqueryui.com/sortable/
      sortableArea$.sortable({
        handle:       ".z-sortable--handle"
        ,items:       "tr:has(.z-sortable--handle)"
        ,axis:        "y"
        ,tolerance:   "pointer"
        ,scrollSpeed: 8
        ,placeholder: "a-GV-cell u-color-1-bg"
        
        ,helper: function( _event, ui ){
          ui.children().each( function(){
            $( this ).width( $( this ).width() ).addClass( helperBgClass )
          })
          return ui;
        }
        
        ,update: function( _event, ui ){
          
          var model = view.model
            , item$ = $( ui.item )
            // get moved record
            , item = ( item$.length ) ? model.getRecord( item$.data( "id" ) ) : null
            // get next or previous row from visible table depending is record moved up or down
            , after$ = ( ui.originalPosition.top &gt; ui.position.top ) ? item$.next() : item$.prev()
            , after = ( after$.length ) ? model.getRecord( after$.data( "id" ) ) : null
          ;
          
          // remove helper background class, if row isn't actually moved
          item$.children().removeClass( helperBgClass );
          // check if we have found record and place where record is moved
          if( item &amp;&amp; after ){
            // if record is moved upwards, we need get previous record from model
            after = ( ui.originalPosition.top &gt; ui.position.top ) ? model.recordAt( model.indexOf( after ) - 1 ) : after;
            // move record
             if( item !== after ){
              model.moveRecords( [item], null, after );
            }
          }
          
        }
      
      });

    });

    return $.extend( true, options, sortableIG.options.region );

  }

})( apex.jQuery, apex.region, sortableIG );</code></pre><p>Add to the Page CSS Inline:</p><pre><code class="language-css">.z-sortable--handle{
  cursor: move;
}
.ui-sortable-helper{
  display: inline-flex;
}</code></pre><p><img src="https://static.jaris.fi/b/v1/i/P20230819081331172765/page_attributes.png"></p><p>Now when you run a page, you can drag and drop to arrange the rows of the interactive grid from the handle column.</p><p><a href="https://apex.oracle.com/pls/apex/f?p=39006:35" target="_blank">See working example in apex.oracle.com.</a></p><p>If you need support for touch events, you can try <a href="https://github.com/furf/jquery-ui-touch-punch" target="_blank">jQuery UI Touch Punch</a>. Download <a href="https://github.com/furf/jquery-ui-touch-punch/blob/master/jquery.ui.touch-punch.js" target="_blank">jquery.ui.touch-punch.js</a> and upload the file to Static Application Files. Reference the file e.g. on page JavaScript File URLs:</p><pre><code class="language-js">&#35;APP_FILES&#35;jquery.ui.touch-punch.js</code></pre></div>]]></content:encoded>
      <pubDate>Sat, 19 Aug 2023 11:00:00 GMT</pubDate>
      <guid isPermaLink="false">20230819081331172765</guid>
    </item>
    <item>
      <title>Export APEX workspace and schema in Oracle cloud</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>Oracle Cloud</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=20230325083035133857</link>
      <description>How to export APEX workspaces, applications and parsing schemas to object storage bucket in the Oracle cloud.</description>
      <content:encoded><![CDATA[<div class="ck-content"><p>You can create APEX workspaces, applications and schemas exports from database using PL/SQL. This might be useful e.g. if you working on Oracle cloud always free autonomous databases. In this example we use always free autonomous database workload type APEX and store exports to object storage bucket in the Oracle cloud. You can setup object versioning and lifecycle policy rules e.g. to move old versions to infrequent access or archive storage and delete old versions after some period. You can also create database job to schedule exports.</p><p>First create new compartment to isolate needed cloud resources.</p><p>Login to your Oracle cloud tenancy, navigate to “Identity &amp; Security” → “Compartments” and click “Create Compartment”.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/compartment_setup1.png" alt="Compartments page"></p><p>Enter name and description, select root compartment as “Parent Compartment” and click “Create Compartment”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/compartment_setup2.png" alt="Create Compartment popup"></p><p>Once the compartment is created, it will appear in the list.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/compartment_setup3.png" alt="Compartments page"></p><p>We need to add a policy to the object storage service order to use lifecycle policy rules in the bucket.</p><p>Navigate to “Identity &amp; Security” → “Policies”, select root compartment and click “Create Policy”.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/root_policy_setup1.png" alt="Policies page"></p><p>Enter name, description, change “Show manual editor” to on and enter policy statements.</p><pre><code class="language-plaintext">Allow service objectstorage-&lt;oci region identifier&gt; to manage object-family in tenancy</code></pre><p>Note! Replace part <i>&lt;oci region identifier&gt;</i> in statement with your region identifier.&nbsp;</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/root_policy_setup2.png" alt="Create policy popup"></p><p>Once the policy is created, you can see its details.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/root_policy_setup3.png" alt="Policy detail page"></p><p>Next create object storage bucket and set lifecycle policy rules for the bucket.</p><p>Navigate to “Storage” → “Buckets”, select compartment you created earlier and click “Create Bucket”.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/bucket_setup1.png" alt="Object storage &amp; archive storage page"></p><p>Enter bucket name, check “Enable Object Versioning” and click “Create”.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/bucket_setup2.png" alt="Create bucket popup"></p><p>Once the bucket is created, it will appear in the list.</p><p>Click the bucket name to view its detail.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/bucket_setup3.png" alt="Object storage &amp; archive storage page"></p><p>Note down namespace as you will need it later.</p><p>From left side menu select “Lifecycle Policy Rules” and click “Create Rule”.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/bucket_setup5.png" alt="Bucket lifecycle policy rules page"></p><p>Enter name, select “Previous Versions of Objects”, set “Lifecycle Action” to “Move Infrequent Access”, set “Number of Days” and click “Create”.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/bucket_setup6.png" alt="Create lifecycle rule popupCreate lifecycle rule popup"></p><p>Once the lifecycle policy rule is created, it will appear in the list.</p><p>Click “Create Rule” to create another rule.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/bucket_setup7.png" alt="Bucket lifecycle policy rules page"></p><p>Enter name, select “Previous Versions of Objects”, set “Lifecycle Action” to “Delete”, set “Number of Days” and click “Create”.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/bucket_setup8.png" alt="Create lifecycle rule popup"></p><p>Once the lifecycle policy rule is created, it will appear in the list.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/bucket_setup9.png" alt="Bucket lifecycle policy rules page"></p><p>Next create group, policy and user to access objects storage bucket.</p><p>Navigate to “Identity &amp; Security” → “Groups” and click “Create Group”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/group_setup1.png" alt="Groups page"></p><p>Enter name, description and click “Create”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/group_setup2.png" alt="Create group popup"></p><p>Once the group is created, you can see its details.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/group_setup3.png" alt="Group details page"></p><p>Navigate to “Identity &amp; Security” → “Policies” and select compartment you created earlier.</p><p>Click “Create Policy”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/policy_setup1.png" alt="Policies page"></p><p>Enter name, description, change “Show manual editor” to on and enter policy statements.</p><pre><code class="language-plaintext">Allow group &lt;group name&gt; to read buckets in compartment &lt;compartment name&gt; where any {target.bucket.name='&lt;bucket name&gt;'}
Allow group &lt;group name&gt; to manage objects in compartment &lt;compartment name&gt; where any {target.bucket.name='&lt;bucket name&gt;'}</code></pre><p>Note! Replace parts <i>&lt;group name&gt;</i>, <i>&lt;compartment name&gt;</i> and <i>&lt;bucket name&gt;</i> in statement with names you have used for those objects earlier.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/policy_setup2.png" alt="Create policy popup"></p><p>When the policy is created you see its details.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/policy_setup3.png" alt="Policy details page"></p><p>Navigate to “Identity &amp; Security” → “Users” and click “Create User”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup1.png" alt="Users page"></p><p>Select “IAM &nbsp;User”, enter name and description and then click “Create”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup2.png" alt="Create user popup"></p><p>Once the user is created, you can see its details.</p><p>Click “Add User to Group”</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup3.png" alt="User details page"></p><p>Select group you create earlier and click “Add”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup4.png" alt="Add user to group popup"></p><p>Once a user is added to a group, the group name will appear in the "Groups" list.</p><p>Click “Edit User Capabilities”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup5.png" alt="User details page"></p><p>Limit user capabilities only to “Auth Token” and click “Save Changes”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup6.png" alt="Edit user capabilities popup"></p><p>Next, we need to create an authentication token so that the database can move the exports to object storage bucket.</p><p>Navigate to user “Auth Tokens” and click “Generate Token”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup7.png" alt="User auth tokens page"></p><p>Give token a description and click “Generate Token”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup8.png" alt="Generate token popup"></p><p>Copy the token and place it somewhere safe as you can not view it later and then click “Close”.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup9.png" alt="Generated token popup"></p><p>Once the token is generated, it will appear to "Auth Tokens" list.</p><p><img src="https://static.jaris.fi/b/i/P20230325083035133857/user_setup10.png" alt="User auth tokens page"></p><p>Next login to your autonomous database e.g. using Database Actions as admin user.</p><p>Create new database user, grant needed privileges and enable database actions.</p><pre><code class="language-sql">create user &lt;username&gt; identified by "&lt;password&gt;";

-- Grant privileges
grant connect, resource, dwrole, apex_administrator_role, datapump_cloud_exp, datapump_cloud_imp to &lt;username&gt;;
grant execute on dbms_cloud to &lt;username&gt;;

alter user &lt;username&gt; default role all;

-- Add quota for user
alter user &lt;username&gt; quota unlimited on data;

-- Enable database actions
begin
  ords.enable_schema(
    p_enabled              	=&gt; true
    ,p_schema               =&gt; '&lt;username&gt;'
    ,p_url_mapping_type     =&gt; 'BASE_PATH'
  	,p_url_mapping_pattern	=&gt; '&lt;username&gt;'
  );
end;
/</code></pre><p>Note! Replace parts <i>&lt;username&gt;</i> and <i>&lt;password&gt;</i> with values you like to use.</p><p>Create database package to to new database user schema containing needed procedures to create exports . Package has procedure that exports all APEX workspaces, workspace files, applications and application parsing schemas and move those to object storage bucket. Package contains also procedure to import schema from object storage bucket.</p><p>You can review package code from <a href="https://gist.githubusercontent.com/jariolaine/60bfc2c08c0aa01658c78ea5cc918b46">Github Gist</a> before creating it using below commands.</p><pre><code class="language-sql">alter session set current_schema = &lt;username&gt;;

declare
	l_pkg_sql clob;
begin

-- Fetch package code from GitHub Gist.
  l_pkg_sql :=
		apex_web_service.make_rest_request(
    	p_url =&gt; 'https://gist.githubusercontent.com/jariolaine/60bfc2c08c0aa01658c78ea5cc918b46/raw/backup_api.sql'
      ,p_http_method =&gt; 'GET'
    )
	;

-- Install package.
	dbms_cloud_repo.install_sql(
  	content =&gt; l_pkg_sql
  );

end;
/</code></pre><p>Note! Replace part <i>&lt;username&gt;</i> with database user you created earlier.</p><p>Login to your database using database user you created earlier. You can view user packages to verify earlier created package exists.</p><p>Create credential to access object storage.</p><pre><code class="language-sql">begin

	dbms_cloud.create_credential(
		credential_name	=&gt; 'OBJECT_STORAGE_CRED'
		,username       =&gt; '&lt;iam user&gt;'
		,password       =&gt; '&lt;auth token&gt;'
	);

end;
/</code></pre><p>Note! Replace parts <i>&lt;iam user&gt;</i> and <i>&lt;auth token&gt;</i> with IAM user and token you created earlier.</p><p>Now you can test to create exports by running the procedure.</p><pre><code class="language-plaintext">begin

	backup_api.export_apex_workspaces(
  	p_credential_name	=&gt; 'OBJECT_STORAGE_CRED'
    ,p_region         =&gt; '&lt;oci region identifier&gt;'
    ,p_namespace      =&gt; '&lt;object storage namespace&gt;'
    ,p_bucket         =&gt; '&lt;bucke name&gt;'
	);

end;
/</code></pre><p>Note! Replace parts <i>&lt;oci region identifier&gt;</i>, <i>&lt;object storage namespace&gt;</i> and <i>&lt;bucket name&gt; &nbsp;</i>with your region identifier, namespace and bucket name.</p><p>View your object storage bucket objects. APEX exports have prefix <i>APEX/&lt;workspace name&gt;</i> and schema exports <i>datapump</i>.</p><p>If you have run database procedure multiple times you can see objects have versions.</p><p><img src="https://static.jaris.fi/b//i/P20230325083035133857/bucket_objects.png" alt="Bucket objects"></p><p>We can also create job and schedule exports. Login to database using user you created earlier and create job.</p><pre><code class="language-sql">declare
	l_job_name varchar2(256);
begin

-- Set job name
	l_job_name := 'APEX_EXPORT_JOB';

-- Create scheduler job
	dbms_scheduler.create_job(
  	job_name              =&gt; l_job_name
    ,job_type             =&gt; 'STORED_PROCEDURE'
    ,job_action           =&gt; 'backup_api.export_apex_workspaces'
    ,number_of_arguments	=&gt; 4
    ,start_date           =&gt; to_timestamp_tz( to_char( sysdate + 1, 'DD.MM.YYYY' ) || ' 01:00:00 +02:00' ,'DD.MM.YYYY HH24:MI:SS TZR' )
    ,repeat_interval      =&gt; 'FREQ=DAILY;'
    ,enabled              =&gt; false
    ,auto_drop            =&gt; false
    ,comments             =&gt; 'Daily APEX workspace export job'
	);

-- Set job arguments
	dbms_scheduler.set_job_argument_value(
		job_name            =&gt; l_job_name
		,argument_position  =&gt; 1
		,argument_value     =&gt; 'OBJECT_STORAGE_CRED'
	);

	dbms_scheduler.set_job_argument_value(
		job_name            =&gt; l_job_name
		,argument_position  =&gt; 2
		,argument_value     =&gt; '&lt;oci region identifier&gt;'
	);

	dbms_scheduler.set_job_argument_value(
		job_name            =&gt; l_job_name
		,argument_position  =&gt; 3
		,argument_value     =&gt; '&lt;object storage namespace&gt;'
	);

	dbms_scheduler.set_job_argument_value(
		job_name            =&gt; l_job_name
		,argument_position  =&gt; 4
		,argument_value     =&gt; '&lt;bucket name&gt;'
	);

-- Enable job
	dbms_scheduler.enable( l_job_name );

end;
/</code></pre><p>Note! Replace parts <i>&lt;oci region identifier&gt;</i>, <i>&lt;object storage namespace&gt;</i> and <i>&lt;bucket name&gt;</i> with your region identifier, namespace and bucket name.</p><p>Now we have set daily exports. Later if you see object storage bucket objects, you can see lifecycle policy rules move and delete object versions after days you have defined. Please note that latest version of the objects are not moved or deleted.</p><p>If you wish import schema from object storage bucket you can run procedure:</p><pre><code class="language-sql">begin

	backup_api.import_schema(
		p_credential_name	=&gt; 'OBJECT_STORAGE_CRED'
		,p_region         =&gt; '&lt;oci region identifier&gt;'
		,p_namespace      =&gt; '&lt;object storage namespace&gt;'
		,p_bucket         =&gt; '&lt;bucket name&gt;'
		,p_schema         =&gt; '&lt;schema name&gt;'
		,p_new_schema     =&gt; '&lt;new schema name&gt;'
	);

end;
/</code></pre><p>Note! Replace parts <i>&lt;oci region identifier&gt;</i>, <i>&lt;object storage namespace&gt;,</i> <i>&lt;bucket name&gt;</i>, <i>&lt;schema name&gt;</i> and <i>&lt;new schema name&gt;</i> with your region identifier, namespace, bucket name, schema name and new schema name.</p><p>If you omit parameter p_new_schema schema will not be remapped to new name.</p></div>]]></content:encoded>
      <pubDate>Sat, 25 Mar 2023 19:03:00 GMT</pubDate>
      <guid isPermaLink="false">20230325083035133857</guid>
    </item>
    <item>
      <title>Collapse Interactive Report Control Break</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Tips and Tricks</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=20220406135639638575</link>
      <description>How to add button to interactive report control break to collapse and expand rows</description>
      <content:encoded><![CDATA[<div class="ck-content"><p>The interactive grid has a feature that collapse and expands the control break. This feature is missing from interactive reports, but it's relatively easy to add the same feature with dynamic action and JavaScript.</p><p>First create interactive report and then add dynamic action</p><ul><li>Event: After Refresh</li><li>Selection Type: Region</li><li>Region: {select your interactive report region}</li><li>Action: Execute JavaScript Code</li><li>Fire on Initialization: On</li><li>Code:</li></ul><pre><code class="language-js">let lButton         = apex.util.htmlBuilder()
// Get messages for button title. Reusing IG messages
  , lCollapseTitle  = apex.lang.getMessage( "APEX.GV.BREAK_COLLAPSE" )
  , lExpandTitle    = apex.lang.getMessage( "APEX.GV.BREAK_EXPAND" )
// Button icon classes for collapse and expand
  , lBtnIcons       = [ "fa-chevron-down", "fa-chevron-right" ]
// IR control break row jQuery selector
  , lCtrBreak       = "th.a-IRR-header--group"
;
// Prepare button HTML
lButton.markup( "&lt;button" )
  .attr( "type", "button" )
  .attr( "aria-expanded", "true" )
  .attr( "title", lCollapseTitle )
  .attr( "aria-label", lCollapseTitle )
  .attr( "class", "t-Button t-Button--noLabel t-Button--icon t-Button--small t-Button--gapRight" )
  .markup( "&gt;&lt;span" )
  .attr( "aria-hidden", "true" )
  .attr( "class", "t-Icon fa " + lBtnIcons[0] )
  .markup( "&gt;&lt;/span&gt;&lt;/button&gt;" )
;
// Set click event to button
let button$ = $( lButton.toString() ).click( function(){
  let this$     = $( this )
    , lExpanded = this$.attr( "aria-expanded" ) === "true" ? "false" : "true"
    , lNewTitle = lExpanded === "true" ? lCollapseTitle : lExpandTitle
  ;
// Change button attributes and icon
  this$.attr({
    "title": lNewTitle
  , "aria-label": lNewTitle
  , "aria-expanded": lExpanded
  }).children().toggleClass( lBtnIcons ).end()
  // Hide/show IR rows from row clicked to the next control break
    .closest( "tr" ).nextUntil( ":has(" + lCtrBreak + ")" ).toggle()
  ;
});
// Attach button to IR control break rows
$( this.triggeringElement ).find( lCtrBreak ).prepend( button$ )
// Uncomment below line to initiallyt collapse control breaks.
// Remember also change button initial attributes.
//  .closest( "tr" ).nextUntil( ":has(" + lCtrBreak + ")" ).toggle()
;</code></pre><p>Run the page and add a control break from the interactive report action menu.</p><p><a href="https://apex.oracle.com/pls/apex/f?p=39006:68" target="_blank">See working example</a></p></div>]]></content:encoded>
      <pubDate>Tue, 05 Apr 2022 22:00:00 GMT</pubDate>
      <guid isPermaLink="false">20220406135639638575</guid>
    </item>
    <item>
      <title>Add tooltip to Interactive Grid column headers</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Tips and Tricks</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=20220326091656733057</link>
      <description>How to add tooltip for Interactive Grid column headers.</description>
      <content:encoded><![CDATA[<p>Here is how you can add tooltip to Interactive Grid column headers. For this I have placed tooltip text to IG column help and add title attribute column headers using small JavaScript code.</p>

<p>First navigate to application shared components and under Other Components click Shortcuts.</p>

<p>Create new Shortcut from scratch. Give name for Scortcut e.g. IG_TOOLTIP_JSON. Change Type to Function Body returning VARCHAR2. Place code to Shortcut and press Create.</p>

<pre class="blog-program-code">
declare
  l_json varchar2(32700);
begin
  select json_arrayagg(
    json_object(
      &#39;staticId&#39; value static_id || &#39;_HDR&#39;,
      &#39;columnId&#39; value &#39;C&#39; || column_id || &#39;_HDR&#39;,
      &#39;helpText&#39; value help_text
    )
  ) as d
  into l_json
  from apex_appl_page_ig_columns
  where application_id = :APP_ID
    and page_id = :APP_PAGE_ID
    and help_text is not null
  ;
  return l_json;
end;
</pre>

<p>Then edit your Interactive Grid region and set Static ID for region.</p>

<p>Add JavaScript to page Function and Global Variable Declaration.</p>

<pre class="blog-program-code">
(function($){

  $(function(){

    /* 
      MY_IG_REGION is IG region Static ID.
      Change variable value to match IG region static ID
    */
    var regionStaticId = &quot;MY_IG_REGION&quot;;

    setIgHeaderHelp(regionStaticId).on(&quot;interactivegridviewchange interactivegridreportsettingschange&quot;, function(event, ui) {
      setIgHeaderHelp(this.id);
    });

  });

  function setIgHeaderHelp(region){

    var region$ = $($x(region));

    /* IG_TOOLTIP_JSON is Shortcut name */
    $.each(&quot;IG_TOOLTIP_JSON&quot;, function(i, tooltipData){
      region$.find([$x(tooltipData.columnId), $x(tooltipData.staticId)]).parent(&quot;th&quot;).attr({&quot;title&quot; : tooltipData.helpText});
    });

    return region$;

  }

})(apex.jQuery);
</pre>

<p>Remember change variable regionStaticId value to match your IG region Static ID.</p>

<p>For last edit IG columns and add needed text to Help Text field.</p>

<p><a href="https://apex.oracle.com/pls/apex/f?p=39006:58" target="_blank">See working example</a></p>
]]></content:encoded>
      <pubDate>Sat, 26 Mar 2022 09:15:00 GMT</pubDate>
      <guid isPermaLink="false">20220326091656733057</guid>
    </item>
    <item>
      <title>C-ICAP and ClamAV for ORDS</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>Oracle Database</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=20210408172721693098</link>
      <description>How to install C-ICAP and ClamAV on Ubuntu 18.04 server for ORDS</description>
      <content:encoded><![CDATA[<div class="ck-content"><p><img class="u-pullRight" src="https://static.jaris.fi/b/i/P20210408172721693098/clamav-128.png" alt="" width="128" height="128">Oracle REST Data Service (ORDS) has feature to scan uploaded files for viruses. You can install e.g. C-ICAP and ClamAV for that purpose. Here are steps how to do that on Ubuntu 18.04 server assuming you have already working ORDS installation on same or different server.</p><p style="clear:both;">First install needed packages and dependencies.</p><pre class="blog-program-code">sudo apt install libc-icap-mod-virus-scan libclamunrar9</pre><p>After packages are installed, edit file /etc/default/c-icap to allow C-ICAP start</p><pre class="blog-program-code">sudo sed -i 's/START=no/START=yes/g' /etc/default/c-icap
</pre><p>Edit file /etc/c-icap/c-icap.conf and add below lines to end of file</p><pre class="blog-program-code">Include virus_scan.conf
Include clamav_mod.conf
virus_scan.DefaultEngine clamav
virus_scan.ScanFileTypes TEXT DATA EXECUTABLE ARCHIVE GRAPHICS STREAM DOCUMENT
ServiceAlias AVSCAN virus_scan?allow204=on&amp;sizelimit=off&amp;mode=simple</pre><p>Restart C-ICAP server</p><pre class="blog-program-code">sudo systemctl restart c-icap
</pre><p>Next edit ORDS configuration file default.xml and add below lines to it before closing properties tag</p><pre class="blog-program-code">&lt;entry key="icap.port"&gt;1344&lt;/entry&gt;
&lt;entry key="icap.server"&gt;127.0.0.1&lt;/entry&gt;
</pre><p>Change key icap.server value to point server where you did install C-ICAP if ORDS is running on different server.</p><p>After you have modify ORDS configuration, restart ORDS standalone or web server where ORDS is deployed.</p><p>With your own risk, you can try virus scanner with&nbsp;test files from&nbsp;<a href="https://www.eicar.org/?page_id=3950" target="_blank">www.eicar.org</a>. Please read instructions,&nbsp;before downloading test files.</p></div>]]></content:encoded>
      <pubDate>Thu, 08 Apr 2021 18:40:00 GMT</pubDate>
      <guid isPermaLink="false">20210408172721693098</guid>
    </item>
    <item>
      <title>APEX and shuttle filter - Revisited</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX and jQuery</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=202002211441057383</link>
      <description>Shuttle item filtering JavaScript code updated to APEX 19.2 and new features.</description>
      <content:encoded><![CDATA[<p><img alt="" height="128" src="https://static.jaris.fi/b/i/P202002211441057383/shuttle.png" style="float:right" width="128" />Previously I did write post about <a href="/ords/r/jaris/blog/post?p2_post_id=201410131041380630">APEX and shuttle filter</a>. I received comments that filtering does not work when shuttle have parent cascading item. Issue was that JavaScript code stores shuttle values to variable for filtering only on page load.</p>

<p>I did receive also proposal to add button for clearing filter item. This is also included to new JavaScript code along with some style changes for APEX 19.2.</p>

<p>Here is modified code for page JavaScript Function and Global Variable Declaration:</p>

<pre class="blog-program-code">
(function($) {
  $.fn.htmldbShuttlefilter = function(options) {

    options = $.extend({}, {
      &quot;filterPlaceholder&quot;: &quot;Filter&quot;,
      &quot;buttonTitle&quot;: &quot;Clear filter&quot;,
      &quot;buttonClass&quot;: [
        ,&quot;a-Button&quot;
        ,&quot;a-Button--noLabel&quot;
        ,&quot;a-Button--withIcon&quot;
        ,&quot;a-Button--small&quot;
        ,&quot;a-Button--noUI&quot;
        ,&quot;a-Button--shuttle&quot;
        ,&quot;margin-top-none&quot;
      ],
      &quot;buttonIcon&quot;: &quot;fa-times&quot;
    }, options);

    function getShuttleValues(elem$) {
      return elem$.children(&quot;option&quot;).map(function() {
        return {
          text: $(this).text(),
          value: $(this).val()
        };
      });
    }

    return this.each(function(i) {

      var $self = $(this),

        shuttleResetBtnId = $self.attr(&quot;id&quot;) + &quot;_RESET&quot;,
        $select = $self.find(&quot;select&quot;),

        // get shuttle values
        shuttleValues = getShuttleValues($select),

        // filter reset button
        $resetBtn = $(&quot;&lt;button/&gt;&quot;, {
          &quot;type&quot;: &quot;button&quot;,
          &quot;title&quot;: options.buttonTitle,
          &quot;aria-label&quot;: options.buttonTitle,
          &quot;class&quot;: options.buttonClass.join(&quot; &quot;),
          &quot;css&quot;: {
            &quot;padding&quot;: &quot;4px&quot;
          }
        }).append(
          $(&quot;&lt;span/&gt;&quot;, {
            &quot;aria-hidden&quot;: &quot;true&quot;,
            &quot;class&quot;: &quot;a-Icon fa &quot; + options.buttonIcon,
          })
        ).click(function() {
          // clear filter text field
          $filter.val(&quot;&quot;).keyup();
        }),

        // filter text field
        $filter = $(&quot;&lt;input/&gt;&quot;, {
          &quot;type&quot;: &quot;text&quot;,
          &quot;value&quot;: &quot;&quot;,
          &quot;autocomplete&quot;: &quot;off&quot;,
          &quot;placeholder&quot;: options.filterPlaceholder,
          &quot;class&quot;: &quot;text_field apex-item-text&quot;,
          &quot;css&quot;: {
            &quot;width&quot;: &quot;100%&quot;
          }
        }).keyup(function() {

          // filter shuttle when typing to text field
          var filterval = new RegExp(&quot;^&quot; + $(this).val() + &quot;.*&quot;, &quot;i&quot;),
            selectedValues = $select.eq(1).children(&quot;option&quot;).map(function() {
              return $(this).val();
            });

          // empty shuttle available values
          $select.eq(0).empty();
          // add values that match filter criteria
          $.each(shuttleValues, function(idx, obj) {
            if (
              obj.text.match(filterval) &amp;&amp;
              $.inArray(obj.value, selectedValues) &lt; 0
            ) {
              $select.eq(0).append(new Option(obj.text, obj.value));
            }
          });
        });

      // add needed elements to page
      $self.prepend(
        $(&quot;&lt;div/&gt;&quot;, {
          &quot;class&quot;: &quot;t-Form-itemWrapper&quot;
        })
        .append($filter).append(
          $(&quot;&lt;span/&gt;&quot;, {
            &quot;class&quot;: &quot;t-Form-itemText t-Form-itemText--post&quot;
          }).append($resetBtn)
        )

      ).on(&quot;apexafterrefresh&quot;, function() {
        // initiliaze filter when shuttle is refreshed by parent item
        $filter.val(&quot;&quot;);
        shuttleValues = getShuttleValues($select);
      });

      // clear also filter from shuttle reset button
      $(&quot;&#35;&quot; + shuttleResetBtnId).click(function() {
        $filter.val(&quot;&quot;);
      });
    });
  }
})(apex.jQuery);
</pre>

<p>If you like add filter to all shuttles in page, then add to page JavaScript Execute when Page Loads:</p>

<pre class="blog-program-code">
$(&quot;.apex-item-group--shuttle&quot;).htmldbShuttlefilter();</pre>

<p>Depending on your need, change jQuery selector.</p>

<p><a href="https://apex.oracle.com/pls/apex/f?p=39006:52" target="_blank">See working example</a> where Dept shuttle is parent for Emp.</p>
]]></content:encoded>
      <pubDate>Fri, 21 Feb 2020 11:20:00 GMT</pubDate>
      <guid isPermaLink="false">202002211441057383</guid>
    </item>
    <item>
      <title>Stopping XE 18c properly when shutting down CentOs 7</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>Oracle Database</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201902171002516775</link>
      <description>Stopping Oracle XE 18c database properly using systemd when shutting down CentOs 7 system</description>
      <content:encoded><![CDATA[<p><img alt="" height="150" src="https://static.jaris.fi/b/i/P201902171002516775/system-shutdown-150x150.png" style="float:right" width="150" />You can configure automatically start and stop Oracle XE 18c database on operating system startup and shutdown. See details from <a href="https://docs.oracle.com/en/database/oracle/oracle-database/18/xeinl/starting-and-stopping-oracle-database.html" target="_blank">installation guide</a>. I did install XE 18c on CentOs 7 and executed steps mentioned in installation guide. When viewing database alert.log after system reboot, noticed that database wasn&#39;t actually stopped properly. Processes were just killed. I didn&#39;t investigate more why that happens on my system. For workaround I did revert steps mentioned in installation guide and created service file for systemd.</p>

<p>First stop XE database and revert automatically start and stop:</p>

<pre class="blog-program-code">
systemctl stop oracle-xe-18c
systemctl disable oracle-xe-18c
</pre>

<p>Create service file:</p>

<pre class="blog-program-code">
cat &gt;&gt; /usr/lib/systemd/system/ora-xe-18c.service &lt;&lt; EOF
[Unit]
Description=Oracle 18c XE databases service
After=syslog.target network.target

[Service]
Type=forking
Restart=no
KillMode=none
TimeoutStopSec=10min
RemainAfterExit=yes
User=oracle
Group=oinstall
ExecStart=/opt/oracle/product/18c/dbhomeXE/bin/dbstart /opt/oracle/product/18c/dbhomeXE &amp;
ExecStop=/opt/oracle/product/18c/dbhomeXE/bin/dbshut /opt/oracle/product/18c/dbhomeXE

[Install]
WantedBy=multi-user.target
EOF</pre>

<p>Backup and modify /etc/oratab:</p>

<pre class="blog-program-code">
cp /etc/oratab /etc/oratab.backup_`date +%Y%m%d`
sed -i &#39;s/XE:\/opt\/oracle\/product\/18c\/dbhomeXE:N/XE:\/opt\/oracle\/product\/18c\/dbhomeXE:Y/g&#39; /etc/oratab</pre>

<p>Enable new service and start XE database again:</p>

<pre class="blog-program-code">
systemctl daemon-reload
systemctl enable ora-xe-18c
systemctl start ora-xe-18c
</pre>
]]></content:encoded>
      <pubDate>Sun, 17 Feb 2019 10:02:00 GMT</pubDate>
      <guid isPermaLink="false">201902171002516775</guid>
    </item>
    <item>
      <title>Caching APEX static files</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX and Apache HTTPD</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201803041325416124</link>
      <description>Configuring Apache HTTPD mod_file_cache, mod_cache and mod_cache_disk for APEX static files in server and database</description>
      <content:encoded><![CDATA[<div class="ck-content"><p><img style="float:right;" src="https://static.jaris.fi/b/i/P201803041325416124/apache_logo.png" alt="Apache HTTP Server" height="128" width="128">The APEX installation includes static files that are located on the web server. These&nbsp;files may changed normally only when you upgrade or apply patch to APEX. Because files are changing rarely, they are good subject stored to cache. If you use Apache HTTPD to serve static files, you can configure <a href="https://httpd.apache.org/docs/2.4/mod/mod_file_cache.html" target="_blank">mod_file_cache</a> for ones that are used most in your applications.</p><p>You might have also other static content e.g. images, JavaScript and CSS files that are stored to APEX workspace or application static files. Also these files aren't usually changed often and you can store files to cache and reduce calls to database. This can be done using HTTPD modules&nbsp;<a href="https://httpd.apache.org/docs/2.4/mod/mod_cache.html" target="_blank">mod_cache</a> and&nbsp;<a href="https://httpd.apache.org/docs/2.4/mod/mod_cache_disk.html" target="_blank">mod_cache_disk</a>.</p><p>I provide here only basic information that you are aware about these HTTPD cache features and how those may used with APEX. Assumption is that you have knowledge how&nbsp;configure&nbsp;HTTPD.&nbsp;You need test and see how these works on your system.</p><p>First check are needed modules loaded to HTTPD.&nbsp;In Linux machine you can use command <i>httpd -M</i> to list all modules that are loaded.<br>You should look cache_module, cache_disk_module and file_cache_module.</p><p>Create configuration file called e.g. apex_file_cache.conf to specify the APEX static files you like cache. Here is example configuration file content for APEX 5.1.4 when applications use&nbsp;<i>Universal Theme</i>&nbsp;and styles&nbsp;<i>Vita</i>:</p><pre class="blog-program-code">&#35; APEX 5.1.4 mod_file_cache configuration for Universal Theme style Vita
&lt;IfModule file_cache_module&gt;
&nbsp; &#35; CSS
&nbsp; CacheFile /apex/images/app_ui/css/Core.min.css
&nbsp; CacheFile /apex/images/app_ui/css/Theme-Standard.min.css
&nbsp; CacheFile /apex/images/libraries/jquery-ui/1.10.4/themes/base/jquery-ui.min.css
&nbsp; CacheFile /apex/images/libraries/font-apex/1.0/css/font-apex.min.css
&nbsp; CacheFile /apex/images/themes/theme_42/1.1/css/Core.min.css
&nbsp; CacheFile /apex/images/themes/theme_42/1.1/css/Vita.min.css
  &#35; JavaScript
&nbsp; CacheFile /apex/images/libraries/apex/minified/desktop.min.js
&nbsp; CacheFile /apex/images/libraries/hammer/2.0.4/hammer-2.0.4.min.js
&nbsp; CacheFile /apex/images/libraries/apex/minified/widget.apexTabs.min.js
&nbsp; CacheFile /apex/images/libraries/apex/minified/widget.stickyWidget.min.js
&nbsp; CacheFile /apex/images/libraries/apex/minified/widget.stickyTableHeader.min.js
&nbsp; CacheFile /apex/images/themes/theme_42/1.1/js/modernizr-custom.min.js
&nbsp; CacheFile /apex/images/themes/theme_42/1.1/js/theme42.min.js
&lt;/IfModule&gt;
</pre><p>In example I use&nbsp;<a href="https://httpd.apache.org/docs/2.4/mod/mod_file_cache.html&#35;cachefile" target="_blank">CacheFile</a>&nbsp;directive&nbsp;but you should test does your platform support it or do you get better result with <a href="https://httpd.apache.org/docs/2.4/mod/mod_file_cache.html&#35;mmapfile" target="_blank">MMapFile</a>&nbsp;directive. Check also path where the files are located in server.&nbsp;Include configuration file to your httpd.conf file, run test for configuration&nbsp;and restart HTTPD.</p><p>NOTE! when you upgrade or patch APEX, you need check are same files still relevant for cache and do changes accordingly to the configuration file. Also you need restart HTTPD to avoid end up serving requests that are completely bogus as <a href="https://httpd.apache.org/docs/2.4/mod/mod_file_cache.html&#35;using" target="_blank">mod_file_cache</a> documentation says.<br>You shouldn't try cache all APEX +12 000 static files,&nbsp;that installation contains, with <a href="https://httpd.apache.org/docs/2.4/mod/mod_file_cache.html&#35;mmapfile" target="_blank">mod_file_cache</a>. Most probably your applications isn't using those all even in special cases. You need carefully select only most&nbsp;used files.</p><p>To configure cache for APEX workspace and application static files create configuration file called e.g.&nbsp;apex_cache_disk.conf. Here is example configuration for workspaces&nbsp;ws1, ws2, ws3 and ws4:</p><pre class="blog-program-code">&#35; APEX 5.1.4 mod_cache configuration for static workspace and application files
&lt;IfModule cache_disk_module&gt;
&nbsp; CacheRoot &nbsp; &nbsp; &nbsp;/var/cache/httpd/proxy
&nbsp; CacheEnable &nbsp; &nbsp;disk /apex/ws1/r/
&nbsp; CacheEnable &nbsp; &nbsp;disk /apex/ws2/r/
&nbsp; CacheEnable &nbsp; &nbsp;disk /apex/ws3/r/
&nbsp; CacheEnable &nbsp; &nbsp;disk /apex/ws4/r/
&nbsp; CacheLock &nbsp; &nbsp; &nbsp;on
&lt;/IfModule&gt;
</pre><p>You need check what are URL's in your system relating to APEX workspace and application static files and adjust configuration accordingly. Include configuration file to your httpd.conf file, run test for configuration&nbsp;and restart HTTPD.</p><p>Optionally you can also add <a href="https://httpd.apache.org/docs/2.4/mod/mod_cache.html&#35;cacheignorecachecontrol" target="_blank">CacheIgnoreCacheControl</a> directive that&nbsp;clients can't force refresh cache in HTTPD. Then it is recommended where you refer static file add query string to end. That way developers&nbsp;can force HTTPD fetch&nbsp;new file when files are changed. You can use e.g. APP_VERSION substitution string&nbsp;like:</p><pre class="blog-program-code">&#35;APP_IMAGES&#35;style&#35;MIN&#35;.css?v=&#35;APP_VERSION&#35;
</pre><p>If you change&nbsp;file in workspace or application static files, change also version attribute in <a href="https://docs.oracle.com/database/apex-5.1/HTMDB/managing-application-attributes.htm&#35;GUID-4C7EB51C-C36A-4A52-86BE-60FEC6FCAB71" target="_blank">application definitions</a>&nbsp;so HTTPD knows reload file.<br>After enabling mod_cache, you should also see and setup <a href="https://httpd.apache.org/docs/2.4/programs/htcacheclean.html" target="_blank">htcachecleanup</a>.</p><p>Depending if you aren't using RESTful services from APEX workspaces, after configuring mod_cache for workspace and application files, you might like check and change ORDS configuration for&nbsp;APEX_REST_PUBLIC_USER and APEX_LISTENER users pool. See if you can&nbsp;lower those pools jdbc.MinLimit so there aren't connections waiting for nothing while&nbsp;HTTPD serves workspace and application static files from cache.</p></div>]]></content:encoded>
      <pubDate>Sun, 04 Mar 2018 13:25:00 GMT</pubDate>
      <guid isPermaLink="false">201803041325416124</guid>
    </item>
    <item>
      <title>Changing Report Column Date Format</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Tips and Tricks</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201802111100046420</link>
      <description>Simple trick how change APEX application report date columns format mask to SINCE, and save selection to user preferences for next session.</description>
      <content:encoded><![CDATA[<p><img alt="" height="128" src="https://static.jaris.fi/b/i/P201802111100046420/clock.png" style="float:right" width="128" />In APEX there is build in date formats SINCE and SINCE_SHORT that I use often in report date columns. Unfortunately you can&#39;t use SINCE format in application Globalization Attributes, so you need separately set it to columns. But, sometimes you need to see the actual date value in column.<br />
<br />
Here is simple example how you can switch between application global date format and SINCE.</p>

<p>First create application item G_DATE_DISPLAY_FORMAT.</p>

<div><img alt="Application item" height="168" src="https://static.jaris.fi/b/i/P201802111100046420/application_item.png" width="391" /></div>

<p>Create two entries to Navigation Bar list as follows.<br />
First list entry:</p>

<ul>
	<li>List Entry Label: Show Since</li>
	<li>Target page: &amp;APP_PAGE_ID.</li>
	<li>Request: SET_DATE_FORMAT_SINCE_ON</li>
	<li>Condition: Value of User Preference in Expression 1 != Expression 2
	<ul>
		<li>Expression 1: U_DATE_DISPLAY_FORMAT</li>
		<li>Expression 2: SINCE</li>
	</ul>
	</li>
</ul>

<p>Second list entry is almost same. Just change label, request and condition</p>

<ul>
	<li>List Entry Label: Show Date</li>
	<li>Target page: &amp;APP_PAGE_ID.</li>
	<li>Request: SET_DATE_FORMAT_SINCE_OFF</li>
	<li>Condition: Value of User Preference in Expression 1 = Expression 2
	<ul>
		<li>Expression 1: U_DATE_DISPLAY_FORMAT</li>
		<li>Expression 2: SINCE</li>
	</ul>
	</li>
</ul>

<p>Create <em>On Load: Before Header</em> application process.<br />
PL/SQL Code:</p>

<pre class="blog-program-code">
declare
  l_since varchar2(40);
begin

  l_since := 
    case when :REQUEST = &#39;SET_DATE_FORMAT_SINCE_ON&#39;
    then &#39;SINCE&#39;
    end
  ;
  :G_DATE_DISPLAY_FORMAT  := l_since;

  /* save user selection to preference */
  apex_util.set_preference(
     p_preference =&gt; &#39;U_DATE_DISPLAY_FORMAT&#39;
    ,p_value      =&gt; l_since
    ,p_user       =&gt; :APP_USER
  );

end;</pre>

<p>Set process conditionally by request containing SET_DATE_FORMAT_SINCE_ON and SET_DATE_FORMAT_SINCE_OFF</p>

<div><img alt="Application process condition" height="132" src="https://static.jaris.fi/b/i/P201802111100046420/application_process_condition.png" width="475" /></div>

<p>Application process stores information also to preference. If you like you can set application item value for new sessions in Post-Authentication Procedure.</p>

<p>Place code to authentication schema source PL/SQL Code and Enter to Post-Authentication Procedure Name field <strong><em>set_date_display_format</em></strong></p>

<pre class="blog-program-code">
procedure set_date_display_format
as
begin
  :G_DATE_DISPLAY_FORMAT := apex_util.get_preference(
     p_preference =&gt; &#39;U_DATE_DISPLAY_FORMAT&#39;
    ,p_user =&gt; :APP_USER
  );
end;</pre>

<div><img alt="Authentication schema" height="112" src="https://static.jaris.fi/b/i/P201802111100046420/authentication_schema.png" width="511" /></div>

<p>Then edit report columns and set format mask to application item substitution string &amp;G_DATE_DISPLAY_FORMAT.</p>

<div><img alt="Report column format mask" height="123" src="https://static.jaris.fi/b/i/P201802111100046420/column_format_mask.png" width="371" /></div>

<p>Here you can see <a href="https://apex.oracle.com/pls/apex/f?p=45689:1" target="_blank">example</a> where right top there is link to switch date format mask. When you click the link in navigation bar, you can see report <em>Hired on</em> column format changes.</p>
]]></content:encoded>
      <pubDate>Sun, 11 Feb 2018 10:00:00 GMT</pubDate>
      <guid isPermaLink="false">201802111100046420</guid>
    </item>
    <item>
      <title>Oracle Database Developer Choice Awards</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Community</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201510100617461772</link>
      <description>Oracle Database Developer Choice Awards 2015</description>
      <content:encoded><![CDATA[<p><img alt="" height="100" src="https://static.jaris.fi/b/i/P201510100617461772/award.png" style="float:right" width="100" />I&#39;m finalist for <a href="https://community.oracle.com/community/database/awards/apex-voting" target="_blank">Oracle Database Developer Choice Awards in Oracle Application Express (APEX) category</a>.<br />
I&#39;m very impressed and surprised for this honor. I want thank all who have filled nomination form and voted. Special thanks to all who have also leave comments and supported me by blog posts and spreading word other ways.<br />
If you haven&#39;t vote yet, now it&#39;s time. Voting ends at 15th of October. Please remember you can vote multiple persons in all categories. I hope <a href="http://deneskubicek.blogspot.fi/2015/10/oracle-database-developer-choice-awards.html" target="_blank">Denes Kubicek&#39;s blog post</a> encourage everybody to vote.</p>
]]></content:encoded>
      <pubDate>Sat, 10 Oct 2015 06:17:00 GMT</pubDate>
      <guid isPermaLink="false">201510100617461772</guid>
    </item>
    <item>
      <title>SQL Developer ORDS administration &quot;peer not authenticated&quot; error</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>Oracle SQL Developer</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201503071132220465</link>
      <description>How to resolve &quot;peer not authenticated&quot; error on SQL Developer ORDS administration</description>
      <content:encoded><![CDATA[<div class="ck-content"><p>If your web server use self signed certificate for https you most probably get "peer not authenticated" error when you try administrate Oracle REST Data Service (ORDS) using Oracle SQL Developer.<br><img src="https://static.jaris.fi/b/i/P201503071132220465/sql-developer-peer-not-authenticated.png" alt="Peer not authenticated error message" height="139" width="395"><br>To resolve this issue, you need import server certificate to Java cacerts keystore.</p><p>You can download server certificate e.g. using browser. In this example I use Firefox.</p><p>First navigate to your server URL and click lock icon next to URL. From popup click More Information.<br><br><img src="https://static.jaris.fi/b/i/P201503071132220465/ff-get-cert-1.png" alt="Get server certificate using Firefox step 1" height="389" width="655"></p><p>From opening window click View Certificate<br><br><img src="https://static.jaris.fi/b/i/P201503071132220465/ff-get-cert-2.png" alt="Get server certificate using Firefox step 2" height="479" width="655"></p><p>Click Details tab and then Export button<br><br><img src="https://static.jaris.fi/b/i/P201503071132220465/ff-get-cert-3.png" alt="Get server certificate using Firefox step 3" height="691" width="655"></p><p>Change the ‘Save as type’ to ‘X.509 Certificate with chain (PEM)’ and save the certificates to a file.<br><br><img src="https://static.jaris.fi/b/i/P201503071132220465/ff-get-cert-4.png" alt="Get server certificate using Firefox step 4" height="409" width="655"></p><p>Open command prompt and import certificate to Java cacert keystore. Please note that you need import certificate to Java installation/version witch SQL Developer uses.</p><p>Here is example of command how import certificate in Windows</p><pre class="blog-program-code">C:\sqldeveloper\jdk\jre\bin\keytool.exe -import -noprompt -trustcacerts ^
-alias vbox-apex.localdomain ^
-file C:\Temp\vbox-apex.localdomain.crt ^
-keystore C:\sqldeveloper\jdk\jre\lib\security\cacerts ^
-storepass changeit</pre><p><img src="https://static.jaris.fi/b/i/P201503071132220465/cmd-import-cert.png" alt="Import certificate using Java keytool" height="216" width="653"></p><p><br>Now administrating Oracle REST Data Service using Oracle SQL Developer over https works.<br><br><img src="https://static.jaris.fi/b/i/P201503071132220465/ords-connection.png" alt="ORDS connection" height="262" width="369"></p></div>]]></content:encoded>
      <pubDate>Sat, 07 Mar 2015 11:32:00 GMT</pubDate>
      <guid isPermaLink="false">201503071132220465</guid>
    </item>
    <item>
      <title>New version of APEX Blogging Platform released</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Sample Applications</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201412281415140822</link>
      <description>Completly rewritten new version of APEX Blogging Platform is now released.</description>
      <content:encoded><![CDATA[<p>Finally I did have some time finish and publish new version of APEX Blogging Platform. Application is completely rewritten and have much more features and new theme. It is simple to install also to hosted APEX instance.<br />
<br />
<img alt="New APEX Blogging Platform" height="287" src="https://static.jaris.fi/b/i/P201412281415140822/newblog2901.png" width="650" /></p>

<p>You can download application from <a href="https://sourceforge.net/projects/apex-blog/" target="_blank">SourceForge</a>. If you have comments or enhancement ideas please submit those to project page Tickets or Discussion.</p>
]]></content:encoded>
      <pubDate>Sun, 28 Dec 2014 13:15:00 GMT</pubDate>
      <guid isPermaLink="false">201412281415140822</guid>
    </item>
    <item>
      <title>APEX 4.2 interactive report with row detail</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX and jQuery</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201411021608150690</link>
      <description>Interactive report with the functionality to show a detail row per each row</description>
      <content:encoded><![CDATA[<p>Here is how create interactive report with the functionality to show a detail row per each row on APEX 4.2.</p>

<p>Below you can see interactive report query I did use for this example:</p>

<pre class="blog-program-code">
SELECT p.product_id
,p.product_name
,p.category
,p.product_avail
,p.list_price
,(
  SELECT sum(quantity)
  FROM demo_order_items
  WHERE product_id = p.product_id
  ) AS units
,(
  SELECT sum(quantity * p.list_price)
  FROM demo_order_items
  WHERE product_id = p.product_id
  ) AS sales
,(
  SELECT count(o.customer_id)
  FROM demo_orders o
   ,demo_order_items t
  WHERE o.order_id = t.order_id
   AND t.product_id = p.product_id
  GROUP BY p.product_id
  ) AS customers
,(
  SELECT max(o.order_timestamp) od
  FROM demo_orders o
   ,demo_order_items i
  WHERE o.order_id = i.order_id
   AND i.product_id = p.product_id
  ) AS last_date_sold
,(
  SELECT APEX_LANG.LANG(&#39;Details&#39;)
  FROM DUAL
  ) AS details
FROM demo_product_info p</pre>

<p>Edit DETAILS column attributes and make column as link</p>

<ul>
	<li>Link Text : &#35;DETAILS&#35;</li>
	<li>Link Attributes : class=&quot;product-details&quot; data-product=&quot;&#35;PRODUCT_ID&#35;&quot;</li>
	<li>Target : URL</li>
	<li>URL : &#35;</li>
</ul>

<p>Add to page JavaScript File URLs</p>

<pre class="blog-program-code">
&#35;IMAGE_PREFIX&#35;libraries/jquery-ui/1.8.22/ui/minified/jquery.ui.button.min.js</pre>

<p>and to Function and Global Variable Declaration</p>

<pre class="blog-program-code">
var gDetailCache = new Object();
(function($){
 $.fn.htmldbDetailRow=function(options){
  options=$.extend({},{
   &quot;trIdPrefix&quot;:&quot;D&quot;,
   &quot;btnShowClass&quot;:&quot;ui-icon-plusthick&quot;,
   &quot;btnHideClass&quot;:&quot;ui-icon-minusthick&quot;,
   &quot;btnAjaxClass&quot;:&quot;ui-icon-refresh&quot;
  },options);
  this.each(function(){
   var $Self  = $(this).removeAttr(&quot;href&quot;).button({icons:{primary:options.btnShowClass},text:false}),
       $Row   = $Self.closest(&quot;tr&quot;),
       $Ico   = $Self.children(&quot;span.ui-button-icon-primary&quot;),
       lC     = $Row.children(&quot;td&quot;).length,
       lId    = $Self.data(options.btnData),
       lTrId  = options.trIdPrefix+lId,
       lClass = options.btnShowClass + &quot; &quot; + options.btnHideClass
   ;
   $Self.click(function(){
    $Tr=$($x(lTrId));
    if($Tr.length===0){
     $Self.button(&quot;option&quot;,{icons:{primary:options.btnAjaxClass},&quot;disabled&quot;:true});
     apex.server.process(options.onDemanProcess,
      {x01:lId},{dataType:&quot;text&quot;,success:function(d){
       var $Tr=$(
        &#39;&lt;tr id=&quot;&#39; + lTrId + &#39;&quot;&gt;&#39; +
        &#39;&lt;td class=&quot;&#39; + options.tdClass + &#39;&quot; colspan=&quot;&#39; + lC + &#39;&quot;&gt;&#39;
        + d +
        &#39;&lt;/td&gt;&#39; +
        &#39;&lt;/tr&gt;&#39;
       ),lA=new Object();
       lA[lTrId]={d:$Tr,s:true};
       $.extend(gDetailCache,lA);
       $Row.after($Tr);
       $Ico=$Self.button(&quot;option&quot;,{icons:{primary:options.btnHideClass},&quot;disabled&quot;:false})
       .children(&quot;span.ui-button-icon-primary&quot;);
      }
     });
    }else{
     $Tr.toggle(0,function(){
      $Ico.toggleClass(lClass);
      gDetailCache[lTrId].s=!gDetailCache[lTrId].s
     })
    }
   });
   if(lTrId in gDetailCache){
    gDetailCache[lTrId].d.children().attr({&quot;colspan&quot;:lC});
    $Row.after(gDetailCache[lTrId].d);
    if(gDetailCache[lTrId].s){
     $Ico.toggleClass(lClass)
    }else{
     gDetailCache[lTrId].d.hide()
    }
   }
  })
  return this
 }
})(apex.jQuery);</pre>

<p>Add to page CSS Inline</p>

<pre class="blog-program-code">
.prodinfo{
 padding:6px!important;
 font-size:12pt!important;
 color:&#35;660000!important;
 font-weight:bold!important;
 text-align:center!important;
}</pre>

<p>Create on demand process GET_PRODUCT_INFO</p>

<pre class="blog-program-code">
DECLARE
  l_info VARCHAR2(32000);
BEGIN
  SELECT product_description
  INTO l_info
  FROM demo_product_info
  WHERE product_id = apex_application.g_x01; 
  HTP.PRN(l_info);
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    HTP.PRN(&#39;No additional information&#39;);
  WHEN OTHERS THEN
    HTP.PRN(sqlerrm);
END;</pre>

<p>Create dynamic action</p>

<ul>
	<li>Name: IR detail row</li>
	<li>Event: After Refresh</li>
	<li>Selection Type: Region</li>
	<li>Region: <em style="font-style:italic">{select IR region}</em></li>
	<li>Condition: -No Condition-</li>
	<li>Action: Execute JavaScript code</li>
	<li>Fire On Page Load: True</li>
	<li>Code:
	<pre class="blog-program-code">
$(this.triggeringElement)
.find(&#39;a.product-details&#39;)
.htmldbDetailRow({
 onDemanProcess:&quot;GET_PRODUCT_INFO&quot;, // on demand process name
 tdClass:&quot;prodinfo&quot;,                // details class
 btnData:&quot;product&quot;                  // button data name
});
</pre>
	</li>
	<li>Selection Type: None</li>
</ul>

<p>Now when you run page you have button on each row to expand/collapse row details.<br />
<br />
Please note that when you e.g. paginate to next page and back, example remember rows that were expanded =).<br />
<a href="https://apex.oracle.com/pls/apex/f?p=39006:53" target="_blank">See working example</a>.</p>

<p><img alt="IR detail row" height="354" src="https://static.jaris.fi/b/i/P201411021608150690/ir_detail_row.png" width="655" /></p>
]]></content:encoded>
      <pubDate>Sun, 02 Nov 2014 15:08:00 GMT</pubDate>
      <guid isPermaLink="false">201411021608150690</guid>
    </item>
    <item>
      <title>APEX and shuttle filter</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX and jQuery</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201410131041380630</link>
      <description>How to create filter / search field for APEX shuttle item values using jQuery.</description>
      <content:encoded><![CDATA[<p>Here is small tip how to create filter / search field for APEX shuttle item values. For this you need just place jQuery code to page JavaScript Function and Global Variable Declaration.</p>

<pre class="blog-program-code">
(function($){
$.fn.htmldbShuttlefilter=function(options){
  options=$.extend({},{&quot;label&quot;:&quot;Filter&quot;},options);
  return this.each(function(i){
   var $self      = $(this)
   ,filterId      = $self.attr(&quot;id&quot;) + &quot;_FILTER&quot;
   ,$select       = $self.find(&quot;select&quot;)
   ,shuttleValues = $select.children(&quot;option&quot;).map(function(){
    return {text:$(this).text(),value:$(this).val(),option:this}
   })
   ,$filter = $(&quot;&lt;input/&gt;&quot;,{&quot;type&quot;:&quot;text&quot;,&quot;value&quot;:&quot;&quot;,&quot;size&quot;:&quot;255&quot;,&quot;autocomplete&quot;:&quot;off&quot;,&quot;id&quot;:filterId})
   .keyup(function(){
    var filterval   = new RegExp(&quot;^&quot;+$(this).val()+&quot;.*&quot;,&quot;i&quot;)
    ,selectedValues = $select.eq(1).children(&quot;option&quot;).map(function(){
     return $(this).val();
    });
    $select.eq(0).empty();
    $.each(shuttleValues,function(idx,obj){
     if(obj[&quot;text&quot;].match(filterval) &amp;&amp; $.inArray(obj[&quot;value&quot;],selectedValues)&lt;0){
      $select.eq(0).append(obj[&quot;option&quot;]);
     }
    });
   })
   .width($self.width());
   $(&quot;&lt;div/&gt;&quot;,{&quot;css&quot;:{&quot;padding-bottom&quot;:&quot;5px&quot;}})
   .insertBefore($self)
   .append(
    $(&quot;&lt;label/&gt;&quot;,{&quot;for&quot;:filterId})
    .append($(&quot;&lt;span/&gt;&quot;,{&quot;css&quot;:{&quot;font-weight&quot;:&quot;bold&quot;}}).text(options.label))
   )
   .append(&quot;&lt;br/&gt;&quot;).append($filter);
   $self.find(&quot;img[alt=&#39;Reset&#39;]&quot;).click(function(){$filter.val(&quot;&quot;)});
  });
}
})(jQuery);</pre>

<p>Then add to page JavaScript Execute when Page Loads</p>

<pre class="blog-program-code">
$(&quot;&#35;Px_SHUTTLE_ITEM_NAME&quot;).htmldbShuttlefilter({});</pre>

<p>Replace Px_SHUTTLE_ITEM_NAME with your real shuttle item name.</p>

<p><a href="https://apex.oracle.com/pls/apex/f?p=39006:52" target="_blank">See working example</a>.</p>

<p><img alt="Shuttle with filter" height="304" src="https://static.jaris.fi/b/i/P201410131041380630/shuttle_with_filter.png" width="588" /></p>
]]></content:encoded>
      <pubDate>Mon, 13 Oct 2014 08:41:00 GMT</pubDate>
      <guid isPermaLink="false">201410131041380630</guid>
    </item>
    <item>
      <title>Yet another way to create &quot;editable interactive report&quot; part 2</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Tips and Tricks</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201406180631350353</link>
      <description>Enhance editable interactive report whit select list and textarea on APEX 4.2</description>
      <content:encoded><![CDATA[<div class="ck-content"><p>While back I did write blog post <a href="/ords/r/jaris/blog/post?p2_post_id=201402122308060084">Yet another way to create "editable interactive report"</a>. This post is part 2, where I explain how you can add select lists and text area to report. In example I have use <a href="http://docs.oracle.com/cd/E37097_01/doc/doc.42/e35127/apex_collection.htm&#35;AEAPI531" target="_blank">APEX_COLLECTION</a>, but you can modify code to work with real table.</p><p>First create Before Regions PL/SQL process that creates apex_collection:</p><pre class="blog-program-code">APEX_COLLECTION.CREATE_COLLECTION_FROM_QUERYB2 (
      p_collection_name =&gt; 'EMP',
      p_query =&gt; '
        SELECT e1.empno
          , e1.sal
          , COALESCE(e1.comm, 0) AS comm
          , e1.mgr
          , e1.deptno
          , e1.hiredate
          , NULL
          , NULL
          , NULL
          , NULL
          , e1.ename
          , e1.job
       FROM emp e1
      '
    );
    </pre><p>Then go to Shared Components &gt; Lists of Values and create new LOV:</p><ul><li>From Scratch</li><li>name: MANAGER</li><li>type: Dynamic</li><li>Query:</li></ul><pre class="blog-program-code">select ename d, empno r
    from   emp
    order by 1</pre><p>Create another new LOV:</p><ul><li>From Scratch</li><li>name: DEPARTMENT</li><li>type: Dynamic</li><li>Query:</li></ul><pre class="blog-program-code">select dname d, deptno r
    from   dept
    order by 1</pre><p>Go back to edit page and create interactive report from query:</p><pre class="blog-program-code">SELECT seq_id
      ,n001    AS empno
      ,c001    AS ename
      ,c002    AS job
      ,n004    AS mgr
      ,d001    AS hiredate
      ,n002    AS sal
      ,n003    AS comm
      ,n005    AS dname
      ,clob001 AS enotes
      ,n004    AS mgrno
      ,n005    AS deptno
    FROM apex_collections
    WHERE collection_name = 'EMP'</pre><p>Change ENOTES column <i>Display as</i>&nbsp;to <i>standard report column</i>, MGRNO and DEPTNO columns to <i>hidden</i>.</p><p><img src="https://static.jaris.fi/b/i/P201406180631350353/eir2_column_attributes.png" alt="Column attributes" height="138" width="685"></p><p>Edit ENOTES column attributes and add to column formatting:</p><pre class="blog-program-code">&lt;textarea class="ir-edit-input" id="CLOB-&#35;SEQ_ID&#35;-1" cols="30" rows="3"&gt;&#35;ENOTES&#35;&lt;/textarea&gt;</pre><p><img src="https://static.jaris.fi/b/i/P201406180631350353/eir2_clob_column_formatting.png" alt="Clob column formatting" height="230" width="590"></p><p>You can control text are width and height using attributes cols and rows.</p><p>Edit MGR column attributes and change Display Type to Display as Text (based on LOV, escape special characters).<br>Set Named List of Values to MANAGER</p><p><img src="https://static.jaris.fi/b/i/P201406180631350353/eir2_mgr_column.png" alt="Manager column attributes" height="498" width="685"></p><p>Set column formatting</p><pre class="blog-program-code">&lt;select class="ir-edit-input" data-lov="MANAGER" data-display-null="- Select -" data-last-value="&#35;MGRNO&#35;" id="N-&#35;SEQ_ID&#35;-4"&gt;&lt;/select&gt;</pre><p>Attribute data-lov defines shared components LOV name used for&nbsp;column select list.<br>Include&nbsp;attribute data-display-null if LOV should have null value. This attribute value is used as null display value.</p><p>From report query you can see that apex_collection columns n004 and n005 are defined two times with different alias. We did set columns with alias MGRNO and DETPNO as hidden column. Attribute data-last-value value should be set to that hidden column substitution string because we like have real column value to this attribute. If we use column substitution string value from column where Display Type was set to Display as Text (based on LOV, escape special characters), then attribute gets LOV display value. And select list do not work correctly in that case.</p><p><img src="https://static.jaris.fi/b/i/P201406180631350353/eir2_mgr_column_formatting.png" alt="Manager column formatting" height="215" width="590"></p><p>Edit DNAME column attributes and change Display Type to Display as Text (based on LOV, escape special characters).<br>Set Named List of Values to DEPARTMENT</p><p><img src="https://static.jaris.fi/b/i/P201406180631350353/eir2_dname_column.png" alt="Department column attributes" height="493" width="685"></p><p>Set column formatting</p><pre class="blog-program-code">&lt;select class="ir-edit-input" data-lov="DEPARTMENT" data-last-value="&#35;DEPTNO&#35;" id="N-&#35;SEQ_ID&#35;-5"&gt;&lt;/select&gt;</pre><p><img src="https://static.jaris.fi/b/i/P201406180631350353/eir2_dname_column_formatting.png" alt="Department column formatting" height="210" width="590"></p><p>Setup for other columns see <a href="/ords/r/jaris/blog/post?p2_post_id=201402122308060084">previous blog post</a>.</p><p>Create After Footer PL/SQL process:</p><pre class="blog-program-code">DECLARE
      cur  SYS_REFCURSOR;
      lov_d VARCHAR2(4000);
      lov_r VARCHAR2(4000);
      l_cnt PLS_INTEGER;
    BEGIN
      htp.prn('&lt;script type="text/javascript"&gt;');
      FOR c1 IN (
        SELECT list_of_values_query
         ,list_of_values_name
        FROM apex_application_lovs
        WHERE application_id = :APP_ID
        AND list_of_values_name IN('MANAGER', 'DEPARTMENT')
      ) LOOP
        l_cnt := 0;
        OPEN cur FOR c1.list_of_values_query;
        LOOP
        FETCH cur INTO lov_d, lov_r;
        EXIT WHEN cur%NOTFOUND;
          l_cnt := cur%ROWCOUNT;
          IF l_cnt = 1 THEN
            htp.prn('var ' || c1.list_of_values_name || ' = [{');
          ELSE
            htp.prn(',{');
          END IF;
          htp.prn('"D":"' || lov_d || '","R":"' || lov_r || '"}');
        END LOOP;
        CLOSE cur;
        IF l_cnt = 0 THEN
          htp.p('var ' || c1.list_of_values_name || ' = [];');
        ELSE
          htp.p('];');
        END IF;
      END LOOP;
      htp.prn('&lt;/script&gt;');
    END;</pre><p>This process will print LOV values as JSON objects.</p><p>We need JavaScript function that creates select list options from JSON objects.<br>Add to page JavaScript Function and Global Variable Declaration:</p><pre class="blog-program-code">(function($){
     $.fn.htmldbCreateSelect = function(){
      this.filter("select").empty().each(function(i){
       lSelf = $(this);
       lov = eval(lSelf.data("lov"));
       if(lSelf.data("display-null")){
        lSelf.append($("&lt;option/&gt;",{"value":"","text":lSelf.data("display-null")}));
       }
       $.each(lov,function(i,d){
        lSelf.append($("&lt;option/&gt;",{"value":d["R"],"text":d["D"]}));
       });
       lSelf.val(lSelf.data("last-value"));
      });
      return this;
     }
    })(apex.jQuery);</pre><p>Next create dynamic action</p><ul><li>Name: Edit IR</li><li>Event: After Refresh</li><li>Selection Type: Region</li><li>Region: <i>{select IR region}</i></li><li>Condition: -No Condition-</li><li>Action: Execute JavaScript code</li><li>Fire On Page Load: True</li><li>Selection Type: None</li><li>Code:</li></ul><pre class="blog-program-code">$(this.triggeringElement)
    .find("td .ir-edit-input")
    .change({region:this.triggeringElement},function(e){
     var $lThis=$(this), $lRegion=$(e.data.region);
     apex.server.process("SAVE_DATA",{
      x01:this.id,
      f01:$s_Split(this.value, 4000)
     },{
      dataType:"html",
      beforeSend:function(){
       $lThis.prop("disabled", true).parent("td").addClass("ui-autocomplete-loading");
      },
      success:function(pData){
       if(pData!=="OK"){
        alert(pData);
        $lThis.val($lThis.data("last-value")).prop("disabled", false).parent("td").removeClass("ui-autocomplete-loading");
        return false;
       }
       /* Refresh IR after value is saved */
       $lRegion.trigger("apexrefresh");
      }
     });
    })
    .htmldbCreateSelect()
    .filter("[id^=D]")
    .datepicker({
     dateFormat:"dd.mm.yy",
     constrainInput:true,
     changeMonth:true,
     changeYear:true,
     showButtonPanel:true,
     showOn:"both",
     buttonImageOnly:true,
     buttonImage:"&#35;IMAGE_PREFIX&#35;asfdcldr.gif",
     showAnim:"slideDown"
    });
    </pre><p>Then create On Demand process called SAVE_DATA</p><pre class="blog-program-code">DECLARE
      l_val      VARCHAR2(32700);
      l_len      PLS_INTEGER := 0;
      l_data     CLOB;
      l_date_val DATE;
      l_num_val  NUMBER;
      l_arr      APEX_APPLICATION_GLOBAL.VC_ARR2;
    BEGIN
      l_len := APEX_APPLICATION.G_f01.COUNT;
     
      IF l_len &gt; 0 THEN
        l_val := APEX_APPLICATION.G_f01(1);
      END IF;
      l_arr := APEX_UTIL.STRING_TO_TABLE(APEX_APPLICATION.G_X01, '-');
      IF l_arr(1) = 'C' THEN
        APEX_COLLECTION.UPDATE_MEMBER_ATTRIBUTE (
          p_collection_name =&gt; 'EMP',
          p_seq             =&gt; l_arr(2),
          p_attr_number     =&gt; l_arr(3),
          p_attr_value      =&gt; l_val
        );
      ELSIF l_arr(1) = 'N' THEN
        BEGIN
          l_num_val := TO_NUMBER(l_val);
        EXCEPTION WHEN VALUE_ERROR
        THEN
          HTP.PRN(q'[Couldn't save value. Please check that value is valid number.]');
          RETURN;
        END;
        APEX_COLLECTION.UPDATE_MEMBER_ATTRIBUTE (
          p_collection_name =&gt; 'EMP',
          p_seq             =&gt; l_arr(2),
          p_attr_number     =&gt; l_arr(3),
          p_number_value    =&gt; l_num_val
        );
      ELSIF l_arr(1) = 'D' THEN
        BEGIN
          l_date_val := TO_DATE(l_val, 'DD.MM.YYYY');
        EXCEPTION WHEN OTHERS
        THEN
          HTP.PRN(q'[Couldn't save value. Please check that value is valid date.]');
          RETURN;
        END;
        APEX_COLLECTION.UPDATE_MEMBER_ATTRIBUTE (
          p_collection_name =&gt; 'EMP',
          p_seq             =&gt; l_arr(2),
          p_attr_number     =&gt; l_arr(3),
          p_date_value      =&gt; l_date_val
        );
      ELSIF l_arr(1) = 'CLOB' THEN
       
        IF l_len = 0
        OR COALESCE(LENGTH(APEX_APPLICATION.G_f01(1)), 0) = 0
        THEN
          APEX_COLLECTION.UPDATE_MEMBER_ATTRIBUTE (
            p_collection_name =&gt; 'EMP',
            p_seq             =&gt; l_arr(2),
            p_clob_number     =&gt; 1,
            p_clob_value      =&gt; NULL
          );
        ELSE
         
          dbms_lob.createtemporary(
            lob_loc =&gt; l_data,
            cache   =&gt; TRUE,
            dur     =&gt; dbms_lob.session
          );
          dbms_lob.open(l_data, dbms_lob.lob_readwrite);
       
          FOR i IN 1 .. l_len
          LOOP
            dbms_lob.writeappend(
              lob_loc =&gt; l_data,
              amount  =&gt; LENGTH(APEX_APPLICATION.G_f01(i)),
              buffer  =&gt; APEX_APPLICATION.G_f01(i)
            );
          END LOOP;
          dbms_lob.close(l_data);
          APEX_COLLECTION.UPDATE_MEMBER_ATTRIBUTE (
            p_collection_name =&gt; 'EMP',
            p_seq             =&gt; l_arr(2),
            p_clob_number     =&gt; 1,
            p_clob_value      =&gt; l_data
          );
        END IF;
      END IF;
     
      HTP.PRN('OK');
    END;</pre><p>And for last add to page CSS inline:</p><pre class="blog-program-code">.ui-autocomplete-loading {
     background: url("&#35;IMAGE_PREFIX&#35;libraries/jquery-ui/1.8/themes/base/images/ui-anim_basic_16x16.gif") no-repeat scroll 98% 10% &#35;EFEFEF !important;
    }
    .apexir_WORKSHEET_DATA td {
     padding-right: 22px !important;
     white-space: nowrap;
     vertical-align: top;
    }
    .apexir_WORKSHEET_DATA .ir-edit-input {
     color: inherit;
     text-align: inherit;
    }</pre><p>Now when you run page, you should have "editable IR" where is select list for manager and department, and text area for notes.</p><p><img src="https://static.jaris.fi/b/i/P201406180631350353/eir2_apex42_example.png" alt="APEX 4.2 editable interactive report" height="299" width="685"></p><p><a href="https://apex.oracle.com/pls/apex/f?p=39006:47" target="_blank">See working example</a>.</p><p>Sample is also available for <a href="https://static.jaris.fi/b/download/editable_ir_apex42.zip">download</a>.</p></div>]]></content:encoded>
      <pubDate>Wed, 18 Jun 2014 04:31:00 GMT</pubDate>
      <guid isPermaLink="false">201406180631350353</guid>
    </item>
    <item>
      <title>APEX 4.2 resizable interactive report column heading menu</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Tips and Tricks</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201404041301590191</link>
      <description>How to use jQuery ui resizable widget to resize APEX 4.2 interactive report column heading menu.</description>
      <content:encoded><![CDATA[<p>I have previously write post <a href="/ords/r/jaris/blog/post?p2_post_id=897800346179133">Resizing interactive report column dropdown list</a> that did work also for APEX 3.2.</p>

<p>Here is more simple solution that works on APEX 4.2.</p>

<p>First create page where is interactive report. Edit page attributes and add to page CSS inline</p>

<pre class="blog-program-code">
&#35;apexir_rollover.ui-resizable {
 padding-right: 8px;
}
&#35;apexir_rollover div.ui-resizable-handle {
 background: url(&quot;&#35;IMAGE_PREFIX&#35;qb/close.png&quot;) no-repeat scroll 0 50% &#35;F0F0F0;
 border-left: 2px solid &#35;F0F0F0;
 right: 0;
}</pre>

<p>Then create dynamic action</p>

<ul>
	<li>Name: Set resizable ir column heading menu</li>
	<li>Event: Page load</li>
	<li>Condition: -No Condition-</li>
	<li>Action: Execute JavaScript code</li>
	<li>Code:
	<pre class="blog-program-code">
this.affectedElements.resizable({
 minWidth:200,
 handles:&quot;e&quot;,
 stop:function(){$(this).css({&quot;height&quot;:&quot;&quot;})}
});
</pre>
	</li>
	<li>Selection Type: jQuery Selector</li>
	<li>jQuery Selector: &#35;apexir_rollover</li>
</ul>

<p>Now when you run page and click interactive column heading you can resize menu.</p>

<p><a href="https://apex.oracle.com/pls/apex/f?p=39006:43" target="_blank">See working example</a>.</p>

<p><img alt="Resizable interactive report column menu" height="408" src="https://static.jaris.fi/b/i/P201404041301590191/resizable_ir_col_menu.png" width="383" /></p>
]]></content:encoded>
      <pubDate>Fri, 04 Apr 2014 11:01:00 GMT</pubDate>
      <guid isPermaLink="false">201404041301590191</guid>
    </item>
    <item>
      <title>Convert APEX 4.2 tabular form popup key LOV to jQuery autocomplete</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX and jQuery</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201403270854590172</link>
      <description>How to convert APEX 4.2 tabular form popup key LOV to jQuery autocomplete widget.</description>
      <content:encoded><![CDATA[<div class="ck-content"><p>Currently in APEX 4.2.4 there is no native autocomplete item for tabular from. If you need one, you can use e.g. jQuery UI autocomplete widget.</p><p>Here is how you can enable autocomplete for APEX tabular form Popup Key LOV items. This solution can be used for Popup LOV (query based LOV) or Popup Key LOV (named LOV) if named LOV source is query.</p><p>First create tabular from using wizard. In this example I did create tabular form top of EMP table.</p><p>Edit page attributes and add to page JavaScript file URLs</p><pre class="blog-program-code">&#35;IMAGE_PREFIX&#35;libraries/jquery-ui/1.8.22/ui/minified/jquery.ui.autocomplete.min.js</pre><p>And page JavaScript Function and Global Variable Declaration</p><pre class="blog-program-code">/* array for search result cache */
var mgrCache = {};
(function ($) {
$.fn.htmldbAutoComplete = function (opt, optui) {
  var lSelf = this;
  // Result highlight
  $.ui.autocomplete.prototype._renderItem = function (ul, item) {
   var term = this.term.split(' ').join('|');
   var re = new RegExp("(" + term + ")", "gi");
   var t = item.label.replace(re, "&lt;b&gt;$1&lt;/b&gt;");
   return $("&lt;li&gt;&lt;/li&gt;")
   .data("item.autocomplete", item)
   .append("&lt;a&gt;" + t + "&lt;/a&gt;")
   .appendTo(ul);
  };
  // Autocomplete options
  optui = $.extend({
    delay : 500,
    change : function (ev, ui) {
     $(this).val() ? ui.item || __setmyval(this, $(this).data("last-value"), $(this).siblings("input").data("last-value")) : __setmyval(this, null, null);
    },
    select : function (ev, ui) {
     __setmyval(this, ui.item.value, ui.item.retval);
    },
    source : function (req, res) {
     if (opt.cacheVar) {
      if (req.term in opt.cacheVar) {
       res(opt.cacheVar[req.term]);
       return;
      }
     }
     apex.server.process(opt.process, {
      x01 : req.term,
      x02 : this.element.data("col-name")
     }, {
      success : function (data) {
       opt.cacheVar ? (opt.cacheVar[req.term] = data.row, res(opt.cacheVar[req.term])) : res(data.row);
      }
     });
    }
   }, optui);
  $("form").submit(function () {
   lSelf.attr("disabled","disabled");
  });
  opt.removeIcon &amp;&amp; lSelf.siblings("a").remove();
  return lSelf
  .removeAttr("disabled")
  .removeAttr("onfocus")
  .autocomplete(optui)
  .each(function () {
   __setmylastval(this);
   $(this).siblings("input").change(function() {
    __setmylastval(this);
   });
   this.onfocus = null;
  })
  .change(function () {
   $(this).val() || __setmyval(this, null, null);
  });
  /* private functions */
  function __setmyval(pThis, pDis, pRet) {
   $(pThis).val(pDis).data("last-value", pDis).siblings("input").val(pRet).data("last-value", pRet);
  }
  function __setmylastval(pThis){
   var lThis = $(pThis), lThat = lThis.siblings("input");
   lThis.data("last-value", lThis.val());
   lThat.data("last-value", lThat.val()); 
  }
}
})(apex.jQuery);</pre><p>First line <i>var mgrCache = {};</i> is needed if you like cache autocomplete search results.</p><p>Change "Add Row" button action to Defined by Dynamic Action.</p><p>Edit tabular form and change e.g. MGR column Display As to Popup LOV (query based LOV).<br>Add to Element Attributes</p><pre class="blog-program-code">data-col-name="MGR"</pre><p>And List of values definition</p><pre class="blog-program-code">SELECT ENAME
  ,EMPNO
FROM EMP
</pre><p>Create On Demand process called GET_LOV</p><pre class="blog-program-code">DECLARE
  l_cursor	PLS_INTEGER;
  l_status	PLS_INTEGER;
  l_col_cnt PLS_INTEGER;
  l_row_cnt	SIMPLE_INTEGER := 0;
  l_column	dbms_sql.desc_tab2;
  l_value	VARCHAR2(32767);
  l_sql		VARCHAR2(32767);
BEGIN
  /* Get LOV query */
  SELECT COALESCE(c.inline_list_of_values, l.list_of_values_query) as qry
  INTO l_sql
  FROM apex_application_page_rpt_cols c
  LEFT JOIN apex_application_lovs l
	ON c.named_list_of_values = l.list_of_values_name 
	AND l.application_id = :APP_ID
  WHERE c.application_id = :APP_ID
  AND c.page_id = :APP_PAGE_ID
  AND c.column_alias = APEX_APPLICATION.G_x02
  ;
  
  /* Get LOV query columns */
  l_cursor := dbms_sql.open_cursor;
  dbms_sql.parse (l_cursor, l_sql, dbms_sql.native);
  dbms_sql.describe_columns2(l_cursor, l_col_cnt, l_column);
  dbms_sql.close_cursor(l_cursor);
  
  /* Check that query do have return and display value columns */
  IF l_column.count != 2 THEN
    raise_application_error (-20001, 'Invalid list of values query');
  END IF;
  
  /* Add autocomplete search to LOV query */
  l_sql := 'SELECT * FROM (' 
	|| l_sql 
	|| ') WHERE INSTR(UPPER('
	|| l_column(1).col_name
	|| '), :term) &gt; 0'
	|| ' ORDER BY 1'
	;
  
  l_cursor := dbms_sql.open_cursor;
  dbms_sql.parse (l_cursor, l_sql, dbms_sql.native);
  dbms_sql.bind_variable(l_cursor, ':term', UPPER( APEX_APPLICATION.G_x01));
  dbms_sql.describe_columns2(l_cursor, l_col_cnt, l_column);
  
  FOR i IN 1 .. l_column.count
  LOOP
    dbms_sql.define_column(l_cursor, i, l_value, 32767);
  END LOOP;
  
  l_status := dbms_sql.execute(l_cursor);
  
  /* Construct return data */
  LOOP
    EXIT
  WHEN (dbms_sql.fetch_rows(l_cursor) &lt;= 0 );
 
	l_row_cnt := l_row_cnt + 1;
    IF l_row_cnt = 1 THEN
      htp.prn('{"row":[{');
	ELSE
	  htp.prn(',{');
    END IF;
	
	FOR i IN 1 .. l_column.count
    LOOP
      dbms_sql.column_value(l_cursor, i, l_value);
      IF i = 1 THEN
        htp.prn('"value":"' || l_value || '"');
      ELSIF i = 2 THEN
        htp.prn(',"retval":"' || l_value || '"}');
      END IF;
    END LOOP;	
  END LOOP;
  IF l_row_cnt = 0 THEN
	htp.prn('{"row":[]}');
  ELSE
	htp.prn(']}');
  END IF;
  
  dbms_sql.close_cursor(l_cursor);
  
END;
</pre><p>Create dynamic action</p><ul><li>Name: Set autocomplete</li><li>Event: After Refresh</li><li>Selection Type: Region</li><li>Region: <i>{select tabular form region}</i></li><li>Condition: -No Condition-</li><li>Action: Execute JavaScript code</li><li>Fire On Page Load: True</li><li>Code:</li></ul><pre class="blog-program-code">this.affectedElements.find("input[data-col-name=MGR]")
.htmldbAutoComplete({
 process:"GET_LOV", /* On demand process name */
 removeIcon:false,  /* Remove popup lov icon true/false */
 cacheVar:mgrCache  /* Global variable name to hold search result cache. This is optional */
});
</pre><ul><li>Selection Type: Region</li><li>Region: <i>{select tabular form region}</i></li></ul><p>Create another dynamic action</p><ul><li>Name: Add row</li><li>Event: Click</li><li>Selection Type: Button</li><li>Button: <i>{select Add Row button}</i></li><li>Condition: -No Condition-</li><li>Action: Execute JavaScript code</li><li>Fire On Page Load: False</li><li>Code:</li></ul><pre class="blog-program-code">apex.widget.tabular.addRow();
this.affectedElements.find("input[data-col-name=MGR]:last")
.htmldbAutoComplete({
 process:"GET_LOV",
 removeIcon:false,
 cacheVar:mgrCache
});
</pre><ul><li>Selection Type: Region</li><li>Region: <i>{select tabular form region}</i></li></ul><p>Run page and type e.g. A to MGR column field to see autocomplete in action.</p><p><a href="https://apex.oracle.com/pls/apex/f?p=39006:27" target="_blank">See working example</a>.</p><p><img src="https://static.jaris.fi/b/i/P201403270854590172/tabular_form_popup_key_autocomplete.png" alt="Tabular form autocomplete" height="416" width="617"></p><p>You can also <a href="https://static.jaris.fi/b/download/tabular_form_jq_autocomplete_apex42.zip">download sample</a>.</p></div>]]></content:encoded>
      <pubDate>Thu, 27 Mar 2014 07:54:00 GMT</pubDate>
      <guid isPermaLink="false">201403270854590172</guid>
    </item>
    <item>
      <title>Narrow result set for APEX IR alternative default reports in query</title>
      <dc:creator>Jari Laine</dc:creator>
      <category>APEX Tips and Tricks</category>
      <link>https://cloud.jaris.fi/ords/r/jaris/blog/post?p2_post_id=201402172217120115</link>
      <description>How to use interactive report alternative default report alias in query to narrow result set in APEX 4.2</description>
      <content:encoded><![CDATA[<p>Here is small tip how utilize APEX_APPLICATION_PAGE_IR_RPT view and APEX_IR.GET_LAST_VIEWED_REPORT_ID function to narrow down interactive report query result for developer saved alternative default reports.</p>

<p>First create interactive report from query</p>

<pre class="blog-program-code">
SELECT empno
  ,ename
  ,job
  ,mgr
  ,hiredate
  ,sal
  ,comm
  ,deptno
FROM emp</pre>

<p>Go edit region attributes and set Static ID e.g. EMPREP</p>

<p>Next create hidden item. Set name to item and rest of attributes use wizard defaults.</p>

<p>Create computation for hidden item you just created</p>

<ul>
	<li>Computation point: SQL Query (return single value)</li>
	<li>Computation type: Before Region(s)</li>
	<li>Computation:
	<pre class="blog-program-code">
SELECT region_id
FROM apex_application_page_regions
WHERE static_id = &#39;EMPREP&#39;
	AND application_id = :APP_ID
	AND page_id = :APP_PAGE_ID
</pre>

	<p>Change EMPREP accordingly your interactive report region static id.</p>
	</li>
	<li>Condition Type: Value of Item / Column in Expression 1 Is NULL</li>
	<li>Expression 1: {<em>your hidden item name</em>}</li>
</ul>

<p>Then run page and save four alternative defaults for report without making any changes to report filters.</p>

<p><img alt="Save default report step 1" height="201" src="https://static.jaris.fi/b/i/P201402172217120115/save_ir_report_alt_step1.png" width="590" /></p>

<p><img alt="Save default report step 2" height="170" src="https://static.jaris.fi/b/i/P201402172217120115/save_ir_report_alt_step2.png" width="530" /></p>

<p>Now you should have primary and four alternative reports in select list witch all brings same amount rows.</p>

<p><img alt="Saved reports" height="275" src="https://static.jaris.fi/b/i/P201402172217120115/saved_ir_alt_reports.png" width="428" /></p>

<p>Go back to edit page and edit IR saved reports. Give all Alternative Default reports meaningful alias.</p>

<p><img alt="Saved reports alias" height="205" src="https://static.jaris.fi/b/i/P201402172217120115/ir_saved_reports_alias.png" width="686" /></p>

<p>Go edit report query and add where clause. Whole query should be like</p>

<pre class="blog-program-code">
SELECT empno
  ,ename
  ,job
  ,mgr
  ,hiredate
  ,sal
  ,comm
  ,deptno
FROM emp
WHERE NOT EXISTS (
&nbsp; SELECT 1
&nbsp; FROM apex_application_page_ir_rpt
&nbsp; WHERE application_id = :APP_ID
&nbsp;&nbsp; AND page_id = :APP_PAGE_ID
&nbsp;&nbsp; AND session_id IS NULL
&nbsp;&nbsp; AND report_id = apex_ir.get_last_viewed_report_id(:APP_PAGE_ID, :Px_HIDDEN_ITEM)
&nbsp;&nbsp; AND (
&nbsp;&nbsp;&nbsp; (
&nbsp;&nbsp;&nbsp;&nbsp; REPORT_ALIAS = &#39;NULL_COMM&#39;
&nbsp;&nbsp;&nbsp;&nbsp; AND COMM IS NOT NULL
&nbsp;&nbsp;&nbsp;&nbsp; )
&nbsp;&nbsp;&nbsp; OR (
&nbsp;&nbsp;&nbsp;&nbsp; REPORT_ALIAS = &#39;SMALL_SAL&#39;
&nbsp;&nbsp;&nbsp;&nbsp; AND SAL &gt; 1500
&nbsp;&nbsp;&nbsp;&nbsp; )
&nbsp;&nbsp;&nbsp; OR (
&nbsp;&nbsp;&nbsp;&nbsp; REPORT_ALIAS = &#39;BIG_SAL&#39;
&nbsp;&nbsp;&nbsp;&nbsp; AND SAL &lt;= 1500
&nbsp;&nbsp;&nbsp;&nbsp; )
&nbsp;&nbsp;&nbsp; OR (
&nbsp;&nbsp;&nbsp;&nbsp; REPORT_ALIAS = &#39;NOT_NULL_COMM&#39;
&nbsp;&nbsp;&nbsp;&nbsp; AND COMM IS NULL
&nbsp;&nbsp;&nbsp;&nbsp; )
&nbsp;&nbsp;&nbsp; )
&nbsp; )</pre>

<p>Change Px_HIDDEN_ITEM to your hidden item name.</p>

<p>Now when you run page and select from reports select list different report each should bring different result set</p>

<p><img alt="IR alternative result sets" height="254" src="https://static.jaris.fi/b/i/P201402172217120115/ir_with_different_saved_results.png" width="685" /></p>

<p><a href="https://apex.oracle.com/pls/otn/f?p=39006:49" target="_blank">See working example</a>.</p>
]]></content:encoded>
      <pubDate>Mon, 17 Feb 2014 21:17:00 GMT</pubDate>
      <guid isPermaLink="false">201402172217120115</guid>
    </item>
  </channel>
</rss>
