React JS: Ensure Progratimacally created input fields are unique from each other - html

I'm creating a form to seed a varying number of teams to a tournament, by first mapping them to a form group with labels and number input. How can I make it so each field has to be unique before the form is considered valid?
By unique, I mean each field with take a certain number in a range, say if a tournament has 14 teams, then each field should be a number between 1 and 14, but two fields shouldn't be able to take the same number.
renderButton() {
return (
<Form onSubmit={this.handleSeedingSubmit}>
{this.state.teams.map((team)=>
<FormGroup key={team.name}>
<Form.Label >{team.name}</Form.Label>
<Form.Control type = "number" name={team.name} min={1} max={this.state.tournament.noTeams} onChange={this.onChangeHandler} required />
</FormGroup>
)}
<Button type="submit" >
Submit
</Button>
</Form>
);
}
On submit each team is being mapped with {name, seeding}. I want every team to have a unique seeding as they will be sorted into pools based on seeding later.

Well what you can do is make the number inputs controlled by storing their values in the store:
state = {
// other state,
inputs: {}
}
then in onChangeHandler set the value of each input in the state:
function onChangeHandler(e) {
const { name, value } = e.target;
this.setState({
inputs: {
...this.state.inputs,
[name]: value
}
})
}
then when your form is submitted you can add a check to see if the values are unique or not, there are many ways to do that, what I'm doing here is remove the duplicates from the array and then check the length of the array against the values in the state like this:
function handleSeedingSubmit(e) {
e.preventDefault();
const { inputs } = this.state;
const valuesInState = Object.values(input);
const uniqueValuesArr = [...new Set(valuesInState)];
const areInputsValid = valuesInState.length === uniqueValuesArr.length;
if (!areInputsValid) {
// set Error here
return;
}
// Hurray!! Inputs are valid
// Handle Success case here
}
Hope it helps :)

Related

Deleting a negative value from number input and validation

I feel that this is a dumb question, sorry.
I have an input with the number type and I want to add some validation to it. It is not required, so an empty string should be valid, negative numbers too, but not the - sign.
Consider now I've entered -102 into that field and removing symbols one by one, watching for changes.
Here's the basic codepen for it. As you can see, when there's just a - sign left the value (event.target.value) is an smpty string, which should be valid.
So, how can I check if there is only a minus sign left and mark this field as invalid?
const el = document.getElementById('input');
el.addEventListener('keyup', (e) => {
const value = e.target.value;
console.log(value)
})
<input type="number" id="input">
you can use pattern to only accept positive or negative number
in JS you can call method checkValidity() or use {yourinput}.validity.valid to check if value entered in input is valid or not
in css you can use the pseudo class :invalid to design your input when value entered don't match the pattern/field expectation
const el = document.getElementById('input');
el.addEventListener('keyup', (e) => {
console.log(el.checkValidity());
if (!el.checkValidity()) {
//treat case input invalid
}
})
input:invalid {
color: red;
}
<input type="number" id="input" pattern="\d+">

How can I automatically insert commas when a user inputs currency value in an Angular 7 reactive form, no [(ngModel)]

