Friday, December 2, 2016

Overlay Side Navigation Menu

In the last couple of weeks, I've been playing with the side navigation menu. By default, the side navigation menu "pushes" the page content back and forth as it expands and collapses.
Which looks just like this:


By overriding the Universal Theme's CSS related to the side navigation menu, I was able to make the menu display as an overlay.

I've also tested the solution on the Apex 5.1 EA2 and noticed that the html markup of the menu changed quite a bit in order to handle RTL applications.

You can retrieve both CSS here:
https://github.com/maxime-tremblay/apex-css-overlay-sidemenu

Then will need to include the corresponding CSS file either in the theme roller's custom CSS attribute or as an external file in your application's CSS files.

Additionally, by default, the side navigation menu remembers the previous state it was in. So if on a page you expand the menu and refresh it, the menu will be rendered as expanded.

What we can do to prevent that from happening is have some JavaScript code execute on page load that is going to collapse the menu if it's expanded.
$('.t-PageBody.js-navExpanded #t_Button_navControl').click();

That code can be executed as a dynamic action on page 0 or in a global JavaScript file so that it's going to affect all pages in the application.

You will get something that looks like this:


If you would like to have the menu be fullscreen you can simply uncomment the fullscreen part at the end of the CSS file.

You can have a look at my Demo Application

Thursday, November 24, 2016

Right Side Column Page and Validation Error Message Region

If you ever used the "Right Side Column" page template, you might have noticed that the validation error message region is displayed under the right side region's expand/collapse trigger button.


As you can see above, the expand/collapse trigger button is on top of the close notification "X" button.

There are two ways to go in order to fix that, both using CSS only.

The first method is to add some right margin to the notification region.

.js-rightExpanded .t-Body-content .t-Body-alert,
.js-rightCollapsed .t-Body-content .t-Body-alert {
    margin-right: 40px; /* Width of the right side region's trigger button */
}

It will then look like this:

The second method is to reposition the notification region on top of the right side region's trigger button.

.js-rightExpanded .t-Body-content .t-Body-alert,
.js-rightCollapsed .t-Body-content .t-Body-alert {
    position: relative; /* Need to position the div for the z-index to work */
    z-index: 500; /* z-index of right side column is 490 */
}

It will then look like this:

Interactive Report Alternating Rows


For classic reports there is a region attribute template option to have the rows have alternating row color.


That template option is not part of the interactive report template options.


We can achieve the same effect in an interactive using once again some CSS.

.customAlternatingRow .a-IRR-table tr:nth-child(odd) td {
    background-color: yellow;
}

.customAlternatingRow .a-IRR-table tr:nth-child(even) td {
    background-color: yellowgreen;
}

The only thing left to do is to add the "customAlternatingRow" class to your region (under Appearance, CSS Classes) or at the page level.

It will then look like this:

The classic report is using   #fcfcfc (very light gray) for the odd rows and nothing for the even rows.

.customAlternatingRow .a-IRR-table tr:nth-child(odd) td {
    background-color: #fcfcfc;
}

Which would look like this:


If you would like to also change the highlighted/hovered row color, you can use the following CSS:

.customRowHighlight .a-IRR-table tr:hover td {
    background-color: #f9f9f9;
}

Note:
If you would like this to be applied on all interactive reports in your application, simply remove the ".customAlternatingRow" and/or ".customRowHighlight" from the CSS.

Browser Support of the :nth-child() Selector (from w3schools)

Selector Chrome IE Firefox Safari Opera
:nth-child() 4.0 9.0 3.5 3.2 9.6


You can have a look at my Demo Application


Monday, November 21, 2016

Auto Publish All Translations When Importing an Application

When working with translated application, once everything is done in the source application, you need to seed all the translations, then translate what needs to be and then publish all the translations.
When migrating the application, you will have two choices.

  1. Export the source application and export all mapped translated applications. Then import all the application in the other environment
  2. Export the source application only including the translations (export preferences attribute). Then import the application in the other environment. Then publish all translations.

I usually go with the second method.
If the person that migrates the application knows their way around Apex, it's not that big of a deal.
But when it's not the developer that migrates the applications (e.g. when it's the DBAs), it can be more complicated to explain them the complete procedure.
Furthermore, it could happen that the person who imports the application simply forgets to publish the translations afterwards.

