mPDF: workaround for heading [h1-hx] margins in table - mpdf

Question:
How can I add margins and/or paddings to headings (h1 - h4) that are part of a TD or TH?
Current Situation:
I have a pre-generated HTML document that is being generated by JIRA. The structure of this document is as follows:
<tr class="rowAlternate">
<td class="jira-macro-table-underline-pdfexport">
<h1><a name="StandardizedInterface"></a>Standardized Interface</span></h1>
<h2><a name="ShortDescription"></a>Short Description</h2>
<ul> ... </ul>
</td>
</tr>
I programmatically extend this document with <tocentry> and other mPDF-specific elements so it can be used as a handout, the generated PDF looks quite good but there is one major issue I have with headings in tables.
This is how the document shows up in the browser:
Inside generated PDF:
As can be see the margins of the headings have disappeared in the PDF export. All my tests with adding inline CSS to the headings or to wrap them with other elements have failed so far.
The mPDF documentation says:
Block-level tags (DIV, P etc) are ignored inside tables, including any
CSS styles.
This will most likely mean that this can't be done with pure CSS or wrapping.
I hope that someone else encountered this problem before and could share some insights as how to achieve spacing around block elements.

You'll want to convert the mark-up to a format similar to the following:
<table>
<tr>
<td class="h1">Report</td>
</tr>
<tr>
<td class="h2">Description</td>
</tr>
<tr>
<td>Body</td>
</tr>
</table>
You can then apply appropriate padding to the <td> elements by targeting the class name:
<style>
table, th, td {
border: none;
}
td.h1 {
padding: 30px 0;
}
td.h2 {
padding: 15px 0;
}
</style>
Use a DOM parser like the one from Symfony to help you traverse your current mark-up and rebuild it in the proper format automatically.

Workaround / solution
I used a workaround that solved the problem in my case by using preg_replace_callback() that I needed anyways to format my document properly. Whenever the callback handles a heading, I add a transparent image right after the text of the heading whose height I define by doing a bit of math.
Code
function formatHeading($p) {
// I only want headings for h1-h3:
$tocEntry= '';
if (is_numeric($p[2]) && $p[2] >= 1 && $p[2] <= 3) {
$tocEntry= "<tocentry level=\"{$p[2]}\" content=\"{$p[3]}\" />";
}
// calculates the heights of the vertical spacer
$spacerHeight= ((6-$p[2]) * 10);
return
"{$p[1]}{$tocEntry} {$p[3]} {$p[4]}
<img src=\"../assets/images/transparent.png\" height=\"{$spacerHeight}\" style=\"vertical-align:middle;\" width=\"1\" border=\"0\"/>";
}
// Matches e.g. "<h2><a name="SimpleTest"></a>Simple test</h2>"
$buffered= preg_replace_callback('|(<h(\d)+>?<a?[\w\s\d="%>]+<\/a>)(.+?[\s\r\n\']*.*)(<\/h\d+>)|',
"formatHeading", $buffered);
Result
To illustrate the position of the spacer I used a non-transparent image to generate the PDF:

Related

Is there a selector that looks a the content of table td and decides to apply backcolor red, for instance, if the td contains "$-", a negative number [duplicate]