I have an input field where the user can input a numeric value. I need to automatically insert commas after every 3rd digit. When the user deletes numbers, the commas need to be in the correct places (after every 3rd digit, starting from the first number) as well as stay in place instead of relocating to the end of the input value. I cannot use ngModel, this is a reactive form.
I have tried this method in my TS file, to mask the user input
maskInputAmount(e) {
const t = e.target.value.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})
(\d{0,3})/);
e.target.value = t[2] ? t[1] + ',' + t[2] + (t[3] ? ',' + t[3] : '') :
t[1];
}
And in my HTML input field
<input (input)="maskInputAmount($event)" maxlength=11
formControlName="businessNetWorth" id="businessNetWorth"
type="text" class="form-control col-3 col-lg-12" data-hint="yes">
I am having commas come after every 3rd number. However, when deleting numbers from the end of the input, the commas at the front of the number should update correctly. For example, I enter '123,456,789'. When I delete the last two numbers I get '123,456,7' when it should be '1,234,567'.
One other issue, when a user deletes one of the first numbers, the comma in the input box automatically repositions itself to the end of the input value, I need it to stay in place. For example: '123,456,789'. I delete '3' and have '124,567,89' and the cursor is now behind the '9' when it should stay in front of the '2'.
How can I change my maskInputAmount(e) method to make this behave correctly?
Following code worked for me. (Assume present currency is in Indian rupees. If you want to have your own currency then you need to mention your country's code in code).
app.component.html
<input type="text" [formControl]="currency" (input)="changeToCurrency(currencyTextRef)" #currencyTextRef>
//sending reference of input element #currencyTextRef to function
{{ currency.value }}
app.component.ts
currency = new FormControl();
temp;
currncyLength=0;
changeToCurrency(currencyTextRef) {
this.currncyLength = this.currency.value.length;
console.log("currency len is "+this.currncyLength);
let index:number;
// if(currencyTextRef.selectionStart || currencyTextRef.selectionStart == '0') {
// console.log("index isss "+currencyTextRef.selectionStart);
index = currencyTextRef.selectionStart; //getting caret(cursor) position
// }
console.log("index is "+index);
// console.log("value is "+this.currency.value);
let a = this.currency.value;
a = a.replace(/,/g,'');
let num:number = + a;
let temp = new Intl.NumberFormat('en-IN').format(num); //inplace of en-IN you can mention your country's code
// console.log("temp is "+temp);
this.currency.setValue(temp.toString());
console.log("pressent len iss "+this.currency.value.length)
if(this.currncyLength<this.currency.value.length) {
console.log("incoming to < ")
index+=1;
currencyTextRef.setSelectionRange(index,index);
}
else if(this.currncyLength >=this.currency.value.length) {
console.log("incoming to > ");
// index-=1;
currencyTextRef.setSelectionRange(index,index);
}
// else {
// currencyTextRef.setSelectionRange(index,index);
// }
}
Following link might help.
Intl number MDN

How to get the previous value in (input) event in angular 4

I am trying to add the value and also need to remove the previous value by comparing with a new value.
var total = [];
onSearchChange(event) {
total.push(event);
var sumNumber = total.reduce(
(acc, cur) => acc + Number(cur),
0
);
console.log("get all the changed value, I need to remove the previous values in the total list");
}
<input type='number' (input)="onSearchChange($event.target.value)" />
I don't know if the event itself retains the previous value. You can create a component property to hold the previous value and set it in every input event.
<input id="inputId" type="number" value=23 (input)="foo($event.target.value)">
// have some default value only if you want
previousValue: number
foo(value) {
console.log("previous value ", this.previousValue);
console.log("new value ", value);
this.previousValue = value
}
ngAfterViewInit() {
this.previousValue = parseInt((<HTMLInputElement>document.getElementById('inputId')).value)
}
https://stackblitz.com/edit/angular-ufd15s?file=src%2Fapp%2Fapp.component.ts
You can also add a helper event listener keydown (which seems unnecessary but just saying) on the input element. keydown will occur before input so with keydown you can grab the previous value. https://stackblitz.com/edit/angular-sjxvgp?file=src%2Fapp%2Fapp.component.ts

searching json file for key angular 2+

On my website I would like to verify the input a user does when he puts in the 3 letter code or 2 letter code. It should return the value of the key.
So if he types in "BTC" it should return "bitcoin" and show it next to the input.
I found this example, but it seems quite complicated for what I want: https://github.com/ghiden/angucomplete-alt and it is angularjs
Search three lettercode coin: <input ng-model="query">
{
"42": "42 Coin",
"365": "365Coin",
"404": "404Coin",
"611": "SixEleven",
"808": "808"
}
The easiest way is to probably do this:
- Check form input on each type
oninput="checkInput($event)"
Then you can see if it matches any of the keys:
function outputData(value) {
alert(value);
}
function checkInput(event) {
// Check event.target.value with either if cases or a switch statement and
//then trigger the function that outputs the key value.
if (event.target.value === 'BTC') {
outputData('Bitcon');
} else if (event.target.value === '....') {
.....
}
}

How to filter or custom filter array of objects based on matching values from another object

I implemented an advance search with 15 input fields in AngularJS.
In the page load itself the result set is return from database in JSON format and i need to do the filter in client side only.
The input criteria's equivalent column is available in the result set and i need to check in its respective column only.
I am converting each column by JSON.stringify() and check with the search params like the below :
$scope.filteredData = $scope.actualData.filter(function(item) {
return JSON.stringify(item.FirstName).toLowerCase().indexOf(lowerFirstName) != -1 &&
JSON.stringify(item.LastName).toLowerCase().indexOf(lowerLastName) != -1 &&
JSON.stringify(item.EmailAddress).toLowerCase().indexOf(lowerEmailAddress) != -1 &&
JSON.stringify(item.Address1).toLowerCase().indexOf(lowerAddress1) != -1 &&
JSON.stringify(item.Address2).toLowerCase().indexOf(lowerAddress2) != -1;
...... etc // upto 15 fields
});
Since i have the 15 input fields and the actual result set contains a minimum of 50,000 records.
So converting each record's each column by JSON.stringify() and check with search params will surely cause the performance issue.
Is there any other way to achieve the filtering in client side with other approach.
I posted a sample code in Plunker with 5 input fields only : http://plnkr.co/edit/nUWZEbGvz7HG6gb91YZP
sylwester's answer is the normal way you'd filter things. Your code looks like you want to filter down to only the object that matches every input field. You code attempts to find an object where every property matches the searchParams object. At that point, I don't see what benefit there is to finding that object, because the user already created the object again! Nonetheless, here's a proper version of your code:
Live demo here.
<div ng-repeat="data in actualData | filter:searchData()">
$scope.searchData = function() {
return function(item) {
return Object.keys(item).every(function(key) {
// skip the $$hashKey property Angular adds to objects
if (key === '$$hashKey') { return true; }
var searchKey = key.charAt(0).toLowerCase()+key.slice(1);
return item[key].toLowerCase() === $scope.searchParams[searchKey].toLowerCase();
});
};
};
You really need to limit the data coming from the server for the browser's sake and for the server's sake. It's easy to implement a LIMIT, OFFSET system. It sounds like, overall, you just need to be able to query the server for a certain record.
From your comments, it seems you definitely want Angular's built in filter filter:searchParams, and just capitalize your searchParams models to match your data. For fun, I'll include more options for finer tuning.
This one almost mimics filter:searchParams. You can change > 1 to adjust when the partial matching kicks in, or have it return true only when both items are strictly equal === to disable partial matching. The difference here is that all items are hidden until matched, whereas filter:searchParams will show all items and then remove what doesn't match.
Live demo here.
$scope.searchData = function() {
return function(item) {
return Object.keys(item).some(function(key) {
if (key === '$$hashKey') { return false; }
var searchKey = key.charAt(0).toLowerCase()+key.slice(1);
var currentVal = $scope.searchParams[searchKey].toLowerCase();
var match = item[key].toLowerCase().match(currentVal);
return currentVal.length > 1 && match;
});
};
};
Lastly, to perfectly mimic filter:searchParams, you'd just put in a check to NOT filter the items until there is user input and the input is long enough to start the partial match.
Live demo here.
$scope.searchData = function() {
var partialMatchLength = 2;
return function(item) {
var shouldFilter = Object.keys($scope.searchParams).some(function(key) {
return $scope.searchParams[key] && $scope.searchParams[key].length >= partialMatchLength;
});
if (!shouldFilter) { return true; }
return Object.keys(item).some(function(key) {
if (key === '$$hashKey') { return false; }
var searchKey = key.charAt(0).toLowerCase()+key.slice(1);
var currentVal = $scope.searchParams[searchKey].toLowerCase();
var match = item[key].toLowerCase().match(currentVal);
return currentVal.length >= partialMatchLength && match;
});
};
};
First of all you ng-repeter with 50.000 records more likely is going to kill your browser, so you should thing about pagination.
Secondly you can easy filter your data using angular filter please see that demo
http://plnkr.co/edit/R8b8G4xCMSQmX1144UJG?p=preview
<div ng-controller="ListCtrl">
<br />
First Name:
<input type="text" id="txtFirstname" ng-model="searchParams.FirstName">
<br/>Last Name:
<input type="text" id="txtLastname" ng-model="searchParams.LastName">
<br/>Email Address:
<input type="text" id="txtEmailAddress" ng-model="searchParams.EmailAddress">
<br/>Address 1:
<input type="text" id="txtAddress1" ng-model="searchParams.Address1">
<br/>Address 2:
<input type="text" id="txtAddress2" ng-model="searchParams.Address2">
<br/>
<button class="btn btn-primary" ng-click="searchData()">Search</button>
<br />
<hr />
<b>Filtered Data(s):</b>
<div ng-repeat="data in actualData | filter:searchParams ">
<span ng-bind="data.FirstName"></span>
<span ng-bind="data.LastName"></span> |
Address : {{data.Address1}}
</div>
<hr />
</div>