Angularjs: Validating directive components with or without forms - javascript

I have a very simple modal which contains a text area and a button. I have created a directive for the text area as follows:
app.directive('vipTextArea', function() {
return {
restrict: 'E',
templateUrl: 'app/common/textArea-partial.html',
require: 'ngModel',
scope: {
textAreaLabel: '#',
textAreaName: '#',
textRequired: '#',
iconLabel: '#',
textAreaIcon: '#',
formName: '#',
value : '=ngModel'
}
}
});
And here is the template:
<div class="col-sm-8 noPadding">
<label>{{ textAreaLabel }}</label>
</div>
<div class="col-sm-4 noPadding rightAlign">
<vip-button-icon button-title="{{ iconLabel }}" icon-class="{{ textAreaIcon }}"></vip- button-icon>
</div>
<br/>
<textarea name="{{ textAreaName }}" ng-model="value" class="textAreaSize" ng-required=" {{ textRequired }}" ng-class="{ inputError: ({{ formName }}.{{ textAreaName }}.$error.required && {{ formName }}.{{ textAreaName }}.$dirty) }"></textarea>
<p class="errorMsg" ng-show="{{ formName }}.{{ textAreaName }}.$error.required && {{ formName }}.{{ textAreaName }}.$dirty">This field is required</p>
I am using this directive here:
<form name="viewCertificate">
<modal-header modal-title="View Certificate"></modal-header>
<div class="modal-body modalBodyHeight">
<vip-text-area form-name="viewCertificate" text-area-name="certificate" text-area-label="Certificate text below" text-required="true" icon-label="Upload" text-area-icon="upload" ng-model="certificate"></vip-text-area>
</div>
<div class="modal-footer">
<vip-button form-name="viewCertificate" button-title="Ok" button-size="small" button-color="blue" ng-click="ok()"></vip-button>
<vip-button form-name="viewCertificate" button-title="Cancel" button-size="small" button-color="white" ng-click="cancel()" class="buttonMargin"></vip-button>
</div></form>
All I am trying to achieve is validate the text area if it is empty. I am a newbie to angular, so please correct me if I am using this incorrectly. I am planning on using other directives like custom input fields, so need a common solution for all possible components.

