Incorporate directive's functionality into Angular form validation - javascript

I have two questions that are related:
First: I have the following directive, who's purpose is to validate whether an input[type=file] is valid or not, however I have no idea how it does it least of all, what the actual code means, here it is:
angular.module('sccateringApp')
.directive('fileModel', ['$parse', function($parse) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var model = $parse(attrs.fileModel);
var modelSetter = model.assign;
element.bind('change', function(){
scope.$apply(function(){
modelSetter(scope, element[0].files[0]);
});
});
}
};
}]);
Like I said, I have no idea what the above code actually does, the explanation I got from the forum where I copied that was that it validated an input type file. Is this correct? (So far I haven't been able to verify if it works or not since it doesn't work with the code I'm using at the moment to validate my forms).
Second: Having the form below, using angular form validation it doesn't allow the submit button to be clicked until the actual inputs inside the form match the validation rules (enter a name for the category, and the description should have a max length of 144 characters). I included the directive into the file input, however the actual ng-model for the form ignores the required in the input type file and just verifies the rules are met for the first two inputs.
Here is my form:
<form method="post" role="form" name="newCategoryForm" ng-submit="submitForm()" enctype="multipart/form-data" novalidate>
<div class="row">
<div class="row">
<div class="col s12">
<div input-field>
<input type="text" name="cat-name" id="cat-name" ng-class="{ 'ng-invalid' : newCategoryForm.catname.$invalid && !newCategoryForm.catname.$pristine }"
ng-model="catname" required>
<label>Nombre</label>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<div input-field>
<textarea class="materialize-textarea" name="cat-description" id="cat-description" length="144"
ng-model="catdescription" ng-maxlength="144" required></textarea>
<label>Descripción</label>
</div>
</div>
</div>
<div class="row">
<div class="col s12">
<h6>Imagen de Fondo</h6>
<div class="file-field input-field">
<div class="btn pink darken-2 waves-effect waves-light">
<span>Archivo</span>
<input type="file" name="cat-bgimg" id="cat-bgimg"
file-model="variable" required>
</div>
<div class="file-path-wrapper">
<input class="file-path" type="text">
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-large pink darken-2 waves-effect waves-light center-button" ng-disabled="newCategoryForm.$invalid">Crear Categoría</button>
</form>
The first two inputs get validated correctly, the third one (file input) doesn't and I don't really know why since the directive got included on the input (I know natively, ngModel doesn't validate file inputs).
Any ideas or suggestions of how can I fix this? I'm really new to Angular, and all the tutorials are pretty much useless. I come from 5 years of experience working on jQuery, and the transition to Angular hasn't been easy at all.

The directive posted above is used to make the submit get the data found in the <input type="file"></input>.
Also, a variable should be initialized in the controller so that the values found inside the form are copied to said variable, then this variable needs to be sent as a parameter inside the ng-submit="submitForm().
Example:
angular.module('sccateringApp')
.controller('newSubcategoryController', function (httpcalls, $scope) {
...
$scope.subcategory = [];
...
$scope.submitForm = function(subcategory){
...
$scope.request.insertSubcategory(subcategory);
}
});
Each ng-model inside the form would be:
<input type="text" ng-model="category.name">
So that the category variable found in the controller acquires that value.

Related

How to clear a form within an angular app that appears on click?

I have seen several tutorials on how to clear angular (version 1.4.0) forms, however.. none of them seem to work in my case. I am puzzled. The following form is display: none; to begin with, but comes into existence when the 'Add' button is clicked.
<div id="..." class="displayNone ...">
<form class="..." name="addFooForm">
<div class="...">
<div class="row">
<div class="col-md-offset-3 col-md-6">
<br>
<div class="form-group">
<label>Foos</label>
<input type="text" class="..." id="..." ng-model="foo.value">
</div>
</div>
</div>
</div>
<div class="...">
...
<input type="button" value="Cancel" class="..." ng-click="resetAddFooForm()">
</div>
</form>
</div>
And in my controller, I have the following.
$scope.resetAddFooForm = function () {
// XXX:
console.log('about to reset the form');
$scope.foo = {};
// $scope.addFooForm.$setValidity();
// $scope.addFooForm.$setPristine();
// $scope.addFooForm.$setUntouched();
};
But, although the console.log message is being displayed. The form field is not being cleared. I have have even tried doing it directly as follows.
$scope.resetAddFooForm = function () {
// XXX:
console.log('about to reset the form');
$scope.fooVal = '';
// $scope.addFooForm.$setValidity();
// $scope.addFooForm.$setPristine();
// $scope.addFooForm.$setUntouched();
};
.. with the above html modified as follows.
<div class="form-group">
<label>Foos</label>
<input type="text" class="..." id="foo" ng-model="fooVal">
</div>
But nothing seems to be working. Is it perhaps because the $scope is different due to the form being dynamically inserted? But then how do I tackle that?
I just want to be able to clear the fields (and also clear the angular properties like dirty/pristine, etc) of this dynamically generated form.
Update:
I was able to send this from the html back to the controller, and then use it do what I wanted. So it appears that the problem may actually be to do with differing scopes.
<div class="...">
...
<input type="button" value="Cancel" class="..." ng-click="resetAddFooForm(this)">
</div>
And then have this retrieved the controller.
$scope.resetAddFooForm = function (elem) {
elem.foo = {};
elem.$setValidity();
elem.$setPristine();
elem.$setUntouched();
};
Any hints? Why do I need to send this, when I should really be able to simply use $scope. Why doesn't that work?
Initially declare empty object on your controller
$scope.foo = {}
then try empty it on reset
You can manually reset fields value.

Validate a form field with a dynamically given name in angular

I create a form dynamically in the view by iterating through an object that has the different questions to be asked to the user. One of the attributes of every question is formFieldName which is a a random string I use to give each form field a different name.
<form name="includedForm.newRequestForm" class="form-horizontal" role="form" novalidate>
<div ng-if="message.question.attributes.structure.type == 'object'">
<div ng-repeat="(index,objField) in message.question.attributes.structure.properties">
<div ng-if="objField.type == 'array'" class="form-group" show-errors>
<label for="{{objField.formFieldName}}" class="control-label col-sm-6">{{objField.title}}
<br /><i><small>{{objField.description}}</small></i></label>
<div class="col-sm-6">
<select class="form-control" name="{{objField.formFieldName}}" multiple ng-model="objField.userValue" ng-required="objField.required">
<option ng-repeat="option in objField.items.enum" value="{{option}}">{{option}}</option>
</select>
</div>
</div>
<div ng-if="objField.type == 'boolean'" class="form-group" show-errors>
<label for="{{objField.formFieldName}}" class="control-label col-sm-6">{{objField.title}}</label>
<div class="col-sm-6">
<input class="form-control" name="{{objField.formFieldName}}" ng-model="objField.userValue" type="checkbox" ng-value="option" ng-checked="message.question.attributes" />
</div>
</div>
</div>
</div>
<div class="col-sm-12">
<button ng-click="markAsDone(message)" class="btn btn-primary">Done</button>
</div>
<form>
In the controller I'm able to get the formFieldName attribute but I can't figure out how to use it to do the validation.
var MarkAsDone = function(message) {
$scope.includedForm = {};
var formField = message.question.attributes.formFieldName;
if ($scope.includedForm.newRequestForm.{{formField}}.$valid){
//submit the form
}
}
to answer you question:
first, {{}} is === $scope so you don't use that anywhere other than HTML. You use $scope in your JS and {{}} in HTML which creates a pipe (2-way binding) so that $scope.variable.property has bidirectional binding to {{variable.property }} in HTML.
$scope.includeForm.email === {{ includeForm.email }} === ng-model="includeForm.email" === ng-bind="includeForm.email"
if you set anyone of those all are set so if you set $scope it will show up in HTML and obviously as user input gets captured it is already in $scope ... all connected
when attempting to get the value from HTML back into JS you would need create and set a $scope i.e so if you create $scope.dataModel.dataProperty and use that in ng-model=dataModel.dataProperty (example) you again have two way binding ... you don't need to do anything as angular is taking care of the data pipeline. So if you want to extract the value to var, which is probably a waste as the $scope is already set as soon as the user checks the box
var formField = $scope.dataModel.dataProperty;
// but like I said no need as $scope.dataModel.dataProperty; is your var
In JS if you want to use a dynamic property as an object property key you would place the dynamic value in [] e.g.
$scope.variable[dynamicProperty].method;
// you can set a static property as a key with dot notation i.e.
$scope.variable.staticProperty = val;
Hope that helps

Angular setPristine on ngBlur

I have a form with password field for which I implemented a password directive.
I currently implemented only 1 validation but I've a list of validations on the field.
I want them to displayed in either red or green depending on valid/invalid respectively - when the user starts typing into the field. If the users control goes out of the box AND all validations are passed, I want to set the field to valid and set it to pristine so that the validation list won't show up.
However if any of the validations fail, I want all of them to be seen even if the field is out of focus. Below is my snippet for the form group.
<div class="form-group">
<label for="inputPass" class="col-sm-3 control-label text-sm text-left">Password</label>
<div class="col-sm-9">
<input type="password" placeholder="Password"
class="form-control" ng-model="registerAccount.password"
required name="inputPass" id="inputPass"
password ng-blur="form.inputPass.$invalid ? return: form.inputPass.$setPristine">
<div ng-show="form.inputPass.$dirty" class="help-block">
<p class="text-danger" ng-show="form.inputPass.$error.required">
Password is required.
</p>
<p ng-class="form.inputPass.$error.invalidLength ? 'text-danger' : 'text-success'">
Password should be atleast 8 characters.
</p>
</div>
</div>
</div>
Following is my directive
'use strict';
angular.module('nileLeApp')
.directive('password', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function ($scope, $element, $attrs, ngModelCtrl) {
$scope.$watch($attrs.ngModel, function (value) {
if (value) {
var length = (value.length >= 8);
ngModelCtrl.$setValidity('invalidLength', length);
}
});
}
};
});
When the focus out of the field, the validation list is still showing up. I was expecting it to be hidden because the field is being set to pristine. Any ideas ? I wanted it to be similar to password field in https://dl.dropboxusercontent.com/u/636000/password_verification/index.html. As the user types the password, the validations are reflected in red/green.
You are not calling $setPristine method. Should be form.inputPass.$setPristine():
ng-blur="form.inputPass.$invalid ? return: form.inputPass.$setPristine()"
or cleaner variation:
ng-blur="form.inputPass.$valid && form.inputPass.$setPristine()"
You may not need to use a special directive:
<form name="testForm">
<input ng-model="testVal" name="testVal" required ng-minlength="3" ng-maxlength="10" ng-pattern="/^\w+$/">
<div ng-show="testForm.$invalid">
<div ng-class="{'errors':testForm.testVal.$error.required,'success':!testForm.testVal.$error.required}">Required</div>
<div ng-class="{'errors':testForm.testVal.$error.minlength,'success':!testForm.testVal.$error.minlength}">Minlength</div>
<div ng-class="{'errors':testForm.testVal.$error.maxlength,'success':!testForm.testVal.$error.maxlength}">Maxlength</div>
<div ng-class="{'errors':testForm.testVal.$error.pattern,'success':!testForm.testVal.$error.pattern}">Pattern</div>
</div>
</form>
This code does the same thing and given you the link: dl.dropboxusercontent.com/u/636000/password_verification/index.html
If you need to establish a more sophisticated checks, you can use the following directive use-form-error:
Live example on JsFiddle
<form name="testForm">
<input ng-model="testVal" name="testVal" required ng-minlength="3" ng-maxlength="10" ng-pattern="/^\w+$/" use-form-error="containWow" use-error-expression="testVal.indexOf('wow')>-1">
<div ng-show="testForm.$invalid">
<div ng-class="{'errors':testForm.testVal.$error.required,'success':!testForm.testVal.$error.required}">Required</div>
<div ng-class="{'errors':testForm.testVal.$error.minlength,'success':!testForm.testVal.$error.minlength}">Minlength</div>
<div ng-class="{'errors':testForm.testVal.$error.maxlength,'success':!testForm.testVal.$error.maxlength}">Maxlength</div>
<div ng-class="{'errors':testForm.testVal.$error.pattern,'success':!testForm.testVal.$error.pattern}">Pattern</div>
<div ng-class="{'errors':testForm.testVal.$error.containWow,'success':!testForm.testVal.$error.containWow}">It's 'wow' contains</div>
</div>
</form>

