Skip to main content

ADF 11g : Label Modifications and Persisting Resource Bundle Changes

In a comment on one of my posts on the AMIS technology blog I had a question on UIComponents. The question was if it is possible to modify prompts and labels of components programmatically. As a matter of fact this is possible. You can do this for the session only, or you can choose to persist these changes. In this post I first explain how to implement the "session only" functionality, and in the second part I explain how to use a persisted resource bundle with immediate refresh.

Application Setup

The application used in this post is based on the HR schema and the business components are created and not changed.

First of all you create a page that is used to hold components of which you want to change labels and prompts. Create two panelboxes next to each other. In the first one drop the employees collection and in the second one drop the child employees collection. Drop both as read only form. When browsing the first box, the second one will show the subordinate employees (if any).



Making the Prompts Adjustable

In order to have full control on the attributes of the panelboxes, you have to bind the panelboxes to a bean.
1:  <af:panelBox text="PanelBox1" id="pb1"  
2: binding="#{componentEditor.firstPanelBox}">
3: ...........
4: <af:panelBox text="PanelBox2" id="pb2"
5: binding="#{componentEditor.secondPanelBox}">
6: ...........

In the componentEditor bean, you now have full control over the properties of the panelboxes. You need a way to enter the new values for the prompts of the panelboxes. We will use a popup for that. Add a popup behavior to both of the panelboxes.
1:        <af:showPopupBehavior popupId="labelEditor"  
2: triggerType="click"/>

Define the popup in the page. In this popup we will also use a button to submit the changes to the server.
1:      <af:popup id="labelEditor">  
2: <af:dialog okVisible="false" title="change texts">
3: <af:panelGroupLayout layout="vertical">
4: <af:commandButton text="change text"
5: actionListener="#{componentEditor.editLabel}"/>
6: </af:panelGroupLayout>
7: </af:dialog>
8: </af:popup>

The next step is to add input components to the popup that you can use to enter the new values for the components' label. For this example we will add two; one for each panelbox.
1:         <af:inputText label="box 1"   
2: binding="#{componentEditor.boxOneLabelValue}"
3: value="test">
4: </af:inputText>
5: <af:inputText label="box 2"
6: binding="#{componentEditor.boxTwoLabelValue}"
7: value="test">
8: </af:inputText>

Note that these two input components are bound to the same bean as all other components. If you invoke the popup you can now enter alternative values for the panelbox' headers.


The code in the backing bean that is invoked when the button is pressed, is responsible for actually changing of the prompts. This code is not very difficult.
1:   public void editLabel(ActionEvent actionEvent) {  
2: // Add event code here...
3: firstPanelBox.setText((String)getBoxOneLabelValue().getValue());
4: secondPanelBox.setText((String)getBoxTwoLabelValue().getValue());
5: AdfFacesContext.getCurrentInstance().addPartialTarget(this.getFirstPanelBox());
6: AdfFacesContext.getCurrentInstance().addPartialTarget(this.getSecondPanelBox());
7: }

When the button is pressed the changes are visible immediately.



Persisting Changes

So far, the changes are only visible during the session. If you need to persist the changes you would need a table to store the values, some ADF-BC objects to do the ORM for you, and some logic in the bean to invoke the save action. Besides that, we will use a class based resource bundle.

