How to Use the Built-in Avatar and Badge Template Components in Oracle APEX

Show initials, photos and colored status badges from query columns with the Avatar and Badge template components, and handle missing photos and invalid states.

An avatar is a small picture, a set of initials or an icon that stands for a person or an item. A badge is a short label with a value, such as a status or a count, drawn in a color that tells the state at a glance. Together they make lists easier to read.

Oracle APEX ships both as template components of Universal Theme. You do not write HTML or CSS for them. You point them at the columns of a query, and the theme draws them, including colors, sizes and accessible labels.

The earlier guide Oracle APEX Cards, Content Rows, and Template Components lists the whole family of template components. This guide goes deeper into two of them. It uses them inside a Content Row, as regions of their own, and as a single badge.

If you want to build a template component of your own instead, read How to Create Your Own Template Component in Oracle APEX. This guide uses team members as the example, and the same steps work for any table with people, products or tickets.

Step 1: Create the Tables

The example needs two tables. TEAM_STATUSES holds the statuses with the badge state of each one, and TEAM_MEMBERS holds the people:

Example:

create table team_statuses (
    status_code  varchar2(10)  not null,
    status_label varchar2(30)  not null,
    badge_state  varchar2(10)  not null,
    sort_seq     number        not null,
    constraint team_statuses_pk primary key (status_code),
    constraint team_statuses_seq_uk unique (sort_seq),
    constraint team_statuses_state_ck check (badge_state in ('success', 'warning', 'danger', 'info'))
);

create table team_members (
    member_id    number generated by default on null as identity,
    full_name    varchar2(100) not null,
    email        varchar2(150) not null,
    job_title    varchar2(100) not null,
    status_code  varchar2(10)  default on null 'OFFLINE' not null,
    open_tasks   number        default on null 0 not null,
    photo_file   varchar2(200),
    initials     varchar2(10) generated always as (
                     upper(substr(full_name, 1, 1) || substr(full_name, instr(full_name, ' ') + 1, 1))
                 ) virtual,
    constraint team_members_pk primary key (member_id),
    constraint team_members_email_uk unique (email),
    constraint team_members_status_fk foreign key (status_code)
        references team_statuses (status_code),
    constraint team_members_tasks_ck check (open_tasks >= 0)
);

create index team_members_status_ix on team_members (status_code);

insert into team_statuses (status_code, status_label, badge_state, sort_seq) values ('ACTIVE', 'Active', 'success', 10);
insert into team_statuses (status_code, status_label, badge_state, sort_seq) values ('AWAY', 'Away', 'warning', 20);
insert into team_statuses (status_code, status_label, badge_state, sort_seq) values ('ON_LEAVE', 'On Leave', 'info', 30);
insert into team_statuses (status_code, status_label, badge_state, sort_seq) values ('OFFLINE', 'Offline', 'danger', 40);

insert into team_members (full_name, email, job_title, status_code, open_tasks, photo_file)
values ('Priya Sharma', 'priya.sharma@example.com', 'Project Manager', 'ACTIVE', 12, 'photos/priya.png');
insert into team_members (full_name, email, job_title, status_code, open_tasks, photo_file)
values ('Marcus Weber', 'marcus.weber@example.com', 'Senior Developer', 'ACTIVE', 7, null);
insert into team_members (full_name, email, job_title, status_code, open_tasks, photo_file)
values ('Aiko Tanaka', 'aiko.tanaka@example.com', 'UX Designer', 'AWAY', 4, 'photos/aiko.png');
insert into team_members (full_name, email, job_title, status_code, open_tasks, photo_file)
values ('Liam O''Connor', 'liam.oconnor@example.com', 'QA Engineer', 'OFFLINE', 0, null);
insert into team_members (full_name, email, job_title, status_code, open_tasks, photo_file)
values ('Sofia Rossi', 'sofia.rossi@example.com', 'Business Analyst', 'ON_LEAVE', 3, null);
insert into team_members (full_name, email, job_title, status_code, open_tasks, photo_file)
values ('Daniel Okafor', 'daniel.okafor@example.com', 'Database Administrator', 'ACTIVE', 9, null);

commit;