I thought it would be great if the option to include translations when exporting the application also published them when importing the application, but unfortunately, it's not the case.

One thing you can do is to use the apex_lang.publish_application API
http://docs.oracle.com/cd/E59726_01/doc.50/e39149/apex_lang.htm#CHDCBEDE

You can use it just like in the API's documentation example as a script you would need to run after the import process.

But it would be great if that was executed as part of the install process.

Supporting Objects to the rescue!!

Supporting Objects


What we need to do is to create an Installation Script from scratch using the Editor that simply calls the API.







Use below code:

declare
    -- if using apex 5.0+, you can use 'apex_application_install.get_application_id' to retrieve the application id
    -- otherwise, you need to hardcode the application id
    l_app_id number := apex_application_install.get_application_id;
begin
    for i in (select primary_application_id, translated_app_language
                from apex_application_trans_map
               where primary_application_id = l_app_id)
    loop
        apex_lang.publish_application(i.primary_application_id, i.translated_app_language);
    end loop;
end;
/

Then, when you'll export your application, make sure you include the supporting objects and that you export the translations.


Note: Depending on how you will import the application, the "Export Supporting Object Definitions" attribute has to be set to either "Yes" or "Yes and Install on Import Automatically".

Looking at the attribute's help:
  • Yes Includes supporting object definitions in the application export. Does not automatically load supporting objects when invoked from a command line.
  • Yes and Install on Import Automatically Includes supporting object definitions and a call to install supporting objects in the application export. This option is valid only for command line installs. When application imports from the command line, it automatically installs or upgrades the supporting objects.
So, if you import the application from Apex either will work, but if you import from a command line, you should set the attribute to "Yes and Install on Import Automatically".

When you'll import the application, simply choose to install the supporting objects.




Voila, your application's translations will be automatically published as part of the import process.

EDIT: Added a note regarding the "Export Supporting Object Definitions" attribute.

Monday, November 14, 2016

Required Asterisk Left of Item Label

If you ever had a customer asked you to move the required asterisk of the item's label before the item itself, you probably had to copy the existing template and move the span that holds the asterisk before the item itself then you had to go through all you labels to change the template.

Lately, I've been testing my demo application in the EA of Apex 5.1 and looking at the code and how things changed from 5.0 to 5.1.

Among all the changes, I noticed that the Apex team started to use the CSS3 order property.
Definition
The order property specifies the order of a flexible item relative to the rest of the flexible items inside the same container.
Note: If the element is not a flexible item, the order property has no effect.

Basically, this property can reorder items within the same container granted that the container is using the flex property.

To move the asterisk span using only CSS, we can use the following code:
.t-Form-fieldContainer .t-Form-labelContainer {
  display: flex; /* Make sure the container is using flex */
  justify-content: flex-end; /* Keep items right aligned */
}

.t-Form--labelsAbove .t-Form-fieldContainer .t-Form-labelContainer,
.t-Form--leftLabels .t-Form-fieldContainer .t-Form-labelContainer,
.t-Form-fieldContainer.t-Form-fieldContainer--stacked .t-Form-labelContainer {
  justify-content: flex-start; /* Keep items left aligned */
}

.t-Form-fieldContainer .t-Form-labelContainer .t-Form-required{
  order: 1; /* Asterisk as first element */
}

.t-Form-fieldContainer .t-Form-labelContainer .t-Form-label{
  order: 2; /* Label as second element */
}

You can have a look at my Demo Application

EDIT1: Fix an issue with above labels.

EDIT2: Fix an issue with "Blank with Attributes" region template.

EDIT3: Apex 5.1 now handles the required asterisk using CSS only and the position now depends on the label's alignment.
Right aligned: asterisk before
Left aligned: asterisk after

Friday, November 4, 2016

Handling Dialog Cancel Event

If you ever had to handle a modal dialog close to trigger some action on the parent page, you probably used a dynamic action using the "Dialog Closed" event.

When closing a modal dialog there are two ways to do it.
  1. Close
    • Using an after submit process of type "Close Dialog";
    • Using a dynamic action with a "Close Dialog" action;
    • Branching to a page that has a "Normal" page mode.
  2. Cancel
    • Using a dynamic action with a "Cancel Dialog" action;
    • Clicking on the top right "X" icon/button.

