Wrap exist directive in angular - angularjs

I am using angular js in my application, and I have a form which contains a lot of fields:
<form>
<div class="form-group">
<label for="idname">Name:</label>
<input type="name" class="form-control" id="idname" ng-model="name" name="name">
</div>
<div class="form-group">
<label for="idemail">Email Address:</label>
<input type="password" class="form-control" id="idemail" ng-model="email" name="email">
</div>
<div class="form-group">
<label for="idtype">Choose Type:</label>
<select class="form-control" id="idtype" name="type">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
There are almost 60+ fileds, and most of the markups is bootstrap styles related, so I want to simplify the markups, like this:
<form>
<input label="Name:" type="text" model="name">
<input label="Email Address:" type="email" model="email">
<select label="Choose Type:" model="type">
....
</select>
<button type="submit" class="btn btn-default">Submit</button>
</form>
I want to add some extra attributes like label and model for the form inputs, then I will generate the label and input element and wrap them with the bootstrap styles. Also I want the third directives like min-length or anything else still take effect.
I tried to create the directive like this:
.directive('label', function() {
return {
priority:1,
restrict: 'A',
templateUrl: "field.html",
replace:true
};
})
However it does not work as expected, the custom-ed element like
<input label="Name:" type="text" model="name">
are not inserted into the template, and the label is not generated:
<div class="form-group">
</div>
I think the compile and link may be necessary, but I have no idea how to implement them.
This is the demo.
Is it possible to fix it?

Replace is deprecated since Angular 1.3.
But it works: https://jsfiddle.net/basslagter/yh0qdbhL/1/
What you can do (because of the deprecation) is make a custom element:
<custom-label label="Name:" type="text" model="name"></custom-label>
Defined as:
var myApp = angular.module('myApp', []);
myApp.directive("customLabel", function () {
return {
restrict: "E",
template: '<label>The label was: {{vm.label}}</label>',
controllerAs: 'vm',
scope: { label: '#' },
bindToController: true,
controller: function(){}
};
});
See: https://jsfiddle.net/basslagter/yh0qdbhL/

Related

Programmatically apply classes to angularjs form

I have an angular directive to facilitate the adding of bootstrap classes at runtime to streamline the need to apply "form-group", "control-label" and "form-control". This works perfectly as long as I don't try to include multiple levels at once, meaning that I cannot seem to make this work to include multiple divs in "form-group. I have attached the code, raw HTML and processed HTML for review to see if someone might have some insight into how I might modify to make this tool meaningful.
Directive:
(function () {
"use strict";
angular.module("ppac")
.directive("formfix", formfix);
var addClasses = function (element) {
var input = element.querySelector("input, textarea, select");
var type = input.getAttribute("type");
if (type !== "checkbox" && type !== "radio") {
input.classList.add("form-control");
}
var label = element.querySelector("label");
label.classList.add("control-label");
element.classList.add("form-group");
};
function formfix() {
return {
restrict: "A",
link: function (scope, element) {
addClasses(element[0]);
}
}
}
})();
HTML Form:
<form name="contactForm" ng-submit="model.submit()" novalidate>
<div class="form-horizontal">
<div formfix>
<label for="firstName" class="col-md-2">First Name</label>
<div class="col-md-3">
<input type="text" name="firstName" id="firstName" ng-model="model.contact.firstName" />
</div>
<label for="lastName" class="col-md-2">Last Name</label>
<div class="col-md-3">
<input type="text" name="lastName" id="lastName" ng-model="model.contact.lastName" />
</div>
</div>
</div>
</form>
Processed HTML:
<form name="contactForm" ng-submit="model.submit()" novalidate="" class="ng-pristine ng-valid">
<div class="form-horizontal">
<div formfix="" class="form-group">
<label for="firstName" class="col-md-2 control-label">First Name</label>
<div class="col-md-3">
<input type="text" name="firstName" id="firstName" ng-model="model.contact.firstName" class="ng-pristine ng-valid form-control ng-empty ng-touched">
</div>
<label for="lastName" class="col-md-2">Last Name</label>
<div class="col-md-3">
<input type="text" name="lastName" id="lastName" ng-model="model.contact.lastName" class="ng-pristine ng-untouched ng-valid ng-empty">
</div>
</div>
</div>
</form>
You can use ng-class to add class dynamically as the document says
The ngClass directive allows you to dynamically set CSS classes on an
HTML element by databinding an expression that represents all classes
to be added.
Reference
https://docs.angularjs.org/api/ng/directive/ngClass

AngularJS How to manipulate array of objects if each element is stored inside a stand alone directive?