select m.member_id, m.full_name, m.initials, m.job_title, s.status_label, s.badge_state, m.open_tasks, m.photo_file
from   team_members m
join   team_statuses s on s.status_code = m.status_code
order  by m.member_id;

Output:

Table TEAM_STATUSES created.

Table TEAM_MEMBERS created.

Index TEAM_MEMBERS_STATUS_IX created.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

1 row inserted.

Commit complete.

   MEMBER_ID FULL_NAME        INITIALS    JOB_TITLE                 STATUS_LABEL    BADGE_STATE       OPEN_TASKS PHOTO_FILE
____________ ________________ ___________ _________________________ _______________ ______________ _____________ ___________________
           1 Priya Sharma     PS          Project Manager           Active          success                   12 photos/priya.png
           2 Marcus Weber     MW          Senior Developer          Active          success                    7
           3 Aiko Tanaka      AT          UX Designer               Away            warning                    4 photos/aiko.png
           4 Liam O'Connor    LO          QA Engineer               Offline         danger                     0
           5 Sofia Rossi      SR          Business Analyst          On Leave        info                       3
           6 Daniel Okafor    DO          Database Administrator    Active          success                    9

6 rows selected.

A few details in the tables:

  • BADGE_STATE stores the color name of the badge. The check constraint TEAM_STATUSES_STATE_CK allows only success, warning, danger and info, so a mistyped state can never reach the page.
  • INITIALS is a virtual column that takes the first letter of the first name and the first letter after the first space. The query does not need to calculate it.
  • PHOTO_FILE holds the path of a photo in the static files of the application. Only two members have a photo.
  • STATUS_CODE and OPEN_TASKS use DEFAULT ON NULL, so a new member starts offline with no open tasks.

Step 2: Upload the Photos as Static Application Files

The photo avatars read their pictures from the static files of the application. This example uses two small sample photos and one default picture for members without a photo. The files are priya.png, aiko.png and default.png.

In App Builder, open the application and click Shared Components. Under Files and Reports, click Static Application Files:

Static Application Files page with the Create File button marked with a red box
Create a static application file.

Click Create File. Enter photos in the Directory field and drop the image file on the Content area. The File Name fills in from the file. Click Create, and repeat for each of the three files:

Create Application Static File page with the Directory photos and the dropped file priya.png marked with red boxes
The directory and the uploaded file.

The list now shows the three files. The Reference column shows how to address them, for example #APP_FILES#photos/priya.png:

Static Application Files list with photos/aiko.png, photos/default.png and photos/priya.png marked with a red box
The three photo files in the application.

Step 3: Create the Team Directory Page

In App Builder, open the application and click Create Page. On the Component tab, select Blank Page:

Create a Page wizard with the Blank Page tile marked with a red box
Choose the Blank Page page type.

On the next screen, enter Page Number 1150 and Name Team Directory. Turn off Use Breadcrumb, keep Use Navigation on and enter the Icon fa-users. Then click Create Page:

Create Blank Page wizard with Page Number 1150, Name Team Directory, Use Breadcrumb off, Use Navigation on and Icon fa-users marked with red boxes
The page number, name and navigation entry.

Step 4: Show Avatars and Badges in a Content Row

A Content Row region shows one row per record and has avatar and badge slots built in. It is the quickest way to use both components together.

In Page Designer, drag a Content Row region into the Body. Set Name and Title to Team Members, set Source Type to SQL Query, and enter this query:

Example:

select m.member_id,
       m.full_name,
       m.job_title,
       m.email,
       m.initials,
       m.open_tasks,
       s.status_label,
       s.badge_state
from   team_members m
join   team_statuses s on s.status_code = m.status_code

Output:

   MEMBER_ID FULL_NAME        JOB_TITLE                 EMAIL                        INITIALS       OPEN_TASKS STATUS_LABEL    BADGE_STATE
____________ ________________ _________________________ ____________________________ ___________ _____________ _______________ ______________
           1 Priya Sharma     Project Manager           priya.sharma@example.com     PS                     12 Active          success
           2 Marcus Weber     Senior Developer          marcus.weber@example.com     MW                      7 Active          success
           3 Aiko Tanaka      UX Designer               aiko.tanaka@example.com      AT                      4 Away            warning
           4 Liam O'Connor    QA Engineer               liam.oconnor@example.com     LO                      0 Offline         danger
           5 Sofia Rossi      Business Analyst          sofia.rossi@example.com      SR                      3 On Leave        info
           6 Daniel Okafor    Database Administrator    daniel.okafor@example.com    DO                      9 Active          success

6 rows selected.
Page Designer with the Team Members region, Type Content Row, and the SQL query marked with red boxes
A Content Row region on the team query.

In a first test, the query ended with ORDER BY member_id. Page Designer marked the SQL Query property with an error and would not save the region. Remove the ORDER BY from the query and use the Order By group of the region instead. Set Type to Static Value and Order By Clause to member_id:

Order By group with Type Static Value and Order By Clause member_id marked with a red box
The sort order is set in the Order By group.

Click the Attributes tab. In the Settings group, enter these values. Turn on Display Avatar and Display Badge, which adds the Avatar and Badge groups:

  • Overline: &JOB_TITLE.
  • Title: &FULL_NAME.
  • Description: &EMAIL.
Settings group with Overline, Title, Description, Display Avatar and Display Badge marked with a red box
The text slots and the two switches.

In the Avatar group, set Type to Initials and Initials to the INITIALS column. Enter &FULL_NAME. as Description, so screen readers announce the name. Set Shape to Circular:

Avatar group with Type Initials, Initials INITIALS, Description and Shape Circular marked with a red box
The avatar shows the initials of each member.

In the Badge group, enter Status as Label, select STATUS_LABEL as Value and BADGE_STATE as State. The Label is required even though the badge does not show it here, because Display Label is off:

Badge group with Label Status, Value STATUS_LABEL and State BADGE_STATE marked with a red box
The badge shows the status in the color of its state.

Save the page and run it. Every member has an avatar with initials and a badge in the color of the status:

Content Row region with six team members, each with a status badge and a circular avatar with initials
The Content Row with avatars and badges.

The avatar colors differ from row to row. The theme picks them, and you do not set them. The state colors follow the BADGE_STATE column: green for success, orange for warning, red for danger and blue for info.

Step 5: Show Photos with an Avatar Region

The Avatar template component is also a region type of its own. It draws one avatar per row and is useful for a row of team photos, or the people on a task.

Drag an Avatar region into the Body. Set Name and Title to Photo Avatars, set Source Type to SQL Query, and enter this query:

Example:

select member_id,
       full_name,
       '#APP_FILES#' || coalesce(photo_file, 'photos/default.png') as photo_url
from   team_members

Output:

   MEMBER_ID FULL_NAME        PHOTO_URL
____________ ________________ ________________________________
           1 Priya Sharma     #APP_FILES#photos/priya.png
           2 Marcus Weber     #APP_FILES#photos/default.png
           3 Aiko Tanaka      #APP_FILES#photos/aiko.png
           4 Liam O'Connor    #APP_FILES#photos/default.png
           5 Sofia Rossi      #APP_FILES#photos/default.png
           6 Daniel Okafor    #APP_FILES#photos/default.png

6 rows selected.

The output shows the text #APP_FILES# because the substitution runs when the page renders, not in SQL. Members without a photo get photos/default.png. The reason is explained in the test section below. Set the Order By group to Static Value with member_id:

Page Designer with the Photo Avatars region, Type Avatar, and the SQL query marked with red boxes
An Avatar region on the photo query.

On the Attributes tab, set Type to Image. Click the Image property to open the Media dialog:

Settings group with Type Image, Image, Description, Shape Circular and Size Large marked with a red box
The avatar settings.

In the Media dialog, set Source to URL Column and select PHOTO_URL as the URL Column. The other sources are a fixed URL and a BLOB column. Click OK:

Media dialog with Source URL Column and URL Column PHOTO_URL marked with red boxes
The image comes from a URL column.

Enter &FULL_NAME. as Description, set Shape to Circular and Size to Large. Save the page. Each member now has a round photo:

Photo Avatars region with two photos and four default silhouettes marked with a red box
The photo avatars, with the default picture for members without a photo.

Step 6: Show Counts with a Badge Region

