How to Insert an Element's Index (Child Number) as Text Within The Element Solely Using CSS

Is there any way to insert an element's index (child number) as text within the element solely using CSS?

Yes, you could do this using CSS counters. CSS counters can be created and incremented when a matching selector is encountered. For this case, we can create a counter at the .wrapper element's level and then increment the counter value whenever a new .line element is encountered.

The counter-reset property is used to create the counter whereas the counter-increment is used to increment the value. The value of the counter can then be assigned to the pseudo-element through the content property with value as counter(counter-name).

As Aaron Lavers points out in his comment, CSS counters are not a new thing and is supported from IE8 itself.

#wrapper {  counter-reset: line-number;}.line {  counter-increment: line-number;}.line:before {  content: counter(line-number)": ";}
<div id="wrapper">  <div class="line">asdf</div>  <div class="line">asdfasdf</div>  <div class="line">asdfasdfasdfasdf</div>  <div class="line">asdf</div></div>

Why can't an element with a z-index value cover its child?

There are two important things you need to know: the painting order and the stacking context. If you refer to the specification, you can find how and when elements are painted.


  1. Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order (most negative first) then tree order.



  1. All positioned, opacity or transform descendants, in tree order that fall into the following categories:

    1. All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order.



  1. Stacking contexts formed by positioned descendants with z-indices greater than or equal to 1 in z-index order (smallest first) then tree order.

It's clear from this that we first paint elements with negative z-index at step (3), then the one with z-index equal to 0 at step (8), and finally the ones with positive z-index at step (9), which is logical. We can also read in another part of the specification:

Each box belongs to one stacking context. Each box in a given stacking context has an integer stack level, which is its position on the z-axis relative to other boxes in the same stacking context. Boxes with greater stack levels are always formatted in front of boxes with lower stack levels. Boxes may have negative stack levels. Boxes with the same stack level in a stacking context are stacked bottom-to-top according to document tree order.


To understand when each element will be painted you need to know its stacking context and its stack level inside this stacking context (defined by z-index). You also need to know whether that element establishes a stacking context. This is the tricky part, because setting z-index will do this:

For a positioned box, the z-index property specifies:

  1. The stack level of the box in the current stacking context.
  2. Whether the box establishes a stacking context

Values have the following meanings:

<integer>

This integer is the stack level of the generated box in the current stacking context. The box also establishes a new stacking context.

auto

The stack level of the generated box in the current stacking context is 0. The box does not establish a new stacking context unless it is the root element.


Now we have all the information to better understand each case. If the parent element has a z-index value of something other than auto, then it will create a stacking context, thus the child element will be painted inside whatever their z-index is (negative or positive). The z-index of the child element will simply tell us the order of painting inside the parent element (this covers your second point).

Now, if only the child element has a positive z-index and we set nothing on the parent element, then considering the painting order, the child will be painted later (in step (9)) and the parent in step (8). The only logical way to paint the parent above is to increase the z-index, but doing this will make us fall into the previous case where the parent will establish a stacking context and the child element will belong to it.

There is no way to have the parent above a child element when setting a positive z-index to the child. Also there is no way to have the parent above the child if we set a z-index to the parent element different from auto (either positive or negative).1

The only case where we can have a child below its parent is to set a negative z-index on the child element and keep the parent at z-index: auto, thus this one will not create a stacking context and following the painting order the child will be painted first.


In addition to z-index, there are other properties that create a stacking context. In case you face an expected stacking order, you need to consider those properties, too, in order to see if there is a stacking context created.


Some important facts that we can conclude from the above:

  1. Stacking contexts can be contained in other stacking contexts, and together create a hierarchy of stacking contexts.
  2. Each stacking context is completely independent of its siblings: only descendant elements are considered when stacking is processed.
  3. Each stacking context is self-contained: after the element's contents are stacked, the whole element is considered in the stacking order of the parent stacking context. ref

1: there is some hacky ways if we consider the use of 3D transformation.

Example with an element going under its parent element even if this one has a z-index specified.

.box {
position:relative;
z-index:0;
height:80px;
background:blue;
transform-style: preserve-3d; /* This is important */
}
.box > div {
margin:0 50px;
height:100px;
background:red;
z-index:-1; /* this will do nothing */
transform:translateZ(-1px); /* this will do the magic */
}
<div class="box">
<div></div>
</div>

Insert element - splitting others if necessary

I managed to throw something together... took a bit more than I originally thought.

I created the following functions:

