Notorious2tall
Notorious2tall

Reputation: 1448

Jquery datepicker popup not closing on select date in IE8

I've got a web form with a start date field. I've tied a jquery datepicker to the txt field. Now when I choose a date in FF, the selected date is populated in the text box and the calendar popup closes. However when I do the same thing in IE8, the selected date is populated in the text box but the popup remains open. I've also noticed that a script error is generated as soon as I select a date in the popup calendar.

I'm using jquery 1.3.2, jquery-ui 1.7.2, and .NET 3.5. Here's an example of my code:

<script type="text/javascript">
  $(document).ready(function() {
    $("#<%=txtStartDate.ClientID%>").datepicker({
      changeMonth: true,
      changeYear: true,
      showButtonPanel: true,
      showOn: 'button',
      buttonImage: '/_layouts/images/CALENDAR.GIF',
      buttonImageOnly: true
    });
  });
</script>
<div id="stylized">
  <asp:ValidationSummary ID="vs" runat="server" CssClass="messages-error" HeaderText=" Action required before the form can be submitted." ForeColor="" ValidationGroup="sh" />
  <div class="formrow">
    <div class="ms-formlabel formlabel">
      <asp:Label ID="lblStartDate" runat="server" CssClass="ms-standardheader" AssociatedControlID="txtStartDate">Start Date:</asp:Label>
    </div>
    <div class="ms-formbody formfield">
      <asp:RequiredFieldValidator ID="reqStartDate" runat="server" ControlToValidate="txtStartDate" ErrorMessage="Start Date is a required field." Text="*" Display="Dynamic" ValidationGroup="sh"></asp:RequiredFieldValidator>
      <asp:CompareValidator ID="cvStartDate" runat="server" ControlToValidate="txtStartDate" ErrorMessage="Date must be in the format MM/DD/YYYY" Text="*" Display="Dynamic" ValidationGroup="sh" Operator="DataTypeCheck" Type="Date"></asp:CompareValidator>
      <asp:TextBox ID="txtStartDate" runat="server"></asp:TextBox>
      <span class="formMessage">ex. MM/DD/YYYY</span>
    </div>
  </div>
  <div id="buttonrow">
    <asp:Button ID="btnSubmit" runat="server" Text="Submit" CssClass="ms-ButtonHeightWidth" OnClick="Submit_Click" ValidationGroup="sh" />
    <asp:Button ID="btnCancel" runat="server" Text="Cancel" CssClass="ms-ButtonHeightWidth" OnClick="Cancel_Click" CausesValidation="false" />
  </div>
</div>

Here's the script error I get in IE when I select the date:

'length' is null or not an object

WebResource.axd

Here's the code where the error is being thrown from:

function ValidatorOnChange(event) {
  if (!event) {
    event = window.event;
  }
  Page_InvalidControlToBeFocused = null;
  var targetedControl;
  if ((typeof(event.srcElement) != "undefined") && (event.srcElement != null)) {
    targetedControl = event.srcElement;
  }
  else {
    targetedControl = event.target;
  }
  var vals;
  if (typeof(targetedControl.Validators) != "undefined") {
    vals = targetedControl.Validators;
  }
  else {
    if (targetedControl.tagName.toLowerCase() == "label") {
        targetedControl = document.getElementById(targetedControl.htmlFor);
        vals = targetedControl.Validators;
    }
  }
  var i;
  for (i = 0; i < vals.length; i++) {
    ValidatorValidate(vals[i], null, event);
  }
  ValidatorUpdateIsValid();
}

It happens on the .length in the for loop at the end. Vals is null and isn't found in the previous if/else. I've stepped through the javascript and if (typeof(targetedControl.Validators) != "undefined") returns false and then if (targetedControl.tagName.toLowerCase() == "label") returns false too. Thus the length is null or not an object error.

Now I'm not sure if the datepicker popup not closing in IE and the script error in the WebResources.axd file are related errors, but I'm leaning that way. Can anyone tell me why the popup isn't closing?

Upvotes: 19

Views: 24730

Answers (10)

Peter Taylor
Peter Taylor

