Use a directive to display scope value and update scope - angularjs

I have an array of vertex values that I loop over and display to the user.
The values are stored as a space delimited string, like this:
vrtx = ["10.3 20.3 10.5", "13.2 29.2 3.0", "16.3 2.3 20.2", ...]
I want to provide a friendly interface to the user to edit the values.
This requires me to split the strings into three separate numbers and put them into three individual inputs.
I want to create a directive, as I am not able to store the values as separate values, but after the editing is done, I want to join the values back and update the scope to store the new values as strings.
This is the directive I have so far:
myApp.directive('fieldValue', function () {
return {
scope: {
field:'='
},
link: function (scope, element, attrs) {
var fields = scope.field.split(' ');
element.html("<input type='number' value='"+ fields[0] +"' class='vertex-input'/><input type='number' value='"+ fields[1] +"' class='vertex-input'/><input type='number' value='"+ fields[2] +"' class='vertex-input'/>");
}
}
});
This splits the value and shows the three inputs with the individual values, but my question is how do I join these values and save them back to the scope? Any suggestions would be greatly appreciated! TIA

You could use formatters/parsers to achieve what you want, but you may have to adjust your input to use ngModel, and I'm not quite sure how it would work with 3 separate input fields:
myApp.directive('fieldValue', function () {
return {
scope: {
field:'='
},
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var fields = scope.field.split(' ');
element.html("<input type='number' value='"+ fields[0] +"' class='vertex-input'/><input type='number' value='"+ fields[1] +"' class='vertex-input'/><input type='number' value='"+ fields[2] +"' class='vertex-input'/>");
ngModel.$formatters.push(function(value){
//return the value as you want it displayed
});
ngModel.$parsers.push(function(value){
//return the value as you want it stored
});
}
}
});
This way gives you a lot more flexibility in my opinion to use any strategy you want. Read more about it here: https://alexperry.io/angularjs/2014/12/10/parsers-and-formatters-angular.html
And, more official docs here: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController

I think that your question is the continuation of this one, so I would like to suggest you a solution without a directive, based on the answer of your precedent question.
When the value of an input is modified, call a function that merged all your inputs:
<input type="text" ng-model="arr[0]" ng-change="update()" />
<input type="text" ng-model="arr[1]" ng-change="update()" />
<input type="text" ng-model="arr[2]" ng-change="update()" />
$scope.update = function() {
$scope.myString = $scope.arr[0] + ' ' + $scope.arr[1] + ' ' + $scope.arr[2];
}
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', ['$scope', function($scope) {
$scope.myString = 'My awesome text';
$scope.arr = $scope.myString.split(/[ ]+/);
$scope.update = function() {
$scope.myString = $scope.arr[0] + ' ' + $scope.arr[1] + ' ' + $scope.arr[2];
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<input type="text" ng-model="arr[0]" ng-change="update()" />
<input type="text" ng-model="arr[1]" ng-change="update()" />
<input type="text" ng-model="arr[2]" ng-change="update()" />
<br/>Result: {{myString}}
</div>
</div>
Try it on JSFiddle.

Related

Validating list of variables in ng-repeat

In this plunk I have an ng-repeat of input fields. Each field has to pass two validations: (1) the value cannot be empty, and (2) if num = 1 then the value needs to be a number. I'm using ng-form in each row to validate the values independently (I cannot have the ng-repeat inside a <form>.
The problem is that messages are not displayed correctly. To replicate, in the plunk add a 1 to the third field. It becomes b1 that is not a valid number, still the Value should be a number error message is not displayed. It is displayed after you change the value again, for example to b11. Where is the problem and how to fix it?
HTML
<div ng-repeat="v in vals">
<ng-form name="formval">
<input type="text" name="val" ng-model="v.val" style="float:left"
ng-change="seeError(formval,v.num,v.val)" required/>
<div ng-show="!formval.val.$valid" ng-messages="formval.val.$error" class="errorMsg">
<div ng-message="shouldBeNumber">Value should be a number</div>
<div ng-message="required">Value cannot be empty</div>
</div>
{{ 'error row #' + $index}} {{formval.val.$error}}
<br/><br/><br/>
</ng-form>
</div>
Javascript
var app = angular.module('app', []);
app.controller('ctl', function ($scope) {
$scope.vals = [
{val: 'a', num: 0},
{val: 2, num: 1 },
{val: 'b', num: 1}
];
$scope.seeError = function(form,num,value){
delete form.val.$error.shouldBeNumber;
if (value && value.trim()==="") // omit as form will show an error
return;
if (num===1) // should be a number
if (isNaN(parseFloat(value)))
form.val.$error.shouldBeNumber = true;
};
});
I've changed a little of your code.
Please check this fiddle.
form validation
$scope.seeError = function(form,num,value){
form.val.$error.required = false;
form.val.$error.shouldBeNumber = false;
if (value===undefined) // omit as form will show an error
form.val.$error.required = true;
if (num===1&&isNaN(Number(value))) // should be a number
form.val.$error.shouldBeNumber = true;
};
I am not certain that my fiddle is proper to your purpose,
but I hope this can help you. :)
You should fix some bugs at first:
1) add this to the head-tag
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.js"></script>
2) and in the JS
var app = angular.module('app', ['ngMessages']);
then...
in html
<input type="text" name="val" ng-model="v.val" style="float:left" required ng-model-sniffer/>
<div ng-messages="formval.val.$error" class="errorMsg">
<div ng-message="shouldBeNumber">Value should be a number</div>
<div ng-message="required">Value cannot be empty</div>
</div>
in JS
app.directive('ngModelSniffer', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, model)
{
model.$validators.shouldBeNumber = function(modelValue, viewValue)
{
return !isNaN(parseFloat(viewValue));
};
}
};
});
when the list is initially displayed ng-change is not fired because
The ngChange expression is only evaluated when a change in the input
value causes a new value to be committed to the model.
It will not be evaluated:
if the value returned from the $parsers transformation pipeline has not changed
if the input has continued to be invalid since the model will stay null
if the model is changed programmatically and not by a change to the input value
you can use a custom filter to achieve the same behaviour
html
<div ng-repeat="v in vals | filter:checkError">
</div>
js
$scope.checkError = function (item) {
if(item.val is not number)
displayError = true;
return true;
};

