14

This is a hard one to articulate and I am new to mobile web development so please bear with me:

On my webpage, I have 3 Nested dropdown lists (Area, Town, Street).

Nested as in, each dropdown's items are modified when the selection in the dropdown above it changes. e.g selecting an Area changes the Town and Street lists and selecting a Town changes the Street list.

I use XMLHTTPRequests in the onchange() javascript event of the dropdowns to fetch and populate the other downdowns. This works fine on Android and Desktop browsers.

On Mobile Safari, when a drowdown is touched, a list is shown where the user can select items. In addition the selection box has the "Previous/Next/Autofill/Done" buttons to navigate to other form elements.

So the user touches the first dropdown, selects a value and presses the Next button. This causes two problems:

First, On this action the first dropdown's oncange() event is not triggered reliably! Sometimes it fires sometimes not.

If after selecting an Area, the user touches somewhere else on the webpage or presses the "Done" button then the onchange() is fired normally and the Towns and Streets are populated normally.

Second, the element that comes into focus when pressing then "Next" button is the dropdown whos elements need to be changed after being fetched. When the onchange() of the previous dropdown does get fired then, either the list is no updated or the items in the select box turn blue and all of them have a tick mark showing that they are all selected..

From what i can tell the problem would be solved if i can disable the Next/Previous buttons in the selection box or somehow fix how the onchange() is fired and the next in focus dropdown's list items are repopulated while it is in focus.

Here is the code (simplified):

<!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>
    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no" />

    <title></title>
</head>
<body onload="AppStart();">
    <form action="#">
    Area:
    <select id="ddlArea">
        <option value="">-- Select Area -- </option>
        <option value="1">Area 1</option>
        <option value="2">Area 2</option>
        <option value="3">Area 3</option>
        <option value="4">Area 4</option>
        <option value="5">Area 5</option>
    </select>
    <br />
    Town:
    <select id="ddlTown">
        <option value="">Please wait ...</option>
    </select>
    <br />
    Street:
    <select id="ddlStreet">
        <option value="">-- Select Area or Town -- </option>
    </select>
    <br />
    Unit:
    <select id="ddlUnit">
        <option value="">-- No Street Selected -- </option>
    </select>

    <script type="text/javascript">

        var ddlArea, ddlTown, ddlStreet, ddlUnit;
        function AppStart() {
            ddlArea = document.getElementById("ddlArea");
            ddlTown = document.getElementById("ddlTown");
            ddlStreet = document.getElementById("ddlStreet");
            ddlUnit = document.getElementById("ddlUnit");

            ddlArea.onchange = areaChanged;
            ddlTown.onchange = townChanged;
            ddlStreet.onchange = streetChanged;

            setTimeout(function() { updateTown(""); }, 250);
        }

        var areaId = "", townId = "", streetId = "", unitId = "";
        function areaChanged(e) {
            areaId = ddlArea.options[ddlArea.selectedIndex].value
            ddlClear(ddlTown, createOption("Please Wait...", ""));
            ddlClear(ddlStreet, createOption("Please Wait...", ""));
            ddlClear(ddlUnit, createOption("-- No Street Selected --", ""));
            setTimeout(function() { updateTown(areaId); }, 500);
            setTimeout(function() { updateStreet(areaId, ""); }, 700);
        }

        function townChanged(e) {
            townId = ddlTown.options[ddlTown.selectedIndex].value
            ddlClear(ddlStreet, createOption("Please Wait...", ""));
            ddlClear(ddlUnit, createOption("-- No Street Selected --", ""));
            setTimeout(function() { updateStreet(areaId, townId); }, 400);
        }

        function streetChanged(e) {
            streetId = ddlStreet.options[ddlStreet.selectedIndex].value
            ddlClear(ddlUnit, createOption("Please Wait...", ""));
            setTimeout(function() { updateUnit(streetId); }, 600);
        }

        function updateTown(areaID) {
            ddlClear(ddlTown, createOption("-- Select Town --", ""));
            var items = isNaN(parseInt(areaID)) ? 10 : parseInt(areaID);
            if (areaID == "") areaID = "ALL";
            for (var i = 0; i < items; i++) {
                ddlTown.appendChild(createOption("Town " + (i+1) + ", Area " + areaID, i));
            }
        }

        function updateStreet(areaID, townID) {
            ddlClear(ddlStreet, createOption("-- Select Street --", ""));
            var items1 = isNaN(parseInt(areaID)) ? 10 : parseInt(areaID);
            var items2 = isNaN(parseInt(townID)) ? 10 : parseInt(townID);
            var items = items1 + items2;
            if (areaID == "") areaID = "ALL";
            if (townID = "") townId = "ALL";
            for (var i = 0; i < items; i++) {
                ddlStreet.appendChild(createOption("Street " + (i + 1) + ", Area " + areaID + ", Town " + townID, i));
            }
        }

        function updateUnit(streetID) {
            ddlClear(ddlUnit, createOption("-- Select Unit --", ""));
            var items = isNaN(parseInt(streetID)) ? 10 : parseInt(streetID);
            if (streetID == "") streetID = "ALL";
            for (var i = 0; i < items; i++) {
                ddlUnit.appendChild(createOption("Unit " + (i + 1) + ", Street " + streetID, i));
            }
        }

        function ddlClear(Dropdown, option) {
            while (Dropdown.options.length > 0) {
                try { Dropdown.options[0] = null; } catch (e) { };
            }
            if (option != null) {
                Dropdown.appendChild(option);
            }
        }

        function createOption(text, value) {
            var oOption = document.createElement("OPTION");
            oOption.innerHTML = text;
            oOption.value = value;
            return oOption;
        }

    </script>

    </form>
