What does the CSS:first-child and:last-child selector mean?

I have code using jQuery that was based on CSS selectors :first-child and :last-child (and that worked for quite a while), to get the first input and the last input within a group containing input + select + input... The first input was the initial value of a range, and the last was the final value of said range, being in the middle is the select with delete/include operators of the extremities.

Was like this:

var $inputIni = $("input:first-child", controlDiv),
    $inputFim = $("input:last-child", controlDiv),
    $select = $("select", controlDiv);

Jsfiddle

I came to the conclusion that the inputs did not have the clear purpose to the end user, and I decided to put labels for them, as well as for the select from the middle. From there the selector :first-child no longer worked. I can no longer get to the elements using the selector... why that?

Jsfiddle

I imagine that the same problem, whatever it is, may also one day happen with the selector :last-child, then as understand the problem?

Author: Miguel Angelo, 2014-03-13

1 answers

Pseudo-classes do not work like this

It is common to make this confusion about the meaning of selectors :first-child and :last-child, also called pseudo-classes, in addition to other pseudo-classes which denote position.

Pseudo-classes, when used in a selector, do not take into account the rest of the selector in question, therefore the selector input:first-child does not refer to the first input , but instead to the input that is the first child of its parent.

That means that the pseudo-class :first-child will mark whatever the first element that is the child of any other. The same occurs with the pseudo-class :last-child which will mark only the last child.

Similarly, input:last-child means: the input that is the last child . Other pseudo - classes of position also work analogously. Let's look at a list with the respective meanings:

Solutions to the problem

There are some solutions to the problem presented:

  • Using IDs for specific inputs rather than what is being done, perhaps concatenating with the ID of the Div that groups the elements, as indicated in the jsfiddle, like this: <input name="ctl_ini" id="ctl_ini" /> and the selector like this: $("#ctl_ini")

  • Use the pseudo-classes :first-of-type and :last-of-type like so: $("#ctl input:first-of-type") and $("#ctl input:last-of-type")

    Jsfiddle

    Note that for use in CSS, support is a bit limited. But in jQuery can use without worry.

A new confusion with :first-of-type

The pseudo-classes :first-of-type and others that are based on the type of the element, again cause a certain confusion, because apparently in the selector input:first-of-type the pseudo-class is relying on the rest of the selector to get the result, which is not true.

Example:

  • a.cls:first-of-type: element of Type a, with CSS class cls which is coincidentally the first of its kind.

    Since we are selecting the type of the element: a, then the only "first of the type" will be coincidentally of Type a, but that's not to say that the pseudo-class :first-of-type was based on what came before in the selector. So much is true that it ignores the selector class: cls in its decision, i.e. that selector does not mean the first a with the class cls.

Solution keeping :first-child and :last-child

As before you selected the input s that were first and last Children of your father and now put them within label s, keep in mind that now the label s are in the exact same place where the input s were, so they are label:first-child/select/label:last-child. With this in mind we can solve the situation by simply changing the selectors a little:

var fncDoSomethingWithCtl = function ($ctl) {
    
    var $inputIni = $("label:first-child input", $ctl),
        $inputFim = $("label:last-child input", $ctl),
        $select = $("select", $ctl);
    
    $inputIni.css({"border-color": "#C1E0FF", 
                   "border-width":"4px", 
                   "border-style":"solid"});
    
    $inputFim.css({"border-color": "#E0C1FF", 
                   "border-width":"4px", 
                   "border-style":"solid"});
    
    $select.css({"border-color": "#C1FFE0", 
                 "border-width":"4px", 
                 "border-style":"solid"});
    
}

$(function () {
    
    var $ctlDiv = $("#ctl");
    fncDoSomethingWithCtl($ctlDiv);
    
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="ctl">
    <label>
        <span>Início:</span>
        <input type="text"/>
    </label>
    <select>
        <option>inc até inc</option>
        <option>exc até inc</option>
        <option>inc até exc</option>
        <option>exc até exc</option>
    </select>
    <label>
        <span>Fim:</span>
        <input type="text" />
    </label>
</div>

Conclusion

The conclusion, is a security measure for the developer:

Pseudo-classes should always be treated as mere coincidences... to know what the pseudo-class really means, just use it without anything else in the same selector and then do an AND like this:

  • a:first-child will select the elements are selected both by selector a as by selector :first-child

Then just test both selectors separately, and then merge the results and pay close attention to the levels of each element within a document, as in this case, label took the place of input and made it its child, where input fell into the file hierarchy and turned :last-child from a label where :first-child was a span.

Reference:

Information about pseudo-classes

 18
Author: Miguel Angelo, 2016-01-19 11:30:03