react table row show column on button click - reactjs

I have a table being built dynamically by mapping over an array. Each item in the array gets a row. One of the columns in each of these rows is a select. I only want that column's content to show when a button in the same row's next column is clicked.
My plan was to add some sort of a toggle bool property to each object in my array, but then when I try to toggle it in my button's onclick, my eslint is complaining because I'm trying to modify a property of the parameter I sent into the function called by the onclick.
What is the appropriate way to do this?
Here's the code for the table:
<table>
<tbody>
{myArray.map(row => (
<tr key={`test-${row.name}`}>
<td>
<div className="testClass">{row.id}</div>
</td>
<td>{row.name}</td>
<td>
<Select
options={this.getOptions(row.id)}
onSelect={this.onOptionSelect}
placeholder="Select something"
/>
</td>
<td><button onClick={() => { changeStuff(row); }}>{ row.myToggle ? 'Save' : 'Change something' }</button></td>
</tr>
))}
</tbody>
</table>

In click handler, you can update your array altogether to show/hide the select option.
Based on my understanding, I have tried creating below snippet. This is the way i could come up with, as per my understanding. I have maintained 'hidden' field in the array of objects. Instead of 'Select' I have used a simple button. You can change accordingly. Hope this helps.
const list = [
{
name: "Person 1",
phone: "123-4567",
id: 11,
hidden:true
},
{
name: "Person 2",
phone: "123-4567",
id: 12,
hidden:true
},
{
name: "Person 3",
phone: "123-4567",
id: 23,
hidden:true
},
{
name: "Person 4",
phone: "123-4567",
id: 34,
hidden:true
},
{
name: "Person 5",
phone: "123-4567",
id: 45,
hidden:true
}
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
list: list
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(item) {
let updatedList = this.state.list.map(obj => {
if(obj.id === item.id) {
return Object.assign({}, obj, {
hidden:!item.hidden
});
}
return obj;
});
this.setState({
list : updatedList
});
}
render() {
return (
<div>
<table>
<tbody>
{this.state.list.map(item =>
<tr key={item.itemId}>
<td>
{item.name}
</td>
<td>
{item.phone}
</td>
<td >
<button hidden={item.hidden}> Action </button>
</td>
<td>
<button
className="delete"
onClick={() => this.handleClick(item)}
>
Change
</button>
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
table td {
font-size: 14px;
font-weight: normal;
padding: 10px;
border: 1px solid #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Related

Angular filter table with search across two columns simultaneously

I have a table with multiple columns that I currently filter using a text input (search) field:
HTML (simplified):
<div class="search">
<input type="text" placeholder="Search" data-ng-model="vm.filter_on" />
</div>
<tr data-ng-repeat="product in vm.data | filter: vm.filter_on>
<td>{{product.id}}</td>
<td>{{product.name}}</td>
<td>{{product.brand}}</td>
</tr>
Let's say I have these three products:
{
id: 1,
name: "Waffles",
brand: "Walmart",
},
{
name: "Pizza",
brand: "Walmart",
},
{
name: "Soda",
brand: "Target",
}
If I enter "Walmart" in the search bar, I will see the first two objects. What I want to know is if it's possible to search "Walmart piz" and only be shown the second object--essentially, have the search term try to match across values from multiple columns.
Most of what I've found when looking for a solution has been about trying to set the specific columns a search will consider, but I haven't found anything that solves my exact use case.
I created a workaround using the nifty filter from this question, which solves the issue of searching with multiple fragments rather than full terms: AngularJS filter for multiple strings
But even then, I still need to combine the column data into a single string for the search to work. Is there any way to do this more elegantly in Angular?
You should create custom filter:
angular.module('app', []).controller('ctrl', function($scope) {
var vm = this;
vm.filter_on = "Walmart piz";
vm.data = [
{ id: 1, name: "Waffles", brand: "Walmart" },
{ name: "Pizza", brand: "Walmart" },
{ name: "Soda", brand: "Target" }
]
}).filter('custom', function(){
return function(input, search){
if(!search)
return input;
var items = search.split(' ').filter(x => x).map(x => x.toLowerCase());
return input.filter(x => {
for(var item of items){
var flag = false;
for(var prop in x){
if(prop != '$$hashKey' && (x[prop] + '').toLowerCase().indexOf(item) != -1){
flag = true;
break;
}
}
if(!flag)
return false;
}
return true;
})
}
})
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js">
</script>
<div class="search" ng-app='app' ng-controller='ctrl as vm'>
<input type="text" placeholder="Search" ng-model="vm.filter_on" />
<br>
<br>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>brand</th>
<tr>
</thead>
<tbody>
<tr data-ng-repeat="product in vm.data | custom: vm.filter_on">
<td>{{product.id}}</td>
<td>{{product.name}}</td>
<td>{{product.brand}}</td>
</tr>
</tbody>
</table>
</div>

Reactjs do not re-render when children node changed

I contrusted a table-form with two children node buttons and rows using Reactjs.
After change the value of each child node, the parent node receive the new state,
but no re-render happened, why?
<!DOCTYPE html>
<html>
<head>
<title> w3du </title><meta charset="utf-8">
<!-- Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
<style>
.quarcol {
width:25%
}
.vmidcol {
vertical-align:middle
}
.cyan {
color:#fff;
background-color:#63aae7
}
.blue {
color:#fff;
background-color:#60a0e0
}
</style>
</head>
<body>
<div class="container">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title text-center">Panel</h3>
</div>
<div id="canvas"></div>
</div>
</div>
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.js"></script>
<script src="https://cdn.bootcss.com/react/15.6.1/react.js"></script>
<script src="https://cdn.bootcss.com/react/15.6.1/react-dom.js"></script>
<script src="https://cdn.bootcss.com/babel-standalone/6.26.0/babel.js"></script>
<script src="https://cdn.bootcss.com/js-signals/1.0.0/js-signals.js"></script>
<script type="text/babel">
The 'table-row' stuff, the 3rd column is number, after modified, the associated button doesn't changed its value, vice versa
var BudgetTableRow = React.createClass({
getInitialState : function(){
return {
n : this.props.n,
price : this.props.price
};
},
handleChangeNum : function(e){
this.setState({
n : e.target.value
});
this.props.callback(this.props.idx, e.target.value);
},
render : function(){
var styfull = {
width : '100%',
height : '30%',
};
var stycenter = {
verticalAlign : 'middle',
horizontalAlign : 'middle'
};
return (
<tr className={this.props.color}>
<td className="quarcol text-center monofont" style={stycenter}>
<a href="#" data-toggle="tooltip" title={"<h1>"+this.props.label+"</h1>"}>{this.props.name}</a>
</td>
<td className="quarcol"><input name={this.props.label+'-price'}
className="form-control text-center monofont"
type="text" defaultValue={this.state.price} style={styfull}
onChange={this.handleChangePrice}
/></td>
<td className="quarcol"><input name={this.props.label+'-n'}
className="form-control text-center monofont"
type="text" defaultValue={this.state.n} style={styfull}
onChange={this.handleChangeNum} onFocus={this.handleFocus} onBlur={this.handleBlur}
/></td>
<td className="quarcol text-center monofont" style={stycenter}>
{this.state.price * this.state.n}
</td>
</tr>
);
}
});
The 'button' stuff, after modified, the associated table-row doesn't changed its value
var BudgetTableButton = React.createClass({
getInitialState : function(){
return {
n : this.props.n
};
},
handelClick : function(e){
e.preventDefault();
var n = (this.state.n=="0" || this.state.n=="") ? "1" : "0";
this.setState({
n : n
});
this.props.callback(this.props.idx, n);
},
render : function(){
var cls = (this.state.n=="0" || this.state.n=="") ? null : 'btn-info';
return (
<button className={'btn ' + cls} onClick={this.handelClick}>{this.props.label} {this.state.n}</button>
);
}
});
The parent node, the console.log() does right, but the form doesn't re-render, i used unique-key, is it something wrong with this.state.items.map ?
var BudgetTable = React.createClass({
getInitialState : function(){
return {
items : this.props.items
};
},
handleCallback : function(idx, n){
var items = this.state.items;
items[idx].n = n;
this.setState({
items : items
});
},
render : function(){
console.log(this.state.items[0]);
return (
<table className="table monofont">
<thead>
<tr className="blue">
<td className="text-center" colSpan="4">Table</td>
</tr>
</thead>
<tbody>
<tr><td colSpan="4">
{this.state.items.map((it, idx) =>
<BudgetTableButton key={it.label+'r'} label={it.label} idx={idx} callback={this.handleCallback} n={it.n} />
)}
</td></tr>
{this.state.items.map((it, idx) =>
<BudgetTableRow key={it.label+'b'} label={it.label} name={it.name} price={it.price} idx={idx} callback={this.handleCallback} n={it.n} />
)}
</tbody>
</table>
);
}
});
function init(){
return [
{
"price": 1340,
"name": "shoe",
"label": "Q41",
"n" : 1
}, {
"price": 1290,
"name": "clothes",
"label": "Q42",
"n" : 1
}
];
}
ReactDOM.render(
<BudgetTable items={init()} />,
document.getElementById("canvas")
);
</script>
</body>
</html>
The problem in your example is that you are using two states.
(I will talk here about the button component, but both cases are the same)
As you probably already know, the local state can only trigger update on the component it belongs to.
You are trying to sync the button's state with the state of the main app. However getInitialState is only called on initial render and not on re-render (info here).
In order to do what you want, you will need to get the button/row information from the props and not from the state (you already have them in props).
Furthermore, I would delete the children's state at all, because you don't need it. It is better that you keep the state in the top level component and only pass props and functions (like the callback fn) down to the children.
So in this case, if you change the button to the following code, it works flawlessly:
var BudgetTableButton = React.createClass({
handelClick : function(e){
e.preventDefault();
var n = this.props.n === "0" || this.props.n === "" ? "1" : "0";
this.props.callback(this.props.idx, n);
},
render : function(){
var cls = (this.props.n=="0" || this.props.n=="") ? null : 'btn-info';
return (
<button className={'btn ' + cls} onClick={this.handelClick}>{this.props.label} {this.props.n}</button>
);
}
});
You are not mutating the state.
handleCallback : function(idx, n){
var items = [...this.state.items];
items[idx].n = n;
this.setState({
items : items
});
}

AngularJS mg-repeat items with checkbox preventing default on cancelled confirmation

I am using angular (first version) and I having trouble trying to accomplish a task. I have a list of item that I retrieve from the server DB. I am listing those items in a HTML table. There's a Boolean field that I want to update dynamically when the user check or uncheck a checkbox. The problem is during the confirmation. When I cancel the confirmation the check if still retaining its state (check/uncheck) and not going back to its previous state. I tried using "preventDefault", it didn't work. I tried "refreshing" the item array so the view might refresh the data, it didn't work. Here's a fiddle with a representation of what I have:
Fiddle
<div ng-app ng-controller="demoController">
<h3>
<span class="status">{{ status }}</span>
</h3>
<h2>
Movies i've seen
</h2>
<table>
<tr>
<th>Name</th>
<th>Have I seen it?</th>
</tr>
<tbody>
<tr ng-repeat="movie in movies">
<td> {{movie.name}}</td>
<td style="text-align: center">
<input value=" {{ movie.name }}" type="checkbox" ng-checked="movie.seen" ng-click="confirmSeen(this, $index)" /> </td>
</tr>
</tbody>
</table>
</div>
function demoController($scope) {
$scope.status = "AngularJS is up";
$scope.confirmSeen = function(e, idx) {
var movie = $scope.movies[idx];
if (movie !== undefined) {
var msg = "";
if(movie.seen) {
msg = "Are you sure you want to mark " + movie.name + " as unseen?";
} else {
msg = "Are you sure you want to mark " + movie.name + " as seen?";
}
if (confirm(msg)) {
movie.seen = !movie.seen;
$scope.movies.splice(idx, 1, movie);
} else {
$scope.movies.splice(idx, 1, movie);
e.stopImmediatePropagation();
e.preventDefault();
}
} else {
e.stopImmediatePropagation();
e.preventDefault();
}
}
$scope.movies = [{
name: "Conan",
seen: false
}, {
name: "Scarface",
seen: true
}, {
name: "GhostBuster",
seen: false
}, {
name: "The Shawshank Redemption",
seen: true
}, {
name: "Goodfellas",
seen: true
}, {
name: "Life",
seen: false
}];
}
You've got to pass the $event in the ng-click function.
Simply replace:
ng-click="confirmSeen(this, $index)"
To:
ng-click="confirmSeen($event, $index)"

Apply color on the item using custom filters in AngularJs

I have an array of objects:
[
{
idPriority: 1,
priority: "Critical",
isChecked: 0
},
{
idPriority: 2,
priority: "High",
isChecked: 0
},
{
idPriority: 3,
priority: "Medium",
isChecked: 0
},
{
idPriority: 4,
priority: "Low",
isChecked: 0
}
]
I want to display this data inside a table using ng-repeat and apply a custom filter such that if the priority of the object is High or critical, it should be displayed in red color and if the priority is medium or low, it should be displayed in yellow color. Is it even possible?
You can create different columns with different styles and change their visibility according to the value of priority
<tr ng-repeat=" item in items">
<td>
<span class="style for red" ng-show="item.priority=='Critical'">Critical</span>
<span class="style for yellow" ng-show="item.priority=='High'">High</span>
<span class="style for green" ng-show="item.priority=='Medium'">Medium</span>
</td>
</tr>
or if you want a custom filter, you can create a filter and set its class in it
$scope.filterPriority = function (item) {
if (item.priorty == 'High') {
item.class = 'class for high';
}
else if (item.priorty == 'Low') {
item.class = 'class for low';
}
else {
item.class = 'default class';
}
return true;
};
and in your html
<table>
<tr ng-repeat=" item in items | filter:filterPriority">
<td>
<span class="{{item.class}}"></span>
</td>
</tr>
You can achieve this using CSS. for example
<tr class="{{ item.priority }}"> some content</tr>
would add the appropriate classes. And somewhere in your stylesheet
.Critical { color: red; }
.High { color: blue; }
.Medium { color: green; }

angularjs ng-repeat with dynamic json/object

I am looking a solution for dynamic data structure(inconsistent like different property name and property length) with ng-repeat. sample code are below.
$scope.data = [{
"table":[{
"employee":"name1",
"value1":"10",
"value2":"20"
},
{
"employee":"name2",
"value1":"15",
"value2":"30"
}]
},
{
"table":[{
"company":"name1",
"compnayValue":"12"
},
{
"company":"name2",
"compnayValue":"12"
}]
}]
<ul>
<li ng-repeat="item in data">
<table>
<tr ng-repeat="row in item.table">
<td>{{??}}</td>
<td>{{??}}</td>
</tr>
</table>
</li>
</ul>
You could enumerate all properties and display their values by another ng-repeat on td:
<li ng-repeat="item in data">
<table>
<tr ng-repeat="row in item.table">
<td ng-repeat="(key, value) in row">
{{row[key]}}
</td>
</tr>
</table>
</li>
but that would break the tabular format of data since some rows would have more tds. To prevent that you could first find out the set of all keys on all rows, do a th repeat with those first and then display them on the corresponding td below, e.g.:
<th ng-repeat="propertyName in allPossiblePropertyNames">
{{propertyName}}
</th>
and
<td ng-repeat="propertyName in allPossiblePropertyNames">
{{row[propertyName ]}}
</td>
Use <tbody> to represent an object inside table array and (key,value) syntax mentioned in iterating over object properties section to iterate over it's properties like:
angular.module('test', []).controller('testCtrl', function($scope) {
$scope.data = [{
"table": [{
"employee": "name1",
"value1": "10",
"value2": "20"
}, {
"employee": "name2",
"value1": "15",
"value2": "30"
}]
}, {
"table": [{
"company": "name1",
"compnayValue": "12"
}, {
"company": "name2",
"compnayValue": "12"
}]
}]
});
ul {
padding: 0;
}
ul li {
list-style-type: none;
margin-bottom: 10px;
}
table {
width: 100%;
table-layout: fixed;
background: #ebebeb;
}
tbody:nth-child(odd) tr {
color: #fff;
background: dodgerblue;
}
tbody:nth-child(even) tr {
color: #fff;
background: hotpink;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="testCtrl">
<ul>
<li ng-repeat="item in data">
<table>
<tbody ng-repeat="row in item.table">
<tr ng-repeat="(key, value) in row">
<td>
{{key}}
</td>
<td>
{{value}}
</td>
</tr>
</tbody>
</table>
</li>
</ul>
</div>
Check this plunker, you can define template depends on your data :
https://plnkr.co/edit/fVGhKZy5gnBzuPwspy5s?p=preview
Use angular filter :
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.data = [{
"table":[{
"employee":"name1",
"value1":"10",
"value2":"20"
},
{
"employee":"name2",
"value1":"15",
"value2":"30"
}]
},
{
"table":[{
"company":"name1",
"compnayValue":"12"
},
{
"company":"name2",
"compnayValue":"12"
}]
}]
})
.filter('isCompnay', function() {
return function(input) {
console.log(input.employee === undefined)
return input.company ? input : undefined;
};
})
.filter('isEmployee', function() {
return function(input) {
console.log(input.employee === undefined)
return input.employee ? input : undefined;
};
});

Resources