Details

    • Type: New Feature New Feature
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 1.1.11
    • Component/s: Data List
    • Labels:
      None
    • Environment:
      ALL

      Description

      dataList and dataTable: allow ajax reRender specified rows. Richfaces uses ajaxKeys to specified row indice to be reRendered for ajax request.

        Issue Links

          Activity

          Hide
          Leonardo Uribe added a comment -

          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.

          Show
          Leonardo Uribe added a comment - 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.
          Hide
          Leonardo Uribe added a comment -

          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.

          Show
          Leonardo Uribe added a comment - 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.

            People

            • Assignee:
              Leonardo Uribe
              Reporter:
              Dave
            • Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development