I am looking for a CSS selector for the following table:
Peter | male | 34
Susanne | female | 12
Is there any selector to match all TDs containing "male"?
If I read the specification correctly, no.
You can match on an element, the name of an attribute in the element, and the value of a named attribute in an element. I don't see anything for matching content within an element, though.
Looks like they were thinking about it for the CSS3 spec but it didn't make the cut.
:contains() CSS3 selector http://www.w3.org/TR/css3-selectors/#content-selectors
Using jQuery:
$('td:contains("male")')
You'd have to add a data attribute to the rows called data-gender with a male or female value and use the attribute selector:
HTML:
<td data-gender="male">...</td>
CSS:
td[data-gender="male"] { ... }
There is actually a very conceptual basis for why this hasn't been implemented. It is a combination of basically 3 aspects:
The text content of an element is effectively a child of that element
You cannot target the text content directly
CSS does not allow for ascension with selectors
These 3 together mean that by the time you have the text content you cannot ascend back to the containing element, and you cannot style the present text. This is likely significant as descending only allows for a singular tracking of context and SAX style parsing. Ascending or other selectors involving other axes introduce the need for more complex traversal or similar solutions that would greatly complicate the application of CSS to the DOM.
You could set content as data attribute and then use attribute selectors, as shown here:
/* Select every cell matching the word "male" */
td[data-content="male"] {
color: red;
}
/* Select every cell starting on "p" case insensitive */
td[data-content^="p" i] {
color: blue;
}
/* Select every cell containing "4" */
td[data-content*="4"] {
color: green;
}
<table>
<tr>
<td data-content="Peter">Peter</td>
<td data-content="male">male</td>
<td data-content="34">34</td>
</tr>
<tr>
<td data-content="Susanne">Susanne</td>
<td data-content="female">female</td>
<td data-content="14">14</td>
</tr>
</table>
You can also use jQuery to easily set the data-content attributes:
$(function(){
$("td").each(function(){
var $this = $(this);
$this.attr("data-content", $this.text());
});
});
As CSS lacks this feature you will have to use JavaScript to style cells by content. For example with XPath's contains:
var elms = document.evaluate( "//td[contains(., 'male')]", node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null )
Then use the result like so:
for ( var i=0 ; i < elms.snapshotLength; i++ ){
elms.snapshotItem(i).style.background = "pink";
}
https://jsfiddle.net/gaby_de_wilde/o7bka7Ls/9/
As of Jan 2021, there IS something that will do just this. :has() ... only one catch: this is not supported in any browser yet
Example: The following selector matches only elements that directly contain an child:
a:has(> img)
References:
https://developer.mozilla.org/en-US/docs/Web/CSS/:has
https://caniuse.com/?search=has
I'm afraid this is not possible, because the content is no attribute nor is it accessible via a pseudo class. The full list of CSS3 selectors can be found in the CSS3 specification.
For those who are looking to do Selenium CSS text selections, this script might be of some use.
The trick is to select the parent of the element that you are looking for, and then search for the child that has the text:
public static IWebElement FindByText(this IWebDriver driver, string text)
{
var list = driver.FindElement(By.CssSelector("#RiskAddressList"));
var element = ((IJavaScriptExecutor)driver).ExecuteScript(string.Format(" var x = $(arguments[0]).find(\":contains('{0}')\"); return x;", text), list);
return ((System.Collections.ObjectModel.ReadOnlyCollection<IWebElement>)element)[0];
}
This will return the first element if there is more than one since it's always one element, in my case.
Excellent answers all around, but I think I can add something that worked for me in a practical scenario: exploiting the aria-label attribute for CSS.
For the readers that don't know: aria-label is an attribute that is used in conjunction with other similar attributes to let a screen-reader know what something is, in case someone with a visual impairment is using your website. Many websites add these attributes to elements with images or text in them, as "descriptors".
This makes it highly website-specific, but in case your element contains this, it's fairly simple to select that element using the content of the attribute:
HTML:
<td aria-label="male">Male</td>
<td aria-label="female">Female</td>
CSS:
td[aria-label="male"] {
outline: 1px dotted green;
}
This is technically the same thing as using the data-attribute solution, but this will work for you if you are not the author of the website, plus this is not some out-of-the-way solution that is specifically designed to support this use case; it's fairly common on its own. The one downside of it is that there's really no guarantee that your intended element will have this attribute present.
If you don't create the DOM yourself (e.g. in a userscript) you can do the following with pure JS:
for ( td of document.querySelectorAll('td') ) {
console.debug("text:", td, td.innerText)
td.setAttribute('text', td.innerText)
}
for ( td of document.querySelectorAll('td[text="male"]') )
console.debug("male:", td, td.innerText)
<table>
<tr>
<td>Peter</td>
<td>male</td>
<td>34</td>
</tr>
<tr>
<td>Susanne</td>
<td>female</td>
<td>12</td>
</tr>
</table>
Console output
text: <td> Peter
text: <td> male
text: <td> 34
text: <td> Susanne
text: <td> female
text: <td> 12
male: <td text="male"> male
Most of the answers here try to offer alternative to how to write the HTML code to include more data because at least up to CSS3 you cannot select an element by partial inner text. But it can be done, you just need to add a bit of vanilla JavaScript, notice since female also contains male it will be selected:
cells = document.querySelectorAll('td');
console.log(cells);
[].forEach.call(cells, function (el) {
if(el.innerText.indexOf("male") !== -1){
//el.click(); click or any other option
console.log(el)
}
});
<table>
<tr>
<td>Peter</td>
<td>male</td>
<td>34</td>
</tr>
<tr>
<td>Susanne</td>
<td>female</td>
<td>14</td>
</tr>
</table>
<table>
<tr>
<td data-content="Peter">Peter</td>
<td data-content="male">male</td>
<td data-content="34">34</td>
</tr>
<tr>
<td data-conten="Susanne">Susanne</td>
<td data-content="female">female</td>
<td data-content="14">14</td>
</tr>
</table>
I agree the data attribute (voyager's answer) is how it should be handled, BUT, CSS rules like:
td.male { color: blue; }
td.female { color: pink; }
can often be much easier to set up, especially with client-side libs like angularjs which could be as simple as:
<td class="{{person.gender}}">
Just make sure that the content is only one word! Or you could even map to different CSS class names with:
<td ng-class="{'masculine': person.isMale(), 'feminine': person.isFemale()}">
For completeness, here's the data attribute approach:
<td data-gender="{{person.gender}}">
If you're using Chimp / Webdriver.io, they support a lot more CSS selectors than the CSS spec.
This, for example, will click on the first anchor that contains the words "Bad bear":
browser.click("a*=Bad Bear");
#voyager's answer about using data-* attribute (e.g. data-gender="female|male" is the most effective and standards compliant approach as of 2017:
[data-gender='male'] {background-color: #000; color: #ccc;}
Pretty much most goals can be attained as there are some albeit limited selectors oriented around text. The ::first-letter is a pseudo-element that can apply limited styling to the first letter of an element. There is also a ::first-line pseudo-element besides obviously selecting the first line of an element (such as a paragraph) also implies that it is obvious that CSS could be used to extend this existing capability to style specific aspects of a textNode.
Until such advocacy succeeds and is implemented the next best thing I could suggest when applicable is to explode/split words using a space deliminator, output each individual word inside of a span element and then if the word/styling goal is predictable use in combination with :nth selectors:
$p = explode(' ',$words);
foreach ($p as $key1 => $value1)
{
echo '<span>'.$value1.'</span>;
}
Else if not predictable to, again, use voyager's answer about using data-* attribute. An example using PHP:
$p = explode(' ',$words);
foreach ($p as $key1 => $value1)
{
echo '<span data-word="'.$value1.'">'.$value1.'</span>;
}
If you want to apply style to the content you want. Easy trick.
td { border: 1px solid black; }
td:empty { background: lime; }
td:empty::after { content: "male"; }
<table>
<tr>
<td>Peter</td>
<td><!--male--></td>
<td>34</td>
</tr>
<tr>
<td>Susanne</td>
<td>female</td>
<td>14</td>
</tr>
</table>
https://jsfiddle.net/hyda8kqz/
I find the attribute option to be your best bet if you don't want to use javascript or jquery.
E.g to style all table cells with the word ready, In HTML do this:
<td status*="ready">Ready</td>
Then in css:
td[status*="ready"] {
color: red;
}
Doing small Filter Widgets like this:
var searchField = document.querySelector('HOWEVER_YOU_MAY_FIND_IT')
var faqEntries = document.querySelectorAll('WRAPPING_ELEMENT .entry')
searchField.addEventListener('keyup', function (evt) {
var testValue = evt.target.value.toLocaleLowerCase();
var regExp = RegExp(testValue);
faqEntries.forEach(function (entry) {
var text = entry.textContent.toLocaleLowerCase();
entry.classList.remove('show', 'hide');
if (regExp.test(text)) {
entry.classList.add('show')
} else {
entry.classList.add('hide')
}
})
})
The syntax of this question looks like Robot Framework syntax.
In this case, although there is no css selector that you can use for contains, there is a SeleniumLibrary keyword that you can use instead.
The Wait Until Element Contains.
Example:
Wait Until Element Contains | ${element} | ${contains}
Wait Until Element Contains | td | male

Replace text with image in the page using CSS [duplicate]

I am looking for a CSS selector for the following table:
Peter | male | 34
Susanne | female | 12
Is there any selector to match all TDs containing "male"?
If I read the specification correctly, no.
You can match on an element, the name of an attribute in the element, and the value of a named attribute in an element. I don't see anything for matching content within an element, though.
Looks like they were thinking about it for the CSS3 spec but it didn't make the cut.
:contains() CSS3 selector http://www.w3.org/TR/css3-selectors/#content-selectors
Using jQuery:
$('td:contains("male")')
You'd have to add a data attribute to the rows called data-gender with a male or female value and use the attribute selector:
HTML:
<td data-gender="male">...</td>
CSS:
td[data-gender="male"] { ... }
There is actually a very conceptual basis for why this hasn't been implemented. It is a combination of basically 3 aspects:
The text content of an element is effectively a child of that element
You cannot target the text content directly
CSS does not allow for ascension with selectors
These 3 together mean that by the time you have the text content you cannot ascend back to the containing element, and you cannot style the present text. This is likely significant as descending only allows for a singular tracking of context and SAX style parsing. Ascending or other selectors involving other axes introduce the need for more complex traversal or similar solutions that would greatly complicate the application of CSS to the DOM.
You could set content as data attribute and then use attribute selectors, as shown here:
/* Select every cell matching the word "male" */
td[data-content="male"] {
color: red;
}
/* Select every cell starting on "p" case insensitive */
td[data-content^="p" i] {
color: blue;
}
/* Select every cell containing "4" */
td[data-content*="4"] {
color: green;
}
<table>
<tr>
<td data-content="Peter">Peter</td>
<td data-content="male">male</td>
<td data-content="34">34</td>
</tr>
<tr>
<td data-content="Susanne">Susanne</td>
<td data-content="female">female</td>
<td data-content="14">14</td>
</tr>
</table>
You can also use jQuery to easily set the data-content attributes:
$(function(){
$("td").each(function(){
var $this = $(this);
$this.attr("data-content", $this.text());
});
});
As CSS lacks this feature you will have to use JavaScript to style cells by content. For example with XPath's contains:
var elms = document.evaluate( "//td[contains(., 'male')]", node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null )
Then use the result like so:
for ( var i=0 ; i < elms.snapshotLength; i++ ){
elms.snapshotItem(i).style.background = "pink";
}
https://jsfiddle.net/gaby_de_wilde/o7bka7Ls/9/
As of Jan 2021, there IS something that will do just this. :has() ... only one catch: this is not supported in any browser yet
Example: The following selector matches only elements that directly contain an child:
a:has(> img)
References:
https://developer.mozilla.org/en-US/docs/Web/CSS/:has
https://caniuse.com/?search=has
I'm afraid this is not possible, because the content is no attribute nor is it accessible via a pseudo class. The full list of CSS3 selectors can be found in the CSS3 specification.
For those who are looking to do Selenium CSS text selections, this script might be of some use.
The trick is to select the parent of the element that you are looking for, and then search for the child that has the text:
public static IWebElement FindByText(this IWebDriver driver, string text)
{
var list = driver.FindElement(By.CssSelector("#RiskAddressList"));
var element = ((IJavaScriptExecutor)driver).ExecuteScript(string.Format(" var x = $(arguments[0]).find(\":contains('{0}')\"); return x;", text), list);
return ((System.Collections.ObjectModel.ReadOnlyCollection<IWebElement>)element)[0];
}
This will return the first element if there is more than one since it's always one element, in my case.
Excellent answers all around, but I think I can add something that worked for me in a practical scenario: exploiting the aria-label attribute for CSS.
For the readers that don't know: aria-label is an attribute that is used in conjunction with other similar attributes to let a screen-reader know what something is, in case someone with a visual impairment is using your website. Many websites add these attributes to elements with images or text in them, as "descriptors".
This makes it highly website-specific, but in case your element contains this, it's fairly simple to select that element using the content of the attribute:
HTML:
<td aria-label="male">Male</td>
<td aria-label="female">Female</td>
CSS:
td[aria-label="male"] {
outline: 1px dotted green;
}
This is technically the same thing as using the data-attribute solution, but this will work for you if you are not the author of the website, plus this is not some out-of-the-way solution that is specifically designed to support this use case; it's fairly common on its own. The one downside of it is that there's really no guarantee that your intended element will have this attribute present.
If you don't create the DOM yourself (e.g. in a userscript) you can do the following with pure JS:
for ( td of document.querySelectorAll('td') ) {
console.debug("text:", td, td.innerText)
td.setAttribute('text', td.innerText)
}
for ( td of document.querySelectorAll('td[text="male"]') )
console.debug("male:", td, td.innerText)
<table>
<tr>
<td>Peter</td>
<td>male</td>
<td>34</td>
</tr>
<tr>
<td>Susanne</td>
<td>female</td>
<td>12</td>
</tr>
</table>
Console output
text: <td> Peter
text: <td> male
text: <td> 34
text: <td> Susanne
text: <td> female
text: <td> 12
male: <td text="male"> male
Most of the answers here try to offer alternative to how to write the HTML code to include more data because at least up to CSS3 you cannot select an element by partial inner text. But it can be done, you just need to add a bit of vanilla JavaScript, notice since female also contains male it will be selected:
cells = document.querySelectorAll('td');
console.log(cells);
[].forEach.call(cells, function (el) {
if(el.innerText.indexOf("male") !== -1){
//el.click(); click or any other option
console.log(el)
}
});
<table>
<tr>
<td>Peter</td>
<td>male</td>
<td>34</td>
</tr>
<tr>
<td>Susanne</td>
<td>female</td>
<td>14</td>
</tr>
</table>
<table>
<tr>
<td data-content="Peter">Peter</td>
<td data-content="male">male</td>
<td data-content="34">34</td>
</tr>
<tr>
<td data-conten="Susanne">Susanne</td>
<td data-content="female">female</td>
<td data-content="14">14</td>
</tr>
</table>
I agree the data attribute (voyager's answer) is how it should be handled, BUT, CSS rules like:
td.male { color: blue; }
td.female { color: pink; }
can often be much easier to set up, especially with client-side libs like angularjs which could be as simple as:
<td class="{{person.gender}}">
Just make sure that the content is only one word! Or you could even map to different CSS class names with:
<td ng-class="{'masculine': person.isMale(), 'feminine': person.isFemale()}">
For completeness, here's the data attribute approach:
<td data-gender="{{person.gender}}">
If you're using Chimp / Webdriver.io, they support a lot more CSS selectors than the CSS spec.
This, for example, will click on the first anchor that contains the words "Bad bear":
browser.click("a*=Bad Bear");
#voyager's answer about using data-* attribute (e.g. data-gender="female|male" is the most effective and standards compliant approach as of 2017:
[data-gender='male'] {background-color: #000; color: #ccc;}
Pretty much most goals can be attained as there are some albeit limited selectors oriented around text. The ::first-letter is a pseudo-element that can apply limited styling to the first letter of an element. There is also a ::first-line pseudo-element besides obviously selecting the first line of an element (such as a paragraph) also implies that it is obvious that CSS could be used to extend this existing capability to style specific aspects of a textNode.
Until such advocacy succeeds and is implemented the next best thing I could suggest when applicable is to explode/split words using a space deliminator, output each individual word inside of a span element and then if the word/styling goal is predictable use in combination with :nth selectors:
$p = explode(' ',$words);
foreach ($p as $key1 => $value1)
{
echo '<span>'.$value1.'</span>;
}
Else if not predictable to, again, use voyager's answer about using data-* attribute. An example using PHP:
$p = explode(' ',$words);
foreach ($p as $key1 => $value1)
{
echo '<span data-word="'.$value1.'">'.$value1.'</span>;
}
If you want to apply style to the content you want. Easy trick.
td { border: 1px solid black; }
td:empty { background: lime; }
td:empty::after { content: "male"; }
<table>
<tr>
<td>Peter</td>
<td><!--male--></td>
<td>34</td>
</tr>
<tr>
<td>Susanne</td>
<td>female</td>
<td>14</td>
</tr>
</table>
https://jsfiddle.net/hyda8kqz/
I find the attribute option to be your best bet if you don't want to use javascript or jquery.
E.g to style all table cells with the word ready, In HTML do this:
<td status*="ready">Ready</td>
Then in css:
td[status*="ready"] {
color: red;
}
Doing small Filter Widgets like this:
var searchField = document.querySelector('HOWEVER_YOU_MAY_FIND_IT')
var faqEntries = document.querySelectorAll('WRAPPING_ELEMENT .entry')
searchField.addEventListener('keyup', function (evt) {
var testValue = evt.target.value.toLocaleLowerCase();
var regExp = RegExp(testValue);
faqEntries.forEach(function (entry) {
var text = entry.textContent.toLocaleLowerCase();
entry.classList.remove('show', 'hide');
if (regExp.test(text)) {
entry.classList.add('show')
} else {
entry.classList.add('hide')
}
})
})
The syntax of this question looks like Robot Framework syntax.
In this case, although there is no css selector that you can use for contains, there is a SeleniumLibrary keyword that you can use instead.
The Wait Until Element Contains.
Example:
Wait Until Element Contains | ${element} | ${contains}
Wait Until Element Contains | td | male

Using CSS to Show Image after Specific Text Value [duplicate]

I am looking for a CSS selector for the following table:
Peter | male | 34
Susanne | female | 12
Is there any selector to match all TDs containing "male"?
If I read the specification correctly, no.
You can match on an element, the name of an attribute in the element, and the value of a named attribute in an element. I don't see anything for matching content within an element, though.
Looks like they were thinking about it for the CSS3 spec but it didn't make the cut.
:contains() CSS3 selector http://www.w3.org/TR/css3-selectors/#content-selectors
Using jQuery:
$('td:contains("male")')
You'd have to add a data attribute to the rows called data-gender with a male or female value and use the attribute selector:
HTML:
<td data-gender="male">...</td>
CSS:
td[data-gender="male"] { ... }
There is actually a very conceptual basis for why this hasn't been implemented. It is a combination of basically 3 aspects:
The text content of an element is effectively a child of that element
You cannot target the text content directly
CSS does not allow for ascension with selectors
These 3 together mean that by the time you have the text content you cannot ascend back to the containing element, and you cannot style the present text. This is likely significant as descending only allows for a singular tracking of context and SAX style parsing. Ascending or other selectors involving other axes introduce the need for more complex traversal or similar solutions that would greatly complicate the application of CSS to the DOM.
You could set content as data attribute and then use attribute selectors, as shown here:
/* Select every cell matching the word "male" */
td[data-content="male"] {
color: red;
}
/* Select every cell starting on "p" case insensitive */
td[data-content^="p" i] {
color: blue;
}
/* Select every cell containing "4" */
td[data-content*="4"] {
color: green;
}
<table>
<tr>
<td data-content="Peter">Peter</td>
<td data-content="male">male</td>
<td data-content="34">34</td>
</tr>
<tr>
<td data-content="Susanne">Susanne</td>
<td data-content="female">female</td>
<td data-content="14">14</td>
</tr>
</table>
You can also use jQuery to easily set the data-content attributes:
$(function(){
$("td").each(function(){
var $this = $(this);
$this.attr("data-content", $this.text());
});
});
As CSS lacks this feature you will have to use JavaScript to style cells by content. For example with XPath's contains:
var elms = document.evaluate( "//td[contains(., 'male')]", node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null )
Then use the result like so:
for ( var i=0 ; i < elms.snapshotLength; i++ ){
elms.snapshotItem(i).style.background = "pink";
}
https://jsfiddle.net/gaby_de_wilde/o7bka7Ls/9/
As of Jan 2021, there IS something that will do just this. :has() ... only one catch: this is not supported in any browser yet
Example: The following selector matches only elements that directly contain an child:
a:has(> img)
References:
https://developer.mozilla.org/en-US/docs/Web/CSS/:has
https://caniuse.com/?search=has
I'm afraid this is not possible, because the content is no attribute nor is it accessible via a pseudo class. The full list of CSS3 selectors can be found in the CSS3 specification.
For those who are looking to do Selenium CSS text selections, this script might be of some use.
The trick is to select the parent of the element that you are looking for, and then search for the child that has the text:
public static IWebElement FindByText(this IWebDriver driver, string text)
{
var list = driver.FindElement(By.CssSelector("#RiskAddressList"));
var element = ((IJavaScriptExecutor)driver).ExecuteScript(string.Format(" var x = $(arguments[0]).find(\":contains('{0}')\"); return x;", text), list);
return ((System.Collections.ObjectModel.ReadOnlyCollection<IWebElement>)element)[0];
}
This will return the first element if there is more than one since it's always one element, in my case.
Excellent answers all around, but I think I can add something that worked for me in a practical scenario: exploiting the aria-label attribute for CSS.
For the readers that don't know: aria-label is an attribute that is used in conjunction with other similar attributes to let a screen-reader know what something is, in case someone with a visual impairment is using your website. Many websites add these attributes to elements with images or text in them, as "descriptors".
This makes it highly website-specific, but in case your element contains this, it's fairly simple to select that element using the content of the attribute:
HTML:
<td aria-label="male">Male</td>
<td aria-label="female">Female</td>
CSS:
td[aria-label="male"] {
outline: 1px dotted green;
}
This is technically the same thing as using the data-attribute solution, but this will work for you if you are not the author of the website, plus this is not some out-of-the-way solution that is specifically designed to support this use case; it's fairly common on its own. The one downside of it is that there's really no guarantee that your intended element will have this attribute present.
If you don't create the DOM yourself (e.g. in a userscript) you can do the following with pure JS:
for ( td of document.querySelectorAll('td') ) {
console.debug("text:", td, td.innerText)
td.setAttribute('text', td.innerText)
}
for ( td of document.querySelectorAll('td[text="male"]') )
console.debug("male:", td, td.innerText)
<table>
<tr>
<td>Peter</td>
<td>male</td>
<td>34</td>
</tr>
<tr>
<td>Susanne</td>
<td>female</td>
<td>12</td>
</tr>
</table>
Console output
text: <td> Peter
text: <td> male
text: <td> 34
text: <td> Susanne
text: <td> female
text: <td> 12
male: <td text="male"> male
Most of the answers here try to offer alternative to how to write the HTML code to include more data because at least up to CSS3 you cannot select an element by partial inner text. But it can be done, you just need to add a bit of vanilla JavaScript, notice since female also contains male it will be selected:
cells = document.querySelectorAll('td');
console.log(cells);
[].forEach.call(cells, function (el) {
if(el.innerText.indexOf("male") !== -1){
//el.click(); click or any other option
console.log(el)
}
});
<table>
<tr>
<td>Peter</td>
<td>male</td>
<td>34</td>
</tr>
<tr>
<td>Susanne</td>
<td>female</td>
<td>14</td>
</tr>
</table>
<table>
<tr>
<td data-content="Peter">Peter</td>
<td data-content="male">male</td>
<td data-content="34">34</td>
</tr>
<tr>
<td data-conten="Susanne">Susanne</td>
<td data-content="female">female</td>
<td data-content="14">14</td>
</tr>
</table>
I agree the data attribute (voyager's answer) is how it should be handled, BUT, CSS rules like:
td.male { color: blue; }
td.female { color: pink; }
can often be much easier to set up, especially with client-side libs like angularjs which could be as simple as:
<td class="{{person.gender}}">
Just make sure that the content is only one word! Or you could even map to different CSS class names with:
<td ng-class="{'masculine': person.isMale(), 'feminine': person.isFemale()}">
For completeness, here's the data attribute approach:
<td data-gender="{{person.gender}}">
If you're using Chimp / Webdriver.io, they support a lot more CSS selectors than the CSS spec.
This, for example, will click on the first anchor that contains the words "Bad bear":
browser.click("a*=Bad Bear");
#voyager's answer about using data-* attribute (e.g. data-gender="female|male" is the most effective and standards compliant approach as of 2017:
[data-gender='male'] {background-color: #000; color: #ccc;}
Pretty much most goals can be attained as there are some albeit limited selectors oriented around text. The ::first-letter is a pseudo-element that can apply limited styling to the first letter of an element. There is also a ::first-line pseudo-element besides obviously selecting the first line of an element (such as a paragraph) also implies that it is obvious that CSS could be used to extend this existing capability to style specific aspects of a textNode.
Until such advocacy succeeds and is implemented the next best thing I could suggest when applicable is to explode/split words using a space deliminator, output each individual word inside of a span element and then if the word/styling goal is predictable use in combination with :nth selectors:
$p = explode(' ',$words);
foreach ($p as $key1 => $value1)
{
echo '<span>'.$value1.'</span>;
}
Else if not predictable to, again, use voyager's answer about using data-* attribute. An example using PHP:
$p = explode(' ',$words);
foreach ($p as $key1 => $value1)
{
echo '<span data-word="'.$value1.'">'.$value1.'</span>;
}
I find the attribute option to be your best bet if you don't want to use javascript or jquery.
E.g to style all table cells with the word ready, In HTML do this:
<td status*="ready">Ready</td>
Then in css:
td[status*="ready"] {
color: red;
}
If you want to apply style to the content you want. Easy trick.
td { border: 1px solid black; }
td:empty { background: lime; }
td:empty::after { content: "male"; }
<table>
<tr>
<td>Peter</td>
<td><!--male--></td>
<td>34</td>
</tr>
<tr>
<td>Susanne</td>
<td>female</td>
<td>14</td>
</tr>
</table>
https://jsfiddle.net/hyda8kqz/
Doing small Filter Widgets like this:
var searchField = document.querySelector('HOWEVER_YOU_MAY_FIND_IT')
var faqEntries = document.querySelectorAll('WRAPPING_ELEMENT .entry')
searchField.addEventListener('keyup', function (evt) {
var testValue = evt.target.value.toLocaleLowerCase();
var regExp = RegExp(testValue);
faqEntries.forEach(function (entry) {
var text = entry.textContent.toLocaleLowerCase();
entry.classList.remove('show', 'hide');
if (regExp.test(text)) {
entry.classList.add('show')
} else {
entry.classList.add('hide')
}
})
})
The syntax of this question looks like Robot Framework syntax.
In this case, although there is no css selector that you can use for contains, there is a SeleniumLibrary keyword that you can use instead.
The Wait Until Element Contains.
Example:
Wait Until Element Contains | ${element} | ${contains}
Wait Until Element Contains | td | male

Alternate <tr> background-color even if a <tr> is not displayed

How can I add a background-color to every second table row even some table rows are not displayed?
In my HTML is a simple table with some table rows. Just like that:
<label for="cb">hide blue</label>
<input id="cb" type="checkbox" onchange="hideBlue(this.checked)" />
<table>
<tr data-cat="red">
<td>red car</td>
</tr>
<tr data-cat="blue">
<td>blue jacket</td>
</tr>
<tr data-cat="red">
<td>red bull</td>
</tr>
<tr data-cat="red">
<td>red pen</td>
</tr>
</table>
As you can see there is also a checkbox which can toggle the "blue" category-rows.
My CSS is looking like that:
tr:nth-child(odd) {
background-color:#aaa;
}
But now if someone hides the "blue" category (by adding a display:none-class), the background colors are not alternate right anymore.
How can I prevent this failure?
I already tried to add :not(.displaynone) to the css rule but this didn't work.
Here is a jsFiddle
There is a way to get the updating alternating row colors using only CSS selectors.
tr:nth-child(odd) {
background-color:#aaa;
}
tr.displaynone ~ tr {
background-color: transparent;
}
tr.displaynone ~ tr:nth-child(even) {
background-color:#aaa;
}
What this does is it sets all siblings after the displaynone row background-color to transparent, and then sets the even rows after that background-color to #aaa.
This works for your example fiddle (forked here), but it will not work if you have to hide more than one element at once. (example, comment out the marked line and click on rows).
If you need to hide multiple rows at once, this solution will not work for you, and you'd have to use javascript to compute the row color whenever you hide/show rows.
As per your requirement only jquery can solve your problem. Even one <tr> is hidden the background color will be in sequence. Check DEMO.
Here is the jQuery Code.
$(document).ready(function(){
$("tr[data-cat='red']:even").css({'background':'red'});
$("tr[data-cat='red']:odd").css({'background':'orange'});
$("tr:not([data-cat='red'])").css({'background':'blue'});
});
You haven't tagged jQuery, but I'm giving you it as an alternative anyways (also for other users who might stumble upon the same problem).
function loopTable() {
var x=0;
$("table>tbody>tr").each(function() {
if( $(this).is(":visible") ) {
if( x%2==0 )
$(this).addClass('red');
else
$(this).removeClass('red');
x++;
}
});
}
This function should be called on body load, and everytime the table is altered. Or on interval.
DEMO.

CSS not working

Sometimes the dang thing works and at other times it doesn't. I have many tables in my app and the CSS for all of them is working. There's nothing different for this one, except for it, the CSS isn't being applied, God knows why. Help.
table.catalogContainer
{
border: none;
padding: 50px;
margin-left: 100px;
margin-right: 100px;
margin-top: 50px;
margin-bottom: 50px;
}
td.catalogCell
{
border: none;
padding: 30px, 20px, 50px, 20px;
}
<div id = "catalog">
<table class = "catalogContainer">
<% while ((category = Helper.GetNextCategory(categoryIndex++)) != null)
{ %>
<tr>
<td class = "catalogCell">
<img src = "../../Content/Category.gif"
width = "25px" height = "25px" alt = "Category" />
<b>
<%= Html.ActionLink(category.Name,
"DisplayCategory",
"Catalog",
new { id = category.Id },
null) %>
</b>
</td>
<td>
</td>
</tr>
<% } %>
</table>
Update
Ok, I found the problem. The border that was showing in the table still was due to the second on which I did not apply the class yet.
It may be that the CSS isn't being pulled in time while your script is running. Try the following pattern in your style sheet:
#catalog table
{
...
}
#catalog table td
{
...
}
Edit
I see you've found the problem. Still, you can clean up your implementation a bit by using the cascade a bit more effectively as shown above.
What browser are you testing with? When you view the page source does everything look as it should? Maybe try posting the generated source html for that section of the page. It's possible your template code is outputting some bad stuff that is breaking your html for this particular table (which could affect page rendering).
I find firebug invaluable when troubleshooting css. You can easily see which styles are being applied to particular elements. You can use it to select an element and view all sorts of css information, and even try different values on the fly.
Additionally,
the commas in your shorthand padding td.cataglogCellare are unnecessary and may cause some issues in different browsers/versions.
Additionally Additionally,
you really shouldn't be using the <b> tag to markup your content. <em> or <strong> are much better semantically and you can style to create whatever presentation is needed.

Resources