Announcement

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

    setItems() generating "Cannot read property 'length' of undefined"error...

    ... when invoked more than once from a ChangeHandler

    [SmartClient Version: v11.0p_2016-09-30/PowerEdition Deployment (built 2016-09-30)]

    This is an odd one and I think I've been thorough and accurate in documenting the characteristics. Let me know if anything I state below doesn't jive with your experience.

    We have a need to replace a FormItem depending on a selection made from a SelectItem. The SelectItem and FormItem are on the same form. We are able to change the value of the SelectItem and the FormItem changes as expected because we call DynamicForm.setitems() in a ChangeHandler on that SelectItem.

    However, subsequent invocations of the ChangeHandler result in a stack trace in the SmartClient Console. The following is actually the stack trace from the example shown below:

    Code:
    11:34:51.428:MUP9:WARN:Log:TypeError: Cannot read property 'length' of undefined
    Stack from error.stack:
        PickListMenu.itemClick(<no args: exited>) on [PickListMenu ID:isc_PickListMenu_0] @ ISC_Forms.js:1824:404
        PickListMenu.recordClick(<no args: exited>) on [PickListMenu ID:isc_PickListMenu_0] @ ISC_Forms.js:1823:41
        ListGrid.rowClick(<no args: exited>) on [PickListMenu ID:isc_PickListMenu_0] @ ISC_Grids.js:1683:22
        [c]Class.invokeSuper(<no args: exited>) on [PickListMenu ID:isc_PickListMenu_0] @ ISC_Core.js:296:93
        [c]Class.Super(<no args: exited>) on [PickListMenu ID:isc_PickListMenu_0] @ ISC_Core.js:288:170
        PickListMenu.rowClick(<no args: exited>) on [PickListMenu ID:isc_PickListMenu_0] @ ISC_Forms.js:1819:245
        eval(<no args: exited>) on [GridBody ID:isc_PickListMenu_0_body] @ [no file]:4:115
        GridRenderer._rowClick(<no args: exited>) on [GridBody ID:isc_PickListMenu_0_body] @ ISC_Grids.js:717:183
        [c]Class.invokeSuper(<no args: exited>) on [GridBody ID:isc_PickListMenu_0_body] @ ISC_Core.js:296:93
        [c]Class.Super(<no args: exited>) on [GridBody ID:isc_PickListMenu_0_body] @ ISC_Core.js:288:170
        GridBody._rowClick(<no args: exited>) on [GridBody ID:isc_PickListMenu_0_body] @ ISC_Grids.js:784:76
        GridRenderer.click(<no args: exited>) on [GridBody ID:isc_PickListMenu_0_body] @ ISC_Grids.js:715:167
        Canvas.handleClick(<no args: exited>) on [GridBody ID:isc_PickListMenu_0_body] @ ISC_Core.js:3043:400
        [c]EventHandler.bubbleEvent(<no args: exited>) on [Class EventHandler] @ ISC_Core.js:1728:89
        [c]EventHandler.handleClick(<no args: exited>) on [Class EventHandler] @ ISC_Core.js:1586:50
        EventHandler._handleMouseUp(<no args: exited>) on [Class EventHandler] @ ISC_Core.js:1571:11
        [c]EventHandler.handleMouseUp(<no args: exited>) on [Class EventHandler] @ ISC_Core.js:1562:57
        [c]EventHandler.dispatch(_1=>[c]EventHandler.handleMouseUp(), _2=>[object MouseEvent]) on [Class EventHandler] @ ISC_Core.js:1806:108
        HTMLDocument.eval(event=>[object MouseEvent]) @ [no file]:3:123
    The following example starts by displaying a SelectItem that allows one of three different objects to be created: a TextItem, a SelectItem or a CheckboxItem. There are also four buttons to do the same thing, but we can ignore those for now.

    Scenario 1
    First, run the sample and select "Text Field" from the Field Type selector. This creates a TextItem as expected. Now select a different value and the stack trace shown above is generated in the console and there is no change in the main UI.

    Scenario 2
    Now, re-run the sample and instead of selecting a value from the Field Type selector, use the buttons to add fields of the various different types. Either remove the field or leave it there depending on your preference, and then select either a value from the Field Type selector. The field appears as expected, but subsequent changes to the Field Type selector result in the the same as in Scenario 1.

    Scenario 3
    Re-run the sample and select a value from the Field Type selector. The field is displayed as expected. Now use the buttons to add fields of the various different types. The field continues to change as many times as the buttons are clicked, but once attempting another selection from the Field Type selector the errant behavior occurs again.

    Scenario 4
    Change line 50 to set fieldTypeSelector.defaultToFirstOption(true) and re-run the sample. Note that since the ChangeHandler has not been invoked, the TextItem does not display, but that's Ok since we're simply testing what happens if the SelectItem already has a value. Select a different value from the Field Type selector. The field is displayed as expected. This suggests that it is not the fact that the SelectItem already has a value that causes the problem.

    These results seem to indicate that it is not simply the invocation of setItems() that is the problem, nor that the SelectItem has a value already when another is selected, but rather the invocation of setItems() from within the SelectItem's ChangeHandler seems to cause the problem. But only the ChangeHandler has been invoked previously.

    Note that the following warnings are generated in the console, but I believe I am OK ignoring these since I am simply trying to replace an object with itself when calling setItems() a second and subsequent times. Is this true?

    Having said that, I do wonder if the first warning ("Unsupported 'form' property") is relevant to this problem.

    Code:
    11:50:17.047:MUP7:WARN:DynamicForm:isc_DynamicForm_0:Unsupported 'form' property [[DynamicForm ID:isc_DynamicForm_0]] set on item:[SelectItem ID:isc_SelectItem_2 name:fieldTypeSelector].  Ignoring.
    11:50:17.058:MUP7:WARN:Log:ID 'isc_SelectItem_2' for object '[SelectItem ID:isc_SelectItem_2 name:fieldTypeSelector]' collides with the ID of an existing object. This can occur when the specified ID for a new SmartClient Canvas is the same as a native attribute of 'window', or another variable already assigned in global scope. The global reference to this object will be replaced. Consider instead using a different ID to avoid this collision altogether, especially if the colliding ID is a native attribute of window.  Replacing such objects often has serious and unintended consequences. In this case, the current value of window['isc_SelectItem_2'] is: 
    
     SelectItem{eventParent: [DynamicForm ID:isc_DynamicForm_0],
    containerWidget: [DynamicForm ID:isc_DynamicForm_0],
    form: [DynamicForm ID:isc_DynamicForm_0],
    AUTOIDClass: "SelectItem",
    name: "fieldTypeSelector",
    editorType: "SelectItem",
    title: "Field Type",
    valueMap: Array[3],
    type: "text",
    validators: Array[1],
    ID: "isc_SelectItem_2",
    pendingStatus: false,
    }
    My sample is a simple rewrite of buildinds as follows:

    Code:
    package com.smartgwt.sample.client;
    
    import com.google.gwt.core.client.EntryPoint;
    import com.smartgwt.client.widgets.IButton;
    import com.smartgwt.client.widgets.events.ClickEvent;
    import com.smartgwt.client.widgets.events.ClickHandler;
    import com.smartgwt.client.widgets.form.DynamicForm;
    import com.smartgwt.client.widgets.form.fields.CheckboxItem;
    import com.smartgwt.client.widgets.form.fields.SelectItem;
    import com.smartgwt.client.widgets.form.fields.TextItem;
    import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
    import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
    import com.smartgwt.client.widgets.layout.HLayout;
    
    /**
     * Entry point classes define <code>onModuleLoad()</code>.
     */
    public class BuiltInDS implements EntryPoint {
    
        /**
         * This is the entry point method.
         */
        public void onModuleLoad() {
            final DynamicForm form = new DynamicForm();
            final SelectItem fieldTypeSelector = new SelectItem("fieldTypeSelector", "Field Type");        
    
            final SelectItem selectItem = new SelectItem("Select");        
            final TextItem textItem = new TextItem("Text");        
            final CheckboxItem checkboxItem = new CheckboxItem("Checkbox","Checkbox");        
    
            fieldTypeSelector.setValueMap("Text Field", "Select Field", "Checkbox Field");
            fieldTypeSelector.setDefaultToFirstOption(false);
            
            fieldTypeSelector.addChangedHandler(new ChangedHandler() {
    
                @Override
                public void onChanged(ChangedEvent event) {
                    String value = event.getValue().toString();
                    if ("Text Field".equals(value)) {
                        form.setItems(fieldTypeSelector, textItem);
                    } else if ("Select Field".equals(value)) {
                        form.setItems(fieldTypeSelector, selectItem);
                    } else if ("Checkbox Field".equals(value)) {
                        form.setItems(fieldTypeSelector, checkboxItem);
                    } else {
                        form.setItems(fieldTypeSelector);
                    } 
                }            
            });
    
            final IButton addTextItemBtn = new IButton("Text Field");
            addTextItemBtn.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent event) {
                    form.setItems(fieldTypeSelector, textItem);
                }
            });
    
            final IButton addSelectorItemBtn = new IButton("Select Field");
            addSelectorItemBtn.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent event) {
                    form.setItems(fieldTypeSelector, selectItem);
                }
            });
    
            final IButton addCheckboxItemBtn = new IButton("Checkbox Field");
            addCheckboxItemBtn.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent event) {
                    form.setItems(fieldTypeSelector, checkboxItem);
                }
            });
    
            final IButton removeItemBtn = new IButton("Remove Field");
            removeItemBtn.addClickHandler(new ClickHandler() {
                public void onClick(ClickEvent event) {
                    form.setItems(fieldTypeSelector);
                }
            });
    
            form.setItems(fieldTypeSelector);
            
            HLayout hlayout = new HLayout();
            hlayout.setMembers(form, addTextItemBtn, addSelectorItemBtn, addCheckboxItemBtn, removeItemBtn);
            hlayout.draw();
        
        }
    }

    #2
    Re-using the same FormItems in multiple setItems() calls is invalid (hence the warning), as is replacing all the items in a form in the middle of handling a change event. If you think about it, there are tons of ambiguities about what this is supposed to mean with respect to whether validation should still happen, or automatic saving, whether values should still be considered to have been changed, how default values are handled, etc.

    The best way to do this kind of interface, where an arbitrary number of formItems may be added by the user, is usually to use a VStack of single-item forms. This avoids all of the ambiguities that come from replacing the entire set of items right in the middle of processing events, and becomes much more efficient as the form becomes larger, since you aren't constantly rebuilding everything. It also makes it possible to do things like drag reorder fields or display controls next to fields (like an X for removal), if that's ever useful.

    Comment


      #3
      Well, sometimes the familiar masks the obvious. This has worked in many prior versions for us, but your point is well taken. Despite working in the past I agree that it's clearly a bogus expectation that this should continue to work indefinitely. We'll look into achieving what we need another way.

      Thanks,
      Gary

      Comment


        #4
        Thanks again. I re-worked this fairly old code to instead create all the necessary FormItems but each with visibility set to false. Then in the ChangeHandler I just hide() the current item and show() the newly selected one. Works like a charm.

        Regards,
        Gary

        Comment

        Working...
        X