SitePoint Sponsor

User Tag List

Results 1 to 9 of 9
  1. #1
    SitePoint Enthusiast Does's Avatar
    Join Date
    Feb 2004
    Location
    USA
    Posts
    51
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Question Disable checkboxes when changing option

    I'm a complete novice when it comes to javascript, in fact this is my very first javascript function I have ever written.

    I'm trying to do the following:
    I have a form with several checkboxes and a pulldown.
    Based on what option you select with the pulldown, certain checkboxes should be disabled and unchecked and the text should change color.
    When you select Quest B you can't select Spell and Wand and they should be unselected.

    This is what I so far and it works
    HTML Code:
    <html>
    <head>
    <style type="text/css">
    .selected {color: #000000}
    .unselected {color: #00ff00 }
    </style>
    </head>
    <body>
    <script language="JavaScript">
    <!--
    function disable(z)
    {
        var l = z.sel.value;
        if (l == 'QB') var c=new Array(3,4);
        if (l == 'QC') var c=new Array(0,3);
        for (x=0;x<z.elements.length;x++){
            if (z.elements[x].type == 'checkbox'){
                z.elements[x].disabled = false;
                z.elements[x].parentNode.className = 'selected';
                var id=z.elements[x].id;
                for (i in c) {
                    if (id == c[i]) {
                        z.elements[x].checked = false;
                        z.elements[x].disabled = true;
                        z.elements[x].parentNode.className = 'unselected';
                    }
                }
            }
        }
    }
    //-->
    </script>
    <form name="gratis">
        <select name="sel" onchange="disable(document.forms.gratis)">
            <option value="QA">Quest A</option>
            <option value="QB">Quest B</option>
            <option value="QC">Quest C</option>
        </select>
        <br />
        <div class="selected">
            <input name="1" id="1" value="Light" type="checkbox"/> Light <BR />
        </div>
        <div class="selected">
            <input name="2" id="2" value="Books" type="checkbox"/> Books <BR />
        </div>
        <div class="selected">
            <input name="3" id="3" value="Spell" type="checkbox"/> Spell <BR />
        </div>
        <div class="selected">
            <input name="4" id="4" value="Wand" type="checkbox"/> Wand <BR />
        </div>
    </form>
    
    </body>
    </html>
    I'm wondering if there's a better or cleaner solution, jQuery solutions are also appreciated.

    Thank you
    Peter
    Last edited by Does; Aug 25, 2008 at 12:37.

  2. #2
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,718
    Mentioned
    103 Post(s)
    Tagged
    4 Thread(s)
    First of all, get rid of the comment tags surrounding the script. Their only purpose is to prevent Netscape 1 and Mosaic from barfing because javascript wasn't invented yet. That hack hasn't been required for many a millennia now.

    The BR elements are bad form (pardon the pun), and you have generic block-level divisions with divs. These should be paragraphs instead. Where you have several divs with class elements is a strong indication of abuse. Use paragraphs instead to contain inline text and you can't go wrong.

    The class="selected" attribute isn't needed as it applies a style that sets the color to the already default color of black. You don't need to set default colors, and you don't need to apply classes to define what is already set.

    The id attributes are superfluous and can be removed. named elements should be used instead to access form elements. On the flipside, forms should be identified by an identifier, not a name attribute.

    It would help if people can click the text for the checkbox to select it, so you can use the label tag to achieve this. You can easily do this without using lots of identifier attributes by wrapping the label around the input element and the text.

    The inline event registration should not be used to attach form events. Instead use the traditional event registration.

    That leaves the form looking like this:

    Code html4strict:
    <form id="gratis">
        <select name="sel">
            <option value="QA">Quest A</option>
            <option value="QB">Quest B</option>
            <option value="QC">Quest C</option>
        </select>
        <p><label><input type="checkbox" name="1" value="Light"> Light</label></p>
        <p><label><input type="checkbox" name="2" value="Books"> Books</label></p>
        <p><label><input type="checkbox" name="3" value="Spell"> Spell</label></p>
        <p><label><input type="checkbox" name="4" value="Wand"> Wand</label></p>
    </form>

    When attaching events to form elements from javascript, you need to run the script after the elements are available. This can either be done from the head by running the code inside a window.onload function, or preferably by placing the script at the end of the body, just before the </body> tag. Not only does this allow fast and easy access to the page elements, but it also speeds up the loading of the page. That it's a best practice for speeding up your web site also helps too.

    Attach the function to the event with the following:

    Code javascript:
    var form = document.getElementById('gratis');
    form.elements.sel.onchange = disable;

    You don't need to pass the form as a parameter because the function has a reference to the form element, and from that you can get to the whole form itself.

    Use well named variables in your scripts. Why? Well what on earth do the following lines do?

    Code javascript:
    for (i in c) {
        if (id == c[i]) {

    It's impossible to tell without finding out how c was declared. Instead of using a mystery array, let's make it easier to tell what's going on by organising them in the following construct:

    Code javascript:
    var disabled = {
    	'QB': [3, 4],
    	'QC': [0, 3]
    };

    The loop counters aren't initialized, so they're going to become global variables. This is a practice that's best avoided. The loop also refers to the same elements all the time, which should be assigned to a variable instead.

    So now we have the following variables declared, and the beginnings of a loop

    Code javascript:
    function disable()
    {
    	var form = this.form,
    		quest = form.elements.sel.value,
    		disabled = {
    		'QB': [3, 4],
    		'QC': [0, 3]
    	},
    		el,
    		i,
    		name;
        if (disabled[quest]) {
    		for (i = 0; i < form.elements.length; i +=1) {
    			el = form.elements[i];

    If we have a set of rules for quest, we can either enable or disable each checkbox depending on if the element name is in there or not. We got rid of the id before but that's all right, because the form elements all have names that we can use instead.

    We can use a function called inArray to check of a value is contained. JavaScript versions 1.6 and above have an indexOf method, but IE only supports to version 1.3.

    Code javascript:
    function inArray(array, value) {
    	for (var i = 0; i < array.length; i += 1) {
    		if (value === array[i]) {
    			return true;
    		}
    	}
    }

    And we can use it as follows:

    Code javascript:
    if (el.type == 'checkbox') {
    	if (inArray(disabled[quest], parseInt(el.name, 10))) {
    		el.checked = false;
    		el.disabled = true;
    		el.parentNode.className = 'unselected';
    	} else {
    		el.disabled = false;
    		el.parentNode.className = '';
    	}
    }

    The value from the name attribute was explicitly converted to an integer, because it's bad to rely on loose typing when comparing values.

    In full, here is the updated script code

    Code javascript:
    function inArray(array, value) {
    	for (var i = 0; i < array.length; i += 1) {
    		if (value === array[i]) {
    			return true;
    		}
    	}
    }
    function disable()
    {
    	var form = this.form,
    		quest = form.elements.sel.value,
    		disabled = {
    		'QB': [3, 4],
    		'QC': [0, 3]
    	},
    		el,
    		i, j;
        if (disabled[quest]) {
    		for (i = 0; i < form.elements.length; i +=1) {
    			el = form.elements[i];
    	        if (el.type == 'checkbox') {
    				if (inArray(disabled[quest], parseInt(el.name, 10))) {
    					el.checked = false;
    					el.disabled = true;
    					el.parentNode.className = 'unselected';
    				} else {
    					el.disabled = false;
    					el.parentNode.className = '';
    				}
    			}
    		}
    	}
    }
    var form = document.getElementById('gratis');
    form.elements.sel.onchange = disable;

    And here is how the document should be structured

    Code html4strict:
    <html>
    <head>
    </head>
    <body>
    ...
    <script>
    </script>
    </body>
    </html>
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  3. #3
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,718
    Mentioned
    103 Post(s)
    Tagged
    4 Thread(s)
    You can get even better code by placing the requirements in the select options themself.

    Code html4strict:
    <select name="sel">
        <option value="QA">Quest A</option>
        <option value="QB" class="not3 not4">Quest B</option>
        <option value="QC" class="not3">Quest C</option>
    </select>

    And using a hasClass() function instead that look like this

    Code javascript:
    function hasClass(el, text) {
    	var re = new RegExp('(^|\\s)' + text + '(\\s|$)');
    	if (el.className.match(re)) {
    		return true;
    	}
    }

    This leaves the rest of the script looking like the following:

    Code javascript:
    function disable()
    {
    	var form = this.form,
    		sel = form.elements.sel,
    		quest = sel.options[sel.selectedIndex],
    		el,
    		i, j;
    	for (i = 0; i < form.elements.length; i +=1) {
    		el = form.elements[i];
    		if (el.type == 'checkbox') {
    			if (hasClass(quest, 'not'+el.name)) {
    				el.checked = false;
    				el.disabled = true;
    				el.parentNode.className = 'unselected';
    			} else {
    				el.disabled = false;
    				el.parentNode.className = '';
    			}
    		}
    	}
    }
    var form = document.getElementById('gratis');
    form.elements.sel.onchange = disable;
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  4. #4
    SitePoint Enthusiast Does's Avatar
    Join Date
    Feb 2004
    Location
    USA
    Posts
    51
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Awesome replies Paul, thank you SO much.
    So I did some rewriting and here's what I came up with:
    Code:
    <html>
    <head>
    <title>Title</title>
    <style type="text/css">
    	.disabled { color : green }
    </style>
    </head>
    <body>
    	<form id="gratis" action="" method="post">
    		<select name="sel">
    			<option value="QA"> Quest A </option>
           			<option value="QB"> Quest B </option>
    		       	<option value="QC"> Quest C </option>
       		</select>
    		<p><label><input type="checkbox" name="choices[]" value="light"> Light </label></p>
    		<p><label><input type="checkbox" name="choices[]" value="books"> Books </label></p>
    		<p><label><input type="checkbox" name="choices[]" value="spell"> Spell </label></p>
    		<p><label><input type="checkbox" name="choices[]" value="wand"> Wand </label></p>
    		<p><input type="submit" name="submit" value="Submit"</p>
    	</form>
    
    <script type="text/javascript">
    function inArray(array, value) {
    	for (var i = 0; i < array.length; i += 1) {
    		if (value === array[i]) {
    			return true;
    		}
    	}
    }
    
    function modify(){
    
    	var Form = this.form,
    		Selected = Form.elements.sel.value,
    		Disabled = {
    		'QB': ['spell' , 'wand'],
    		'QC': ['spell']
    		},
    		Element;
    
    	if (Disabled[Selected]) {
    		for (i = 0; i < Form.elements.length; i++) {
    			Element = Form.elements[i];
    			if (Element.type == 'checkbox') {
    				if (inArray(Disabled[Selected], Element.value)) {
    				 	Element.disabled = true; 
    				 	Element.checked = false;
    					Element.parentNode.className = 'disabled';
    				} else {
    					Element.disabled = false; 
    			 		Element.parentNode.className = '';
    				}
    			}
    		}
    	}
    }
    
    var form=document.getElementById('gratis');
    form.elements.sel.onchange = modify;
    
    </script>
    	</body>
    </html>
    I'm using the value of the checkboxes to make it more clear what's happening and I can move the checkboxes around if needed. Also as this will be part of a PHP script it makes processing easier to use choices[] as the name.

    The HTML layout will change in the final version

    Suggestions are still welcome.

  5. #5
    SitePoint Evangelist
    Join Date
    Apr 2008
    Location
    Dublin, Ireland
    Posts
    461
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Would id values for the form elements not be useful if we decide to make them more accessible friendly and use labels to reference them?

    I've always wondered which is the best way to access a form element, by id (if we give them an id) or through the forms.elements. Has anyone ever tested it for performance etc.?

  6. #6
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,718
    Mentioned
    103 Post(s)
    Tagged
    4 Thread(s)
    The name attribute is required for form elements, and can easily be accessed via form.elements

    When id attributes are used on form elements you introduce management problems, in that each and every id must be unique. It also limits the usefulness of scripts.
    If you have two similar forms on the same page they can use the same name for the form elements, but the identifiers must be different. Using named elements instead of identifiers, you can pass a different form identifier and use the same script across them.

    Code html4strict:
    <form id="login">
    <p><label>Name: <input type="text" name="name"></label></p>
    <p><label>Password: <input type="password" name="password" password="password"><label></p>
    <p><input type="submit" value="Login"></p>
    </form>
    <form id="subscription">
    <p><label>Name: <input type="text" name="name"></label></p>
    <p><label>Email: <input type="text" name="email"><label></p>
    <p><input type="submit" value="Subscribe"></p>
    </form>

    When accessing page elements, whether it be via css or javascript, traversal of the DOM should be used to access different elements. Where that's difficult to achieve, only then should identifiers be considered, but because they must be unique, you should use them sparingly. Identifiers should be used to identify large unique groups of elements, whereas class names should be used to identify certain specific ones. Where many elements have the same classname, it's time to rethink how it should be structured.
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  7. #7
    SitePoint Evangelist
    Join Date
    Apr 2008
    Location
    Dublin, Ireland
    Posts
    461
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by pmw57 View Post
    The name attribute is required for form elements, and can easily be accessed via form.elements.
    Well I was never suggesting we not use name attributes. I'm well aware they are required.

    Quote Originally Posted by pmw57 View Post
    When id attributes are used you introduce management problems, in that each and every id must be unique. It also limits the usefulness of scripts. If you have two similar forms on the same page they can use the same name for the form elements, but the identifiers must be different. With named elements, you can pass a different form identifier and use the same script.
    Permissible or not I never want to use duplicate names just from a clarity perspective. I want my forms to be accessible so I will also use fieldsets and labels and therefore I need an id to put in the for attribute. I work in .net and the VS IDE will always warn me if I use a duplicate id value. I don't find it that difficult to manage it at all. I think if your form has so many elements that it becomes difficult to manage then it might be getting out of hand.

    Quote Originally Posted by pmw57 View Post
    When accessing page elements, whether it be via css or javascript, you should first use traversal of the DOM to gain access to them. Where that's difficult to achieve, only then should identifiers be considered, but because they must be unique, you should use them sparingly. Identifiers should be used to identify large groups of elements, whereas class names should be used to identify certain specific ones.
    Interesting. I always assumed that getElementById was the function that traversed the DOM wheras the form.elements is an internally held array of the form elements and therefore might be quicker. I am geniunely curious to know which is more efficient/faster. Not sure where class names enters into this discussion but then we have already veered off the OP's question.

  8. #8
    Unobtrusively zen silver trophybronze trophy
    paul_wilkins's Avatar
    Join Date
    Jan 2007
    Location
    Christchurch, New Zealand
    Posts
    14,718
    Mentioned
    103 Post(s)
    Tagged
    4 Thread(s)
    Quote Originally Posted by BrianOConnell View Post
    Permissible or not I never want to use duplicate names just from a clarity perspective. I want my forms to be accessible so I will also use fieldsets and labels and therefore I need an id to put in the for attribute.
    That depends on how you arrange them. If the label is separate from the content that it's related to then you do need the identifier to link them together.

    However, you can associate the label with the form element inplicitly, without requiring the for attribute or id element, by nesting the element within the label element.
    http://www.w3.org/TR/html401/interac....html#h-17.9.1

    Here is a comparison of the two methods:

    Code html4strict:
    <p>
        <label for="login-name">Name:</label>
        <input type="text" name="name" id="login-name">
    </p>

    Code html4strict:
    <p><label>Name: <input type="text" name="name"></label></p>

    Quote Originally Posted by BrianOConnell View Post
    Interesting. I always assumed that getElementById was the function that traversed the DOM wheras the form.elements is an internally held array of the form elements and therefore might be quicker. I am geniunely curious to know which is more efficient/faster.
    On my system 10,000 assignments by document.getElementById averages at 282 ms whereas by form.elements averages at 265 ms
    Programming Group Advisor
    Reference: JavaScript, Quirksmode Validate: HTML Validation, JSLint
    Car is to Carpet as Java is to JavaScript

  9. #9
    SitePoint Evangelist
    Join Date
    Apr 2008
    Location
    Dublin, Ireland
    Posts
    461
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yeah I'm a .net developer so I can use asp:labels and associate them with other controls to force rendering of labels but they never render around the element unfortunately.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •