2

I'm trying to use this Bootstrap snippet: http://bootsnipp.com/snippets/featured/dynamic-form-fields-add-new-field-on-focus-or-change

I'm using it in 2 different modal dialogs on the same page.

I implement it as they suggest on the link above:

HTML:

<div class="container">
    <h3>Selects</h3>
    <div class="row">
        <div class="form-group form-group-multiple-selects col-xs-11 col-sm-8 col-md-4">
            <div class="input-group input-group-multiple-select col-xs-12">
                <select class="form-control" name="values[]">
                    <option value="">Select one</option>
                    <option value="1">Option 1</option>
                    <option value="2">Option 2</option>
                    <option value="3">Option 3</option>
                    <option value="4">Option 4</option>
                </select>
                <span class="input-group-addon input-group-addon-remove">
                    <span class="glyphicon glyphicon-remove"></span>
                </span>
            </div>
        </div>
    </div>
</div>

Javascript:

/* 
    Text fields 
*/
$(function(){
    $(document).on('focus', 'div.form-group-options div.input-group-option:last-child input', function(){
        var sInputGroupHtml = $(this).parent().html();
        var sInputGroupClasses = $(this).parent().attr('class');
        $(this).parent().parent().append('<div class="'+sInputGroupClasses+'">'+sInputGroupHtml+'</div>');
    });

    $(document).on('click', 'div.form-group-options .input-group-addon-remove', function(){
        $(this).parent().remove();
    });
});

/* 
    Selects 
*/
$(function(){

    var values = new Array();

    $(document).on('change', '.form-group-multiple-selects .input-group-multiple-select:last-child select', function(){

        var selectsLength = $('.form-group-multiple-selects .input-group-multiple-select select').length;
        var optionsLength = ($(this).find('option').length)-1;

        if(selectsLength < optionsLength){
            var sInputGroupHtml = $(this).parent().html();
            var sInputGroupClasses = $(this).parent().attr('class');
            $(this).parent().parent().append('<div class="'+sInputGroupClasses+'">'+sInputGroupHtml+'</div>');  
        }

        updateValues();

    });

    $(document).on('change', '.form-group-multiple-selects .input-group-multiple-select:not(:last-child) select', function(){

        updateValues();

    });

    $(document).on('click', '.input-group-addon-remove', function(){
        $(this).parent().remove();
        updateValues();
    });

    function updateValues()
    {
        values = new Array();
        $('.form-group-multiple-selects .input-group-multiple-select select').each(function(){
            var value = $(this).val();
            if(value != 0 && value != ""){
                values.push(value);
            }
        });

        $('.form-group-multiple-selects .input-group-multiple-select select').find('option').each(function(){
            var optionValue = $(this).val();
            var selectValue = $(this).parent().val();
            if(in_array(optionValue,values)!= -1 && selectValue != optionValue)
            {
                $(this).attr('disabled', 'disabled');
            }
            else
            {
                $(this).removeAttr('disabled');
            }
        });
    }

    function in_array(needle, haystack){
        var found = 0;
        for (var i=0, length=haystack.length;i<length;i++) {
            if (haystack[i] == needle) return i;
            found++;
        }
        return -1;
    }
});

This works completely as expected, when only using it once on a page. But when using it 2 times on the same page, they conflict and does not work correct. I tried changing all the id's to idname2 (with a 2 at the end) and did a copy of the entire javascript replacing all the id names with 2 at the end as well. While this works, it also seams like a very bad way of implementing this.

Can you help me figure out how I could implement it, so that I can use this snipped at least 2 times on the same page? Any help will be very appreciated.

3
  • Just a suggestion but, have you considered using KnockoutJS instead? knockoutjs.com/examples/betterList.html & knockoutjs.com/examples/gridEditor.html , one solution to the above would be to pass a parent wrapping dom element to all the jquery selectors that are referencing classes in that code but it would probably be a lot cleaner if you tried KO and had a viewmodel with observable arrays for each of the select lists you want Commented Jun 17, 2014 at 0:46
  • @Pricey Didn't know about KnockoutJS, I will have a look at it. Can it be used with dropdowns as well? - the reason I like the snippet above is that it is made with bootstrap and I don't need to load any external javascript libs. Could you explain how I got started with wrapping the dom elements? I'm not that experienced with javascript, but I'm open to try it out. Commented Jun 17, 2014 at 0:51
  • I use bootstrap and Knockout all the time, I would suggest you try the KO tutorials learn.knockoutjs.com, that will be much simpler and more reliable for you if you are not too experienced with javascript. Commented Jun 17, 2014 at 0:53

1 Answer 1

1

I know this isn't adjusting the javascript you provided but this should provide you a basis for a better structure you can build on.. here is the basic suggested example using Knockout v3 and the bootstrap css and markup you provided.

Fiddle here: http://jsfiddle.net/n6ngC/55/

UPDATED fiddle: http://jsfiddle.net/n6ngC/85

Knockout is lightweight and can be downloaded from http://knockoutjs.com/downloads/index.html (if you are using NuGet then get it from there instead)

This needs a bit more work because an item is initially always being added due to the change of the dropdown.. I would suggest using a button for the new selection instead, but obviously that depends on requirements.

This does not currently keep track of changes to the selections you have made after making them, but changing the observable array to contain observable items would fix that.

See below: HTML

<div class="container">
    <h3>Meals</h3>
    <div class="row" data-bind="foreach: meals">
        <div class="form-group form-group-multiple-selects col-xs-11 col-sm-8 col-md-4">
            <div class="input-group input-group-multiple-select col-xs-12">
                <select class="form-control" data-bind="options: $root.availableMeals, value: $data, optionsValue: 'id', optionsText: 'text'">
                </select>
                <span class="input-group-addon input-group-addon-remove" data-bind="click: $root.removeMealOption">
                    <span class="glyphicon glyphicon-remove"></span>
                </span>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="form-group form-group-multiple-selects col-xs-11 col-sm-8 col-md-4">
            <div class="input-group input-group-multiple-select col-xs-12">
                <select class="form-control" data-bind="options: availableMeals, value: newMeal, optionsValue: 'id', optionsText: 'text'">
                </select>
            </div>
        </div>
    </div>    
</div>

<div class="container">
    <h3>People</h3>
    <div class="row" data-bind="foreach: people">
        <div class="form-group form-group-multiple-selects col-xs-11 col-sm-8 col-md-4">
            <div class="input-group input-group-multiple-select col-xs-12">
                <select class="form-control" data-bind="options: $root.availablePeople, value: $data, optionsValue: 'id', optionsText: 'text'">
                </select>
                <span class="input-group-addon input-group-addon-remove" data-bind="click: $root.removePersonOption">
                    <span class="glyphicon glyphicon-remove"></span>
                </span>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="form-group form-group-multiple-selects col-xs-11 col-sm-8 col-md-4">
            <div class="input-group input-group-multiple-select col-xs-12">
                <select class="form-control" data-bind="options: availablePeople, value: newPerson, optionsValue: 'id', optionsText: 'text'">
                </select>
            </div>
        </div>
    </div>    
</div>

Javascript:

function MyViewModel() {
    var self = this;

    self.newMeal = ko.observable();
    self.newPerson = ko.observable();

    // the available list of options for each dropdown    
    self.availableMeals = [
        { id:"ST", text: "Standard (sandwich)" },
        { id: "PR", text: "Premium (lobster)" },
        { id: "UL", text: "Ultimate (whole zebra)" }
    ];

    self.availablePeople = [
        { id:"ST", text: "Steve" },
        { id: "BT", text: "Bert" },
        { id: "ER", text: "Ernie" }
    ];   

    // the selected values
    // this will be a list of objects that have their own observable properties
    self.meals = ko.observableArray([]); 
    self.people = ko.observableArray([]);

    // separate add and remove options
    self.addMealOption = function(meal) { self.meals.push(meal); }
    self.removeMealOption = function(meal) { self.meals.remove(meal); }

    self.addPersonOption = function(person) { self.people.push(person); }
    self.removePersonOption = function(person) { self.people.remove(person); } 

    self.newMeal.subscribe(function(value) {
        self.addMealOption(value);
    });

    self.newPerson.subscribe(function(value) {
        self.addPersonOption(value);
    }); 
}

// Activates knockout.js
ko.applyBindings(new MyViewModel());
Sign up to request clarification or add additional context in comments.

5 Comments

Really, big thanks for your work. I did some of the knockoutJS tuts last night, but still felt I had some way to go before being here. I gave you an upvote for this, but I will leave the question open if someone takes up the challenge for the orginal question. I will try to implement this, and if I figure out how to 'fix' that you can add the same item multiple times, I will post my modifications/addons here. Again thanks a lot for this!
@Bolli If you don't want to add the same item multiple times then to give an example for that you can change the availableMeals to an observableArray and then use a computed observable instead for the new dropdown selection, which filters the array by removing any self.meals selections from what is available. Take a look at the KO utility functions particularly ko.utils.arrayFilter knockmeout.net/2011/04/utility-functions-in-knockoutjs.html The knockmeout.net website is a brilliant source of information, written by the guy who works on KO.
@Bolli If i get some free time I'll look at updating the fiddle example to something that is more "production ready"
Cool! I'm playing with it locally now - will post some code if/when I figure it out.
@Bolli Here is an "in progress" update jsfiddle.net/n6ngC/85 it has a few bugs that need fixing and the selected options when changed are not yet updated automatically, but this is a step in the right direction... I will update the answer once this is completed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.