Skip to main content

Implementation of ADF XML Menu Model

This post will cover the coding details of implementing an ADF XML menu model.

Do you need to manage all your navigation menus in a centralized file including its label, URL, title and other custom attributes you may have? If so, the solution is to use ADF XML menu model.

ADF XML menu  model is the same concept as the built in WebCenter Navigation Model in Oracle WebCenter Portal. It uses a XML file to hold the navigation metadata - id, label, title, destination/url and other custom attributes you may need.

The XML file is the data model layer. Using XML menu, you can generate various kinds of navigation user interface components, such as navigation links, bars, tabs, breadcrumbs, etc. You can also apply business logic to conditionally control the rendering of the navigation menu. The business logic including security can be applied at either the XML model layer or the UI presentation layer depending on where it fits your purpose. You can create multiple navigation hierarchy (nested navigations) in your XML menu model.

Without further ado, here are the details you like to know on implementation of such XML menu model in ADF application.

1. Create ADF Menu Model

ADF menu model is created on top of the unbounded task flow in application. By default one application comes with one unbounded task flow, but you can create additional if needed. If you have a use case requiring different independent sets of menu models, then you should create additional unbounded task flows as the unbounded task flow and menu model go as 1 to 1. At ADF runtime, multiple unbounded task flows will be compiled into one for the application they sit in.

Select the target unbounded task flow, right click on it and select "Create ADF menu model...". It would create an XML file placeholder.

2. Update Model with Your Data

The generated xml file is where you need to compile your navigation data. It comes with a top level "menu" element and inside it, it can be "itemNode", "groupNode" or "sharedNode".
  • itemNode: Specifies a node that performs navigation upon user selection. An itemNode can have children itemNodes.
  • groupNode: Groups child components; the groupNode itself does no navigation. Child nodes node can be itemNode or another groupNode.
  • sharedNode: References another XMLMenuModel instance. A sharedNode element is not a true node; it does not perform navigation nor does it render anything on its own.
An itemNode comes with a variety of build-in metadata. But they still can fall short for your use cases. A good thing about this model is you can create as many as custom attributes as needed. For example:
<itemNode id="exmapleId" label="#{bundle.EXAMPLELABEL}" rendered="#{sessionScope.IsRendered eq 'Y'}" url="/faces/mypage"/>

As exampled, you can use expression language as the attribute values.

3. Create Managed Bean for the Menu Model

During step 1, when creating the ADF menu model, a managed bean for XMLMenuModel will be automatically configured in faces-config.xml. This is all good if you don't have any custom attributes in the menu model. But in case you do, here is the info you like to refer to.