I've created a custom directive to serve as a template to show some information using ng-repeat. I also need to do some manipulation on the array that being looped. My question is how can I remove or add elements to that array, if each element is inside a separate scope inside a stand-alone directive? Without the use of the directive the task is easy:
<div class="well" ng-repeat="dat in data">
<form novalidate>
<div class="form-group">
<label for="name">name:</label>
<input type="text" class="form-control" id="name" ng-model="dat.name" ng-disabled="enableEdit">
</div>
<div class="form-group">
<label for="balance">balance:</label>
<input type="text" class="form-control" id="balance" ng-model="dat.balance" ng-disabled="enableEdit">
</div>
<div class="form-group">
<label for="fruit">favorite Fruit:</label>
<input type="text" class="form-control" id="fruit" ng-model="dat.favoriteFruit" ng-disabled="enableEdit">
</div>
<div class="form-group">
<label for="greeting">greeting:</label>
<input type="text" class="form-control" id="greeting" ng-model="dat.greeting" ng-disabled="enableEdit">
</div>
<button class="btn btn-danger" ng-click="remove($index)">remove</button>
<button class="btn btn-default" ng-click="enableEdit=!enableEdit">edit</button>
<button class="btn btn-success" ng-click="save(dat,$index);enableEdit=!enableEdit" ng-disabled="enableEdit">save</button>
</form>
</div>
After the refactoring to directive, the task is not so obvious:
<div class="well" ng-repeat="dat in data">
<data-directive user="dat" index="{{$index}}" arr="data"></data-directive>
</div>
The directive template looks as follows:
<form novalidate>
<div class="form-group">
<label for="name">name:</label>
<input type="text" class="form-control" id="name" ng-model="user.name" ng-disabled="enableEdit">
</div>
<div class="form-group">
<label for="balance">balance:</label>
<input type="text" class="form-control" id="balance" ng-model="user.balance" ng-disabled="enableEdit">
</div>
<div class="form-group">
<label for="fruit">favorite Fruit:</label>
<input type="text" class="form-control" id="fruit" ng-model="user.favoriteFruit" ng-disabled="enableEdit">
</div>
<div class="form-group">
<label for="greeting">greeting:</label>
<input type="text" class="form-control" id="greeting" ng-model="user.greeting" ng-disabled="enableEdit">
</div>
<button class="btn btn-danger" ng-click="remove(index)">remove</button>
<button class="btn btn-default" ng-click="enableEdit=!enableEdit">edit</button>
<button class="btn btn-success" ng-click="save(user,$index);enableEdit=!enableEdit" ng-disabled="enableEdit">save</button>
</form>
Directive js:
app.directive("data-directive", ["dataService", function (dataService) {
return {
restrict: 'E',
templateUrl:"directives/dataDirectiveTemplate.html",
scope:{
user: "=",
index: "#",
arr: "="
},
controller: function ($scope) {
$scope.enableEdit = true;
$scope.remove = function (index) {
console.log(arr);
dataService.removeItem(arr, parseInt(index));
};
$scope.save = function (item,index) {
dataService.saveItem(item, index, $scope.data);
};
$scope.changes = function () {
console.log($scope.data);
};
}
}
}]);
I managed to pass the $index variable, but how do I pass the whole data array of objects?
Let's first define an owner for data. It can be the controller whose view is using data-directive or dataService.
Now, owner holds data and all responsibility to manipulate it.
If it is dataService, its method can be called from the directive and data is already available with it.
If it is controller it can pass method using & e.g.:
scope: {
user: "=",
index: "#",
arr: "=",
onRemove: "&",
onSave: "&",
onChange: "&",
},

Angular Form Validation using Ng Model

Question: How can I show the validation error using only the ng-model if I cannot name the form and its elements.
I have a html form to collect credit card details. To prevent the credit card data from touching my server, I cannot name the form elements. So my form looks like:
<form ng-submit="vm.processForm()">
<div class="form-row">
<label>
<span>Card Number</span>
<input type="text" size="20" data-stripe="number" ng-model="vm.number" required>
</label>
</div>
<div class="form-row">
<label>
<span>Expiration (MM/YY)</span>
<input type="text" size="2" data-stripe="exp_month" ng-model="vm.exp_month" required>
</label>
<span> / </span>
<input type="text" size="2" data-stripe="exp_year" ng-model="vm.exp_year" required>
</div>
<div class="form-row">
<label>
<span>CVC</span>
<input type="text" size="4" data-stripe="cvc" ng-model="vm.cvc" required>
</label>
</div>
<input type="submit" class="submit" value="Submit Payment">
</form>
Usually in Angular, I used to check validation using the form elements name, for example like this:
<p ng-show="userForm.creditcard.$error.required">Your credit card number is required.</p>
But since I cannot name the form and its elements, how can I show the validation error using only the ng-model? Because, the following doesn't work:
<p ng-show="vm.number.$error.required">Your credit card number is required.</p>
I am using Angular v1.4.8.
I created a directive to export de the model controller. I don't think that is the best way but it works.
.directive('exportModel', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attr, ngModel) {
attr.$observe('exportModel', function (value) {
scope[value] = ngModel;
})
}
}
})
http://jsfiddle.net/Lvc0u55v/11352/

