I have a table dynamically built like this:
<table>
<colgroup>
<col/>
<col/>
</colgroup>
<tr>
<th>#</th>
<th>Name</th>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-2</td>
</tr>
<tr>
<td>☐</td>
<td>piece-of-data-3</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-4</td>
</tr>
</table>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
☐
indicates an empty ballot box (☐) whereas ☑
indicate a checked ballot box (☑).
The table is produced by PowerShell's ConvertTo-Html
function which prevents using id
s - for example - to differentiate specific rows or columns. Also, the PS scripts are used in environments with no internet connectivity which limits me to use only native JavaScript (no jQuery or such, no way to place .js file alongside either). How can I color the entire tr
red based on the first td
in that row having an empty ballot box? Example of the desired outcome: https://jsfiddle.net/b1pgezfm/
CodePudding user response:
Traversing the DOM is basic Javascript - in your example, you'd check each <td>
element for the content you're looking for. If you find it, grab its parent <tr>
element and change the parent's background color.
const rows = document.querySelectorAll('td');
rows.forEach((row) => {
if (row.innerHTML === '☐') {
const parent = row.parentNode;
parent.style.backgroundColor = 'red';
}
});
<table>
<colgroup>
<col/>
<col/>
</colgroup>
<tr>
<th>#</th>
<th>Name</th>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-2</td>
</tr>
<tr>
<td>☐</td>
<td>piece-of-data-3</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-4</td>
</tr>
</table>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
My first thought was also: Javascript. But if you have the controll over your DOM, you can also do it with pure CSS. Like here:
tr[class*="isUnchecked"] {
background: #ffff00;
}
<table>
<colgroup>
<col/>
<col/>
</colgroup>
<tr>
<th>#</th>
<th>Name</th>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-2</td>
</tr>
<tr class="isUnchecked">
<td>☐</td>
<td>piece-of-data-3</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-4</td>
</tr>
</table>
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
One approach I could suggest is the following:
// using a named function to perform the highlighting,
// supplying default values for the available options
// (these are, of course, customisable by passing in
// new property-values to the function):
const highlight = (opts = {
// String, CSS selector to identify the elements
// from which highlighting should be determined:
elementSelector: 'td:first-child',
// String, CSS selector to identify the element
// to which highlighing should be applied:
ancestorSelector: 'tr',
// Number, the unicode character code of the character
// that should lead to highlighting being present:
characterCode: 9744,
// String, the class to apply to the ancestor to
// show highlighting:
highlightClass: 'highlight'
}) => {
// retrieving the property-values from the opts Object,
// to use those values within the function (without having
// to preface with the 'opts' Object-name, so we can use
// 'elementSelector' instead of 'opts.elementSelector'):
const {
elementSelector,
ancestorSelector,
characterCode,
highlightClass
} = opts;
// here we select all elements in the document, using
// document.querySelectorAll() with the elementSelector
// variable; then we use NodeList.prototype.forEach()
// to iterate over that NodeList, using an Arrow function:
document.querySelectorAll(elementSelector).forEach(
// here we pass in a reference to the current element/node
// of the NodeList over which we're iterating, and
// and navigate from that element to its ancestor using
// the CSS selector passed to the function:
(el) => el.closest(ancestorSelector)
// here we use the Element.classList API to toggle a
// class-name on that element:
.classList.toggle(
// we toggle the class-name that was passed to the function:
highlightClass,
// if this assessment evaluates to true/truthy the class-name
// is applied, if it evaluates to false/falsey it is removed;
// in the assessment we retrieve the textContent of the current
// element, remove the leading/trailing whitespace (using
// String.prototype.trim()) and then retrieve the character code
// of the character at the zeroth (first) position in the String.
// that character-code is then compared to see if it is
// exactly-equal to the character code passed in to the function:
el.textContent.trim().charCodeAt(0) === characterCode)
)
};
// calling the function:
highlight();
*,
::before,
::after {
box-sizing: border-box;
font: 1rem / 1.5 sans-serif;
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
}
.highlight {
background-color: red;
}
td:last-child {
padding-inline-start: 1em;
}
<table>
<colgroup>
<col />
<col />
</colgroup>
<tr>
<th>#</th>
<th>Name</th>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-2</td>
</tr>
<tr>
<td>☐</td>
<td>piece-of-data-3</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-4</td>
</tr>
</table>
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
If, however, you'd rather use a function to retrieve the elements to be highlighted and then use another function to apply that highlighting, the following can be used:
// defining named functions the retrieve() function
// taking optional arguments as before (in the previous
// highlight() function):
const retrieve = (opts = {
elementSelector: 'td:first-child',
characterCode: 9744
}) => {
const {
elementSelector,
ancestorSelector,
characterCode,
highlightClass
} = opts;
// here we use Array.from() to convert the NodeList returned
// from document.querySelector() into an Array, in order to
// use Array.prototype.filter() to filter the collection of
// Nodes:
return Array.from(
document.querySelectorAll(elementSelector)
// within Array.prototype.filter() we use an Arrow function to:
).filter(
// to trim the text-content of the current element of the Array
// of elements of its leading/trailing white-space, and then
// establish if the zeroth character is exactly-equal to the
// characterCode passed to the function; a true/truthy result
// retains the current array-element in the new array returned,
// and a false/falsey value leads to the array-element being
// discarded:
(el) => el.textContent.trim().charCodeAt(0) === characterCode
);
},
// the function expects an iterable collection ('collection'),
// and also allows custom options to be passed in by the user
// (as in the previous code/demo):
highlight = (collection, opts = {
'ancestor': 'tr',
'highlightClass': 'highlight'
}) => {
// here we convert the iterable collection into an Array, using
// Array.from(), and then use Array.prototype.forEach() to iterate
// over that collection:
Array.from(collection).forEach(
// again, using an Arrow function we navigate to the closest ancestor
// element that matches the supplied CSS selector, and we add the
// supplied highlightClass-name to apply that class to the
// matched ancestor element:
(el) => el.closest(opts.ancestor).classList.add(opts.highlightClass)
);
};
// calling the functions:
highlight(retrieve());
*,
::before,
::after {
box-sizing: border-box;
font: 1rem / 1.5 sans-serif;
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
}
.highlight {
background-color: red;
}
td:last-child {
padding-inline-start: 1em;
}
<table>
<colgroup>
<col />
<col />
</colgroup>
<tr>
<th>#</th>
<th>Name</th>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-2</td>
</tr>
<tr>
<td>☐</td>
<td>piece-of-data-3</td>
</tr>
<tr>
<td>☑</td>
<td>piece-of-data-4</td>
</tr>
</table>
<iframe name="sif5" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
References: