I don't get this sorted out. I try to add validation (for form submit deactivation if invalid) wether an array contains items or not. i tried using a custom directive but it never gets called when the model updates :(
as the angular stuff is rendered inside a play applications template and the form is not defined within my scope I cannot do some easy form invalidation.
what i try to achieve is invalidating the form until some category has been added to $scope.app.categories thereby deactivating the submit button which is also not within my scope.
here comes the code (angular version 1.2.23):
<input type="text" ng-model="app.categories" name="size" custom />
<input id="tagsinput" type="text" ng-model="new.category" on-keyup="disabled" keys="[13]"/>
<a class="btn" ng-click="addCategory()">Add</a>
// loading of app happens above this is the contoller function
$scope.addCategory = function () {
$scope.app.categories.push({name: $scope.new.category});
$scope.new.category = "";
}
app.directive('custom', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$formatters.unshift(function(val1, val2, val3, val4) {
// not even gets called would like to validate app.categories here :(
console.log(true);
});
}
};
});
You're using $formatters, but for validation you should use $validators. Instead of your line starting with ctrl.$formatters, try the following:
ctrl.$validators.yourValidatorName = function(modelValue, viewValue) {
console.log("Validator is called.");
return true; // This means validation passed.
};
You can find this in the documentation. Also do not forget to add the directive to the HTML element, so in your case <input ... custom>. To disable a submit button if validation fails, use <input type="submit" data-ng-disabled="formName.$invalid">.
Related
I'm trying to make a field on a form valid by using the $valid class. I have the below HTML and JS code but even though the JS seems to work by modifying code editors, it doesn't validate the HTML. It doesn't seem to make it so $valid does anything but become true if the field has any text in it.
<input name="user" ng-model="user" ng-controller="SearchController" placeholder="User ID" required><br>
<span style="color:red" ng-show="searchForm.user.$dirty && searchForm.user.$invalid">A valid user ID is required.</span>
and
var searchApp = angular.module('searchApp', []);
searchApp.controller('SearchController', ['$scope', function ($scope) {
var users = ['11', '22', '33', '44']
return {
require: 'ngModel',
link: function (scope, element, attr, mCtrl) {
function idValidation(value) {
if (users.includes(value)) {
mCtrl.$setValidity('charE', true);
} else {
mCtrl.$setValidity('charE', false);
}
return value;
}
mCtrl.$parsers.push(idValidation);
}
};
}]);
The JS logic seems to work, but the HTML is not working correctly.
So you have a few minor errors in your code's logic to make this function correctly.
What you are attempting to do is create a custom validator. Your directive is using a $parser function, which is intended to parse a model's value for display. What you really want to be doing is working with a $validators function.
So, you would want to do something along the following:
var searchApp = angular.module('searchApp', []);
searchApp.directive('validUser', ['$scope', function ($scope) {
var users = ['11', '22', '33', '44']
return {
require: 'ngModel',
link: function (scope, element, attr, mCtrl) {
mCtrl.$validators.charE = function (value) {
return users.includes(value)
}
}
};
}]);
What happens is anytime the ngModel changes, it will run the value through the validators and determine if they are valid or invalid and automatically set ng-invalid-{name} or ng-valid-{name} (in this example, ng-invalid-charE and ng-valid-charE).
You can than use ngMessages to define your errors and they will show and hide as appropriate based on the validity.
Your ngController should more likely be a directive that adds the validator and your controller to contain the entire Search functionality/form HTML and be above your input to define your scope. Changing it to a directive would mean you'd remove ngController from the input and add an attribute for valid-user (as I've updated the name).
I have a custom directive and want to use the validators from an input field used inside this directive template. Is there a way to extend ngModelCtrl validators with the input validators?
This is my directive:
angular.module('myModule')
.directive('myUrl', function() {
return {
restrict: 'A',
require: ['ngModel', '^form'],
replace: true,
templateUrl: '/components/url/my-url.html',
scope: {},
link: function(scope, element, attrs, ctrls) {
var ngModelCtrl = ctrls[0];
var formCtrl = ctrls[1];
scope.formField = ngModelCtrl;
// Scope data vars
scope.url = '';
// Watchers
scope.$watch('url', function(newValue) {
ngModelCtrl.$setViewValue(newValue);
});
// Validators
var inputValidators = formCtrl['my-url'].$validators;
ngModelCtrl.$validators = angular.copy(inputValidators);
// Custom render
ngModelCtrl.$render = customRender;
function customRender() {
scope.url = ngModelCtrl.$viewValue;
}
}
}
});
This is my view:
<div>
<label>My URL</label>\
<input type="url" name="my-url" placeholder="http://" ng-model="url">
<div ng-messages="formField.$error" ng-show="formField.$invalid">
<span ng-message="url">Invalid URL.</span>
</div>
</div>
Here's a fiddle with this problem: https://jsfiddle.net/bsmaniotto/gzLmy1op/
Thanks.
EDIT:
Just realized that the problem is not on binding the input[url] validators to my custom directive ngModelCtrl's validators. The input[url] element is being validated and when an invalid input is entered the $modelValue is not set, and therefore, for my custom directive it is as nothing is inputted.
input validation is pretty easy in angular if you're using a controller. you can add:
ui-event: {keyup: 'controller.validationFunctionName(whatever)'}
Just fill in 'whatever' with the text in your input field and every time a key is let go it will check your validation function
If you want it to display an error beneath it just create another div that has an ng-if= to the result of that keyup function
I have made an attempt to create a custom validation directive to validate two items simultaneously at server side.
I have two values regNumber and regDate that have to be validated along with each other. So when user enters both of them correctly,they are validate. But, if one of them is entered incorrectly, they both have to be invalidated.
To accomplish the goal, I have written a the following directive base on this post.
Everything is working fine except When I enter both together regNumber and regDate incorrectly. Then, even If I change them both to correct values, still they are invalidated.
By "Everythin is working fine" I mean when I enter an invalid value for regNumber and a valid value for regDate, and change the regNumber back to a valid value, it works fine and vice versa(first regDate and then regNumber).
I think $setValidatity will be set for the latest input which has been changed and not for both of them but even if my guess is true, I don't know how to solve it. :D
Directive:
osiApp.directive('uniqueOrder', function ($http, $rootScope) {
var toId;
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elem, attr, ctrl) {
scope.$watch(attr.ngModel, function (value) {
if (scope.osiRequest.regDate) {
ctrl.$setValidity('uniqueOrder', true);
if (toId) clearTimeout(toId);
toId = setTimeout(function () {
$http({
method: 'GET',
url: $rootScope.baseAddress + '/ValidateOrderRegistrationNumber/Get',
params: {
orderRegistrationDate: scope.osiRequest.regDate ,
orderRegistrationNumber: scope.osiRequest.regNumber
}
}).success(function (isValid) {
ctrl.$setValidity('uniqueOrder', isValid);
});
}, 200);
}
});
}
}
});
HTML:
<div class="form-group" ng-class="myForm.regNumber.$error.uniqueOrder ||
myForm.regDate.$error.uniqueOrder ? 'has-error' : ''">
<input class="form-control"
name="regNumber" ng-model="osiRequest.regNumber" unique-order>
</div>
<div class="form-group" ng-class="myForm.regNumber.$error.uniqueOrder ||
myForm.regDate.$error.uniqueOrder ? 'has-error' : ''">
<input class="form-control"
name="regDate" ng-model="osiRequest.regDate " unique-order>
</div>
As mentioned by #NicolasMoise you have to be able to access both model in your directive, few ways to achieve this :
Adding both ng-model osiRequest.regDate & osiRequest.regNumber as your directive attributes unique-order. Check "scope" parameter for directive.
Access parent controller scope from directive by using $parent ( not ideal )
I created a dynamic form where there are repeated fields submitted as an array. However, i want to validate each field individually and display the error message next to it. If i only have one row, it works fine, but once i add a second row, the first row stops displaying errors.
<form name='user' id='user' novalidate>
<div ng-repeat="bonus in bonuses">
<input name='codes[]' ng-model="bonus.code" lower-than="{{bonus.end_code}}" />
<input name='end_codes[]' ng-model="bonus.end_code" />
<span class="text-error" ng-show="user['codes[]'].$error.lowerThan">
Code must be less than End Code.
</span>
</div>
</form>
AngularJS
var app = angular.module('newBonus', []);
app.controller('NewBonusController', function($scope) {
$scope.bonuses = [];
$scope.addFields = function () {
$scope.bonuses.push({code:'', end_code: ''});
}
$scope.submit = function(){
console.log($scope.bonuses);
}
});
// Validate that one field is less or equal than other.
app.directive('lowerThan', [
function() {
var link = function($scope, $element, $attrs, ctrl) {
var validate = function(viewValue) {
var comparisonModel = $attrs.lowerThan;
if(!viewValue || !comparisonModel){
// It's valid because we have nothing to compare against
ctrl.$setValidity('lowerThan', true);
}
// It's valid if model is lower than the model we're comparing against
ctrl.$setValidity('lowerThan', viewValue <= comparisonModel );
return viewValue;
};
ctrl.$parsers.unshift(validate);
ctrl.$formatters.push(validate);
$attrs.$observe('lowerThan', function(comparisonModel){
return validate(ctrl.$viewValue);
});
};
return {
require: 'ngModel',
link: link
};
}
]);
plunker: http://plnkr.co/edit/Fyqmg2AlQLciAiQn1gxY
I can settle for not having it next to each field, as long as changes to other field sets does trigger the error message properly in which case i can just pop it at the top. The main issue i see is that because they're arrays codes[] which are passed to the form at the end, they will not work properly.
The submit button is disabled properly on form validation, so i'm not sure why the message only locks onto the last row added.
Use a child form to separate the scope.
<ng-form name="frmChild">
<input name='codes' ng-model="bonus.code" lower-than="{{bonus.end_code}}" />
<input name='end_codes' ng-model="bonus.end_code" />
<span class="text-error" ng-show="frmChild.codes.$error.lowerThan">
Code must be less than End Code.
</span>
</ng-form>
For a specific use case I have to submit a single form the "old way". Means, I use a form with action="". The response is streamed, so I am not reloading the page. I am completely aware that a typical AngularJS app would not submit a form that way, but so far I have no other choice.
That said, i tried to populate some hidden fields from Angular:
<input type="hidden" name="someData" ng-model="data" /> {{data}}
Please note, the correct value in data is shown.
The form looks like a standard form:
<form id="aaa" name="aaa" action="/reports/aaa.html" method="post">
...
<input type="submit" value="Export" />
</form>
If I hit submit, no value is sent to the server. If I change the input field to type "text" it works as expected. My assumption is the hidden field is not really populated, while the text field actually is shown due two-way-binding.
Any ideas how I can submit a hidden field populated by AngularJS?
You cannot use double binding with hidden field.
The solution is to use brackets :
<input type="hidden" name="someData" value="{{data}}" /> {{data}}
EDIT : See this thread on github : https://github.com/angular/angular.js/pull/2574
EDIT:
Since Angular 1.2, you can use 'ng-value' directive to bind an expression to the value attribute of input. This directive should be used with input radio or checkbox but works well with hidden input.
Here is the solution using ng-value:
<input type="hidden" name="someData" ng-value="data" />
Here is a fiddle using ng-value with an hidden input: http://jsfiddle.net/6SD9N
You can always use a type=text and display:none; since Angular ignores hidden elements. As OP says, normally you wouldn't do this, but this seems like a special case.
<input type="text" name="someData" ng-model="data" style="display: none;"/>
In the controller:
$scope.entityId = $routeParams.entityId;
In the view:
<input type="hidden" name="entityId" ng-model="entity.entityId" ng-init="entity.entityId = entityId" />
I've found a nice solution written by Mike on sapiensworks. It is as simple as using a directive that watches for changes on your model:
.directive('ngUpdateHidden',function() {
return function(scope, el, attr) {
var model = attr['ngModel'];
scope.$watch(model, function(nv) {
el.val(nv);
});
};
})
and then bind your input:
<input type="hidden" name="item.Name" ng-model="item.Name" ng-update-hidden />
But the solution provided by tymeJV could be better as input hidden doesn't fire change event in javascript as yycorman told on this post, so when changing the value through a jQuery plugin will still work.
Edit
I've changed the directive to apply the a new value back to the model when change event is triggered, so it will work as an input text.
.directive('ngUpdateHidden', function () {
return {
restrict: 'AE', //attribute or element
scope: {},
replace: true,
require: 'ngModel',
link: function ($scope, elem, attr, ngModel) {
$scope.$watch(ngModel, function (nv) {
elem.val(nv);
});
elem.change(function () { //bind the change event to hidden input
$scope.$apply(function () {
ngModel.$setViewValue( elem.val());
});
});
}
};
})
so when you trigger $("#yourInputHidden").trigger('change') event with jQuery, it will update the binded model as well.
Found a strange behaviour about this hidden value () and we can't make it to work.
After playing around we found the best way is just defined the value in controller itself after the form scope.
.controller('AddController', [$scope, $http, $state, $stateParams, function($scope, $http, $state, $stateParams) {
$scope.routineForm = {};
$scope.routineForm.hiddenfield1 = "whatever_value_you_pass_on";
$scope.sendData = function {
// JSON http post action to API
}
}])
I achieved this via -
<p style="display:none">{{user.role="store_user"}}</p>
update #tymeJV 's answer
eg:
<div style="display: none">
<input type="text" name='price' ng-model="price" ng-init="price = <%= #product.price.to_s %>" >
</div>
I had facing the same problem,
I really need to send a key from my jsp to java script,
It spend around 4h or more of my day to solve it.
I include this tag on my JavaScript/JSP:
$scope.sucessMessage = function (){
var message = ($scope.messages.sucess).format($scope.portfolio.name,$scope.portfolio.id);
$scope.inforMessage = message;
alert(message);
}
String.prototype.format = function() {
var formatted = this;
for( var arg in arguments ) {
formatted = formatted.replace("{" + arg + "}", arguments[arg]);
}
return formatted;
};
<!-- Messages definition -->
<input type="hidden" name="sucess" ng-init="messages.sucess='<fmt:message key='portfolio.create.sucessMessage' />'" >
<!-- Message showed affter insert -->
<div class="alert alert-info" ng-show="(inforMessage.length > 0)">
{{inforMessage}}
</div>
<!-- properties
portfolio.create.sucessMessage=Portf\u00f3lio {0} criado com sucesso! ID={1}. -->
The result was:
Portfólio 1 criado com sucesso! ID=3.
Best Regards
Just in case someone still struggles with this, I had similar problem when trying to keep track of user session/userid on multipage form
Ive fixed that by adding
.when("/q2/:uid" in the routing:
.when("/q2/:uid", {
templateUrl: "partials/q2.html",
controller: 'formController',
paramExample: uid
})
And added this as a hidden field to pass params between webform pages
<< input type="hidden" required ng-model="formData.userid" ng-init="formData.userid=uid" />
Im new to Angular so not sure its the best possible solution but it seems to work ok for me now
Directly assign the value to model in data-ng-value attribute.
Since Angular interpreter doesn't recognize hidden fields as part of ngModel.
<input type="hidden" name="pfuserid" data-ng-value="newPortfolio.UserId = data.Id"/>
I use a classical javascript to set value to hidden input
$scope.SetPersonValue = function (PersonValue)
{
document.getElementById('TypeOfPerson').value = PersonValue;
if (PersonValue != 'person')
{
document.getElementById('Discount').checked = false;
$scope.isCollapsed = true;
}
else
{
$scope.isCollapsed = false;
}
}
Below Code will work for this IFF it in the same order as its mentionened
make sure you order is type then name, ng-model ng-init, value. thats It.
Here I would like to share my working code :
<input type="text" name="someData" ng-model="data" ng-init="data=2" style="display: none;"/>
OR
<input type="hidden" name="someData" ng-model="data" ng-init="data=2"/>
OR
<input type="hidden" name="someData" ng-init="data=2"/>