When using a branch to close the modal dialog, the modal dialog will close then it's parent will branch to the specified page.

Except when using the branch method, when you close the modal dialog, Apex will trigger a "Dialog Close" event. Then, on the parent page, you will be able to handle it using a dynamic action "Dialog Closed" event.
The thing is that the cancel methods will not trigger any event on the parent page. So, by default, you won't be able to handle the dialog close when it was cancelled.

One way to have both the close and cancel method trigger an even on the parent page would be to override the dialog close event (Apex is using jQuery's  UI Dialog Widget).

So what we will do is this.

You will need to define the following JavaScript function (either on the parent pages, on page 0 or in your JavaScript global library file)
function customEvent(event, data){
    apex.event.trigger(document, event, data);
}

Then, under the modal dialog page attributes, in the dialog section, set the "Attributes" to:
close: function() { customEvent('customDialogClose', {modalPageId: 'MODAL_CLOSE_FIXED'});}

What this will do is that it will trigger (on the parent page) the event "customDialogClose".
The second parameter is a data attribute that could be used to differentiate modals between each other. It can can be useful if you are calling more than one on the parent page or if you want to send data back to the parent page.

Finally, in your parent page you can define a custom dynamic action as follow:
Event: Custom
Custom Event: customDialogClose
Selection Type: JavaScript Expression
JavaScript Expression: document

One thing to note is that the default close event will still be triggered when using the close method.

You can have a look at my Demo Application

Monday, October 17, 2016

iOS Modal Scrolling

If you ever tried to scroll a modal page from within Safari on iOS you probably noticed that what is getting scrolled is the parent page content and not the actual modal content.

There is a simple fix for that:
body .ui-dialog .ui-dialog-content{
    -webkit-overflow-scrolling: touch;
}

If you're not using the built-in modal, the idea is to add the css property to the wrapping element of the iframe.
Let's say you're using Skillbuilders' Modal Plugin, you can use the following:
#cboxLoadedContent{
    -webkit-overflow-scrolling: touch;
}

You can have a look at my Demo Application

Tuesday, October 11, 2016

Return Page Item From an AJAX Callback Process

Recently, I was working on a form that had multiple items retrieved when a select list item changed. The select list triggered a dynamic action that executed some PL/SQL code and returned item values.

It was looking like that:

At some point, the process became "too big" for the "Execute PL/SQL Code" action (it can only hold up to 4000 characters).

So, I decided to move everything to a page "AJAX Callback" process which can hold up to 32767 characters.

So I replaced the dynamic action "Execute PL/SQL Code" action with an "Execute JavaScript" action as follow:
/* Show a processing image */
var lSpinner$ = apex.util.showSpinner();

apex.server.process("SOME_PROCESS",
                    {pageItems: "#P2300_LOV"
                     },
                    {success: function(pData) {
                        /* If the AJAX is successful set the value or the returned items */
                        if (pData.success === true){
                            /* Loop through the array and set the value of each item */
                            for (var i=0; i < pData.items.length; i++){
                                apex.item(pData.items[i].id).setValue(pData.items[i].value);
                            }
                        }
                        
                        /* Remove the processing image */
                        lSpinner$.remove();
                      },
                     error: function(request, status, error) {
                        alert(request.responseText);
                         
                        /* Remove the processing image */
                        lSpinner$.remove();
                      }
                     }
                    );

And I created the AJAX Callback as follow:
declare
    /* Local variables */

    /* Utility function to output item's id and value */
    procedure output_json_item(p_item_name  in varchar2,
                               p_item_value in varchar2
                               )
    as
    begin
        apex_json.open_object;
        apex_json.write('id', p_item_name);
        apex_json.write('value', p_item_value, true); /* true so that null values are written as well */
        apex_json.close_object;
    end output_json_item;
