I am trying to create a custom UIData component and I am having troubles with Ajax. The first call works fine, but subsequent calls cannot resolve my UIData 'var' attribute. When trying to debug, I can see that the first ajax call restores my custom UIData and puts the 'var' into the RequestMap. Subsequent calls though do not call again restoreState resulting in an empty 'var' variable.
PS. Apologies for this post not being very SSCCE but it would be very large.
The problem was that I was not using
UIComponentBase.restoreAttachedState(context, values[1]);
UIComponentBase.saveAttachedState(context, getValue());
in Save and restore state
public Object saveState(FacesContext context)
public void restoreState(FacesContext context, Object state)
Another issue was that I didn't reset the rowIndex of the UIData
setRowIndex(-1);
in the
public boolean visitTree(VisitContext context, VisitCallback callback)
This causes the id of the saved state to be adjusted with the index resulting to a key miss in the next restore phase.
Although my answer might be interesting for some, it didn't answer how UIData 'var' is resolved. The answer is that in each iteration/phase of UIData processing the setRowIndex(int) method is called which sets the 'var' attribute with the data from the dataModel in the request map (See extract below). This is called by the UIData method invokeOnComponent or UIData.visitTree() which is called by FaceletViewHandlingStrategy.locateComponentByClientId() in JSF 1.2 or by various places in JSF2 including state Management Strategies, ViewContextImpl and many others:
See change in this link under -Tree visiting
http://andyschwartz.wordpress.com/2009/07/31/whats-new-in-jsf-2/
Documentation on visitTree:
http://docs.oracle.com/cd/E17802_01/j2ee/javaee/javaserverfaces/2.0/docs/api/javax/faces/component/UIComponent.html#visitTree(javax.faces.component.visit.VisitContext, javax.faces.component.visit.VisitCallback)
This is the extract from UIData:
String var = (String) getStateHelper().get(PropertyKeys.var);
if (var != null) {
Map<String, Object> requestMap =
getFacesContext().getExternalContext().getRequestMap();
if (rowIndex == -1) {
oldVar = requestMap.remove(var);
} else if (isRowAvailable()) {
requestMap.put(var, getRowData());
} else {
requestMap.remove(var);
if (null != oldVar) {
requestMap.put(var, oldVar);
oldVar = null;
}
}
Related
I have an application which is based on JSF in which I have a managed bean with request-scoped.
I have a singleton class having static ConcurrentHashMap, this class is used to store some information where I am using current FacesContext as the key. private static ConcurrentHashMap<FacesContext, ConcurrentHashMap<String, Object>> contextStore = new ConcurrentHashMap<>();
This class is used globally across the application.
This map will be shared by multiple requests.
For every request, I am storing some information in it as the FacesContext as the key. I need this map in different parts of this application during the request lifetime and it should be cleared when the request is completed in order to avoid the memory leak.
ie, the current FacesContext key should be removed from this map.
Is there any way to clear this map (this class has a method clearAllByContext to clear the map for the current FacesContext)on completing the current request?
The following are the methods to set, get, and clear the Map.
// Method to set values
public static void set(FacesContext context, String key, Object value) {
ConcurrentHashMap eachStoreByContext = contextStore.get(context);
if (eachStoreByContext == null) {
eachStoreByContext = new ConcurrentHashMap();
contextStore.put(context, eachStoreByContext);
}
eachStoreByContext.put(key, value);
}
// Method to get values
public static Object get(FacesContext context, String key) {
ConcurrentHashMap eachStoreByContext = contextStore.get(context);
if (eachStoreByContext != null) {
return eachStoreByContext.get(key);
}
return null;
}
//Method to clear all values for the Facescontext
public static void clearAllByContext(FacesContext context) {
contextStore.remove(context);
}
I need to clear this Map by calling clearAllByContext(FacesContext context) on completing every request. Is there any FacesContextListener available so that I can invoke this method in that?
I have this JSF backing bean method:
public void filterProductsByCategory()
{
filtered = true;
products = controller.obtainProductListByCategory(selectedDesiredCategory);
showMessage("Filtered by selected category");
}
Which calls the obtainProductListByCategory method. This returns a list of products. When it is time to refresh the JSF page (it access the getters for the variables it needs), it enters the getter here:
public List<Product> getProducts()
{
if(filtered)
{
filtered = false;
return products;
}
products = controller.obtainProductList();
return products;
}
The obtainProductList retrieves all the products in the database. My application is a webmarket and what I'm trying to do is to first: get all the products displayed (which is working fine), then: sort by category (there's the problem). For some reason even after the last method enters the if statement and returns, it goes back inside, with the variable filtered false and proceeds with the method.
I found out through debugging that this code seems to execute everytime too (Located in Method.java):
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass(1);
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
Curious enough, the above loop I mentioned is repeated for as many products exist in my List the first time I retrieve all the objects (all the objects in my database). In the end the web page gets all the products back again, even when I tried to filter the results in my datatable What is going on here?
My application has a save and retrieve function. I have the save/retrieve working in that the objects are saved to a database and retrieved correctly. However, in my retrieve landing page, depending on the state of the saved application, I either want to validate some details with the user, or silently navigate to the last accessed view. The latter is where I'm having trouble.
We're using spring beans and in my SaveAndRetrieve page bean I have:
#PostConstruct
public void initialise() {
caseNotFound = false;
caseReference = saveAndRetrieveActionHandler.getRequestedCaseReference();
LOGGER.debug("Retrieve initialise. Case ref is {}", caseReference);
if (caseReference != null) {
try {
saveAndRetrieveActionHandler.retrieveApplicationByCaseRef();
LOGGER.debug("Retrieve initialise - case found");
final NavigationOutcome outcome = saveAndRetrieveActionHandler.getLastAccessedView();
if (outcome.getApplicationState() == ApplicationState.QUOTE) {
LOGGER.info("Quote retrieved, navigating to view");
// HERE IS WHERE THE TROUBLE LIES! THIS DOESNT WORK
FacesUtils.setNextViewNavigation(outcome.getViewId());
}
} catch (final FrameworkException fe) {
LOGGER.debug("Exception caught {}", fe);
caseNotFound = true;
}
}
}
outcome is an enumeration containing amongst other things the view I need to navigate to, and the application state (another enumeration). If applicationState is quote, I want to silently navigate. For all other applicationStates I want to challenge the user to verify them.
My facesUtils method is:
public static void setNextViewNavigation(final String p_lastAccessedViewId) {
if (p_lastAccessedViewId != null) {
getCurrentViewRoot().setViewId(p_lastAccessedViewId);
}
}
I've also tried calling this method
public static void navigateToOutcome(final String p_outcome) {
final FacesContext context = getFacesContext();
final NavigationHandler navigationHandler = context.getApplication().getNavigationHandler();
navigationHandler.handleNavigation(context, null, p_outcome);
}
Despite my efforts, I'm seeing the landing page wheras I want to silently navigate to the saved page
Basically I want to abort the current lifecycle and reset the viewroot to the saved view. (note I am not saving the component tree itself, just my business objects)
One more piece of information, this is jsf1.2, but with facelets. I cannot use any jsf2 specific functionality, nor can I use any third party JSF extenstions.
Help please!
We solved this by using a ui:include tag with the src attribute being a jsf method that determines the name of the page to navigate to.
How to import a javascript and a css file in head tag of a html generated by a custom jsf component? I've found some articles about using resources for this purpose.
As in (source: http://technology.amis.nl/blog/6047/creating-a-custom-jsf-12-component-with-facets-resource-handling-events-and-listeners-valueexpression-and-methodexpression-attributes):
protected void writeScriptResource(
FacesContext context,
String resourcePath) throws IOException
{
Set scriptResources = _getScriptResourcesAlreadyWritten(context);
// Set.add() returns true only if item was added to the set
// and returns false if item was already present in the set
if (scriptResources.add(resourcePath))
{
ViewHandler handler = context.getApplication().getViewHandler();
String resourceURL = handler.getResourceURL(context, SCRIPT_PATH +resourcePath);
ResponseWriter out = context.getResponseWriter();
out.startElement("script", null);
out.writeAttribute("type", "text/javascript", null);
out.writeAttribute("src", resourceURL, null);
out.endElement("script");
}
}
private Set _getScriptResourcesAlreadyWritten(
FacesContext context)
{
ExternalContext external = context.getExternalContext();
Map requestScope = external.getRequestMap();
Set written = (Set)requestScope.get(_SCRIPT_RESOURCES_KEY);
if (written == null)
{
written = new HashSet();
requestScope.put(_SCRIPT_RESOURCES_KEY, written);
}
return written;
}
static private final String _SCRIPT_RESOURCES_KEY =
ShufflerRenderer.class.getName() + ".SCRIPTS_WRITTEN";
However besides writing in head tag, the import link is also write in my component code. The method writeScriptResource() is called in the beginning of encodeBegin method.
Does anyone know how to do that in order to avoid inject inline scripts and styles in my page?
Thanks in advance,
Paulo S.
I don't know if you can use JSF 2 but with composite components is easier to include resources using h:outputStylesheet and h:outputScript. When the page is rendered, resources will be in the head of the html.
i am trying to get the client id of a component in a datatable. the problem is that jsf puts row index before the component id automatically, i.e.
<a id="mappedidentifier_table:1:mappedidentifier_Update" href="#">Update</a>
for a link in the second row (index=1).
i am using the following methods to get the clientId
public static String findClientId(String id) {
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot view = context.getViewRoot();
if (view == null) {
throw new IllegalStateException("view == null");
}
UIComponent component = findComponent(view, id);
if (component == null) {
throw new IllegalStateException("no component has id='" + id + "'");
}
return component.getClientId(context);
}
private static UIComponent findComponent(UIComponent component, String id) {
if (id.equals(component.getId())) {
return component;
}
Iterator<UIComponent> kids = component.getFacetsAndChildren();
while (kids.hasNext()) {
UIComponent kid = kids.next();
UIComponent found = findComponent(kid, id);
if (found != null) {
return found;
}
}
return null;
}
however this returns
mappedidentifier_table:mappedidentifier_Update,
instead of
mappedidentifier_table:1:mappedidentifier_Update,
therefore it does not match any element cause the row index in id is missing.
i've read http://illegalargumentexception.blogspot.com/2009/05/jsf-using-component-ids-in-data-table.html
however i intend to have a simpler implementation, rather than TLD function or facelets like the author did.
does anyone have any thoughts ?
thanks,
dzh
however this returns mappedidentifier_table:mappedidentifier_Update instead of mappedidentifier_table:1:mappedidentifier_Update
This will happen if you resolve the clientId outside the context of a row. The row context is set up at each stage of the lifecycle by the UIData control.
It might also happen if you're using immediate evaluation instead of deferred evaluation in your EL expressions - ${} instead of #{}.
As an aside, and as noted in the article, that lookup algorithm will only work if the component identifier is unique to the view; the spec says it need only be unique to the NamingContainer; this need not be a problem if you are careful about using unique component IDs on your page.