Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    Urgent. Timing issue: blur nullifies the field value

    Hi,

    I need help to find a solution for the issue: “blur” event happens after the value is set to the field but before it is set to the native field element, so the updateValue() method nullifies the field value.
    Mostly it happens with date and text fields. We’ve observed it in Firefox (more frequently) but in IE as well.

    The scenario is the following:

    After some user action, we need to update a part of the page: for example, create and show new DynamicForm with different fields in an existing Layout.
    We have "autoDraw" set to false, so we call show() for the form. After calling show(), we initialize fields with values by calling field.setValue(..). Note, that if call field.getValue() right after that, it returns the correct value.

    Let say, first field of the form is a date field and it automatically (or sometimes randomly) receives the focus after the form is drawn.

    Then we have a script running on a small timeout to reset tab indexes for all elements on the page. We have to do it every time when a part of the page is changed since the default tab index is set according to the drawing order. This script goes through all Canvas elements and its children and calls setTabIndex() for all focusable items.

    Resetting tab index causes focus movement. Sometimes blur happens for the date field BEFORE the native element has the actual field value. Blur handler calls updateValue() that pulls the current native element value (which is still null) and nullifies the field value.

    I set a conditional breakpoint at updateValue() in browser and I saw that at the moment of the blur event the field value is actually correct (“_value” member) and the form and fields are displayed and drawn on the page, but don’t have any values displayed yet.

    Sorry, I was unable to reproduce it with an example in your feature explorer or standalone. Since it seems to be a timing issue, it is tough to provide an isolated example.

    So, my questions are:

    1) How we can force to propagate the field value to the native element of the text or date field? The paradox is that field is drawn already, and even has the focus, but do not have field value set for some reason.

    2) Maybe there is a safer way to reset tab indexes on the page, without moving focus around? But I guess, this is just one case, blur also may happen due to other page changes or redrawing.

    Increasing a timeout for the script helps a bit but does not solve the issue; even with 500ms timeout it still happens, although less frequently.

    3) How can we prevent from calling blur() on the element that is not fully initialized yet? For example, can we “deactivate” somehow current focus item before resetting indexes?

    -----
    SmartClient : SC_SNAPSHOT-2011-02-10 (2011-02-10)
    GWT: 2.1.0
    We DO NOT use Smart GWT.
    Browsers: IE 7&8, Firefox 3.6

    #2
    To begin with, accessing the native element to get the value is definitely not something SmartClient guarantees should work, in fact, quite the reverse: the logical value of the formItem is sometimes something the native element cannot represent (such as the value being null or a Date instance when the native element is a text input). So, unfortunately you basically need to abandon your approach of accessing the native element directly. getValue() is the supported API.

    You also, unfortunately, need to abandon your approach of resetting tab indices everywhere via native DOM access. It's not clear why you started down this path, since SmartClient actually automatically manages this, changing the order of tabbing as new widgets are added or visual order is changed. But regardless, the set of elements that are "focusable" from the DOM perspective is not the same as the set of elements SmartClient regards as focusable, and various tricks have to be employed on various browsers to make things work.

    Comment


      #3
      Originally posted by Isomorphic
      To begin with, accessing the native element to get the value is definitely not something SmartClient guarantees should work, in fact, quite the reverse: the logical value of the formItem is sometimes something the native element cannot represent (such as the value being null or a Date instance when the native element is a text input). So, unfortunately you basically need to abandon your approach of accessing the native element directly. getValue() is the supported API.
      Dear Isomorphic,
      We never access native DOM elements and ALWAYS use getValue() and setValue() methods.
      It's YOUR code (updateValue() method that is called on YOUR blur handler of the native element) that accesses it and takes the empty value from the native element before the actual field value is set to it.

      I had to debug your obfuscated JS code to understand why the field does not have value that was set to it by you API method setValue().

      ISC_Forms.js:1263
      Code:
      ...
      ,isc.A.updateValue=function isc_FormItem_updateValue(){if(!this.hasElement()||this.getDataElement()==null)return;var _1=this.getElementValue();return this.$10y(_1)}
      ..
      At the moment of the blur event, the actual field value is NOT empty (this.getValue() returns correct value), but the native element does not have actual value yet and it DISPLAYS NOTHING, so this.getElementValue() returns null.

      Sometimes you can even see a visible delay between the moment the field is drawn and the moment it displays the value.

      Originally posted by Isomorphic
      You also, unfortunately, need to abandon your approach of resetting tab indices everywhere via native DOM access.
      We also use your API method Canvas.setTabIndex() and NEVER access native DOM elements. We reset tab index only for Canvases "focusable" from your perspective.

      Originally posted by Isomorphic
      It's not clear why you started down this path, since SmartClient actually automatically manages this, changing the order of tabbing as new widgets are added or visual order is changed.
      From your Reference, "tabIndex" property of Canvas:

      "Note that by default SmartClient auto-assigns tab-indices, ensuring focusable widgets are reachable by tabbing in the order IN WHICH THEY ARE DRAWN ON THE PAGE."

      Our pages are built dynamically and sometimes page parts (new objects like dynamic forms, layouts, tabsets, etc) are loaded, created and drawn asynchronously.
      Unfortunately, we have situations when the tab index of items on the page after changes is not naturally straightforward, sometimes completely unpredictable.
      So we HAD TO reset tab index to FIX the tab order. And the tabbing is working fine after that (as expected), this is not I am complaining about.

      My actual problem is the BLUR EVENT that happens on focus movement during Canvas.setTabIndex() call.

      Please re-read my first post.

      Comment


        #4
        Glad to hear you are not directly accessing DOM elements, however, the advice ends up the same..

        The docs for setTabIndex() are correct - the problem is that you're assuming the word "drawn" means literally the method draw() when what is being referred to is visual order. Again, as can be seen in many samples, SmartClient automatically keeps the tabIndex in sync with the visual order.

        Globally reassigning all tab indexes periodically just isn't a viable strategy. The fact that one component reacts badly to a randomly timed blur() is the tip of a very large iceberg, including edge cases where older IE will hard crash, as well as components that (correctly) disappear, save changes, or otherwise take permanent action when focus moves elsewhere.

        So, if there are cases where the automatic tabIndex management isn't working for you, let's examine those specific cases.

        Comment


          #5
          1. This blur may happen anytime, not only when resetting tab index, this is just one case. We saw that focus moves on item redrawing (display or clear field errors for example) or other cases.

          This focus movement can happen asynchronously, while the value is still being set to the native element.

          I still think it's a hidden problem.

          2. I do not think it's possible for us to find what exactly brakes the order.

          We are not working with ready pages, we provide a framework.
          All our pages are build dynamically according to some metadata objects developed by other developers (they are not working with SC objects directly).

          Sometimes page consists of hundreds Canvas object (I am not kidding). Layers of nested layouts can be more then 10.

          I would say that default tabbing working in most of the cases, but not in 100% cases.

          Any page layout can be "specific" and break tab order, especially if the page has several tables (which have focusable body), tabsets and menu buttons.

          As an example, I attached a screenshot with such page that is rendered and generated dynamically.

          Tab order is fine on first 2 tabs of displaying tabset, but on the 3rd and 4th tab it's broken:

          -> "Preferences" group of fields gets focus first,
          -> then "Next: Order Sumurry" button,
          -> then right side group with Billing and Contact...Order Summary expandable layouts (headers are actually focusable MenuButtons),
          -> and only then "Calling Cards" group of fields, just because it was created and drawn after others.

          Note, that tabbing within the groups works fine, although each of them consist of several dynamic forms and layouts. But tab order is broken for parent Layout objects.

          Why your tab index managing did not work in this case, how can I figure it out?

          I need to know, how your tab index managing works, when exactly Canvas gets its auto tab index, what could possibly went wrong?

          Please provide tips that can help avoid such situations.
          Attached Files

          Comment


            #6
            1. Sorry, allowing a programmatically triggered blur to happen at totally arbitrary times is not supportable, for the reasons previously given. If you find a supported usage where you can get this to happen, that would be a real bug.

            2. Those stats put your application merely in the middle of the range of SmartClient application sizes (yes, seriously). We're sorry that you've built your code in such a way that you've made it hard to troubleshoot, but, that still doesn't enable us to troubleshoot a tabbing order problem from a screenshot :)

            To explain the tab order management, at a high level:

            1. V & HLayouts set tab order to visual display order, recursively. If members are reordered the tabbing order is automatically updated.

            2. Forms set tab order to item order and recurse into CanvasItems

            3. Modal dialogs cause a closed-loop of tabbing to be used (including multi-level modality)

            So for your tabbing problems:

            1. you may be able to fix any localized tabbing problem by an explicit call to focusInItem() or focus(). It's just whole-browser tabIndex updates at random intervals that's an issue

            2. for anything you think is a bug, we will need a test case. Sorry, we don't have a way to provide a shortcut to this. Normally, these are pretty straightforward to prepare.

            Comment


              #7
              I did not expect you to troubleshoot by the screenshot, this was to prove that your auto allocating tab indexes is not working sometimes even for the "middle size" page.

              I know you never admit it's your problem until you have an exact test case.
              The problem is that it can take days to reproduce the exact situation. I've already spent much time trying to reproduce it with simpler example.
              I will keep trying.

              All I can see that this is happening because of different sequence of adding children to Layouts.

              Comment


                #8
                The tabIndex management works fine for much larger applications. It's not the size, it's some specific interaction.

                It's not a matter of "admitting" something - there is nothing that we can do for you right now, at all. We have no way of reproducing a problem. We're just pointing you to next steps.

                As far as test case prep - it should really take no more than 2-3 hours, tops, to extract a test case from a larger application. You have the reproducible case and you whittle off pieces until something makes the problem go away. In the Debugging chapter in the Reference, there are some instructions which list some shortcuts you may not have thought of.

                Comment


                  #9
                  I found out what's wrong. Your tab index managing works the following way:

                  1. When creating a new object, it allocates some default global tab index, so unparented object gets the last tab index.

                  2. When adding this object to a layout, it gives the object "local" tab index and recalculates tab indexes of siblings, shifting them forward.
                  But it does it ONLY (!) if the parent layout is drawn.

                  3. In normal case indexes are getting recalculated when the parent layout gets drawn.

                  What's happening in our case:

                  Let's say we have
                  Code:
                  Top Layout (drawn)
                     - Layout A (drawn)
                     - Layout B (drawn)
                     - Layout C (drawn)
                  We need to insert new content into Layouts A and B, but those contents are getting loaded and inserted asynchronously.

                  In some cases, Layout B content gets loaded first and inserts its content:
                  first "Layout B-B", then "Dynamic Form B"
                  Code:
                  Top Layout (drawn)
                     - Layout A (drawn)
                         - still loading...
                     - Layout B (drawn)
                         - layout B-B (undrawn, but gets right local index)
                            - Dynamic form B (undrawn, still has global index)
                     - Layout C (drawn)
                  At the same time other "thread" inserts new content into layout A (after the B content has been already inserted but before it's drawn):
                  Code:
                  Top Layout (drawn)
                     - Layout A (drawn)
                         - layout A-A (undrawn, but gets right local index)
                            - Dynamic form A (undrawn, still has global index)
                     - Layout B (drawn)
                         - ...
                     - Layout C (drawn)
                  Then comes "draw Layout B" timeout action and starts recalculating indexes for "Layout B-B" and its children. To get start index it gets previous tab object, which is "Dynamic form A" that still has wrong uncalculated index.

                  So the tabbing sequence gets broken: we get "Layout A" -> "Layout C" -> and only then (after all objects on the page) "Layout B".

                  The solution is simple: force to calculate children tab indexes after a new member is added to the Layout.

                  Code:
                      parent.addMember(child);
                      if (!parent.isDrawn()) {
                        try {
                          parent.updateMemberTabIndex(child);
                        }
                        catch (err) {}
                      }
                  Because it's a timing issue, it's hard to reproduce it with a standalone sample.

                  At least, thank you for convincing me that it should work. :)
                  Last edited by ESherbakova; 25 Apr 2011, 06:58.

                  Comment


                    #10
                    Do you mean anything special by "content" in this circumstance - HTML content as opposed to widgets?

                    How are you adding Layout A-A to Layout A? In addMember() there is already the line:

                    Code:
                    if (this.isDrawn()) this.updateMemberTabIndex(newMember);
                    Are you bypassing this somehow?

                    Comment


                      #11
                      1. "Content" means any content - layouts, dynamic forms, etc.
                      We do not use Smart GWT, only pure JS API, page is built dynamically. We actually load the JS script to create the "content" objects, and then run the script, create child object and add it to the container layout with addMember() method.

                      2. You did not read carefully my post.
                      Yes, exactly, it is bypassing it, because the parent layout might not been drawn yet, so if (this.isDrawn()) fails.

                      My solution is to call the "updateMemberTabIndex" method ALWAYS, no matter if the parent is drawn it or not.

                      So, if it IS drawn, it will be called inside addMember(), if NOT drawn, my code calls it manually.

                      I guess (not 100% sure), when adding a new member, it just marks the new member for redraw, but does not draw it in the same call stack, that's why there is a gap between adding and drawing, which allows other "thread" to intrude and insert other "content" above.

                      For ex, I noticed that layout's "reflow" happens on timeout.

                      Comment


                        #12
                        Please re-read your post: you are claiming there is an issue adding a member to an already-drawn layout and not having it's tabIndex updated. However, due to the code we pasted, the tab index *is* automatically updated when a member is added to a drawn layout. If it wasn't clear, that code is from addMember - "this" is the Layout.

                        If you disagree, it should be trivial to demonstrate the bug you're claiming.

                        Comment


                          #13
                          First, it adds a new layout "B-B" to already drawn layout "B".
                          Then it adds another object "dynamic form B" to the not-drawn layout "B-B".

                          I am not claiming anything right now, my problem is solved.
                          Once I call "updateMemberTabIndex" for the "undrawn" case, it magically fix all our tab index issues.

                          I am just trying to point you to the case when it's not working and explain what happening.

                          Comment


                            #14
                            So, just to confirm, the issue arises when:

                            1. you create and draw a Layout

                            2. you add a second Layout to it as a member

                            3. you immediately add some other component (eg a DynamicForm) to the second layout, during the delay before it's been drawn

                            If you can confirm this is the situation, we'll check into it. However, note that building things in this order is basically the least efficient possible. The best approach is to build the hierarchy from the bottom, and only draw at the end. This ensures a minimum of unnecessary layout calculations, tab index reassignments, etc on a hierarchy of components that isn't in it's final form yet.

                            You may not be able to do this perfectly on all screens, but it seems like you should be able to reverse steps 2 and 3 above, which would help.

                            Comment


                              #15
                              I described just an abstract case, a possible scenario. If it was that simple, I would give you a sample right away.
                              In our case it is much more complicated than this.

                              Thank you for suggestions, but we cannot change our logic of page loading.
                              Our pages are loaded and built step by step from parent to children as a puzzle. Some "pieces" of page are created and added to the parent at once. But some layouts have dynamic content that is loaded as a separate script on timer, that content can include other dynamic content, and so on (we cannot know how many layers in advance).

                              Technically it is possible to load all pieces first, and only then add to the parents inversely. But then we have 2 new issues.
                              1) Visual time (delay) of appearing new controls on the page gets bigger. Can take seconds in worst case.
                              2) Sometimes we got "Long running script" message in IE coming from
                              "draw"... call stack when it tries to draw too many things at once.

                              In fact we use "loading depth" parameter set to 4. And it works fine in most of cases.

                              -----

                              What I did to locate the issue:

                              I found the layout that has broken tab index on the page (and all its children respectively) and set a conditional breakpoint at the setTabIndex() method to see how "tabIndex" field is getting updated for that layout.

                              First it stopped at the "create".. call stack and I saw it gives it some default index.
                              Second time it stops at "addMember".. and recalculates tab index to the correct one (because this particular layout it being added to the DRAWN layout).

                              But then, third time when it stops, I saw it's coming from layout's
                              "reflow"...
                              "isc_Layout__setupMembers"...
                              "isc_Canvas__slotTabBetween"...

                              and when its get previous tab widget for the layout, that "previous widget" has wrong uncalculated "deafult" index because it was loaded and added later to the undrawn parent.

                              So then I tried to call "updateMemberTabIndex" for "undrawn" parent right after "addMember" and it worked. And I am totally happy with my solution.

                              It is up to you what you are going to do with it.

                              Comment

                              Working...
                              X