The Badge template component is a region type too. It draws one badge per row, which suits a summary of counts. Drag a Badge region into the Body, set Name and Title to Status Summary and enter this query:

Example:

select s.status_code,
       s.status_label,
       s.badge_state,
       s.sort_seq,
       count(m.member_id) as member_count
from   team_statuses s
left   join team_members m on m.status_code = s.status_code
group  by s.status_code, s.status_label, s.badge_state, s.sort_seq

Output:

STATUS_CODE    STATUS_LABEL    BADGE_STATE       SORT_SEQ    MEMBER_COUNT
______________ _______________ ______________ ___________ _______________
ACTIVE         Active          success                 10               3
AWAY           Away            warning                 20               1
OFFLINE        Offline         danger                  40               1
ON_LEAVE       On Leave        info                    30               1

The left join keeps a status in the summary even when no member has it. Set the Order By group to Static Value with sort_seq:

Page Designer with the Status Summary region, Type Badge, and the SQL query marked with red boxes
A Badge region on the summary query.

On the Attributes tab, enter &STATUS_LABEL. as Label, select MEMBER_COUNT as Value and BADGE_STATE as State. Turn on Display Label and set Size to Large:

Settings group with Label, Value MEMBER_COUNT, State BADGE_STATE, Display Label on and Size Large marked with a red box
Each badge shows a label and a count.

Run the page. The summary shows one badge per status, with the same colors as in the Content Row:

Status Summary region with the badges Active 3, Away 1, On Leave 1 and Offline 1 marked with a red box
One badge per status, with its count.

Step 7: Show One Badge with the Single Display

A template component region has a Display setting. Multiple (Report) draws one component per row. Single (Partial) draws one component from one row, which is right for a single figure such as a total.

Drag another Badge region into the Body, above the others. Set Name to Open Tasks and enter this query:

Example:

select sum(open_tasks) as total_tasks,
       'warning'       as task_state
from   team_members

Output:

   TOTAL_TASKS TASK_STATE
______________ _____________
            35 warning

The state comes from a column, so the query returns the word warning as TASK_STATE. On the Attributes tab, set Display to Single (Partial):

Appearance group with Display Single (Partial) marked with a red box
The Single display for one badge.

Enter Open Tasks as Label, select TOTAL_TASKS as Value and TASK_STATE as State. Enter fa-tasks as Icon, turn on Display Label and set Size to Large:

Settings group with Label, Value TOTAL_TASKS, State TASK_STATE, Icon fa-tasks, Display Label and Size marked with a red box
The label, value, state and icon of the single badge.

The badge shows the total of all open tasks:

A single orange badge with the icon and the text Open Tasks 35 marked with a red box
One badge from one row.

Test the Page

Run the whole page. It shows the Open Tasks badge, the Status Summary, the Team Members rows and the Photo Avatars:

Team Directory page with the Open Tasks badge, the Status Summary, the Content Row and the Photo Avatars
The finished page.

The avatars and badges carry text for screen readers. In the test, the badge of Marcus Weber had the label "Status Active", and the avatar of Priya Sharma had the label "Priya Sharma". The first comes from the Label and Value of the badge, and the second from the Description of the avatar.

Members without a photo

The Image type of the avatar has no fallback. In a first test, the query used '#APP_FILES#' || photo_file without the default. The four members without a photo got an image with an empty file name, and the browser showed broken images:

Photo Avatars region with two photos and four missing images marked with a red box
Without a default picture, four avatars are broken images.

The fix is in the query. COALESCE(photo_file, 'photos/default.png') gives every member a picture. If you want initials instead of a silhouette, use the Initials type, as in the Content Row, for everyone. One avatar region cannot show a photo for some rows and initials for others.

Change the data

The page reads the tables every time it renders, so a change shows at once. This test sets Marcus Weber to Away and adds five open tasks to Liam O'Connor:

Example:

update team_members set status_code = 'AWAY' where member_id = 2;
update team_members set open_tasks = open_tasks + 5 where member_id = 4;
commit;

select m.full_name, s.status_label, m.open_tasks
from   team_members m
join   team_statuses s on s.status_code = m.status_code
where  m.member_id in (2, 4)
order  by m.member_id;