First let's create the table. For now keep it simple, but be aware that in real life you could also add languages, userid, pageid's (ID's are unique per page).
1:   CREATE TABLE CUTSTOM_LABELS  
2: (
3: COMP_ID VARCHAR2(32 BYTE) NOT NULL ENABLE,
4: LABEL VARCHAR2(64 BYTE) NOT NULL ENABLE
5: )


Creating Business Components

Add the business components that will be responsible for persisting the changes, by simply creating them via the wizard. While in the wizard, make sure to add the viewobject to the application module as well, as it will be used in the page.


Now select the "CutstomLabelsView" collection from the datacontrol and drop it as a table on the popup.

Creating the Resource Bundle

Create a new class called demoResources that extends ListResourceBundle class.
In this class create a method getContents() to collect data from the database via the binding framework, and add the fetched data to the resource bundle.
1:   public Object[][] getContents() {  
2: DCIteratorBinding labelIter =
3: getBindings().findIteratorBinding("CutstomLabelsView1Iterator");
4: RowSetIterator labelRSI = labelIter.getRowSetIterator();
5: int count = labelRSI.getRowCount();
6: Object[][] theseContents = new String[count][count];
7: Row labelRow = labelRSI.first();
8: boolean firstRow = true;
9: int i = 0;
10: do {
11: if (!firstRow) {
12: labelRow = labelRSI.next();
13: } else {
14: firstRow = false;
15: }
16: theseContents[i][0] = labelRow.getAttribute("CompId");
17: theseContents[i][1] = labelRow.getAttribute("Label");
18: i++;
19: } while (labelRSI.hasNext());
20: return theseContents;
21: }


Preparing the Page

In the page, we have to make sure that the resource bundle is recognized and used.
This can be achieved by using , as is shown in the code fragment below.
1:    <f:view>  
2: <f:loadBundle basename="lucbors.blogspot.demo.view.bundle.demoResources"
3: var="res"/>
4: .............

For all labels that need to be dynamic, create a reference to the resource bundle. In this example we will just use a few. These, and all others, all look like the following example.
1:       <af:panelBox text="#{res['BOX1']}" id="pb1"  
2: binding="#{componentEditor.firstPanelBox}">

You can pick as many components as you like, just as long as the resource keys that you reference are also available in the database table. You might want to consider adding functionality to the application to add resource keys to the table.

The Magic Part

In order to refresh the bundle every time changes are committed to the database, we just add an extra method to the demoResources class. This method, called refreshBundle gets the bundleName as argument, and when invoked, it will force the bundle to refresh. This can be done by invoking the clearCache method on this bundle.
1:   public static void refreshBundle(String bundleName) {  
2: try {
3: ResourceBundle.getBundle(bundleName).clearCache();
4: } catch (Exception e) {
5: System.out.println("Failed to refresh bundle " + bundleName +
6: " due to " + e.getMessage());
7: }
8: }

Now we only need to make sure that necessary code is executed when committing changes to the database. First add an action attribute to the commit button. This action has to invoke a method ( saveAndRefresh() ) in the componentEditor bean.
1:          <af:commandButton text="Commit"  
2: id="cb5" partialSubmit="true"
3: action="#{componentEditor.saveAndRefresh}"
4: actionListener="#{bindings.Commit.execute}"/>

In the saveAndRefresh method, invoke the refreshBundle() method.
1:    public String saveAndRefresh() {  
2: // Add event code here...
3: AdfFacesContext.getCurrentInstance().addPartialTarget(this.getFirstPanelBox());
4: AdfFacesContext.getCurrentInstance().addPartialTarget(this.getSecondPanelBox());
5: demoResources.refreshBundle("lucbors.blogspot.demo.view.bundle.demoResources");
6: return null;
7: }

And now, when running the application, every time changes are made to the custom Labels table, the bundle is refreshed, and the new labels are displayed immediately.


And see how this results in an immediate and persisted change.



Conclusion

The use case in this post is just an example of how to persist resource bundle changes and immediatley see these changes in the application. However, by extending ListResourceBundle class, as I did, you can also add objects to the resource bundle. This could for instance be used to implement some kind of content management functionality that will reflect changes immediately.

The application used in this blog is available here.

Comments

HusainD said…
Excellent Article. Hope Oracle comes up with a more integrated solution. :)
SaÅ¡o Celarc said…
Hi!

How to adapt the example to work in 10g?

Regards,
Saso
SaÅ¡o Celarc said…
Hi!

How to adapt the example to work in 10g?

Regards,
Saso
Luc Bors said…
Saso, you don't have to change an awful lot. I think the only issue you have is the invoking component. You might consider using an adf popup instead by the dialog framework. The rest, that is databasepart, adf bc, and resourcebundle should also be working in adf 10.
SaÅ¡o Celarc said…
Luc, thanks for answer. The problem is with clearCache(), which I think is not present in Jdev 10.

