React table - rearranging - group by keep position of column - react-table

I'm using the useGroupBy hook to group my react-table table. However when grouping the default behaviour is to group re-arrange the columns in the table but I wan't to keep them in the same position, do you know how to do this with the optional useGroupByHook?
Here is the codesandbox: https://codesandbox.io/s/ecstatic-tree-bq19p?file=/src/App.js
When pressing groupby i.e age it will move age to the left..
Before grouping:

You can fix the ordering during table rendering:
const fixColumnOrder = (cells) => columns.map(column => cells.find(cell => cell.column.id === column.id))
<tr {...row.getRowProps()}>
{fixColumnOrder(row.cells).map((cell) => {
return (
<td {...cell.getCellProps()}>
{cell.render('Cell')}
</td>
)
})}
</tr>
You need to do the same trick for the headers

Related

Using logical operators in querySelector CSS

I am trying to select all table cells that have colSpan or rowSpan bigger than 1. I know that you can do querySelectorAll('td[colspan="3"]') to select cells that meet a narrower condition.
But I need something like querySelectorAll('td[colspan>"1"]').
It is not possible to put conditional operator inside the querySelector. In your case, if you want to achieve it using only CSS selector then you can ignore the particular colspan condition and select the remaining using :not operator.
td[colspan]:not(td[colspan = "1"]) {
background : red;
}
table, th, td {
border: 1px solid black;
}
td[colspan]:not(td[colspan = "1"]) {
background : red;
}
<table>
<tr>
<th>Month</th>
<th>Savings</th>
</tr>
<tr>
<td colspan="1">January</td>
<td>$100</td>
</tr>
<tr>
<td>February</td>
<td>$80</td>
</tr>
<tr>
<td colspan="2">Sum: $180</td>
</tr>
</table>
According to this link : https://bobbyhadz.com/blog/javascript-get-all-elements-by-data-attribute#get-all-dom-elements-by-partial-match-of-a-data-attribute
you can get elements with data-attribute starts with, ends with or contains with something like below:
// ✅ Get all where value of data-id starts with `bo`
const elements1 = document.querySelectorAll('[data-id^="bo"]');
console.log(elements1); // 👉️ [div, div]
// ✅ Get all where value of data-id ends with `ox1`
const elements2 = document.querySelectorAll('[data-id$="ox1"]');
console.log(elements2); // 👉️ [div]
// ✅ Get all where value of data-id contains with `box`
const elements3 = document.querySelectorAll('[data-id*="box"]');
console.log(elements3); // 👉️ [div, div]
in your case you can do something like that:
const getAllTdColspanBiggerThanOne= (a) => {
for (let i in a) if (a.hasOwnProperty(i)) {
if (a[i].getAttribute('colspan') > 1) {
alert(a[i].getAttribute('colspan'));
}
}
}
const td = document.querySelectorAll("td[colspan]") // or document.querySelectorAll("[colspan]")
const tdColspanBiggerThanOne = getAllTdColspanBiggerThanOne(td)

React conditional class name setting for styling

