Skip to content

Commit a47e689

Browse files
refact(chapter-10): refactor field directive compilation and add further templates
1 parent bf1a1f2 commit a47e689

File tree

10 files changed

+141
-77
lines changed

10 files changed

+141
-77
lines changed

1820EN_10_Code/04_field_directive/field-directive.js

+79-44
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,112 @@
1-
angular.module('field-directive', ['1820EN_10_Code/04_field_directive/template/text.html', '1820EN_10_Code/04_field_directive/template/number.html'])
1+
angular.module('field-directive', [
2+
'1820EN_10_Code/04_field_directive/template/input.html',
3+
'1820EN_10_Code/04_field_directive/template/textarea.html',
4+
'1820EN_10_Code/04_field_directive/template/select.html'
5+
])
26

37
.directive('field', function($compile, $http, $templateCache, $interpolate) {
48

9+
var findInputElement = function(element) {
10+
return angular.element(element.find('input')[0] || element.find('textarea')[0] || element.find('select')[0]);
11+
};
12+
513
return {
614
restrict:'E',
715
priority: 100, // We need this directive to happen before ng-model
8-
transclude: 'element',
16+
terminal: true, // We are going to deal with this element
917
require: '?^form', // If we are in a form then we can access the ngModelController
10-
compile:function compile(element, attrs, transclude) {
11-
var modelId, templatePromise, getFieldElement;
18+
compile:function compile(element, attrs) {
19+
20+
// Find all the <validator> child elements and extract their validation message info
21+
var validationMessages = [];
22+
angular.forEach(element.find('validator'), function(validatorElement) {
23+
validatorElement = angular.element(validatorElement);
24+
validationMessages.push({
25+
key: validatorElement.attr('key'),
26+
getMessage: $interpolate(validatorElement.text())
27+
});
28+
});
1229

13-
// Generate an id for the input from the ng-model expression
14-
// (we need to replace dots with something to work with browsers and also form scope)
15-
modelId = attrs.ngModel.replace('.', '_').toLowerCase();
30+
// Find the content that will go into the new label
31+
var labelContent = '';
32+
if ( element.attr('label') ) {
33+
labelContent = element.attr('label');
34+
element[0].removeAttribute('label');
35+
}
36+
if ( element.find('label')[0] ) {
37+
labelContent = element.find('label').html();
38+
}
39+
if ( !labelContent ) {
40+
throw new Error('No label provided');
41+
}
1642

1743
// Load up the template for this kind of field
18-
getFieldElement = $http.get('1820EN_10_Code/04_field_directive/template/' + attrs.type + '.html', {cache:$templateCache}).then(function(response) {
44+
var template = attrs.template || 'input'; // Default to the simple input if none given
45+
var getFieldElement = $http.get('1820EN_10_Code/04_field_directive/template/' + template + '.html', {cache:$templateCache}).then(function(response) {
1946
var newElement = angular.element(response.data);
20-
var inputElement = newElement.find('input') || newElement.find('textarea') || newElement.find('select');
21-
// Copy over the ng-model attribute directly because it won't work using interpolation in the template
22-
inputElement.attr('ng-model', attrs.ngModel);
47+
var inputElement = findInputElement(newElement);
48+
49+
// Copy over the attributes to the input element
50+
// At least the ng-model attribute must be copied because we can't use interpolation in the template
51+
angular.forEach(element[0].attributes, function (attribute) {
52+
var value = attribute.value;
53+
var key = attribute.name;
54+
inputElement.attr(key, value);
55+
});
56+
57+
// Update the label's contents
58+
var labelElement = newElement.find('label');
59+
labelElement.html(labelContent);
60+
2361
return newElement;
2462
});
2563

2664
return function (scope, element, attrs, formController) {
27-
var childScope = scope.$new();
28-
childScope.id = childScope.name = modelId + '_' + childScope.$id;
65+
// We have to wait for the field element template to be loaded
66+
getFieldElement.then(function(newElement) {
67+
// Our template will have its own child scope
68+
var childScope = scope.$new();
2969

30-
attrs.$observe('label', function(value) {
31-
// We map the label attribute to the child scope
32-
childScope.label = value;
33-
});
70+
// Generate an id for the input from the ng-model expression
71+
// (we need to replace dots with something to work with browsers and also form scope)
72+
// (We couldn't do this in the compile function as we need the scope to
73+
// be able to calculate the unique id)
74+
childScope.$modelId = attrs.ngModel.replace('.', '_').toLowerCase() + '_' + childScope.$id;
3475

35-
getFieldElement.then(function(newElement) {
36-
// We need to set the input element's name here before we compile.
37-
// If we leave it to interpolation, the formController doesn't pick it up
38-
var inputElement = newElement.find('input') || newElement.find('textarea') || newElement.find('select');
39-
inputElement.attr('name', childScope.name);
40-
inputElement.attr('id', childScope.id);
41-
newElement.find('label').attr('for', childScope.id);
76+
// Wire up the input (id and name) and its label (for)
77+
// (We need to set the input element's name here before we compile.
78+
// If we leave it to interpolation, the formController doesn't pick it up)
79+
var inputElement = findInputElement(newElement);
80+
inputElement.attr('name', childScope.$modelId);
81+
inputElement.attr('id', childScope.$modelId);
82+
newElement.find('label').attr('for', childScope.$modelId);
4283

84+
// TODO: Consider moving this validator stuff into its own directive
85+
// and use a directive controller to wire it all up
4386
childScope.$validationMessages = {};
44-
var originalElement = transclude(scope);
45-
angular.forEach(originalElement.find('validator'), function(validatorElement) {
46-
validatorElement = angular.element(validatorElement);
47-
48-
// We need to watch the message incase it has interpolated values that need processing
49-
scope.$watch($interpolate(validatorElement.text()), function (message) {
50-
childScope.$validationMessages[validatorElement.attr('key')] = message;
51-
});
52-
53-
// Extract the options and bind them to the input element
54-
var validationAttributes = scope.$eval(validatorElement.attr('options'));
55-
angular.forEach(validationAttributes, function(value, key) {
56-
inputElement.attr(key, value);
87+
angular.forEach(validationMessages, function(validationMessage) {
88+
// We need to watch incase it has interpolated values that need processing
89+
scope.$watch(validationMessage.getMessage, function (message) {
90+
childScope.$validationMessages[validationMessage.key] = message;
5791
});
5892
});
5993

60-
// We must compile in the postLink function rather than the compile function
94+
// We must compile our new element in the postLink function rather than in the compile function
95+
// (i.e. after any parent form element has been linked)
6196
// otherwise the new input won't pick up the FormController
6297
$compile(newElement)(childScope, function(clone) {
63-
// We can only add the new element after the directive element because
64-
// transclusion caused the directive element to be converted to a template.
65-
// Comments are ignored by ng-repeat, otherwise this would not work
98+
// Place our new element after the original element
6699
element.after(clone);
100+
// Remove our original element
101+
element.remove();
67102
});
68103

69-
// Only after the new element has been compiled will we have access to the $field
104+
// Only after the new element has been compiled do we have access to the ngModelController
105+
// (i.e. formController[childScope.name])
70106
if ( formController ) {
71107
childScope.$form = formController;
72-
childScope.$field = formController[childScope.name];
108+
childScope.$field = formController[childScope.$modelId];
73109
}
74-
75110
});
76111
};
77112
}

1820EN_10_Code/04_field_directive/index.html

+25-10
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,34 @@
55
<title>AngularJS Book - Chapter 10 - field Directive</title>
66
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap-combined.min.css" rel="stylesheet">
77
<script src="../../lib/angular/1.0.4/angular.js"></script>
8+
9+
<!-- Load the field directive -->
810
<script src="field-directive.js"></script>
9-
<script src="template/text.html.js"></script>
10-
<script src="template/number.html.js"></script>
11+
12+
<!-- Load up the templates -->
13+
<script src="template/input.html.js"></script>
14+
<script src="template/textarea.html.js"></script>
15+
<script src="template/select.html.js"></script>
1116
</head>
1217
<body ng-init="myModel = {}">
13-
<form name="form" class="form-inline" ng-init="x = 3" novalidate>
14-
<button ng-click="x=5">X</button>
15-
<field ng-model="myModel.myNumber" type="number" label="My Number">
16-
<validator key="number">Please enter a valid number</validator>
17-
</field> {{ myModel.myNumber }}
18-
<field ng-model="myModel.myText" type="text" label="My Text">
19-
<validator key="required" options="{required: true}">My Text is {{ x }} required</validator>
20-
</field> {{ myModel.myText }}
18+
<form name="form" class="form-inline" novalidate>
19+
<field ng-model="myModel.myNumber" type="number" >
20+
<label>Labels which can <span ng-repeat="i in [0,1]">{{i}}</span> contain directives</label>
21+
</field>
22+
23+
<field ng-model="myModel.myText" type="text" label="My {{'Text'}}" required="true">
24+
<validator key="required">My Text is required</validator>
25+
</field>
26+
27+
<field ng-model="myModel.myBigText" template="textarea" label="My Big Text" ng-maxlength="5" ng-minlength="2" required="true">
28+
<validator key="maxlength">My Big Text must be less than 5</validator>
29+
<validator key="minlength">My Big Text must be greater than 2</validator>
30+
<validator key="required">My Big Text must be greater than 2</validator>
31+
</field>
32+
33+
<field ng-model="myModel.choice" template="select" label="Choose something" ng-options="x for x in ['A','B', 'C']"></field>
34+
35+
<pre>{{myModel | json}}</pre>
2136
</form>
2237
</body>
2338
</html>

1820EN_10_Code/04_field_directive/loadTemplate.js

-7
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div class="control-group" ng-class="{'error' : $field.$invalid && $field.$dirty, 'success' : $field.$valid && $field.$dirty}">
2+
<label class="control-label" >{{label}}</label>
3+
<div class="controls">
4+
<input>
5+
<span ng-repeat="(key, error) in $field.$error" ng-show="error && $field.$dirty" class="help-inline">{{$validationMessages[key]}}</span>
6+
</div>
7+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
angular.module("1820EN_10_Code/04_field_directive/template/input.html", []).run(["$templateCache", function($templateCache) {
2+
$templateCache.put("1820EN_10_Code/04_field_directive/template/input.html",
3+
"<div class=\"control-group\" ng-class=\"{'error' : $field.$invalid && $field.$dirty, 'success' : $field.$valid && $field.$dirty}\">" +
4+
" <label class=\"control-label\" >{{label}}</label>" +
5+
" <div class=\"controls\">" +
6+
" <input>" +
7+
" <span ng-repeat=\"(key, error) in $field.$error\" ng-show=\"error && $field.$dirty\" class=\"help-inline\">{{$validationMessages[key]}}</span>" +
8+
" </div>" +
9+
"</div>");
10+
}]);

1820EN_10_Code/04_field_directive/template/number.html.js

-10
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<div class="control-group" ng-class="{'error' : $field.$invalid && $field.$dirty, 'success' : $field.$valid && $field.$dirty}">
22
<label class="control-label">{{label}}</label>
33
<div class="controls">
4-
<input type="number">
5-
<span ng-repeat="error in $errors" class="help-inline">{{$validationMessages[error]}}</span>
4+
<select></select>
5+
<span ng-repeat="(key, error) in $field.$error"
6+
ng-show="error && $field.$dirty"
7+
class="help-inline">{{$validationMessages[key]}}</span>
68
</div>
79
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
angular.module("1820EN_10_Code/04_field_directive/template/select.html", []).run(["$templateCache", function($templateCache) {
2+
$templateCache.put("1820EN_10_Code/04_field_directive/template/select.html",
3+
"<div class=\"control-group\" ng-class=\"{'error' : $field.$invalid && $field.$dirty, 'success' : $field.$valid && $field.$dirty}\">" +
4+
" <label class=\"control-label\">{{label}}</label>" +
5+
" <div class=\"controls\">" +
6+
" <select></select>" +
7+
" <span ng-repeat=\"(key, error) in $field.$error\"" +
8+
" ng-show=\"error && $field.$dirty\"" +
9+
" class=\"help-inline\">{{$validationMessages[key]}}</span>" +
10+
" </div>" +
11+
"</div>");
12+
}]);
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div class="control-group" ng-class="{'error' : $field.$invalid && $field.$dirty, 'success' : $field.$valid && $field.$dirty}">
22
<label class="control-label">{{label}}</label>
33
<div class="controls">
4-
<input type="text">
4+
<textarea></textarea>
55
<span ng-repeat="(key, error) in $field.$error" ng-show="error && $field.$dirty" class="help-inline">{{$validationMessages[key]}}</span>
66
</div>
77
</div>

1820EN_10_Code/04_field_directive/template/text.html.js renamed to 1820EN_10_Code/04_field_directive/template/textarea.html.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
angular.module("1820EN_10_Code/04_field_directive/template/text.html", []).run(["$templateCache", function($templateCache) {
2-
$templateCache.put("1820EN_10_Code/04_field_directive/template/text.html",
1+
angular.module("1820EN_10_Code/04_field_directive/template/textarea.html", []).run(["$templateCache", function($templateCache) {
2+
$templateCache.put("1820EN_10_Code/04_field_directive/template/textarea.html",
33
"<div class=\"control-group\" ng-class=\"{'error' : $field.$invalid && $field.$dirty, 'success' : $field.$valid && $field.$dirty}\">" +
44
" <label class=\"control-label\">{{label}}</label>" +
55
" <div class=\"controls\">" +
6-
" <input type=\"text\">" +
6+
" <textarea></textarea>" +
77
" <span ng-repeat=\"(key, error) in $field.$error\" ng-show=\"error && $field.$dirty\" class=\"help-inline\">{{$validationMessages[key]}}</span>" +
88
" </div>" +
99
"</div>");

0 commit comments

Comments
 (0)