You need to add your form to your directive scope like this:
.directive('vipTextArea', function() {
return {
require: ['^form','ngModel'],
restrict: 'E',
templateUrl: 'app/common/textArea-partial.html',
scope: {
textAreaLabel: '#',
textAreaName: '#',
textRequired: '#',
iconLabel: '#',
textAreaIcon: '#',
formName: '#',
value : '=ngModel'
},
link: function(scope, elem, attrs, ctrl){
scope.form = ctrl[0];
}
}
and then update your paragraph with the scope form:
<p ng-show="form.{{ textAreaName }}.$invalid && form.{{ textAreaName }}.$dirty">This field is required</p>

Related

ng-options as directive parameter

So, I have the same question asked on this question, sadly, no one has answered, so, here it goes again
I'm trying to create a select directive, where I can send the ng-options as a parameter.
This is my directive
app.directive('dropDown', function () {
return {
restrict: 'E',
template: function (element, attrs) {
return '<div class="col-sm-{{labelCol}} control-label">' +
'<label>{{label}}:</label>' +
'<div>' +
'<div clas="col-sm-{{controlCol}}">' +
'<label style="cursor:pointer" ng-show="!edit && forEdit" ng-disabled="disabled" ng-click="edit = true;">{{ngModel}}</label>' +
'<i ng-show="!edit && forEdit && !disabled" class="fa fa-pencil-square-o" style="cursor:pointer" aria-hidden="true" ng-click="edit = true;"></i>' +
'<select name="{{name}}" ng-change="ngChange" ng-blur="edit = false" ng-show="edit || !forEdit" class="form-control" ng-model="ngModel" ng-required="required" ng-options={{options}}/>' +
'</div>'
},
replace: true,
scope: {
ngModel: '=',
ngChange: '&',
label: '#',
labelCol: '#',
controlCol: '#',
type: '#',
name: '#',
disabled: '=',
required: '=',
forEdit: "=",
options: "#"
},
link: function (scope, element, attrs) { },
compile: function (element, attrs) {
if (!attrs.labelCol) attrs.labelCol = '4';
if (!attrs.controlCol) attrs.controlCol = '8';
if (!attrs.required) attrs.required = false;
if (!attrs.disabled) attrs.disabled = false;
if (!attrs.forEdit) attrs.forEdit = false;
attrs.edit = !attrs.forEdit;
}
}
})
And this is a implementation of the directive
<div class="row">
<drop-down ng-model="site" for-edit="true" label="Site Test" options="x.SITE_CODE as x.SITE_NAME for x in sites"></drop-down>
</div>
<div class="row">
<drop-down ng-model="site1" for-edit="true" label="Site Test" options="x for x in sites1"></drop-down>
</div>
And I'm getting the same response
Error: [$parse:syntax] Syntax Error: Token 'in' is an unexpected token
at column 3 of the expression [x in sites1] starting at [in sites1].
Error: [$parse:syntax] Syntax Error: Token 'as' is an unexpected token
at column 13 of the expression [x.SITE_CODE as x.SITE_NAME for x in
sites] starting at [as x.SITE_NAME for x in sites].
Any idea how to achieve my desired result?
Edit1:
If it help, here's the arrays that should be filling the selects
$scope.sites = JSON.parse("[{\"SITE_CODE\":\"1\",\"SITE_NAME\":\"SITE1\",},{\"SITE_CODE\":\"2\",\"SITE_NAME\":\"SITE2\"},{\"SITE_CODE\":\"3\",\"SITE_NAME\":\"SITE3\"},{\"SITE_CODE\":\"4\",\"SITE_NAME\":\"SITE4\"}]");
$scope.sites1 = ["SITE1", "SITE2", "SITE3", "SITE4"];
Edit 2:
Added the error for the more complex ng-options sentence
Edit 3:
So, I just realized that, I'm setting the ngOptions as a 2 way databinding field on the scope, as, it's not necesary, so I changed it from = to # and now, I'm getting another error message
Error: [$compile:ctreq] Controller 'select', required by directive 'ngOptions', can't be found!
Which, it's unreasonable, as I'm indeed setting the ngOptions, and I can verify it on the compile
Edit 4:
So, after some testing, I'm finally getting my controls rendered, but sadly, withouth values
The selects are clearly on the controller div
<div class="content" ng-controller="testController">
<div class="row">
<drop-down ng-model="site" for-edit="true" label="Site Test" options="x for x in sites"></drop-down>
</div>
<div class="row">
<drop-down ng-model="site" label="Site Test" options="x for x in sites"></drop-down>
</div>
<div class="row">
<drop-down ng-model="site1" for-edit="true" label="Site Test" options="x for x in sites1"></drop-down>
</div>
</div>
The controller indeed has this collections
app.controller('testController', ['$scope', function ($scope) {
$scope.sites = JSON.parse("[{\"SITE_CODE\":\"1\",\"SITE_NAME\":\"SITE1\",},{\"SITE_CODE\":\"2\",\"SITE_NAME\":\"SITE2\"},{\"SITE_CODE\":\"3\",\"SITE_NAME\":\"SITE3\"},{\"SITE_CODE\":\"4\",\"SITE_NAME\":\"SITE4\"}]");
$scope.sites1 = ["SITE1", "SITE2", "SITE3", "SITE4"];
}]);
But my rendered controls comes without any values
This is the rendered html for one of the controls
<div ng-model="site" label="Site Test" options="x for x in sites" class="ng-isolate-scope ng-valid">
<div class="col-sm-4 control-label"><label class="ng-binding">Site Test:</label></div>
<div class="col-sm-8">
<label style="cursor:pointer" ng-show="!edit && forEdit" ng-disabled="disabled" ng-click="edit = true;" class="ng-binding ng-hide"></label>
<i ng-show="!edit && forEdit && !disabled" class="fa fa-pencil-square-o ng-hide" style="cursor:pointer" aria-hidden="true" ng-click="edit = true;"></i>
<select name="" ng-change="ngChange" ng-blur="edit = false" ng-show="edit || !forEdit" class="form-control ng-pristine ng-valid ng-valid-required ng-touched" ng-model="ngModel" ng-required="required" ng-options="x for x in sites">
<option value="?" selected="selected"></option>
</select>
</div>
</div>
At least now I'm getting my controls rendered, now, on to show some values on them
The syntax ng-options="x in sites1" is incorrect.
In it's most simplest form it should be label for value in array:
ng-options="x for x in sites1"
Also check out the angular docs for ngOptions to see all of the permitted argument forms.
Well, after much testing, I finally am able to achieve my desired result.
I'll leave the directive here to whoever might want to use it, as it allows to
Set a desired options string
Set a property to show in case we store the complete object in the model
Disabled status, that will only show the model value
Inline edit of the value
var app = angular.module("app", []);
app.controller('testController', ['$scope', function($scope) {
$scope.sites = JSON.parse("[{\"SITE_CODE\":\"1\",\"SITE_NAME\":\"TEST 1\"},{\"SITE_CODE\":\"2\",\"SITE_NAME\":\"TEST 2\"},{\"SITE_CODE\":\"3\",\"SITE_NAME\":\"TEST 3\"},{\"SITE_CODE\":\"4\",\"SITE_NAME\":\"TEST 4\"}]");
$scope.sites1 = ["TEST 1", "TEST 2", "TEST 3"];
}]);
app.directive('dropDown', function() {
return {
restrict: 'E',
require: 'ngOptions',
template: function(element, attrs) {
return '<div>' +
'<div class="col-sm-{{labelCol}} control-label">' +
'<label>{{label}}:</label>' +
'</div>' +
'<div class="col-sm-{{controlCol}}">' +
'<label ng-show="!edit && forEdit">{{ngModel[textValue] !== undefined ? ngModel[textValue] : ngModel}}</label> ' +
'<span ng-show="!edit && forEdit && !disabled" class="fa fa-pencil-square-o" style="cursor:pointer" aria-hidden="true" ng-click="edit = true;">click here for edit</span>' +
'<select name="{{name}}" ng-change="ngChange" ng-blur="edit = false" ng-show="edit || !forEdit" class="form-control" ng-model="ngModel" ng-required="required" ng-options="{{options}}"/>' +
'</div>' +
'</div>';
},
replace: true,
scope: {
ngModel: '=',
ngChange: '&',
label: '#',
labelCol: '#',
controlCol: '#',
type: '#',
name: '#',
disabled: '=',
required: '=',
forEdit: "=",
options: "#",
items: "=",
textValue: "#"
},
compile: function(element, attrs) {
if (!attrs.labelCol) attrs.labelCol = '4';
if (!attrs.controlCol) attrs.controlCol = '8';
if (!attrs.required) attrs.required = false;
if (!attrs.disabled) attrs.disabled = false;
if (!attrs.forEdit) attrs.forEdit = false;
if (attrs.disabled)
attrs.forEdit = "true";
attrs.edit = !attrs.forEdit;
},
link: function(scope, element, attrs) {
},
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div class="" ng-controller="testController">
<div class="row">
<drop-down ng-model="site" for-edit="true" label="Site Test Inline" text-value="SITE_NAME" options="x.SITE_NAME for x in items" items="sites"></drop-down>
<drop-down ng-model="site1" for-edit="false" label="Site Test Select" options="x for x in items" items="sites1"></drop-down>
<drop-down ng-model="site1" disabled="true" label="Site Test Disabled" options="x for x in items" items="sites1"></drop-down>
</div>
</div>
</div>

I pass scope values to my directive but it's undefined

I have a problem to pass into my directive 3 parameters from the scope of my controller.
See the directive :
angular.module('app.administration').directive('wcModuleForm', function()
{
return {
restrict: 'E',
scope: {
'module': '=',
'applications': '=',
'standards': '='
},
templateUrl: 'app/administration/directives/module/wc-module-form.tpl.html',
link: function(scope, form)
{
form.bootstrapValidator({...});
}
};
});
And in the html i call the directive :
<wc-module-form
module="moduleForm"
applications="applications"
standards="standards">
</wc-module-form>
The 3 values moduleForm, applications and standards are in my scope controller.
But when i test in the template of the directive, all values are undefined, i don't understand why?
<h4>module : {{(module === undefined) ? 'undefined' : 'defined'}}</h4> -> **undefined**
<h4>applications : {{(applications === undefined) ? 'undefined' : 'defined'}}</h4> -> **undefined**
<h4>standard : {{(standards === undefined) ? 'undefined' : 'defined'}}</h4> -> **undefined**
when i put a watch on 'module' in the link function of the directive with a console.log, nothing at all.
the template of the directive is a bootstrap modal which contain a form to add or edit a module :
<div class="modal fade" id="moduleFormModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title">{{ (module.id !== undefined) ? "Ajout d'un module" : "Edition d'un module" }}</h4>
<h4>module : {{(module === undefined) ? 'undefined' : 'defined'}}</h4>
<h4>applications : {{(applications === undefined) ? 'undefined' : 'defined'}}</h4>
<h4>standard : {{(standards === undefined) ? 'undefined' : 'defined'}}</h4>
</div>
<div class="modal-body">
<form id="movieForm" method="post" class="ng-pristine ng-valid bv-form" novalidate="novalidate">
<button type="submit" class="bv-hidden-submit" style="display: none; width: 0px; height: 0px;"></button>
<div class="form-group">
<label class="control-label">Nom</label>
<input type="text" class="form-control" name="name" ng-model="module.name">
</div>
<div class="form-group">
<label class="control-label">Pictogramme</label>
<input type="text" class="form-control" name="picto" ng-model="module.picto">
</div>
<div class="form-group">
<label class="control-label">Couleur</label>
<input type="text" smart-colorpicker class="form-control" name="color" ng-model="module.color">
</div>
<div class="form-group">
<div class="selectContainer">
<label class="control-label">Application</label>
<select class="form-control" name="application" ng-model="module.application_id">
<option ng-repeat="application in applications" value="application.id">{{ application.name }}</option>
</select>
</div>
</div>
<div class="form-group">
<div class="selectContainer">
<label class="control-label">Standard</label>
<select class="form-control" name="standard" ng-model="module.standard_id">
<option ng-repeat="standard in standards" value="standard.id">{{ standard.name }}</option>
</select>
</div>
</div>
<div class="form-actions">
<div class="row">
<div class="col-md-12">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary"><i class="fa fa-save"></i> Sauvegarder</button>
</div>
</div>
</div>
</form>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
and the controller :
'use strict';
angular.module('app.administration')
.controller('AdministrationCtrl', ['$scope', '$rootScope', '$http', 'APP_CONFIG', function($scope, $rootScope, $http, APP_CONFIG)
{
/**
* différentes applications existantes
* #type [{object}]
*/
$scope.applications = [];
/**
* différentes standards existantes
* #type [{object}]
*/
$scope.standards = [];
/**
* module pour le formulaire
* #type {{}}
*/
$scope.moduleForm = {id: 5,
name: 'Fonction',
standard_id: 2,
application_id: 1,
picto: 'fa fa-gears',
color: '#F20E0E'};
}]);
So, if you have an idea, thanks in advance.
it's good i find a solution :
see the directive :
angular.module('app.administration').directive('wcModuleForm', function()
{
return {
restrict: 'E',
scope: {
module: '=',
applications: '=',
standards: '='
},
templateUrl: 'app/administration/directives/module/wc-module-form.tpl.html',
link: function($scope, form)
{
form.bootstrapValidator({...});
}
};
});
i add a '$' to scope and it's good :)
but i don't understand why it's work now so if someone know why, he will can explain it to me. thanks :)
Is this what you are looking for?
(function () {
'use strict';
angular
.module('app.administration')
.directive('wcModuleForm', wcModuleForm);
wcModuleForm.$inject = [];
function wcModuleForm() {
return {
restrict: 'E',
scope: {
module: '=',
applications: '=',
standards: '='
},
controller: function ($scope) {
form.bootstrapValidator({
module: $scope.module,
applications: $scope.applications,
standards: $scope.standards
});
},
template: '<div>{{ module }}{{ applications }}{{ standards }}</div>'
}
}
}());
Try with removing the quotes from your scope attributes in wcModuleForm directive.
Like this:
return {
restrict: 'E',
scope: {
module: '=',
applications: '=',
standards: '='
},
templateUrl: 'app/administration/directives/module/wc-module-form.tpl.html',
link: function(scope, form)
{
form.bootstrapValidator({...});
}
};
If it doesn't work, maybe module is a predefined attribute. Try using another name. Like <directive data-application="..."></directive> will not work because data is reserved.

Bind inner directive to its parent form

Having a directive, my-drtv , with <input ng-required="true"> within <div ng-form="myForm"> .
Currently the inner ng-required isn't bind to its form (myForm) , how could I set to my-drtv the same form as its parent ?
(so that in the initial state myForm.$valid should be false)
JS:
var myAppModule = angular.module('myAppModule', []).
directive('myDrtv',function(){
return {
restrict: 'A',
template: '<div><input ng-required="true"></div>'
}
});
HTML:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.9/angular.min.js"></script>
<html ng-app="myAppModule">
<body>
<div ng-form="myForm">
<div my-drtv>
</div>
<br>
{{myForm.$valid}}
</div>
</body>
</html>
In order to have validation behaviour input fields must have both ngModel and name attributes. Then it will be registered as control under form controller. So you could do
.directive('myDrtv', function() {
return {
restrict: 'A',
template: '<div><input name="inp" ng-model="inp" ng-required="true"></div>'
}
});
or better pass model and name from outside, then directive will become much more reusable:
.directive('myDrtv', function() {
return {
restrict: 'A',
scope: {
model: '=ngModel'
},
template: function(element, attrs) {
return '<div><input name="' + attrs.name + '" ng-model="model" ng-required="true"></div>';
}
}
});
and use it like this:
<div ng-form="myForm">
<div my-drtv name="some" ng-model="someField"></div>
</div>
Demo: http://plnkr.co/edit/FV7jeiuLvqPmIlVyyXWr?p=preview

Cannot get the variable in ng-model in directive template

I have a directive for comment input as follows, I want to reset the form after the user posted the comment. However, I can not get the newComment value of ng-model in link function. How to solve such problem.
commenting.html
<div class="directive__commenting">
<div class="col-content">
<h4>{{title}}({{count}})</h4>
<form class="form" name="commentForm" ng-submit="createComment(commentForm, newComment)" ng-if="isLoggedIn()">
<div class="form-group">
<textarea class="form-control" name="newComment" rows="3" placeholder="你怎么看?" ng-model="newComment" required></textarea>
</div>
<div class="right">
<span id="count-words" ng-class="{'red': isWordsExceeded(newComment)}">{{140 - newComment.length}}</span>
<button class="send-button btn btn-default" type="submit" ng-disabled="isWordsExceeded()">{{btnActionTitle}}</button>
</div>
</form>
</div>
</div> <!-- #create-comment -->
commenting.directive.js
'use strict';
angular.module('myapp')
.directive('commenting', function (Auth) {
return {
templateUrl: 'components/directive/commenting/commenting.html',
restrict: 'EA',
scope: {
title: '=',
count: '=',
btnActionTitle: '=',
action: '='
},
link: function (scope) { //, element, attrs
scope.isLoggedIn = Auth.isLoggedIn;
scope.isWordsExceeded = function (newComment) {
return newComment && newComment.length > 140;
}; //- isWordsExceeded
scope.createComment = function (form, newComment) {
scope.action(form, newComment)
.then(function () { //success
// clear the form, however here scope.newComment is undefined
})
.catch(function () { //fail
});
};
}
};
});
The directive is added in a html file as follows.
<div class="row" id="create-comment">
<commenting title="'Comments'" count="model.comments.length" btn-action-title="'Submit comment'" action="createComment"></commenting>
</div> <!-- #create-comment -->
Try to access through scope like
scope.newComment

How data is passed from custom directive view to controller?

code for the directive template
//"textBox.html"
<div class="well">
<label class="control-label">Text</label>
<div class="controls">
<input id="label" type="text" class="txt span3" ng-model="label" placeholder='Label for text field...'>
<input type="text" class="span3" ng-model="value" placeholder='Default value...'>
<input type="text" class="span3" ng-model="helpText" placeholder="Help text...">
<input type="checkbox" class="span1" ng-model="required" ng-true-value="true" ng-false-value="false">Required
<input type="checkbox" class="span1" ng-model="advanced" ng-true-value="true" ng-false-value="false">Advanced?
<img src="../../images/bullet_cross.png" alt="Remove" style="cursor: pointer" id="text" border="0" ng-click="deleteField($event)">
</div>
</div>
directive is using like this in main html page
//"algorithm.html"
<text-box></text-box>
controller for the custom directive
//"controller.js"
var algorithm = angular.module('algorithmController',[]);
/***********directive to render text field***********/
algorithm.directive('textField' , function(){
return{
restrict: 'E',
templateUrl: '../partials/algorithm/textBox.html',
require: 'ngModel',
replace: true,
link: function(scope, iElement, iAttrs, ngModelCtrl) {
// how should i get updated data(i.e. if user change after typing) over here entered by user??
}
};
});
You can create an isolate scope using the '=' syntax, which will create two way binding to your controller and the directive. You don't even necessarily need ngModel required in your directive.
.directive("textField", function () {
return {
restrict : "E",
template : "<input type='text' ng-model='val'/>",
scope : {
val : "="
}
};
});
Here is a very simple example doing what you requested;
http://jsfiddle.net/smaye81/xaohrv53/2/

Categories