Here's a jsfiddle example of what I'm trying to accomplish.
I'm trying to build a US phone number input where the view displays as (333) 555-1212, but the model binds to the numeric integer 3335551212.
My intention is to add custom validators to NgModelController which is why I have require: ng-model; there are simpler solutions without the isolate scope and NgModelController, but I need both.
You'll see an immediate error in the console: Error: Multiple directives [ngModel, ngModel] asking for 'ngModel' controller on: <input ng-model="user.mobile numeric" name="telephone" type="tel"> -- thought I was using an isolate scope here...
Thank you for looking #mimir137 but I appear to have solved it:
http://jsfiddle.net/hr121r18/8/
The directive was using replace: true, which ends up with this structure:
<form ng-controller="FooCtrl" class="ng-scope">
<p>Enter US phone number</p>
<input ng-model="user.mobile numeric" name="telephone" type="tel">
</form>
Both the template and the markup called for ng-model which led to the symptomatic error in the problem description. Once I removed that, it leads to this markup (note the wrapper element phone-number):
<form ng-controller="FooCtrl" class="ng-valid ng-scope ng-dirty ng-valid-parse" abineguid="BC0D9644F7434BBF80094FF6ABDF4418">
<p>Enter US phone number</p>
<phone-number ng-model="user.mobile" class="ng-untouched ng-valid ng-isolate-scope ng-dirty ng-valid-parse">
<input ng-model="numeric" name="telephone" type="tel" class="ng-valid ng-dirty ng-touched">
</phone-number>
</form>
But removing this required changes to $render; the elem passed into the link function is now phone-number and so you need to dig to grab the input inside it and set the value on that:
ngModel.$render = function () {
elem.find('input').val($filter('phonenumber')(ngModel.$viewValue));
};
There were a few other issues. $render() also needed to be called from the watcher.
Final:
var app = angular.module('myApp', []);
// i want to bind user.mobile to the numeric version of the number, e.g. 3335551212, but
// display it in a formatted version of a us phone number (333) 555-1212
// i am trying to make the directive's scope.numeric to have two-way binding with the controller's
// $scope.user.mobile (using isolate scope, etc.).
app.controller('FooCtrl', function ($scope) {
$scope.user = {
mobile: 3335551212
};
});
app.directive('phoneNumber', ['$filter', function ($filter) {
return {
restrict: 'E',
template: '<input ng-model="numeric" name="telephone" type="tel">',
require: 'ngModel',
scope: {
numeric: '=ngModel'
},
link: function (scope, elem, attrs, ngModel) {
// update $viewValue on model change
scope.$watch('numeric', function () {
ngModel.$setViewValue(scope.numeric);
ngModel.$render();
});
// $modelValue convert to $viewValue as (999) 999-9999
ngModel.$formatters.push(function (modelValue) {
return $filter('phonenumber')(String(modelValue).replace(/[^0-9]+/, ''));
});
// $viewValue back to model
ngModel.$parsers.push(function (viewValue) {
var n = viewValue;
if (angular.isString(n)) {
n = parseInt(n.replace(/[^0-9]+/g, ''));
}
return n;
});
// render $viewValue through filter
ngModel.$render = function () {
elem.find('input').val($filter('phonenumber')(ngModel.$viewValue));
};
}
};
}]);
app.filter('phonenumber', function () {
return function (number) {
if (!number) {
return '';
}
number = String(number);
var formattedNumber = number;
var c = (number[0] === '1') ? '1 ' : '';
number = number[0] === '1' ? number.slice(1) : number;
var area = number.substring(0, 3),
exchange = number.substring(3, 6),
subscriber = number.substring(6, 10);
if (exchange) {
formattedNumber = (c + '(' + area + ') ' + exchange);
}
if (subscriber) {
formattedNumber += ('-' + subscriber);
}
return formattedNumber;
}
});
HTML
<form ng-controller="FooCtrl">
<p>Enter US phone number</p>
<phone-number ng-model='user.mobile'></phone-number>
</form>
I created this fiddle that gets rid of most of your errors coming up in the console. Hopefully this will at least be able to put you on the right track.
I changed the template so that you can see that the filter is actually working.
It now has the typical {{ngModel | FilterName}} in plain text underneath the textbox.
The only real issue is displaying it in the textbox. I'm sure you will have no problem with that. I will check in the morning just in case you still have questions regarding this.
Edit: Alright it appears you have solved it already. Great job!
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 angular directive that I'm using to change the display of price on an item. It's stored as a yearly value and I need to toggle between allowing users to see/edit as yearly or as monthly. So my custom directive is working on my input boxes (not able to use it for a label, div, span, but that's another issue).
I'm able to see that when I toggle the attribute, it picks up the change, but the value in the input doesn't change and I assume it's because not re-rendering the element. Is there any way I can force that to happen?
Here's my directive
angular.module('common.directive').directive('priceMonthly', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ngModel) {
var perMonth = false;
attr.$observe('priceMonthly', function(isMonthly) {
perMonth = (isMonthly.toLowerCase() === "true");
console.log("per month is " + perMonth); //updating perMonth correctly at this point
//guessing I need to do something here or in the function that changes the value of the attribute
});
function fromView(value){
return perMonth ? value * 12 : value;
}
function toView(value){
return perMonth ? value / 12 : value;
}
ngModel.$parsers.push(fromView);
ngModel.$formatters.push(toView);
}
};
});
Here's where I'm using it.
<input type="text" price-monthly="{{monthlySqftView}}"
class="form-control no-animate" name="item.priceLow"
ng-model="item.priceLow" ui-money-mask="2"/>
Here's a jsfiddle of where I'm currently at. So if the dropdown is on monthly, and I type in say 12, the price is correctly 144 which would be the full year price. Now if you change the dropdown to yearly, I need the input to update to 144.
https://jsfiddle.net/6tnbonzy/
Try scope.$apply() after you made required changes
#rschlachter, i am not sure if this can help you. AngularJS uses scope.digest() to clean the dirty data. you might need to put this line of code into your method
attr.$observe('priceMonthly', function(isMonthly) {
perMonth = (isMonthly.toLowerCase() === "true");
console.log("per month is " + perMonth); //updating perMonth correctly at this point
//guessing I need to do something here or in the function that changes the value of the attribute
//put it here, if you instantiated scope already.
scope.digest();
//scope.apply();
});
hope it works
It seems you want to do something like this jsfiddle?
<form name="ExampleForm">
<select ng-model="period">
<option value="monthly">monthly</option>
<option value="yearly">yearly</option>
</select>
<pre>
period = {{period}}
</pre>
Edit as {{period}}: <input price-monthly="obj.price" period="period" ng-model="obj.price" >
<pre>
price = {{obj.price}}
</pre>
</form>
And JS controller
.controller('ExampleController', function($scope) {
$scope.period = 'monthly';})
And JS directive
.directive('priceMonthly', function() {
return {
restrict: 'A',
scope:{
priceMonthly:"=",
period:"="
},
link: function(scope) {
scope.$watch('period',function(newval){
if(newval=='monthly')
scope.priceMonthly = scope.priceMonthly*12;
if(newval=='yearly')
scope.priceMonthly = scope.priceMonthly/12;
})
}
};});
I hope this will help you.
I have a text box. I would like to call a method inside controller only when user has filled in 'n' or more number of characters in the textbox.
Can someone please give me pointers on how to approach this?
Thanks
Id recommend just using ngChange and binding to an evaluation function. Below is a sample
angular.module('inputChange', [])
.controller('TextInputController', ['$scope', function ($scope) {
var inputMin = 3;
$scope.someVal = '';
$scope.result = '';
$scope.textChanged = function() {
if ($scope.someVal.length >= inputMin) executeSomething()
else $scope.result = '';
};
function executeSomething() {
$scope.result = $scope.someVal;
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="inputChange" ng-controller="TextInputController">
<input type="text" ng-model="someVal" ng-change="textChanged()" ng-Trim="false" />
<br />
someVal: <span ng-bind="someVal"></span>
<br />
Result: <span ng-bind="result"></span>
<br />
someVal Length: <span ng-bind="someVal.length"></span>
<br />
Result Length: <span ng-bind="result.length"></span>
</div>
You could simply achieve this by using ng-keyup directive
ng-keyup="(1myNgModel.length >= n) && myFunction()"
Desired function will only gets called only if length of model is greater than equal to n length
Working Plunkr
Though the better version would be having ng-model-options with debounce time, so that it will reduce number of value change. After that we can easily use ng-change directive to fire function.
<input type="text" ng-model="myNgModel"
ng-change="(myNgModel.length >= 3) && myFunction()"
ng-model-options="{ debounce: 200 }" />
Updated Demo
You can add a directive to your element and $watch for model changes. Then you can fire any logic you wish when your model has changed and has a value. In this case, lets call our model expression. Here is an example for a <textarea> element. This approach can just as well be used for an <input /> element as well.
<textarea watcher ng-model="expression"></textarea>
app.directive('watcher', [function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
scope.$watch(attrs.ngModel, function (v) {
if(v) {
// you have a value
} else {
// no value
}
});
}
}
}]);
JSFiddle Example
A good way to do this is to use a directive. Here's how it might be done:
view:
<div ng-app="foo" ng-controller="fooController">
<textarea text-length-handler="doThing()" text-length="6" ng-model="text">
</textarea>
</div>
js:
angular.module('foo', [])
.directive('textLength', function(){
return {
restrict: 'A',
require: 'ngModel',
scope: {
textLengthHandler: '&'
},
link: function ($scope, $element, $attrs, ctrl) {
var limit = parseInt($attrs.textLength);
var handler = function(){
if (ctrl.$modelValue.length >= limit) {
$scope.textLengthHandler()
}
};
$element.on('keypress', handler);
// remove the handler when the directive disappears
$scope.$on('destroy', function(){
$element.off('keypress', handler)
});
}
}
})
Fiddle here:
http://jsfiddle.net/dtq0mz8m/
If you tie the input field to a variable using ngModel, you can watch it from the controller (is not very elegant, though) using $watch or $observe whenever it changes, and check the length.
I have this custom validation directive:
/**
* Overwrites default url validation using Django's URL validator
* Original source: http://stackoverflow.com/questions/21138574/overwriting-the-angularjs-url-validator
*/
angular.module('dmn.vcInputUrl', [])
.directive('vcUrl', function() {
// Match Django's URL validator, which allows schemeless urls.
var URL_REGEXP = /^((?:http|ftp)s?:\/\/)(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::\d+)?(?:\/?|[\/?]\S+)$/i;
var validator = function(value) {
if (!URL_REGEXP.test(value) && URL_REGEXP.test('http://' + value)) {
return 'http://' + value;
} else {
return value;
}
}
return {
require: '?ngModel',
link: function link(scope, element, attrs, ngModel) {
function allowSchemelessUrls() {
// Silently prefixes schemeless URLs with 'http://' when converting a view value to model value.
ngModel.$parsers.unshift(validator);
ngModel.$validators.url = function(value) {
return ngModel.$isEmpty(value) || URL_REGEXP.test(value);
};
}
if (ngModel && attrs.type === 'url') {
allowSchemelessUrls();
}
}
};
});
It works fine when you 'dirty' the input by typing or pasting, but I need it to run this validation, overwriting the default type="url" validation when the value is initially set in the ngModel.
I've tried adding ngModel.$formatters.unshift(validator); but it results in the 'http://' being added to input, which I need to avoid as user's changes are manually approved and it would be a waste of time to approve the addition of 'http://'.
Any help would be appreciated!
Set ng-model-options on the input type field, for example:
<input type="text"
ng-model-options="{ updateOn: 'default', debounce: {'default': 0} }"</input>
This will ensure your validator gets fired "when the value is initially set in the ngModel", as you have stated in the question.
See detailed AngularJs documentaion on ngModelOptions:enter link description here
validation of Url :
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form name="form">
URL: <input type="url" ng-model="url.text" placeholder="Enter Link" name="fb_link"></input>
<span class="error" ng-show="form.fb_link.$error.url"></span>
</form>
I have a simple html form containing regular text input. ng-minlength, ng-maxlength and ng-pattern angular built-in form input directives are set on the input.
Problem: ng-pattern check is applied before the length check by ng-minlength and ng-maxlength.
Question: how can I change the default check order: i.e. first check for the length, then apply pattern check?
Example:
<body ng-app>
<div>
<form name="myForm">
Name: <input name="name" type="text" ng-model="name" ng-minlength="3" ng-maxlength="16" ng-pattern="/^\w+$/"/>
<div ng-show="myForm.name.$dirty && myForm.name.$invalid">
<span ng-show="myForm.name.$error.pattern">Pattern error</span>
<span ng-show="myForm.name.$error.minlength || myForm.name.$error.maxlength">Length error</span>
</div>
<br/>
<input type="submit" value="Submit">
</form>
</div>
</body>
Current behavior:
enter "#" - see "Pattern error"
enter "###" - see "Pattern error"
Desired behavior:
enter "#" - see "Length error"
enter "###" - see "Pattern error"
FYI, related jsfiddle.
Thanks in advance.
Write your own directive:
var mod = angular.module("myApp", []);
mod.directive("nameValidation", function () {
return {
restrict: "A",
require: "ngModel",
link: function (scope, element, attrs, ngModelCtrl) {
var validate = function (value) {
var minLen = parseInt(attrs.myMinlength, 10),
maxLen = parseInt(attrs.myMaxlength, 10),
pattern = attrs.myPattern,
match = pattern.match(/^\/(.*)\/([gim]*)$/),
lenErr = false;
if (match) {
pattern = new RegExp(match[1], match[2]);
}
if (!ngModelCtrl.$isEmpty(value)) {
ngModelCtrl.$setValidity("pattern", true);
if ((minLen && value.length < minLen) || (maxLen && value.length > maxLen)) {
ngModelCtrl.$setValidity("length", false);
lenErr = true;
}
else {
ngModelCtrl.$setValidity("length", true);
lenErr = false;
}
if (!lenErr) {
if (match && !pattern.test(value)) {
ngModelCtrl.$setValidity("pattern", false);
}
else {
ngModelCtrl.$setValidity("pattern", true);
}
}
}
else {
ngModelCtrl.$setValidity("length", true);
ngModelCtrl.$setValidity("pattern", true);
}
}
ngModelCtrl.$parsers.push(validate);
ngModelCtrl.$formatters.push(validate);
}
}
});
Then in your HTML, include the app and use the directive:
<body ng-app="myApp">
<div>
<form name="myForm">
Name: <input name="name" type="text" ng-model="name" name-validation="" my-minlength="3" my-maxlength="16" my-pattern="/^\w+$/"/>
<div ng-show="myForm.name.$dirty && myForm.name.$invalid">
<span ng-show="myForm.name.$error.pattern">Pattern error</span>
<span ng-show="myForm.name.$error.length">Length error</span>
</div>
<br/>
<input type="submit" value="Submit">
</form>
</div>
</body>
The directive uses my-minlength, my-maxlength, and my-pattern for the three values. If length fails, that will trip first. If not, then pattern will show as error if it doesn't match. Consider renaming this directive if you want to use it other places besides name as minlength, maxlength, and pattern can be passed to it via attributes. If they are left off, they will be ignored.
See jsfiddle: http://jsfiddle.net/4zpxk/6/
I searched in angular code why this behavior. Then in the function 'textInputType' that it's the specific function that handles text inputs for the angular 'input' directive I found this at the end of this function, where we can see three blocks of code.
// pattern validator
if (pattern){
//validator logic
}
// min length validator
if (attr.ngMinlength) {
//validator logic
}
// max length validator
if (attr.ngMaxlength) {
//validator logic
}
So, no matter if you change the declaration order of your ng-* attributes in the html input element you will always get same result but if you change the order of the blocks, I mean, put the min length validator block before pattern validator block you will have the result that you expect.
This is a solution for your problem but you have to make a litte change in angular code and I don't know if you really like this. But you got a very common situation where order of the declaration of validation concepts matters, so, something more must be done to handle this. Thanks
You cannot change the default check order unfortunately.
One solution is to write a custom validator, not that difficult. Based on this answer, I came up with this code (fiddle)
Usage: There is an array of validation functions in the scope, they get passed to our custom directive "validators" as:
<input name="name" type="text" ng-model="name" validators="nameValidators"/>
A validator function would look like (e.g. for the minlength constraint):
function minlength(value, ngModel) {
if( value == null || value == "" || value.length >= 3 ) {
ngModel.$setValidity('minlength', true);
return value;
}
else {
ngModel.$setValidity('minlength', false);
return;
}
}
Important points are: it takes the value and the ngModel as arguments, performs the test (here value.length >= 3) and calls ngModel.$setValidity() as appropriate.
The directive registers the given functions with ngModel.$parsers:
app.directive("validators", function($parse) {
return {
restrict: "A",
require: "ngModel",
link: function(scope, el, attrs, ngModel) {
var getter = $parse(attrs.validators),
validators = getter(scope),
i;
for( i=0; i < validators.length; i++ ) {
ngModel.$parsers.push((function(index) {
return function(value) {
return validators[index](value, ngModel);
};
})(i));
}
}
};
});
Many details can be tweaked and improved, but the outline works (again link to fiddle). Now the order of validation is explicitly set by the order of the validator functions in the nameValidators array.
If you use ng-messages you should be able to set the order via the order of ng-message elements, e.g:
<div ng-messages="field.$error">
<ul class="validation-errors">
<li ng-message="required">This has the highest prio</li>
<li ng-message="min">Second in command</li>
<li ng-message="max">I'm last</li>
</ul>
</div>
Also the docs on this: https://docs.angularjs.org/api/ngMessages/directive/ngMessages
i just changed the order of your directives, pattern first
<input name="name" type="text" ng-model="name" ng-pattern="/^\w+$/" ng-minlength="3" ng-maxlength="16"/>
EDIT: uuum, tested your fiddel without changes and it shows your desired behavior ...
directives are compiled by priority, bbut i don't know how to set angulars directives priority ... sorry, should have tested this first