// send async request to the given URL (which will send back serialized ListBoxModel object),
// then use the result to fill the list box.
function updateListBox(listBox,url,config) {
    config = config || {};
    config = object(config);
    var originalOnSuccess = config.onSuccess;
    var l = $(listBox);

    var settingMain = listBox.closest('.setting-main')
    if (!settingMain) {
        console.warn("Couldn't find the expected parent element (.setting-main) for element", listBox)
        return;
    }

    var status = settingMain.nextElementSibling;
    if (!status) {
        console.warn("Couldn't find the expected status element", settingMain)
        return;
    }
    if (status.firstChild && status.firstChild.getAttribute('data-select-ajax-error')) {
        status.innerHTML = "";
    }
    config.onSuccess = function (rsp) {
        l.removeClassName("select-ajax-pending");
        var currentSelection = l.value;

        // clear the contents
        while(l.length>0)   l.options[0] = null;

        var selectionSet = false; // is the selection forced by the server?
        var possibleIndex = null; // if there's a new option that matches the current value, remember its index
        var opts = eval('('+rsp.responseText+')').values;
        for( var i=0; i<opts.length; i++ ) {
            l.options[i] = new Option(opts[i].name,opts[i].value);
            if(opts[i].selected) {
                l.selectedIndex = i;
                selectionSet = true;
            }
            if (opts[i].value==currentSelection)
                possibleIndex = i;
        }

        // if no value is explicitly selected by the server, try to select the same value
        if (!selectionSet && possibleIndex!=null)
            l.selectedIndex = possibleIndex;

        if (originalOnSuccess!=undefined)
            originalOnSuccess(rsp);
    };
    config.onFailure = function (rsp) {
        l.removeClassName("select-ajax-pending");
        status.innerHTML = rsp.responseText;
        if (status.firstChild) {
            status.firstChild.setAttribute('data-select-ajax-error', 'true')
        }
        Behaviour.applySubtree(status);
        // deleting values can result in the data loss, so let's not do that unless instructed
        var header = rsp.getResponseHeader('X-Jenkins-Select-Error');
        if (header && "clear" === header.toLowerCase()) {
            // clear the contents
            while (l.length > 0)   l.options[0] = null;
        }

    };

    l.addClassName("select-ajax-pending");
    new Ajax.Request(url, config);
}

Behaviour.specify("SELECT.select", 'select', 1000, function(e) {

        function hasChanged(selectEl, originalValue) {
            // seems like a race condition allows this to fire before the 'selectEl' is defined. If that happens, exit..
            if(!selectEl || !selectEl.options || !selectEl.options.length > 0)
              return false;
            var firstValue = selectEl.options[0].value;
            var selectedValue = selectEl.value;
            if (originalValue == "" && selectedValue == firstValue) {
                // There was no value pre-selected but after the call to updateListBox the first value is selected by
                // default. This must not be considered a change.
                return false;
            } else {
                return originalValue != selectedValue;
            }
        };

        // controls that this SELECT box depends on
        refillOnChange(e,function(params) {
            var value = e.value;
            updateListBox(e,e.getAttribute("fillUrl"),{
                parameters: params,
                onSuccess: function() {
                    if (value=="") {
                        // reflect the initial value. if the control depends on several other SELECT.select,
                        // it may take several updates before we get the right items, which is why all these precautions.
                        var v = e.getAttribute("value");
                        if (v) {
                            e.value = v;
                            if (e.value==v) e.removeAttribute("value"); // we were able to apply our initial value
                        }
                    }

                    fireEvent(e,"filled"); // let other interested parties know that the items have changed

                    // if the update changed the current selection, others listening to this control needs to be notified.
                    if (hasChanged(e, value)) {
                        fireEvent(e,"change");
                    }
                }
            });
        });
});

@@ -304,16 +304,454 @@
 tBox);%0A%0A
+    // Hacky function to retrofit compatibility with tables-to-divs%0A    // If the %3Cselect%3E tag parent is a %3Ctd%3E element we can consider it's following a%0A    // form entry using tables-to-divs markup.%0A    function getStatusElement() %7B%0A        function getStatusForTabularForms() %7B%0A            return findFollowingTR(listBox, %22validation-error-area%22).firstChild.nextSibling;%0A        %7D%0A        function getStatusForDivBasedForms() %7B%0A        
     var 
@@ -797,16 +797,24 @@
 -main')%0A
+        
     if (
@@ -825,24 +825,32 @@
 tingMain) %7B%0A
+        
         cons
@@ -940,32 +940,40 @@
 istBox)%0A        
+  
+      
 return;%0A    %7D%0A%0A 
@@ -972,27 +972,37 @@
     
-%7D%0A%0A    var status =
+        %7D%0A%0A            return
  set
@@ -1026,24 +1026,214 @@
 entSibling;%0A
+        %7D%0A%0A        return listBox.parentNode.tagName === 'TD'%0A            ? getStatusForTabularForms()%0A            : getStatusForDivBasedForms();%0A    %7D%0A%0A    var status = getStatusElement();%0A
     if (!sta
@@ -1307,21 +1307,8 @@
 ent%22
-, settingMain
 )%0A  

Fix broken select.js widget with tabular markup (#5081)