React maintain focus position after clicking on different element - reactjs

I have a two column table. The right column has input fields. When an input field is focused (as shown in picture below), it displays a non focusable button on the right side of the table. When the user clicks this button, it should modify the focused input field with it's text but reversed. This button is non TABable.
My problem is that when the user clicks the button to reverse the text, it unfocuses his current position in the table. Meaning, when he clicks tab again after clicking the button, the focus goes back to the top of the page instead of continuing from the input box that just got reversed.
How can I retain the tab position even when the user clicks on other non TABable elements?

Your best tactic is to keep track of what element is currently focused on in a global state variable. When each element is focused on, whether through clicking on it or tabbing onto it, you have to detect the change in focus and update the state variable which is keeping track of that. That means that each of your table cells that is tabable needs an eventListener for onFocus that updates the state variable. Then you can attach an event listener to your button which will shift the focus to the adjascent cell. So if your table looks like this:
<table>
<tr>
<td class="title">Source</td>
<td class="title">Note</td>
</tr>
<tr>
<td id="1" tabindex="1">Climbing</td>
<td id="2" tabindex="2">I really like this sport</td>
</tr>
<tr>
<td id="3" tabindex="3">Running</td>
<td id="4" tabindex="4">Meh not a fan</td>
</tr>
<tr>
<td id="5" tabindex="5">Biking</td>
<td id="6" tabindex="6">I need a bike</td>
</tr>
<tr>
<td id="7" tabindex="7">Eating</td>
<td id="8" tabindex="8">who isnt a fan</td>
</tr>
</table>
<button id="toggleButton">Toggle focus</toggle>
You can set up some basic JS like so:
// global state variable
let currentlyFocusedElement;
const tds = document.querySelectorAll('td')
// attach onFocus events to all your tabable boxes
tds.forEach( td => {
td.addEventListener('focus', () => {
currentlyFocusedElement = td
console.log(currentlyFocusedElement)
})
})
const toggleButton = document.querySelector('#toggleButton')
// use the button to change the focus programmatically
// my if statements here are a little slapdash, but they work to solve your problem, can be easily adjusted
toggleButton.addEventListener('click', () => {
const number = currentlyFocusedElement.id
console.log(number)
if (number % 2 === 0){
document.getElementById(`${number-1}`).focus()
} else if (number % 2 === 1) {
document.getElementById(Number(number)+1).focus()
}
})
I made a working example here: codepen tab thing
Of course after writing this all up I saw that your question has a reactjs tag and it makes me wish you would have been way more specific in your question and even included your code. I'm sure you can adapt some of my vanilla js solution to a react environment.

Related

Cypress - Click checkbox that depends on the value of another checkbox on a table

I have a table with 2 columns of checkboxes.
I want to click the first checkbox that has the checked value on the right.
Note: All the checkboxes on the left are not disabled and all the checkboxes on the right are disabled.
Edit:
My DOM looks like this:
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{items.map(item) => (
<tr>
<td>
<input type="checkbox" />
</td>
<td>
<input type="checkbox" checked={item.posted} disabled />
</td>
</tr>
)
}
</tbody>
</table>
It is reiterated by the data from the api thats why the checked state of the checkbox is dynamic
Try finding the first checked, then the move to the first checkbox
cy.get('input:checked') // all checked inputs
.eq(0) // first one
.parent('td') // move to cell
.prev() // previous cell
.find('input') // it's input
.click()
To clarify how to correctly use .each(), you need to use the 3rd parameter collection and also return false to avoid checking every row which is checked on the right.
cy.get('input[type="checkbox"]')
.each(($ele, index, collection) => {
if ($ele.is(':checked')) {
cy.wrap(collection).eq(index-1).click()
return false; // break early to only click the first
}
})
You try something like this, iterate over all the checkboxes, once you find the checkbox that is disabled, you can go to a the previous checkbox using .eq(index-1) and then perform the click().
cy.get('input[type="checkbox"]').each(($ele, index) => {
if ($ele.is(":disabled")) {
cy.wrap($ele).eq(index-1).click()
}
})

How to Toggle Adjacent row (in a loop) in reactjs by using a function

I have an api call and then data is stored in state. Then I render that data by using map and i have got a table. Look is over two rows of that table. One row is visible and other is hidden. The first row has got the toggle button. How can I create a function that toggle the second row for each record.
<table>
{data.map(user => (
<>
<tr>
<td>{user.name}</td>
<td>
<button>Toggle</button>
</td>
</tr>
<tr className="additional-info">
<td colSpan="2">{user.company.catchPhrase}</td>
</tr>
</>
))}
</table>
i want to toggle the tr with class additional info.
the code is here
https://codesandbox.io/s/flamboyant-williamson-7m729
toggling the visiblity of a component can be considered a state change. You need to store the state of whether a component is visible or not somewhere and because you are using hooks, a simple way is to use setState hook.
I have made a code sample that uses that approach here.

ui.bootstrap.collapse - one collapse for each row in a table column - open only the collapse which has been clicked (not all of them)

