javascriptjqueryformsmultichoiceitems

Javascript to Select Multiple options with restrictions


I need a form with multiple steps where the first step restricts options in the successive steps.

So below is an example of use:

  1. User selects from 4 global options

    • If user selects option 1
      • Then the user can only select 8 options (no more no less) from a multiple choice box.
    • If user select option 2
      • Then the user can only select 10 options (no more no less) from a multiple choice box.
    • Same is the case with options 3 and 4
  2. After all these selections, this form has to show a final price based on the options user selected in the FIRST STEP. No matter what choices were selected in successive steps.

  3. When this form shows final price, user has to click SUBMIT button and send this result with all these options through an email.

Can someone help me in figuring out the javascript required to do this? Most important thing right now for me is to figure out 'restrictions codes' in multiple items choice boxes.

This is the form I was thinking about:

<form action="" id="menuform" onsubmit="return false;">
    <fieldset>
        <legend>Select one of this four menus</legend>
        <label >Menu Option</label>
            <input type="radio"  name="selectedmenu" value="Menu1"
onclick="calculateTotal()" />
        Menu 1 -  serves 8 courses ($20)
            <input type="radio"  name="selectedmenu" value="Menu2"
onclick="calculateTotal()" />
        Menu 2 - serves 12 courses ($25)
            <input type="radio"  name="selectedmenu" value="Menu3"
onclick="calculateTotal()" />
        Menu 3 - serves 16 courses ($35)
            <input type="radio"  name="selectedmenu" value="Menu4"
onclick="calculateTotal()" />
        Menu 4 - serves 30 courses ($75)

        <label >Filling</label>
            <select id="filling" name='filling'
onchange="calculateTotal()">
                <option value="dish1">Dish 1 ($1)</option>
                <option value="dish2">Dish 2 ($5)</option>
                <option value="dish3">Dish 3 ($5)</option>
                (more...)
            </select>
        <br/>
        <div id="totalPrice"></div>
    </fieldset>
</form>

Then, for Javascript code I was trying with this to store and return some values but my problem is how to force to select an exact number of options in the SECOND STEP:

var menu_prices = new Array();
    menu_prices["Menu1"]=20;
    menu_prices["Menu2"]=25;
    menu_prices["Menu3"]=35;
    menu_prices["Menu4"]=75;

function getMenuPrice()
{
    var menuPrice=0;
    var theForm = document.forms["menuform"];
    var selectedMenu = theForm.elements["selectedmenu"];
    for(var i = 0; i < selectedMenu.length; i++)
    {
        if(selectedMenu[i].checked)
        {
            menuPrice = menu_prices[selectedMenu[i].value];
            break;
        }
    }
    return menuPrice;
}

function getTotal()
{
    var menuPrice = getMenuPrice() + getOtherOptionsPrices();

    document.getElementById('totalPrice').innerHTML =
                                      "Total Price For Menu $"+menuPrice;
}