Cannot get form data with ng-model

So I have two forms inside the same controller.
<form name="myForm" id="myForm" class="form-horizontal" ng-submit="saveMyForm(myForm)" >
<input type="text" id="name" ng-model="name" />
//...etc
</form>
and another form
<form name="passForm" id="passForm" ng-submit="savePassForm(passForm)" >
<input type="password" id="oldpassword" name="oldpassword" ng-model="oldpassword" >
<input type="password" id="pw1" name="pw1" ng-model="pw1" >
<input type="password" id="pw2" name="pw2" ng-model="pw2" pw-check="pw1" >
</form>
<div class="msg-block" ng-show="passForm.$error">
<span class="msg-error loginError" ng-show="passForm.pw2.$error.pwmatch">
Passwords don't match.
</span>
</div>
To check if passwords match I have this directive
app.directive('pwCheck', [function () {
return {
require: 'ngModel',
link: function (scope, elem, attrs, ctrl) {
var firstPassword = '#' + attrs.pwCheck;
elem.add(firstPassword).on('keyup', function () {
scope.$apply(function () {
var v = elem.val()===$(firstPassword).val();
ctrl.$setValidity('pwmatch', v);
});
});
}
}
}]);
So my first form works fine.
In my second form, the one for passwords, I cannot grab the passwords from the fields to send them to the server. I do
var passData = {
"oldpassword" : $scope.oldpassword,
"newpassword" : $scope.pw2
}
$scope.changepassword = function(form){
if(form.$valid) {
var promisePass = passwordFactory.changePass(passData);
promisePass.success(function (data, status) {
//handle
When I check my console, there are no data, passData is empty.
What am I missing here? Is it the fact that there are two forms inside the same controller? Does the directive messes things up?
Please help me fix this.
Thanks
I see a couple issues. First, the function name you have specified here:
<form name="passForm" id="passForm" ng-submit="savePassForm(passForm)" >
Does not match the name in your controller:
$scope.changepassword = function(form){
Second, you create your passData object outside of the submit function. This means it's going to have the values of the scope variables when the controller first loaded, likely undefined. Move the creation of passData inside your function and then it will be created with the current values of the scope variables.

Share validation state for multiple fields against the same validator

I will start out stating that I have searched google and SO and I have not found an answer for this specific situation. Yes, there are other posts that sound the same but are more based on a "MoreThan / LessThan" mentality. This does not following that mentality at all so please do not mark this as a duplicate referring to them.
Check out the Plunker Example
I am attempting to make sure the user does not enter an address that already exists else where on the page. To do this I need to validate all the address fields since different locations may have the same street address. I need the validator to set all the related fields to valid if any are invalid once the address has been fixed to not be a duplicate. Currently it only sets the last field modified to valid and leaves the rest as invalid.
Plunker example demonstrates what is happening. I have tried many different approaches such as iterating through all fields and setting them to prestine and untouched and then setting them to dirty and touched to trigger validations again but I am having no luck getting this working.
Validator
angular.directive('ruleFunc', ['$parse', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, $element, $attrs, $ngModel) {
var validatorName = $attrs.ruleName;
var validatorFunc = $attrs.ruleFunc;
if (!angular.isDefined(validatorName)) {
throw Error("rule-name attribute must be defined.");
}
if (!angular.isDefined(validatorFunc)) {
throw Error("rule-func attribute must be defined.");
}
// in real code I passing a function call with the model as the param
// this example demonstrated the issue I am having though
var expressionHandler = $parse(validatorFunc);
// had to use viewChangeListener because changes to the model
// were not showing up correctly in the actual implementation
$ngModel.$viewChangeListeners.push(function() {
var valid = expressionHandler($scope);
$ngModel.$setValidity(validatorName, valid);
});
});
Form
<form name="AddressForm" novalidate>
<h1>Address Form</h1>
<div style="margin:20px">
<input id="Street" type="text" name="Street" placeholder="Street" data-ng-model="ctrl.address.street" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.Street.$error.profileHasContact}}
<br />
<input id="City" type="text" name="City" placeholder="City" data-ng-model="ctrl.address.city" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.City.$error.profileHasContact}}
<br />
<input id="State" type="text" name="State" placeholder="State" data-ng-model="ctrl.address.state" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.State.$error.profileHasContact}}
<br />
<input id="Zip" type="text" name="Zip" placeholder="Zip" data-ng-model="ctrl.address.zip" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.Zip.$error.profileHasContact}}
<br />
<div ng-if="(AddressForm.Street.$error.profileHasContact
|| AddressForm.City.$error.profileHasContact
|| AddressForm.State.$error.profileHasContact
|| AddressForm.Zip.$error.profileHasContact)">Address already exists in Main Contacts</div>
<button type="submit">Submit</button>
</div>
I did find a post that was close enough that I could hack together a solution.
Form validation - Required one of many in a group
Here is the updated plunker
Updated Validator
directive('ruleFunc', ['$parse', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, $element, $attrs, $ngModel) {
var validatorName = $attrs.ruleName;
var validatorFunc = $attrs.ruleFunc;
var groupName = $attrs.ruleGroup;
if (!angular.isDefined(validatorName)) {
throw Error("rule-name attribute must be defined.");
}
if (!angular.isDefined(validatorFunc)) {
throw Error("rule-func attribute must be defined.");
}
if(angular.isDefined(groupName)){
// setup place to store groups if needed
if (!$scope.__ruleValidationGroups) {
$scope.__ruleValidationGroups = {};
}
var groups = $scope.__ruleValidationGroups;
// setip group if needed
if(!groups[groupName]){
groups[groupName] = {};
}
var group = groups[groupName];
// assign model to group
group[$attrs.ngModel] = {
model: $ngModel
}
}
function updateValidity(valid){
if(angular.isDefined(groupName)){
// set all models in group to same validity
for(var prop in group){
if(group.hasOwnProperty(prop)){
group[prop].model.$setValidity(validatorName, valid);
}
}
}
else
{
// set this model validity if not in group
$ngModel.$setValidity(validatorName, valid);
}
}
var expressionHandler = $parse(validatorFunc);
$ngModel.$viewChangeListeners.push(function() {
var valid = expressionHandler($scope);
updateValidity(valid);
});
}
};
}]);