I'm using ui-bootstrap.collapse inside a table with dynamic data from a JSON API. Two of the multiple columns contain collapsed partials for each of its rows.
When I'm clicking on one of the icons to toggle that particular partial, every partial of the entire column open up.
Same issue when I'm trying to close the partial again - any of the icons work (not only the one of that particular partial).
I'm suspecting that I got to add a unique id or something like this to each of the partials to make one only that particular one pop up. But I'm not able to get this working.
Can any one point me in the right direction please?
Here is some of my code (I replaced the dynamic data with static data to make it simpler:
...
<tbody ng-repeat="url in urls">
<tr>
<td>{{url.url}}<span class="pull-right" ng-click="toggleUrl()">▼</span></td>
<td>{{url.title}}</td>
<td>{{url.traffic}}<span class="pull-right" ng-click="toggleTraffic()">▼</span></td>
</tr>
<!-- urlCollapsed -->
<tr collapse="!urlCollapsed">
<td colspan="4" style="background-color: pink">
</td>
</tr>
...
and a working plunker with the entire code: http://plnkr.co/edit/e4UldAFjjIr26Il73C88?p=preview
You need to pass url or some unique field to toggleUrl() method and then at the method make specific variable for each roe partial nd make them true. Accordingly you'll have to mak specific partial for each nd shoe them accordingly on ng-show.
Something like this
$scope.toggleUrl = function (url) {
if (url === '/1') {
$scope.url1Collapsed = !$scope.url1Collapsed;
} else {
$scope.urlCollapsed = !$scope.urlCollapsed;
}
};
and on page
<tr collapse="!url1Collapsed">
<td colspan="4" style="background-color: pink">partial 1</td>
</tr>
<tr collapse="!urlCollapsed">
<td colspan="4" style="background-color: pink"> partial 0</td>
</tr>

Make a single button active in a group of buttons created by a ng-repeat

I am working on a directive that displays a table using ng-repeat directives. Each cell in the table may contain a button depending on the data. Here is the pertinent snippet of the template.
<tbody>
<tr ng-repeat="row in data.rows">
<td>{{ row.rowName }}</td>
<td ng-repeat="cell in row.cells">
<button id="button-{{ row.id }}-{{ cell.id }}"
class="btn btn-primary"
ng-show="cell.isActive"
ng-click="onClick(row.id, cell.id, $event)">Select</button>
</td>
</tr>
</tbody>
In the controller I have
$scope.onClick = function (rowId, cellId, event) {
$scope.selectedRowId = rowId;
$scope.selectedCellId = cellId;
$scope.selectedButtonId = event.target.id;
};
The goal is to make it such that when a user clicks a button in the table, that button becomes active. When the user clicks on another button, it will become active making the previously active button inactive. This functionality will be used to drive what is displayed on another section of the page.
I have tried a few things, but I think the most angular way of doing things is to use ng-class in the buttons to apply the active and inactive classes based on what button has been clicked. To this end, I am assigning them all unique ids and tracking which button has been clicked based on the event.
The problem is I can not figure out how to make the ng-class work. Is there some way I can get the id of the current button that I am in, or am I going about this completely wrong in the first place.
Thanks.
In your button code set the html ng-class attribute :
ng-class="active : selectedRowId = row.id & selectedCellId = cell.id"

How do I make an entire row clickable except for one cell and its margins in angular?

Let's say I have a row that looks like
<tr>
<td>John</td>
<td>Smith</td>
<td>Approved?<input type="checkbox"/></td>
</tr>
Each row shows one employee, and allows you to check to approve/disapprove the employee (e.g. for registration for a course). I want the user to be able to click anywhere on the row to get greater detail about the employee, but if they click on the last column ("Approved?") it should not go to greater detail, since it should just change the checkbox.
Here are the solutions I know of, none is great:
Entire row: <tr class="clickable" ng-click="go()">. Makes all the cells and margins clickable, and only requires one ng-click entry, but then the checkbox causes "go()" to execute, which is bad.
Each cell: <td class="clickable" ng-click="go()">...<td class="clickable" ng-click="go()">. Pro: can restrict to just the cells I want. Con: lots of repetition (not DRY), and misses the margins.
Entire row with special "go" fn: <tr class="clickable" ng-click="go()">, but "go" knows how to differentiate between different cells. Pro: Has exactly the effect. Con: requires lots of view/html knowledge in a specialized controller action.
How can I make the first 2 columns and their margins clickable, but not the 3rd or its margins?
You can use two click handlers, one for a whole tr , second for a last td' checkbox. In the second use Event's stopPropagation method.
Controller:
var TableCtrl = function($scope){
$scope.click1 = function(){
console.log("Click 1 method")
}
$scope.click2 = function(e){
console.log("Click 2 method");
e.stopPropagation();
}
}
Markup:
<table ng-controller="TableCtrl">
<tr ng-click="click1()">
<td>John</td> <td>Smith</td>
<td>Approved?<input type="checkbox" ng-click="click2($event)"/></td>
</tr>
</table>
Working example: http://jsfiddle.net/zDMQB/1/
How about this?
<tr>
<td style="cursor: pointer;" ng-click="alert('hello1')">John</td>
<td style="cursor: pointer;" ng-click="alert('hello2')">Smith</td>
<td>Approved?<input type="checkbox"/></td>
</tr>

Resources