Restrict double minus in mui number textfiled - reactjs

Currently, Number type inputs accepting double minus (i.e: --1). But I want only one - sign. If user press another minus then it should preventDefault().
Found a solution here. But it has some edge cases like, if user input '-123' and then go back and remove the '-' then user can't give the '-' again.

Since such values (-786-6, --712, ...) are allowed in TextField of type="number"
What I did is changing the textField proprety type from number to text, yes we'll accept strings but the user will be forced to enter a valid number.
Let's create a useState hook for the TextField input :
const [entredValue,setEntredValue] = useState('');
Then each time we verify if the entred input is a valid number before updating entredValue. this is a function made for that :
function restrict(test) {
if (test == "") return true;
if (test.length == 1) {
if (isNaN(test[0]) && test[0] !== "-") return false;
}
var found = 0;
for (let i = 0; i < test.length; i++) {
if (test[i] === ".") found++;
}
if (found > 1 || test == "-.") return false;
if (
test.length > 1 &&
isNaN(test[test.length - 1]) &&
test[test.length - 1] !== "."
)
return false;
let result = test.substr(0, test.length - 1);
if (test.length > 2 && isNaN(result)) return false;
return true;
}
Then update the state value this way :
<TextField
onChange={(event) => {
if (restrict(event.target.value)) {
setEntredValue(event.target.value);
}}}
/>
finally when you have to use entredValue:
parseFloat(entredValue); // always a valid number

Related

How can I adjust my formula to continuously autofill the missing paramater?

