21.2: Fine-Tuning Initial Render of Tabs & Cards

If you use a Static Content region with the Tabs Container template to present subregions on separate tabs, in APEX 21.2 you might notice during initial page render that you momentarily see the contents of all the tab “pages” before they disappear behind the initially selected tab. Depending on the contents of the tab pages, the result may be more or less noticeable. However, since one of my applications presents multiple tabs of colorful Badge Lists, the “Easter eggs” below compelled me to search for a solution…

Three Badge List regions situated in a Tabs Container all render momentarily during page load

Luckily, my colleagues Tim and John helped me with a combination of simple CSS suggestions that solved the problem. After adding two rules to an application-level CSS file, the undesirable effect vanished.

While the two CSS rules that avoid the flashing tabs could be applied to each page where needed, I prefer a “one-and-done” solution. An application-level CSS file is best for rules that apply to all pages, so I added the two rules in there. Under Shared Components > Static Application Files I created an app.css file with the contents shown below:

App-level app.css file with two rules to suppress flashing of all tab containers’ contents

Next, I clicked on the copy icon next to the #APP_FILES#app#MIN#.css name under Reference to copy that file path to the clipboard so I could paste it into the list of CSS files that my application will load with every page. That configuration is also under the Shared Components settings, on the User Interface Attributes page, in the Cascading Style Sheets section. I pasted the file reference on its own line in the text area as shown below:

List of App-level CSS file URLs that APEX will load with every page

With this app-level CSS file in place, my landing page was looking sharp.

Landing page with Tabs Container of three Badge Lists and a Chart region

Energized that my app’s tab pages were looking great now, I turned my focus next to my application’s Cards regions. I noticed that APEX shows a row of placeholder cards when the page initially draws to help the end-user understand an asynchronous data request is in progress to retrieve the actual card information.

APEX Card regions initially show placeholder cards, then actual cards appear once data loads

This employs a familiar technique that other modern web applications like LinkedIn, Facebook, and YouTube use.

YouTube shows placeholder cards initially, then actual cards appear once data loads

In situations where the card data takes time to retrieve, placeholders are a useful affordance for the end-user. In my particular app, it seemed to add less value since my cards render almost instantly.

I used Chrome Dev tools to explore the structure of the page to see if a similar CSS technique might be able to hide the placeholder cards. After none of my experiments panned out, again my colleague Tim nudged me back onto the happy path with a suggestion. I edited my app.css file to add the one additional rule you see below that delivered the results I was hoping for.

/* Contents of app.css */
/*
 * Suppress tab container contents from flashing
 * during initial page render
 */
.a-Tabs-panel {
	display: none;
}

.no-anim .t-TabsRegion-items > div {
    display: none;
}

/*
 * Suppress cards region from rendering initial row
 * of placeholder cards during initial page render
 */
.no-anim .a-CardView-item > div {
    display: none;
}

These CSS rules use a combination of class selectors ( .someClassName) and the child element selector ( > someElement). Effectively they say:

  • Hide elements with the a-Tabs-panel class
  • Hide div child of parent with classt-TabsRegion-items inside element with class no-anim
  • Hide div child of parent with class a-CardView-item inside element with class no-anim

Keep in mind that these rules only affect the initial display state of the affected elements since APEX automatically makes the relevant elements visible again once they are ready for the end-user to see.

With these three rules in place, the user sees only the selected tab’s Badge List as expected and my quick-loading cards without placeholders. In case you want to try the example application, download it from here. Try commenting out the CSS rules in app.css in the example to see the difference with and without so you can decide what’s best for your own application use cases.

Card region showing Paul Theroux books

Nota Bene

I’ve tested these techniques in APEX 21.2 but need to remind you that the particular structure of how APEX universal theme generates HTML elements in this release is not guaranteed to remain the same across future APEX releases.

Adding Tags Using Multi-Value PopupLOVs and Smart Filters

Overview