Create a Java bean extending "org.apache.myfaces.trinidad.model.XMLMenuModel", and in the bean, you will need to cover some key methods to expose the navigation node and custom attributes. Here is a pretty comprehensive example:
  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
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
  private static Object getNodeById(XMLMenuModel menuModel, String nodeId) {
    Object returningNode = null;
    
    FacesContext context = FacesContext.getCurrentInstance();
    ELResolver resolver = context.getApplication().getELResolver();
    ELContext elContext = context.getELContext();

    if (menuModel == null) {
      return returningNode;
    }

    for ( int i = 0; i < menuModel.getRowCount(); i++)
    {
      menuModel.setRowIndex(i);
      Object parentNode = menuModel.getRowData();
      String parentNodeId = resolver.getValue(elContext, parentNode, "id").toString();
      if (parentNodeId.equals(nodeId)) {
        returningNode = parentNode;
        break;
      }

      if (menuModel.isContainer() && !menuModel.isContainerEmpty())
      {
        menuModel.enterContainer();
        for ( int j = 0; j < menuModel.getRowCount(); j++)
        {
          menuModel.setRowIndex(j);
          Object childNode = menuModel.getRowData();
          String childNodeId = resolver.getValue(elContext, childNode, "id").toString();
          if (childNodeId.equals(nodeId)) {
            returningNode = childNode;
            break;
          }
        }
        menuModel.exitContainer();
      }
    }
    return returningNode;
  }

  public static String getUrlById(String nodeId) {
    PortalMenuModel menuModel = (PortalMenuModel)ADFUtils.evaluateEL("#{portal_menu}");
    Object node = getNodeById(menuModel, nodeId);

    if (node == null) {
      return "";
    }

    String url = menuModel.getCustomProperty(node, "url").toString();
    url += "&" + getNodeIdUrlParam(node);
    return url;
  }

  public Map<String, String> getUrlById() {
      return new HashMap<String, String>() {
          @Override
          public String get(Object key) {
              String str = (String)key;
              str = getUrlById(str);
              return str;
          }
      };
  }

  public static String getLabelById(String nodeId) {
    PortalMenuModel menuModel = (PortalMenuModel)ADFUtils.evaluateEL("#{portal_menu}");
    Object node = getNodeById(menuModel, nodeId);
    
    if (node == null) {
      return "";
    }
    
    FacesContext context = FacesContext.getCurrentInstance();
    ELResolver resolver = context.getApplication().getELResolver();
    ELContext elContext = context.getELContext();
    return resolver.getValue(elContext, node, "label").toString();
  }

  public Map<String, String> getLabelById() {
      return new HashMap<String, String>() {
          @Override
          public String get(Object key) {
              String str = (String)key;
              str = getLabelById(str);
              return str;
          }
      };
  }

  public static String findURLByIdWithContextRoot(String nodeId) {
      return HTTPUtils.getContextPath()+getUrlById(nodeId);
  }

  public static void redirectToPageLinkId(String nodeId) {
      FacesContext fctx = FacesContext.getCurrentInstance();
      ExternalContext ectx = fctx.getExternalContext();
      try {
          String url = getUrlById(nodeId);
          ectx.redirect( HTTPUtils.getRequest().getContextPath() +  url); 
          fctx.responseComplete();
      } catch (IOException e) {
          logger.error("Exception occurred in PortalMenuModel while redirecting without url params:" + e.getMessage());
      } 
  }

  public static void redirectToPageLinkId(String nodeId, Map<String,String> urlParam) {
      FacesContext fctx = FacesContext.getCurrentInstance();
      ExternalContext ectx = fctx.getExternalContext();
      try {
          Iterator<String> paramIterator = urlParam.keySet().iterator();
          StringBuilder sb = new StringBuilder();
          while(paramIterator.hasNext()) {
              String key = paramIterator.next();
              sb.append("&"+ key + "=" + urlParam.get(key) );
          }
          String url = getUrlById(nodeId);
          ectx.redirect( HTTPUtils.getRequest().getContextPath() +  url + sb.toString()); 
          fctx.responseComplete();
      } catch (IOException e) {
        logger.error("Exception occurred in PortalMenuModel while redirecting with url params:" + e.getMessage());
      } 
  }

  public static String getNodeIdUrlParam (Object node) {
    FacesContext context = FacesContext.getCurrentInstance();
    ELResolver resolver = context.getApplication().getELResolver();
    ELContext elContext = context.getELContext();

    String nodeIdParam = resolver.getValue(elContext, node, "uniqueId").toString();
    return NODE_ID_PARAM + "=" + nodeIdParam;
  }

In the example, the menu model instance is retrieved by expression "#{portal_menu}" which you can define it in the unbounded task flow in a request scope. 

4. Render the Navigation on UI

The last step is to put the XML menu model data reference into the UI layer. There are quite a few different UI components that can be used for this. Here is the cheat sheet for the ADF menu components.

Alternatively, you can use <af:iterator> to iterate through the menu model and render them using a simple go link. For example:
1
2
3
4
5
<af:iterator var="node" id="i1" value="#{portal_menu}">
  <af:goLink destination="#{portal_menu.urlById[node.id]}">
    <af:outputText id="ot1" value="#{Bundle[node.customPropList['navTitle']]}" escape="false"/>
  </af:goLink>
</af:iterator>

Comments