$location.path does not change the route after form submit

I have seen many issues regarding the $location.path in angular js on stackoverflow but none of them would solve the issue I have.
I have defined below routes in my app.js:
angular.module(
'hotPosts',
[ 'hotPosts.filters', 'hotPosts.services',
'hotPosts.directives',
'hotPosts.controllers', 'ngRoute', 'ngSanitize' ]).config(
[ '$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
$routeProvider.when('/hot-posts', {
templateUrl : 'gptarin/partials/hot-posts.jsp',
controller : 'ListPostsCtrl'
});
$routeProvider.when('/list-users', {
templateUrl : 'gptarin/partials/list-users.jsp',
controller : 'ListUserCtrl'
});
$routeProvider.when('/edit-user/:userId', {
templateUrl : 'gptarin/partials/edit-user.jsp',
controller : 'EditUserCtrl'
});
$routeProvider.when('/create-user', {
templateUrl : 'gptarin/partials/edit-user.jsp',
controller : 'EditUserCtrl'
});
$routeProvider.otherwise({
redirectTo : '/hot-posts'
});
$locationProvider.html5Mode(false);
} ]);
The list-users.jsp partial will show a list of users where I can select a record to update. When I click ong update button the ngRoute successfully routes the app to edit-user.jsp partial. However, when I click the Save button in that page, it does not change the route to "/list-users", even though I used the $location.path('/list-users') in Controller EditUserCtrl. It redirects me to "[app_url]/?".
Here is my controllers:
app.controller('EditUserCtrl', [
'$scope',
'$routeParams',
'UserListFactory',
'UserFactory',
'$location',
function($scope, $routeParams, UserListFactory, UserFactory,
$location) {
// callback for ng-click 'updateUser':
// force it to refresh in the client
$scope.saveUser = function() {
if (angular.isUndefinedOrNull($scope.user)) {
$scope.user = {};
UserListFactory.create($scope.user, function() {
$location.path('/list-users');
});
} else if (angular.isUndefinedOrNull($scope.user.id)) {
UserListFactory.create($scope.user, function() {
$location.path('/list-users');
});
} else {
UserFactory.update($scope.user, function() {
$location.path('/list-users');
});
}
};
// callback for ng-click 'cancel':
$scope.cancel = function() {
$location.path('/list-users');
};
if ($routeParams.userId !== undefined) {
$scope.user = UserFactory.show({
userId : $routeParams.userId
});
}
} ]);
The update function (saveUser) uses a service which makes Restful requests to the backend server via ngResource. The service works fine (all tests are passed).
I have enclosed the $location.path in the success callback function when calling the resource actions.
I have tried catching "$locationChangeSuccess" and "$locationChangeError" and saw that in this case a $locationChangeError is thrown but I do not know which promise object is rejected causing this error.
Any help is highly appreciated.
Edit:
Here is the edit-user.jsp partial
<div class="container-fluid">
<div class="panel panel-primary">
<div class="panel-heading">
<h2 class="panel-title label">Edit User</h2>
</div>
<div class="panel-body">
<form role="form" action="#" class="form-horizontal" ng-submit="saveUser()">
<div class="form-group col-sm-11">
<div class="form-group">
<label for="accountId" class="col-sm-1 control-label">G+
Id: </label>
<div class="col-sm-4">
<input type="text" id="accountId" class="form-control"
ng-model="user.gpId"></input>
</div>
<label for="accountName" class="col-sm-2 control-label">Name:
</label>
<div class="col-sm-5">
<input type="text" id="accountName" class="form-control"
ng-model="user.name"></input>
</div>
</div>
<div class="form-group">
<div class="col-sm-9">
<input type="checkbox" id="showPosts" class="form-control"
ng-model="user.listPosts">ShowPosts</input>
</div>
</div>
</div>
<div class="form-group col-sm-1">
<div class="row">
<div class="col-sm-offset-1 col-sm-12 pull-right">
<button type="submit" class="btn btn-primary">Save</button>
</div>
</div>
<div class="row">
<div class="col-sm-offset-1 col-sm-12 pull-right">
<button type="button" class="btn btn-primary" ng-click="cancel()">Cancel</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
Well After a few days of trying everything and not finding any help over internet I sort of fixed this issue.
I decided to share it with whoever reads this post so that it does not take several days of them as well.
When I traced my app more carefully I figured out that my cancel button worked just fine and $location.path was successfully sending me back to the /list-users page.
Further investigations showed that the difference between my Cancel and Save buttons was that the Cancel button uses the ng-click whereas I defined the type of my Save button to be "submit".
I then changed my html code so that instead of providing the function call saveUser() in ng-submit of the form, I used an ng-click for the Save button and changed its type to "button".
Here is the working version of my html partial. I did not need to change anything in js files.
<form role="form" action="#" class="form-horizontal">
<!-- ng-submit="saveUser()"> -->
<div class="form-group col-sm-11">
<div class="form-group">
<label for="accountId" class="col-sm-1 control-label">G+ Id: </label>
<div class="col-sm-4">
<input type="text" id="accountId" class="form-control" ng-model="user.gpId"></input>
</div>
<label for="accountName" class="col-sm-2 control-label">Name: </label>
<div class="col-sm-5">
<input type="text" id="accountName" class="form-control" ng-model="user.name"></input>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<label>
<input type="checkbox" id="showPosts" ng-model="user.listPosts"> Show Posts
</label>
</div>
<div class="col-sm-12">
<button type="button" class="btn btn-primary" ng-click="saveUser()">Save</button>
<button type="button" class="btn btn-primary" ng-click="cancel()">Cancel</button>
</div>
</div>
</div>
</form>
I still still do not now the mechanics of form submit in angular and why it causes one of the promise objects that the $location.path expects to fail (and hence causing an error in routing).
Any clarifying comment in this regard is much appreciated.
Well after more than a year of experience with Angular, I now know what was the problem in the first instance.
Reading Angular's documentation for ng-submit I found out this:
Additionally it prevents the default action (which for form means sending the request to the server and reloading the current page), but only if the form does not contain action, data-action, or x-action attributes.
Looking at the code it is evident that I mistakenly added an action attribute when I was defining the form element:
<form role="form" action="#" class="form-horizontal" ng-submit="saveUser()">
Removing it will fix the problem:
<form role="form" class="form-horizontal" ng-submit="saveUser()">
you can add $event.preventDefault(); before saveUser(); it will works.

