iOS7 / onBlur, onChange and alerts will bring your site down
Seems that iOS 7 has had a bit of a mixed review since its launch less than a week ago, but today I came across an issue which will bring your site down for iOS 7 users in certain circumstances.
Select Boxes now ignore onBlur
The site we had the problem with is a car warranty specialist site. During the quote process, we use AJAX and onChange events to load up data directly into select boxes. For example, a user chooses their car make (‘Ford’), which then populates the range field (‘C-Max’,’Cougar’…’StreetKa’,’Tourneo’).
With the previous OS, we had to attach an onChange listener to the select box in order to blur the user’s focus. If we didn’t do this, the user could click the ‘next’ button on their keyword and focus on the range field. The browser would then receive the data, but would be unable to amend the DOM as the select box was being viewed. Essentially what we were doing was preventing the user from using the next button on certain fields.
iOS 7, however, prevents the developer from doing this. Now the select box ignores the blur function, which leaves the user with an empty select box node.
Luckily, this is relatively easy to get around:
Old Code:
// get range data after a manufacturer is selected
$("#make").blur(function(){
getmake();
}).change(function(){
$(this).blur();
});
So, in line 1, we are adding a blur listener to the select box with the id ‘make‘. When ‘make‘ is blurred (ie it loses focus; the user clicks off it, somewhere else on the screen), we run a function called ‘getmake()’. We then chain another listener, an onChange event, to the same select box and tell it to blur whenever the value changes (ie when someone chooses a different option).
Essentially we are saying that if the user clicks the ‘done’ button on their keyboard (which counts as losing focus from our ‘make‘ element), we should run the ‘getmake()‘ function. If the user clicks the ‘next‘ button on their keyboard, it triggers the onChange listener and we then tell the browser to lose focus from the ‘make‘ element. This, in turn, will fire the blur function in line 1.
New Code:
// get range data after a manufacturer is selected
$("#make").blur(function(){
getmake();
}).change(function(){
$(this).blur();
}).focus(function(){
if($(this).children().length==1 && $(this).children().first().attr('value')=='error'){
$(this).blur();
}
});
So now we add an onFocus listener. What this does is check to see if the select element has one child and if so, it checks that the child has a value of ‘error‘. If both conditions are true, it then runs the blur function – ie tells the browser to lose focus.
We then make sure our select boxes have one child (before DOM manipulation), like so:
Why does this solution work?
It seems that Safari in iOS7 will skip the blur listeners if they happen synchronously. So, the next button/link in iOS7 will essentially pause slightly before running the focus, probably to render the blurry soft focus effect. Therefore, our onChange event does still happen, but the browser then runs a focus event a little while after.
By adding a focus listener with conditions, we are restoring the balance of how we want the site to work for the user.
Mixing onChange and alerts freezes Safari in iOS7
But as these things tend to go, once you find one error another soon surfaces and the next one was massive. It elicited this response from one annoyed customer: “Will you tell the clowns who set up the page to fix the frigging the website so I can leave it”.
So what could have gone so badly wrong?
On the same site, we don’t allow customers will older cars purchase a warranty. The customers choose a registration date from two select boxes. If the month and the year values when chosen mean that the car is over 12 years old, we show an alert box and disable the form.
These were triggered by an onChange listener that would run a function called ‘checkcardate‘:
function checkcardate(){
var d = new Date();
var year = d.getFullYear();
var month = d.getMonth();
var regyear = $('#regyear').val();
var regmonth = $('#regmonth').val();
if((regyear < year-12)||((regyear == (year-12))&&(regmonth<(month+1))))
{
alert("As your vehicle is over 12 years old we are unable to provide a warranty quote");
window.document.quoteform.action='index.html';
$('#submit').hide();
return false;
} else {
$('#submit').show();
window.document.quoteform.action='';
return true;
}
}
But when customers on iOS 7 chose an option that triggered the listener function, they would get the following screen:
And they could not escape from the screen. All elements in Safari became unresponsive. The only way to exit was to hit the home button, switch to multi tasking and swipe Safari out of the list.
We first tried blurring the browser focus as we did on the first problem, but that proved ineffective. The only solution was to introduce a slight delay in the alert box call.
Instead of <select onChange=”checkcardate()”… we used <select onChange=”ios7checkcardate()”… and created a new function like so:
var ios7timer = null;
function ios7checkcardate(){
ios7timer = setTimeout('checkcardate()',500);
}
That half a second delay was enough for Safari to draw whatever it needed to before displaying the alert. Before we introduced this, it looked as though Safari had the user’s focus trapped somewhere on the page, and then set a modal layer on top of everything that required a response. Until the user taps OK on the alert box, the page will remain unresponsive, but as the focus is underneath this layer, the user is trapped forever.
Conclusion
Please check your sites for onChange / onBlur listeners when used in conjunction with alert functions or DOM manipulation.
Apple being Apple, they’re bound to fix this sooner or later, but you don’t want to be picking up emails at 11pm entitled “Your pathetic website” when it has nothing to do with your code and everything to do with Apple’s.
Filed under: Apple Mac,iOS6,iOS7,iPhone,jQuery - @ September 24, 2013 2:28 pm