So i'm building a calculator/estimator that is basically just a more complicated version of this margin calculator: https://www.omnicalculator.com/finance/margin
So here's one edge case that I'm trying to fix right now.
My costs are broken into 3 parts due to outsourced data- labor, material and laborAndMaterial. LaborAndMaterial is the sum of labor and material, but it can be the only known cost factor so that's why it's broken into 3 parts.
So here's the problem. Say we know that laborAndMaterial is set to 100 and labor and material are 0
cost: {
labor: 0,
material: 0,
laborAndMaterial: 100
}
Then the user enters 50 for labor. Because laborAndMaterial is 100 and labor is now 50 we can autofill material to 50.
But what's happening right now as the user is typing "50" it autofills material to 95 as the user types the 5 in 50. Then when they enter the "0" it sets the laborAndMaterial to 145 (50 + 95). But in that example I need to adjust how I autofill material to continue to update as the user enters more numbers (labor = 5 -> 50 -> 506) (material = 95, 50, -406). As of now I basically have my formula run like:
if(key === "cogs.labor") {
if(laborAndMaterial > 0) {
params["cogs.material"] = laborAndMaterial - value // value is what was entered
}
}
But I still need to allow for the other edge cause that as labor is entered and material is known it updates the laborAndMaterial value
cost {
labor: 50,
material: 50,
laborAndMaterial: 100
}
So if someone enters 100 for labor and we know material is 50 we can autofill laborAndMaterial to 150.
So I have something like:
if(material > 0) {
params["cogs.laborAndMaterial"] = material + value // value is what was entered
}
Any ideas how I can tweak my formula to decide the autofill and continue to update that paramater while still allowing for multiple edge cases?
The margin calculator from omnicalculator is a good example as they solved the issue, but I've been scratching my head over it for some time.
I think you basically need to differentiate between which cost centers are treated as input and which are treated as output. So when you start, each piece of data you're provided is input, and the data you use to autofill the rest of the form is output.
As the user types, any information they give is then treated as input data. Given that any two values can be used to calculate the third, you can only have two of the fields be treated as input at a time.
Here's a code sample to get an idea of what I mean:
// This is a queue to hold your two input cost centers
const inputFields = [];
// Determine the odd one out that we need to calculate
function getVariableCostCenter() {
if (!inputFields.includes('labor')) {
return 'labor';
}
if (!inputFields.includes('material')) {
return 'material';
}
return 'laborAndMaterial';
}
function calculateCostCenters(costCenters) {
const variableCostCenter = getVariableCostCenter();
if (variableCostCenter === 'labor') {
return {
...costCenters,
labor: costCenters.laborAndMaterial - costCenters.material,
};
}
if (variableCostCenter === 'material') {
return {
...costCenters,
material: costCenters.laborAndMaterial - costCenters.labor,
};
}
return {
...costCenters,
laborAndMaterial: costCenters.labor + costCenters.material,
};
}
function initializeCostCenters(costCenters) {
// First, we determine which field(s) are known up front
if (costCenters.labor > 0) {
inputFields.push('labor');
}
if (costCenters.material > 0) {
inputFields.push('material');
}
if (costCenters.laborAndMaterial > 0 && inputFields.length < 2) {
inputFields.push('laborAndMaterial');
}
// ...then do whatever you normally do to populate the form
}
function updateCostCenters(previousCostCenters) {
// Update the input fields so that the user's input
// is always treated as one of the two input fields
if (!inputFields.includes(key)) {
inputFields.shift();
inputFields.push(field);
}
const costCenters = calculateCostCenters({
...previousCostCenters,
[key]: value,
});
params['cogs.labor'] = costCenters.labor;
params['cogs.material'] = costCenters.material;
params['cogs.laborAndMaterial'] = costCenters.laborAndMaterial;
}
Pretty roughly it might look like below.
Note that I remembering last touched fields, which are became "fixed", because we can not recalculate values circularly.
Also, note that I use direct value update, while in some frameworks/libs it might generate change/input event, so you would want to set values silently.
setup = {
labor: {
value: 0
},
material: {
value: 0
},
laborAndMaterial: {
value: 100
}
};
// the number which we are treat as "fixed", may be changed later
let prevFixed = 'labor';
let fixed = 'labor';
const calculateTheRest = () => {
if (!setup.material.touched && !setup.laborAndMaterial.touched ||
!setup.labor.touched && !setup.laborAndMaterial.touched ||
!setup.labor.touched && !setup.material.touched) {
return false; // two unknowns, can't recalculate
}
if (!setup.labor.touched || fixed !== 'labor' && prevFixed !== 'labor') {
setup.labor.value = setup.laborAndMaterial.value - setup.material.value;
} else if (!setup.material.touched || fixed !== 'material' && prevFixed !== 'material') {
setup.material.value = setup.laborAndMaterial.value - setup.labor.value;
} else {
setup.laborAndMaterial.value = setup.material.value + setup.labor.value;
}
}
const $els = {};
Object.keys(setup).forEach(key => $els[key] = document.querySelector('#' + key))
const onInputChanged = (e) => {
const key = e.target.id;
setup[key].value = +e.target.value;
setup[key].touched = true;
if (fixed !== key) {
prevFixed = fixed;
fixed = key;
}
calculateTheRest();
Object.keys(setup).forEach(key => $els[key].value = setup[key].value);
}
Object.keys(setup).forEach(key => {
$els[key].value = setup[key].value; // initial set
setup[key].touched = setup[key].value !== 0; // 0 on initial setup are the numbers that not set
$els[key].addEventListener('input', onInputChanged);
})
<p><label>labor: <input id="labor" type="number"/></label></p>
<p><label>material: <input id="material" type="number"/></label></p>
<p><label> laborAndMaterial: <input id="laborAndMaterial" type="number" /></label></p>
I think you need to implement a condition on your laborAndMaterial field.
Check the condition: -
if(labor > 0 && material > 0){
let laborAndMaterial = labor + material;
}
And after that set the laborAndMaterial variable value into field,
I think it will may help you.

Angular ng-repeat filtering

