AngularJS Directive for twitter bootstrap form-group - angularjs

I've been playing with angular lately, so far so good, but im struggling with directives.
I'm trying to create a directive that generates the html mark up for a standard bootstrap form group with its corresponding validation messages.
So basically I'm trying to convert this:
<form-group label="Password">
<input type="password" data-ng-model="vm.password" name="password" id="password" class="form-control form-control-validate"
required data-ng-minlength="6"
data-required-error="Password is required" data-minlength-error="Your password must have at least 6 characters" />
</form-group>
into this:
<div class="form-group" data-ng-class="{'has-error': invalid}">
<label for="password" class="col-md-2 control-label">Password</label>
<div class="col-md-10">
<input data-ng-model="vm.password" type="password" id="password" name="password" class="form-control"
required data-ng-minlength="6"/>
<div data-ng-show="changePasswordForm.$dirty && changePasswordForm.oldPassword.$invalid">
<label data-ng-show="changePasswordForm.oldPassword.$error.required" class="label label-danger">
Password is required
<br />
</label>
<label data-ng-show="changePasswordForm.oldPassword.$error.minlength" class="label label-danger">
Your password must have at least 6 characters
</label>
</div>
</div>
</div>
So far this is what I have:
app.directive('formGroup', function () {
return {
templateUrl: 'app/directives/formGroup.html',
restrict: 'E',
replace: true,
transclude: true,
require: "^form",
scope: {
label: "#",
},
link: function (scope, element, attrs, formController) {
var input = element.find(":input");
var id = input.attr("id");
scope.for = id;
var inputName = input.attr("name");
// Build the scope expression that contains the validation status.
// e.g. "form.example.$invalid"
var inputInvalid = [formController.$name, inputName, "$invalid"].join(".");
scope.$parent.$watch(inputInvalid, function (invalid) {
scope.invalid = invalid;
});
}
};
});
formGroup.html:
<div class="form-group" ng-class="{ 'has-error': invalid }">
<label class="col-md-2 control-label" for="{{for}}">{{label}}</label>
<div class="col-md-10">
<div data-ng-transclude=""></div>
</div>
</div>
This sets correctly the bootstrap class "has-error" to the form-group if the input is invalid.
Now I want to add validation messages, and I couldn't find a way that works. This is what I have:
app.directive('formControlValidate', function () {
return {
templateUrl: "app/directives/formControlValidate.html",
restrict: 'C',
require: ["^form", "ngModel"],
scope: { },
transclude: true,
//replace: true,
link: function (scope, element, attrs, controls) {
var form = controls[0];
var inputName = attrs.name;
var inputErrors = [form.$name, inputName, "$error"].join(".");
scope.$parent.$watch(inputErrors, function (newValue) {
if (newValue) {
scope.errors = [];
angular.forEach(newValue, function (value, key) {
var error = attrs[key + 'Error'];
if (value && error) {
scope.errors.push(error);
}
});
}
}, true);
}
};
formControlValidate.html:
<div class="controls" ng-transclude></div>
<div data-ng-repeat="error in errors">
<div class="label label-danger">
{{error}}
</div>
</div>
But this doesn't work. I'm randomly changing parameters in both directives but can't figure out what how to make it work.
Any ideas or improvements would be greatly appreciated.
Thanks!

UPDATE: this is my latest gist (angular 1.3): https://gist.github.com/lpsBetty/3259e966947809465cbe
OLD solution:
I tried something similiar, maybe this link can help you too: http://kazimanzurrashid.com/posts/create-angularjs-directives-to-remove-duplicate-codes-in-form
This was my solution. I don't know why but I had to use form.$dirty, it was not possible to use input.$dirty..
(and I use angular-translate)
HTML:
<form-group input="form.password">
<input type="password" class="form-control" placeholder="{{ 'user.password' | translate }}" required
name="password" ng-model="user.password" />
</form-group>
Directive:
app.directive('formGroup', function ($parse) {
return {
restrict: 'E',
require: '^form',
transclude: true,
replace: true,
scope: {
cssClass: '#class',
input: '='
},
template: '<div class="form-group" ng-class="{\'has-error\':hasError, cssClass:true}">'+
'<div ng-transclude></div>' +
'<div ng-show="hasError">' +
'<span ng-repeat="(key,error) in input.$error" class="help-block"' +
'ng-show="input.$error[key]">{{\'form.invalid.\'+key | translate}}</span>' +
'</div>' +
'</div>',
link: function (scope, element, attrs, ctrl) {
var form = ctrl;
var input = attrs.input;
scope.$parent.$watch(input+'.$invalid', function (hasError) {
scope.hasError = hasError && form.$dirty;
});
}
};
});

Related

disable submit button in directive

I simply want to add the class disable when form.$valid is false. The first submit button (not directive) obiouvsly works, but I don't know how to make visible the form state inside the directive. I cannot hardcode the form name in directive, it should be reusable in several forms..
<form id="tA" name="form" ng-controller="ctrl" ng-submit="form.$valid && save()" novalidate>
<input type="text" class="form-control" placeholder="title" name="name" ng-model="model.name" required ng-required="true">
<button class="btn btn-default" ng-class="{'disabled' : !form.$valid}" class="btn btn-default">Submit</button>
<button-raised disabled="!form.$valid">directive Submit</button-raised>
app.directive('buttonRaised', function() {
return {
restrict: 'E',
template: '<button class="mdl-button" ng-class="ngClass" ng-transclude></button>',
scope: {
ngModel: '='
},
transclude: true,
link: function($scope, el, $attrs,formCtrl) {
console.log(formCtrl)
$scope.ngClass = {
// 'disabled': $scope.$parent.formCtrl.$valid,
};
}
};
});
one way to do this is to pass your boolean as a parameter of your directive :
app.directive('buttonRaised', function() {
return {
restrict: 'E',
template: '<button class="mdl-button" ng-disabled="disabled" ng-class="ngClass" ng-transclude></button>',
scope: {
ngModel: '=',
disabled: '='
},
transclude: true,
link: function($scope, el, $attrs,formCtrl) {
console.log(formCtrl)
$scope.ngClass = {
// 'disabled': $scope.$parent.formCtrl.$valid,
};
}
};
});
You can change the directive and html as per this working this
The directive
app.directive('buttonRaised', function() {
return {
restrict: 'E',
template: '<button class="mdl-button" ng-class="{\'disabled\': disableButton}" ng-transclude></button>',
scope: {
disableButton: '='
},
transclude: true,
link: function($scope, el, $attrs,formCtrl) {
console.log(formCtrl)
}
};
});
The html
<form id="tA" name="form" ng-controller="ctrl" ng-submit="form.$valid && save()" novalidate>
<input type="text" class="form-control" placeholder="title" name="name" ng-model="model.name" required ng-required="true">
<button class="btn btn-default" ng-class="{'disabled' : !form.$valid}" class="btn btn-default">Submit</button>
// directive taking parameter disable-button
<button-raised disable-button="!form.$valid">directive Submit</button-raised>

ng-messages not showing in directive

I'm trying to re-use code as much as I can so I'm wrapping ng-messages in a directive. However, the messages are not shown..
<form name="elementForm" novalidate>
<md-input-container>
<label>Category</label>
<input ng-required="true" ng-model="item.category" name="category" type="text">
<administration-input-validation control="category" formname="elementForm"></administration-input-validation>
</md-input-container>
</form>
Directive:
angular.module('app.analytics')
.directive('administrationInputValidation', function () {
return {
template:
'<div ng-messages>' +
'<div ng-message="required">This is required</div>' +
'</div>',
restrict: 'E',
replace: true,
require: '^form',
scope: {
control: '=',
formname: '='
},
link: function(scope, element, attrs) {
var formStr = attrs.formname + "." + attrs.control;
element.attr("ng-messages", formStr + "." + "$error");
}
};
});
I also tried hardcoding but it's still not showing
<div ng-messages="elementForm.category.$error">
UPDATE
Binding works now, but it's not showing the messages

Get access to form controller validation errors in a custom directive

I have a directive that wraps a form element with some inputs. One of the options is passing in a formName. Usually, with a form with the example name of myForm, to show an error you would do something like myForm.firstName.$error.required.
But, how do I get access to the errors when the form name is dynamically being passed in to the directive?
example usage
<my-custom-form formName='myForm' formSubmit='parentCtrl.foo()'></my-custom-form>
directive
angular.module('example')
.directive('myCustomForm', [
function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'myCustomForm.directive.html',
scope: {
fornName: '#',
formSubmit: '&'
},
require: ['myCustomForm', 'form'],
link: function(scope, element, attrs, ctrls) {
var directiveCtrl = ctrls[0];
var formCtrl = ctrls[1];
scope.data = {};
scope.hasError = function(field) {
// how do i show the errors here?
};
scope.onSubmit = function() {
scope.formSubmit();
};
}
};
}]);
template
<form name="{{ formName }}" ng-submit="onSubmit()" novalidate>
<div class="form-group" ng-class="{'is-invalid': hasError('fullName') }">
<input type="text" name="fullName" ng-model="data.full_name" required />
<div ng-show="hasError('fullName')">
<p>How do I show this error?</p>
</div>
</div>
<div class="form-group" ng-class="{'is-invalid': hasError('email') }">
<input type="text" name="email" ng-model="data.email" ng-minlength="4" required />
<div ng-show="hasError('email')">
<p>How do I show this error?</p>
</div>
</div>
<button type="submit">Submit</button>
</form>
I think the only problem with your code is that the directive requires itself, I don't think that will work. Just removing the myCustomForm from the require works fine.
To check if the field has errors, you just need to check if the $error object in the form controller is empty.
require: ['form'],
link: function(scope, element, attrs, ctrls) {
var formCtrl = ctrls[0];
scope.data = {};
scope.hasError = function(field) {
// Field has errors if $error is not an empty object
return !angular.equals({}, formCtrl[field].$error);
};
Plunker

Angular directive: How to make assign value to parent scope?

I have a controller AppCtrl as
scope.transaction = {}
The index looks like
<div class="control-group">
<label class="control-label">Date</label>
<div class="controls">
<div class="control-group input-append date form_datetime">
<date-time-picker data-ng-model="transaction.date"></date-time-picker>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">Amount</label>
<div class="controls">
<div class="input-append">
<input type="text" name="transactionAmount" ng-model="transaction.amount" required>
</div>
and my custom directive looks like
angular.module('customDirectives', []).directive('dateTimePicker', function() {
return {
restrict: 'E',
replace: true,
scope: {
transaction['date']: '=' # COMPILATION ERROR HERE
},
template: '<div class="control-group input-append date form_datetime">'+
'<input type="text" readonly data-date-format="yyyy-mm-dd hh:ii" name="transactionDate" ng-model="transaction.date" data-date-time required>'+
'<span class="add-on"><em class="icon-remove"></em></span>'+
'<span class="add-on"><em class="icon-th"></em></span>'+
'</div>',
link: function(scope, element, attrs, ngModel) {
var input = element.find('input');
element.datetimepicker({
format: "yyyy-mm-ddThh:ii:ssZ",
showMeridian: true,
autoclose: true,
todayBtn: true,
pickerPosition: 'bottom-left'
});
element.bind('blur keyup change', function(){
console.log('binding element');
scope.$apply(date);
});
function date() {
console.log('setting date',input.val());
scope.ngModel = input.val();
}
date(); // initialize
}
}
});
I want to assign the date value from my directive to $scope.transaction.date but it is failing as compilation error, how can I achieve this?
scope: {
transaction['date']: '=' # COMPILATION ERROR HERE
},
Should be
scope: {
transactionDate: '='
},
And
<date-time-picker data-ng-model="transaction.date"></date-time-picker>
Should be
<date-time-picker transaction-date="transaction.date"></date-time-picker>
Then within your directive you can call scope.transactionDate = myValue;
within scope.$apply();
EDIT: If you want to use ng-model within your directive then you can use
....
restrict: 'E',
require: '?ngModel',
....
And
controller.$setViewValue(value); //this will in directive code where you want set the value of the ng-model bound variable.
In Html
<date-time-picker data-ng-model="transaction.date"></date-time-picker>

Angular shows only one custom element

I try to create custom html form elements with angular.js. The idea is that I have one maintemplate for the formfield-layout and paste in the template needed for textfield, datefield or whatever.
var app = angular.module("myApp", []);
app.controller("MainCtrl", function ($scope) {
$scope.window = window;
});
// create general formfield-layout
function buildFormTemplate(innerTemplate) {
var t = '<div class="control-group">'
+ '<label class="control-label" for="{{x.id}}">{{x.label}}';
t += '<span ng-show="x.required" class="required">*</span>',
t += '</label><div class="controlls">';
t += innerTemplate;
t += '</div>';
t += '</div>';
return t;
}
app.directive("textfield", function() {
return {
restrict: "E",
scope: {},
replace: true,
template: buildFormTemplate('<input id="{{x.id}}" type="text" name="{{x.name}}" value="{{x.value}}" />'),
link: function(scope, elm, attrs) {
scope.x = attrs;
}
};
});
This code works like I expect, but if I have multiple textfield-elements only one is rendered. The other textfield-elements are gone.
<textfield id="myLabel" label="label1" name="mytext1" value="with label"/>
<textfield id="anotherOne" label="label2" name="mytext2" value=""/>
renders
<div class="control-group" id="myLabel" label="label1" name="mytext1" value="with label">
<label class="control-label ng-binding" for="myLabel">
label1<span ng-show="x.required" class="required" style="display: none;">*</span></label>
<div class="controlls">
<input id="myLabel" type="text" name="mytext1" value="with label">
</div>
</div>
The second textfield is gone. What am I missing?
Close the tags and it works fine
<textfield id="anotherOne" label="label2" name="mytext2" value=""></textfield>
DEMO: http://plnkr.co/edit/ky0Tpt8qudSLSGho2QAk?p=preview

Resources