Closed owlyowl closed 9 years ago
Or would it be possible to inject the types provided by the angular credit cards library in to a directive outside of the project?
so we could kick off validation only once the user has entered the minimum number of characters
That's not an option with the way ngModelController
works. Validation is run every time the model changes. There's no possibility of "kicking off" validation when a particular condition is met, short of dynamically adding a validation rule (terrible idea).
What it sounds like you want to do is not show an error message until it's clear that the data is invalid and the user is finished entering it. Personally I use the ngModelController.$touched
property with ngIf
to hide the ngMessages
until the field has been blurred.
If you can walk me through the exact UI you're trying to create I can better understand whether there needs to be a feature here.
Hi Ben, All good I implemented it like so:
link: ($scope: ng.IScope, element: JQuery, attrs: any, ngModel: any) => {
if (!ngModel) return;
var minCardTypeLengths = {
'Visa': 13,
'MasterCard': 15,
'American Express': 15,
'Diners Club': 14,
'Discover': 16,
'JCB': 16,
'UnionPay': 16
}
var form: ng.IFormController = ngModel[1];
var inputName: string = element.attr("name");
var dirtySet: boolean = false;
var validationApplied: boolean = false;
$scope.$watch(attrs.cardViewModel, function (value:
string[]) {
var preliminaryCardType =
form['cardNumber'].$ccEagerType;
var minCardLengthBeforeValidation = 16;
if (preliminaryCardType && preliminaryCardType.length >
0) {
minCardLengthBeforeValidation =
minCardTypeLengths[preliminaryCardType] || 16;
}
if ((value && value.length > 0 && value.join('').length
>= 0)) {
ngModel[0].$setViewValue(value.join(''));
ngModel[0].$render();
if (!dirtySet) {
ngModel[0].$pristine = false;
ngModel[0].$dirty = true;
ngModel[0].$setTouched();
dirtySet = true;
}
if (value.join('').length >=
minCardLengthBeforeValidation) {
validationApplied = true;
if (form[inputName].$invalid) {
element.closest('.input-wrap').removeClass('is-valid');
element.closest('.input-wrap').addClass('is-invalid');
} else {
element.closest('.input-wrap').removeClass('is-invalid');
element.closest('.input-wrap').addClass('is-valid');
}
} else if(validationApplied) {
element.closest('.input-wrap').removeClass('is-valid');
element.closest('.input-wrap').removeClass('is-invalid');
}
} else if(dirtySet) {
console.log('removing class');
element.closest('.input-wrap').removeClass('is-valid');
element.closest('.input-wrap').removeClass('is-invalid');
}
}, true);
}
it's a bit dirty for now but will clean it up later
Please post this stuff from GitHub in the future since it's impossible to read w/ your mail client's formatting.
Definitely have to advise very strongly against shipping something like this. It's incredibly brittle. You're basically undoing a huge chunk of Angular's form handling.
As mentioned, I want to encourage you to think about validations the way Angular does. Validations are just a set of functions that return true/false. The place for complex logic is in determining how to convey validation state to the user and apply it to your interface.
Hi Ben,
I was trying to find a neater way to do it but I have 4 text boxes which take the credit card input which then populate a hidden with a single string representing the credit card number as a combination of all 4 inputs.
The problem I had was that angular doesn't set the hidden as being dirty or touched or anything similar when it gets updated from the view:
<div class="field-wrap cc-number">
<label for="card-number">
Card number<br />
<span class="cc-icon {{ creditCardForm.cardNumber | creditCardIconFromType }}"></span>
<div class="input-wrap input-border" data-auto-tab=".group4">
<input type="text" pattern="[0-9]*" size="4" maxlength="4" class="group4" id="cardNumber1" name="cardNumber1" data-ng-model="payment.viewCardNumber[0]" data-only-numbers placeholder="XXXX" />
<input type="text" pattern="[0-9]*" size="4" maxlength="4" class="group4" id="cardNumber2" name="cardNumber2" data-ng-model="payment.viewCardNumber[1]" data-only-numbers placeholder="XXXX" />
<input type="text" pattern="[0-9]*" size="4" maxlength="4" class="group4" id="cardNumber3" name="cardNumber3" data-ng-model="payment.viewCardNumber[2]" data-only-numbers placeholder="XXXX" />
<input type="text" pattern="[0-9]*" size="4" maxlength="4" class="group4" id="cardNumber4" name="cardNumber4" data-ng-model="payment.viewCardNumber[3]" data-only-numbers placeholder="XXXX" />
<input type="hidden" data-ng-model="payment.creditCardNumber" data-card-view-model="payment.viewCardNumber" data-cc-number data-cc-eager-type="payment.creditCardType" name="cardNumber" data-ng-model-options="{ allowInvalid: true }" />
<span data-ng-show="creditCardForm.cardNumber.$touched || creditCardForm.cardNumber.$dirty" class="input-message-validate">
<span data-ng-messages="creditCardForm.cardNumber.$error" data-ng-show="creditCardForm.cardNumber.$valid === false">
<span data-ng-message="ccNumber">Bad credit card #</span>
</span>
<i class="icon-tick" data-ng-show="creditCardForm.cardNumber.$valid === true"></i>
</span>
</div>
</label>
</div>
Not sure why it's not set as dirty. Seems like a lot of effort here for something that's probably frustrating your users. The 4 inputs would piss me off as a user.
Yeah I couldn't figure out why it wasn't being set as dirty. I was thinking of spiking it in to a codepen or plunkr or something and having more of a look
http://plnkr.co/edit/Cr0aSShpXvxxK77E8Qgw?p=preview
This is the current implementation.. if you remove the manual setting of dirty and things like that you'll see it doesn't update. I think it is because the field is a hidden field and maybe needs to be a text input with display: none
Hi just wondering if it was possible to expose credit card type/length information so we could kick off validation only once the user has entered the minimum number of characters for a given card type.