AngularJS ng-include on ng-click

I would like to find a way to insert HTML (which is optimalized for the controller) into alert div. However I couldn't find a way to do it...
<script type="text/ng-include" id="login.html">
<form data-select="exeption" class="loginBox shadowed" onclick="event.stopPropagation();" novalidate name="login">
<h2>Login alert</h2>
<!--inputs and such, they need to be controlled by the controller-->
</form>
</script>
<script type="text/ng-include" id="bug.html">
<form data-select="exeption" class="bugBox shadowed" onclick="event.stopPropagation();" novalidate name="report">
<h2>Bug report</h2>
<!--inputs and such, they need to be controlled by the controller-->
</form>
</script>
This two templates should be evoked by the JS itself or by user. Those templates should get into this div, but I can't use innerHTML since in templates are some ng-models and such things...
<div id="alert" data-ng-click="empty()" data-ng-controller="alert" role="navigation"></div>
Usually what I do is use ng-if / ng-show .
I'm not sure I understood your request correctly, so I'll write a little example; let's say you have a simple login form:
<form>
<label>
username:
<input name="username" type="text" ng-model="username"/>
</label>
<label>
password:
<input name="password" type="password" ng-model="password"/>
</label>
<button type="submit" ng-click="login()">login</button>
<div class="message" ng-if="message">
</div>
</form>
Inside the controller:
$scope.username = '';
$scope.password = '';
$scope.message = '';
$scope.login = function() {
// login example function with ajax request and success/error promises
myLogin($scope.username, $scope.password)
.success(function() {
$scope.message = 'Logged in!';
})
.error(function(errorMessage) {
$scope.message = errorMessage;
})
}
This way your div is empty when $scope.message is empty and you can show it automatically just giving $scope.message a value.
If you need to have an ng-include, simplest thing you could do is to use it inside a div that you show when you need it:
<div ng-if="showMessage">
<div ng-include="template.html"/>
</div>
UPDATE: following my last example, if you wanted to include a different type of message for every situation, you could use multiple ngIf, including different template; example:
<div ng-if="showMessage">
<div ng-if="message.type == 'alert'" ng-include="'alert.html'"/>
<div ng-if="message.type == 'info'" ng-include="'info.html'"/>
<div ng-if="message.type == 'warning'" ng-include="'warning.html'"/>
<div ng-if="message.type == 'error'" ng-include="'error.html'"/>
</div>
This way you can also do an ngInclude for a login form, or another kind of popup.
UPDATE 2: same last example, but with another solution:
<div ng-if="showMessage">
<div ng-include="templatePath"/>
</div>
then you can give in the controller the whole path to the partial:
$scope.templatePath = 'alert.html';

Categories