Jump to content

Microsoft browers and checkbox form elements?


maxxd
Go to solution Solved by kicken,

Recommended Posts

Hey y'all, I've got a weird one here and was hoping someone had a word or two of wisdom. I've got a php script that outputs a couple banks of checkboxes in a dynamic form. I've also written a JavaScript script that will sort through the form elements on page load and hide a form element, replacing it with a setup of divs that I can then style. I've got the JS working to emulate form element interaction depending on form element type and state.

To cut way back (I hope) on the TL;DR quotient, I've cut the code down pretty significantly below. Hopefully it still makes sense.

Here's the JS:

;function StylishForms(frm){
    "use strict";
    var     _form,
            _overlay,
            _elementHelpers = {},
            _elementTotals = {
                'checkbox' : 0,
                'selects' : 0,
                'text' : 0,
            };
    kickOff(frm);

    function kickOff(frm){
        if(typeof frm === 'string'){
            _form = document.forms[frm];
        }else{
            if(typeof _form.jquery !== 'undefined'){
                _form = document.forms[_form.attr('id')];
            }else{
                _form = frm;
            }
        }
        for(var i in _form.elements){
            console.log(_form.elements[i].type + ' : ' + _form.elements[i].name);
        }
    }
}

Second JS file:

(function($){
    if($('form').length > 0){
        $('form').each(function(){
            StylishForms( $(this) );
        });
    }
}) (jQuery);

And the HTML:

<form action="" method="post" class="wpcf7-form testing-forms" novalidate="novalidate" id="test_1">
    <p>
        <span class="wpcf7-form-control-wrap selector">
            <select name="selector" class="wpcf7-form-control wpcf7-select selector" id="testing" aria-invalid="false">
                <option value="first option">first option</option>
                <option value="option 1">option 1</option>
                <option value="option 2">option 2</option>
                <option value="option 3">option 3</option>
                <option value="option 4">option 4</option>
                <option value="option 5">option 5</option>
                <option value="option 6">option 6</option>
                <option value="option 7">option 7</option>
            </select>
        </span>
    </p>
    <p>
        <span class="wpcf7-form-control-wrap secondselect">
            <select name="secondselect" class="wpcf7-form-control wpcf7-select" id="test_more" aria-invalid="false">
                <option value="Second Option 1">Second Option 1</option>
                <option value="Second Option 2">Second Option 2</option>
                <option value="Second Option 3" selected="selected">Second Option 3</option>
                <option value="Second Option 4">Second Option 4</option>
            </select>
        </span>
    </p>
    <p>
        <span class="wpcf7-form-control-wrap checkTester">
            <span class="wpcf7-form-control wpcf7-checkbox" id="checkTesterOption">
                <span class="wpcf7-list-item first">
                    <input type="checkbox" name="checkTester[]" value="Option 1" /> 
                    <span class="wpcf7-list-item-label">Option 1</span>
                </span>
                <span class="wpcf7-list-item">
                    <input type="checkbox" name="checkTester[]" value="Option 2" /> 
                    <span class="wpcf7-list-item-label">Option 2</span>
                </span>
                <span class="wpcf7-list-item last">
                    <input type="checkbox" name="checkTester[]" value="Option 3" /> 
                    <span class="wpcf7-list-item-label">Option 3</span>
                </span>
            </span>
        </span>
        <br />
        <span class="wpcf7-form-control-wrap checkTester2">
            <span class="wpcf7-form-control wpcf7-checkbox" id="checkTesterOption2">
                <span class="wpcf7-list-item first last">
                    <input type="checkbox" name="checkTester2[]" value="Option 2-1" /> 
                    <span class="wpcf7-list-item-label">Option 2-1</span>
                </span>
            </span>
        </span>
    </p>
    <p>
        <span class="wpcf7-form-control-wrap text_testing">
            <input type="text" name="text_testing" value="" size="40" class="wpcf7-form-control wpcf7-text" aria-invalid="false" />
        </span>
    </p>
    <p>
        <input type="submit" value="submit" class="wpcf7-form-control wpcf7-submit" />
    </p>
</form>

And finally, the console output:

select-one : selector
select-one : secondselect
undefined : undefined
undefined : undefined
undefined : undefined
checkbox : checkTester2[]
text : text_testing
submit :

