3 Replies Latest reply on Nov 4, 2008 6:51 AM by captainvoid

    bug in tooltip when used in iterative components?

    captainvoid

      Hi,
      I use rich:tooltip in rich:tataTable with datascroller and ExtendedDataModel in my backing bean. The row keys in my dataModel are Strings.
      The tooltips don't show up if the corresponding row key contains "special" characters like for example backslashes or UTF-8 characters.
      I think it is a javascript issue because the generated HTML of the tooltip looks like this with the row key "\u\d\o":

      <div class="dr-rich-tool-tip rich-tool-tip" id="j_id101:refDataList:cn=\u\d\o:j_id105" style="position: absolute; display: none; z-index: 99;">
      <div id="j_id101:refDataList:cn=\u\d\o:j_id105content">cn=\u\d\o</div>
      <span id="j_id101:refDataList:cn=\u\d\o:j_id105script" style="display: none;">
      <script type="text/javascript" id="scriptj_id101:refDataList:cn=\u\d\o:j_id105">
      //<![CDATA[
      new ToolTip({'delay':500,'hideDelay':0,'showEvent':'mouseover','hideEvent':''} ,{'oncomplete':null,'onshow':null,'onhide':null} ,"j_id101:refDataList:cn=\u\d\o:j_id105","j_id101:refDataList:cn=\u\d\o:j_id103","client",false,"bottom-right",true,10,10,"A4J.AJAX.Submit('_viewRoot','j_id101',event,ajaxOptions)",{'control':this,'oncomplete':function(request,event,data){;{var _toolTip = $('j_id101:refDataList:cn=\u\d\o:j_id105').component;_toolTip.toolTipContent = $('j_id101:refDataList:cn=\u\d\o:j_id105content');_toolTip.displayDiv();}},'parameters':{'j_id101:refDataList:cn=\\u\\d\\o:j_id105':'j_id101:refDataList:cn=\\u\\d\\o:j_id105','ajaxSingle':'j_id101:refDataList:cn=\\u\\d\\o:j_id105'} ,'onbeforedomupdate':function(request,showEvent,data){;{ var _toolTip = $('j_id101:refDataList:cn=\u\d\o:j_id105').component;_toolTip.toolTip.style.display = 'none'; }},'actionUrl':'/trustedx-console2.1/endEntities/list.seam'} );;
      
      //]]>
      </script>
      </span>
      </div>
      

      or like this with the row key "本語" (japanese characters, couldn't post them correctly here):
      <div class="dr-rich-tool-tip rich-tool-tip" id="j_id101:refDataList:#26412;#35486; :j_id105" style="position: absolute; display: none; z-index: 99;">
      <div id="j_id101:refDataList:#26412;#35486; :j_id105content">本語</div>
      <span id="j_id101:refDataList:#26412;#35486; :j_id105script" style="display: none;">
      <script type="text/javascript" id="scriptj_id101:refDataList:本語:j_id105">
      //<![CDATA[
      new ToolTip({'hideEvent':'','showEvent':'mouseover','hideDelay':0,'delay':500} ,{'onhide':null,'oncomplete':null,'onshow':null} ,"j_id101:refDataList:本語:j_id105","j_id101:refDataList:本語:j_id103","client",false,"bottom-right",true,10,10,"A4J.AJAX.Submit('_viewRoot','j_id101',event,ajaxOptions)",{'control':this,'oncomplete':function(request,event,data){;{var _toolTip = $('j_id101:refDataList:本語:j_id105').component;_toolTip.toolTipContent = $('j_id101:refDataList:本語:j_id105content');_toolTip.displayDiv();}},'parameters':{'j_id101:refDataList:\u672C\u8A9E :j_id105':'j_id101:refDataList:\u672C\u8A9E :j_id105','ajaxSingle':'j_id101:refDataList:\u672C\u8A9E :j_id105'} ,'onbeforedomupdate':function(request,showEvent,data){;{ var _toolTip = $('j_id101:refDataList:本語:j_id105').component;_toolTip.toolTip.style.display = 'none'; }},'actionUrl':'/trustedx-console2.1/endEntities/list.seam'} );;
      
      //]]>
      </script>
      </span>
      </div>
      


      So you can see that the (unescaped!) rowkey is actually used in the id attribute of the "script" tag and also inside the script itself. It is correctly escaped in the div and span tags...

      My environment: richfaces-3.2.2GA, SEAM 1.2.1, JSF 1.2.06, facelets, tomcat 6

      page.xhtml:
       <h:form>
       <p>
       <rich:dataTable id="refDataList"
       value="#{paginatedRefDataModel}"
       var="item"
       rows="#{constants.max_list_elems}" >
       <!-- main column -->
       <rich:column>
       <f:facet name="header">#{messages['refData.list.columnHeader.description']}</f:facet>
       <rich:toolTip
       mode="client"
       followMouse="true"
       showDelay="500"
       layout="block"
       value="#{item.id}" />
       <s:link action="edit" value="#{item.description}" >
       <f:param name="id" value="#{item.id}" />
       <f:param name="type" value="#{paginatedRefDataModel.currentType}" />
       <f:param name="listPageNumber" value="#{paginatedRefDataModel.currentPageNumber}" />
       </s:link>
       </rich:column>
       <f:facet name="footer">
       <rich:datascroller id="paginator"
       for="refDataList"
       maxPages="10"
       page="#{paginatedRefDataModel.currentPageNumber}"
       ajaxSingle="false" >
       </rich:datascroller>
       </f:facet>
       </rich:dataTable>
       </p>
       <s:div>
       <s:button action="create" value="#{messages[tx:concat2('refData.list.create.', fn:toLowerCase(paginatedRefDataModel.currentType))]}" >
       <f:param name="type" value="#{paginatedRefDataModel.currentType}" />
       </s:button>
       <a4j:commandButton id="deleteButton"
       disabled="#{!paginatedRefDataModel.somethingSelected}"
       action="#{refDataDeletionController.markDataModelSelectionForDeletion()}"
       value="#{messages['refData.list.delete']}"
       oncomplete="javascript:Richfaces.showModalPanel('deleteConfirm')"
       reRender="deleteConfirm" />
       </s:div>
      
       </h:form>
      


      backing bean "paginatedRefDataModel":

      @Name(RefDataPaginatedDataModel.COMPONENT_NAME)
      @Scope(ScopeType.PAGE)
      @AutoCreate
      public class RefDataPaginatedDataModel extends ExtendedDataModel implements Modifiable {
      
       public static final String COMPONENT_NAME = "paginatedRefDataModel";
      
       @Logger
       private static Log logger;
      
       @In(EntityManager.COMPONENT_NAME)
       private EntityManager em;
      
       /** The refData class that this data model should retrieve. Cannot be changed once set. */
       private Class<? extends RefDataI> refDataClass;
      
       /** Holds the current row key set by {@link #setRowKey(Object)}*/
       private String currentId;
      
       /** Selected checkboxes in the data table. */
       private Map<String, Boolean> selectionMap = new LinkedHashMap<String, Boolean>();
      
       /** Cache for the last retrieved first row number */
       private int currentFirstRow;
      
       /** Cache for the last retrieved number of rows */
       private int currentNumberOfRows;
      
       /** Holds the current page number. Init value 1. */
       private int currentPageNumber = 1;
      
       /**
       * Flag indicating that the cache should not be used in the next walk-action.
       * If set to <code>true</code>, it will be
       * reset to <code>false</code> automatically at the next invocation
       * of {@link #walk(FacesContext, DataVisitor, Range, Object)}.
       */
       private boolean disableCache;
      
       /** The wrapped data (keys and values)*/
       private Map<String, RefDataI> wrappedData = new LinkedHashMap<String, RefDataI>();
      
       /** The wrapped keys */
       private List<String> wrappedKeys;
      
       /** The wrapped values */
       private List<RefDataI> wrappedValues;
      
       /** Cache for the total row count */
       private Long totalRowCount;
      
       @Create
       public void init() {
       logger.debug("=================== RefDataPaginatedDataModel instantiated =======");
       }
      
       /**
       * Sets the refData class. Once set, it cannot be changed anymore.
       * (Throws IllegalArgumentException).
       *
       * @param refDataClass the refData class
       */
       private void setRefDataClass(Class<? extends RefDataI> refDataClass) {
       logger.debug("==============>>>>>> RefData set to [#0] in data model =======", refDataClass.getName());
       if (this.refDataClass == null || this.refDataClass == refDataClass)
       this.refDataClass = refDataClass;
       else {
       throw new IllegalArgumentException("Cannot change refDataClass of RefDataPaginatedDataModel.");
       }
       }
      
       /**
       * Sets the refData type (= class name without package), retrieves
       * the corresponding class and calls
       * {@link #setRefDataClass(Class)}.
       *
       * @param type the refData type (= class name without package)
       */
       public void setCurrentType(String type) {
       logger.debug("Setting currentType to [#0]", type);
       if (type != null) {
       Class<? extends RefDataI> clazz = Utils.getRefDataClassFromShortClassName(type);
       setRefDataClass(clazz);
       }
       else {
       logger.error("[#0] is no recognized list type for endEntities page", type);
       }
       }
      
       public String getCurrentType() {
       String type = null;
       if (refDataClass != null)
       type = Utils.cutPackageName(refDataClass.getName());
       return type;
       }
      
       /**
       * Delete-event listener which which sets the disableCache flag
       * and also nulls the cached totalRowCount.
       */
       @Observer(EventTypes.DELETE)
       public void onDelete(Set<Class<? extends RefDataI>> deletedRefDataClasses) {
       logger.debug("received DELETE event [#1]", deletedRefDataClasses);
       for (Class<? extends RefDataI> clazz : deletedRefDataClasses) {
       if (clazz == refDataClass) {
       logger.debug("invalidate caches");
       disableCache = true;
       totalRowCount = null;
       logger.debug("clear checkboxes");
       selectionMap.clear();
       return;
       }
       }
       logger.debug("no action - deleted refData not in this table.");
       }
      
       /**
       * Create-event listener which which sets the disableCache flag
       * and also nulls the cached totalRowCount.
       */
       @Observer(EventTypes.CREATE)
       public void onCreate(Class<? extends RefDataI> deletedRefDataClass) {
       logger.debug("received CREATE event [#1]", deletedRefDataClass);
       if (deletedRefDataClass == refDataClass) {
       logger.debug("invalidate caches");
       disableCache = true;
       totalRowCount = null;
       }
       else
       logger.debug("no action");
       }
      
       /**
       * Returns <code>true</code if any of the checkboxes
       * is selected.
       *
       * @return <code>true</code if any of the checkboxes
       * is selected, <code>false</code> if not
       */
       public boolean isSomethingSelected() {
       logger.debug("isSomethingSelected called");
       for (Entry<String, Boolean> entry : getSelection().entrySet()) {
       Boolean value = entry.getValue();
       if (value == Boolean.TRUE) {
       logger.debug("At least one checbox selected");
       return true;
       }
       }
       logger.debug("No checbox selected");
       return false;
       }
      
       /**
       * Calculates the total row count.
       *
       * @return the total row count
       */
       protected Long getCount() {
       logger.debug("==============>>>>>> getCount called =======");
       return new Long(em.find(refDataClass).size());
       }
      
       @SuppressWarnings("unchecked")
       protected List<RefDataI> getList(int firstRow, int maxResults) {
       logger.debug("==============>>>>>> getList(firstRow, maxResults) called =======");
       // the following cast is safe because the Set<RefDataI> is only used for reading
       Set<RefDataI> results = (Set<RefDataI>) em.find(refDataClass, firstRow, maxResults);
       return new ArrayList<RefDataI>(results);
       }
      
       protected RefDataI findById(String id) {
       return em.find(refDataClass, id);
       }
      
       private void wrap(FacesContext context,
       DataVisitor visitor,
       Range range,
       Object argument,
       List<RefDataI> subList) throws IOException {
       wrappedKeys = new ArrayList<String>();
       wrappedData = new LinkedHashMap<String, RefDataI>();
       for (RefDataI item : subList) {
       String id = item.getId();
       wrappedKeys.add(id);
       wrappedData.put(id, item);
       visitor.process(context, id, argument);
       }
       }
      
       private boolean hasById(String anId) {
       for (RefDataI listItem : wrappedValues) {
       String id = listItem.getId();
       if (id.equals(anId))
       return true;
       }
       return false;
       }
      
       // ------------ methods overriding ExtendedDataModel -------
       /**
       * This is main part of Visitor pattern.
       * Method called by framework many times during request processing.
       */
       @Override
       public void walk(FacesContext context,
       DataVisitor visitor,
       Range range,
       Object argument) throws IOException {
       int firstRow = ((SequenceRange) range).getFirstRow();
       int numberOfRows = ((SequenceRange) range).getRows();
       logger.debug("walk from row [#0] #1 rows", firstRow, numberOfRows);
       boolean cached = firstRow == currentFirstRow && numberOfRows <= currentNumberOfRows;
       currentFirstRow = firstRow;
       currentNumberOfRows = numberOfRows;
       if (cached && !disableCache) {
       logger.debug("use cached list");
       for (String key : wrappedData.keySet()) {
       setRowKey(key);
       visitor.process(context, key, argument);
       }
       }
       else {
       logger.debug("retrieving fresh data...");
       if (disableCache)
       disableCache = false;
       List<RefDataI> subList = getList(firstRow, numberOfRows);
       wrappedValues = subList;
       wrap(context, visitor, range, argument, subList);
       }
       }
      
       /**
       * This method is normally called by
       * the visitor before request Data Row.
       */
       @Override
       public void setRowKey(Object key) {
       this.currentId = (String) key;
       }
      
       /**
       * This method is never called from framework.
       * @see org.ajax4jsf.model.ExtendedDataModel#getRowKey()
       */
       @Override
       public Object getRowKey() {
       return currentId;
       }
      
       // ------------ getter and setters -------
       /**
       * Sets the currentPageNumber.
       *
       * @param currentPageNumber the currentPageNumber to set
       */
       public void setCurrentPageNumber(int currentPageNumber) {
       logger.debug("set current page number to [#0]", currentPageNumber);
       this.currentPageNumber = currentPageNumber;
       }
      
       /**
       * Returns the currentPageNumber.
       *
       * @return the currentPageNumber
       */
       public int getCurrentPageNumber() {
       logger.debug("get current page number: [#0]", currentPageNumber);
       return currentPageNumber;
       }
      
       /**
       * Returns the refDataClass.
       *
       * @return the refDataClass
       */
       public Class<? extends RefDataI> getRefDataClass() {
       return refDataClass;
       }
      
       /**
       * Returns the selectionMap for the checkboxes in the table rows.
       *
       * @return the selectionMap
       */
       public Map<String, Boolean> getSelection() {
       logger.debug("getSelection called - return [#0]", selectionMap);
       return selectionMap;
       }
      
       /**
       * Sets the selectionMap for the checkboxes in the table rows.
       *
       * @param selectionMap the selectionMap to set
       */
       public void setSelection(Map<String, Boolean> selectionMap) {
       logger.debug("setSelection called [#0]", selectionMap);
       this.selectionMap = selectionMap;
       }
      
      
       // ------------ methods inherited from javax.faces.modelDataModel -------
       /**
       * From javax.faces.model.DataModel:<br>
       * Return the number of rows of data objects
       * represented by this DataModel.
       *
       * @return
       */
       @Override
       public int getRowCount() {
       if (totalRowCount == null) {
       totalRowCount = getCount();
       }
       return totalRowCount.intValue();
       }
      
       /**
       * This is main way to obtain data row. It is intensively used by framework.
       * We strongly recommend use of local cache in that method.
       * From javax.faces.model.DataModel:<br>
       * Return an object representing the data for the currenty selected row index.
       */
       @Override
       public Object getRowData() {
       if (currentId == null) {
       return null;
       }
       else {
       RefDataI ret = wrappedData.get(currentId);
       if (ret == null) {
       ret = findById(currentId);
       wrappedData.put(currentId, ret);
       }
       return ret;
       }
       }
      
       /**
       * From javax.faces.model.DataModel:<br>
       * Return a flag indicating whether there is rowData
       * available at the current rowIndex.
       */
       @Override
       public boolean isRowAvailable() {
       if (currentId == null)
       return false;
       else
       return hasById(currentId);
       }
      
       /**
       * (Unused rudiment from old JSF staff.)
       *
       * From javax.faces.model.DataModel:<br>
       * Return the zero-relative index of the currently selected row.
       */
       @Override
       public int getRowIndex() {
       return 0;
      // throw new UnsupportedOperationException();
       }
      
       /**
       * Unused rudiment from old JSF staff.
       *
       * From javax.faces.model.DataModel:<br>
       * Return the object representing the data
       * wrapped by this DataModel, if any.
       */
       @Override
       public Object getWrappedData() {
       return this;
      // throw new UnsupportedOperationException();
       }
      
       /**
       * Unused rudiment from old JSF staff.
       *
       * From javax.faces.model.DataModel:<br>
       * Set the zero-relative index of the currently selected row,
       * or -1 to indicate that we are not positioned on a row.
       */
       @Override
       public void setRowIndex(int rowIndex) {
      // throw new UnsupportedOperationException();
       }
      
       /**
       * Unused rudiment from old JSF staff.
       *
       * From javax.faces.model.DataModel:<br>
       * Set the object representing the data collection wrapped by this DataModel.
       */
       @Override
       public void setWrappedData(Object data) {
      // throw new UnsupportedOperationException();
       }
      }
      


      Anybody an idea how I can get around this? Are the richfaces guys aware of this problem?
      Thanks for feedback!.