I am trying to make a quiz app using React.
I am currently working on the main quiz page where I have 4 buttons, and each of the buttons denotes an answer I'm importing from a question bank.
I want the current selected button to be highlighted, and for this I am currently using a state for each button. Is there any way to just use one state and deal with all four of the buttons, as this way is too tedious and cannot be used for a large number of such buttons? Also, I want only one button, the one the user selects finally, to be highlighted. So for this reason I need to set the state of all the other buttons to null, which makes the task even more tedious.
Here is the div containing the buttons
<div>
<button className={selected1} onClick={() => dealingWithOptions("A")}>{questions[currentQuestion].optionA}</button>
<button className={selected2} onClick={() => dealingWithOptions("B")}>{questions[currentQuestion].optionB}</button>
<button className={selected3} onClick={() => dealingWithOptions("C")}>{questions[currentQuestion].optionC}</button>
<button className={selected4} onClick={() => dealingWithOptions("D")}>{questions[currentQuestion].optionD}</button>
</div>
Here is the function dealing with the options clicking
const [selected1,setSelectedButton1] = useState("")
const [selected2,setSelectedButton2] = useState("")
const dealingWithOptions = (op) => {
setOptionChosen(op);
if (op=="A") {
setSelectedButton1("selected1");
setSelectedButton2("")
setSelectedButton3("")
setSelectedButton4("")
} else if (op=='B') {
setSelectedButton1("");
setSelectedButton2("selected2")
setSelectedButton3("")
setSelectedButton4("")
} else if (op=='C') {
setSelectedButton1("");
setSelectedButton2("")
setSelectedButton3("selected3")
setSelectedButton4("");
}
else if (op=='D') {
setSelectedButton1("");
setSelectedButton2("")
setSelectedButton3("")
setSelectedButton4("selected3");
}
}
It can be solved and optimized in many ways. I am trying to give what suit your current code most.
I assume you have a state that stores choosen option.
Now update all the button like this
<button className={choosen == "A" ? "selected" : "" } onClick={() => dealingWithOptions("A")}>{questions[currentQuestion].optionA}</button>
Here choosen is the state where the selected option is being stored.
Explanation: Here what we are doing is, we are matching for each button that if it is the selected button then add the selected class else add nothing.
I would suggest having one state, that stores the chosen option (so either A, B, C or D) and then in your JSX part you have a condition that assigns the classname "selected" to the appropiate button.
If the selection changes so will the state, which triggers a rerender (so you don't even have to take away the "selected" class, since it will just be assinged to (and only) the right one on rerender
I'd suggest you set one piece of state to maintain the selected button.
setSelectedButton('A')
Or undefined if none is selected
Then,
<button className={selectedButton === 'A' ? 'selected1' : ''}>...
That said for conditional classes I'd use something like clsx rather than building class strings manually
e.g.
<button className={clsx({selected1: selectedButton === 'A'})}>...
Several ways to achieve this. You can set one state: const [selectedId, setSelectedId] = useState("")
Define the function: const selectHandler = (e) => setSelectedId(e.target.name)
On your buttons call the function and set the state to the Id: <button className={selectedId === "a" && "selected"} name="a" onClick={() => setSelectedId("a")}>Button A</button> For the rest of the buttons, change the name as well as the parameter passed to the function. Also the string inside the className comparison

React table v8 - how to render custom cell content?

I'm using #tanstack/react-table v8, and I want to have some icons and buttons in one of my table row cells. Looking at the docs I can use a display column for this:
Display columns do not have a data model which means they cannot be sorted, filtered, etc, but they can be used to display arbitrary content in the table, eg. a row actions button, checkbox, expander, etc.
The docs also mention this about custom rendering of cell content:
By default, columns cells will display their data model value as a string. You can override this behavior by providing custom rendering implementations. ... You can provide a custom cell formatter by passing a function to the cell property ...
The docs show this example for custom rendering of a cell:
columnHelper.accessor('firstName', {
cell: props => <span>{props.getValue().toUpperCase()}</span>,
})
So, I have defined a display column using ColumnHelper:
columnHelper.display({
id: "actions",
cell: (props) => (
<div className="w-10 h-10 bg-red">
<span>actions?</span>
</div>
),
}),
How do I access this render function when creating the table? I'm creating the table body as follows (the table variable is returned by the useReactTable hook):
<tbody>
{table.getRowModel().rows.map((row, index) => (
<tr className={classNames(index % 2 !== 0 && "bg-tableRowGray")}>
{row.getAllCells().map((cell) => (
<td>{cell.getValue()}</td>
))}
</tr>
))}
</tbody>
cell.getValue() and cell.renderValue() do not call the custom render function and I don't see any other render functions on the cell object. Am I just overlooking something really simple here?
Turns out I was overlooking something simple. #tanstack/react-table exports a flexRender function which is used to render the cell, e.g.
import {flexRender} from "#tanstack/react-table";
...
<tbody>
{table.getRowModel().rows.map((row, index) => (
<tr className={classNames(index % 2 !== 0 && "bg-tableRowGray")}>
{row.getAllCells().map((cell) => (<td>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>))}
</tr>
))}
</tbody>

angular 8 getting alternate color on rows that uses ngFor

I am running into issues in getting the alternate rows with 2 different colors. I have a JSON data coming from backend, which is a dynamic array of objects. I need to show the data in the table as in the example here:
https://stackblitz.com/edit/create-xpkbjm?file=app/app.component.html
The odd, even rule that I find in most Stackoverflow questions doesn't seem to work here.
I would like to show the rows with alternate colors(the rows come as groups). The data should be presented similar to the link in the above stackblitz, but each rows should have alternate color. The odd, even rule only work for each group, but doesn't alternate the color in the table as a whole.
You can do this with plain old css,
tr:nth-child(2n) {
background: lightgray;
}
You can update the object that you're using to loop over and create the table.
Currently, you're using two loops,
one over tbody with groups
and one over tr with group.events
Instead, you can create another variable that is a list of events,
// app.component.ts
events = this.groups.map(g => g.events).flat();
Which simplifies your HTML to,
<!-- app.component.html -->
<tr *ngFor="let event of events; index as i">
<td>
{{event.event}}
</td>
</tr>
Check on stackblitz
UPDATE:
You can track total elements using a group/count property on each event across the whole object like so,
//app.component.ts
constructor() {
this.createGroups();
}
createGroups() {
let count = 0;
this.groups.forEach(g => {
g.events.forEach(e => {
e.group = count++;
});
});
console.log(this.groups);
}
And then use [ngClass] to bind a css class based on this property,
<tbody>
<tr *ngFor="let group of groups; index as i">
<table>
<tbody>
<tr *ngIf="i!==0">
<td></td>
</tr>
<tr *ngFor="let event of group.events;" [ngClass]="{'even': event.group % 2 !== 0}">
<td>{{event.event}} {{event.group}}</td>
</tr>
</tbody>
</table>
</tr>
</tbody>
Updated stackblitz
You can do this using pure CSS without the need to modify the angular.
tbody tr
{
background-color: #FFFFFF;
color: #fff;
}
tbody tr:nth-child(odd)
{
background-color: #000000;
color: #fff;
}

Change a block type in InnerBlocks template without losing content

I have a block type that contains other blocks using InnerBlocks, with the output generated by an InnerBlocks template (similar to the columns core block). I want to be able to change one of the blocks to another block type, (one that has the transform defined), but when I do, the content gets erased.
Is there a way to do this?
By way of analogy, if the core columns block had sub-blocks "column-third" and "column-half", would there be a way of changing from two "column-half"s to three "column-third"s without losing the data?
To expand on the above analogy, suppose I made clones of the core columns and column blocks, and I modify the columns block like so:
The template generation function:
var childBlockName = 'core/column';
const getColumnsTemplate = memoize( ( columns ) => {
return times( columns, () => [ childBlockName ] );
} );
and the RangeControl in the edit function:
<RangeControl
label={ __( 'Columns' ) }
value={ columns }
onChange={ ( nextColumns ) => {
childBlockName = 'coreclone/column';
setAttributes( {
columns: nextColumns,
} );
} }
min={ 2 }
max={ 6 }
/>
core/column and coreclone/column are identical. In this example, when I change the number of columns, the child block-type changes in the template. The core/column blocks don't get converted to coreclone/column blocks, they just get wiped and new coreclone/column blocks are made. I would like them to be transformed to coreclone/column blocks without the contents being wiped.

Resources