Google’s Gmail was the first app I remember that popularized users’ inventing their own descriptive labels and applying them as “tags” to data. Instead of moving an email from the “Inbox” folder into one named “Charitable Donations 2019 USA”, for example, it encouraged me to tag the message with multiple, distinct labels like “2019”, “taxes”, “charitable donations”, and “USA”. While not initially as intuitive as folders, this approach quickly proved its value. It let me quickly locate mails related to a particular year, to charitable donations, to taxes in general, or to the US, or any combination of those criteria.

For the same reasons, a flexible tagging facility comes in handy for many kinds of data we work with everyday. Whether it’s pictures in a photo library, products in an online store, or books in your collection, tags that help users quickly find what they are looking for are a boon. When combined with Oracle APEX’s powerful faceted search and Smart Filters capabilities, it packs a powerful productivity punch.

In this article, I explain the technique I used over the holidays to add a flexible tagging facility to my art tracker application using a no-code approach that takes advantage of APEX’s multiple value support in popup LOV page items and Smart Filters. Here, we’ll apply the approach to a simple application that tracks books, publishers and authors. You can find the link to download the example application at the end of the article.

Book Finder page featuring Cards region showing title, authors, publisher, and tags

Multi-Value PopupLOV for Tags

The BOOK_TAGS table defines an ID and NAME for the descriptive tags you can apply to books. The sample data includes pre-defined tags like “Trains” (2), “Travel Narrative” (1), “Children” (5), “Fiction” (6), and others.

The BOOK_TITLES table contains a book’s TITLE, the PUBLISHER_ID, and a TAGS column, whose value is a colon-separated list of tag ids. For example, a book that is a fictional travel narrative about riding on trains might have the value “6:1:2” representing the id values of the three tags “Fiction”, “Travel Narrative”, and “Trains”. Since the order of the the tags is not significant to our use case, it also might have the value “1:2:6” depending on the order in which the end-user added the tags to the list.

The figure below shows how I configured the P3_TAGS page item in page 3’s form region to support visualizing and editing the possibly-multiple tags applied to a book. Notice that the page item type is Popup LOV, the Multiple Values property is enabled, the literal colon character (:) is indicated as the value Separator, and the Search as You Type property is on. I’ve configured a shared component TAGS_LOV List of Values to provide the alphabetized list of available tags.

Configuring multi-value popup LOV page item in an edit form page

Pay special attention to the Manual Entry setting above. We’ve consciously disabled that property since by design at the moment APEX shows the end user the underlying values (e.g. 6 , 1 , 2 ) for a manual-entry popup LOV instead of showing their corresponding display text values (e.g. Fiction , Travel Narrative, Trains ). That may be appropriate for some multi-value use cases where the LOV entry’s display and return values are the same, but here I preferred that my end-users would see the tag display values.

The result is the easy-to-use book editing page shown below. Notice that the P3_AUTHOR_IDS page item is configured in the same way as P3_TAGS to allow entering the colon-separated list of one or more author ids for the book.

Multi-value popup LOV page items editing book details at runtime

Multi-Value Smart Filter for Tags

APEX makes it simple to easily filter on one or more tags applied to records in both faceted search as well as the new Smart Filters region. The figure below shows how the P5_TAGS smart filters search facet in the Book Finder cards region page (5) is configured to enable this behavior. Notice the search facet Type is set to Checkbox Group, the Multiple ValuesType is set to Delimited List with the Separator configured to be a literal colon character (:), and we’ve enabled the Trim Whitespace option.

Configuring multi-value P5_TAGS smart filter facet to handle colon-delimited values

This is the only configuration necessary to get the tags field working for searching. The result produces a smart filter search field (above our cards region) on page 5 in the example app that looks like the figure below. Ticking one or more tags in the list narrows the search results to show only books having that/those tags applied (in any order).

Checkbox Group smart filters facet to search for books by one or more tags applied.

