开发者

Jquery datepicker popup not closing on select date in IE8

开发者 https://www.devze.com 2022-12-11 12:21 出处:网络
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 clo

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">St开发者_如何学Pythonart 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?


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.


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

onSelect: function() {}


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).


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.
    }
});


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.


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.


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();
}


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!


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号