I post the question also in JDev forum: http://forums.oracle.com/forums/thread.jspa?threadID=1556706&tstart=15

Regards, Saso
Kiran said…
nice article but i need to keep all the key/value in property file frm it would take the labels. and when user will change the values in the property file it will update the labels of the components. Please tell me the way to achieve it. thanks kiran.
amer sohail said…
Its really a very nice article by Luc. I am having a problem. I was also needed the values from database, so i followed ur approach and it is working fine but my problem is that i have binding resourcebundle with viewobjects so that every attribute will get label from it instead of repeating it on each page. but now my problem is that it is called once when application is accessed. I want to getContents be called per new session. How i can do that?
Hardik Mehta said…
Hi Luc,

Your post has helped me in implementing the DB bases localization feature in my application. Great post and thanks for the same.
Hi Luc.

Thanks a lot for your article.

It served me a lot, but I used it to associate the resource bundle to the Message Validations of a Business Rule.

Check it in my blog:


http://www.notjustjava.com/2012/04/load-messages-of-resource-bundle-from-database-with-adf/
Phuu Tek said…
Hi Luc,

I am in need of a similar solution but as you mentioned in this post, this will work only on he current user session.

What if I wanted to totally re-load the resource bundle? Will this be possible?

Other than the obvious part of restarting the server.

I tried it with this code


ResourceBundle.clearCache(Thread.currentThread().getContextClassLoader());

Popular posts from this blog

ADF 12.1.3 : Implementing Default Table Filter Values

In one of my projects I ran into a requirement where the end user needs to be presented with default values in the table filters. This sounds like it is a common requirement, which is easy to implement. However it proved to be not so common, as it is not in the documentation nor are there any Blogpost to be found that talk about this feature. In this blogpost I describe how to implement this. The Use Case Explained Users of the application would typically enter today's date in a table filter in order to get all data that is valid for today. They do this each and every time. In order to facilitate them I want to have the table filter pre-filled with today's date (at the moment of writing July 31st 2015). So whenever the page is displayed, it should display 'today' in the table filter and execute the query accordingly. The problem is to get the value in the filter without the user typing it. Lets first take a look at how the ADF Search and Filters are implemented by...

ADF 11g Quicky 3 : Adding Error, Info and Warning messages

How can we add a message programatically ? Last week I got this question for the second time in a months time. I decided to write a short blogpost on how this works. Adding messages is very easy, you just need to know how it works. You can add a message to your faces context by creating a new FacesMessage. Set the severity (ERROR, WARNING, INFO or FATAL ), set the message text, and if nessecary a message detail. The fragment below shows the code for an ERROR message. 1: public void setMessagesErr(ActionEvent actionEvent) { 2: String msg = "This is a message"; 3: AdfFacesContext adfFacesContext = null; 4: adfFacesContext = AdfFacesContext.getCurrentInstance(); 5: FacesContext ctx = FacesContext.getCurrentInstance(); 6: FacesMessage fm = 7: new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, ""); 8: ctx.addMessage(null, fm); 9: } I created a simple page with a couple of buttons to show the result of setting the message. When the but...

ADF 12c : Using Jasper Reports en JasperSoft Studio 6.1; What Libraries do you need?

Over the last couple of years, or better in the last decade I have implemented several reporting solutions with Jasper Reports in ADF. I did that in ADF 10g, ADF 11.1.1.x, ADF 11.1.2.x and ADF 12.1.x I also used several version of Jasper Reports. There is a whole lot of documentation, blogposts and presentations available. So when today I got a request from one of my customers to make a setup for the implementation of Jasper Reports 6.1 in ADF 12.1.3 I did not expect any problems. Boy was I wrong. Here is the Story With all the knowledge from the past, I decided to follow the known steps. 1) Download iReport Designer, 2) Build a report in iReport 3) Create an ADF application 4) Add the necessary libraries to use the report 5) Call the report from a button via a Managed Bean Step 1 In the past I used iReport designer to build the reports. When you go to the download site of iReport designer you now see an interesting message. So I took this serious and decided not to u...