Solution

  • Structure your HTML markup carefully, which helps you to target elements via JavaScript easily. Especially, using data-attributes on radio buttons for quantity and price which could be then easily retrieved in respective event handlers. Something like this:

    <form action="" method="post" id="menuform" name="menuform">
        <fieldset>
            <legend>Select menu option</legend>
            <label>
                <input type="radio" name="selectedmenu" checked value="menu01" data-qty='3' data-price='20' />
                <span>1 -  serves 3 courses ($20)</span>
            </label>
            ...
        <fieldset>
            <legend id='fillingsPrompt'>Select fillings</legend>
            <select id="fillings" name="fillings" size="6" multiple>
                <option value="1">Dish 1 ($1)</option>
            ...
        <fieldset>
            <legend>Checkout</legend>
            <div id="totalPrice"></div>
            <input type="submit" value="Submit" />
        </fieldset>
        <fieldset>
            <legend>Messages</legend>
            <p id="result"></p>
        </fieldset>
    </form>
    

    Identify and select all elements that you'll need:

    var menuform        = document.getElementById('menuform'), 
        radios          = document.getElementsByName('selectedmenu'), 
        fillings        = document.getElementById('fillings'), 
        fillingsPrompt  = document.getElementById('fillingsPrompt'), 
        totalPrice      = document.getElementById('totalPrice'), 
        result          = document.getElementById('result'), 
        fillingsAllowed = 0, currentSelection = [], currency = '$'
    ;
    

    Add event listeners to your radio buttons, select, and the submit button:

    menuform.addEventListener('submit', handleSubmit);
    fillings.addEventListener('change', handleFillings);
    for (var i = radios.length; i--; ) {
        radios[i].addEventListener('change', handleLimit);
    }
    

    Code the actual event handlers:

    // When radio buttons are selected, update parameters for limit
    function handleLimit(e) { updateParameters(e.target); }
    
    // When options are selected in the dropdown, 
    // check against the limit and reset the selection if it exceeds
    function handleFillings(e) {
        var count = getSelectedCount();
        if (count > fillingsAllowed) { resetSelect(); }
        else { currentSelection = getSelectedValues(); }
    }
    
    // When submit button is clicked, 
    // check the count of selection against the limit, and
    // show appropriate error message
    function handleSubmit(e) {
        var count = getSelectedCount();
        e.preventDefault();
        if (count != fillingsAllowed) { 
            result.textContent = 'Must select exactly ' + fillingsAllowed + ' fillings!';
        } else {
            result.textContent = 'Ok. ';
        }
    }
    

    And then code all the helper functions used in the handlers above:

    function updateParameters(elem) {
        // update the limit based on quantity data attribute on radio
        fillingsAllowed = elem.getAttribute('data-qty');
        // show the amount based on price data-attribute
        totalPrice.textContent = 'Amount: ' + currency + elem.getAttribute('data-price');
        // show the hint on legend of fieldset for selecting options
        fillingsPrompt.textContent = 'Select ' + fillingsAllowed + ' fillings';
    }
    
    // iterate options and get count of selected ones
    function getSelectedCount() {
        var options = fillings.options, count = 0;
        for (var i=0; i < options.length; i++) {
            if (options[i].selected) count++;
        }
        return count;
    }
    
    // iterate options and get selected values in an array
    function getSelectedValues() {
        var options = fillings.options, values = [0];
        for (var i=0; i < options.length; i++) {
            if (options[i].selected) values.push(options[i].value);
        }
        return values;
    }
    
    // remove selection from all options, and 
    // re-select based on the array used in the previous function
    function resetSelect() {
        var options = fillings.options;
        for (var i=0; i < options.length; i++) {
            options[i].selected = false;
            if (currentSelection.indexOf(options[i].value) != -1) {
                options[i].selected = true;
            }
        }
    }
    

    Everything put together, the demo looks like this:

    Fiddle: https://jsfiddle.net/abhitalks/L813qudw/

    Snippet:

    var menuform 		= document.getElementById('menuform'), 
    	radios 			= document.getElementsByName('selectedmenu'), 
    	fillings 		= document.getElementById('fillings'), 
    	fillingsPrompt 	= document.getElementById('fillingsPrompt'), 
    	totalPrice 		= document.getElementById('totalPrice'), 
    	result 			= document.getElementById('result'), 
    	fillingsAllowed = 0, currentSelection = [], currency = '$'
    ;
    
    // listen to events 
    menuform.addEventListener('submit', handleSubmit);
    fillings.addEventListener('change', handleFillings);
    for (var i = radios.length; i--; ) {
        radios[i].addEventListener('change', handleLimit);
    }
    
    // event handlers
    function handleLimit(e) { updateParameters(e.target); }
    function handleFillings(e) {
    	var count = getSelectedCount();
    	if (count > fillingsAllowed) { resetSelect(); }
    	else { currentSelection = getSelectedValues(); }
    }
    function handleSubmit(e) {
    	var count = getSelectedCount();
    	e.preventDefault();
    	if (count != fillingsAllowed) { 
    		result.textContent = 'Must select exactly ' + fillingsAllowed + ' fillings!';
    	} else {
    		result.textContent = 'Ok. ';
    	}
    }
    
    // fire initial update based on the first radio
    updateParameters(radios[0]);
    
    // helper functions
    function updateParameters(elem) {
    	fillingsAllowed = elem.getAttribute('data-qty');
    	totalPrice.textContent = 'Amount: ' + currency + elem.getAttribute('data-price');
    	fillingsPrompt.textContent = 'Select ' + fillingsAllowed + ' fillings';
    }
    function getSelectedCount() {
    	var options = fillings.options, count = 0;
    	for (var i=0; i < options.length; i++) {
    		if (options[i].selected) count++;
    	}
    	return count;
    }
    function getSelectedValues() {
    	var options = fillings.options, values = [0];
    	for (var i=0; i < options.length; i++) {
    		if (options[i].selected) values.push(options[i].value);
    	}
    	return values;
    }
    function resetSelect() {
    	var options = fillings.options;
    	for (var i=0; i < options.length; i++) {
    		options[i].selected = false;
    		if (currentSelection.indexOf(options[i].value) != -1) {
    			options[i].selected = true;
    		}
    	}
    }
    fieldset { 
    	margin: 1vw; font-family: monospace; 
    	display: inline-block; width: 40vw; vertical-align: top;
    }
    legend { color: #d33; padding: 0px 4px; }
    legend::before { content: '[ '; }
    legend::after { content: ' ]'; }
    fieldset > label { display: block; margin: 4px 0px; }
    fieldset input, fieldset span { vertical-align: middle; }
    fieldset > select { width: 100%; font-family: monospace; }
    input[type=submit] { margin-top: 12px; }
    #totalPrice, #result { 
    	height: 24px; line-height: 24px; 
    	background-color: #dde; padding: 4px;
    	font-family: monospace;
    }
    #result { color: #d33; font-family: monospace; }
    <form action="" method="post" id="menuform" name="menuform">
    	<fieldset>
    		<legend>Select menu option</legend>
    		<label>
    			<input type="radio" name="selectedmenu" checked 
    				value="menu01" data-qty='3' data-price='20' />
    			<span>1 -  serves 3 courses ($20)</span>
    		</label>
    		<label>
    			<input type="radio" name="selectedmenu" 
    				value="menu02" data-qty='4' data-price='25' />
    			<span>2 - serves 4 courses ($25)</span>
    		</label>
    		<label>
    			<input type="radio" name="selectedmenu" 
    				value="menu03" data-qty='5' data-price='35' />
    			<span>3 - serves 5 courses ($35)</span>
    		</label>
    		<label>
    			<input type="radio" name="selectedmenu" 
    				value="menu04" data-qty='6' data-price='75' />
    			<span>4 - serves 6 courses ($75)</span>
    		</label>
    	</fieldset>
    	<fieldset>
    		<legend id='fillingsPrompt'>Select fillings</legend>
    		<select id="fillings" name="fillings" size="6" multiple>
    			<option value="1">Dish 1 ($1)</option>
    			<option value="2">Dish 2 ($5)</option>
    			<option value="3">Dish 3 ($5)</option>
    			<option value="4">Dish 4 ($1)</option>
    			<option value="5">Dish 5 ($5)</option>
    			<option value="6">Dish 6 ($5)</option>
    		</select>
    	</fieldset>
    	<fieldset>
    		<legend>Checkout</legend>
    		<div id="totalPrice"></div>
    		<input type="submit" value="Submit" />
    	</fieldset>
    	<fieldset>
    		<legend>Messages</legend>
    		<p id="result"></p>
    	</fieldset>
    </form>
    <hr>


    ...how can I change <option> and use <input type="checkbox"> instead for the SECOND STEP?

    In order to use checkboxes instead of select, no major changes are required.

    Changed mark-up:

    <fieldset>
        <legend id='fillingsPrompt'>Select fillings</legend>
        <label>
            <input type='checkbox' name='fillings' value="1" />
            <span>Dish 1 ($5)</span>
        </label>
        ...
    

    JavaScript changes:

    1. Adding the event-handlers for checkboxes instead of select, would require just iterating over those:

    (just like the radios already done)

    for (var i = fillings.length; i--; ) {
        fillings[i].addEventListener('change', handleFillings);
    }
    
    1. In all the helper functions, remove the variable declaration for options:

    (as it is now no longer required)

    var options = fillings.options
    
    1. And, In all the helper functions,

      change: options.length and options[i].selected

      to, fillings.length and fillings[i].checked respectively.

    That's it.

    Fiddle 2: https://jsfiddle.net/abhitalks/hp88wdfc/

    Snippet 2:

    var menuform = document.getElementById('menuform'), 
    	radios = document.getElementsByName('selectedmenu'), 
    	fillings = document.getElementsByName('fillings'), 
    	fillingsPrompt = document.getElementById('fillingsPrompt'), 
    	totalPrice = document.getElementById('totalPrice'), 
    	result = document.getElementById('result'), 
    	fillingsAllowed = 0, currentSelection = [], currency = '$'
    ;
    
    // listen to events 
    menuform.addEventListener('submit', handleSubmit);
    for (var i = fillings.length; i--; ) {
        fillings[i].addEventListener('change', handleFillings);
    }
    for (var i = radios.length; i--; ) {
        radios[i].addEventListener('change', handleLimit);
    }
    
    // event handlers
    function handleLimit(e) { updateParameters(e.target); }
    function handleFillings(e) {
    	var count = getSelectedCount();
    	if (count > fillingsAllowed) { resetSelect(); }
    	else { currentSelection = getSelectedValues(); }
    }
    function handleSubmit(e) {
    	var count = getSelectedCount();
    	e.preventDefault();
    	if (count != fillingsAllowed) { 
    		result.textContent = 'Must select exactly ' + fillingsAllowed + ' fillings!';
    	} else {
    		result.textContent = 'Ok. ';
    	}
    }
    
    // fire initial update based on the first radio
    updateParameters(radios[0]);
    
    // helper functions
    function updateParameters(elem) {
    	fillingsAllowed = elem.getAttribute('data-qty');
    	totalPrice.textContent = 'Amount: ' + currency + elem.getAttribute('data-price');
    	fillingsPrompt.textContent = 'Select ' + fillingsAllowed + ' fillings';
    }
    function getSelectedCount() {
    	var count = 0;
    	for (var i=0; i < fillings.length; i++) {
    		if (fillings[i].checked) count++;
    	}
    	return count;
    }
    function getSelectedValues() {
    	var values = [0];
    	for (var i=0; i < fillings.length; i++) {
    		if (fillings[i].checked) values.push(fillings[i].value);
    	}
    	return values;
    }
    function resetSelect() {
    	for (var i=0; i < fillings.length; i++) {
    		fillings[i].checked = false;
    		if (currentSelection.indexOf(fillings[i].value) != -1) {
    			fillings[i].checked = true;
    		}
    	}
    }
    fieldset { 
    	margin: 1vw; font-family: monospace; 
    	display: inline-block; width: 40vw; vertical-align: top;
    }
    legend { color: #d33; padding: 0px 4px; }
    legend::before { content: '[ '; }
    legend::after { content: ' ]'; }
    fieldset:first-of-type > label { display: block; margin: 4px 0px; }
    fieldset:nth-of-type(2) > label { 
    	display: inline-block; width: 45%; 
    }
    fieldset input, fieldset span { vertical-align: middle; }
    input[type=submit] { margin-top: 12px; }
    #totalPrice, #result { 
    	height: 24px; line-height: 24px; 
    	background-color: #dde; padding: 4px;
    	font-family: monospace;
    }
    #result { color: #d33; font-family: monospace; }
    <form action="" method="post" id="menuform" name="menuform">
    	<fieldset>
    		<legend>Select menu option</legend>
    		<label>
    			<input type="radio" name="selectedmenu" checked 
    				value="menu01" data-qty='3' data-price='20' />
    			<span>1 -  serves 3 courses ($20)</span>
    		</label>
    		<label>
    			<input type="radio" name="selectedmenu" 
    				value="menu02" data-qty='4' data-price='25' />
    			<span>2 - serves 4 courses ($25)</span>
    		</label>
    		<label>
    			<input type="radio" name="selectedmenu" 
    				value="menu03" data-qty='5' data-price='35' />
    			<span>3 - serves 5 courses ($35)</span>
    		</label>
    		<label>
    			<input type="radio" name="selectedmenu" 
    				value="menu04" data-qty='6' data-price='75' />
    			<span>4 - serves 6 courses ($75)</span>
    		</label>
    	</fieldset>
    	<fieldset>
    		<legend id='fillingsPrompt'>Select fillings</legend>
    		<label>
    			<input type='checkbox' name='fillings' value="1" />
    			<span>Dish 1 ($5)</span>
    		</label>
    		<label>
    			<input type='checkbox' name='fillings' value="2" />
    			<span>Dish 2 ($5)</span>
    		</label>
    		<label>
    			<input type='checkbox' name='fillings' value="3" />
    			<span>Dish 3 ($5)</span>
    		</label>
    		<label>
    			<input type='checkbox' name='fillings' value="4" />
    			<span>Dish 4 ($5)</span>
    		</label>
    		<label>
    			<input type='checkbox' name='fillings' value="5" />
    			<span>Dish 5 ($5)</span>
    		</label>
    		<label>
    			<input type='checkbox' name='fillings' value="6" />
    			<span>Dish 6 ($5)</span>
    		</label>
    	</fieldset>
    	<fieldset>
    		<legend>Checkout</legend>
    		<div id="totalPrice"></div>
    		<input type="submit" value="Submit" />
    	</fieldset>
    	<fieldset>
    		<legend>Messages</legend>
    		<p id="result"></p>
    	</fieldset>
    </form>
    <hr>