ICEfaces
  1. ICEfaces
  2. ICE-6352

Changes to Component inner hierarchy does not always update javascript

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 2.0-Beta2
    • Fix Version/s: 2.0.0
    • Component/s: ACE-Components
    • Labels:
      None
    • Environment:
      ICEFaces ACE components

      Description

      I've noticed this issue on the LinkButton, but it should manifest in all ACE components under the right circumstances.

      The HTML structure built by the renderer encapsulating a LinkButton looks like this:

      <div>
        <span id="xxx_span" >
           <span class="first-child">
               <a> A child node </a>
           </span>
        </span>
        <span id="xxx_script">
          <script> Yadda </script>
        </span>
      </div>

      The problem occurs when there is some behaviour that changes the markup in the span with the id "xxx_span", "first-child", or the anchor tag. The reason is that our dom-diffing algorithm finds the first parent element with an id and applies an update to that element. This excludes the javascript span and the <script> element is not reevaluated by the client.

      In the case of LinkButton, my test page changes the style string on one of the inner spans of the anchor hierarchy. It's necessary to change the style on this level or it wont override the default Sam or Rime styling, and this sends an update of just the inner <span> tags which causes the YUI component that was created on the initial update to be replaced with simple html since the initialize method in the javascript is not re-executed. This manifests most visibly when the linkButton value attribute is replaced on the link with the normal html value of: "A child node", in the above case.

      The component renderer is going to need to keep track of elements and attributes which are on the innermost elements of a nested component structure, and if they change, going to have to write something in the javascript layer to get it to be updated as well, so the entire structure can be re-bound to a YUI component.

      As a first pass we might be able to pass these elements into jsfProps directly, ensuring the javascript changes as well. If this gets to be too large, a concept similar to the JSONBuilder class might be used to accumulate all the string changes into one long string which then we take a hash of to make it a reasonable size.

         
      1. after.JPG
        53 kB
      2. before.JPG
        57 kB

        Issue Links

          Activity

          Hide
          Greg Dick added a comment -

          Screenshots showing before and after clicking on the linkButton

          Show
          Greg Dick added a comment - Screenshots showing before and after clicking on the linkButton
          Hide
          Greg Dick added a comment -

          The test consists of a table with 5 linkButtons in it, each of which maintain their inline style in a row specific manner. When the link is clicked, the inline style is changed. The inline style is changed on the span which has the id xxx_span. This is necessary to get the style to not be overridden by other style rules.

          The update sent in this case is:

          <?xml version='1.0' encoding='UTF-8'?>
          <partial-response>
          <changes>
          <update id="usingTheValueExpressionForm:calTable:0:_t9_span"><![CDATA[<span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: line-through;"><span class="first-child"><a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"></a>

          </span></span>]]>
          </update>
          <update id="javax.faces.ViewState"><![CDATA[1082959432022557316:2376052174934832377]]></update>

          </changes>
          </partial-response>

          Through alerts and logging, I see that the initialize function in javascript is not called on this component again, and I think that means the YUI elements are removed from the page and the above <span> structure replaces it. As can be seen from the screenshot above, the label on the link button is replaced with an empty string, and this is in keeping with the thought that the bound YUI component is replaced with the HTML equivalent of <span> <span><a>*</a></span></span>

          Where * represents an empty string.

          Adding the style to the javascript itself in an effort to cause the javascript to be reexecuted also fails. The update sent to the client consists of:

          <?xml version='1.0' encoding='UTF-8'?>
          <partial-response>
          <changes>
          <update id="usingTheValueExpressionForm:calTable:0:_t9_span"><![CDATA[<span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: underline;"><span class="first-child">
          <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"></a></span></span>]]>
          </update>
          <update id="_t9_script"><![CDATA[<span id="_t9_script"><script>ice.component.linkButton.updateProperties('usingTheValueExpressionForm:calTable:0:_t9',

          {"type":"link","tabindex":0,"label":"ValueExpression style","disabled":false}

          ,

          {"singleSubmit":false,"doAction":true,"ariaEnabled":true,"style":"text-decoration: underline;"}

          );
          </script>
          </span>]]></update>
          <update id="javax.faces.ViewState"><![CDATA[-1962529559664138106:2692136651730273617]]></update>
          </changes></partial-response>

          In this case, the update does include the sibling script span, but the initialize method again fails to execute, with similar results.

          Show
          Greg Dick added a comment - The test consists of a table with 5 linkButtons in it, each of which maintain their inline style in a row specific manner. When the link is clicked, the inline style is changed. The inline style is changed on the span which has the id xxx_span. This is necessary to get the style to not be overridden by other style rules. The update sent in this case is: <?xml version='1.0' encoding='UTF-8'?> <partial-response> <changes> <update id="usingTheValueExpressionForm:calTable:0:_t9_span"><![CDATA[<span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: line-through;"><span class="first-child"><a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"></a> </span></span>]]> </update> <update id="javax.faces.ViewState"><![CDATA [1082959432022557316:2376052174934832377] ]></update> </changes> </partial-response> Through alerts and logging, I see that the initialize function in javascript is not called on this component again, and I think that means the YUI elements are removed from the page and the above <span> structure replaces it. As can be seen from the screenshot above, the label on the link button is replaced with an empty string, and this is in keeping with the thought that the bound YUI component is replaced with the HTML equivalent of <span> <span><a>*</a></span></span> Where * represents an empty string. Adding the style to the javascript itself in an effort to cause the javascript to be reexecuted also fails. The update sent to the client consists of: <?xml version='1.0' encoding='UTF-8'?> <partial-response> <changes> <update id="usingTheValueExpressionForm:calTable:0:_t9_span"><![CDATA[<span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: underline;"><span class="first-child"> <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"></a></span></span>]]> </update> <update id="_t9_script"><![CDATA[<span id="_t9_script"><script>ice.component.linkButton.updateProperties('usingTheValueExpressionForm:calTable:0:_t9', {"type":"link","tabindex":0,"label":"ValueExpression style","disabled":false} , {"singleSubmit":false,"doAction":true,"ariaEnabled":true,"style":"text-decoration: underline;"} ); </script> </span>]]></update> <update id="javax.faces.ViewState"><![CDATA [-1962529559664138106:2692136651730273617] ]></update> </changes></partial-response> In this case, the update does include the sibling script span, but the initialize method again fails to execute, with similar results.
          Hide
          Ted Goddard added a comment -

          Initial page source:

          <div class="ice-linkbutton" id="usingTheValueExpressionForm:calTable:0:_t9">
          <span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: underline;">
          <span class="first-child">
          <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"/>
          </span>
          </span>
          <span id="_t9_script">
          <script>ice.component.linkButton.updateProperties('usingTheValueExpressionForm:calTable:0:_t9',

          {"type":"link","tabindex":0,"label":"ValueExpression style","disabled":false}

          ,

          {"singleSubmit":false,"doAction":true,"ariaEnabled":true}

          );</script>
          </span>
          </div>

          After JavaScript initialization:

          <div class="ice-linkbutton" id="usingTheValueExpressionForm:calTable:0:_t9">
          <span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: blink;">
          <span class="first-child" role="link" aria-labelledby="ValueExpression style" aria-describedby="JSF action event source">
          <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" tabindex="0" id="usingTheValueExpressionForm:calTable:0:_t9_span-button">ValueExpression style</a>
          </span>
          </span>
          <span id="_t9_script">
          <script>ice.component.linkButton.updateProperties('usingTheValueExpressionForm:calTable:0:_t9',

          {"type":"link","tabindex":0,"label":"ValueExpression style","disabled":false}

          ,

          {"singleSubmit":false,"doAction":true,"ariaEnabled":true}

          );</script>
          </span>
          </div>

          Updates from clicking on the button:

          <partial-response>
          <changes>
          <update id="usingTheValueExpressionForm:calTable:0:_t9_span"><![CDATA[
          <span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: underline;">
          <span class="first-child">
          <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"/>
          </span>
          </span>
          ]]></update>
          <update id="javax.faces.ViewState"><![CDATA[8843399075223559913:-5980998028922626448]]></update>
          </changes>
          </partial-response>

          Resulting in:

          <div class="ice-linkbutton" id="usingTheValueExpressionForm:calTable:0:_t9">
          <span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: blink;">
          <span class="first-child">
          <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"/>
          </span>
          </span>
          <span id="_t9_script">
          <script>ice.component.linkButton.updateProperties('usingTheValueExpressionForm:calTable:0:_t9',

          {"type":"link","tabindex":0,"label":"ValueExpression style","disabled":false}

          ,

          {"singleSubmit":false,"doAction":true,"ariaEnabled":true}

          );</script>
          </span>
          </div>

          Show
          Ted Goddard added a comment - Initial page source: <div class="ice-linkbutton" id="usingTheValueExpressionForm:calTable:0:_t9"> <span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: underline;"> <span class="first-child"> <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"/> </span> </span> <span id="_t9_script"> <script>ice.component.linkButton.updateProperties('usingTheValueExpressionForm:calTable:0:_t9', {"type":"link","tabindex":0,"label":"ValueExpression style","disabled":false} , {"singleSubmit":false,"doAction":true,"ariaEnabled":true} );</script> </span> </div> After JavaScript initialization: <div class="ice-linkbutton" id="usingTheValueExpressionForm:calTable:0:_t9"> <span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: blink;"> <span class="first-child" role="link" aria-labelledby="ValueExpression style" aria-describedby="JSF action event source"> <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" tabindex="0" id="usingTheValueExpressionForm:calTable:0:_t9_span-button">ValueExpression style</a> </span> </span> <span id="_t9_script"> <script>ice.component.linkButton.updateProperties('usingTheValueExpressionForm:calTable:0:_t9', {"type":"link","tabindex":0,"label":"ValueExpression style","disabled":false} , {"singleSubmit":false,"doAction":true,"ariaEnabled":true} );</script> </span> </div> Updates from clicking on the button: <partial-response> <changes> <update id="usingTheValueExpressionForm:calTable:0:_t9_span"><![CDATA[ <span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: underline;"> <span class="first-child"> <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"/> </span> </span> ]]></update> <update id="javax.faces.ViewState"><![CDATA [8843399075223559913:-5980998028922626448] ]></update> </changes> </partial-response> Resulting in: <div class="ice-linkbutton" id="usingTheValueExpressionForm:calTable:0:_t9"> <span class="yui-button yui-link-button" id="usingTheValueExpressionForm:calTable:0:_t9_span" style="text-decoration: blink;"> <span class="first-child"> <a onclick="return ice.component.linkButton.clickHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );" onkeydown="return ice.component.linkButton.keyDownHandler(event, 'usingTheValueExpressionForm:calTable:0:_t9' );"/> </span> </span> <span id="_t9_script"> <script>ice.component.linkButton.updateProperties('usingTheValueExpressionForm:calTable:0:_t9', {"type":"link","tabindex":0,"label":"ValueExpression style","disabled":false} , {"singleSubmit":false,"doAction":true,"ariaEnabled":true} );</script> </span> </div>
          Hide
          Ted Goddard added a comment -

          In this particular case, we can see that updateProperties is not called – only the span is replaced with initial values.

          In terms of difference from the initial page source, it appears that YUI is doing the following:

          changing style "underline" to "blink"
          adding role="link" aria-labelledby="ValueExpression style" aria-describedby="JSF action event source" to first-child span
          adding tabindex="0" id="usingTheValueExpressionForm:calTable:0:_t9_span-button">ValueExpression style</a> to the anchor

          Show
          Ted Goddard added a comment - In this particular case, we can see that updateProperties is not called – only the span is replaced with initial values. In terms of difference from the initial page source, it appears that YUI is doing the following: changing style "underline" to "blink" adding role="link" aria-labelledby="ValueExpression style" aria-describedby="JSF action event source" to first-child span adding tabindex="0" id="usingTheValueExpressionForm:calTable:0:_t9_span-button">ValueExpression style</a> to the anchor
          Hide
          Ted Goddard added a comment -

          "underline" to "blink" above appears to be from capturing the output in different stages of the application. The root cause of the problem is the anchor tag being initialized to an empty tag.

          Show
          Ted Goddard added a comment - "underline" to "blink" above appears to be from capturing the output in different stages of the application. The root cause of the problem is the anchor tag being initialized to an empty tag.
          Hide
          Ken Fyten added a comment -

          FYI, #3 in http://jira.icefaces.org/browse/ICE-5833 seems to overlap this issue somewhat.

          Show
          Ken Fyten added a comment - FYI, #3 in http://jira.icefaces.org/browse/ICE-5833 seems to overlap this issue somewhat.
          Hide
          Greg Dick added a comment -

          The general approach taken follows the points below:

          1) Can this property be changed and should we expect the change to be reflected dynamically?
          An example of this is the linkButton tables in /ace-test/UIDataTest.jsf where the link has an action listener that changes the
          inline style attribute of the link. The style is defined on one of the spans making up the link and not the outer div containing the structure and the script. Hence, only the inner spans are included in an update.

          2) Are these properties tied to the HTML that YUI uses as markers for inserting its own elements as opposed
          to those properties that are set on the YUI component via jsProps or consumed privately via jsfProps? If so, changing
          them will cause a DOM update to those HTML elements which will cause them to be replaced in the DOM in the client
          which will replace the YUI modified elements with our updated, but plain elements.

          3) A hashCode of these missing properties should be constructed and inserted into the jsfProps hashmap. For the linkbutton, the following properties are required:

          StringBuilder sb = new StringBuilder();
          sb.append( linkButton.getValue() ).
          append(linkButton.getHref()).
          append(linkButton.getHrefLang()).
          append(linkButton.getStyleClass()).
          append(linkButton.getStyle()).
          append(linkButton.getTarget());

          Then insert the hashCode into the map:

          JSONBuilder jBuild = JSONBuilder.create().
          beginMap().
          entry("singleSubmit", linkButton.isSingleSubmit()).
          entry("doAction", doAction).
          entry("hashCode", sb.toString().hashCode()). <-------------- here
          entry("ariaEnabled", EnvUtils.isAriaEnabled(facesContext));

          Then change the updateProperties method in the component's javascript file so that if the hashCode changes, the context is released, which will cause initialize() to be called. This change is common to all components and is shown here.

          updateProperties:function(clientId, jsProps, jsfProps, events) {

          var context = ice.component.getJSContext(clientId);
          if (context && context.isAttached()) {
          var prevJSFProps = context.getJSFProps();
          if (prevJSFProps.hashCode != jsfProps.hashCode)

          { context.getComponent().destroy(); document.getElementById(clientId)['JSContext'] = null; JSContext[clientId] = null; }

          }
          ice.component.updateProperties(clientId, jsProps, jsfProps, events, this);
          },

          Since this is common to all components, when time permits, we can modify the code in component.js to do this.

          For progress, I have:

          LinkButton supports the above dynamic properties.

          CheckBox supports dynamic style and styleClass.

          slider supports dynamic style and styleClass

          TabSet doesn't require changes since the style and styleClass attributes are written to the outer div which contains the script. Changing these properties reinitializes the component completely.

          Changes have been made to pushButton, but for some reason, the updateProperties script doesn't run when the component is modified in this way.

          Show
          Greg Dick added a comment - The general approach taken follows the points below: 1) Can this property be changed and should we expect the change to be reflected dynamically? An example of this is the linkButton tables in /ace-test/UIDataTest.jsf where the link has an action listener that changes the inline style attribute of the link. The style is defined on one of the spans making up the link and not the outer div containing the structure and the script. Hence, only the inner spans are included in an update. 2) Are these properties tied to the HTML that YUI uses as markers for inserting its own elements as opposed to those properties that are set on the YUI component via jsProps or consumed privately via jsfProps? If so, changing them will cause a DOM update to those HTML elements which will cause them to be replaced in the DOM in the client which will replace the YUI modified elements with our updated, but plain elements. 3) A hashCode of these missing properties should be constructed and inserted into the jsfProps hashmap. For the linkbutton, the following properties are required: StringBuilder sb = new StringBuilder(); sb.append( linkButton.getValue() ). append(linkButton.getHref()). append(linkButton.getHrefLang()). append(linkButton.getStyleClass()). append(linkButton.getStyle()). append(linkButton.getTarget()); Then insert the hashCode into the map: JSONBuilder jBuild = JSONBuilder.create(). beginMap(). entry("singleSubmit", linkButton.isSingleSubmit()). entry("doAction", doAction). entry("hashCode", sb.toString().hashCode()). <-------------- here entry("ariaEnabled", EnvUtils.isAriaEnabled(facesContext)); Then change the updateProperties method in the component's javascript file so that if the hashCode changes, the context is released, which will cause initialize() to be called. This change is common to all components and is shown here. updateProperties:function(clientId, jsProps, jsfProps, events) { var context = ice.component.getJSContext(clientId); if (context && context.isAttached()) { var prevJSFProps = context.getJSFProps(); if (prevJSFProps.hashCode != jsfProps.hashCode) { context.getComponent().destroy(); document.getElementById(clientId)['JSContext'] = null; JSContext[clientId] = null; } } ice.component.updateProperties(clientId, jsProps, jsfProps, events, this); }, Since this is common to all components, when time permits, we can modify the code in component.js to do this. For progress, I have: LinkButton supports the above dynamic properties. CheckBox supports dynamic style and styleClass. slider supports dynamic style and styleClass TabSet doesn't require changes since the style and styleClass attributes are written to the outer div which contains the script. Changing these properties reinitializes the component completely. Changes have been made to pushButton, but for some reason, the updateProperties script doesn't run when the component is modified in this way.
          Hide
          Greg Dick added a comment -

          All the components handle this issue with the exception of pushButton, which is indicated in ICE-6418

          Show
          Greg Dick added a comment - All the components handle this issue with the exception of pushButton, which is indicated in ICE-6418

            People

            • Assignee:
              Greg Dick
              Reporter:
              Greg Dick
            • Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: