I am completely new to AngularJS and was wondering what's the proper way to clear text from my amount field?
I have a default 0.00 set in the field but I want the functionality of when a user clicks in the field the 0.00 disappears.
Ideally I would like this functionality to only work for the default value but either way i'm not bothered if it removes the new value added.
I have tried Googling and the only results I can find are for a clear button set to the field which I don't want.
Right now my HTML is:
<number-only-input input-value="transferitems.cashvalue" input-name="cashvalueinput" />
And my controller scope is:
$scope.totaltransfervalue = 0.00;
I need to apply this functionality to 4 others amount fields on the page also.
My directive for the number-only-input is:
app.directive('numberOnlyInput', function ($filter) {
return {
restrict: 'EA',
template: '<input name="{{inputName}}" ng-model="inputValue" ng-blur="oninputblur()" style="width:100% !important" class="form-control" required />',
scope: {
inputValue: '=',
inputName: '='
},
link: function (scope, element, attrs, ngModelController) {
scope.oninputblur= function() {
scope.inputValue = $filter('currency')(scope.inputValue, '', 2);
}
scope.$watch('inputValue', function (newValue, oldValue) {
if (newValue == oldValue) { return; }
if (!newValue) {
scope.inputValue = "";
} else {
var arr = String(newValue).split("");
if (arr.length === 0) return;
if (arr.length === 1 && (arr[0] == '-' || arr[0] === '.')) return;
if (arr.length === 2 && newValue === '-.') return;
if (isNaN(newValue)) {
scope.inputValue = oldValue;
}
}
});
}
};
});
Sounds like a job for the HTML placeholder attribute
<input placeholder="{{totalTransferValue}}" input-value="transferitems.cashvalue" input-name="cashvalueinput" />
It displays the current value of the scope variable as dimmed content which disappears when user start typing in the box. This is not limited to the default value.
No longer relevant since the question has been radically changed.
You should be able to achieve what you want by tweaking the directive itself.
First, you need to add ng-focus to the template
template: '<input ng-focus="oninputfocus()" ...
Then implement the handler
link: function (scope, element, attrs, ngModelController) {
scope.oninputfocus = function(){
// if(scope.inputValue == myDefaultValue) {
scope.inputValue = '';
// }
},
scope.oninputblur= ...
And you should be all set. I included (as comment) how the check to only clear the default value from the field.
Related
I'm checking whether or not a username already exists when I create a new user. I also want to be able to edit a user, and I'm actually using the same form for this. When the form is in edit-mode, you are not allowed to change the username and the input field will be disabled.
I have a directive that checks with the database if the username is unique, and validates or invalidates the input. This directive should not fire when the form is in edit-mode, because the username is not allowed to be edited, and the form should validate.
<input name="alias"
class="form-control"
type="text"
ng-model="user.alias"
required
ng-model-options="{ debounce: { default : 500, blur: 0 }}"
validate-alias="{{formMode == 'new' ? true : false}}"
/>
And the directive:
.directive('validateAlias', function($http, $q) {
return {
restrict: 'A',
require: 'ngModel',
validateAlias: '=',
link: function(scope, element, attrs, ngModel) {
attrs.$observe('validateAlias', function(){
console.log("validateAlias: " + attrs.validateAlias + ", which is a " + typeof attrs.validateAlias);
//this returns "validateAlias: false, which is a string"
if (attrs.validateAlias === 'true') console.log("should work"); else console.log("n/a");
//this returns "should work"
if (attrs.validateAlias === 'true') {
ngModel.$asyncValidators.username = function (alias) {
return $http.get(aliasurl + alias).then(
function (response) {
if (response.data.length > 0) {
return $q.reject('Username not unique');
} else {
return $q.resolve();
}
}
);
};
}
});
}
};
})
So what happens is that in the edit-mode the first if-statement returns what I expect (false, aka do not validate, and the attribute in the DOM matches this, displaying validate-alias='false'), yet the username is still checked with the database.
Everything works fine when I'm in new-mode, but that's probably just coincidence, not because the code works as it should.
I am doing something wrong, but I can't figure out where...!
Edit:
Right, I've changed things around a little bit. I now have the following directive:
.directive('validateAlias', function($http, $q) {
return {
restrict: 'A',
scope: {
validateAlias: '#'
},
link: function(scope, element, attrs) {
scope.$watch(attrs.validateAlias, function(v){
console.log("validateAlias: " + v + ", which is a " + typeof v);
if (v === true) console.log("the check is active"); else console.log("the check is NOT active");
if (v === true) {
ngModel.$asyncValidators.username = function (alias) {
return $http.get(aliasurl + alias).then(
function (response) {
if (response.data.length > 0) {
return $q.reject('Username not unique');
} else {
return $q.resolve();
}
}
);
};
}
});
}
};
})
Now the $watch does not seem to work... what am I missing?
Final edit:
The DOM had a set variable "false" for the attribute I made... the watch worked fine, but the DOM was generated once, so there was nothing to watch.
Luckily for me, every time the attribute changed, I could reload the DOM as well (virtually switching screens), and so the scope reads the attribute again, and the variable is passed, making the directive do exactly what I want. The code I posted after the first edit is what I used.
I'm not sure if that's the Angular way, but it works for now. When I find a better solution, I'll update the code.
Try this:-
.directive('validateAlias', function($http, $q) {
return {
restrict: 'A',
require: 'ngModel',
scope: {
validateAlias: '=',
}
link: function(scope, element, attrs, ngModel) {
scope.$watch('validateAlias', function(watchData){
console.log("validateAlias: " + watchData + ", which is a " + typeof watchData);
if (watchData === 'true') console.log("should work"); else console.log("n/a");
//this returns "should work"
if (watchData.value === 'true') {
ngModel.$asyncValidators.username = function (alias) {
return $http.get(aliasurl + alias).then(
function (response) {
if (response.data.length > 0) {
return $q.reject('Username not unique');
} else {
return $q.resolve();
}
}
);
};
}
});
}
};
})
I have my form validations dynamically added to the form from a response to a web service call. When the call returns it tells my validation directive what validations I need to add to the form. (I do this because I want to reuse the same validations on the server during submit as I do on the client.) This works wonderfully when the only validations are of type "required". The problem I have is when the model value of the form control does not pass the the validation rules, the model value is then "undefined". Therefore nothing get's sent to the server on form submission to validate on the server side. I do realize I could block the form submission if the form is not valid, however, I am letting the server determine the validity of the data that comes across.
What could I do to force the model value to be the "invalid value" regardless if it violated a validation rule? Better suggestions? Below is a snipit of my directive I am using.
//this directive should be put on an ng-form element and it will hide/show any validations set on each input
.directive('formValidator', ['validatorService', function (vs) {
return {
restrict: 'A',
require: '^form',
link: function (scope, element, attrs, form) {
function iterateOverErrorsObject(errors, func, ignorechecking) {
if (!func)
return;
//show any new errors
for (var key in errors) {
if (key.indexOf('__') == 0)
continue;
_.each(errors[key], function (obj) {
if (form[obj.$name] == obj || ignorechecking) { //ensure the obj is for the current form
var input = vs.findElementByName(element, obj.$name);
if (input.length > 0) {
func(input, obj);
}
}
});
}
}
scope.$watch(function () { return form.$error; }, function (newval, oldval, scp) {
iterateOverErrorsObject(oldval, function (input, obj) {
vs.hideErrors(input);
}, true);
iterateOverErrorsObject(newval, function (input, obj) {
vs.showErrors(input, obj, form._attr);
});
}, true);
//something told the validator to show it's errors
scope.$on('show-errors', function (evt) {
iterateOverErrorsObject(form.$error, function (input, obj) {
vs.showErrors(input, obj, form._attr);
});
});
scope.$on('hide-errors', function (evt) {
vs.hideAllErrors(form);
});
}
};
}])
//this directive is to be put on the ng-form element and will dynamically add/remove validators based on the validations configuration
//which comes back from the service call "Validate"
.directive('dynamicValidators', ['$compile', function ($compile) {
return {
priority: 0,
restrict: 'A',
//require: 'ngModel',
require: '^form',
scope: {
'validations': '=',
},
link: function (scope, element, attrs, ctrl) {
(function (form, scp) {
// this will hold any information necessary to get the error message displayed
// **have to add the form because the ctrl gets recreated every time the form.$error changes
function setAttr(ctrl, key, value) {
if (!ctrl._attr)
ctrl._attr = {};
if (!form._attr)
from._attr = {};
ctrl._attr[key] = value;
var obj = form._attr[ctrl.$name] = {};
obj[key] = value;
};
scope.$watch('validations', function (nv, ov) {
form._attr = {};
//remove old validators
if (ov && ov.length > 0) {
_.each(ov, function (e) {
var fctrl = form[e.MemberNames[0]];
if (fctrl && fctrl.$validators) {
delete fctrl.$validators[e.ErrorKey];
//fctrl.$setValidity(e.ErrorKey, true);
fctrl.$validate();
}
});
}
//add new validators
if (nv && nv.length > 0) {
_.each(nv, function (e) {
var fctrl = form[e.MemberNames[0]];
if (!fctrl)
return;
if (e.ErrorKey == 'required') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
if (modelValue instanceof Array)
return modelValue.length > 0;
else
return modelValue !== '' && modelValue !== null && modelValue !== undefined;
};
} else if (e.ErrorKey == 'alphanumeric') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
return viewValue == null || (viewValue != null && /^[a-zA-Z0-9]*$/.test(modelValue));
};
} else if (e.ErrorKey == 'min') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
return modelValue === undefined || modelValue === null || modelValue === "" || modelValue >= e.ErrorValue;
}
} else if (e.ErrorKey == 'max') {
setAttr(fctrl, e.ErrorKey, e.ErrorValue);
fctrl.$validators[e.ErrorKey] = function (modelValue, viewValue) {
return modelValue === undefined || modelValue === null || modelValue === "" || modelValue <= e.ErrorValue;
}
}
//make the validator fire to set the status of the validator
if (fctrl.$validators[e.ErrorKey])
//fctrl.$setValidity(e.ErrorKey, fctrl.$validators[e.ErrorKey](fctrl.$modelValue, fctrl.$viewValue))
fctrl.$validate();
});
}
});
})(ctrl, scope);
},
}
}]);
If you still want to send to the server invalid data, you can use the allowInvalid option with the ngModelOptions directive:
<input type="text" name="userName"
ng-model="user.name"
ng-model-options="{ allowInvalid: true }" />
From the documentation for ngModelOptions:
Model updates and validation
The default behaviour in ngModel is that the model value is set to
undefined when the validation determines that the value is invalid. By
setting the allowInvalid property to true, the model will still be
updated even if the value is invalid.
I'm trying to get input from text boxes to bind to scope variables as actual JavaScript Date objects, not strings. The inputs are generated dynamically so I'm unable to cast/convert before the values are sent to the server.
So far, I have created a directive that uses moment.js to parse the value on the scope, and convert it to a Date() object. The problem seems to be that angular converts the value back to a string immediately after. I guess it rechecks the html input and overwrites the Date() object set in the directive.
Here is a working Plunkr demonstrating the issue
(function () {
'use strict';
angular.module('app', ['ng'])
.controller('myController', ['$scope', function() {
$scope.testObj = null;
}])
.directive('dateBinding', function () {
return {
restrict: 'A',
require: 'ngModel',
scope: false,
link: function (scope, element, attrs, ngModel) {
var parseFormat = attrs.dateBinding;
scope.$watch(
function() {
console.log('watching model', ngModel.$modelValue);
return ngModel.$modelValue;
},
function (val) {
console.log('recieved model', val);
if (val && typeof val == 'string') {
console.log('attempting parse date', val);
if(moment(val, parseFormat).isValid())
{
console.log('string is valid date');
ngModel.$modelValue = moment(val, parseFormat).toDate();
console.log('completed value assignment', ngModel.$modelValue);
console.log('model is of type ' + typeof ngModel.$modelValue);
console.log('model is date', (ngModel.$modelValue instanceof Date));
}
else
{
console.log('string is not a valid date');
}
}
}
);
}
};
})
} ());
You can see the behaviour by opening the console in a browser while running the plunkr. The line 'completed value assignment' shows that at least momentarily, ngModel.$modelValue (from $scope.testObj) is a Date() object.
The final line in the output below shows the watch firing again, and the model value is once again a string as it appears in the html input.
How can I have the value persist as a Date object (once a valid date can be parsed).
You have to use the $parsers and $formatters pipelines, described in the docs of ngModelController. The code would be:
.directive('dateBinding', function () {
return {
restrict: 'A',
require: 'ngModel',
scope: false,
link: function (scope, element, attrs, ngModel) {
var parseFormat = attrs.dateBinding;
function parse(value) {
var m = moment(value, parseFormat);
if( m && m.isValid() ) {
ngModel.$setValidity('dateBinding', true);
return m.toDate();
}
else {
ngModel.$setValidity('dateBinding', false);
return; // undefined
}
}
function format(value) {
if( value && value instanceof Date ) {
return moment(d).format(parseFormat);
}
else {
return '';
}
}
ngModel.$formatters.push(format);
ngModel.$parsers.unshift(parse);
}
};
});
See (and play with) the forked plunk: http://plnkr.co/edit/VboH2iq6HRlaDhX3g1AY?p=preview
I have a list of checkboxes that are backed by a model that is an array of ids.
<input type="checkbox" name="checkers" value="black" ng-model="board" />
<input type="checkbox" name="checkers" value="white" ng-model="board" />
the model would look like:
[ 'black', 'white' ]
so there is a number of 'hacks' to get this to work like one would think and even a directive checklist-model.
My problem is I have a directive that does dynamic validation using ngModelController's $validators. That directive looks something like this:
module.directive('validator', function($parse) {
return {
restrict: 'A',
require: '?ngModel',
link: function($scope, $element, $attrs, ngModelCtrl) {
var rules = $parse($attrs.validator)($scope);
ngModelCtrl.$validators.myValidator = function(val){
// this is simplified, real case is much more complex
if(rules.minSelections > 0){
return !(val.length <= rules.minSelections);
}
if(rules.required){
return !val.length;
}
}
}
}
});
I attached it to my checkboxes like:
<input type="checkbox" name="checkers" val="black" validators="{ minSelections: 1 }" ng-model="board" />
<input type="checkbox" name="checkers" val="white" validators="{ minSelections: 1 }" ng-model="board" />
problem is the val in the myValidator validation always returns true/false. I can't ever seem to get ahold of the 'actual' model I need despite several different approaches and even using that directive. On a note: the $validators runs BEFORE the click on that directive.
Does anyone have any suggestions?
I ended up creating my own checkbox directive and manually triggering validation to happen.
If you take a look below, you can see how I watch the collection and if the value has changed I commit the value and re-trigger the validation manually.
Heres the code for others:
define(['angular'], function (angular) {
// Use to style checkboxes, bind checkboxes to arrays, and run validators on checkboxes
// Modified from: https://github.com/bkuhl/angular-form-ui/tree/master/src/directives/checkBox
var module = angular.module('components.checkbox', []);
/**
* <check-box ng-model="isChecked()"></check-box>
* Required attribute: ng-model="[expression]"
* Optional attribute: value="[expression]"
*/
module.directive('checkBox', function () {
return {
replace: true,
restrict: 'E',
scope: {
'externalValue': '=ngModel',
'value': '&'
},
require: 'ngModel',
template: function (el, attrs) {
var html = '<div class="ngCheckBox' + ((angular.isDefined(attrs.class)) ? ' class="'+attrs.class+'"' : '') + '">'+
'<span ng-class="{checked: isChecked}">' +
'<input type="checkbox" ng-model="isChecked"' + ((angular.isDefined(attrs.id)) ? ' id="'+attrs.id+'"' : '') + '' + ((angular.isDefined(attrs.name)) ? ' name="'+attrs.name+'"' : '') + '' + ((angular.isDefined(attrs.required)) ? ' name="'+attrs.required+'"' : '') + '/>'+
'</span>'+
'</div>';
return html;
},
controller: function ($scope) {
if (angular.isArray($scope.externalValue)) {
$scope.isChecked = $scope.externalValue.indexOf($scope.value()) >= 0;
} else {
$scope.isChecked = !!$scope.externalValue;
}
$scope.$watch('isChecked', function (newValue, oldValue) {
if (angular.isDefined(newValue) && angular.isDefined(oldValue)) {
//add or remove items if this is an array
if (angular.isArray($scope.externalValue)) {
var index = $scope.externalValue.indexOf($scope.value());
if(newValue) {
if( index < 0 ) $scope.externalValue.push($scope.value());
} else {
if( index >= 0 ) $scope.externalValue.splice(index, 1);
}
} else {
//simple boolean value
$scope.externalValue = newValue;
}
}
});
},
link: function ($scope, $elm, $attrs, ngModel) {
$scope.$watchCollection('externalValue', function(newVal) {
if (newVal.length) {
ngModel.$setTouched();
ngModel.$commitViewValue();
ngModel.$validate();
}
});
}
};
});
return module;
});
i have requirement where min value of one field depends on the input given in another field.
<input type="number" name="minval" class="form-control" ng-model="user.minval"
ng-required="true">
this input is used to validate another field
<input type="number" name="inputval" class="form-control" ng-model="user.inputval"
ng-required="true" min="{{user.minval}}">
but this is not working as expected.. if i change the "minval" later the input does not get revalidated..
i have tried setting the initial value for min from JS as was suggested in some solution but thats also not helping...
PLUNKER LINK
use ng-min/ng-max directives
app.directive('ngMin', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.ngMin, function(){
if (ctrl.$isDirty) ctrl.$setViewValue(ctrl.$viewValue);
});
var isEmpty = function (value) {
return angular.isUndefined(value) || value === "" || value === null;
}
var minValidator = function(value) {
var min = scope.$eval(attr.ngMin) || 0;
if (!isEmpty(value) && value < min) {
ctrl.$setValidity('ngMin', false);
return undefined;
} else {
ctrl.$setValidity('ngMin', true);
return value;
}
};
ctrl.$parsers.push(minValidator);
ctrl.$formatters.push(minValidator);
}
};
});
app.directive('ngMax', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.ngMax, function(){
if (ctrl.$isDirty) ctrl.$setViewValue(ctrl.$viewValue);
});
var maxValidator = function(value) {
var max = scope.$eval(attr.ngMax) || Infinity;
if (!isEmpty(value) && value > max) {
ctrl.$setValidity('ngMax', false);
return undefined;
} else {
ctrl.$setValidity('ngMax', true);
return value;
}
};
ctrl.$parsers.push(maxValidator);
ctrl.$formatters.push(maxValidator);
}
};
});
I've developed a couple of directives that actually restrict the user from setting an invalid value instead of simply throwing an error when an invalid value is provided.
These directives also do not require ngModel (though I doubt you would use them without) and what's really cool is that it will wrap the value around to the min/max if both settings are provided!
I've tried to simplify the directives as much as possible to make them easier for our readers.
Here is a JSFiddle of the whole thing: JSFiddle
And here are the directives:
app.directive('ngMin', function($parse){
return {
restrict: 'A',
link: function(scope, element, attrs){
function validate(){
if(element
&& element[0]
&& element[0].localName === 'input'
&& isNumber(attrs.ngMin)
&& isNumber(element[0].value)
&& parseFloat(element[0].value) < parseFloat(attrs.ngMin)){
if(isNumber(attrs.ngMax)){
element[0].value = parseFloat(attrs.ngMax);
if(attrs.hasOwnProperty("ngModel"))
$parse(attrs.ngModel).assign(scope, parseFloat(attrs.ngMax));
}
else {
element[0].value = parseFloat(attrs.ngMin);
if(attrs.hasOwnProperty("ngModel"))
$parse(attrs.ngModel).assign(scope, parseFloat(attrs.ngMin));
}
}
}
scope.$watch(function(){
return attrs.ngMin + "-" + element[0].value;
}, function(newVal, oldVal){
if(newVal != oldVal)
validate();
});
validate();
}
};
});
app.directive('ngMax', function($parse){
return {
restrict: 'A',
link: function(scope, element, attrs){
function validate(){
if(element
&& element[0]
&& element[0].localName === 'input'
&& isNumber(attrs.ngMax)
&& isNumber(element[0].value)
&& parseFloat(element[0].value) > parseFloat(attrs.ngMax)){
if(isNumber(attrs.ngMin)){
element[0].value = parseFloat(attrs.ngMin);
if(attrs.hasOwnProperty("ngModel"))
$parse(attrs.ngModel).assign(scope, parseFloat(attrs.ngMin));
}
else {
element[0].value = parseFloat(attrs.ngMax);
if(attrs.hasOwnProperty("ngModel"))
$parse(attrs.ngModel).assign(scope, parseFloat(attrs.ngMax));
}
}
}
scope.$watch(function(){
return attrs.ngMax + "-" + element[0].value;
}, function(newVal, oldVal){
if(newVal != oldVal)
validate();
});
validate();
}
};
});
...also, you will need this little helper function as well:
function isNumber(n){
return !isNaN(parseFloat(n)) && isFinite(n);
}
To invoke these directives, just set them on an input box where type="number":
<input ng-model="myModel" ng-min="0" ng-max="1024" />
And that should do it!
When you provide both an ngMin and ngMax, these directive will wrap the value around, so that when your value becomes less than ngMin, it will be set to ngMax, and vice-versa.
If you only provide ngMin or ngMax, the input value will simply be capped at these values.
I prefer this method of preventing bad values rather than alerting the user that they have entered a bad value.