Reusable, and Translatable: Oracle APEX Text Messages
Text messages are a feature that I should utilize more in Oracle APEX. They are easy to understand and implement, but I have to remind myself that they exist, so I am writing this blog post.
They are basically translatable text fragments that you can reference almost anywhere. My rule of thumb is that if you need a specific text in multiple languages or places, you should create a text message.
Here are some scenarios where I use them:
- Help text (e.g., for a page item)
- Error messages
- Success messages
- Confirm dialogs (“do you really want to delete this record?“)
How to Create a Text Message
#When you visit the shared components, you can navigate to text messages in the bottom-right section. You can see and edit existing ones, or create new ones.
The name field is the identifier for the text message. If you want to use the message in JavaScript, you have to explicitly check the “Use with JavaScript” checkbox. The language field is only relevant if your app supports multiple languages.
How to use them
#When you create a text message, you can use it in your app in different ways.
Substitution String
#You can use substitution strings in almost any place in your APEX app. They will automatically be replaced with the text message. You wrap the message ID in &.
and put the prefix APP_TEXT$
in front of it:
You may have seen this already if you let APEX create a form page for you. It will by default use the substitution &APP_TEXT$DELETE_MSG!RAW.
for the delete confirm message. You can create this text message and change the text to change the warning text on any page.
Note that the reference uses the !RAW
suffix. Without it, the text will be escaped. I am not sure why the auto-generated form page uses it, as the message does not include any HTML.
PL/SQL
#You can also use text messages in PL/SQL. The APEX_LANG
package has the function message
for this purpose:
You can add additional parameters that will be replaced in the text message. The text message can include placeholders %0
to %9
which will be replaced by the corresponding parameter:
Additionally, we could pass a language and reference messages from another app in the same workspace:
JavaScript
#I never had a use case where I needed the text messages in a JavaScript context. But you can still use them, and this is really nice!
It would be inefficient when all text messages would be loaded on every page load, even when you don’t use them. That’s why we need to explicitly load them before usage. You can do this with the apex.lang.loadMessages
function. It takes an array of message IDs and returns a promise. When the promise is fulfilled, you can use the messages. Remember that you need to check the “Use with JavaScript” checkbox when you create the text message.
Strangely, the API to access the messages is different. You can use the apex.lang.getMessage
function to get a message and additionally apex.lang.formatMessage
to format one with parameters. I am not sure why there are separate functions and why you can’t pass the parameters as an object with named parameters, like it works with the PL/SQL function. Instead, you have to pass the parameters as separate arguments, one after another.
There are additional functions like hasMessage
, loadMessagesIfNeeded
, and formatMessageNoEscape
. You can go to apex.oracle.com/jsapi and choose apex.lang
to find out more about them.
About the JS:
As loadMessages
calls the database and this is asynchronous, the function returns a promise. From the perspective of the browser, the database is an entirely different system that it has no control over. We send a message to it and hope that after some time it fulfills our request and returns something (the messages). This is different from synchronous code, where the browser has full control over the execution and the tasks are completed line by line.
The await
keyword is a way to wait for the promise to resolve (anything to come back from the DB). It is only allowed in async functions. You may have seen another syntax where you use .then(...)
to handle the result of the promise. This is the old way to do it; I like the newer await
syntax more.
To be able to use await
we have to put our code into a function and label it as asynchronous by writing async
before it. We could have wrapped the code into a named function and immediately called it like async function run() { ... } run();
. I like to use IIFEs (Immediately Invoked Function Expression) where you don’t give a name to the function and call it directly by putting these strange sets of ”()” in there.
Auto-use of translated text messages
#If you have a multi-language app, you can translate your text messages. When you start creating a new translation, your existing text messages will be copied, and you can translate the text. If you add a new text message, you have to manually create another one for the other language.
Substitution strings have the benefit that APEX automatically uses the text message in the user’s language if it exists. This is really convenient.
I haven’t worked a lot with translated apps, but Flows for APEX builds on it. You can check out their builder app to see it in action. Here is one example for the message “APP_VIEW”:
Difference between shortcuts
#In the shared components under “Other Components” there are also shortcuts. The help text says: “Use shortcuts to write frequently used code once and then reference it in many places within your application. Shortcuts also allow for the dynamic generation of code in places that typically only support static text.”
They work similarly, as they also allow you to store text, and you reference them with some kind of substitution syntax. You need to wrap the name in parentheses, like "MY_SHORTCUT"
. They do not allow translations or parameters, and they don’t have a nice API like text messages do.
They allow for dynamic content, as you can call a PL/SQL function that returns a string. There is even a type for images, but I have no clue how or when to use it.
My take is that they are more focused on HTML content than basic text content. I have not yet found a use case for them in practice; if you have, please share it in the comments.