begin
    /* ********************** *
     * PL/SQL Process Content *
     * ********************** */
    if :P2300_LOV = '1' then
        :P2300_ITEM1 := 1;
        :P2300_ITEM2 := 1;
        :P2300_ITEM3 := 1;
        :P2300_ITEM4 := 1;
    elsif :P2300_LOV = '2' then
        :P2300_ITEM1 := 2;
        :P2300_ITEM2 := 2;
        :P2300_ITEM3 := 2;
        :P2300_ITEM4 := 2;
    elsif :P2300_LOV = '3' then
        :P2300_ITEM1 := 3;
        :P2300_ITEM2 := 3;
        :P2300_ITEM3 := 3;
        :P2300_ITEM4 := 3;
    end if;
        
    apex_json.open_object;
    apex_json.write('success', true);
    
    apex_json.open_array('items');

    /* Call the utility procedure for every item you want to return */
    output_json_item('P2300_ITEM1', :P2300_ITEM1);
    output_json_item('P2300_ITEM2', :P2300_ITEM2);
    output_json_item('P2300_ITEM3', :P2300_ITEM3);
    output_json_item('P2300_ITEM4', :P2300_ITEM4);
    
    apex_json.close_array;
    
    apex_json.close_object;
exception
    when others then
        apex_json.open_object;
        apex_json.write('success', false);
        apex_json.write('message', sqlerrm);
        apex_json.close_object;
end;

The idea here is to have PL/SQL AJAX Callback execute and if it successfully completes, it will be returning the list of items along with their values as JSON on which we are going to loop through and set the items' value using the JavaScript item API.

You can have a look at my Demo Application

Thursday, September 29, 2016

Customizing Cards Report

The other day I was working on a cards report for a customer and they wanted to have the cards on the same line be of equal height. They also wanted to have some kind of visual indicator of what card was currently being hovered.

Here's what the built-in card report looks like:


First, let's have a look at the equal height for the cards on the same line.
For this we'll be using the flexbox properties. If you're not familiar with flexbox, you can have a look at this article: A Complete Guide to Flexbox.

Add following class to you region's CSS Class Attribute: "t-Cards--equal-height" and the following CSS to your page.
.t-Cards--equal-height .t-Cards {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: wrap;
        flex-wrap: wrap;
} 

.t-Cards--equal-height .t-Cards .t-Cards-item {
    margin-bottom: 10px; /* Adds some space between lines */
} 

.t-Cards--equal-height .t-Cards .t-Cards-item .t-Card,
.t-Cards--equal-height .t-Cards .t-Cards-item .t-Card .t-Card-wrap {
    height: 100%;
}

Result will look like this:


Now, let's highlight the header when hovering a card.
Add following class to you region's CSS Class Attribute: "t-Cards--header-highlight" and the following CSS to your page.
.t-Cards--header-highlight .t-Cards .t-Cards-item .t-Card:hover .t-Card-titleWrap{
    background-color: #c8102e;
}

.t-Cards--header-highlight .t-Cards .t-Cards-item .t-Card:hover .t-Card-titleWrap .t-Card-title {
    color: #fff;
}

.t-Cards--header-highlight .t-Cards .t-Cards-item .t-Card:hover .t-Card-wrap {
    border: 1px solid rgba(0,0,0,.05);
    box-shadow: 0 2px 2px rgba(0,0,0,.05);
}

/* Featured Style Padding/Margin Fix */
.t-Cards--header-highlight .t-Cards.t-Cards--featured .t-Cards-item .t-Card .t-Card-titleWrap {
    margin: 0 !important;
    padding: 24px 16px;
}

.t-Cards--header-highlight .t-Cards.t-Cards--featured .t-Cards-item .t-Card .t-Card-body {
   margin: 0 16px 24px 16px !important;
}

The second part of the above CSS is to change the cards's margin to padding, we need to do this because the background-color is not affecting the margins, but it is for the paddings.

Result will look like this:


The above CSS works for all cards report styles (Basic, Compact and Featured).

You can have a look at my Demo Application

EDIT: Apex 5.1 displays cards with equal height by default.

Tuesday, September 20, 2016

Securing Ajax Callback Process

When navigating from a report page to the corresponding detail page, it's always a good practice to enable the parameters checksum to prevent users from tampering with the item values that are in the url.