The select and text objects work exactly as expected across OS's and browsers - it's the checkboxes that are the issue. In Firefox, Opera, and Chrome on Windows, Mac, IOS, and Android they work as expected. In Safari on IOS and Mac, everything works as expected. On IE 10, 11, or Edge (obviously on Windows), they fail almost entirely. You can see from the console output that the only time Edge reports a checkbox is when there's only one associated with the name (in this case, checkTester2[]). If there's more than one checkbox, it reports both the input type and name as undefined.

As much as I'd love to simply say "people using MS-based browsers need to stop doing that", unfortunately I can't. But I also can't find what I'm missing here. Anyone see anything that I don't? Any and all help is much appreciated.

Link to comment
Share on other sites

How about using explicit indexes so that the names are in fact different?

<input type="checkbox" name="checkTester[0]" value="Option 1">
<input type="checkbox" name="checkTester[1]" value="Option 2">
<input type="checkbox" name="checkTester[2]" value="Option 3">

The resulting PHP array won't be as “pretty”, because unchecked boxes will lead to index gaps. But that should be entirely irrelevant and can easily be fixed with array_values() if necessary.

Link to comment
Share on other sites

How about using explicit indexes so that the names are in fact different?

<input type="checkbox" name="checkTester[0]" value="Option 1">
<input type="checkbox" name="checkTester[1]" value="Option 2">
<input type="checkbox" name="checkTester[2]" value="Option 3">

The resulting PHP array won't be as “pretty”, because unchecked boxes will lead to index gaps. But that should be entirely irrelevant and can easily be fixed with array_values() if necessary.

 

Very much worth exploring - thanks for the idea. Unfortunately, it's a WordPress site and contact form plugin, so I'm not entirely sure it's possible, but I'm definitely going to check it out now - thanks again!

Link to comment
Share on other sites

Did I hear somebody ask for an analysis? No? TOO BAD!

 

Demo:

<html><body>
<form action="" method="post" class="wpcf7-form testing-forms" novalidate="novalidate" id="test_1">
    <p>
        <span class="wpcf7-form-control-wrap selector">
            <select name="selector" class="wpcf7-form-control wpcf7-select selector" id="testing" aria-invalid="false">
                <option value="first option">first option</option>
                <option value="option 1">option 1</option>
                <option value="option 2">option 2</option>
                <option value="option 3">option 3</option>
                <option value="option 4">option 4</option>
                <option value="option 5">option 5</option>
                <option value="option 6">option 6</option>
                <option value="option 7">option 7</option>
            </select>
        </span>
    </p>
    <p>
        <span class="wpcf7-form-control-wrap secondselect">
            <select name="secondselect" class="wpcf7-form-control wpcf7-select" id="test_more" aria-invalid="false">
                <option value="Second Option 1">Second Option 1</option>
                <option value="Second Option 2">Second Option 2</option>
                <option value="Second Option 3" selected="selected">Second Option 3</option>
                <option value="Second Option 4">Second Option 4</option>
            </select>
        </span>
    </p>
    <p>
        <span class="wpcf7-form-control-wrap checkTester">
            <span class="wpcf7-form-control wpcf7-checkbox" id="checkTesterOption">
                <span class="wpcf7-list-item first">
                    <input type="checkbox" name="checkTester[]" value="Option 1" /> 
                    <span class="wpcf7-list-item-label">Option 1</span>
                </span>
                <span class="wpcf7-list-item">
                    <input type="checkbox" name="checkTester[]" value="Option 2" /> 
                    <span class="wpcf7-list-item-label">Option 2</span>
                </span>
                <span class="wpcf7-list-item last">
                    <input type="checkbox" name="checkTester[]" value="Option 3" /> 
                    <span class="wpcf7-list-item-label">Option 3</span>
                </span>
            </span>
        </span>
        <br />
        <span class="wpcf7-form-control-wrap checkTester2">
            <span class="wpcf7-form-control wpcf7-checkbox" id="checkTesterOption2">
                <span class="wpcf7-list-item first last">
                    <input type="checkbox" name="checkTester2[]" value="Option 2-1" /> 
                    <span class="wpcf7-list-item-label">Option 2-1</span>
                </span>
            </span>
        </span>
    </p>
    <p>
        <span class="wpcf7-form-control-wrap text_testing">
            <input type="text" name="text_testing" value="" size="40" class="wpcf7-form-control wpcf7-text" aria-invalid="false" />
        </span>
    </p>
    <p>
        <input type="submit" value="submit" class="wpcf7-form-control wpcf7-submit" />
    </p>