Reputation: 5046

The reason

The root bug (I think it's probably meant to be a feature, or maybe a workaround for a known IE bug?) is in ASP.Net's ValidatorHookupEvent:

var func;
if (navigator.appName.toLowerCase().indexOf('explorer') > -1) {
    func = new Function(functionPrefix + " " + ev);
}
else {
    func = new Function("event", functionPrefix + " " + ev);
}

As a result, in the default case that there's no other onchange registered, this sets up the onchange for the input to be the equivalent of

function() { ValidatorOnChange(event); }

in IE and

function(event) { ValidatorOnChange(event); }

in other browsers. So iff you're using IE, the event passed to ValidatorOnChange will be window.event (since window is the global object).

A suggested solution

If you don't want to hack around with the ASP.Net scripts then I think the nicest way to handle this is to detect the broken event handler and replace it. As with other suggestions here, I offer an onSelect to include in the datepicker options.

onSelect: function(dateStr, datePicker) {
    // http://stackoverflow.com/questions/1704398
    if (datePicker.input[0].onchange.toString().match(/^[^(]+\(\)\s*{\s*ValidatorOnChange\(event\);\s*}\s*$/)) {
        datePicker.input[0].onchange = ValidatorOnChange;
    }
    datePicker.input.trigger("change");
}

I'm applying this globally with

$.datepicker.setDefaults({
    onSelect: function(dateStr, datePicker) {
        etc.
    }
});

Upvotes: 2

B-K
B-K

Reputation: 378

Building upon the above answers and to provide further detail to my comment above,

Apparently in IE9+ and other browsers, you should now use dispatchEvent to fire the change event. (Why does .fireEvent() not work in IE9?)

The OnSelect function in the datepicker actually has 2 arguments:

  1. The value of the textbox associated with the datepicker
  2. An object with an id property that matches that of the textbox

    mytextbox.datepicker({
        defaultDate: null,
        numberOfMonths: 1,
        dateFormat: DisplayDateFormat,
        onSelect: function (value, source) {
        }
    });
    

All the examples I saw used document.getElementById() to retrieve the textbox, which I thought wouldn't be necessary seeing as the source object has the same id as the textbox. Upon closer examination it turns out that source is an object, not the textbox element. I found the following code resolved the problem though:

    mytextbox.datepicker({
        defaultDate: null,
        numberOfMonths: 1,
        dateFormat: DisplayDateFormat,
        onSelect: function (value, source) {

            var ctrl = document.getElementById(source.id);

            if ("dispatchEvent" in ctrl) {

                // IE9
                var evt = document.createEvent("HTMLEvents");
                evt.initEvent("change", false, true);
                ctrl.dispatchEvent(evt);
            } else if ("fireEvent" in ctrl) {

                // IE7/IE8
                ctrl.fireEvent("onchange");
            } else {

                $(ctrl).change();
            }
        }
    });

Update: It appears that this approach is no longer working - not sure why. It stops the error from being thrown but doesn't trigger the change event.

Upvotes: 0

changcn
changcn

Reputation: 11

change jquery.ui.datepicker.js line 1504

'" href="#"  >' + printDate.getDate() + '</a>')

with

'" href="javascript:DP_jQuery_' + dpuuid + '.datepicker._selectDay(\'#' +
       inst.id + '\',' + printDate.getMonth() + ',' + printDate.getFullYear() + ', this);"  >' + printDate.getDate() + '</a>')

test works OK!

Upvotes: 1

Vexanthrope
Vexanthrope

Reputation: 31

The solutions provided above only prevents the error from occurring.

On the datepicker:

onSelect : function(dateText, inst){ inst.input.trigger('cValidate')

and bind the event to the calendar input element.

.bind('cValidate', function (event) { window.ValidatorOnChange(event); });

this will fire the validatorchanged event with the correct event args (input field).

Upvotes: 3

Ben McIntyre
Ben McIntyre

Reputation: 1968

This is an endemic problem with jQuery datepickers and ASP validation controls. As you are saying, the wrong element cross-triggers an ASP NET javascript validation routine, and then the M$ code throws an error because the triggering element in the routine is undefined.

I solved this one differently from anyone else I have seen - by deciding that M$ should have written their code more robustly, and hence redeclaring some of the M$ validator code to cope with the undefined element. Everything else I have seen is essentially a workaround on the jQuery side, and cuts possible functionality out (eg. using the click event instead of change).

The bit that fails is

   for (i = 0; i < vals.length; i++) {
        ValidatorValidate(vals[i], null, event);
    }

which throws an error when it tries to get a length for the undefined 'vals'.

I just added

if (vals) {
    for (i = 0; i < vals.length; i++) {
        ValidatorValidate(vals[i], null, event);
    }
}

and she's good to go. Final code, which redeclares the entire offending function, is below. I put it as a script include at the bottom of my master page or page (so it occurs after the default declarations and replaces the earlier version).

Yes, this does break upwards compatibility if M$ decide to change their validator code in the future. But one would hope they'll fix it and then we can get rid of this patch altogether.

//  Fix issue with datepicker and ASPNET validators: redeclare MS validator code with fix
 function ValidatorOnChange(event) {
    if (!event) {
        event = window.event;
    }
    Page_InvalidControlToBeFocused = null;
    var targetedControl;
    if ((typeof (event.srcElement) != "undefined") && (event.srcElement != null)) {
        targetedControl = event.srcElement;
    }
    else {
        targetedControl = event.target;
    }
    var vals;
    if (typeof (targetedControl.Validators) != "undefined") {
        vals = targetedControl.Validators;
    }
    else {
        if (targetedControl.tagName.toLowerCase() == "label") {
            targetedControl = document.getElementById(targetedControl.htmlFor);
            vals = targetedControl.Validators;
        }
    }
    var i;
    if (vals) {
        for (i = 0; i < vals.length; i++) {
            ValidatorValidate(vals[i], null, event);
        }
    }
    ValidatorUpdateIsValid();
}

Upvotes: 1

rdworth
rdworth

Reputation: 1062

As a date is selected, the datepicker triggers the change event on the INPUT element, but the ASP.Net validator picks up the click event instead, with the source an A element, and tries to find the validators on that A element, instead of the INPUT. This can be observed by inspecting event.srcElement inside the validator's ValidatorOnChange function. In browsers other than IE, event.type is 'change' and event.target is correctly the INPUT.

While the no-op function onSelect: function() { } prevents the error, by overriding the .change() built-in to the datepicker's default onSelect, it also prevents the validators from triggering. Here's a work-around for both:

onSelect: function() {
  this.fireEvent && this.fireEvent('onchange') || $(this).change();
}

This uses the normal .change() trigger except on IE, where it's required to use .fireEvent to get the event object to be associated with the change and not the click.

Upvotes: 27

Fentex
Fentex

Reputation: 11

The fix...

onSelect: function() {}

..does not appear to work if the problem is with a CustomValidator that relies on a servewr side event handler to validate input.

There are a couple of other fixes mentioned here...

http://dev.jqueryui.com/ticket/4071

The problem is down to IE's event handling differing from other browsers and the client side validation code supplied by ASP Net not reacting gracefully to a situation not contemplated by it's authors.

Upvotes: 1

dlamblin
dlamblin

Reputation: 45341

It doesn't look like you're doing anything wrong since the ValidatorOnChange code is generated for you; there's something wrong in the way it's creating its vals object which appears to end up null on ie8.

It's been asked before, and the solution is overriding the onSelect function with a no-op function.

This is not the only kind of validator problem out there. Here's a vaguely similar issue with the autocomplete feature.

Upvotes: 1

Marek Karbarz
Marek Karbarz

Reputation: 29304

It seems to be a bug of sorts, but adding this line in the datepicker declaration should solve it:

onSelect: function() {}

Upvotes: 16

KevnRoberts
KevnRoberts

Reputation: 446

I don't really know what the problem is, but I did notice that your does not have the ValidationGroup set, and both of your validators have that value set. You might try setting ValidationGroup="sh" in your TextBox and see if that helps.

Upvotes: 0

Related Questions