Hi I did some tests about what can we do about it and based on all information gathered I reached some conclusions. This is not an easy topic but it is very important, so below there is a full explanation about what it going on.
Doing some tests, I notice the solution for: https://issues.apache.org/jira/browse/MYFACES-2616 Fix UIData state saving model (spec issue 153) Which was included as a new feature in JSF 2.1 and included in tomahawk for JSF 2.0 too, uses the container client id to identify: - Which dataModel is bound to the component (using parent.getContainerClientId()) - Which state belongs to a row. Why? Suppose this case where a nested dataTable is used: <t:dataTable ....> .... <h:column ...> .... <t:dataTable .../> .... </h:column> ... </t:dataTable> In this case, the inner dataTable is used across rows of the other dataTable. If the outer dataTable has two rows, both rows share the inner dataTable component instance. In other words, in the component tree there is just one inner dataTable instance. The reason to do that is just for performance. To do the magic and handle nested dataTables correctly, internally we use a Map<String, Object> (see HtmlDataTableHack _rowStates or _rowDeltaStates), and to identify which "virtual" dataTable instance is we use the container client id as a key. Note the container client id has the rowIndex of the outer dataTable, so that makes easier identify the state that belongs to an specific row, and do not mix the inner dataTable dataModel and row state. The first conclusion is to identify the dataModel and row state we can't use an arbitrary identifier that does not take into account the component tree structure. So, the direction to take is do something in getContainerClientId() method. This conclusion has one side effect: the identifier should warrant to be the same for the specified row. Later I'll explain why is this so important. On the other hand, a html id must comply with the following conditions: 1. Must begin with a letter A-Z or a-z 2. Can be followed by: letters (A-Za-z), digits (0-9), hyphens ("-"), underscores ("_"), colons (":"), and periods ("."). 3. Values are case-sensitive The first condition is warrant by the id of the dataTable (because it is a naming container). The second one must be valid for any string to be appended on the container client id. The important thing to note is there is no way to override this condition. Other consideration to take into account is if the implementation requires to use the client id and retrieve the derived rowKey'. After checking the code the conclusion is the only point is on invokeOnComponent, but only as a 'hint' to find the target component. It is not possible to use a combination of rowKey' and rowIndex, because that violates the condition about the identifier should warrant to be the same for the specified row. I tried it and causes the state to be lost, and the problem becomes critical on nested dataTables case. The second conclusion is it is not necessary to provide a conversion from rowKey' to rowKey, because there are not use cases that requires it. If from the client side some change happens on multiple rows, the ids for each row must be passed on the ajax refreshing operation or just refresh all table. If it is not required to convert from rowKey' to rowKey, is it necessary to convert from rowKey to rowIndex? In practice the answer is no. Since we are considering rowIndex could change between requests or inclusive on the same request (dataModel is reloaded on each encodeBegin()), for invokeOnComponent and visitTree it is required to traverse all rows and then check if the row match with the required rowKey'. Really, use a rowIndex for UIData is sometimes enough simple and convenient, but in fact it is a design flaw of UIData. Frameworks like Trinidad has done something different in this case (UIXCollection, UIXIterator ....) and proposed other interfaces to deal with the limits between the model and the view. The intention here is do not solve that problem, instead is try to enhance the current JSF standard solution as much as we can. The solution proposed for ajax stuff ('row' virtual component) is the right thing to do in this case. From Tomahawk point of view, a solution like RichFaces ajaxKeys or Trinidad is an unnecessary complexity, but note both are valid (different frameworks could propose different solutions). Therefore, the changes to be done are: 1. Add this methods to HtmlDataTableHack: /** * Used to assign a value expression that identify in a unique way a row. This value * will be used later instead of rowIndex as a key to be appended to the container * client id using getDerivedSubClientId() method. * * @return */ @JSFProperty public Object getRowKey() public void setRowKey(Object rowKey) /** * Return the fragment to be used on the container client id to * identify a row. As a side effect, it will be used to indicate * a row component state and a datamodel in nested datatable case. * * <p> * The returned value must comply with the following rules: * </p> * <ul> * <li> Can be followed by: letters (A-Za-z), digits (0-9), hyphens ("-"), * underscores ("_"), colons (":"), and periods (".") </li> * <li> Values are case-sensitive </li> * </ul> * * @return */ protected String getDerivedSubClientId() 2. Change getContainerClientId and use getDerivedSubClientId() to retrieve the rowKey' value and append it. 3. Update the necessary stuff on invokeOnComponent and other parts that requires it. I did some test and it works well. If no objections I'll commit this solution soon. regards, Leonardo Uribe 2011/3/14 Leonardo Uribe <[email protected]>: > Hi Mike > > Thanks for the help. The comments there are very helpful. Specially these > comments: > > MK >> ".. One of the things I notice is that the key to the row state Map is > MK >> the clientid of the dataTable, which of course varies based on the row > MK >> index. Is there some reason why the key isn't the row index? ... > > There is not any special reason. As long as it can be an unique identifier > per row, we can use anything. I checked all the code in UIData and there > are no side effects that this change could cause. > > Just for keep in mind, the relevant points are: > > - setRowIndex() > - processDecodes(), processValidators(), processUpdates() > - Ajax processPartialExecute() and processPartialRendering() > - invokeOnComponent() > - visitTree() > > MK >> What about the possibility of storing the row data in the map using a > MK >> key based on the row data itself? That way, changing the row model > MK >> (adding/deleting rows) would automatically pick the right row state? > > It is a viable alternative. By default use the rowIndex but if some option > is active use that proposed key. The only case this value could be different > is if we handle a Map, but in this case we don't need to worry about that > because it just can't handle such structure. > > To do that, we need to override the code that assume the rowIndex should > be attached to the dataTable client id (see > HtmlDataTableHack.getContainerClientId()). > > MK >> The only issues are that we might be keeping the row state for rows > MK >> that no longer in the model, and we might be holding onto a reference > MK >> to an object that should be garbage collected (I've never delved into > MK >> this, but my understanding is that there are ways around referencing > MK >> things that should perhaps be garbage-collected). > > Really that is not a big issue after all, because with JSF 2.0 partial > state saving and some other improvements on MyFaces, the state size is very > small per row. But anyway we could add a method to remove the state of a > row based on a rowKey or identifier. > > MK >> However, if it works, it would automaticallly solve all of the row > MK >> issues transparently to the end-user. Sorting, inserting, deleting > MK >> rows would work transparently. > > Of course. That would be nice!. > > In resume, we need some interface that do the following points: > > 1. From a rowIndex retrieve/save the state of a row based on a unique > identifier per row (let's call it rowKey). > 2. Use a "derivation" of the rowKey and use it on the dataTable container > clientId, (let's call it rowKey' ). In that way, values or actions decoded > will be handled correctly and will apply to the expected data. If the row > is removed. > 3. (Optional) Add a method that retrieve a rowIndex based on a rowKey and > a method that could convert a rowKey' into a rowKey. The implementation > really does not require such methods, but those ones are convenient ones > if the user requires some manipulation or remove the state of unused > rows. > > Note we can use in (1) as key rowKey' or another derivation, instead rowKey. > > I'll investigate more about it and generate a solution based on the > previous ideas. > > regards, > > Leonardo Uribe > > 2011/3/14 Mike Kienenberger <[email protected]>: >> Leonardo, >> >> This sounds similar to something I started to investigate four years ago. >> >> http://mail-archives.apache.org/mod_mbox/myfaces-dev/200704.mbox/%[email protected]%3E >> >> Maybe you could read through the thread and see if there's anything >> useful in there for you. >> I was pulled off my JSF project for a couple of years right afterward, >> so I never got back to it. >> >> >> On Mon, Mar 14, 2011 at 3:47 PM, Leonardo Uribe (JIRA) >> <[email protected]> wrote: >>> >>> [ >>> https://issues.apache.org/jira/browse/TOMAHAWK-1552?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13006593#comment-13006593 >>> ] >>> >>> Leonardo Uribe commented on TOMAHAWK-1552: >>> ------------------------------------------ >>> >>> I committed a solution for t:dataTable. The idea is follow the same idea we >>> did >>> for detailStamp facet: use a "virtual" component and add it as a facet, >>> with a >>> renderer that can render the detailStamp content individually. >>> >>> In this case we created a facet with name "row" and a virtual component with >>> id="row". The effect is it is possible to reference each row with a clientId >>> and that means we can render the row using the standard tag f:ajax. >>> >>> If multiple rows are required to be updated, you can always add the >>> additional >>> rows with a code like this: >>> >>> datatable.setRowIndex(targetRowIndex); >>> PartialViewContext pvc = FacesContext.getCurrentInstance(). >>> getPartialViewContext(); >>> if (!pvc.isRenderAll()) >>> { >>> Collection rows = pvc.getRenderIds(); >>> String idToAdd = datatable.getFacet("row").getClientId(); >>> if (!rows.contains(idToAdd)) >>> { >>> rows.add(idToAdd); >>> } >>> } >>> >>> The advantage is this code relies on the standard ajax mechanism. >>> >>> I reviewed Richfaces and Trinidad to see how they solve the problem. It is >>> clear there is an association between the rowIndex and a rowKey, so to solve >>> this both of them has an extended DataModel to deal with this logic. For >>> example, in trinidad case there is an interface called >>> org.apache.myfaces.trinidad.model.RowKeyIndex . >>> >>> In theory, there is an old known problem in UIData (since JSF 1.0). >>> The interface javax.faces.model.DataModel looks like this: >>> >>> public abstract class DataModel<E> implements Iterable<E>{ >>> public void addDataModelListener(DataModelListener listener); >>> public DataModelListener[] getDataModelListeners(); >>> public void removeDataModelListener(DataModelListener listener); >>> >>> //size and ordering >>> abstract public int getRowCount(); >>> abstract public int getRowIndex(); >>> abstract public void setRowIndex(int rowIndex); >>> >>> abstract public boolean isRowAvailable(); >>> public Iterator<E> iterator(); >>> >>> //row data >>> abstract public E getRowData(); >>> >>> //structure behind model >>> abstract public Object getWrappedData(); >>> abstract public void setWrappedData(Object data); >>> } >>> >>> In practice, the problem is the model can change between requests, so the >>> rowCount/rowIndex changes too, and that causes the row state becomes >>> inconsistent on the component side. In tomahawk there exist a solution using >>> preserveDataModel attribute. But other possible alternative is make >>> t:dataTable or the custom component extending UIData take into consideration >>> this fact. How? I don't know yet. I'm still thinking about how to do it. >>> In theory for an specific row, the component state of that row must be >>> bound in some way to the state using a rowKey. In practice, that rowKey is >>> usually some property of the rowData, so the clientId of each component in a >>> row must reflect this fact, so when an action or input value is decoded, >>> the right state can be loaded and saved. >>> >>> Now, going back to the initial problem, it is not possible for the moment >>> to do something similar to RichFaces ajaxKeys because there is not an >>> alternative solution for the previous problem yet. So, the best we can do >>> for now is provide the previous solution, but I'll continue investigating >>> what can we do in this case. >>> >>> >>>> t:dataList, t:dataTable ajax >>>> ---------------------------- >>>> >>>> Key: TOMAHAWK-1552 >>>> URL: https://issues.apache.org/jira/browse/TOMAHAWK-1552 >>>> Project: MyFaces Tomahawk >>>> Issue Type: New Feature >>>> Components: Data List >>>> Environment: ALL >>>> Reporter: Dave >>>> >>>> dataList and dataTable: allow ajax reRender specified rows. Richfaces >>>> uses ajaxKeys to specified row indice to be reRendered for ajax request. >>> >>> -- >>> This message is automatically generated by JIRA. >>> For more information on JIRA, see: http://www.atlassian.com/software/jira >>> >> >
