|
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 | +]) |
2 | 6 |
|
3 | 7 | .directive('field', function($compile, $http, $templateCache, $interpolate) {
|
4 | 8 |
|
| 9 | + var findInputElement = function(element) { |
| 10 | + return angular.element(element.find('input')[0] || element.find('textarea')[0] || element.find('select')[0]); |
| 11 | + }; |
| 12 | + |
5 | 13 | return {
|
6 | 14 | restrict:'E',
|
7 | 15 | 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 |
9 | 17 | 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 | + }); |
12 | 29 |
|
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 | + } |
16 | 42 |
|
17 | 43 | // 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) { |
19 | 46 | 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 | + |
23 | 61 | return newElement;
|
24 | 62 | });
|
25 | 63 |
|
26 | 64 | 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(); |
29 | 69 |
|
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; |
34 | 75 |
|
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); |
42 | 83 |
|
| 84 | + // TODO: Consider moving this validator stuff into its own directive |
| 85 | + // and use a directive controller to wire it all up |
43 | 86 | 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; |
57 | 91 | });
|
58 | 92 | });
|
59 | 93 |
|
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) |
61 | 96 | // otherwise the new input won't pick up the FormController
|
62 | 97 | $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 |
66 | 99 | element.after(clone);
|
| 100 | + // Remove our original element |
| 101 | + element.remove(); |
67 | 102 | });
|
68 | 103 |
|
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]) |
70 | 106 | if ( formController ) {
|
71 | 107 | childScope.$form = formController;
|
72 |
| - childScope.$field = formController[childScope.name]; |
| 108 | + childScope.$field = formController[childScope.$modelId]; |
73 | 109 | }
|
74 |
| - |
75 | 110 | });
|
76 | 111 | };
|
77 | 112 | }
|
|
0 commit comments