Merge pull request #21307 from civicrm/5.41
[civicrm-core.git] / ext / afform / core / ang / af / afField.component.js
1 (function(angular, $, _) {
2 var id = 0;
3 // Example usage: <div af-fieldset="myModel"><af-field name="do_not_email" /></div>
4 angular.module('af').component('afField', {
5 require: {
6 afFieldset: '^^afFieldset',
7 afJoin: '?^^afJoin',
8 afRepeatItem: '?^^afRepeatItem'
9 },
10 templateUrl: '~/af/afField.html',
11 bindings: {
12 fieldName: '@name',
13 defn: '='
14 },
15 controller: function($scope, $element, crmApi4, $timeout) {
16 var ts = $scope.ts = CRM.ts('org.civicrm.afform'),
17 ctrl = this,
18 boolOptions = [{id: true, label: ts('Yes')}, {id: false, label: ts('No')}],
19 // Only used for is_primary radio button
20 noOptions = [{id: true, label: ''}];
21
22 // Attributes for each of the low & high date fields when using search_range
23 this.inputAttrs = [];
24
25 this.$onInit = function() {
26 var closestController = $($element).closest('[af-fieldset],[af-join],[af-repeat-item]');
27 $scope.dataProvider = closestController.is('[af-repeat-item]') ? ctrl.afRepeatItem : ctrl.afJoin || ctrl.afFieldset;
28 $scope.fieldId = ctrl.fieldName + '-' + id++;
29
30 $element.addClass('af-field-type-' + _.kebabCase(ctrl.defn.input_type));
31
32
33 if (ctrl.defn.search_range) {
34 // Initialize value as object unless using relative date select
35 var initialVal = $scope.dataProvider.getFieldData()[ctrl.fieldName];
36 if (!_.isArray($scope.dataProvider.getFieldData()[ctrl.fieldName]) &&
37 (ctrl.defn.input_type !== 'Select' || !ctrl.defn.is_date || initialVal !== '{}')
38 ) {
39 $scope.dataProvider.getFieldData()[ctrl.fieldName] = {};
40 }
41 // Initialize inputAttrs (only used for datePickers at the moment)
42 if (ctrl.defn.is_date) {
43 this.inputAttrs.push(ctrl.defn.input_attrs || {});
44 for (var i = 1; i <= 2; ++i) {
45 var attrs = _.cloneDeep(ctrl.defn.input_attrs || {});
46 attrs.placeholder = attrs['placeholder' + i];
47 attrs.timePlaceholder = attrs['timePlaceholder' + i];
48 ctrl.inputAttrs.push(attrs);
49 }
50 }
51 }
52
53 // is_primary field - watch others in this afRepeat block to ensure only one is selected
54 if (ctrl.fieldName === 'is_primary' && 'repeatIndex' in $scope.dataProvider) {
55 $scope.$watch('dataProvider.afRepeat.getEntityController().getData()', function (items, prev) {
56 var index = $scope.dataProvider.repeatIndex;
57 // Set first item to primary if there isn't a primary
58 if (items && !index && !_.find(items, 'is_primary')) {
59 $scope.dataProvider.getFieldData().is_primary = true;
60 }
61 // Set this item to not primary if another has been selected
62 if (items && prev && items.length === prev.length && items[index].is_primary && prev[index].is_primary &&
63 _.filter(items, 'is_primary').length > 1
64 ) {
65 $scope.dataProvider.getFieldData().is_primary = false;
66 }
67 }, true);
68 }
69
70 // ChainSelect - watch control field & reload options as needed
71 if (ctrl.defn.input_type === 'ChainSelect') {
72 $scope.$watch('dataProvider.getFieldData()[defn.input_attrs.control_field]', function(val) {
73 if (val) {
74 var params = {
75 where: [['name', '=', ctrl.fieldName]],
76 select: ['options'],
77 loadOptions: ['id', 'label'],
78 values: {}
79 };
80 params.values[ctrl.defn.input_attrs.control_field] = val;
81 crmApi4($scope.dataProvider.getEntityType(), 'getFields', params, 0)
82 .then(function(data) {
83 ctrl.defn.options = data.options;
84 });
85 }
86 });
87 }
88
89 // Set default value
90 if (ctrl.defn.afform_default) {
91 // Wait for parent controllers to initialize
92 $timeout(function() {
93 $scope.dataProvider.getFieldData()[ctrl.fieldName] = ctrl.defn.afform_default;
94 });
95 }
96
97 };
98
99 // Get the repeat index of the entity fieldset (not the join)
100 ctrl.getEntityIndex = function() {
101 // If already in a join repeat, look up the outer repeat
102 if ('repeatIndex' in $scope.dataProvider && $scope.dataProvider.afRepeat.getRepeatType() === 'join') {
103 return $scope.dataProvider.outerRepeatItem ? $scope.dataProvider.outerRepeatItem.repeatIndex : 0;
104 } else {
105 return ctrl.afRepeatItem ? ctrl.afRepeatItem.repeatIndex : 0;
106 }
107 };
108
109 // Params for the Afform.submitFile API when uploading a file field
110 ctrl.getFileUploadParams = function() {
111 return {
112 entityName: ctrl.afFieldset.modelName,
113 fieldName: ctrl.fieldName,
114 joinEntity: ctrl.afJoin ? ctrl.afJoin.entity : null,
115 entityIndex: ctrl.getEntityIndex(),
116 joinIndex: ctrl.afJoin && $scope.dataProvider.repeatIndex || null
117 };
118 };
119
120 $scope.getOptions = function () {
121 return ctrl.defn.options || (ctrl.fieldName === 'is_primary' && ctrl.defn.input_type === 'Radio' ? noOptions : boolOptions);
122 };
123
124 $scope.select2Options = function() {
125 return {
126 results: _.transform($scope.getOptions(), function(result, opt) {
127 result.push({id: opt.id, text: opt.label});
128 }, [])
129 };
130 };
131
132 // Getter/Setter function for fields of type select or entityRef.
133 $scope.getSetSelect = function(val) {
134 var currentVal = $scope.dataProvider.getFieldData()[ctrl.fieldName];
135 // Setter
136 if (arguments.length) {
137 if (ctrl.defn.is_date) {
138 // The '{}' string is a placeholder for "choose date range"
139 if (val === '{}') {
140 val = !_.isPlainObject(currentVal) ? {} : currentVal;
141 }
142 }
143 // If search_range, this select is the "low" value (the high value uses ng-model without a getterSetter fn)
144 else if (ctrl.defn.search_range) {
145 return ($scope.dataProvider.getFieldData()[ctrl.fieldName]['>='] = val);
146 }
147 // A multi-select needs to split string value into an array
148 if (ctrl.defn.input_attrs && ctrl.defn.input_attrs.multiple) {
149 val = val ? val.split(',') : [];
150 }
151 return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val);
152 }
153 // Getter
154 if (_.isArray(currentVal)) {
155 return currentVal.join(',');
156 }
157 if (ctrl.defn.is_date) {
158 return _.isPlainObject(currentVal) ? '{}' : currentVal;
159 }
160 // If search_range, this select is the "low" value (the high value uses ng-model without a getterSetter fn)
161 else if (ctrl.defn.search_range) {
162 return currentVal['>='];
163 }
164 return currentVal;
165 };
166
167 }
168 });
169 })(angular, CRM.$, CRM._);