Set select value in Firefox

I'm creating input replacements for a legacy site, which means I'm stuck using prototype.js (1.7.1.0)

My select inputs work fine cross browsers, except for Firefox (26.0 OSX)

In FF the select options have the 'selected' attribute correctly set, but trying to read the element value or selectedIndex always returns the initial value, and this is essential due to some third party code that I have no control over.

While I was testing to see if the visibility of the original input was relevant (it wasn't) I discovered that it is possible to get the replacements to work, but only if you FIRST use the original input. Further, the original input will not work if you use the replacement first.

I have a test case here: http://jsfiddle.net/DisasterMan/gUam3/4/

When the input replacements are used, the value and index are read and (should be) displayed below.

I've tried picking apart existing code to see how it's done, but haven't found usable select replacements with Prototype. Am planning to try and pick apart a working jQuery version such as http://groenroos.github.io/minimalect/ and see if I can work out how it's done, but any assistance would be greatly appreciated!

<!-- language: javascript -->
// To hell with IEh8! Not worth the time implementing the drop-down
if( ! $$( 'html' )[0].hasClassName( 'ltie9' ) )
{

    //Replace select inputs
    $$( 'select' ).each( function( select )
    {

                                                                    // All other checkboxs that share this name (i.e. this group)
        var allOptions                     = $$( 'select[name="' + select.name + '"] option' ),
                                                                    // id attribute of the input
                replacementId              = 'replace-' + select.name,
                                                                    // CSS styled select replacement
                replacement                = '<div id="' + replacementId + '" data-name="' + select.name + '" class="select-replacement"><span class="option-label"></span><span class="arrow"><span class="head"></span></span></div>',
                                                                    // Replacemet options container id
                replacementOptionsId       = replacementId + '-options',
                                                                    // Replacement options container
                replacementOptionsHtml     = '<div id="' + replacementOptionsId + '" class="select-replacement-options">',
                replacementOptionsElements,
                selectedLabel              = false;

        // Build replacement options elements
        allOptions.each( function ( option, index )
        {
            var selected = false,
                    label    = option.innerHTML;

            if( option.getAttribute( 'selected' ) !== null )
            {
                selected      = true;
                selectedLabel = label;
            }

            replacementOptionsHtml += '<span data-value="' + option.value + '" data-selected="' + selected + '" data-parent="' + replacementId + '" data-index="' + index + '">' + label + '</span>';
        } );

        replacementOptionsHtml += '</div>';


        // Insert replacement
        new Insertion.After( select, replacement );

        replacementElement       = $( replacementId );
        replacementSelectedLabel = replacementElement.down( '.option-label' );

        new Insertion.Bottom( replacementElement, replacementOptionsHtml );

        if( selectedLabel )
        {
            Element.update( replacementSelectedLabel, selectedLabel );
        }

        replacementOptions = $( replacementOptionsId );

        replacementOptionsElements = $$( '#' + replacementOptionsId + ' span' );

        // Hide input
        //select.hide( );
        replacementOptions.hide( );

        // Add click event listener to this select input
        replacementElement.observe( 'click', function ( event )
        {

            var eventSrc = getEventSrc( this, event );

            replacementOptions   = $( eventSrc.id + '-options' );
            replacementOptions.show( );

        } );

        // Options select handler
        replacementOptionsElements.each( function ( option ){

            option.observe( 'click', function ( event ){

                event.stopPropagation( );

                var option                       = getEventSrc( this, event ),
                        selectedValue            = option.getAttribute( 'data-value' ),
                        selectedIndex            = option.getAttribute( 'data-index' ),
                        replacementId            = option.getAttribute( 'data-parent' ),
                        replacementElement       = $( replacementId ),
                        replacementOptions       = $( replacementId + '-options' ),
                        previouslySelected       = replacementElement.down( '[data-selected="true"]' ),
                        replacementSelectedLabel = replacementElement.down( '.option-label' ),
                        selectName               = replacementElement.getAttribute( 'data-name' ),
                        select                   = $$( 'select[name="' + selectName + '"]' )[0],
                        selectedOption           = select.down( 'option[value="' + selectedValue + '"]' ),
                        currentSelectedOption    = select.down( 'option[selected]' );

                triggerEvent( select, 'click')
                // Hide replacement options
                replacementOptions.hide( );

                // Set visibly selected option on replacement select input
                Element.update( replacementSelectedLabel, option.innerHTML );

                // Unset previous selected option
                currentSelectedOption.removeAttribute( 'selected' );

                // Unset previous selected replacement option
                if( previouslySelected !== undefined )
                {
                    previouslySelected.removeAttribute( 'data-selected' );
                }

                // Set selected option
                selectedOption.setAttribute( 'selected', 'selected' );
                selectedOption.selected = true;

                // Set new selected replacement option attribute
                option.setAttribute( 'data-selected', 'true' );

                triggerEvent( select, 'change', function ( )
                {
                    triggerEvent( select, 'blur' );
                } );

                var dateSegment  = select.name.substring( select.name.indexOf( '_' ) + 1 ).toLowerCase(),
                    valDisplay   = $$( '.' + dateSegment + '-value' )[0],
                    indexDisplay = $$( '.' + dateSegment + '-index' )[0];
                valDisplay.update( select.value );
                indexDisplay.update( select.selectedIndex );
console.log(select.value);
console.log(select.selectedIndex);

            } );
        } );

    } );

}


triggerEvent = function( element, eventName, callback )
{
    if (document.createEvent)
    {
        var evt = document.createEvent( 'HTMLEvents' );
        evt.initEvent( eventName, true, true );

        if( callback && typeof( callback ) === "function" )
        {
            callback();
        }
        return element.dispatchEvent( evt );
    }

    if ( element.fireEvent ){
        return element.fireEvent( 'on' + eventName );
    }
}


getEventSrc = function ( object, event )
{
    if ( object === window )
    {
        return event.srcElement;
    }
    else
    {
        return object;
    }
}

Answers


Try using the the setValue() method. http://api.prototypejs.org/dom/Form/Element/prototype/setValue/

In your jsfiddle around line 90-92 add this line

select.setValue(selectedValue);

from what I can tell select is your drop down element, and selectedValue is the new value that you want to set to your drop down element.

Using setValue() cleans up all the mess of trying to set a drop down value consistently in all browsers. Further down your JS I saw you trying to set the attributes - this should handle it.

Also if you want clean drop down replacements I like using Chosen Drop Downs http://harvesthq.github.io/chosen/index.proto.html


When the select replacement is being initialised, setting the selectedIndex to -1 'activates' the element and thereafter enables it to be programmatically selected.

L.22

select['selectedIndex'] = -1;

I found this through sheer trial and error, so I don't have a proper explanation, but I will try to explain briefly how I came to it:

It seems that if the select is set to an option that has no value attribute, Firefox borks at setting the value with javascript.

From the behaviour when you click on the select input, I had a hunch (clearly nonsense!) that Firefox somehow wanted the element to be activated - if I could emulate that click, and wake it up, things would be fine, but I had no idea how.

I tried reverse engineering a jQuery plugin that actually worked to see if there was a cross browser way of setting the value, but nothing helped.

It was while messing with the values that I noticed the key to the replacement working seemed to be related to the CURRENT value of the select. The original select defaulted to an option with no value string (and the inputs are generated by the third party - I can't even control IDs and classes) Once the select had a value, changing it with the replacement worked fine.

Setting index to 0 didn't work, probably because the first tag didn't have a value. Setting to >0 worked, but it was not acceptable to have a value set by default, so I thought I'd give -1 a go, equivalent to deselecting all values, and... The rest is history. Not the good type though. Just a minor victory in the long slog across the muddy no-mans land of retrograde code. Honestly, if you saw some of it, you would cry tears of shame for the authors' mothers.

Working test version:

http://jsfiddle.net/DisasterMan/gUam3/8/


Need Your Help

Track how many times an event occurs over a period of time

python benchmarking

I'm working on a small RPG project in Python, and I have a couple of variables I need to track rates for.

Network Mask to Shorthand in XSL

xslt xslt-1.0 subnet

I'm running into a bit of a pickle where I'm working out network masks into shorthand (ie. 255.255.255.0 = /24)