Weird behavior with directives and angular form validation

I'm trying to create a custom directive to help me creating bootstrap forms and dealing with form validation using angular.
I've come up so far with the following directive:
directive('formGroup', ['$compile',function ($compile) {
var linker = function (scope, element, attrs) {
var fieldName = attrs.formGroup;
var formName = attrs.form;
if (formName === undefined || formName === null) {
//find form name
formName = $(element).closest('form').attr('name');
if (formName === undefined || formName === null) {
console.error('Could not find form name for error ' + fieldName);
}
}
var showErrorOn = formName + '.' + fieldName + '.$invalid';
$(element).addClass('form-group');
$(element).attr('ng-class', '{ \'has-error\' : ' + showErrorOn + ' }');
$(element).removeAttr('form-group');
$compile(element)(scope);
}
return {
restrict: 'A',
link: linker
}}]);
To be used with the following HTML:
<form name="testForm" novalidate>
<div class="row">
<div class="col-md-6" form-group="email">
<label>Email</label>
<input type="email" class="form-control" name="email" ng-model="email" required />
</div>
</div>
</form>
The point of the directive is a couple of things:
Finding the form name so I don't have to type it again for every field.
Add the class "form-group".
Add the class "has-error" whenever the field is invalid.
Everything seems to work just fine until I type a valid e-mail address and add a dot to the end (making the input invalid). After I do that, the input automatically clears out but I can't understand why.
Here's a fiddle: http://jsfiddle.net/kgwocdyu/2/
To reproduce type: "a#email" then type "." and the input will go blank.

how to identify whether the textbox value is change or not?

I have mentioned my HTML.
<div ng-controller="myCtrl">
<input type=text ng-model="name">
</div>
in my JS file
function myCtrl($scope){
$scope.name=//this field comes from DB say 'ABC'
}
My question :
When my html is get loaded, it will disply "ABC" in textbox. Which is fine.
Now if user change that name to "XYZ", So $scope.name value is "XYZ".
So I need to identify that input value is changed. Previous value is "ABC" and now the value is "XYZ" how we can figured it out that value is changed?
You can use the ngChange directive.
<input type="text" ng-model="name" ng-change="change()">
Then, in your controller, add a method called change to the scope:
$scope.change = function () {
console.debug('Changed to ' + $scope.name);
}
Do it like this:
function myCtrl($scope){
$scope.$watch('name', function(newValue, oldValue){
});
}
function myCtrl($scope){
var dbName= 'ABC'; //this field comes from DB say 'ABC'
$scope.name= dbName;
$scope.$watch('name', function(newValue) {
if(newValue != dbName) {
// do something
}
});
}

Resources