Vue 2 dynamic input validation and character limitting - javascript

I'm trying to figure out a way that I can validate the user inputs on this input that is dynamically rendered roughly 30 separate times,
<form>
<div class="pseudo_table">
<div class="table_div" v-for="(item, index) in inputArray" v-if="index > 0 && index <= 30">
<p class="header thead">Year {{ index }}</p>
<input class="interest_input" type="number" maxlength="6" v-model="inputArray[index]" />
<label for="table_input" class="static_value">%</label>
</div>
</div>
</form>
Below you can find a picture of the code thats pasted in the side scroll above. With the added buttons that are mentioned later in the story.
As you can see I am rendering with v-for and have set a maxlength of 6 but since this a number type input the maxlength does not work.
I added a watch option but this actually doesn't work in real time and isn't doing anything to limit character length in the input. Further I would like to limit the range at which an input number can reach. If the number being entered is x.xxx greater than the old value it should show an error as well as disable the save & continue button.
watch: {
inputArray(newVal, oldVal) {
if (newVal - oldVal > this.settingsAssumptions.indexAverageAnnualIncrease) {
}
if (oldVal - newVal > this.settingsAssumptions.indexAverageAnnualDecrease) {
}
if (newVal.toString().length > 6) {
parseFloat(newVal.slice(0, 6)).toFixed(3)
}
}
}
disabling the save and continue button is likely one of the easier things I can implement but I can't wrap my head around the watch functionality in the docs. Does anyone have any insight?
I've tried a suggestion made on another SO post that recommended using the oninput to validate the length and limit it in real time but unfortunately due to my inputs being text-align:right, when typing in the input field and adding a decimal, it will shift to the left of the number.

Related

Go to next input field at maxLength?

I have a form in Vue that has some combined inputs for styling reasons for a phone number input.
My problem is that the user has to hit tab in order to go to the next input field of the phone number.
Is there a way to check for the max length of the current and input and when that is met go to the next input?
<div class="combined-input combined-input--phone" :class="{'error--input': phoneInvalid('patientInformation')}">
<div class="open-parenthesis"></div>
<input type="text" id="phoneArea" maxlength="3" #blur="$v.formData.patientInformation.phone.$touch()" v-model.trim="$v.formData.patientInformation.phone.area.$model">
<div class="close-parenthesis"></div>
<input type="text" id="phoneA" maxlength="3" #blur="$v.formData.patientInformation.phone.$touch()" v-model.trim="$v.formData.patientInformation.phone.a.$model">
<div class="dash"></div>
<input type="text" id="phoneB" maxlength="4" #blur="$v.formData.patientInformation.phone.$touch()" v-model.trim="$v.formData.patientInformation.phone.b.$model">
</div>
Pretty sure this is not recommended UX-wise but here is an example on how you can achieve it: https://codesandbox.io/s/move-to-next-input-on-condition-103b5?file=/src/App.vue
The main part of useful code is
focusNextOncePopulated(event, max) {
if (event.target.value.length === max) {
const nextElement = this.$refs?.[`input-${Number(event.target.dataset.index) +1}`]
if (nextElement) nextElement.focus()
}
},
Explanation: each time you input a char, you check if the length of the field reached the maximum you've set on a particular input. If it is and there is a similar input as a sibling, you focus it.
Adding a debounce of a few milliseconds would be nice too, performance-wise.
EDIT: to prevent any issues of trying to find the correct next input in the DOM, we setup some refs on each of the inputs (recommended way in Vue to select some DOM elements). Then, when we have reached the max, we increment our current's input data-index by one, which enables us to go to the next input.
PS: ?. syntax is optional chaining, it prevents ugly code and allows to avoid an error if there is no next input once max is reached.
PS: Not sure if there is a way to directly get the $refs of an element on it's #input event but if there is, I didn't found out how to do it.

How to make type=“number” to positive numbers only using ember js

