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.

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.

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 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 Values ⟶ Type is set to Delimited List with the Separator configured to be a literal colon character (:
), and we’ve enabled the Trim Whitespace option.

P5_TAGS
smart filter facet to handle colon-delimited valuesThis 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).

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.

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.

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 Advanced ⟶ CSS 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.

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 Advanced ⟶ CSS 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.