Extract data from JSX elements - javascript

Consider this example:
const items = data.get('$$items');
const row = items.map((item) =>
<tr key={item.get('id')} onClick={this.handleClick}>
<td>{item.get('id')}</td>
<td>{item.get('value1')}</td>
<td>
{
this.props.editing ?
<input value={item.get('value2')}> :
<span>item.get('value2')</span>
}
</td>
<td>{item.get('value3')}</td>
<td>{item.get('value5')}</td>
<td>{item.get('value5')}</td>
</tr>
);
return (
<section>
<table>
<thead>
...
</thead>
<tbody>
{ row }
</tbody>
</table>
</section>
Does React provide a way to conveniently (without interacting with the DOM) gather values of each td in a single object? Or even to iterate through each tr, processing data into objects. What I mean is like having a Backbone model, without actually having one.
Thank you for your attention.

There are two ways you could handle this.
onChange event handlers.
On your input field, include;
<input value={item.get('value2')} onChange={this.handleChange}>
And then elsewhere in your component, add a handleChange function:
handleChange: function(event) {
this.setState({value2: event.target.value});
}
Using your component's state to maintaint the current values of "item".
use Refs, and pull that data dynamically when requested.
If you don't want to store the information in your state, you can instead expand your input as such:
<input value={item.get('value2')} ref="value2_input">
After doing so, you'll be able to access this input field from any function on the component through;
this.refs.value2_input.value

Related

Unable to submit two forms with a single button without using JQuery

I want to submit two forms from a single button in next.js without using document.getElementById.
I am using two form tags and fetching their data in two different objects and I want them to be submitted at once with a single button.
I've tried doing this by document.getElementById.Submit() but it throws an error that document is not defined.
Similar question has been asked on SO but the solution offered is by using JQuery which I can't use.
const submitForms = function () {
useEffect(()=>
{
document.getElementById("form1").submit();
document.getElementById("form2").submit();
})
};
Please tell me where am I going wrong?
The basic principle is that you store your form data in state, update the state when the form information changes, and then when the button is clicked, submit the state data to the server endpoint that processes that data.
In this working example we use an object as the state, and assign each form an id (see data attributes). We attach listeners to the forms so that when any of the inputs change we can capture their events as they bubble up the DOM (event delegation).
In the handleChange function we take the id from dataset of the form (currentTarget), and the name and value from the element that was changed (target) - more on the key differences here - and then update the state using that information.
handleClick then stringifies the state, at which point you can submit it to the server endpoint.
It may benefit you to revisit how React works. It has its own way of updating the DOM which means you shouldn't be using any native DOM methods.
const { useEffect, useState } = React;
function Example() {
// Set the state
const [ forms, setForms ] = useState({});
function handleChange(e) {
// Destructure the id from the dataset of the form
const { dataset: { id } } = e.currentTarget;
// Destructure the name and value from the
// element that changed
const { name, value } = e.target;
// Update the state. We preserve the existing
// state data using the spread syntax, and set the new
// value of the property that has the id as key.
// That new value is an object representing the old
// value data (again using the spread syntax), and
// updating the property using the element name and value.
setForms({
...forms,
[id]: { ...forms[id], [name]: value }
});
}
function handleClick() {
// Stringify the data. You can now use
// `fetch` or something like Axios to send the
// data to the server
console.log(JSON.stringify(forms));
}
return (
<div>
<form data-id="formone" onChange={handleChange}>
<fieldset>
<legend>Form 1</legend>
Name: <input name="name" />
<br/>
Age: <input name="age" />
</fieldset>
</form>
<form data-id="formtwo" onChange={handleChange}>
<fieldset>
<legend>Form 2</legend>
Role: <input name="role" />
</fieldset>
</form>
<button
type="button"
onClick={handleClick}
>Submit all form data
</button>
</div>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
form, button { margin-top: 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Additional documentation
Spread syntax
Destructuring assignment
you can use a function and when form submitted, use in first line of your function:
e.preventDefault()
And when click on another button, call this function and when form is submitted also run this function
Don't use DOM function when work with ReactJs or NextJs

angular *ngFor not re-render html table with input

I am stuck with a very weird issue with angular. I have created one simple array in my ts file and I am displaying a table with input by iterating the same array. Now when I change the array (reverse it) it's working fine but if I have entered something in input before changing it, text will stay in input.
here is the code
component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'practice';
myArray = [1,2,3,4,5,6,7];
change() {
// this.myArray = [];
this.myArray.reverse();
}
}
my HTML:
<html>
<table>
<tr *ngFor="let item of myArray; let i = index">
<td>
<input type="text" [value]='item'>
</td>
</tr>
</table>
<button (click)="change()">Change</button>
</html>
Sample video for issue explanation:
https://www.loom.com/share/6f4887183bb94150ad7390f25e5b466a
So as you can see, when I enter something in the input and change the array the value stays with array. I have checked the original array is not changing.
I mean it just got reversed, but nothing else.
What is the issue?
You should use trackby:
<table>
<tr *ngFor="let item of myArray; let i = index ;trackBy: trackItem" >
<td>
<input type="text" [value]='item' >
</td>
</tr>
</table>
<button (click)="change()">Change</button>
in ts:
trackItem (index, item) {
return this.myArray ? this.myArray : undefined;
}
and if you want to keep that value you should bind it using ngModel:
<table>
<tr *ngFor="let item of myArray; let i = index ;trackBy: trackItem" >
<td>
<input type="text" [value]='item' [(ngModel)]="myArray[i]" >
</td>
</tr>
</table>
<button (click)="change()">Change</button>
check this Demo
Why using trackby:
By default, when you use *ngFor without trackBy, *ngFor tracks array of objects changing through object identity. So, if new reference of array of objects is passed to the directive, even if the array is with the same values, Angular will not be able to detect that they are already drawn and presented in the current DOM. Instead, old elements will be removed and a new collection with the same values will be redrawn.
We can help Angular to track which items added or removed by providing a trackBy function. The trackBy function takes the index and the current item as arguments and needs to return the unique identifier for this item. Now when you change the collection, Angular can track which items have been added or removed according to the unique identifier and create or destroy only the items that changed.
Use trackBy when:
1 - Iterating over large array of objects collection
2 - Your business logic might need to modify any of these elements through reordering, modifying specific item, deleting item, or adding a new one

Filters per column in a javascript table (Angular) combined results

I have this ngx-datatable for Angular that doesn't support filter by column. I would like to add an input filter for every column (some are strings, some are multiple choices etc) and combine them to a single filter so I can use it to get data with rxJs from the backend.
What I have for now:
This is the filter component on every column header:
<div class="input-group mb">
<div class="input-group-addon">
<span class="input-group-text" id="search-addon"><em class="icon-magnifier"></em></span>
</div>
<input aria-describedby="search-addon" id="order_id" aria-label="Customer" placeholder="Search" class="form-control" type="text" (keyup)='updateFilter($event)'>
</div>
The update filter function
updateFilter(event) {
let columnName = event.currentTarget.id;
const val = event.target.value.toString().toLowerCase();
const filteredData = this.temp.filter(function(d) {
return d[columnName].toString().toLowerCase().indexOf(val) !== -1 || !val;
});
this.rows= filteredData;
this.table.offset = 0;
}
This works for every column. But how can I combine all the filters and start observing the API response?
Your updateFilter() methods needs to values of all filter inputs, not only the one passed in via $event.
One way of doing can be to create an object filters in your component and two-way bind it's properties to your search inputs in the column headers. Listen to the ngModelChange event and trigger the actual filtering.
class MyComp {
// Other stuff
filters = {};
filter = () => {
// Do the filtering, all filters are set in this.filter object
}
}
In your HTML template bind it and listen to the ngModelChange event to trigger the filtering whenever the value changes (better than using keyUp, as it also triggers when the content changes without a key being pressed, e.g. copy-pasting via context menu).
<input id="order_id" [(ngModel)]="filters.order_id" (ngModelChange)="filter()" ... />

Angular 1: check all check boxes in a large ng-repeat

I have an table created using ng-repeat and there hundreds of rows, up to 600 or 700. Each row includes a checkbox and I have a "Check All" box at the top to check all the boxes in one go. However I'm running into browser performance issues, IE11 (the clients preferred choice) in particular becomes unresponsive. After several minutes all the checkboxes appear checked but you still can't scroll or do anything so it is effectively useless.
I have created a controller array and when the checkAll box is clicked it loops through the model (the one used in ng-repeat) and adds a value to the array. I presume it's this looping through the array that is causing the slow-down but I'm not sure. Pagination has been ruled out, they want all the rows on one page.
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-checked="vm.approvedPayments.indexOf(payment.payId) > - 1"
ng-value="payment.payId" ng-model="payment.approved"
ng-click="vm.handleCheckBoxClick(payment.payId)" />
</td>
</tr>
</tbody>
</table>
Here is the angular function that checks/unchecks all
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = [];
} else {
vm.payments.forEach(function(payment){
vm.approvedPayments.push(payment.payId);
});
}
};
Swapping out the angular vm.tickOrUntickAllCheckBoxes() function for a plain old javascript option makes the checkAll box work almost instantaneously in IE11 however I lose access to the checked payment.payId values. I wonder is there away for angular to get them? Here is the plain javascript checkAll() function:
<script>
function checkAll(x) {
var checkBoxes = document.getElementsByClassName('paymentsApprovalCheckbox');
for (var i = 0; i < checkBoxes.length ; i++) {
checkBoxes[i].checked = (x.checked == true);
}
}
</script>
Then I update the checkAll checkbox like this:
<input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" onclick="checkAll(this)" />
If you check one checkbox individually then the ng-model="payment.approved" in the repeating checkboxes is updated but this does not happen if they are checked with the checkAll function. Is it possible for angular to detect the boxes checked with checkAll()? I guess this is just putting off the same old inevitable slow-down to a slightly later point in the process.
Anyone have any ideas or work-arounds? Thanks!
I would use the ng-model to the best of its abilities. In your controller:
$onInit() {
// If you need this from a REST call to populate, you'll have to
// remember to do that here;
this.model = {
all: true,
items: {}
};
}
In your loop:
<tr>
<th>Table Header</th>
<th>
<input type="checkbox"
id="checkAllCheckBox"
ng-model="vm.model.all"
ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments track by $index">
<td ng-bind="payment.somePaymentValue"></td>
<td>
<input type="checkbox"
class="paymentsApprovalCheckbox"
ng-change="vm.approvedPayments($index)"
ng-model="vm.model.items[$index]" />
</td>
</tr>
Then in your controller:
tickOrUntickAllCheckBoxes() {
const boxes = this.model.items.length;
this.model.all = !this.model.all;
// Several ways to do this, forEach, map, etc.,
this.model.items.forEach((item) => { item.checked = !this.model.all });
}
And for setting it individually:
approvedPayments(idx) {
// Sets all the item's boxes checked, or unchecked;
this.model.items[idx].checked = !this.model.items[idx].checked;
// Possible call to extended model to get payment info;
handleCheckBoxClick(idx);
}
You should be able to put all the payment information into the one approvedPayments() method rather than have two separate methods (move logic out of template and into the controller or a service). I.e., your model could look like:
this.model.items = [
// One 'option' with id, payment etc;
{
id: 73,
paymentId: 73,
somePaymentValue: 210.73,
currencyType: 'GBP',
checked: false
},
{
// Another 'option' etc...
}
]
One issue to note is the incompatibility of ngChecked with ngModel, had to look it up (which is why I haven't used ng-checked in the above).
Thank to everyone for the suggestions. The solution I came up with was to push some of the work back to the server side. Instead of just loading the payments model (in which each payment record contains a lot of info) i am now loading two additional models when the page loads, one of which is a set of key/value pairs where the keys are payId and the values are all false and another one with the same keys and all values are true. Example:
{
"1": false,
"2": false
}
These are used for the checkAll/Uncheck all - just set the vm.approvedIDs variable to the true or false one. Then, the vm.approvedIDs variable is used as the model in the ng-repeat checkbox.
I have to do a bit of extra work on the server side when the user sends the approvedIDs back to the server to get only the key/id of the 'true' entries. Here are the relevant angular controller functions:
$onInit() {
// call http to get 'data' from server
vm.payments = data.payments;
vm.paymentIDsFalse = vm.approvedIDs = data.paymentIDsFalse;
vm.paymentIDsTrue = data.paymentIDsTrue;
};
// tick/untick all boxes
vm.tickOrUntickAllCheckBoxes = function(){
if (vm.allChecked == false) {
vm.approvedPayments = vm.paymentIDsFalse;
} else {
vm.approvedPayments = vm.paymentIDsTrue;
}
};
// tick/untick one box
vm.handleCheckBoxClick = function(payId, currentValue){
vm.approvedPayments[payId] = currentValue;
};
vm.submitApprovedIds = function(){
// post vm.approvedPayments to server
};
HTML:
<table>
<tbody>
<tr>
<th>Table Header</th>
<th><input type="checkbox" id="checkAllCheckBox" ng-model="vm.allChecked" ng-change="vm.tickOrUntickAllCheckBoxes()" />
</tr>
<tr ng-repeat="payment in vm.payments>
<td>{{ payment.somePaymentValue }}</td>
<td>
<input type="checkbox" class="paymentsApprovalCheckbox"
ng-value="payment.payId"
ng-model="vm.approvedPayments[payment.payId]"
ng-click="vm.handleCheckBoxClick(payment.payId, vm.approvedPayments[payment.payId])" />
</td>
</tr>
</tbody>
</table>
It looks to me as if there must be a better way than creating these additional models but it is working pretty smoothly for now and I can move on to the next thing!

Making a pre-populated form a HTML table in jquery

I am trying to create a prepopulated form that pulls values from any selected row in a HTML table . The HTML page is populated by a JSP .
my table looks like this
<table id="data-table" id="test">
<thead>
<tr>
<th>value1</th>
<th>value2</th>
<th>value3</th>
<th>value4</th>
</tr>
</thead>
<tbody>
<tr>
<td id="class1"><%= value.valueOne() %></td>
<td id="class2"><%= value.valueTwo() %></td>
<td id="class3"><%= value.valueThree() %></td>
<td id="class4"><%= value.valueFour() %></td>
</tr>
<%
}
%>
</tbody>
</table>
I want to obtain a prepopulated form with the row values on click of a particular row . I have some js code that does this .
$(document).on("click", ".data-table .class1", function(e) {
var value = $(this).closest('tr').val();
console.log(value);
// Just to check if I get the correct value
});
unfortunately I cannot understand how to get the values for that particular row from the DOM and populate it in a form , That I want to overlay over the table . I would appreciate any pointers . I really would have written more code but I dnt know Jquery and am stuck
Your general strategy should be this:
Populate the table on the server side: done
Have the form pre-existing in the page, but hidden with css (display:none)
Register a click listener on all tr elements to:
find the values inside each td within the tr
select the corresponding form inputs
populate the inputs using jQuery's val(value) function.
unhide the form if it's hidden
With this in mind, I would change your click listener from document to something like this. (Note: I'm assuming value.valueOne() are just numbers or strings, and don't contain any html.
//target just TR elements
$('tr').click(function(){
values = [];
$(this).children().each(function(){
//add contents to the value array.
values.push($(this).html())
});
//fill in the form values
populateForm(values);
});
Populate form would completely depend on your form's HTML, but to get you started here's an idea of what it might look like:
function populateForm(values){
//set the value of the input with id of name to the value in the first td.
$('#name').val(values[0]);
//show the form (id inputForm) now that it's populated
$('#inputForm').show();
}
A couple things are wrong with your html markup and your JQuery selector. You'll never be able to execute the code you've provided...
You have two 'id' parameters in this element, <table id="data-table" id="test">... This will work with the JQuery I've fixed below, but it's malformed html either way.
In your selector, you are using the syntax for finding an element based on it's css class attribute, however your elements in your HTML have those values set as 'id' attributes. Thus, this, $(document).on("click", ".data-table .class1", function(e) {... should be written as follows, $(document).on("click", "#data-table #class1", function(e) {
Now, if you are attempting to get the values within all of the 'td' elements within a row, then all you really need to do is get the parent element of the 'td' that was clicked, and then get it's children. Then, for each child, get their values.
Like this...
$(document).on("click", "#data-table #class1", function(e) {
var elements = $(this).parent().children();
$.each(elements, function(index, el){
alert($(el).html());
});
});
I've saved a JSFiddle for you to see this in action... http://jsfiddle.net/2LjQM/
val() is used to return value of form inputs. You are using it to try to get the value of a row and row has no value.
Without seeing what your output into the TD as html, I assume it is a form control
Try
$(document).on("click", ".data-table .class1", function(e) {
var value = $(this).find(':input').val(); // `:input pseudo selector wull access any form control input,select,textarea
console.log(value);
// Just to check if I get the correct value
});
EDIT: if the TD contains text
var value = $(this).text();
Instead of scraping the DOM, you could invert the logic, and build the rows using javascript instead. Check out this jsBin to see the solution in action: http://jsbin.com/aligaZi/2/edit?js,output
Start with an empty table:
<table class="data-table" id="test">
<thead>
<tr>
<th>value1</th>
<th>value2</th>
<th>value3</th>
<th>value4</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
Since you need to fill a form with the data, I'll be using a simple one as an example:
<form class="data-form">
<label>Value1<input class="value1" /></label>
<label>Value2<input class="value2" /></label>
<label>Value3<input class="value3" /></label>
<label>Value4<input class="value4" /></label>
</form>
Then, on the javascript side:
$(function() {
// Interpolate the values somehow.
// I'm not familiar with JSP syntax, but it shouldn't be too hard.
// I will use dummy data instead.
var tableData = [
{
value1: "row1-v1",
value2: "row1-v2",
value3: "row1-v3",
value4: "row1-v4"
}, {
value1: "row2-v1",
value2: "row2-v2",
value3: "row2-v3",
value4: "row2-v4"
}
];
// For each object, create an HTML row
var rows = $.map(tableData, function(rowData) {
var row = $("<tr></tr>");
row.append($('<td class="class1"></td>').html(rowData.value1));
row.append($('<td class="class2"></td>').html(rowData.value2));
row.append($('<td class="class3"></td>').html(rowData.value3));
row.append($('<td class="class4"></td>').html(rowData.value4));
// When this row is clicked, the form must be filled with this object's data
row.on("click", function() {
fillForm(rowData);
});
return row;
});
$(".data-table").append(rows);
function fillForm(rowData) {
var form = $(".data-form");
form.find("input.value1").val(rowData.value1);
form.find("input.value2").val(rowData.value2);
form.find("input.value3").val(rowData.value3);
form.find("input.value4").val(rowData.value4);
}
});

Categories