/**
* Find the text node and the index within that given a parent node and the index within that.
*
* @param parentNode
* @param index
* @returns {*} - object with 'target' property set to the text node at the index parameter within the
* parentNode parameter and 'index' property set to the index of that point within 'target'
*/
findStartPoint = function(parentNode, index) {
var nodeRight = 0;
var nodeLeft = 0;
var node = null;
for(var i = 0; i < parentNode.childNodes.length; i++){
node = parentNode.childNodes.item(i);
if(node.nodeType !== 7 && node.nodeType !== 8){ //not processing instruction or comment
if(nodeRight <= index){
nodeLeft = nodeRight;
nodeRight += node.text.length;
if(nodeRight > index){
if (node.nodeType === 3) {
return { target: node, index: index-nodeLeft };
} else {
return this.findStartPoint( node, index-nodeLeft );
}
}
}
}
}
return { target: null, index: null };
};

/**
*
* Inserts an element within a givin range, will split tags if necessary
*
* xx <bold>xx foo <italic> bar</italic></bold> baz xx
*
* If I selected 'foo bar baz' in the above:
* - startPoint would be { target: the text node containing 'xx foo ', index: 4 }
* - length would be 'foo bar baz'.length
* - splittableEles could be ['BOLD', 'ITALIC']
* - insert ele could be <hello>
*
* Output would be:
* xx <bold>xx </bold><hello><bold>foo <italic> bar</italic></bold> baz</hello> xx
*
* @param startPoint - an object containing target (text node at beginning of split) and index (index of beginning within this text node)
* @param length - length of selection in characters
* @param splittableEles - elements that we allow to be split
* @param insertEle - element that we will wrap the split within and insert
* @returns {*}
*/
splitInsert = function(startPoint, length, splittableEles, insertEle) {
var target = startPoint.target;
var index = startPoint.index;

if (index == 0 && $(target.parentNode).text().length <= length) {
//consume entire target parent
target.parentNode.parentNode.insertBefore(insertEle, target.parentNode);
insertEle.appendChild(target.parentNode);
} else {
//split and add right of index to insertEle
var content = target.splitText(index);
content.parentNode.insertBefore(insertEle, content);
if (content.length > length) {
//split off the end if content longer than selection
content.splitText(length);
}
insertEle.appendChild(content);
}

while ( insertEle.text.length < length ) {
if (insertEle.nextSibling) {
if ( !this.consumeElementForInsert(insertEle, insertEle.nextSibling, length) ) {
if ( insertEle.nextSibling.nodeType === 3 ) {
this.splitTextForInsert(insertEle, insertEle.nextSibling, length)
} else {
this.splitElementForInsert(insertEle, insertEle.nextSibling, length, splittableEles)
}
}
} else {
//no next sibling... need to split parent. this would make parents next sibling for next iteration
var parent = insertEle.parentNode;
if (-1 == $.inArray(parent.nodeName.toUpperCase(), splittableEles)) {
//selection would require splitting non-splittable element
return { success: false };
}
//wrap insertEle with empty clone of parent, then place after parent
var clone = parent.cloneNode(false);
while (insertEle.firstChild) {
clone.appendChild(insertEle.firstChild);
}
insertEle.appendChild(clone);
parent.parentNode.insertBefore(insertEle, parent.nextSibling);
}
}
return { success: true, newElement: insertEle };
};

/**
* Splits a textnode ('node'), text on the left will be appended to 'container' to make 'container' have
* as many 'characters' as specified
*
* @param container
* @param node
* @param characters
*/
splitTextForInsert = function (container, node, characters) {
var containerLength = $(container).text().length;
if ( node.nodeValue.length + containerLength > characters ) {
node.splitText(characters - containerLength);
}
container.appendChild(node);
};

/**
* Puts 'node' into 'container' as long as it can fit given that 'container' can only have so many 'characters'
*
* @param container
* @param node
* @param characters
*
* @returns {boolean} - true if can consume, false if can't. can't consume if element has more text than needed.
*/
consumeElementForInsert = function (container, node, characters) {
if ( characters - $(container).text().length > $(node).text().length ) {
container.appendChild(node);
return true;
}
return false;
}

/**
* Splits 'node' (recursively if necessary) the amount of 'characters' specified, adds left side into 'container'
*
* @param container - parent/container of node we are splitting
* @param node - node we are splitting
* @param characters - number of characters in markman selection
* @param splittableEles - array of nodeTypes that can be split, upper case
* @param originalContainer - original container (before recursive calls)
* @returns {boolean} - true if we successfully split element or there is nothing to split, false otherwise. false will happen if we try to split
* something not in splittableEles or if we run out of characters
*/
splitElementForInsert = function (container, node, characters, splittableEles, originalContainer) {
originalContainer = originalContainer || container;
if (-1 == $.inArray(node.nodeName.toUpperCase(), splittableEles)) {
return false;
}
node.normalize();
var child = node.firstChild;
if (!child) {
return true;
}
else if (child.nodeType === 3) {
var $container = $(originalContainer);
if (characters - $container.text().length - child.nodeValue.length < 1 ) {
//this portion is enough for the selected range
var clone = node.cloneNode(false);
child.splitText(characters - $container.text().length);
clone.appendChild(child);
container.appendChild(clone);
return true;
} else {
//throw this text in the container and go on to the next as we still need more
if (child.nextSibling) {
var next = child.nextSibling;
container.appendChild(child);
return this.splitElementForInsert( container, next, characters, splittableEles, originalContainer );
} else {
return true;
}
}
}
else if (child.nodeType === 1) {
//child is an element, split that element
var clone = node.cloneNode(false);
container.appendChild(clone);
return this.splitElementForInsert(clone, child, characters, splittableEles, originalContainer);
}
};