As above, the P5_AUTHOR_IDS search facet has been configured identically to the P5_TAGS one to allow narrowing down the search results by any combination of authors and/or tags as shown below where we’ve found books authored by Brian Spendolini about APEX.

Applying two multi-value smart filters: one for authors, one for tags

Displaying Multi-Value Fields in Report Regions

When working with multi-value columns like TAGS and AUTHOR_IDS in our BOOK_TITLES example table, it’s useful in report pages or card regions to show the list of display values corresponding to the one-or-more ids stored in the colon-separated column value. For this task, I employed the handy LISTAGG() function to aggregate the set of related tag display values and author names into an ordered, comma-separated list. I combined it with the useful split_numbers() function in the apex_string utility package. When wrapped by a table() operator, this helpful routine lets us select the numbers in the colon-separated list as a table row source right in the query. To make it easier to use this information from any report or card regions where I needed it in my application, I created the BOOK_TITLES_V database view with the following SELECT statement.

select 
    b.id,
    b.title,
    b.tags,
    b.rating,
    b.publisher_id,
    b.author_ids,
    (select listagg(name,', ')
            within group (order by name collate binary_ai)
       from book_tags 
      where id in (
        select column_value 
          from table(apex_string.split_numbers(b.tags,':'))
        )
    ) display_tags,
    (select listagg(name,', ')
            within group (order by name collate binary_ai)
      from book_authors 
     where id in (
       select column_value 
         from table(apex_string.split_numbers(b.author_ids,':')))
    ) display_authors,
    p.name as publisher
from book_titles b
left outer join book_publisher p 
             on p.id = b.publisher_id

Notice that I’m passing the literal colon character as the separator to the apex_string.split_numbers() function in two places, and passing the separator string consisting of a comma followed by a space to the listagg() function. The collate binary_ai keywords in the order by part of the listagg function’s within group clause ensures that display values sort in an accent-insensitive (and case-insensitive) way.

Enabling Custom PopupLOV Behavior via CSS Class

I had achieved my goal without writing any code and was happy with how simple it was to implement… when I noticed an interesting extra-credit opportunity that piqued my interest.

While interacting with the PopupLOV component, I observed that its Search as You Type filter remained “sticky” across multiple interactions with the dropdown list of choices. For example, consider the screenshot below where I was editing the tags for a Bill Bryson book. If I typed tr into the PopupLOV search field, as expected the list narrowed down to only show relevant tags “Trains” and “Travel Narrative”. However after choosing “Trains”, if I clicked again into the multi-value field to drop-down the list to choose another tag to apply, the list remained filtered as before to only those tags containing tr in their names. However, often the next tag I wanted to apply required me to clear the previous search field text to start fresh with the complete list of tags to choose from or search through.

Search as You Type filter in use in a PopupLOV for tags applied to a book.

After not finding any declarative PopupLOV setting to control the “stickiness” of the search field value, I began by experimenting with various ideas using dynamic actions to force the PopupLOV search field to clear. Not satisfied with the results, I reached out to my colleague John for some expert advice.

He suggested I implement generic JavaScript code in an app.js static application file that would automatically enable a custom behavior whenever a PopupLOV page item had a particular custom CSS class applied to it. This way, the code was reusable and enabled declaratively by simply adding a CSS class name like popup-lov-reset when the non-default search-field-resetting behavior was desired. The figure below shows the modal Edit Book page (12) called by the full card action on the card region of the Book Finder page (5). Notice the custom CSS class name popup-lov-reset in the AdvancedCSS Classes section. This is the signal to our generic code in app.js that this particular PopupLOV prefers the reset-search-field behavior each time the user engages the page item’s dropdown list.

Opting-in to custom application behavior by adding a custom CSS class to a page item

If you’re curious, you can study the full details of the custom JavaScript code John helped me with in the downloaded example app. However, most important were the higher-level principles he taught me along the way about how he recommends structuring application-level JavaScript code. Since I’m not a JavaScript expert, these were the even more interesting bits of precious knowledge that I felt fortunate to learn from him.

