Friday Jul 20, 2012

The ImageMap Pattern

In my last article, I alluded to the fact that the associated sample combined a bunch of existing patterns and techniques, and that I would progressively write those up.  In this article I'm going to talk about the first of these, the ImageMap pattern.

What's the Use Case?

 This pattern is all about solving a pretty common problem, I have, in my service model, some kind of codified value (e.g. a Type or a Status) which I want to reflect visually in my UI using an image. Now if you only have a couple of options to represent then you can use a simple ternary expression in the page, switching based on the value you're getting from the service.  However, once you get over three or four options the EL starts to get hard to read and maintain, and face it, sometimes you might not even notice that it's evaluating to the wrong thing if the expression is wrong.  So what are the options for approaching this? 

  1. Do the translation in the service layer and provide an attribute containing the correct image to display. 
  2. Manage some simple lookup into a shared UI level resource

Option (1) just feels wrong because you'd be letting client side information, the names of image files in this case,  leak into the wrong layer, so the shared resource approach really looks like the way to go. 

Implementation

The approach that we take in this pattern is to exploit the ability of Expression Language to be able to refer to both arrays and maps using the "[<value>]" syntax. For example the expression mybean.image[1] can actually mean several things depending on what "image" is in this case. If the getImage() method in the underlying mybean returns a List then this would translate to pull out index 1 from that list. If on the other hand getImage() returns a Map then the get() method will be called on the map with a key of (in this case) "1".


We can exploit this behavior, and particularly the understanding of the Map expressions to define a mapping between a piece of data from the model, such as a status code, and a particular image to use to represent that. To illustrate this let's take a simple example where we have some possible string status values in the datamodel and want to map that into different images, thus:

Code from the Service Image To Use
 TABLE  /images/table.png
 VIEW  /images/view.png
FUNCTION  /images/plsql_func.png
 ...  ..

Notice that the names of the images are not quite the same as the codification from the service layer so we can't get away with the simplier solution of:

<af:image source="/images/#{bindings.ObjectType}.png" .../> 

So instead, we have to work via an abstraction using a lookup map for the images 

Step 1: Define your image map

We can define the lookup map to work from either as an explicit managed bean, or even more easily as a stand-alone bean definition in the adfc-config.xml file. Conventionally we will store this bean on the application scope so that all users on the system share the same copy:

<managed-bean>
   <managed-bean-name>typeImages</managed-bean-name>
   <managed-bean-class>java.util.HashMap</managed-bean-class>
   <managed-bean-scope>application</managed-bean-scope>
   <map-entries>
     <map-entry>
       <key>TABLE</key>
       <value>/images/table.png</value>
     </map-entry>
     <map-entry>
       <key>VIEW</key>
       <value>/images/view.png</value>
     </map-entry>
     <map-entry>
       <key>FUNCTION</key>
       <value>/images/pls_func.png</value>
     </map-entry>
     ... 
   </map-entries>
</managed-bean>  

Alternatively if the thing we wanted to key off was a simple numerical list then we could have a definition that used a simple array rather than a map; like this:

<managed-bean>
  <managed-bean-name>lifecycleImages</managed-bean-name>
  <managed-bean-class>java.util.ArrayList</managed-bean-class>
  <managed-bean-scope>application</managed-bean-scope>
  <list-entries>
    <value-class>java.lang.String</value-class>
    <value>/images/new.png</value>
    <value>/images/updated.png</value>
    <value>/images/sclosed.png</value>
  </list-entries>
</managed-bean>  

In this latter case index lifecycleImages[0] would map to /images/new.png, lifecycleImages[1] to /images/updated.png etc.

Step 2: using this bean in EL

Now that the list or map has been defined we can use it thus:

<af:image source="#{typeImages[bindings.ObjectType]}"

Where the bindings.ObjectType attribute binding can be expected to turn one of the valid keys in the map (TABLE,VIEW etc.)

 You can see an example of this version of the pattern inside the tile iterator in the home.jspx page in the sample: DRM004 - AutoReduce and Pagination screen

Variations on the Theme

 I'm always thinking about how to optimize performance, and one useful approach when it comes to images is to use the technique of using image sprites. This is where, instead of having lots of discrete images, you have a single image file, a little like a film-strip, containing all of the images. In this case, CSS is used to assign the filmstrip image as a background to a page element and then the CSS background-position is set to select a particular icon on the strip. This has the advantage of only needing a single round trip to grab all the images at once, having a positive effect on your page load time. 

Using this idea we can take the Image Map Pattern approach, but rather than having the map entry value be the name of a discrete image, it can point to the name of the CSS style. This style can be applied in the styleClass attribute of an element to pull the required image from the film-strip. To save you having to create a bunch of extra styles to encode the positions, you could also just hold an offset in the film-strip as the lookup value. 

This latter approach can also be seen in the  DRM004 - AutoReduce and Pagination screen sample, have a look at the managed bean definition for typeImageOffsets. This information is then used to define the images in the list view of the home.jspx page, thus:

<af:spacer styleClass="iconStripBase" inlineStyle="background-position:#{typeImageOffsets[row.ObjectType]}px;"/> 

 Have a look at the sample to see it all in action. 

About

Hawaii, Yes! Duncan has been around Oracle technology way too long but occasionally has interesting things to say. He works in the Development Tools Division at Oracle, but you guessed that right? In his spare time he contributes to the Hudson CI Server Project at Eclipse
Follow DuncanMills on Twitter

Note that comments on this blog are moderated so (1) There may be a delay before it gets published (2) I reserve the right to ignore silly questions and comment spam is not tolerated - it gets deleted so don't even bother, we all have better things to do with our lives.
However, don't be put off, I want to hear what you have to say!

Search

Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today