Don't use the forceId attribute

If you can avoid it don't use forceId, it makes more problems than it solves.

See here for the reasons.

How JSF ids generate HTML tag ids

Every JSF component has an id (explicit or implicit). When generating HTML output, this id is converted to a "clientId" which is output as the "id" or "name" attribute of the generated HTML tag.

JSF components use naming contexts to make them unique in a page. Suppose the input field with id myField is used in a form, with id myForm, which is used inside a panel group with id scope1, which is used inside an t:div with id scope0, etc.

  ...
  <t:div id="scope0" ... >
    ...
    <h:panelGroup id="scope1" ... >
      ...
      <h:form id="myForm" ...>
        ...
        <t:inputHidden id="myField"  ... />
        ...
      </h:form>
      ...
    </h:panelGroup>
    ...
  </t:div>
  ...

The actual HTML output for the hidden input field is:

<INPUT type="hidden" name="myForm:myField" .../>

Changes to the layout of the page which involve moving a component from one naming container to another changes the HTML name or id attribute generated. Standard naming container components are:

  • h:form
  • f:subview
  • h:dataTable

The Problems

Controlling ids of components for client-side javascript access

It can be useful to embed javascript into a page which manipulates the HTML objects generated by JSF. However, as described above, JSF component ids include their "naming container" parents embedded in their clientId (HTML id). This makes ids fairly long. It also means that any change to the naming container layout causes a component's clientId to change thus breaking the javascript.

Controlling ids of components for CSS selection

Cascading stylesheets can select the component to apply a stylesheet rule to by specifying its HTML id:

#someHtmlId {.....}

*css_component_ids - Work around for not using MyFaces and the generated ids (foo:bar)

Controlling ids of input components for interception on post

Sometimes, developers want to read HTTP request parameters directly in their code. They would be the result of a HTTP POST, from a page generated by JSF. However because the name of the submitted value includes the naming container ancestry of that component, any change to the naming container layout causes the name of the submitted parameter to change.

Using forceId

The extended standard components you find in Tomahawk feature an extra attribute forceId. If this is set to true, the naming contexts will not be used, but the actual value of the id you have given will be the id of the component.

So, the code

  ...
  <t:div id="scope0" ... >
    ...
    <h:panelGroup id="scope1" ... >
      ...
      <h:form id="myForm" ...>
        ...
        <t:inputHidden id="myField" forceId="true"  ... />
        ...
      </h:form>
      ...
    </h:panelGroup>
    ...
  </t:div>
  ...

will result in a HTML hidden input tage in the generated page with id and name myField, not myForm:myField.

Problems with forceId

The forceId feature can be dangerous, and should be used with care. Every component in a view must have a unique id, or JSF will throw an exception; the "scoping" feature of JSF that creates compound ids like scope0:myId is there to ensure that a page can safely be built from multiple "parts" without causing id conflicts. Component id 'scoping' can be thought of like files within a directory structure; using forceId is like allowing a certain filename to be useable from the "root" directory to access a file no matter where in the directory tree it really is. This of course then makes it impossible to have two files with the same name, regardless of which directories they are in.

Also, HTML requires that ids be unique within a page. If two components should generate HTML tags with the same id, that would be an invalid HTML page.

In particular, be very cautious about using forceId when also using Tiles. Every file needs to be aware of every other file that it might be combined with in a single page, and must ensure that no forceId component it declares conflicts with a forceId component in any of those other files.

Avoiding forceId for javascript use

Javascript "event handlers" attached to HTML elements can use the keyword "this" to access the DOM component that they are associated with. Example:

function doSomething(src) {
  alert("Calling component is " + src);
}

<h:inputText onchange="doSomething(this)"/>

In some cases this information is sufficient to do away with any need to get the "id" of a component at all.

Access from such an event handler to other components in the same page can often be done by using simple naming conventions. As an example, when an input component has JSF id attribute of "data", and its label has JSF id attribute of "dataLabel", a javascript event handler associated with the input component can get the label via:

var labelId = src.id + "Label";

Note that the id of the calling component will include the name of the nearest naming container (unless forceId has been used!). It is therefore simple to obtain ids for components within the same naming container without using forceId at all. Here's an example that allows one component to toggle the visibility of another component via javascript without needing to use forceId at all:

<script>
// Hide or show a specified target component by toggling its CSS display attribute,
//
// Locates the component with JSF id of targetId which is in the same naming
// container as the calling component. The component's CSS style attribute
// "display" is then toggled between "" and "none", ie visible or hidden.
function toggleVisibility(src, targetId) {
  id = src.id;
  lastColon = id.lastIndexOf(':');
  if (lastColon == -1) {
    basePath = "";
  } else {
    basePath = id.substring(0, lastColon + 1);
  }

  fullTargetId = basePath + targetId;
  target = document.getElementById(fullTargetId);

  if (src.isTargetHidden) {
    src.isTargetHidden = false;
    target.style.display = "";
  } else {
    src.isTargetHidden = true;
    target.style.display = "none";
  }
  
  return false;
}
</script>

<h:graphicImage onclick="toggleVisibility(this, 'grid');" .../>
<h:dataTable id="grid" .../>

Note that the h:dataTable uses just "id", not "forceId".

The s:globalId tag

The Tomahawk Sandbox has a component s:globalId (added 2008-10-30). This suppresses any prefixes for its child components, and therefore can effectively achieve the same effect as tomahawk's forceId attribute, but can be applied to any component (not just Tomahawk ones). Note however that it only works on JSF1.2 or later. Note also that the same issues exist as for the tomahawk forceId feature - so use it only as a last resort, when no other solution can be applied.

  • No labels