The high-level structure of the code in the example application’s app.js file appears below. It exposes a single app namespace inside of which can appear private functions specific to its implementation. This app namespace exports only the members it wants to be the public API. In this application, only the single function handlePopupLOVsWithSearchResetClass() is exported.

// Export just a single global symbol "app" to keep code clean
const app = (function($) {

    // Function private to the impl of the
    // exported 'app' namespace

    function makeResetPopupLov(itemName) {
      // Code removed for clarity here registers an event
      // handler on body of the page to react to the popupopen
      // event of PopupLOV page item in question.
      // See example app for full details.
    }

    // app namespace members
    return {
        //----------------------------------------------------
        // Turn any PopupLOV page items on page into ones that
        // reset their search field when dropdown pops open. 
        //----------------------------------------------------
        handlePopupLOVsWithSearchResetClass: function() {
            $(".popup-lov-reset.apex-item-popup-lov").each(
                function() {
                  makeResetPopupLov(this.id);
                }
             );
        }
    };
    // Ensure $ in app namespace resolves to correct jQuery
})(apex.jQuery);

If we include the “document ready” event handler code below inside the app.js file, then all pages in the application magically inherit the ability to have any PopupLOV page item on the page opt-in to reset-search-field behavior just by adding the CSS class name popup-lov-reset in the page item’s AdvancedCSS Classes section. In contrast, if you only want the functionality to be available on selected pages, then include this event handler code just on the specific pages where you want the behavior to be available.

// Inside app.js, runs for every page after document is ready
// Alternatively, you can just add to the pages where you want
// the behavior to be available.
//
// "Document Ready" event handler code
$(function() {
    app.handlePopupLOVsWithSearchResetClass();
});

If you want to try out the example for yourself, then download the APEX 21.2 example application here.

Interactive, User-Configurable Card Width #JoelKallmanDay

Create a cards region with interactive card width selector, saving user’s preference across logins.

We miss you, Joel.

Everyone in the Oracle APEX community

Oracle APEX card regions let your users browse and act on a grid of tiles, each representing a row of data. The card region directly taps into your end user’s intuition of browsing their mobile phone’s photo library, especially when the cards feature an image, so it’s a compelling way to present data to users.

The card region’s grid style resizes automatically to the screen space available, but by default end users can’t influence the size of each tile in the grid. Read on to learn how to let your users adjust the card width interactively and remember their choice as a preference across logins. At the end, you’ll find a step-by-step video tutorial and downloadable sample application, but we’ll explore the key ideas behind the technique first.

Overview of Strategy

To implement the feature, you’ll add the following to your page with the card region:

  • A select-list page item showing list of available sizes (e.g. Small, Medium, Large)
    • Having corresponding values of the pixel widths 180px, 220px, 300px
    • Defaulted to the static value for the Medium size (220px)
    • Configured with Maintain Session State setting of Per User (Disk).
  • A dynamic action “trigger” for the select list’s Change event with actions:
    1. Execute JavaScript to update the CSS variable controlling the card size
    2. Execute Server-side Code to save the updated value to the APEX session state

What’s a CSS Variable?

A CSS variable is a custom property whose name is prefixed by a double-hyphen (e.g. --preferred-button-width). It can be associated with any element in a page, either explicitly or implicitly by being associated with a class applied to that element. Any CSS expression can reference the value of a variable by using the syntax var(--variable-name) . The usage can also provide a default value to use in case the variable reference has no value of its own by including a second argument like var(--variable-name, defaultValue) . So, a CSS class named myButton could set the width property to the value of the --preferred-button-width variable (providing a default of 80 pixels) like this:

.myButton {
  width : var(--preferred-button-width,80px);
}

If the same variable exists on multiple elements in the page, the value of the most specific occurrence is used. To provide a global default value for a variable, you can set a value for it on the special :root pseudo-class. If no more specific element in the page provides a value, then the one from the root is used.

