Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I have multiple drop down lists (select & option) that are populated by data from the server. they all have the same options, but I need the use to be able to select every option only once - it can appear as selected only on one list.

this is the HTML of a single list - simple select & options list:

<div class="social-option">
    <select name="hex_theme_options[social-service-1]">
        <option selected="selected" value="0"></option>
        <option value="facebook">facebook</option>
        <option value="twitter">twitter</option>
        <option value="linkedin">linkedin</option>
        <option value="e-mail">e-mail</option>
        <option value="phone">phone</option>
        <option value="instagram">instagram</option>
        <option value="flickr">flickr</option>
        <option value="dribbble">dribbble</option>
        <option value="skype">skype</option>
        <option value="picasa">picasa</option>
        <option value="google-plus">google-plus</option>
        <option value="forrst">forrst</option>
    </select>
</div>

And this is the JS code for managing them the way I described. it works but it looks pretty ugly to me, I'd like to improve it's structure. any suggestions are welcome.

http://jsfiddle.net/ilyaD/4BBcZ/7/

(function($){
    $(document).ready(function() {

        var siblings = {
            lock: function (newSelected){
                var selectedSiblings = $('.social-option select').find("option[value=" + newSelected.val() + "]");
                selectedSiblings.not(newSelected).attr('disabled', 'disabled');
            },
            unlock: function (oldSelected){
                var selectedSiblings = $('.social-option select').find("option[value=" + oldSelected.val() + "]");
                selectedSiblings.removeAttr('disabled');
            },
            unlockZero: function (){
                $('.social-option select').find("option[value='0']").removeAttr('disabled');
            }
        };


        function checkSiblings(oldSelected, newSelected) {
            if (oldSelected === '0') {
                siblings.lock(newSelected);
            } else if (newSelected === '0') {
                siblings.unlock(oldSelected);
            } else {
                siblings.unlock(oldSelected);
                siblings.lock(newSelected);
            }
        }


        $('.social-option select').each(function() {
            siblings.lock($('option:selected', this));
            siblings.unlockZero();
        });

        $('.social-option select').on('focus', function () {
            var oldSelected = $('option:selected', this);

            $('.social-option select').on('change', function () {
                var newSelected = $('option:selected', this);
                checkSiblings(oldSelected, newSelected);
            });
        });


    });
})(jQuery);
share|improve this question

4 Answers

up vote 1 down vote accepted

I could be wrong, but I think you could shorten ALOT of the code as follows:

$(function () {
    $(".social-option select").on("change", function(e) {
        $(".social-option select option:disabled").prop("disabled", false);
        $(".social-option select option:selected").each(function(i) {
            var $val = $(this).val();
            if ($val !== '0') {
                $(".social-option select option[value="+$val+"]").prop("disabled", true);
            };
        });
    }).change();
})​;

See fiddle with dynamic drop down

added here

share|improve this answer
Sorry, I deleted the comment as soon as I realized I was wrong. – Bill Barry Jul 12 '12 at 20:48
@BillBarry LoL, it's ok, i deleted mine too! – SpYk3HH Jul 13 '12 at 4:16
@SpYk3HH tahnx, why did you use $val.length > 1 and not simply $val !== '0'? – IlyaD Jul 13 '12 at 8:43
eh, coulda gone either way. a string compare maybe less intensive by a nanosecond or two, but its all up to the end coder. – SpYk3HH Jul 14 '12 at 18:12

Here's what I would do.

   function checkMenus(menus) {
    // Get an array of all the selected values
    var values = $.map(menus.find('option:selected'),function(el,i){ return el.value; });

    /**
     * For each menu, loop through the values
     * array and disable the matching options,
     * unless it's currently selected
     */
    menus.each(function(){
        var opts = $(this).find('option');
        opts.prop('disabled',false);
        for(var i=0;i<values.length;i++) {
            opts.filter(':not(:selected)')
                .filter('[value='+values[i]+']')
                .prop('disabled',true)
                .end();
        }
    });
}

// Run the checkMenus function on document.ready
// and whenever one of the menus changes
$(function() {
    // Create a jQuery object containing the select menus you want to control
    var menus = $('.social-option');
    checkMenus(menus);
    menus.on('change',function(){ checkMenus(menus); });
});

Here's a demo:

http://jsfiddle.net/PD8Bt/

share|improve this answer
thanx, but there is an issue that the blank options get disabled too, I tried adding .filter("[value='0']") but it didn't help – IlyaD Jul 13 '12 at 10:29

This is really dense, but...

var $select = $('.social-option select');

function selectChange() {
    var $selected = $select.children('option:selected').not('[value=0]'),
        search = $selected.map(function () { 
            return 'option[value="' + this.value + '"]';
        }).get().join(','),
        $disabled = $select.children(search).not($selected).prop('disabled', true);

    $select.find('option[disabled]').not($disabled).prop('disabled', false);
}

$select.on('change', selectChange);
selectChange();

Fiddle

share|improve this answer

This might be a little more generic:

(function($) {
    var checkSiblings = function(group, oldSelected, newSelected) {
        group.find("option[value=" + oldSelected.val() + "]").removeAttr('disabled');
        group.find("option[value=" + newSelected.val() + "]").not(newSelected).attr('disabled', 'disabled');
    };
    $.fn.distinctValues = function() {
        var group = this;
        this.each(function(idx, selectBox) {
            $(selectBox).on('change', function() {
                var $this = $(this);
                var newSelected = $('option:selected', this);
                checkSiblings(group, $this.data('oldSelected'), newSelected);
                $this.data('oldSelected', newSelected);
            }).data('oldSelected', $('option:selected', this));
        });
    };
}(jQuery));


jQuery('.social-option select').distinctValues();​

It doesn't handle something it looks like you were trying to do but had only partially implemented, namely allow multiple different select boxes to share the empty option. I'm sure it can be done fairly simply, but my first attempt didn't work, and I'm out of time at the moment. (In other words, that's left as an exercise for the reader :-) )

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.