There have been several challenges in implementing this feature in a way that doesn't damage performance in big tables. While a prototype that follows a similar approach as ice:panelTooltip has already been developed, it was preferred to come up with a solution that minimizes the need for additional markup both in the facelet and in the rendered HTML document. Trying to satisfy all these requirements might not be possible and it's necessary to agree on which approach to implement, based on which requirements are more important. I now discuss the evolution of this development, obstacles and possible solutions.
The changes made for ICE-8155 allowed the ace:tooltip component to target components within iterative containers while ace:tooltip is outside of such container. This was done by collecting the client ids of all the components that were to receive the tooltip functionality, in the render phase, and include them in the initialization script for the tooltip.
Then, an initial effort to solve this issue was made by introducing a helper component called ace:tooltipContext, which was to be placed inside a table (or iterative container) and whose only job was to hold a value expression that would be evaluated inside the table and whose value would be passed to an event object argument for the display listener method. This component worked by wrapping the component that was triggering the tooltip event. This approach was modeled after the approach used by ice:panelGroup and ice:panelTooltip. This helper component needs to be rendered in the HTML document, so that the row can be detected, pretty much like the role of an ice:panelGroup.
Since, it was preferred to avoid adding additional markup per row, a new approach was needed to solve this situation. Also, it was observed that with the current approach of specifying all the client ids of the nodes that were going to receive the tooltip functionality, there was a possibility that if during a dynamic update the component with the tooltip functionality changed its external representation (HTML markup), it would lose its tooltip functionality altogether, since the original nodes would be replaced on the page. So, an approach to use a helper component to wrap the entire table was attempted.
This new approach consisted in wrapping the table in a component provisionally-named ace:tooltipSummoner, which would receive the tooltip functionality and act as a delegate for the inner components that were to actually trigger the tooltip. This involved collecting all the ids in the client from the triggerer element up to the tooltip summoner, passing them to the server, and them using VisitTree procedures to determine the row that triggered the tooltip display event. This did not work as expected, because when the tooltip functionality is applied to this ace:tooltipSummoner wrapper, it is not possible to know which inner element triggered the tooltip display event. The event object returned by jQuery would indicate that the 'target' of the event is the tooltip summoner itself, and not the specific node that was hovered over. Other target properties like 'relatedTarget', 'currentTarget' and 'delegateTarget' didn't return the specific node either. The reason why this happens is that the underlying jQuery plugin 'qtip' uses the .bind() method directly on the node that was specified (in this case the tooltip summoner), and it doesn't provide an option or a way to use delegated events, so the target will always be the element that received the tooltip functionality itself.
One possible solution would involve modifying qtip to support delegated events and carry on with the approach described above. However, we've been using qtip as a black box since the development of ACE components started. Only very minor modifications have been made. Looking at the code reveals that event bindings are done at different points of the code (which consists of 2000+ lines) and that other parts of the code expect certain conditions and make certain assumptions regarding the registration of DOM events. Making major changes to the qtip code would require a considerable amount of time and effort, and it is uncertain how many side effects this could produce.
Another possible solution is to go back to the simpler initial approach, using the ace:tooltipContext component. The problem with components losing the tooltip functionality by being replaced on the HTML page could be solved by simply applying the tooltip functionality to the ace:tooltipContext itself, since it simply renders a span element and never changes (since it only has fixed id and class attributes). This approach requires the least amount of effort. However, if the whole table changes at some point, it would be necessary to also re-run the tooltip script to re-apply the tooltip functionality, which could probably be done at the app code level.
Another solution is a combination of the previous approaches and requires no additional components and uses some of the code developed for the ace:tooltipSummoner approach. It consists in collecting all the client ids, just as in the initial approach, then applying the tooltip functionality to those specified nodes, and then before the tooltip is displayed we collect all the ids of parent nodes, up to the document object so that we can use them in a VisitTree procedure to determine which row triggered the tooltip (there's already code for this). This approach would have the same situation as the ace:tooltipContext approach regarding losing the tooltip functionality if nodes are replaced, but it could be solved by applying the tooltip functionality to nodes that aren't likely to change (like h:panelGroup), wrapping the whole table cell. However it still involves additional markup. This approach requires a moderate amount of effort.
There have been several challenges in implementing this feature in a way that doesn't damage performance in big tables. While a prototype that follows a similar approach as ice:panelTooltip has already been developed, it was preferred to come up with a solution that minimizes the need for additional markup both in the facelet and in the rendered HTML document. Trying to satisfy all these requirements might not be possible and it's necessary to agree on which approach to implement, based on which requirements are more important. I now discuss the evolution of this development, obstacles and possible solutions.
The changes made for
ICE-8155allowed the ace:tooltip component to target components within iterative containers while ace:tooltip is outside of such container. This was done by collecting the client ids of all the components that were to receive the tooltip functionality, in the render phase, and include them in the initialization script for the tooltip.Then, an initial effort to solve this issue was made by introducing a helper component called ace:tooltipContext, which was to be placed inside a table (or iterative container) and whose only job was to hold a value expression that would be evaluated inside the table and whose value would be passed to an event object argument for the display listener method. This component worked by wrapping the component that was triggering the tooltip event. This approach was modeled after the approach used by ice:panelGroup and ice:panelTooltip. This helper component needs to be rendered in the HTML document, so that the row can be detected, pretty much like the role of an ice:panelGroup.
Since, it was preferred to avoid adding additional markup per row, a new approach was needed to solve this situation. Also, it was observed that with the current approach of specifying all the client ids of the nodes that were going to receive the tooltip functionality, there was a possibility that if during a dynamic update the component with the tooltip functionality changed its external representation (HTML markup), it would lose its tooltip functionality altogether, since the original nodes would be replaced on the page. So, an approach to use a helper component to wrap the entire table was attempted.
This new approach consisted in wrapping the table in a component provisionally-named ace:tooltipSummoner, which would receive the tooltip functionality and act as a delegate for the inner components that were to actually trigger the tooltip. This involved collecting all the ids in the client from the triggerer element up to the tooltip summoner, passing them to the server, and them using VisitTree procedures to determine the row that triggered the tooltip display event. This did not work as expected, because when the tooltip functionality is applied to this ace:tooltipSummoner wrapper, it is not possible to know which inner element triggered the tooltip display event. The event object returned by jQuery would indicate that the 'target' of the event is the tooltip summoner itself, and not the specific node that was hovered over. Other target properties like 'relatedTarget', 'currentTarget' and 'delegateTarget' didn't return the specific node either. The reason why this happens is that the underlying jQuery plugin 'qtip' uses the .bind() method directly on the node that was specified (in this case the tooltip summoner), and it doesn't provide an option or a way to use delegated events, so the target will always be the element that received the tooltip functionality itself.
One possible solution would involve modifying qtip to support delegated events and carry on with the approach described above. However, we've been using qtip as a black box since the development of ACE components started. Only very minor modifications have been made. Looking at the code reveals that event bindings are done at different points of the code (which consists of 2000+ lines) and that other parts of the code expect certain conditions and make certain assumptions regarding the registration of DOM events. Making major changes to the qtip code would require a considerable amount of time and effort, and it is uncertain how many side effects this could produce.
Another possible solution is to go back to the simpler initial approach, using the ace:tooltipContext component. The problem with components losing the tooltip functionality by being replaced on the HTML page could be solved by simply applying the tooltip functionality to the ace:tooltipContext itself, since it simply renders a span element and never changes (since it only has fixed id and class attributes). This approach requires the least amount of effort. However, if the whole table changes at some point, it would be necessary to also re-run the tooltip script to re-apply the tooltip functionality, which could probably be done at the app code level.
Another solution is a combination of the previous approaches and requires no additional components and uses some of the code developed for the ace:tooltipSummoner approach. It consists in collecting all the client ids, just as in the initial approach, then applying the tooltip functionality to those specified nodes, and then before the tooltip is displayed we collect all the ids of parent nodes, up to the document object so that we can use them in a VisitTree procedure to determine which row triggered the tooltip (there's already code for this). This approach would have the same situation as the ace:tooltipContext approach regarding losing the tooltip functionality if nodes are replaced, but it could be solved by applying the tooltip functionality to nodes that aren't likely to change (like h:panelGroup), wrapping the whole table cell. However it still involves additional markup. This approach requires a moderate amount of effort.