[FIXED] [1.0 RC1] Ext.net form fields server side events are broken.

  1. #1

    [FIXED] [1.0 RC1] Ext.net form fields server side events are broken.

    I've found that the server side events of most form fields are broken. Looking at the source code of RC1 I see that LoadPostData is not implemented correctly (at least it seems to me). From what I understand from the MS docs it needs to return true in order to raise events.
    • Looking at TextFieldBase.cs everything is good, it compares to the old value and only returns true if it actually changed. TextField and TextArea work as expected. So this proves it can be made working :-)
    • DateField.cs doesn't compare to the old value and always returns true. This has the result that OnTextChanged and OnSelectionChanged are always triggered, even if the field content didn't change at all.
    • I don't know what's wrong with the checkbox, but in my test it never triggered OnCheckedChanged, even though it always returns true from LoadPostData. Some other controls have the same problem.
    • MultiCombo and TimeField always trigger OnTextChanged once you entered a value, but never trigger OnValueChanged. They don't care if the selection actually changed.


    There are probably more, I didn't bother to check every single of them. It's easy to test, just put them on a form with a button and log if the event gets triggered. The problem always occurs and it doesn't matter if it's postback, directevent or directmethod.

    So there are are actually two bugs:
    1. OnChanged events not getting triggered for some controls. For example, OnValueChanged works for DateField, but OnCheckedChanged doesn't work for checkbox and radio button. If I look at the RC1 source, maybe because checkbox implements IPostBackEventHandler.RaisePostBackEvent instead of override void RaisePostDataChangedEvent like DateField? I don't know who the caller is, but if he doesn't go through the interface then that would explain it.
    2. LoadPostData implementations for quite a few controls don't bother to check if the value actually changed. Since also quite a few controls do check if the value changed it shouldn't be a problem to fix the broken ones?



    This is a problem for two reasons:
    1. OnChanged events not getting triggered on some controls. It can be worked around by subscribing to OnTextChanged where available, but that's not possible for all field types (eg. checkbox and radio fields have no TextChanged event).
    2. Change events are triggered when the value didn't actually change. That's very annoying when you have a page where only parts of the controls are in use. For example I have cases where fields are on card layouts or tab panels and when a different panel is visible they still may trigger events.


    It is possible to work around both issues, but it is very annoying:
    1. Use OnTextChanged where other events don't work, and for checkbox/radio button I use direct events to go right back to the server when clicking them (not so good for performance but it works).
    2. Manually track the previous value and ignore the event when it didn't actually change.


    PS: Sorry if this is already fixed in SVN, I searched but couldn't find a relevant topic. I'm still prototyping with the RC1, but planning to buy a commercial license soon.
    Last edited by Daniil; Jun 27, 2011 at 10:28 AM. Reason: [FIXED]
  2. #2
    Sorry it took me so long to get back on this, but I have been busy with other projects. Did anyone take a look on it?

    Good news is that I now got a Pro license so I could actually check the current state in SVN. It seems there have been a few changes in this area compared to RC1, but it's still not working correctly for me.

    • Checkbox now checks for changed values, but the event still doesn't trigger for me. I noticed that Checkbox implements IPostBackEventHandler.RaisePostBackEvent but its baseclass (Field) implements IPostBackDataHandler.RaisePostDataChangedEvent. That seems to be the root problem of events not triggering. Note that there are a few other form controls which make that mistake and thus don't get their events triggered.
    • DateField also does a proper check for value change now, but if the field is empty there's an exception, which sets to empty value (ok) and always triggers an on-changed event (not ok). Since an empty DateField textbox is valid input this is wrong, it should check for the empty string and not go through the exception path.
    • TimeField and MultiCombo didn't change their implementation so they are still wrong. Didn't check the others yet.


    Right now I'm most interested in correct behavior of checkboxes and date fields because that's what we're using in our forms. So, are you planning to fix those in SVN, or should I go ahead and fix them in my own build?


    [Edit] Just looked up the semantics of IPostBackEventHandler and IPostBackDataHandler - and it indeed triggers the change event if I turn the checkbox on AutoPostBack. However in our application we can't do that, we got a lot of fields on the form and don't want to post back on every single field change.
  3. #3
    Hi,

    Thanks for the bug report. I will investigate it today/tomorrow
  4. #4
    Hi all,

    I can confirm the wrong behavior of:
    1. DateField - fires OnTextChanged if even there was no change.
    2. Checkbox - OnCheckedChanged doesn't occur at all

    But I can't confirm the wrong behavior of TimeField and MultiCombo, it seems they work as expected.

    @syncos, could you provide a sample to reproduce the issues with TimeField and MultiCombo?

    Here is my test case.

    Example
    <%@ Page Language="C#" %>
    
    <%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
    
    <script runat="server">
        protected virtual void TextField_OnTextChanged(object sender, EventArgs e)
        {
            DisplayField1.Text = "&nbsp;OnTextChanged occured as expected";
        }
    
        protected virtual void DateField_OnTextChanged(object sender, EventArgs e)
        {
            DisplayField2.Text = "&nbsp;OnTextChanged occured as not expected - field's value was not changed";
        }
    
        protected virtual void Checkbox_OnCheckedChanged(object sender, EventArgs e)
        {
            DisplayField3.Text = "&nbsp;OnTextChanged occured";
        }
    
        protected virtual void ComboBox_OnTextChanged(object sender, EventArgs e)
        {
            DisplayField4.Text = "&nbsp;OnTextChanged occured";
        }
    
        protected virtual void MultiCombo_OnTextChanged(object sender, EventArgs e)
        {
            DisplayField5.Text = "&nbsp;OnTextChanged occured";
        }
    
        protected virtual void TimeField_OnTextChanged(object sender, EventArgs e)
        {
            DisplayField6.Text = "&nbsp;OnTextChanged occured";
        }
    </script>
    
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Ext.Net Example</title>
    </head>
    <body>
        <form runat="server">
        <ext:ResourceManager runat="server" />
        <ext:Container runat="server" Layout="HBoxLayout">
            <Items>
                <ext:TextField 
                    runat="server" 
                    OnTextChanged="TextField_OnTextChanged" 
                    EmptyText="I work fine" />
                <ext:DisplayField ID="DisplayField1" runat="server" />
            </Items>
        </ext:Container>
        <br />
        <ext:Container runat="server" Layout="HBoxLayout">
            <Items>
                <ext:DateField 
                    runat="server" 
                    Width="200" 
                    OnTextChanged="DateField_OnTextChanged"
                    EmptyText="Do not change me" />
                <ext:DisplayField ID="DisplayField2" runat="server" />
            </Items>
        </ext:Container>
        <br />
        <ext:Container runat="server" Layout="HBoxLayout">
            <Items>
                <ext:Checkbox 
                    runat="server" 
                    OnCheckedChanged="Checkbox_OnCheckedChanged" />
                <ext:DisplayField ID="DisplayField3" runat="server" Text="&nbsp;OnCheckedChanged doesn't occur" />
            </Items>
        </ext:Container>
        <br />
        <ext:Container runat="server" Layout="HBoxLayout">
            <Items>
                <ext:ComboBox 
                    runat="server" 
                    OnTextChanged="ComboBox_OnTextChanged" 
                    EmptyText="I work fine">
                    <Items>
                        <ext:ListItem Text="Item 1" Value="1" />
                        <ext:ListItem Text="Item 2" Value="2" />
                    </Items>
                </ext:ComboBox>
                <ext:DisplayField ID="DisplayField4" runat="server" />
            </Items>
        </ext:Container>
        <br />
        <ext:Container runat="server" Layout="HBoxLayout">
            <Items>
                <ext:MultiCombo 
                    runat="server" 
                    OnTextChanged="MultiCombo_OnTextChanged" 
                    EmptyText="I work fine">
                    <Items>
                        <ext:ListItem Text="Item 1" Value="1" />
                        <ext:ListItem Text="Item 2" Value="2" />
                    </Items>
                </ext:MultiCombo>
                <ext:DisplayField ID="DisplayField5" runat="server" />
            </Items>
        </ext:Container>
        <br />
        <ext:Container runat="server" Layout="HBoxLayout">
            <Items>
                <ext:TimeField 
                    runat="server" 
                    OnTextChanged="TimeField_OnTextChanged" 
                    EmptyText="I work fine" />
                <ext:DisplayField ID="DisplayField6" runat="server" />
            </Items>
        </ext:Container>
        <ext:Button runat="server" Text="Submit" AutoPostBack="true" />
        </form>
    </body>
    </html>
  5. #5
    Hi, thanks for investigating.

    Some of the bugs are not visible in your test because they require two postbacks or an initial non-empty value. Since you don't reset the display field text you wouldn't notice what happens on the second postback.

    I've taken some time to do more testing and made a list of everything I noticed as wrong. I didn't bother to list the cases that work, but they'll be marked as such in the test page. Also I noticed a few more errors which I didn't notice before, they are included below. I tried to also include some more controls, though it's just for completeness, I'm still mainly interested in text fields, date fields and combo boxes.

    PS: sorry for this long post, but you asked for detailed test cases ;)



    Errors in handling "empty" values, to reproduce load the test page and just make a postback. This causes my test page to report the changed events as "ok" and missing events as "error" even though it is the other way round (the value shouldn't change in the first place!)
    • ComboBox.Text is set to DateTime.Min.ToString() while ComboBox.Value stays null
    • TimeField.Value, TimeField.Text and NumberField.Value change to some large negative values. I'd say they should also stay null (Value property) resp. empty string (Text property)


    Errors in the event handling. Test case #1 - load page and just make a postback without entering any values.
    • NumberField, TextField and TextArea report TextChanged when text stays empty.
    • DateField reports SelectionChanged when field stays empty.
    • DateField reports TextChanged and NumberField reports NumberChanged when they should stay empty, but probably just because the empty value is handled wrong (see above).


    Errors in event handling. Test case #2 - load page, enter a value for the relevant control, then postback.
    • Checkbox.CheckedChanged isn't triggered even though the value changes
    • ComboBox.ValueChanged isn't triggered even though TextChanged was properly triggered
    • MultiCombo.TextChanged is triggered even though its text isn't changed. I don't know what's wrong here, text staying empty or TextChanged triggering. You could also argue it's correct because the selection actually changed, but then the event is kinda misnamed.


    Errors in event handling. Test case #3 - load page, enter a value for the relevant control, then postback twice. On the second postback there shouldn't be another change event.
    • MultiCombo.TextChanged is triggered even though neither text nor selection changed this time.
    • TimeField.TextChanged is triggered even though the time didn't change this time.


    I made a test suite based on your example, see below. I had to move the DisplayField to the end because I didn't want to bother resizing them on DirectEvent/DirectMethod postbacks. I also put everything into a single container because it made the source shorter, if there was a reason to split containers, feel free to do so again.

    <%@ Page language="C#" autoeventwireup="true" %>
    <%@ Register assembly="Ext.Net" namespace="Ext.Net" tagprefix="ext" %>
    <%@ Import namespace="System.Collections.Generic" %>
    <script runat="server">
        private class TestRecord
        {
            public Control _control;
            public Func<object> _reader;
            public string _event;
            public int _raised;
    
            public TestRecord(Control control, Func<object> reader, Action<EventHandler> subscribe, string eventName)
            {
                _control = control;
                _reader = reader;
                _event = eventName;
    
                subscribe(OnEventRaised);
            }
    
            public string Key
            {
                get { return _control.ID + ":" + _event; }
            }
    
            public void OnEventRaised(object sender, EventArgs e)
            {
                _raised++;
            }
    
            public string FormatValue(object value)
            {
                if (value == null)
                    return "<null>";
                else if (value is string)
                    return string.Format("'{0}'", value);
                else
                    return value.ToString();
            }
    
            public object Check(StringBuilder sb, object oldValue)
            {
                object newValue = _reader();
    
                bool changed = !Object.Equals(oldValue, newValue);
    
                Field field = _control as Field;
                string label = _control.ID;
                if (field != null)
                    label = field.FieldLabel + " " + label;
    
                sb.AppendFormat("\n=== {0} {1} ===\n", label, _event);
    
                if (!changed)
                {
                    sb.AppendFormat("Value didn't change: {0} -> {1}\n",
                        FormatValue(oldValue), FormatValue(newValue));
    
                    // values didn't change, shouldn't have raised any events
                    if (_raised == 0)
                        sb.Append("Event wasn't raised [ok]\n");
                    else
                        sb.Append("Unexpected event [error]\n");
                }
                else
                {
                    sb.AppendFormat("Value changed: {0} -> {1}\n",
                        FormatValue(oldValue), FormatValue(newValue));
    
                    // values changed, event should have been raised exactly once
                    if (_raised == 1)
                        sb.Append("Event was raised [ok]\n");
                    else if (_raised == 0)
                        sb.Append("Failed to raise event [error]\n");
                    else
                        sb.Append("Event raised multiple times [error]\n");
                }
    
                return newValue;
            }
        }
    
        private List<TestRecord> _test = new List<TestRecord>();
    
        protected virtual void Page_Init()
        {
            // register test methods
            _test.Add(new TestRecord(Field1, () => Field1.Text, (x) => Field1.TextChanged += x, "TextChanged"));
            
            _test.Add(new TestRecord(Field2, () => Field2.Text, (x) => Field2.TextChanged += x, "TextChanged"));
            _test.Add(new TestRecord(Field2, () => Field2.SelectedValue, (x) => Field2.SelectionChanged += x, "SelectionChanged"));
            
            _test.Add(new TestRecord(Field3, () => Field3.Checked, (x) => Field3.CheckedChanged += x, "CheckedChanged"));
            
            _test.Add(new TestRecord(Field4, () => Field4.Value, (x) => Field4.ValueChanged += x, "ValueChanged"));
            _test.Add(new TestRecord(Field4, () => Field4.Text, (x) => Field4.TextChanged += x, "TextChanged"));
            
            _test.Add(new TestRecord(Field5, () => Field5.Value, (x) => Field5.ValueChanged += x, "ValueChanged"));
            _test.Add(new TestRecord(Field5, () => Field5.Text, (x) => Field5.TextChanged += x, "TextChanged"));
            
            _test.Add(new TestRecord(Field6, () => Field6.Value, (x) => Field6.ValueChanged += x, "ValueChanged"));
            _test.Add(new TestRecord(Field6, () => Field6.Text, (x) => Field6.TextChanged += x, "TextChanged"));
            
            _test.Add(new TestRecord(Field7, () => Field7.Text, (x) => Field7.TextChanged += x, "TextChanged"));
            
            _test.Add(new TestRecord(Field8, () => Field8.Text, (x) => Field8.TextChanged += x, "TextChanged"));
            _test.Add(new TestRecord(Field8, () => Field8.Number, (x) => Field8.NumberChanged += x, "NumberChanged"));
        }
    
        protected virtual void Page_PreRender()
        {
            // deserialize old values
            Dictionary<string, object> oldValues;
            if (IsPostBack)
                oldValues = (Dictionary<string, object>)ViewState["Values"];
            else
                oldValues = new Dictionary<string, object>();
    
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("PageRequest: Postback={0}, IsAjax={1}\n", IsPostBack, X.IsAjaxRequest);
    
            Dictionary<string, object> newValues = new Dictionary<string, object>();
            if (IsPostBack)
            {
                // If we had a postback compare old with new values.
                foreach (var record in _test)
                    newValues.Add(record.Key, record.Check(sb, oldValues[record.Key]));
            }
            else
            {
                // On first page load there are no old values so we just record the new values.
                foreach (var record in _test)
                    newValues.Add(record.Key, record._reader());
            }
    
            sb.Replace("<", "&lt;");
            sb.Replace(">", "&gt;");
            sb.Replace(" ", "&nbsp;");
            sb.Replace("\n", "<br>");
    
            DebugLog.Text = sb.ToString();
    
            ViewState["Values"] = newValues;
        }
    
        protected void DoDirectEvent(object sender, DirectEventArgs e) { }
    
        [DirectMethod]
        public void DoDirectMethod() { }
        
    </script>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Ext.Net Example</title>
    </head>
    <body>
        <form runat="server">
        <%--
        Enabled AjaxViewStateMode for DirectEvent/DirectMethod submit buttons.
        Otherwise the test methods wouldn't store the new values in ViewState.
        --%>
        <ext:ResourceManager runat="server" ajaxviewstatemode="Enabled" />
        <ext:Container runat="server" layout="FormLayout">
            <Items>
                <ext:TextField runat="server" id="Field1" fieldlabel="TextField" emptytext="empty" />
                <ext:DateField runat="server" id="Field2" fieldlabel="DateField" width="200" emptytext="empty" />
                <ext:Checkbox runat="server" id="Field3" fieldlabel="Checkbox" />
                <ext:ComboBox runat="server" id="Field4" fieldlabel="ComboBox" emptytext="empty">
                    <Items>
                        <ext:ListItem text="Item 1" value="1" />
                        <ext:ListItem text="Item 2" value="2" />
                    </Items>
                </ext:ComboBox>
                <ext:MultiCombo runat="server" id="Field5" fieldlabel="MultiCombo" emptytext="empty">
                    <Items>
                        <ext:ListItem text="Item 1" value="1" />
                        <ext:ListItem text="Item 2" value="2" />
                    </Items>
                </ext:MultiCombo>
                <ext:TimeField runat="server" id="Field6" fieldlabel="TimeField" emptytext="empty" />
                <ext:TextArea runat="server" id="Field7" fieldlabel="TextArea" emptytext="empty" />
                <ext:NumberField runat="server" id="Field8" fieldlabel="NumberField" emptytext="empty" />
                <ext:DisplayField runat="server" id="DebugLog" />
            </Items>
        </ext:Container>
        <br />
        <ext:Button runat="server" text="Submit (Postback)" autopostback="true" />
        <ext:Button runat="server" text="Submit (DirectEvent)">
            <DirectEvents>
                <Click onevent="DoDirectEvent" />
            </DirectEvents>
        </ext:Button>
        <ext:Button runat="server" text="Submit (DirectMethod)">
            <Listeners>
                <Click handler="#{DirectMethods}.DoDirectMethod();" />
            </Listeners>
        </ext:Button>
        </form>
    </body>
    </html>
  6. #6
    Hi,

    Fixed in SVN (except test case #3)
  7. #7
    Thanks, I think all cases I care about are working.

Similar Threads

  1. Dynamic Menu Server Side events
    By SeshuKumar in forum 1.x Help
    Replies: 8
    Last Post: Dec 03, 2012, 5:13 AM
  2. Replies: 0
    Last Post: Oct 27, 2011, 10:02 PM
  3. Replies: 4
    Last Post: Mar 19, 2010, 11:35 AM
  4. Quick method to submit form fields to server
    By danielg in forum 1.x Help
    Replies: 0
    Last Post: Feb 05, 2009, 11:33 AM
  5. Replies: 5
    Last Post: Sep 19, 2008, 6:21 PM

Posting Permissions