</body>
</html>

Help. :/

Vaibhav Garg
  • 3,630
  • 3
  • 33
  • 55

1 Answers1

29

I had the same problem on my site. I was able to fix it by manually polling the selectedIndex property on the select control. That way it fires as soon as you "check" the item in the list. Here's a jQuery plugin I wrote to do this:

$.fn.quickChange = function(handler) {
    return this.each(function() {
        var self = this;
        self.qcindex = self.selectedIndex;
        var interval;
        function handleChange() {
            if (self.selectedIndex != self.qcindex) {
                self.qcindex = self.selectedIndex;
                handler.apply(self);
            }
        }
        $(self).focus(function() {
            interval = setInterval(handleChange, 100);
        }).blur(function() { window.clearInterval(interval); })
        .change(handleChange); //also wire the change event in case the interval technique isn't supported (chrome on android)
    });
};

You use it just like you would use the "change" event. For instance:

$("#mySelect1").quickChange(function() { 
    var currVal = $(this).val();
    //populate mySelect2
});

Edit: Android does not focus the select when you tap it to select a new value, but it also does not have the same problem that iphone does. So fix it by also wiring the old change event.

InvisibleBacon
  • 3,137
  • 26
  • 27
  • Thanks for this.. I had solved this problem by calling blur() on the next dropdown, when the first one's selection was changed and XMLHttpRequest data was returned, so that the selection box disappeared and the user had to touch the second dropdown to access it, which cause the items to be refreshed in the list.. it was a hack which i dont like much.. – Vaibhav Garg Sep 03 '11 at 05:10
  • This is a great solution which did solve the first order failure of the Safari Mobile Web Browser (which is still present on iOS 5). But it seems to have left another secondary effect. Now the "spinner" is populating, but upon hitting "next" or "done" the actual pulldown doesn't seem to update with the selection and doesn't register. Any insights? Also the fix seemed to temporarily break the Android's version of the interface. But then it worked. – iJames Oct 13 '11 at 20:12
  • I discovered the android problem after I posted this. See my edit and new code above for the fix. Can you create a jsfiddle for the first problem? – InvisibleBacon Oct 14 '11 at 19:33
  • Ah, yes that was my next step! Good show! I'll recreate with JSFiddle. Love that mobile debug solution... – iJames Oct 14 '11 at 21:23
  • +1 for a good work-around. Posted my results, based on my own past question - http://stackoverflow.com/questions/7680992/change-event-not-firing-on-select-elements-with-mobile-safari-form-assistant – wdm Nov 23 '11 at 07:29
  • +1 for saving my f*** day. I searched 6 hours for a solution to this problem. Thx buddy... – MUG4N Jan 04 '13 at 14:37
  • Just for the record, this breaks Firefox due to this long-standing bug: https://bugzilla.mozilla.org/show_bug.cgi?id=393494 – Behrang Apr 26 '13 at 01:13
  • Furthermore, if you happen to end up using this hack, make sure you reset the value of `qcindex` if and when you repopulate/manipulate the ` – Behrang Apr 26 '13 at 05:02
  • 4
    Thank you for providing this example. It saved me a lot of time. Although now, with iOS 7 update, this solution is no longer working. I'm trying to debug your code, but so far haven't had any luck getting it to work on the updated iOS. Anyone have any luck? – Q-Ball Sep 30 '13 at 21:41
  • Any help for iOS7 issue? – Zain Shaikh Jan 19 '14 at 05:20
  • I was wondering the same thing about IOS7. I used another method but I had to remove it because it messed up my form validation. – Progrower Jan 20 '14 at 00:44
  • I recently tested IOS7 with this and it still seemed to work for me. I'll have to check again. – InvisibleBacon Jan 20 '14 at 14:09
  • What does jQuery have to do in this? There is no reason to make it big and dirty with extra libraries. All it takes is a timeout function that wraps everything in the onchange. – FlorianB Aug 03 '16 at 17:48