To enable arguments checksum, you must first set the "Page Access Protection" attribute under the page attribute security section to "Arguments must have checksum" and then for each item that is going to be assigned from the url, you need to set the "Session State Protection" to one of the "Checksum Required" value (have a look at the item's help for more details about the different types of checksum).



That way the user is not going to be able to change any value from the url.

I recently had to implement a similar concept using button in a report that were calling an AJAX Callback Process from a dynamic action.

Here's what my report's SQL Query looked like:
select '<button onclick="void(0);"'
           || ' data-data1="' || some_table_id || '"'
           || ' data-data2="' || some_value || '"'
           --|| ' data-data3="' || sys.dbms_crypto.hash(utl_raw.cast_to_raw(some_table_id || some_value || to_char(nvl(last_update_date, creation_date), :DATETIMEFORMAT)), 3) || '"' /* Where 3 -> SHA-1 from sys.dbms_crypto.hash_sh1 */
           || ' data-data3="' || apex_util.get_hash(apex_t_varchar2(some_table_id, some_value, to_char(nvl(last_update_date, creation_date), :DATETIMEFORMAT))) || '"'
           || ' class="t-Button t-Button--hot t-Button--small actionButton"'
           || ' type="button"><span class="t-Button-label">Some Action</span></button>'
       /* Rest of the query */
  from some_table
 where /* where clauses */
Where data1 and data2 are the values that are needed in the AJAX Callback Process and data3 is the checksum for the previous values.
To have a stronger checksum and to ensure that it is only used once, I added the record's last update date to it.
DATETIMEFORMAT is an application substitution string.

Note: in order to be able to use dbms_crypto, it needs to be granted to the current user (not required with the apex_util.get_hash function.
For more information on the Apex API, read this GET_HASH Function.

The corresponding dynamic action to handle the button click was as follow:
Dynamic Action: Event: Click
jQuery Selector: ".actionButton"
var lSpinner$ = apex.util.showSpinner();
var lReportRegion$ = $(this.affectedElements);

apex.server.process("AJAX_PROCESS_NAME",
                    {x01: $(this.triggeringElement).data('data1'),
                     x02: $(this.triggeringElement).data('data2'),
                     x03: $(this.triggeringElement).data('data3')
                     },
                    {success: function( pData ) {
                        if (pData.success === true){
                            /* Show Sucess Message */
                            showSuccessMessage(pData.message);
                            
                            /* Refresh Region */
                            lReportRegion$.trigger('apexrefresh');
                        }
                        else{
                            /* Show Error Message */
                            showErrorMessage([pData.message]);
                        }
                        
                        lSpinner$.remove();
                      }
                     }
                    );
In the previous code, I'm using the affected element attribute to refresh my report region.

Here's the AJAX Callback Process:
declare
    l_some_table_id number;
    l_some_value    varchar2(50);
    l_checksum      varchar2(32767);
    --l_checksum      raw(32767); --sys.dbms_crypto.hash returns raw
    --l_hash_type     pls_integer := sys.dbms_crypto.hash_sh1;
    
    l_last_update   varchar2(100);
begin
    /* Retrieve parameters */
    l_some_table_id := to_number(apex_application.g_x01);
    l_some_value    := apex_application.g_x02;
    l_checksum      := apex_application.g_x03;
    
    select to_char(nvl(last_update_date, creation_date), :DATETIMEFORMAT)
      into l_last_update
      from some_table
     where some_table_id = l_some_table_id;
    
    /* Validate the checksum */
    --if l_checksum = sys.dbms_crypto.hash(utl_raw.cast_to_raw(l_some_table_id || l_some_value || l_last_update), l_hash_type) then
    if l_checksum = apex_util.get_hash(apex_t_varchar2(l_some_table_id, l_some_value, l_last_update)) then
        /* Do something */
        
        apex_json.open_object;
        apex_json.write('success', true);
        apex_json.write('message', 'Action Processed.');
        apex_json.close_object;
    else
        apex_json.open_object;
        apex_json.write('success', false);
        apex_json.write('message', 'Invalid Action.');
        apex_json.close_object;
    end if;
exception
    when no_data_found then
        apex_json.open_object;
        apex_json.write('success', false);
        apex_json.write('message', 'Invalid Action.');
        apex_json.close_object;
    when others then
        apex_json.open_object;
        apex_json.write('success', false);
        apex_json.write('message', 'Invalid Action.');
        apex_json.close_object;
end;


Basically what we do in the process is to recreate a checksum based on the parameters' values and to compare it with the checksum parameter and if they match we execute the code that needs to run.

Note: Edited above code to use the Apex API: apex_util.get_hash. Commented out everything related to dbms_crypto for reference purpose.

Friday, September 9, 2016

Adding a Clear Icon to an Item

Here's how to add a clear icon to an item (similar to the calendar icon of the datepicker item).
Since the datepicker's icon has the same look and feel as what we want, let's reuse the same CSS and html.

Let's inspect the html code of the datepicker's icon:

<button class="ui-datepicker-trigger a-Button a-Button--calendar" type="button">
    <span class="a-Icon icon-calendar"></span>
    <span class="u-VisuallyHidden">Popup Calendar: Date Picker</span>
</button>

  • ui-datepicker-trigger: class used to handle the click event
    • Let's rename that to 'clearItem-trigger'
  • a-Button: standard look and feel of buttons
    • Let's reuse it as we want the same look and feel
  • a-Button--calendar: additional css for the calendar icon
    • Let's rename that to 'a-Button--clearInput'
  • icon-calendar: image of the calendar
    • Let's replace it with a clear icon: 'icon-remove'
We can then retrieve and adapt the CSS for the above classes from the Core.min.css.
We will then have the following CSS:
.t-Form-inputContainer .a-Button--clearInput{
    margin-left: -.1rem;
    order: 3; /* Apex 5.1: Set the correct order */
}

.t-Form--large .a-Button.a-Button--clearInput,
.t-Form-fieldContainer--large .a-Button.a-Button--clearInput {
    padding: .8rem 1.2rem
}

.t-Form--xlarge .a-Button.a-Button--clearInput,
.t-Form-fieldContainer--xlarge .a-Button.a-Button--clearInput {
    padding: 1.4rem 1.2rem
}

.t-Form--login .a-Button.a-Button--clearInput {
    padding: 1rem 1.2rem
}

That's for the visual aspect of the icon.
Let's now add the following JavaScript code that will add the remove icon and that will handle the click event.

JavaScript code on page page load:
$('.clearInput').each(function(){
    var lTriggerHtml = '<button class="clearInput-trigger a-Button a-Button--clearInput" data-item_id="' + $(this).attr('id') + '" type="button">'
                     +     '<span class="a-Icon icon-remove"></span>'
                     +     '<span class="u-VisuallyHidden">Clear Item\'s Value</span>'
                     + '</button>';
    $(this).after(lTriggerHtml);
});

$('.clearInput-trigger').click(function(){
    $('#' + $(this).data('item_id')).val('');
});

The first part will add the clear button after each element that has the 'clearInput' class.
The second part is used to handle the click event.

Now you simply have to add the 'clearInput' class (under the item's Advanced properties) to each item you want the clear button to be added.

One thing to note is that the above code will not trigger the item's change event.
If you would also like to trigger the change event, we could add an additionnal CSS class to the item like 'clearInput--change' and update the second part of the JavaScript code to:

We need to add the two following classes:
$('.clearInput-trigger').click(function(){
    var triggeringElement = $('#' + $(this).data('item_id'));
    
    triggeringElement.val('');
    
    if (triggeringElement.hasClass('clearInput--change')){
        triggeringElement.change();
    }
});

We also need to update the JavaScript Code that adds the button so that it assigns the corresponding CSS class to the button.
$('.clearInput').each(function(){
    var lClearInputClass = $(this).hasClass('clearInput--inside') ? 'a-Button--clearInputInside': 'a-Button--clearInput';
    var lTriggerHtml = '<button class="clearInput-trigger a-Button ' + lClearInputClass + '" data-item_id="' + $(this).attr('id') + '" type="button">'
                     +     '<span class="a-Icon icon-remove"></span>'
                     +     '<span class="u-VisuallyHidden">Clear Item\'s Value</span>'
                     + '</button>';
    
    $(this).after(lTriggerHtml);
});

Now supposed that you would like to have the icon be inside the element.
You need to have the element has right padding (equal to the width of the icon, 32px in our case) and you need to move the icon left by the same amount.

.clearInput--inside{
    padding-right: 32px !important;
}

.t-Form-inputContainer .a-Button--clearInputInside{
    margin-left: -32px;
}

Simply have to set the 'clearInput clearInput--inside' class (under the item's Advanced properties) to each item you want the clear button to be added inside.

Final JavaScript:
$('.clearInput').each(function(){
    var lClearInputClass = $(this).hasClass('clearInput--inside') ? 'a-Button--clearInputInside': 'a-Button--clearInput';
    var lTriggerHtml = '<button class="clearInput-trigger a-Button ' + lClearInputClass + '" data-item_id="' + $(this).attr('id') + '" type="button">'
                     +     '<span class="a-Icon icon-remove"></span>'
                     +     '<span class="u-VisuallyHidden">Clear Item\'s Value</span>'
                     + '</button>';
    
    $(this).after(lTriggerHtml);
});
 
$('.clearInput-trigger').click(function(){
    var triggeringElement = $('#' + $(this).data('item_id'));
     
    triggeringElement.val('');
     
    if (triggeringElement.hasClass('clearInput--change')){
        triggeringElement.change();
    }
});

Final CSS:
.clearInput--inside{
    padding-right: 32px !important;
}

.t-Form-inputContainer .a-Button--clearInputInside{
    margin-left: -32px;
}

.t-Form-inputContainer .a-Button--clearInput{
    margin-left: -.1rem;
    order: 3; /* Apex 5.1: Set the correct order */
}
 
.t-Form--large .a-Button.a-Button--clearInput,
.t-Form-fieldContainer--large .a-Button.a-Button--clearInput {
    padding: .8rem 1.2rem
}
 
.t-Form--xlarge .a-Button.a-Button--clearInput,
.t-Form-fieldContainer--xlarge .a-Button.a-Button--clearInput {
    padding: 1.4rem 1.2rem
}
 
.t-Form--login .a-Button.a-Button--clearInput {
    padding: 1rem 1.2rem
}

Here's the end result:


You can have a look at my Demo Application

Tuesday, June 7, 2016

Close Modal Dialog When Clicking Outside

A couple weeks ago someone on apex.world slack asked a question regarding how to capture the cancel dialog event.

Juergen Schuster came up with a solution using the dialog's page attribute "Attributes" (under the Dialog section) to add/define the dialog with a custom close callback when initializing it.


Using the same logic we can use it to define a new open callback that will trigger the close dialog method when clicking outside the dialog.

Let's define the following JavaScript function on the parent page (if you're going to be using this in more than one place, I suggest you define it either on the page 0 or in a global javascript file)
function closeDialogClickOutside(elem){
   $('.ui-widget-overlay').click(function(){
      $(elem).dialog('close');
   });
}

Then we simply have to set call the function when the dialog opens.
Which we can do by setting the Dialog's attribute "Attributes" to this:
open: function( event, ui ) { closeDialogClickOutside(this); }

Here's what you will get:


You can have a look at my Demo Application

Wednesday, May 4, 2016

Button in Region Title


Note: Instead of using the below code, it's easier to simply use the built-in button positions. Have a look at my demo application for a preview of the all the button position.
If you ever needed to display a button in a report header. You probably tried the "Right of Title" button position and got something like this :


The button is not right aligned in the region header, similar to what we get in an interactive report region using "Right of Interactive Report Search Bar" button region. You could always copy the region template and change it so that there is one button position in the header, but then you loose the subscription to the UT and you also have to copy every template you would want this to work.

We can achieve all that by simply using some CSS.

Let's start by defining the required CSS:
/* Make sure the title container is stretched */
.buttonInHeader .t-Region-headerItems.t-Region-headerItems--title h2,
.buttonInHeaderWithExpand .t-Region-headerItems.t-Region-headerItems--title h2{
    display: block;
}

/* Have the buttons float right */
.buttonInHeader .t-Region-headerItems.t-Region-headerItems--title button,
.buttonInHeaderWithExpand .t-Region-headerItems.t-Region-headerItems--title button{
    float: right;
    margin-left: 5px;
}

/* Add extra space for the expand button */
.buttonInHeaderWithExpand .t-Region-headerItems.t-Region-headerItems--buttons{
    width: 40px;
}

I like to add these class in the Theme Roller CSS so that I can then use them from anywhere.

Then we can simply add the class name in the region Class attribute.


And you will get this:

One thing to remember is that when you have multiple buttons, they will need to be reversed in the page designer because of the float.

You can have a look at my Demo Application

Tuesday, April 5, 2016

Copy Theme Roller Theme From One Application to Another

Here is how you can copy a theme roller custom theme from one application to the other.

From the application that has theme your source theme, open theme roller. Then open the developer tools and in the console run the following command
apex.utr.config()
Copy that line to your clipboard.

Then in the destination application, open the theme roller and the developer tools.
Paste it into the console to load the configuration into Theme Roller.

Then you will only need to save this configuration as a new theme.

Friday, April 1, 2016

Region in Left-side Navbar Menu

As of now, there is now way to edit the Navigation Menu template under the page template to let's say add a region above or below it.

It's possible to do it using jQuery though.

The way to do it is to have a region (in any position on the page) and to move it inside the left side navigation menu.
Make sure the region you want move doesn't have any item in it, because it could result in error.
For more info/details you could have a look at Martin D'Souza's blogposts:
http://www.talkapex.com/2015/07/back-to-basics-html-form.html
http://www.talkapex.com/2015/07/apex-and-html-form.html
http://www.talkapex.com/2015/07/apex-and-order-items-are-submitted.html

Let's start by creating a Pie Chart region using the "Blank with Attributes" template.
Under Appearance, I set the CSS Classes attribute to "underNavBar".
Under the region's attributes, I disabled the labels and the markers. I also fixed the width/height so that it would fit in the side menu region.

Now to get the region to be displayed above or below the left side navigation menu, we need to create a Page Load dynamic action that executes the following JavaScript code:
Use this one if you want the region to display above the left side menu
$('.underNavBar').prependTo('#t_TreeNav');
Use this one if you want the region to display below the left side menu
$('.underNavBar').appendTo('#t_TreeNav');
What's left to do is adjust the CSS of the it all.
#t_TreeNav {
    display: flex;
    flex-direction: column;
    height: 100%;
}

#t_TreeNav ul {
    flex: 1 0 auto;
}

div.underNavBar{
    flex: 0 0 auto;
}
You can also hide the region when the menu is collapsed.
#t_PageBody.apex-side-nav.js-navCollapsed .underNavBar {
   display: none;
}
You will then get something like this:


