0
votes

i have a problem with the Dialog control from the Extention library:

I have created a java custom control wich searches some views, collects some data and displays it. This works nice if i place it on a XPage.

But i want to display the data in a Dialog so i used the Dialog control from the extention library. Using the Dialog control without any configuration also works fine but it takes some time for my control to search the views and display the data every time i open the dialog.So to reduce the waiting time for the user i wanted to use the option "keepComponents="true" from the Dialog control.

Now if i open the Dialog for the first time everything is perfekt but if i open it a secound time it displays the content from the first opening in addition to an error from my controlRenderer wich tells me that it could not get the viewName from the control. This error stacks up for every time i open and close the dialog.

I found a Post on OpenNtf from somebody who had the same issue with multiple content in his dialog when using this option but he didnt get any answers to his question. Is this a bug of the component? Should i forget this option and cache my data in a bean? Why can't the renderer get the Viewname from the component?

1

1 Answers

0
votes

The answer that follows assumes that the phrase "java custom control" in your question refers to a JSF component you developed; in XPages, the term "custom control" usually refers to an instance of a Custom Control design element, which is IBM's implementation of the JSF notion of "composite components".

You've stated that the component initially behaves as intended but fails on subsequent requests. This typically indicates that the restoreState and saveState methods of the component have not been properly implemented.

When the default serialization options are enabled for an application, all component state is written to disk at the end of each request, and read back into memory at the beginning of the next. These two operations are handled, respectively, by the saveState and restoreState methods of each component.

For example, suppose you defined a component for adding HTML canvas tags to an XPage, and decided to support the gesture and touch events associated with that element. So your component class would contain fields to store any code bound to those events:

private String ongesturechange;
private String ongestureend;
private String ongesturestart;
private String ontouchcancel;
private String ontouchend;
private String ontouchmove;
private String ontouchstart;

Each of those fields would typically then have an associated "getter" and "setter" method:

public String getOngesturechange() {
    return getStringProperty("ongesturechange", this.ongesturechange);
}

public void setOngesturechange(String ongesturechange) {
    this.ongesturechange = ongesturechange;
}

When an instance of that component is initialized, the "setter" method associated with each attribute that is defined for that component instance will be passed the value defined for that attribute. For the remainder of the initial page request, then, the private field for each defined attribute will store the value that was set. At the end of the request, the saveState method writes the values of these fields to disk. A typical saveState method looks similar to the following:

@Override
public Object saveState(FacesContext context) {
    Object[] properties = new Object[8];
    int idx = 0;
    properties[idx++] = super.saveState(context);
    properties[idx++] = this.ongesturechange;
    properties[idx++] = this.ongestureend;
    properties[idx++] = this.ongesturestart;
    properties[idx++] = this.ontouchcancel;
    properties[idx++] = this.ontouchend;
    properties[idx++] = this.ontouchmove;
    properties[idx++] = this.ontouchstart;
    return properties;
}

The call to super.saveState() executes the same method, but using the version of the method defined in the parent class. So the on-disk representation of each component is essentially a nested array: each layer in the hierarchy stores all the properties it inherits from its parent class in the first element of the array, then stores all the properties that it defines in additional array elements.

When the component tree is restored on subsequent requests, each component uses its restoreState method to reconstitute the values of all its fields. A typical restoreState method looks similar to the following:

@Override
public void restoreState(FacesContext context, Object state) {
    Object[] properties = (Object[]) state;
    int idx = 0;
    super.restoreState(context, properties[idx++]);
    this.ongesturechange = ((String) properties[idx++]);
    this.ongestureend = ((String) properties[idx++]);
    this.ongesturestart = ((String) properties[idx++]);
    this.ontouchcancel = ((String) properties[idx++]);
    this.ontouchend = ((String) properties[idx++]);
    this.ontouchmove = ((String) properties[idx++]);
    this.ontouchstart = ((String) properties[idx++]);
}

This hierarchically reads the on-disk data back in: each class passes a set of properties to the parent class, then assigns the remaining array elements to the fields they were associated with when the component state was saved.

This process provides an easy way to maintain component state across requests -- each layer of inheritance need only concern itself with the new properties that layer defines -- but these state maintenance methods are easy to forget to implement. If either method is omitted from the component implementation, then the page "forgets" the property values on subsequent requests, because either they were never written to disk, or were not loaded back into memory, or both.

Assuming that this is the root cause of your problem, the reason the problem does not occur when the component is inside a dialog with the default (false) value for keepComponents is because the default dialog behavior is to remove its children from the component tree entirely when the dialog is closed. This behavior is for performance reasons: there's theoretically no benefit to be gained from storing a server-side representation of components that only exist inside a dialog that the user is not currently interacting with. When the dialog is opened again, a new instance of each child component is created using the original property values. In this scenario, it wouldn't matter that your component isn't saving its state, because each time it's used, a new instance is created. But if the dialog is told to keep its children in the component tree, now the component must properly maintain its own state... otherwise its property values are discarded at the end of each request and subsequent requests are unaware of the previous values.

In summary, yes, the data you're displaying should be cached in a bean (or data source) if the data is unlikely to change enough between requests to justify obtaining the data again during every single event. But the reason for the specific behavior you're describing is most likely because your component implementation is not properly maintaining its own state.