Search code examples
javascripthtmldomcss-selectorsdom-traversal

Better way to achieve this vanilla javascript DOM traversal?


I have some auto-generated forms from Marketo, meaning I am forced to work around what is provided. When there is a checkbox with only one value, I need to add a class to the parent label (There is a comment that indicates which one).

The checkbox in question could be any field, not just specifically "explicitOptin", so I can't select based off that (at least not initially). I also have to make sure I don't accidentally select checkboxes with two or more values.

Here is the javascript I came up with:

var ckBox = document.querySelector('.mktoCheckboxList .mktoField:only-of-type').parentNode.previousSibling.previousSibling;
if (ckBox.className.indexOf('mktoSingleCheckbox') == -1) {
    ckBox.className += ' mktoSingleCheckbox';
}
<form id="mktoForm_1690" novalidate="novalidate" class="mktoForm mkt*emphasized text*oHasWidth mktoLayoutLeft" data-styles-ready="true">
<div class="mktoFormRow">
    <div class="mktoFormRow">
        <div class="mktoFieldDescriptor mktoFormCol">
            <div class="mktoOffset"></div>
            <div class="mktoFieldWrap">
                <label for="hazmatchooseallthatapply" class="mktoLabel mktoHasWidth">
                    <div class="mktoAsterix">*</div>Hazmat (choose all that apply):</label>
                <div class="mktoGutter mktoHasWidth"></div>
                <div class="mktoLogicalField mktoCheckboxList mktoHasWidth">
                    <input name="hazmatchooseallthatapply" id="mktoCheckbox_9480_0" type="checkbox" value="Hazardous Materials" class="mktoField">
                    <label for="mktoCheckbox_9480_0">Hazardous Materials</label>
                    <input name="hazmatchooseallthatapply" id="mktoCheckbox_9480_1" type="checkbox" value="Hazardous Waste" class="mktoField">
                    <label for="mktoCheckbox_9480_1">Hazardous Waste</label>
                    <input name="hazmatchooseallthatapply" id="mktoCheckbox_9480_2" type="checkbox" value="Fuel Tanker" class="mktoField">
                    <label for="mktoCheckbox_9480_2">Fuel Tanker</label>
                </div>
                <div class="mktoClear"></div>
            </div>
            <div class="mktoClear"></div>
        </div>
        <div class="mktoClear"></div>
    </div>
    <div class="mktoClear"></div>
</div>
<div class="mktoFormRow">
    <div class="mktoFieldDescriptor mktoFormCol">
        <div class="mktoOffset"></div>
        <div class="mktoFieldWrap">
            <!--This label directly below is the element that needs the new class-->
            <label for="explicitOptin" class="mktoLabel mktoHasWidth mktoSingleCheckbox">
                <div class="mktoAsterix">*</div>Yes, please sign me up to receive information from PrePass by email.</label>
            <div class="mktoGutter mktoHasWidth"></div>
            <div class="mktoLogicalField mktoCheckboxList mktoHasWidth mktoValid">
                <!-- This is the single checkbox input that I am selecting with my javascript -->
                <input name="explicitOptin" id="explicitOptin" type="checkbox" value="yes" class="mktoField">
                <label for="explicitOptin"></label>
            </div>
            <div class="mktoClear"></div>
        </div>
        <div class="mktoClear"></div>
    </div>
    <div class="mktoClear"></div>
</div>

I feel like there has to be a better way than just stringing together parentNode and previousSibling methods.

Any suggestions? I was thinking a possible alternative would be to use the CSS selector that I'm using, but then grab the ID value and search elements that have a for value of that ID. Maybe just stringing together parentNode and previousSiblings is the way to go...?


Solution

  • As of writing this, your solution seems to be the best route to go, with the exception of using element.classList instead of element.className.

    Optional reformating leads to this (long) one-liner:

    document.querySelector('.mktoCheckboxList .mktoField:only-of-type')
      .parentNode.previousSibling.previousSibling
      .classList.add('mktoSingleCheckbox');
    

    LOOKING TO THE FUTURE, browsers will let us use the HTMLInputElement.labels API to gather labels associated with an <input>.

    #                                                                   WOW!
    document.querySelector('.mktoCheckboxList .mktoField:only-of-type').labels[0]
      .classList.add('mktoSingleCheckbox');
    

    Unfortunately, browser support for this API is very limited right now, and trying to get associated labels manually is perhaps more cumbersome than your current method.

    Hope this helps!