You can have a look at my Demo Application

Wednesday, January 27, 2016

Accordion-Like Navigation Menu

By default, it's only possible to expend the side navigation menu children submenu by clicking on the corresponding down arrow.

Here's how you can do if you would like to be able to do the same thing by simply clicking on the list entry text.

On your page 0, create a new dynamic action on page load that is going to run one the following javascript code depending on the target type you have on your list entry
Use this one if you set your target type to URL and your URL target to #
$('#t_Body_nav #t_TreeNav li.a-TreeView-node--topLevel > div.a-TreeView-content:has(a[href="#"])').click(function(){
   $(this).prev('span.a-TreeView-toggle').click();
});
Use this one if you set your target type to - No Target -
$('#t_Body_nav #t_TreeNav li.a-TreeView-node--topLevel > div.a-TreeView-content:not(:has(a))').click(function(){
   $(this).prev('span.a-TreeView-toggle').click();
});
This is going to look at all the top level list entries and if their link is equal to "#" (or if the entry has no link), it's going to trigger the arrow's click event when you click on the list entry text.
So basically, when you click on the text, it's going to be clicking on the arrow as well.

EDIT: If you want to use this for a multi-level menu, the javascript dynamic action code will need to be changed. After debug it, I found out that the submenu items are generated dynamically when expanding it's parent. So the click event from the above code is not registered for the new li tags. We need to
Use this one if you set your target type to URL and your URL target to #
$('#t_Body_nav #t_TreeNav').on('click', 'ul li.a-TreeView-node div.a-TreeView-content:has(a[href="#"])', function(){
   $(this).prev('span.a-TreeView-toggle').click();
});
Use this one if you set your target type to - No Target -
$('#t_Body_nav #t_TreeNav').on('click', 'ul li.a-TreeView-node div.a-TreeView-content:not(:has(a))', function(){
   $(this).prev('span.a-TreeView-toggle').click();
});

You can have a look at my Demo Application

Tuesday, January 26, 2016

Manually Change SQL Developer Language

If like me your OS is not in English, but you would prefer to have SQL Developer be in English, after looking at all of the preferences options, you will find that there is no option to change SQL Developer's language.

It's still possible to do it manually though.

Here are the steps you need to follow:
  • Close SQL Developer 
  • From the root of SQL Developer folder, go into the sqldeveloper\bin subfolder.
  • Edit the sqldeveloper.conf file
  • Append this at the end
#Force English
AddVMOption  -Duser.language=en

SQL Developer should now be in English.