select s.status_label, count(m.member_id) as member_count
from   team_statuses s
left   join team_members m on m.status_code = s.status_code
group  by s.status_label, s.sort_seq
order  by s.sort_seq;

select sum(open_tasks) as total_tasks from team_members;

Output:

1 row updated.

1 row updated.

Commit complete.

FULL_NAME        STATUS_LABEL       OPEN_TASKS
________________ _______________ _____________
Marcus Weber     Away                        7
Liam O'Connor    Offline                     5

STATUS_LABEL       MEMBER_COUNT
_______________ _______________
Active                        2
Away                          2
On Leave                      1
Offline                       1

   TOTAL_TASKS
______________
            40

After a page refresh, the summary shows Active 2 and Away 2, the Open Tasks badge shows 40, and Marcus Weber has an orange Away badge:

Open Tasks 40 badge and the Status Summary with Active 2, Away 2, On Leave 1 and Offline 1 marked with red boxes
The badges follow the data.

This script puts the data back:

Example:

update team_members set status_code = 'ACTIVE' where member_id = 2;
update team_members set open_tasks = 0 where member_id = 4;
commit;

select m.full_name, s.status_label, m.open_tasks
from   team_members m
join   team_statuses s on s.status_code = m.status_code
where  m.member_id in (2, 4)
order  by m.member_id;

Output:

1 row updated.

1 row updated.

Commit complete.

FULL_NAME        STATUS_LABEL       OPEN_TASKS
________________ _______________ _____________
Marcus Weber     Active                      7
Liam O'Connor    Offline                     0

Invalid states and statuses

The state of a badge comes from the data, so the data must stay valid. Both tests below fail because of the constraints from Step 1:

Example:

update team_statuses set badge_state = 'purple' where status_code = 'AWAY';

insert into team_members (full_name, email, job_title, status_code)
values ('Test Person', 'test.person@example.com', 'Tester', 'HOLIDAY');

Output:

Error starting at line : 1 File @ b118/12-negative.sql
In command -
update team_statuses set badge_state = 'purple' where status_code = 'AWAY'
Error report -
ORA-02290: check constraint (APXSOL.TEAM_STATUSES_STATE_CK) violated
Help: https://docs.oracle.com/error-help/db/ora-02290/

Error starting at line : 3 File @ b118/12-negative.sql
In command -
insert into team_members (full_name, email, job_title, status_code)
values ('Test Person', 'test.person@example.com', 'Tester', 'HOLIDAY')
Error report -
ORA-02291: integrity constraint (APXSOL.TEAM_MEMBERS_STATUS_FK) violated -
parent key not found
Help: https://docs.oracle.com/error-help/db/ora-02291/

A state outside the four colors cannot be saved, and a member cannot get a status that does not exist.

Things to Know

  • Both components take a color from a column named in State. The values used here are success, warning, danger and info. The theme turns the value into a CSS class such as t-Badge--success.
  • Keep the state values in a lookup table with a check constraint, as in TEAM_STATUSES. A CASE expression in every query works, but then each page repeats the mapping.
  • In the Content Row, the Badge Label must have a value even when Display Label is off.
  • Do not put ORDER BY in the query of these regions. Use the Order By group, or the region shows an error.
  • The Image type needs a real URL in every row, because there is no fallback. Return a default picture from the query for rows without a photo.
  • Always fill the Description of an avatar. It becomes the label that screen readers read, and an avatar without it is skipped as decoration.

Related Guides

Conclusion

The Avatar and Badge template components turn columns into avatars with initials, icons or photos, and into colored badges with a label and a value. A Content Row uses both together, and the Avatar and Badge regions use each one alone, as one component per row or as a single figure.

The tests showed that the colors follow the data, that a missing photo needs a default picture in the query, and that a lookup table with a check constraint keeps the badge states valid. Keep the logic in the data and let the theme draw the components.

Get more Vinish.dev tutorials on Google

See my articles more often when you search Google.

Vinish Kapoor
Vinish Kapoor

An Oracle ACE, author of four books on Oracle APEX, SQL and PL/SQL, and Oracle Forms, and a software developer building Oracle database applications since 2001.

guest

0 Comments
Oldest
Newest Most Voted