I have the following input field
<input type="text" class="form-control pull-right" ng-model="ceremony.CeremonyFee | number:2">
it is showing up correctly but has been disabled. The error I am receiving is "[ngModel:nonassign] Expression 'ceremony.CeremonyFee | number:2' is non-assignable". I understand why it is in error, but do not know how to get this to work on an input field. Thanks.
input with ng-model is for inputting data, number filter is for displaying data. As filter values are not bindable, they are not compatible, as you can see. You have to decide what you want to do with that input.
Do you want it to be an input? User can input his own number and you only needs to validate? Use i.e. pattern attribute:
<input type="text" ng-model="ceremony.CeremonyFee" pattern="[0-9]+(.[0-9]{,2})?">
Do you want it to be an output? User does not need to input his own value? Do not use ng-model, use value instead:
<input type="text" value="{{ceremony.CeremonyFee | number:2}}" readonly>
UPDATE:
really I don't understand what you need, but, if you want just that users can insert only two digits you should use a simple html attributes, have a look on min, max, step...
Follows a pure js solution, but I don't suggest something like that!
angular.module('test', []).controller('TestCtrl', function($scope) {
var vm = $scope;
var testValue = 0;
Object.defineProperty(vm, 'testValue', {
get: function() { return testValue; },
set: function(val) {
val = Number(val);
if(angular.isNumber(val) && (val < 100 && val > 0)) {
console.log(val);
testValue = val;
}
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section ng-app="test">
<div ng-controller="TestCtrl">
<input style="display:block; width: 100%; padding: 1em .5em;" type="number" ng-model="testValue" />
</div>
</section>
the ng-model directive requires a viewmodel assignable (or bindable) property, so, you cannot add a pipe...
angular.module('test', [])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-init="testValue = 0">
<label ng-bind="testValue | currency"></label>
<input style="display:block;" ng-model="testValue" type="number"/>
</div>
As an error states you have got an 'non-assignable' expression in your ng-model attribute.
You should use only ceremony.CeremonyFee.
| is used on ng-repeat to indicate what expression should be used as filter.
If you want to have that <input> populated with initial data in your controller/link you should give it an initial value ex.
$scope.ceremony = {
CeremonyFee: 'My first ceremony'
}
And every time your <input> element data will be changed CeremonyFee will be updated as well.
I found and used the solution found on this page.
http://jsfiddle.net/k7Lq0rns/1/
'use strict';
angular.module('induction').$inject = ['$scope'];
angular.module('induction').directive('format',['$filter', function ($filter) {
return {
require: '?ngModel',
link: function (scope, elem, attrs, ctrl) {
if (!ctrl) return;
ctrl.$formatters.unshift(function (a) {
return $filter(attrs.format)(ctrl.$modelValue)
});
elem.bind('blur', function(event) {
var plainNumber = elem.val().replace(/[^\d|\-+|\.+]/g, '');
elem.val($filter(attrs.format)(plainNumber));
});
}
};
}]);
relatively easy to apply it.
Related
In the controller if have a variable that tracks the index (starting at 0) of the page for a pagination table:
var page {
pageNumber: 0;
}
Question: how can I show this pageNumber variable in the html, but always incremented by +1? (as the index=0 page is obviously the 1st page and should thus be shown as Page 1)
<input type="text" ng-model="page.pageNumber">
Also, when the model gets updated, the value in the input should automatically change (again: also incremented by +1).
I think this is a use-case for $formatters and $parsers. They operate on the model's property and there is no need to create a dummy property on the model. Documentation here. Please correct me if this is not the use case for $formatters and $parsers.
Please see below.
HTML markup
<body ng-app="app" ng-controller="mainCtrl">
{{page}}
<input paginated-index type="text" ng-model="page">
</body>
js
var app = angular.module('app', []);
app.controller('mainCtrl', function($scope) {
$scope.page = 0;
});
app.directive('paginatedIndex', function()
{
return{
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModelController)
{
ngModelController.$formatters.push(function(value)
{
return value+1;
})
ngModelController.$parsers.push(function(value)
{
return value-1;
})
}
}
});
In your controller, change your page object to this:
$scope.page = {
displayedPage: function(num) {
if(arguments.length) {
$scope.page.pageNumber = num - 1;
return num;
} else {
return $scope.page.pageNumber + 1;
}
},
pageNumber: 0
}
And then yourelement to this:
<input type="text" ng-model="page.displayedPage" ng-model-options="{ getterSetter: true}" />
This will display the page number plus 1, but leave the actual page.pageNumber variable the way it should be.
The getterSetter: true options I've added in will bind the model to a getter/setter function, which allows you to pass in the argument - in this case, your entered page number - and return from that function. You can read more information on this in the documentation for ngModel
you can try using something like this.
$scope.data=$scope.page.pageNumber+1;
$scope.fuc=function(){
$scope.page.pageNumber=$scope.data-1;
};
and your Html will be like
<input type="text" ng-model="data" ng-change="fuc()" >
check this plunk Plunker
I've got a UI page setup through Angular, and I'm trying to take advantage of the built in ng-maxlength validator on an input element. Long story short, I know about $scope.form.$error and how that object has a maxlength property in the case that the validation fails. But I want to display an error message specific to the character length that was violated, and I don't see anywhere that the length that I specified was stored on this object. Does anyone know if it's possible to access this, so I don't have to write out a separate error message for each input that has the max length violated?
EDIT: To answer your question, yes angular does store a boolean value in the $error object that is accessible to your via the key(s) that are set in the object. In the case of the code I provided below and in th jsFiddle, we are setting the key for angular, and the value of either true or false.
Be mindful when setting the value as it is reversed. ex. $setValidity( true ), flips the $error to false.
Ok, here is what I think you were looking for...
In Angularjs v1.2.13 you will not have access to ng-message or the $validator pipeline,
which is why are are using $formatters and $parsers.
In this case, I am using named inputs, but perhaps in your case you need dynamic input names?
Plus, if you are using inputs but no form, then getting the error message to display would have to be done with a separate custom directive.
If so, then please look here for dynamically named input fields for some help.
dynamic input name in Angularjs link
Let me know if this works; I'll make changes as needed to HOOK YOU UP!
In case you don't know, you can write over Angular's maxlength for each individual input.
If you changed 'maxlength' in the updateValidity() function in the directive below, to something like 'butter', then $scope.form.inputname.$error would be something like
$scope.formname.inputname.$error { butter: true }
if you also used ng-maxlength="true", then it would be
$scope.formname.inputname.$error { butter: true, maxlength: true }
Another example if you used ng-maxlength, and capitalized the 'maxlength' in the directive to 'Maxlength'
Then you would get
$scope.formname.inputname.$error { maxlength: true(angular maxlength), Maxlength: true(your maxlength)
And of course if you name it the same, then yours writes over angulars
$scope.formname.inputname.$error { maxlength: true };
The point is YOU can add your own names to the angular $error object; you can write over Angular's; and you can just use what Angular gives you when you use Angular's directives: like ng-required="true", or ng-maxlength="true"
Link to YOUR angularjs version on jsFiddle
jsFiddle LInk
<div ng-app="myApp">
<form name="myForm">
<div ng-controller="MyCtrl">
<br>
<label>Input #1</label>
<br>
<input ng-model="field.myName" name='myName' my-custom-length="8" />
<span ng-show="myForm.myName.$error.maxlength">
Max length exceeded by {{ myForm.myName.maxlength }}
</span>
<br>
<br>
<label>Input #2</label>
<br>
<input ng-model="field.myEmail" name='myEmail' my-custom-length="3" />
<span ng-show="myForm.myEmail.$error.maxlength">
Max length exceeded by {{ myForm.myEmail.maxlength }}
</span>
</div>
</form>
</div>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function ($scope) {
$scope.field = {};
});
app.directive("myCustomLength", function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
if (!ctrl) { return } // ignore if no ngModel controller
ctrl.$formatters.push(validateInput);
ctrl.$parsers.unshift(validateInput);
function validateInput(value) {
if (!value) {
updateValidity(false);
return;
}
inputLength(value);
var state = value.length > attrs.myCustomLength;
updateValidity(state);
}
function inputLength(value) {
ctrl.maxlength = null;
var length = value.length > attrs.myCustomLength;
if (length) {
ctrl.maxlength = (value.length - attrs.myCustomLength).toString();
}
}
function updateValidity(state) {
ctrl.$setValidity('maxlength', !state);
}
} // end link
} // end return
});
CSS Here if you need it.
input.ng-invalid {
border: 3px solid red !important;
}
currently I'm using a custom directive which wraps the ng-minlength and ng-maxlength directives to apply the values of these directives to the model of the input. I need to do this because I'm creating a validation service which uses the angular $error object on a form to return a user friendly message about what's wrong. The problem is, when it comes to min and max lengths, I want to be able to tell the user what the length should be. I've got this working by using the following method
directive('minlength', ['$compile', function ($compile) {
return {
restrict: 'A',
require: 'ngModel',
compile: function compile(tElement) {
tElement.attr('ng-minlength', tElement.attr('minlength'));
tElement.removeAttr('minlength');
return {
post: function postLink(scope, elem, attrs) {
var keys,
form,
field = attrs['ngModel'];
$compile(elem)(scope);
for (keys in scope) {
if (scope.hasOwnProperty(keys)) {
if (keys.substring(0, 2).indexOf('$') < 0) {
if (keys !== 'this') {
form = keys;
break;
}
}
}
}
if (form) {
console.log(attrs);
scope[form][field]['minlength'] = attrs['minlength'];
}
}
}
}
}
}])
But this seems a bit longhanded and possibly difficult to maintain and test. Is there a better way to do this?
If you want just to inform the user about the min and the max:
var app = angular.module('myApp',[]);
app.controller('MyCtrl',function($scope){
$scope.minValue = 5;
$scope.maxValue = 10;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<form name="myForm">
Last name: <input type="text" name="lastName" ng-model="user.last"
ng-minlength="{{minValue}}" ng-maxlength="{{maxValue}}">
<span class="error" ng-show="myForm.lastName.$error.minlength">
Should be {{minValue}} charachters long</span>
<span class="error" ng-show="myForm.lastName.$error.maxlength">
No more than {{maxValue}} characters please</span><br>
</form>
</div>
</div>
This is part of a much more complicated directive that needs to have its own scope as well as require ngModel and replace the existing input. How can I have the directive add the ng-pattern attribute? As you can see in this jsfiddel the validation doesn't change based on the input if the ng-pattern is added in the template. This is because this will be added to an existing application that has a ton of different attributes already on a ton of different input elements, and I'm trying to make the addition as easy to implement as possible by just adding functionality to the existing input fields without messing up other things.
http://jsfiddle.net/MCq8V/
HTML
<div ng-app="demo" ng-init="" ng-controller="Demo">
<form name="myForm" ng-submit="onSubmit()">
<input lowercase type="text" ng-model="data" name="number">
Valid? {{myForm.number.$valid}}
<input type="submit" value="submit"/>
</form>
</div>
JS
var module = angular.module("demo", []);
module.directive('lowercase', function() {
return {
require: 'ngModel',
restrict: 'A',
scope:{},
replace: true,
link: function(scope, element, attr, ngModelCntrl) {
},
template: '<input class="something" ng-pattern="/^\d*$/">',
};
});
module.controller('Demo', Demo);
function Demo($scope) {
$scope.data = 'Some Value';
}
Thanks so much for any help! Ideally I would be able to just change something small and keep the ng-pattern, but I think I may have to do the validation setting on my own.
Here's how the pattern attribute is added to input item in a directive I have in my application. Note the use of compile at the end of the link function. In your case, rather than replace the element contents with a template, you'd just work with the existing element input tag.
link: function (scope, element, attrs, formController) {
// assigned template according to form field type
template = (scope.schema["enum"] !== undefined) &&
(scope.schema["enum"] !== null) ?
$templateCache.get("enumField.html") :
$templateCache.get("" + scope.schema.type + "Field.html");
element.html(template);
// update attributes - type, ng-required, ng-pattern, name
if (scope.schema.type === "number" || scope.schema.type === "integer") {
element.find("input").attr("type", "number");
}
element.find("input").attr("ng-required", scope.required);
if (scope.schema.pattern) {
element.find("input").attr("ng-pattern", "/" + scope.schema.pattern + "/");
}
element.find("input").attr("name", scope.field);
// compile template against current scope
return $compile(element.contents())(scope);
}
I tried quite a few things and it seemed that using a directive to replace an input with an input was tricking Angular up somewhere - so this is what I came up with:
http://jsfiddle.net/MCq8V/1/
HTML
<div ng-app="demo" ng-init="" ng-controller="Demo">
<form name="myForm" ng-submit="onSubmit()">
<div lowercase model="data"></div>
Valid? {{myForm.number.$valid}}
<input type="submit" value="submit"/>
</form>
</div>
JS
var module = angular.module("demo", []);
module.directive('lowercase', function() {
return {
restrict: 'A',
scope:{
data:'=model'
},
replace: true,
template: '<input class="something" ng-pattern="/^\\d*$/" name="number" ng-model="data" type="text">',
};
});
module.controller('Demo', Demo);
function Demo($scope) {
$scope.data = 'Some Value';
}
Also, you needed to escape your backslash in your regex with another backslash.
Let's say I have the following (very simple) data structure:
$scope.accounts = [{
percent: 30,
name: "Checking"},
{ percent: 70,
name: "Savings"}];
Then I have the following structure as part of a form:
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
Now, I want to validate that the percents sum to 100 for each set of accounts, but most of the examples I have seen of custom directives only deal with validating an individual value. What is an idiomatic way to create a directive that would validate multiple dependent fields at once? There are a fair amount of solutions for this in jquery, but I haven't been able to find a good source for Angular.
EDIT: I came up with the following custom directive ("share" is a synonym for the original code's "percent").
The share-validate directive takes a map of the form "{group: accounts, id: $index}" as its value.
app.directive('shareValidate', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
params = angular.copy(scope.$eval(attr.shareValidate));
params.group.splice(params.id, 1);
var sum = +viewValue;
angular.forEach(params.group, function(entity, index) {
sum += +(entity.share);
});
ctrl.$setValidity('share', sum === 100);
return viewValue;
});
}
};
});
This ALMOST works, but can't handle the case in which a field is invalidated, but a subsequent change in another field makes it valid again. For example:
Field 1: 61
Field 2: 52
If I take Field 2 down to 39, Field 2 will now be valid, but Field 1 is still invalid. Ideas?
Ok, the following works (again, "share" is "percent"):
app.directive('shareValidate', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.shareValidate, function(newArr, oldArr) {
var sum = 0;
angular.forEach(newArr, function(entity, i) {
sum += entity.share;
});
if (sum === 100) {
ctrl.$setValidity('share', true);
scope.path.offers.invalidShares = false;
}
else {
ctrl.$setValidity('share', false);
scope.path.offers.invalidShares = true;
}
}, true); //enable deep dirty checking
}
};
});
In the HTML, set the attribute as "share-validate", and the value to the set of objects you want to watch.
You can check angularui library (ui-utility part). It has ui-validate directive.
One way you can implement it then is
<input type="number" name="accountNo" ng-model="account.percent"
ui-validate="{overflow : 'checkOverflow($value,account)' }">
On the controller create the method checkOverflow that return true or false based on account calculation.
I have not tried this myself but want to share the idea. Read the samples present on the site too.
I have a case where I have a dynamic form where I can have a variable number of input fields on my form and I needed to limit the number of input controls that are being added.
I couldn't easily restrict the adding of these input fields since they were generated by a combination of other factors, so I needed to invalidate the form if the number of input fields exceeded the limit. I did this by creating a reference to the form in my controller ctrl.myForm, and then each time the input controls are dynamically generated (in my controller code), I would do the limit check and then set the validity on the form like this: ctrl.myForm.$setValidity("maxCount", false);
This worked well since the validation wasn't determined by a specific input field, but the overall count of my inputs. This same approach could work if you have validation that needs to be done that is determined by the combination of multiple fields.
For my sanity
HTML
<form ng-submit="applyDefaultDays()" name="daysForm" ng-controller="DaysCtrl">
<div class="form-group">
<label for="startDate">Start Date</label>
<div class="input-group">
<input id="startDate"
ng-change="runAllValidators()"
ng-model="startDate"
type="text"
class="form-control"
name="startDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="form-group">
<label for="eEndDate">End Date</label>
<div class="input-group">
<input id="endDate"
ng-change="runAllValidators()"
ng-model="endDate"
type="text"
class="form-control"
name="endDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="text-right">
<button ng-disabled="daysForm.$invalid" type="submit" class="btn btn-default">Apply Default Dates</button>
</div>
JS
'use strict';
angular.module('myModule')
.controller('DaysCtrl', function($scope, $timeout) {
$scope.initDate = new Date();
$scope.startDate = angular.copy($scope.initDate);
$scope.endDate = angular.copy($scope.startDate);
$scope.endDate.setTime($scope.endDate.getTime() + 6*24*60*60*1000);
$scope.$watch("daysForm", function(){
//fields are only populated after controller is initialized
$timeout(function(){
//not all viewalues are set yet for somereason, timeout needed
$scope.daysForm.startDate.$validators.checkAgainst = function(){
$scope.daysForm.startDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
$scope.daysForm.endDate.$validators.checkAgainst = function(){
$scope.daysForm.endDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
});
});
$scope.runAllValidators = function(){
//need to run all validators on change
$scope.daysForm.startDate.$validate();
$scope.daysForm.endDate.$validate();
};
$scope.applyDefaultDays = function(){
//do stuff
}
});
You can define a single directive that is only responsible for this check.
<form>
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
<!-- HERE IT IS -->
<sum-up-to-hundred accounts="accounts"></sum-up-to-hundred>
</form>
And here's the simple directive's code.
app.directive('sumUpToHundred', function() {
return {
scope: {
accounts: '<'
},
require: {
formCtrl: '^form'
},
bindToController: true,
controllerAs: '$ctrl',
controller: function() {
var vm = this;
vm.$doCheck = function(changes) {
var sum = vm.accounts.map((a)=> a.percent).reduce((total, n)=> total + n);
if (sum !== 100) {
vm.formCtrl.$setValidity('sumuptohundred', false);
} else {
vm.formCtrl.$setValidity('sumuptohundred', true);
}
};
}
};
});
Here's a plunker.