How do I build a directive in angular that adds a wrapper around an input and shows message if input is invalid?

I'm a total beginner in Angular, so maybe I am thinking about it totally wrong.
What I'm trying to do is create a directive that wraps an input in some boilerplate html. It should bind the input's ng-model to the parent scope (the myForm controller's scope) but should have access to the validation state of the input.
Here's my setup:
parent_form.html
<form name="myForm">
<div fieldContainer label="'Field 1'" model="'field1'">
<input type="text" ng-model="field1" required/>
</div>
<div fieldContainer label="'Field 2'" model="'field2'">
<input type="text" ng-model="field2" required/>
</div>
</form>
field_container.html
<div class="row">
<div class="col-md-5">{{label}}</div>
<div class="col-md-5" ng-transclude></div>
<div class="col-md-2" ng-show="valid">REQUIRED!</div>
</div>
parent_form.js
angular.module('myApp')
.controller('myForm', function ($scope) {
$scope.field1 = '';
$scope.field2 = '';
})
.directive('fieldContainer', function () {
return {
restrict: 'A',
templateUrl: 'field_container.html',
transclude: true,
scope: {
label: '=label',
model : '=model'
}
}
});
I got it working by:
Adding a name to the field (so that Angular's Form validation takes over)
Setting ng-show on the validation message to be: $parent.myForm[model].$valid
So apparently the transcluded input still uses the parent (myForm) scope and the directive has access to the parent's scope via scope.$parent.
Not sure if this is the cleanest way to do it...
Final code looks like:
parent_form.html
<form name="myForm">
<div fieldContainer label="'Field 1'" model="'field1'">
<input type="text" name="field1" ng-model="field1" required/>
</div>
<div fieldContainer label="'Field 2'" model="'field2'">
<input type="text" name="field2" ng-model="field2" required/>
</div>
</form>
field_container.html
<div class="row">
<div class="col-md-5">{{label}}</div>
<div class="col-md-5" ng-transclude></div>
<div class="col-md-2" ng-show="$parent.myForm[model].$valid">REQUIRED!</div>
</div>

AngularJS custom directives

I'm creating template for text inputing using angular directives.
Directive will receive 3 attributes: title, placeholder and model.
I need attribte model to be implemented into atribute ng-model in directive template.
For example:
if i create element with next attributes
<ng-text-input model="test" title="First name" placeholder="First name"></ng-text-input>
result will be next:
<div class="form-group">
<label class="col-sm-2 control-label">First name</label>
<div class="col-sm-9">
<input ng-model="test" type="text" class="form-control" placeholder="First name">
</div>
</div>
And how can i use model "test" in parent scope?
Thanks
upadete after #Maxdow comment:
directive declaration:
app.directive('ngTextInput', function(){
return {
restrict : 'AE',
scope: {
title: '#',
placeholder : '#',
myModel: '=ngModel'
},
templateUrl : 'ng-textInput.html'
}
});
template:
<script type="text/ng-template" id="ng-textInput.html">
<div class="form-group">
<label class="col-sm-2 control-label">{{title}}</label>
<div class="col-sm-9">
<input ng-model="myModel" type="text" class="form-control" placeholder={{placeholder}}>
</div>
</div>
</script>
using:
<ng-text-input ng-model="test" title="First name" placeholder="First name"></ng-text-input>
but result is still:
<div class="form-group">
<label class="col-sm-2 control-label ng-binding">First name</label>
<div class="col-sm-9">
<input ng-model="myModel" type="text" class="form-control ng-pristine ng-valid" placeholder="First name">
</div>
</div>
What am i doing wrong?
In your directive bind your attribute with ngModel :
app.directive('myDirective', function() {
return {
restrict: 'AE',
scope: {
myModel: '=ngModel'
},
template:'<input ng-model="myModel"/>'
}});
You should be able to use is from your HTML like this :
<my-directive ng-model="whatyouwant"></my-directive>
An example : http://jsfiddle.net/maxdow/6GU6x/

Resources