I have a deeply nested object. I have some records which contain 2 fields that show keys of object properties. I also have select needed to search records by property of object and input to search by key of object. So if I choose option1 and type in input some text, it will be shown the matches in the first field (not second!). And it's similar for second field.
How I try to realize:
I wrote a filter http://plnkr.co/edit/z9DEmfYz2grW9UonLcFK?p=preview
.filter('appFilter', function() {
return function(value, select, input) {
var result = [];
input = input.toLowerCase();
var reg = new RegExp(input,'g');
if (angular.isArray(value)) {
if (input === '' || $scope.isFiltering) {
return value;
} else if (select.value === 'Sequence') {
for (let i = 0; i < value.length; i++) {
if (value[i].Sequence.toLowerCase().match(reg)) {
result.push(value[i]);
}
}
return result;
} else if (select.value === 'ID') {
for (let i = 0; i < value.length; i++) {
if (angular.isArray(value[i].Document)) {
for (let j = 0; j < value[i].Document.length; j++) {
if (value[i].Document[j].ID.toLowerCase().match(reg)) {
result.push(value[i]);
}
}
}
}
return result;
} else {
console.log('error');
}
}
}
})
In controller I set to select's ng-model first option: $scope.selectParameter = $scope.parameter[0];
In debug I set to input parameter some value (123 for example).
So I searching record by first field that contains 123 value. And result finds and pushes the object. But in browser shows anything.
What's the problem? And I can't avoid the empty option with '?' value in my select :(
UPDATED
Nearly solve my problem: http://plnkr.co/edit/z9DEmfYz2grW9UonLcFK?p=preview
It filters by appropriate field and input value. But I faced with another troubles.
When input is empty it doesn't show any record. And second is when I choose second option (ID) filter duplicates some records.
Also I try to switch off filter without clearing the input text by clicking on checkbox.
It's what I want to do but it doesn't work:
else if (input === '' || $scope.isFiltering) {
return value;
}
$scope.isFiltering is ng-model for checkbox input
I tried using angulars default filter. I'm not sure if this is exactly what you want, but maybe it helps a little.
.filter('appFilter', function($filter) {
return function(value, select, input) {
if( !angular.isDefined(input) || input.length < 1) {
return value;
}
// Angulars "filter" lets you pass in a object-structure to search for nested fields.
var query =
(select.value === 'Sequence') ?
{Sequence:input} : {Document:{ID:input}};
return $filter('filter')(value, query);
}
})
http://plnkr.co/edit/Egkw9bUvTPgooc0u2w7C?p=preview

Calculate function after function execution finished - data result exists not return always correct result

That's not really the case of using promisses - because the result is a result of many service calls - in rder to fill an array of car.Make
<td ng-show="IsOK(obj)" class="text-center">
<img ng-show="GetStatus(obj)==''" src='#Url.Content("~/img/spinner.gif")' />
<span class="label label-success" ng-bind="GetStatus(obj)"></span>
</td>
IsValid = (car: Car): boolean => {
return (car.Title != null && car.Title != '' &&
car.Condition != null &&
car.StartDate < car.EndDate);
}
GetStatus = (car: Car): string => {
if (!this.IsValid(car)) {
return "Invalid";
}
if (car.Make == null)
{
return '';
}
for (var i = car.Make.length - 1; i >= 0; i--) {
if (car.Make[i].ColourCode == 'G') {
return car.Make[i].Name;
}
}
return '';
}
car.Make[i] is being calculated on another method and is showing the result of service call. That's why I have car.Make == null this should be true if the call hasn't happened.
When I have more calls to GetStatus() function some of them returns '' as result always even after some time when the whole array Make is being calculated.
When I have more calls to GetStatus() function some of them returns '' as result always even after some time when the whole array Make is being calculated
Based on the code you have provided this can only happen if car.Make == null (which I think you already know) OR no make has color code G (I think this is the insight you are looking for).

AngularJS number input formatted view

I want to use a formatted number input to show thousand seperator dots to user when he types big numbers. Here is the directive code that I used: http://jsfiddle.net/LCZfd/3/
When I use input type="text" it works, but when I want to use input type="number" it's weirdly cleaning by something when user typing big numbers.
What is problem about input[number]?
As written in the comments, input type="number" doesn't support anything but digits, a decimal separator (usually , or . depending on the locale) and - or e. You may still enter whatever you want, but the browser will discard any unknown / incorrect character.
This leaves you with 2 options:
Use type="text" and pattern validation like pattern="[0-9]+([\.,][0-9]+)*" to limit what the user may enter while automatically formatting the value as you do in your example.
Put an overlay on top of the input field that renders the numbers how you want and still allows the user to use the custom type="number" input controls, like demonstrated here.
The latter solution uses an additional <label> tag that contains the current value and is hidden via CSS when you focus the input field.
All these years later, there still isn't an HTML5 solution out of the box for this.
I am using <input type="tel"> or <input type="text"> ("tel" brings up a numeric keyboard in Android and iOS, which in some cases is a bonus.)
Then I needed a directive to:
filter out non-numeric characters
add thousand-separator commas as the user types
use $parsers and keyup to set elem.val() and $formatters to set the display...
...while behind the scenes, assign ng-model a floating point number
The directive example below does this, and it accepts negatives and floating point numbers unless you specify you want only positive or integers.
It's not the full solution I would like, but I think it bridges the gap.
HTML
<input type="text" ng-model="someNumber" number-input />
JAVASCRIPT
myApp.directive('numberInput', function($filter) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModelCtrl) {
ngModelCtrl.$formatters.push(function(modelValue) {
return setDisplayNumber(modelValue, true);
});
// it's best to change the displayed text using elem.val() rather than
// ngModelCtrl.$setViewValue because the latter will re-trigger the parser
// and not necessarily in the correct order with the changed value last.
// see http://radify.io/blog/understanding-ngmodelcontroller-by-example-part-1/
// for an explanation of how ngModelCtrl works.
ngModelCtrl.$parsers.push(function(viewValue) {
setDisplayNumber(viewValue);
return setModelNumber(viewValue);
});
// occasionally the parser chain doesn't run (when the user repeatedly
// types the same non-numeric character)
// for these cases, clean up again half a second later using "keyup"
// (the parser runs much sooner than keyup, so it's better UX to also do it within parser
// to give the feeling that the comma is added as they type)
elem.bind('keyup focus', function() {
setDisplayNumber(elem.val());
});
function setDisplayNumber(val, formatter) {
var valStr, displayValue;
if (typeof val === 'undefined') {
return 0;
}
valStr = val.toString();
displayValue = valStr.replace(/,/g, '').replace(/[A-Za-z]/g, '');
displayValue = parseFloat(displayValue);
displayValue = (!isNaN(displayValue)) ? displayValue.toString() : '';
// handle leading character -/0
if (valStr.length === 1 && valStr[0] === '-') {
displayValue = valStr[0];
} else if (valStr.length === 1 && valStr[0] === '0') {
displayValue = '';
} else {
displayValue = $filter('number')(displayValue);
}
// handle decimal
if (!attrs.integer) {
if (displayValue.indexOf('.') === -1) {
if (valStr.slice(-1) === '.') {
displayValue += '.';
} else if (valStr.slice(-2) === '.0') {
displayValue += '.0';
} else if (valStr.slice(-3) === '.00') {
displayValue += '.00';
}
} // handle last character 0 after decimal and another number
else {
if (valStr.slice(-1) === '0') {
displayValue += '0';
}
}
}
if (attrs.positive && displayValue[0] === '-') {
displayValue = displayValue.substring(1);
}
if (typeof formatter !== 'undefined') {
return (displayValue === '') ? 0 : displayValue;
} else {
elem.val((displayValue === '0') ? '' : displayValue);
}
}
function setModelNumber(val) {
var modelNum = val.toString().replace(/,/g, '').replace(/[A-Za-z]/g, '');
modelNum = parseFloat(modelNum);
modelNum = (!isNaN(modelNum)) ? modelNum : 0;
if (modelNum.toString().indexOf('.') !== -1) {
modelNum = Math.round((modelNum + 0.00001) * 100) / 100;
}
if (attrs.positive) {
modelNum = Math.abs(modelNum);
}
return modelNum;
}
}
};
});
https://jsfiddle.net/benlk/4dto9738/
You need to add the step attribute to your number input.
<input type="number" step="0.01" />
This will allow floating points.
http://jsfiddle.net/LCZfd/1/
Also, I'd recommend reviewing the bug thread on number inputs in Firefox. You may want to consider not using this input type, as it was just finally supported in this release of FF.
https://bugzilla.mozilla.org/show_bug.cgi?id=344616
http://caniuse.com/input-number
You cannot use values with , because type=number only takes numbers, adding a comma makes it a string.
See http://jsfiddle.net/LCZfd/5
You're better off making your own controls if you want commas. One with a true value (the number) and a display value (the string).
you can try this, I modified the directive I saw here...
How do I restrict an input to only accept numbers? ...
here's the modified directive I made... This directive uses the keyup event to modify the input on the fly...
.directive('numericOnly', function($filter) {
return {
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
element.bind('keyup', function (inputValue, e) {
var strinput = modelCtrl.$$rawModelValue;
//filter user input
var transformedInput = strinput ? strinput.replace(/[^,\d.-]/g,'') : null;
//remove trailing 0
if(transformedInput.charAt(0) <= '0'){
transformedInput = null;
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}else{
var decimalSplit = transformedInput.split(".")
var intPart = decimalSplit[0];
var decPart = decimalSplit[1];
//remove previously formated number
intPart = intPart.replace(/,/g, "");
//split whole number into array of 3 digits
if(intPart.length > 3){
var intDiv = Math.floor(intPart.length / 3);
var strfraction = [];
var i = intDiv,
j = 3;
while(intDiv > 0){
strfraction[intDiv] = intPart.slice(intPart.length-j,intPart.length - (j - 3));
j=j+3;
intDiv--;
}
var k = j-3;
if((intPart.length-k) > 0){
strfraction[0] = intPart.slice(0,intPart.length-k);
}
}
//join arrays
if(strfraction == undefined){ return;}
var currencyformat = strfraction.join(',');
//check for leading comma
if(currencyformat.charAt(0)==','){
currencyformat = currencyformat.slice(1);
}
if(decPart == undefined){
modelCtrl.$setViewValue(currencyformat);
modelCtrl.$render();
return;
}else{
currencyformat = currencyformat + "." + decPart.slice(0,2);
modelCtrl.$setViewValue(currencyformat);
modelCtrl.$render();
}
}
});
}
};
you use it like this ...
<input type="text" ng-model="amountallocated" id="amountallocated" numeric-only />

Extjs checkbox setValue based on 1 and 0

I have a checkbox on my form panel,
And I'm on the process where I need to edit a data based on id in MySQL database where there is a option for checkbox 1=checked 0=not checked,
how do I get the value of, for example, the field of checkbox and put it on a conditional clause and set the value checked to checkbox if it is 1 and not checked if it returns 0?...
Need to set to the checkbox config
inputValue: '1',
uncheckedValue: '0'
You can check the documentation at http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.form.field.Checkbox-cfg-uncheckedValue
All checks do not consider the event check if the value is a string. I created a fix. Add before Ext.onReady
Ext.override
(
Ext.form.Checkbox,
{
setValue:function(v){
var checked = this.checked, inputVal = this.inputValue;
if(v === false)
{
this.checked = false;
}
else
{
this.checked = (v === true || v === 'true' || v == '1' || (v instanceof String) || (inputVal ? v == inputVal : String(v).toLowerCase() == 'on'));
}
if(this.rendered)
{
this.el.dom.checked = this.checked;
this.el.dom.defaultChecked = this.checked;
}
if(checked != this.checked)
{
this.fireEvent('check', this, this.checked);
if(this.handler)
{
this.handler.call(this.scope || this, this, this.checked);
}
}
return this;
}
}
);
{
xtype:'checkbox',
fieldLabel:'Caption',
labelSeparator:':',
boxLabel:'LabelText',
name:'status',
inputValue:'0'
}
inputValue must be a String '0'.
this issue has been resolved,
by using 1 checkbox per field, there is no problem at all!....
the checkbox will get checked when the fieldname assigned to it returns a value of 1,
that's it!,
....

Resources