It was only preventing negative numbers from being entered from up/down arrows, whereas user can type negative number from keyboard.
<div class="col-sm-2">
<label>Quantity</label><br>
{{input type="number" value=quantity min="0" class="form-control-
invoice"}}
</div>
This is a good example for data down, actions up (DDAU) principle. The data should flow down to your component (or element) but only by updated by your application if it matches certain criteria. Sadly the {{input}} helper does not support DDAU principle. But you could easily accomplish your goal without using it:
<input type="number" value={{quantity}} onchange={{action "updateQuantity" value="target.value"}} min="0" >
We are using a standard <input> element and bind it's value property to quantity. We bind a custom action to the change event. In that action we could check if the updated value meets our criteria and if so update quantity. The action may look like the following:
actions: {
updateQuantity(val) {
val = parseInt(val);
if (val >= 0) {
this.set('quantity', val);
} else {
window.alert('number must not be negative');
this.notifyPropertyChange('quantity');
}
}
}
Please note that we have to call notifyPropertyChange() if the new value does not meet our criteria. This will trigger an update of UI to reset the value of the <input> element.
I've written an Ember Twiddle demonstrating the approach: https://ember-twiddle.com/bcf03934667364252e52b21930d664fd?openFiles=templates.application.hbs%2C

ng-disabled on inputs from ng-repeat loop

I'm displaying inputs basing on array like this
<div data-ng-repeat="n in langInput.values">
<input type="text"
id="auction_name_{{n.selected}}"
class="form-control"
name="auction_name_{{$index}}"
data-ng-model="inputs.auction_name[$index + 1]"
data-ng-minlength="5"
data-ng-maxlength="60"
required />
<span data-ng-show="sellItem['auction_name_'+$index].$error.required">Wymagane!</span>
It also give's me ability of angularjs validation. Next after <form> is closed I want to create "next" button but I also want to do validation there so if user don't fullfill required inputs he will not be able to click it.
Array which I'm ng-repeating on is:
$scope.langInput = {
count: 3,
values: [
{
id: "1",
selected: "pl"
},
{
id: "2",
selected: "eng"
}
],
add: function () {
if (this.count < 7) {
this.values.push({id: this.count, selected: "eng"});
this.count += 1;
console.log(this.values);
}
},
remove: function () {
if (this.count > 2) {
this.values.pop();
this.count -= 1;
console.log(this.count);
}
}
};
I know I can use this ng-disabled directive however I don't know how I can check this inputs which are displayed in loop because its name is changing depending on $index of loop.
I've created plunker
My situation is that I know that I can disable button when some of element is invalid by ng-disabled="sellItem.$error" but in my form in real project I have this form much bigger and I have many ways of acomplishing form so in the end when user finish fullfilling form user still got some of inputs which are not even shown invalid.
So I can't use ng-disabled="sellItem.$error" because after user complete form he still got invalid inputs in background.
I also can not split form to many little forms because it will call 1 endpoint on submit.
What I did in real project is inject 3 different buttons and show them on correct step. Every of this button need to have ng-disabled to not let user to go to next step without completing step' inputs.
So intead of ng-disabled="sellItem.$error" I need to specify all inputs in ng-disabled of one step ( which is about 5 inputs ).
So it would look something like this:
ng-disabled="sellItem.first_input.$error &&
sellItem.second_input.$error && ..."
And I would do this but then I come to problem that I can't "loop" inside of ng-disabled and I want to "loop" inside it because names of inputs are generated by JS
name="auction_name_{{n.id}}"
they and not constant they change, user can add more inputs and delete them
at page start I have two inputs which after JS run are name="auction_name_1" and name="auction_name_2" (due to binding interpolated value) and then user can and third one name="auction_name_3"so I can't also hardcode them within ng-disabled.
I don't know how I can check this inputs which are displayed in loop because its name is changing depending on $index of loop.
Generally one stores the input as a property of the object in the array so that it stays with the object as its position in the array changes.
Also use the id property of the object:
<form name="sellItem" ng-submit="submit()">
<div data-ng-repeat="n in langInput.values">
<input type="text"
id="auction_name_{{n.selected}}"
class="form-control"
̶n̶a̶m̶e̶=̶"̶a̶u̶c̶t̶i̶o̶n̶_̶n̶a̶m̶e̶_̶{̶{̶$̶i̶n̶d̶e̶x̶}̶}̶"̶
name="auction_name_{{n.id}}"
̶d̶a̶t̶a̶-̶n̶g̶-̶m̶o̶d̶e̶l̶=̶"̶i̶n̶p̶u̶t̶s̶.̶a̶u̶c̶t̶i̶o̶n̶_̶n̶a̶m̶e̶[̶$̶i̶n̶d̶e̶x̶ ̶+̶ ̶1̶]̶"̶
data-ng-model="n.input"
data-ng-minlength="5"
data-ng-maxlength="60"
required />
<span data-ng-show="sellItem['auction_name_'+n.id].$error.required">Wymagane!</span>
<span data-ng-show="sellItem['auction_name_'+n.id].$error.minlength">Za krótkie!</span>
<span data-ng-show="sellItem['auction_name_'+n.id].$error.maxlength">Za długie!</span>
</div>
<button type="submit" ng-disabled="sellItem.$error">
{{Submit}}
</button>
</form>
Be sure to generate unique values for the id property.
Update
Added Submit button.
For more information, see
AngularJS Developer Guide - forms
AngularJS <form> Directive API Reference
AngularJS ng-submit Directive API Reference

ng-model misbehaving with HTML reset method

Im trying to be as ellaborative as i can with my question....
Scenario:
I have three input fields in my html page Two of them are to accept user inputted values and the third one binds(adds) these two values.
Done so far:
I initially used <input value="{{value1+value2}}" id="value3"/> which took the values as string; solved this issue by substracting the string by 0. But, this calculated values wont go off even using the reset button.
Then someone here on SOF told me to use <input ng-model="(value1-0)+(value2-0)" id="value3"/> which works, but i noticed that even though the values disapper visually the model still holds some value.
(When, i enter some value into the first input field, the third calculated field add the value of the inputted field with the previous value of the second input field(value that the second field had previous to the reset)
NOTE:
Reset method resets the values of the first two user inputted fields, but not that of the third calcualtion field while using <input value="{{value1+value2}}" id="value3"/> OR <input ng-bind="value1+value2" id="value3"/>
While, when using <input ng-model="(value1-0)+(value2-0)" id="value3"/> the calculated field is visually cleared but when i enter some value into one of the user inputted fields(value1 or value2) the calculated field adds the entered number with the previous number that the field ccontained.
I tried many ways to solve this issue, but with no suuccess.... can someone please guide me through?
Thanks in advance.....
Here's a simple fiddle . Follow the link and take a look.
Basically, to have only number values in the user inputed fields, I used HTML5 number inputs, readily available in any newer browser.
<div ng-controller="MyCtrl">
<input type="number" ng-model="value1" />
<input type="number" ng-model="value2" />
<input type="text" ng-model="value1 + value2" />
<button type="button" ng-click="reset()">RESET</button>
</div>
And as for the javascript, here is my controller:
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', MyCtrl)
function MyCtrl($scope) {
$scope.value1 = '';
$scope.value2 = '';
$scope.value3 = '';
$scope.reset = function() {
$scope.value1 = '';
$scope.value2 = '';
$scope.value3 = '';
};
}
The three values are first initialized as empty strings, and on ng-click of the RESET button, they are nullified again.
NOTE: For the sake of simplicity I used number inputs instead of trying to implement some kind of javascript validation which I would suggest for production level. The point of my answer was just to explain the principle using the most basic concepts.

Force iOS numeric keyboard with custom / currency pattern

Is there a possiblity to force an iOS-device to show the numeric keyboard while using a custom pattern as input type?
my input pattern:
<input id="price" class="numeric" pattern="\d+((\.|,)\d{1,2})?" name="price"
title="" data-mini="true" data-clear-btn="true" autocomplete="off" autofocus />
I want to type a currency value like '14.99' and show up a keyboard with access to numbers on the iOS device
<input type='number' />
<input pattern='[0-9]*' />
<input pattern='[\d]*' />
are all missing the decimal sign and/or are not validating as number when adding a decimal sign. An alternative way could be a javascript function which is creating the decimal sign on the right place, like pressing 1->2->9->9 in this order creates on keypress() 0.01->0.12->1.29->12.99,
but this requires the input field to be type='text' --> obvious problem here is that the text keyboard is showed when focussing the input field.
How can I solve this issue?
EDIT
Environment:
JQM 1.3.2
jquery 1.8.2
For now, JavaScript is the only solution. Here's the simplest way to do it (using jQuery):
HTML
<input type="text">
JavaScript
$('input[type="text"]').on('touchstart', function() {
$(this).attr('type', 'number');
});
$('input[type="text"]').on('keydown blur', function() {
$(this).attr('type', 'text');
});
The idea is simple. The input starts off and ends up with type="text", but it briefly becomes type="number" on the touchstart event. This causes the correct iOS keyboard to appear. As soon as the user begins to enter any input or leave the field, the input becomes type="text" once again, thus circumventing the validation.
There's one downside to this method. When the user returns to an input that has already been filled out, the input will be lost (if it doesn't validate). This means the user won't be able to go back and edit previous fields. In my case, this isn't all that bad because the user may want to use the calculator over and over again with different values, so automatically deleting the input will save them a few steps. However, this may not be ideal in all cases.
It looks like Mobile Safari supports the new HTML5 input type attributes of email, number, search, tel, and url. These will switch the keyboard that is displayed. See the type attribute.
So for example, you could do this:
<input type="number" />
And when the input box has focus, the number keyboard is shown (as if the user had the full keyboard and hit the "123" button.
If you really only want numbers, you could specify:
<input type="tel" />
And then the user would get the phone number dialing keypad.
I know this works with Mobile Safari -- I only assume it will work with UIWebView.
http://conecode.com/news/2011/12/mobile-safari-uiwebview-input-types/
I made this little snippet to achieve what you want and I've tested it on iPhone 5 v7.0.3
I used e.which to read CharCode entered and then push it into an array (before) which represents digits before decimal mark and another array (after) to move values from (before) array past the decimal mark.
It might look complicated, due to my humble programming skills.
1) Code demo - 2) Currency conversion demo
HTML:
<input type="tel" id="number" />
JS
Variables and functions:
// declare variables
var i = 0,
before = [],
after = [],
value = [],
number = '';
// reset all values
function resetVal() {
i = 0;
before = [];
after = [];
value = [];
number = '';
$("#number").val("");
$(".amount").html("");
}
// add thousand separater
function addComma(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
Main code:
// listen to keyup event
$("#number").on("keyup", function (e, v) {
// accept numbers only (0-9)
if ((e.which >= 48) && (e.which <= 57)) {
// convert CharCode into a number
number = String.fromCharCode(e.which);
// hide value in input
$(this).val("");
// main array which holds all numbers
value.push(number);
// array of numbers before decimal mark
before.push(value[i]);
// move numbers past decimal mark
if (i > 1) {
after.push(value[i - 2]);
before.splice(0, 1);
}
// final value
var val_final = after.join("") + "." + before.join("");
// show value separated by comma(s)
$(this).val(addComma(val_final));
// update counter
i++;
// for demo
$(".amount").html(" " + $(this).val());
} else {
// reset values
resetVal();
}
});
Reset:
// clear arrays once clear btn is pressed
$(".ui-input-text .ui-input-clear").on("click", function () {
resetVal();
});
Result:
I think that you can use the same approach that I suggested to Ranjan.
Using a textfield like a buffer. First you need to detect when the keyboard appears and check if the first responder is the webview. Then you become a textview as the first responder.
When you are setting the text inside the input of the webview, you can add some logic to validate the number.
Here is a link of my example project with the solution, in your case you don't need change the inputView. But the approach is the same, use a Man in the middle.
Cant comment on https://stackoverflow.com/a/19998430/6437391 so posting as a separate answer...
This is the same idea as https://stackoverflow.com/a/19998430/6437391 but instead of switching the type, its the pattern that's switched.
This has the effect of not clearing the value on the textfield on focus when value does not match numeric format, for example, if the value has separators( 1,234.56 ).
$('input[type="text"]').on('touchstart', function() {
$(this).attr('pattern', '[0-9]*');
});
$('input[type="text"]').on('focus', function() {
$(this).attr('pattern', actualpattern);
});

Categories

Resources