I have an object that has 2 fields, while 1 should be less than or equal to another.
Say it's HDD quota settings and I need the threshold to be less than or equal to HDD's size.
I am trying to use angular's ui-utils#validate.
This is how I got so far: http://embed.plnkr.co/EysaRdu2vuuyXAXJcJmE/preview (i hope the link will work)
The problem that I am having is that it works to one direction:
Setting size and then playing with threshold works ok
But if I try to change size, after threshold is in invalid state - nothing happens. This is because invalid threshold is not set on the model and size id being compared against null or undefined (or something like that).
On one hand I understand the logic of not setting invalid value on the model... but here it is getting in my way.
So, any help making this work will be appreciated.
I have played with custom directives and cooked something that works for my case.
On my input for threshold I have less-than-or-equal="quota.size" directive, passing it the model's property to validate against (I want quota.threshold to be less than or equal to quota.size):
<input type="number" name="threshold"
ng-model="quota.threshold"
required
less-than-or-equal="quota.size" />
In link function of lessThanOrEqual directive it starts to watch the quota.size and when quota.size changes it just tries to set the current view value of threshold on model:
link: (scope, elem, attr, ctrl) ->
scope.$watch attr.lessThanOrEqual, (newValue) ->
ctrl.$setViewValue(ctrl.$viewValue)
Then there is the parser that does the validation by calling scope.thresholdValidate(thresholdValue) method passing it the candidate value. This method returns true if validation succeeded and if it does - it returns the new value, otherwise - current model's value:
ctrl.$parsers.push (viewValue) ->
newValue = ctrl.$modelValue
if not scope.thresholdValidate viewValue
ctrl.$setValidity('lessThanOrEqual', false)
else
ctrl.$setValidity('lessThanOrEqual', true)
newValue = viewValue
newValue
I am pushing the parser to parser collection, as opposite to unshifting it like most of the examples suggest, because I want angular to validate required and number directives, so I get here only if I have a valid and parsed number (less work for me, but for text inputs I probably should do the parsing job)
Here is my playground: http://embed.plnkr.co/EysaRdu2vuuyXAXJcJmE/preview
Better late than never, you need to add ng-model-options="{allowInvalid:true}" to your form input elements to stop this happening - the problem is that when a promise is rejected (e.g. using $q or $http) the model, by default, is not updated. Crazy huh! Cost me a day working this out.
I have written a plunkr specifically for this problem - Trust me this code is good ...
http://embed.plnkr.co/xICScojgmcMkghMaYSsJ/preview
Related
I have made an AngularJS directive that requires a model and converts the value of the model (which, in this example is "25mm") to something else that is showed in the view (in this example, converted to inches).
Here is a working plnkr demo: http://plnkr.co/edit/fO1S9GcubHE57Pf7Kt9G?p=preview
The demo, however, doesn't work as expected. After changing the value of the dropdown from "inches" to "feet", I'd expect the view to be re-rendered with the appropriate value. This is not the case.
I have noticed that this is because I'm not "listening" for changes, and that is why I placed the
scope.$watch("[convertFrom, convertTo]", function(n) {
});
which works fine in terms of watching for changes, but if I uncomment that $watch, the formatter stops working.
My second problem is that I'm pushing functions to the arrays $formatters and $parsers. To be exact, one function every time the value of the dropdown changes.
My questions are:
How can I watch for changes and make the formatter work?
How can I not insert a new function in the $formatter and the $parser arrays every time my dropdown changes.
I managed to do it myself. Here is a plunkr demo: http://plnkr.co/edit/jgF2QIP4QeA0Glcnn35B?p=preview
It turns out that I need to push my $parser and $formatter function only once and watch for changes from outside, then trigger a "re-parse" or "re-format":
scope.$watch("[convertFrom, convertTo]", function(n) {
ngModel.$modelValue = '';
});
I extended an Ember.TextField to include a date picker. A function that observes the text field’s value attempts to parse the string in the text field and update a date property. This is all fine and good when you use the date picker, but if you were to try to type a date into the box, it goes crazy because the value gets updated on every keydown (or keyup or whatever Ember’s default event to update the value bindings for a TextField), and it immediately re-updates the value of the text field with the nicely-formatted date string that came from what it just parsed. Example:
Input says 10/26/2014
You insert your cursor after 2014 and hit backspace
The value has changed, so a handler parses 10/26/201 and updates a date property
The date property has changed, so a handler formats the date as MM/d/yyyy and sets the value
The input now says 10/26/0201
Rather than changing the way those handlers work, all my problems would be solved if I could tell Ember to update the value binding when the input’s change event fires, rather than trying to update everything on every keystroke. I know this can be done in AngularJS and Knockout, but I can’t find anything about it for Ember.
EDIT
I know I can change the way my code works to avoid this specific problem. At this point, I’m more interested for purposes of edification, in a yes-or-no answer that specifically addresses the question that is the title of this post. I’m starting to think the answer is no, but wanted to poll the community.
I wrote a blog post that may offer some solutions about Date Pickers And Validation In Ember with examples here is one of the JSBins from the post.
Write your own extension of text field component and add the change callback.
App.DateTextComponent = Em.TextField.extend({
change: function(event){
var value = this.get('value');
// massage data
value += "foo";
this.set('value', value);
}
});
Example: http://emberjs.jsbin.com/suzami/2/edit
If you really want to get a call when the value changes after the fact, don't observe the value, use actions.
App.DateTextComponent = Em.TextField.extend({
change: function(event){
var value = this.get('value');
this.sendAction('changed', value);
}
});
{{date-text value=foo changed='fooChanged'}}
Example: http://emberjs.jsbin.com/suzami/3/edit?html,js,output
I'm using angular 1.3.0rc2, and I'm trying to have an input field, on blur, set another input field as if it was user entered.
If there exists only a synchronous validator on the input I'm trying to set, and no debounce or async validators, things always seem to work as expected when I do something like:
myForm.username.$setViewValue('some value');
myForm.username.$render();
However, things don't seem to work as expected when either an async validator or debounce exists on the input I'm trying to set, and when the input I'm trying to set is in a currently invalid state.
I've created the following plunkr to demonstrate my woes. Try setting the value of "Full Name" and see what happens with the Username field, which I want to also be set on blur of the Full Name field. When the Username field is in an invalid state, changing the value of Full Name and then removing focus from the field does not update Username as I would expect.
http://plnkr.co/edit/h1hvf79W8ovqAlNLpBvS
So, my question is, how can I set one input field from another in such a way that it will work reliably and act as if the user themselves has entered this new input (thus running through validators and updating the model value accordingly)?
Give this a try, it should adjust the username when you blur the full name.
Plunkr
ngModel
HTML
Full Name:
<input name="fullName" ng-model="fullName" test-validator change-field="username" />
User Name:
<input name="username" ng-model="username" required />
JS
angular.module("testApp",[]).controller("testCtrl", function($scope) {
}).directive('testValidator', ['$sce', function ($sce) {
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
if(!ngModel) return;
element.on('blur', function () {
scope.$apply(read);
});
function read() {
if(attrs.changeField) {
var field = attrs.changeField;
scope.myForm[field].$setViewValue(element.val());
scope.myForm[field].$render();
}
}
}
}
}])
OK, So the basic problem we want to solve here is that having a user set an input that turns out to be invalid leads to the underlying model value being set to undefined, whereas programatically setting the input to an invalid state does not.
This was inconsistent, and so I was trying to find a way to circumvent that. It turns out that the resolution isn't as straightforward as hoped (see https://github.com/angular/angular.js/issues/9295)
However, the reverse solution was proposed instead, which involves using ngModelOptions on the enclosing form (or per input but thats more hassle) with "allowInvalid" set to true. This means that invalid values will still be set to the model, rather than the model value being cleared in that case.
Given that the model is always set now regardless of validity, I can revert to a simpler means of programatically setting one field form another - simply setting the underlying model value, and it behaves identically to if a user set the value themselves :)
plunkr demonstration with an async validator on the second input again:
http://plnkr.co/edit/lkDN4qLbbkN79EmRrxsY?p=preview
see how the model ends up the same regardless of whether the second input value is set by hand or set form the first input, irrespective of validity and the sue of async validator, as desired :)
If you add any of the angular directives for validation (ng-minlength, ng-maxlength, ng-pattern, etc.) to an input that is bound to a breeze entity it blocks any user input if found to be invalid.
If the value from ng-model is initially valid it shows up, but if you change the value to something invalid the input field is cleared, the model is set to null, and you can't input anything that may be initially invalid. However if you copy a valid value into the field it shows.
I would be fine with the fact that the model value is set to null when invalid if it didn't clear the input then prevent changes.
Also I have a feeling that whatever is causing this issue is also messing up ui-mask. The same thing happens there just without the angular validation directives.
here is a Plunker I found from a similar question that I modified to show my issue:
http://plnkr.co/edit/dVsF7GFY65a30soLL5W8?p=preview
Edit
After many many hours of research I did find a solution that works although I am not sure of any ill side effects.
It has to do with how angular does validation in the first place by setting the $modelValue to 'undefined' if it fails any validators as it makes it's way through $parsers and $formatters.
I found this code in Angular (line 16331) that gets called by each angular validator:
function validate(ctrl, validatorName, validity, value){
ctrl.$setValidity(validatorName, validity);
return validity ? value : undefined;
}
I changed it to return 'value' instead of 'undefined':
function validate(ctrl, validatorName, validity, value){
ctrl.$setValidity(validatorName, validity);
return value;
}
Angular still sets validation correctly. Although I am sure this isn't the best solution or even a good one.
I suspect the problem arises when Angular sets $modelValue to 'undefined' then Breeze sees that the model has changed and updates the entity which then updates the model which then clears the input and so forth... Or something like that...
I found this to be helpful in my quest. Maybe it will be helpful to one of you that knows much more than I https://github.com/angular/angular.js/issues/1412
Angular 1.3.0-rc.1 introduced the allowInvalid option for use with the ngModelOptions directive. It is essentially a formalization of the OP's hack at line 16331. The option instructs Angular to allow invalid form inputs to be written to $scope, and solves the problem neatly.
Usage:
<input type="email" ng-model-options="{allowInvalid: true}" ng-model="my_breeze_model.email"/>
See this feature request for more information: https://github.com/angular/angular.js/issues/8290.
I'm happy to look at your plunker and see if there is something Breeze can do about this.
I'm not terribly surprised. Ng also struggles when you combine it with HTML 5 validation as I recall. You really should only use one scheme I think.
Do you disagree?
Also, have you considered the zValidate directive in the Breeze Labs breeze.directives.js? We think that is the best way to expose Breeze entity property validation errors in the view.
Another solution to consider is to use the ng-model-options attribute made available with Angular 1.3+.
In this way, you can prevent the Angular digest occurring after every keypress, and instead defer it to, for example, the 'blur' event so that the use has a chance to actually enter the valid data.
It would look like this:
<input type="email" ng-model="customer.email" ng-model-options="{ updateOn: 'blur' }">
However, this still has the limitation that if you enter invalid input, on blur the input will be cleared out and the use will have to enter it all again. Not very user friendly in my opinion, so I'll be trying out the breeze-only approach to circumvent this issue.
However, I thought this solution was also worth mentioning here.
https://docs.angularjs.org/error/ngModel/numfmt describes how Angular considers it a programming error, not a user input error, if programmatic model changes don't respect the input's validation rules.
If your model does not contain actual numbers then it is up to the application developer to use a directive that will do the conversion in the ngModel $formatters and $parsers pipeline.
Their example describes a String model value for an <input type='number'> but the same logic applies here, I think. If your input contains a minLength attribute, the scope should not get updated with strings that are too short.
So to fix it, add a custom directive to your input that pushes a custom parser into the $parsers pipeline.
For instance, the following directive will hold off writing the value of an <input type='text' minLength='4' min4> to the scope until a long enough string has been typed into it:
.directive('min4', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(value) {
return value && value.length >= 4 ? value : "";
});
}
};
});
This prevents the nasty interactions that otherwise occur when Breeze writes updated values back to the scope and ends up overwriting not-yet-legal state in the input.
See Plunker demo
I am having a very strange bug when I am trying to update a user's address. I have this simplified address object with two fields, both observables:
stateProvince.name = ko.observable("");
stateProvince.code = ko.observable("");
Now, when I try to update both of these later, this is the effective program execution in dev tools:
stateProvince.name("New York");
stateProvince.code("NY");
but the second line does not actually change the value of the state code. no exceptions occur, attempting to change it in dev tools does not work, and the strangest part is that everything that fails when changing the code works fine when changing the name. What conditions could cause a knockout observable from failing to update with no errors? I am trying to extend an existing codebase but my searching has not revealed anything that would differentiate these two objects.
Moving from comment to answer:
If code is bound to a select and you are using the value binding (usually with options), then Knockout tries to enforce that your observable's value corresponds to an option. Make sure the your initial values corresponds to an option.
If your options are getting populated later, then you will need to either re-populate the selected value, or you can pre-populate it on the initial load with something like:
this.code = ko.observable(data.code);
//pre-populate with the one matching value
this.codeOptions = ko.observableArray([data.code]);