Which I can then call with something like this...

var left = this.selectionInfo.left - paraIdOffset;
var right = this.selectionInfo.right - paraIdOffset;
var parentNode = this.selectionInfo.parentXmlNode;
var someElement = this.xml.createElement(...);
var splittableEles = ['SPAN'];
var createHappened = false;

var startPoint = findStartPoint(parentNode, left);
var insert = splitInsert(startPoint, right-left, splittableEles, someElement );
if (insert.success) {
createHappened = true;
}

Get an element's nth-child number in pure JavaScript

When you say "number", do you mean 1, 2, etc or "one", "two", etc?

If 1, 2, etc, then the number is simply i+1...

If "one", "two", etc, then you need to get the text inside the element, then probably use a Regexp to parse it and get the value you want.

Bring element to front using CSS

Add z-index:-1 and position:relative to .content

#header {    background: url(http://placehold.it/420x160) center top no-repeat;}#header-inner {    background: url(http://placekitten.com/150/200) right top no-repeat;}.logo-class {    height: 128px;}.content {    margin-left: auto;    margin-right: auto;    table-layout: fixed;    border-collapse: collapse;    z-index: -1;    position:relative;}.td-main {    text-align: center;    padding: 80px 10px 80px 10px;    border: 1px solid #A02422;    background: #ABABAB;}
<body>    <div id="header">        <div id="header-inner">            <table class="content">                <col width="400px" />                <tr>                    <td>                        <table class="content">                            <col width="400px" />                            <tr>                                <td>                                    <div class="logo-class"></div>                                </td>                            </tr>                            <tr>                                <td id="menu"></td>                            </tr>                        </table>                        <table class="content">                            <col width="120px" />                            <col width="160px" />                            <col width="120px" />                            <tr>                                <td class="td-main">text</td>                                <td class="td-main">text</td>                                <td class="td-main">text</td>                            </tr>                        </table>                    </td>                </tr>            </table>        </div>        <!-- header-inner -->    </div>    <!-- header --></body>

How can I change an element's text without changing its child elements?

Mark’s got a better solution using jQuery, but you might be able to do this in regular JavaScript too.

In Javascript, the childNodes property gives you all the child nodes of an element, including text nodes.

So, if you knew the text you wanted to change was always going to be the first thing in the element, then given e.g. this HTML:

<div id="your_div">
**text to change**
<p>
text that should not change
</p>
<p>
text that should not change
</p>
</div>

You could do this:

var your_div = document.getElementById('your_div');

var text_to_change = your_div.childNodes[0];

text_to_change.nodeValue = 'new text';

Of course, you can still use jQuery to select the <div> in the first place (i.e. var your_div = $('your_div').get(0);).

Using .text() to retrieve only text not nested in child tags

I liked this reusable implementation based on the clone() method found here to get only the text inside the parent element.

Code provided for easy reference:

$("#foo")
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text();

Get child node index

you can use the previousSibling property to iterate back through the siblings until you get back null and count how many siblings you've encountered:

var i = 0;
while( (child = child.previousSibling) != null )
i++;
//at the end i will contain the index.

Please note that in languages like Java, there is a getPreviousSibling() function, however in JS this has become a property -- previousSibling.

Use previousElementSibling or nextElementSibling to ignore text and comment nodes.

Is it possible to set the stacking order of pseudo-elements below their parent element?

Pseudo-elements are treated as descendants of their associated element. To position a pseudo-element below its parent, you have to create a new stacking context to change the default stacking order.

Positioning the pseudo-element (absolute) and assigning a z-index value other than “auto” creates the new stacking context.

#element {     position: relative;  /* optional */    width: 100px;    height: 100px;    background-color: blue;}
#element::after { content: ""; width: 150px; height: 150px; background-color: red;
/* create a new stacking context */ position: absolute; z-index: -1; /* to be below the parent element */}
<!DOCTYPE html><html><head>  <meta charset="utf-8">  <title>Position a pseudo-element below its parent</title></head><body>  <div id="element">  </div></body></html>

How to select the first, second, or third element with a given class name?

You probably finally realized this between posting this question and today, but the very nature of selectors makes it impossible to navigate through hierarchically unrelated HTML elements.

Or, to put it simply, since you said in your comment that

there are no uniform parent containers

... it's just not possible with selectors alone, without modifying the markup in some way as shown by the other answers.

You have to use the jQuery .eq() solution.



Related Topics



Leave a reply



Submit