</form>
<pre>
</pre>

<script type="text/javascript">
function typeofex(value) {
	var t = typeof(value);
	return t == "object" && value["constructor"] && value["constructor"]["name"] ? t + "(" + value.constructor.name + ")" : t;
}
var pre = document.getElementsByTagName("pre")[0];
for (var i in document.forms) {
	for (var j in document.forms[i].elements) {
		pre.innerHTML = pre.innerHTML + "forms[" + i + "].elements[" + j + "] is a " + typeofex(document.forms[i].elements[j]) + "\n" +
			".name = '" + document.forms[i].elements[j].name + "'\n" +
			".type = '" + document.forms[i].elements[j].type + "'\n";
	}
}
</script>
</body></html>
Output from Chrome 51.0.2704.103:

forms[0].elements[0] is a object(HTMLSelectElement)
.name = 'selector'
.type = 'select-one'
forms[0].elements[1] is a object(HTMLSelectElement)
.name = 'secondselect'
.type = 'select-one'
forms[0].elements[2] is a object(HTMLInputElement)
.name = 'checkTester[]'
.type = 'checkbox'
forms[0].elements[3] is a object(HTMLInputElement)
.name = 'checkTester[]'
.type = 'checkbox'
forms[0].elements[4] is a object(HTMLInputElement)
.name = 'checkTester[]'
.type = 'checkbox'
forms[0].elements[5] is a object(HTMLInputElement)
.name = 'checkTester2[]'
.type = 'checkbox'
forms[0].elements[6] is a object(HTMLInputElement)
.name = 'text_testing'
.type = 'text'
forms[0].elements[7] is a object(HTMLInputElement)
.name = ''
.type = 'submit'
forms[0].elements[testing] is a object(HTMLSelectElement)
.name = 'selector'
.type = 'select-one'
forms[0].elements[selector] is a object(HTMLSelectElement)
.name = 'selector'
.type = 'select-one'
forms[0].elements[test_more] is a object(HTMLSelectElement)
.name = 'secondselect'
.type = 'select-one'
forms[0].elements[secondselect] is a object(HTMLSelectElement)
.name = 'secondselect'
.type = 'select-one'
forms[0].elements[checkTester[]] is a object(RadioNodeList)
.name = 'undefined'
.type = 'undefined'
forms[0].elements[checkTester2[]] is a object(HTMLInputElement)
.name = 'checkTester2[]'
.type = 'checkbox'
forms[0].elements[text_testing] is a object(HTMLInputElement)
.name = 'text_testing'
.type = 'text'
forms[0].elements[namedItem] is a function
.name = 'namedItem'
.type = 'undefined'
forms[0].elements[length] is a number
.name = 'undefined'
.type = 'undefined'
forms[0].elements[item] is a function
.name = 'item'
.type = 'undefined'
forms[test_1].elements[0] is a object(HTMLSelectElement)
.name = 'selector'
.type = 'select-one'
forms[test_1].elements[1] is a object(HTMLSelectElement)
.name = 'secondselect'
.type = 'select-one'
forms[test_1].elements[2] is a object(HTMLInputElement)
.name = 'checkTester[]'
.type = 'checkbox'
forms[test_1].elements[3] is a object(HTMLInputElement)
.name = 'checkTester[]'
.type = 'checkbox'
forms[test_1].elements[4] is a object(HTMLInputElement)
.name = 'checkTester[]'
.type = 'checkbox'
forms[test_1].elements[5] is a object(HTMLInputElement)
.name = 'checkTester2[]'
.type = 'checkbox'
forms[test_1].elements[6] is a object(HTMLInputElement)
.name = 'text_testing'
.type = 'text'
forms[test_1].elements[7] is a object(HTMLInputElement)
.name = ''
.type = 'submit'
forms[test_1].elements[testing] is a object(HTMLSelectElement)
.name = 'selector'
.type = 'select-one'
forms[test_1].elements[selector] is a object(HTMLSelectElement)
.name = 'selector'
.type = 'select-one'
forms[test_1].elements[test_more] is a object(HTMLSelectElement)
.name = 'secondselect'
.type = 'select-one'
forms[test_1].elements[secondselect] is a object(HTMLSelectElement)
.name = 'secondselect'
.type = 'select-one'
forms[test_1].elements[checkTester[]] is a object(RadioNodeList)
.name = 'undefined'
.type = 'undefined'
forms[test_1].elements[checkTester2[]] is a object(HTMLInputElement)
.name = 'checkTester2[]'
.type = 'checkbox'
forms[test_1].elements[text_testing] is a object(HTMLInputElement)
.name = 'text_testing'
.type = 'text'
forms[test_1].elements[namedItem] is a function
.name = 'namedItem'
.type = 'undefined'
forms[test_1].elements[length] is a number
.name = 'undefined'
.type = 'undefined'
forms[test_1].elements[item] is a function
.name = 'item'
.type = 'undefined'
Output with Edge on Windows 10 build 10586.494:

forms[test_1].elements[selector] is a object(HTMLSelectElement)
.name = 'selector'
.type = 'select-one'
forms[test_1].elements[secondselect] is a object(HTMLSelectElement)
.name = 'secondselect'
.type = 'select-one'
forms[test_1].elements[checkTester[]] is a object(HTMLCollection)
.name = 'undefined'
.type = 'undefined'
forms[test_1].elements[checkTester2[]] is a object(HTMLInputElement)
.name = 'checkTester2[]'
.type = 'checkbox'
forms[test_1].elements[text_testing] is a object(HTMLInputElement)
.name = 'text_testing'
.type = 'text'
forms[test_1].elements[ms__id261] is a object(HTMLInputElement)
.name = ''
.type = 'submit'
forms[test_1].elements[length] is a number
.name = 'undefined'
.type = 'undefined'
forms[test_1].elements[item] is a function
.name = 'item'
.type = 'undefined'
forms[test_1].elements[namedItem] is a function
.name = 'namedItem'
.type = 'undefined'

Chrome:

- elements provides iteration over its Collection numerically and associatively: by index in the collection, by id if given, and by name if given. This seems to apply to all Collections.

- Accessing duplicate elements in the Collection by name gives another Collection

 

Edge:

- elements provides iteration over its Collection associatively by name, using a placeholder value if none is given

- Accessing duplicate elements in the Collection by name gives another Collection

- Iteration over the Collection does not expose numeric keys, however numeric keys to elements are accepted

 

 

So actually the behavior you're seeing in IE/Edge is also present in Chrome, but is hidden by the fact that you still get the information you need as its provided via the numeric keys.

 

What doesn't help this is that the HTML/DOM standards don't seem to indicate precisely how iteration should work.

 

At this point I would offer an alternative to your for..in but you haven't said what you're trying to do with this code. Truthfully, since it surfaces all members of an object, for..in actually provides an attack surface for use with XSS and is thus generally discouraged without the developer taking proper protections.

  • Like 1
Link to comment
Share on other sites

@requinix - thank you for the analysis!

 

What I'm doing with the script is basically replacing form elements with individual series of divs that act as those form elements. So, a checkbox (in this case) is replaced with a simple div and an onclick() function that toggles the underlying (and hidden) checkbox state according to the display state of the div. Display is controlled by adding and removing classes (checked and unchecked), while the checkbox state is controlled via the .checked property of the checkbox in question. The two are linked via a JS-injected data-attribute on the div that contains the ID of the corresponding checkbox.

 

Now that the word 'checkbox' has little meaning any more, I hope that makes sense.

 

From what I'm seeing in your console output, it looks like I need to add a check to see if the current form element is an HTMLCollection, and if so, iterate a loop through that. So in essence, every browser except Edge and IE are flattening a multi-dimensional array of form elements. Am I reading that correctly (I've only had one cup of coffee, so that's a genuine question)? Or is it as simple as using a for..of loop? Off to try...

 

Also, if I'm passing in a specific local form object, how does that create a potential XSS vulnerability?

 

And again, thank you very much!

Link to comment
Share on other sites

  • Solution

Using for...in with an array (or array-like object) is not preferred. There is no guarantee over the order items will be iterated in and in the case of an array-like object it may include other properties you do not want.

 

Use a regular for loop with a numeric index.

 

for(var i=0; i<_form.elements.length; i++){
    console.log(_form.elements[i].type + ' : ' + _form.elements[i].name);
}
  • Like 1
Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.