Skip to content

Commit 62b28f1

Browse files
refact(chapter-10): simplify field directive
1 parent dcae6b1 commit 62b28f1

20 files changed

+148
-116
lines changed

1820EN_10_Code/02_if_directive/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
<script src="../../lib/angular/1.0.4/angular.js"></script>
77
<script src="directive.js"></script>
88
</head>
9-
<body ng-init="model= {shown: true, count: 0}">
10-
<button ng-click="model.shown = !model.shown">Toggle Div</button>
11-
<div if="model.shown" ng-init="model.count=model.count+1">
9+
<body ng-init="model= {show: true, count: 0}">
10+
<button ng-click="model.show = !model.show">Toggle Div</button>
11+
<div if="model.show" ng-init="model.count=model.count+1">
1212
Shown {{model.count}} times
1313
</div>
1414
</div>

1820EN_10_Code/03_basic_accordion_directive/accordion.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ angular.module('accordion', [])
3939
return {
4040
restrict:'E',
4141
controller:'AccordionController',
42-
transclude: true,
43-
templateUrl: 'accordion.html'
42+
link: function(scope, element, attrs) {
43+
element.addClass('accordion');
44+
}
4445
};
4546
})
4647

1820EN_10_Code/03_basic_accordion_directive/accordion.spec.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ describe('accordion', function () {
33

44
beforeEach(module('accordion'));
55
beforeEach(module('accordion-group.html'));
6-
beforeEach(module('accordion.html'));
76

87
beforeEach(inject(function ($rootScope) {
98
$scope = $rootScope;

1820EN_10_Code/03_basic_accordion_directive/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
angular.module('app', ['accordion', 'accordion.html', 'accordion-group.html'])
1+
angular.module('app', ['accordion', 'accordion-group.html'])
22

33
.controller('AccordionDemoCtrl', function ($scope) {
44
$scope.groups = [

1820EN_10_Code/03_basic_accordion_directive/index.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
<script src="../../lib/angular/1.0.4/angular.js"></script>
88
<script src="accordion.js"></script>
99
<script src="app.js"></script>
10-
<script src="template/accordion/accordion.html.js"></script>
1110
<script src="template/accordion/accordion-group.html.js"></script>
1211
</head>
1312
<body>

1820EN_10_Code/03_basic_accordion_directive/template/accordion/accordion.html

Lines changed: 0 additions & 1 deletion
This file was deleted.

1820EN_10_Code/03_basic_accordion_directive/template/accordion/accordion.html.js

Lines changed: 0 additions & 4 deletions
This file was deleted.

1820EN_10_Code/03_basic_accordion_directive/test.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
<script src="../../lib/angular/1.0.4/angular-mocks.js"></script>
1212
</head>
1313
<body>
14-
<script src="template/accordion/accordion.html.js"></script>
1514
<script src="template/accordion/accordion-group.html.js"></script>
1615
<script src="accordion.js"></script>
1716
<script src="accordion.spec.js"></script>
Lines changed: 49 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
angular.module('field-directive', ['input.html', 'textarea.html', 'select.html', 'validationMessages.html'])
1+
angular.module('field-directive', ['input.html', 'textarea.html', 'select.html'])
22

33
.directive('field', function($compile, $http, $templateCache, $interpolate) {
44

@@ -24,46 +24,32 @@ angular.module('field-directive', ['input.html', 'textarea.html', 'select.html',
2424
// Search through the originalDirective's element for elements that contain information about how to map
2525
// validation keys to messages
2626
function getValidationMessageMap(originalElement) {
27-
// Find all the <validator> child elements and extract their (key, message) info
28-
var validationMessages = {};
29-
angular.forEach(originalElement.find('validator'), function(element) {
30-
// Wrap the element in jqLite/jQuery
31-
element = angular.element(element);
32-
// Store the message info to be provided to the scope later
33-
// The content of the validation element may include interpolation {{}}
34-
// so we will actually store a function created by the $interpolate service
35-
// To get the interpolated message we will call this function with the scope. e.g.
36-
// var messageString = getMessage(scope);
37-
validationMessages[element.attr('key')] = $interpolate(element.text());
38-
});
39-
return validationMessages;
27+
// Find all the <validator> child elements and extract their (key, message) info
28+
var validationMessages = {};
29+
angular.forEach(originalElement.find('validator'), function(element) {
30+
// Wrap the element in jqLite/jQuery
31+
element = angular.element(element);
32+
// Store the message info to be provided to the scope later
33+
// The content of the validation element may include interpolation {{}}
34+
// so we will actually store a function created by the $interpolate service
35+
// To get the interpolated message we will call this function with the scope. e.g.
36+
// var messageString = getMessage(scope);
37+
validationMessages[element.attr('key')] = $interpolate(element.text());
38+
});
39+
return validationMessages;
4040
}
4141

4242
// Find the content that will go into the new label
43-
// label="..." attribute trumps child <label> element
44-
function extractLabelContent(originalElement, attrs) {
45-
var labelContent = '';
46-
if ( attrs.fieldLabel ) {
47-
// Label is provided as an attribute on the originalElement
48-
labelContent = attrs.fieldLabel;
49-
} else if ( originalElement.find('label')[0] ) {
50-
// Label is provided as a <label> child element of the originalElement
51-
labelContent = originalElement.find('label').html();
52-
}
53-
if ( !labelContent ) {
54-
throw new Error('No label provided');
55-
}
56-
return labelContent;
43+
// Label is provided as a <label> child element of the original element
44+
function getLabelContent(element) {
45+
var label = element.find('label');
46+
return label[0] && label.html();
5747
}
5848

5949
return {
6050
restrict:'E',
6151
priority: 100, // We need this directive to happen before ng-model
6252
terminal: true, // We are going to deal with this element
63-
controller: function($scope) {
64-
// We will store the validation messages here, in the field's controller,
65-
// and the bind-validation-message directive will be able to access it
66-
},
6753
compile: function(element, attrs) {
6854
if ( attrs.ngRepeat || attrs.ngSwitch || attrs.uiIf ) {
6955
throw new Error('The ng-repeat, ng-switch and ui-if directives are not supported on the same element as the field directive.');
@@ -74,31 +60,40 @@ angular.module('field-directive', ['input.html', 'textarea.html', 'select.html',
7460

7561
// Extract the label and validation message info from the directive's original element
7662
var messageMap = getValidationMessageMap(element);
77-
var labelContent = extractLabelContent(element, attrs);
63+
var labelContent = getLabelContent(element);
7864

7965
// Clear the directive's original element now that we have extracted what we need from it
8066
element.html('');
8167

82-
return function (scope, element, attrs, fieldController) {
83-
// Attach a copy of the message map to this field's controller
84-
fieldController.messageMap = angular.copy(messageMap);
85-
86-
// Load up the template for this kind of field
87-
var template = attrs.template || 'input.html'; // Default to the simple input if none given
88-
var getFieldElement = loadTemplate(template).then(function(newElement) {
89-
90-
// Update the label's contents
91-
var labelElement = newElement.find('label');
92-
labelElement.html(labelContent);
93-
// Our template will have its own child scope
68+
return function postLink(scope, element, attrs) {
69+
// Load up the template for this kind of field, default to the simple input if none given
70+
loadTemplate(attrs.template || 'input.html').then(function(newElement) {
71+
// Set up the scope - the template will have its own scope, which is a child of the directive's scope
9472
var childScope = scope.$new();
95-
73+
// Attach a copy of the message map to the scope
74+
childScope.$messageMap = angular.copy(messageMap);
9675
// Generate an id for the field from the ng-model expression and the current scope
9776
// We replace dots with underscores to work with browsers and ngModel lookup on the FormController
9877
// We couldn't do this in the compile function as we need to be able to calculate the unique id from the scope
9978
childScope.$fieldId = attrs.ngModel.replace('.', '_').toLowerCase() + '_' + childScope.$id;
10079
childScope.$fieldLabel = labelContent;
10180

81+
// Update the $fieldErrors array when the validity of the field changes
82+
childScope.$watch('$field.$dirty && $field.$error', function(errorList) {
83+
childScope.$fieldErrors = [];
84+
if ( errorList ) {
85+
angular.forEach(errorList, function(invalid, key) {
86+
if ( invalid ) {
87+
childScope.$fieldErrors.push(key);
88+
}
89+
});
90+
}
91+
}, true);
92+
93+
// Update the label's contents
94+
var labelElement = newElement.find('label');
95+
labelElement.html(labelContent);
96+
10297
// Copy over all left over attributes to the input element
10398
// We can't use interpolation in the template for directives such as ng-model
10499
var inputElement = findInputElement(newElement);
@@ -129,13 +124,12 @@ angular.module('field-directive', ['input.html', 'textarea.html', 'select.html',
129124
};
130125
})
131126

132-
// A directive to bind the interpolation function of a validation message to the content of an element
133-
// use it as a attribute providing the validation key as the value of the attribute:
127+
// A directive to bind the interpolation function of a validation message to the content of an element.
128+
// Use it as a attribute providing the validation key as the value of the attribute:
134129
// <span bind-validation-message="required"></span>
135130
.directive('bindValidationMessage', function() {
136131
return {
137-
require: '^field',
138-
link: function(scope, element, attrs, fieldController) {
132+
link: function(scope, element, attrs) {
139133
var removeWatch = null;
140134
// The key may be dynamic (i.e. use interpolation) so we need to $observe it
141135
attrs.$observe('bindValidationMessage', function(key) {
@@ -144,21 +138,15 @@ angular.module('field-directive', ['input.html', 'textarea.html', 'select.html',
144138
removeWatch();
145139
removeWatch = null;
146140
}
147-
if ( key && fieldController.messageMap[key] ) {
148-
// Watch the message map interpolation function for this key
149-
removeWatch = scope.$watch(fieldController.messageMap[key], function(message) {
150-
// and update the contents of this element with the interpolated text
141+
if ( key && scope.$messageMap[key] ) {
142+
// scope.$messageMap[key] returns an interpolation function.
143+
// Watch the value of calling this function with the current scope.
144+
// Update the contents of the current element if the message changes
145+
removeWatch = scope.$watch(scope.$messageMap[key], function(message) {
151146
element.html(message);
152147
});
153148
}
154149
});
155150
}
156151
};
157-
})
158-
159-
.directive('validationMessages', function() {
160-
return {
161-
restrict: 'E',
162-
templateUrl: 'validationMessages.html'
163-
};
164152
});

0 commit comments

Comments
 (0)