As we’ll see below, the Universal Theme style class that defines the card region’s grid layout uses a CSS variable to control the size of the cards in the grid. So setting the right variable to a user-chosen value on the appropriate scope for your needs is the crux of the solution. So let’s explore which variable to set and consider on what context makes sense to set it.

Which Variable Do We Need to Set?

While a page containing a grid region is displayed, using the Chrome developer tools to inspect one of the cards (and clicking to enable the CSS-grid related style properties) we can observe that the card items grid layout is setup by this CSS class:

.a-CardView-items--grid {
    grid-template-columns: 
         repeat(auto-fill,minmax(var(--a-cv-item-width,320px),1fr));
}

At first glance, it’s admittedly cryptic, but let’s unpack what it says. This style property defines the grid-template-columns layout to be a repeating set of columns that auto-fill the horizontal space available with uniform-sized grid cells. The browser computes the width of each grid cell automatically so it falls in the range between the values passed to the minmax() function. The first argument, that is the minimum width value, is given by the value of the CSS variable named --a-cv-item-width (or a default to 320 pixels if the variable is not defined). The second argument providing the maximum card width is one fractional unit (1fr), which represents the width of one column in the layout taking into account a spacing between grid cells, too. In short, if we assign the user-preferred card width value to the CSS variable --a-cv-item-width then the grid will instantly react to layout the grid with cards having that width (or slightly bigger to make each grid cell uniformly sized).

On What Context Do We Set the CSS Variable?

We have at least two sensible choices for the context on which to set the card size CSS variable:

  1. On the card region itself, after assigning it a static id in the App Builder, or
  2. On the “global” root context

Choice 1 imposes the user-preferred card width on just the region on which it’s assigned, whereas choice 2 sets the user-preferred card width so that all card regions in the application will abide by it.

The code examples that follow assume you’ve created a page item named P3_CARD_SIZE on the page with the card region, and that the values for the P3_CARD_SIZE select list page item are one of 180px for Small, 220px for Medium, and 300px for Large.

JavaScript to Set the CSS Variable on a Region

After configuring a static id on your card region (e.g. BooksCardRegion), your dynamic action on the P3_CARD_SIZE page item’s Change event can use the following line of JavaScript to change the CSS variable --a-cv-item-width to the new value of the P3_CARD_SIZE page item:

$('#BooksCardRegion').css('--a-cv-item-width',$v('P3_CARD_SIZE'));
JavaScript to Set the CSS Variable Globally

Your dynamic action on the P3_CARD_SIZE page item’s Change event can use the following line of JavaScript to change the CSS variable --a-cv-item-width on the global “root” context to the new value of the P3_CARD_SIZE page item:

$(':root').css('--a-cv-item-width',$v('P3_CARD_SIZE'));

Pushing the Interactive Card Size Change Immediately to the Server

The change to the CSS variable is made in the browser when the dynamic action reacts to the user’s change of the select list page item, and immediately takes visual effect for the end user in their browser. However, to immediately force the value change to be saved in the APEX session, add an additional dynamic action step to Execute server-side Code and which specifies the P3_CARD_SIZE as one of the page items to send to the server. Since it doesn’t need to perform any other server-side logic beyond pushing the values, you can simply use the “do nothing” PL/SQL instruction NULL; to enter into the required PL/SQL script property. If the page item’s Maintain Session State setting is configured to “Per Session (Disk)” then the user’s preference will persist for the duration of the session. If instead it’s set to “Per User (Disk)“, the setting will survive across subsequent user logins.

Step by Step Tutorial Video

NOTE: The video illustrates setting the CSS variable on the :root context, while the downloadable example app illustrates setting the variable on the card region. See above for a consideration on which is appropriate for your use case .

Sample Application

You can download an Oracle APEX 21.1 example application (containing a supporting objects installation script for two sample tables PNL_THEROUX_BOOKS and PNL_PUBLISHERS) from here.