Merge pull request #21643 from colemanw/afformUrlArgs
[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, $location) {
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 // Wait for parent controllers to initialize
90 $timeout(function() {
91 // Unique field name = entity_name index . join . field_name
92 var entityName = ctrl.afFieldset.getName(),
93 joinEntity = ctrl.afJoin ? ctrl.afJoin.entity : null,
94 uniquePrefix = '',
95 urlArgs = $location.search();
96 if (entityName) {
97 var index = ctrl.getEntityIndex();
98 uniquePrefix = entityName + (index ? index + 1 : '') + (joinEntity ? '.' + joinEntity : '') + '.';
99 }
100 // Set default value based on url
101 if (urlArgs && urlArgs[uniquePrefix + ctrl.fieldName]) {
102 setValue(urlArgs[uniquePrefix + ctrl.fieldName]);
103 }
104 // Set default value based on field defn
105 else if (ctrl.defn.afform_default) {
106 setValue(ctrl.defn.afform_default);
107 }
108 });
109 };
110
111 // Set default value; ensure data type matches input type
112 function setValue(value) {
113 if (ctrl.defn.input_type === 'Number' && ctrl.defn.search_range) {
114 if (!_.isPlainObject(value)) {
115 value = {
116 '>=': +(('' + value).split('-')[0] || 0),
117 '<=': +(('' + value).split('-')[1] || 0),
118 };
119 }
120 } else if (ctrl.defn.input_type === 'Number') {
121 value = +value;
122 } else if (ctrl.defn.search_range && !_.isPlainObject(value)) {
123 value = {
124 '>=': ('' + value).split('-')[0],
125 '<=': ('' + value).split('-')[1] || '',
126 };
127 }
128
129 $scope.dataProvider.getFieldData()[ctrl.fieldName] = value;
130 }
131
132 // Get the repeat index of the entity fieldset (not the join)
133 ctrl.getEntityIndex = function() {
134 // If already in a join repeat, look up the outer repeat
135 if ('repeatIndex' in $scope.dataProvider && $scope.dataProvider.afRepeat.getRepeatType() === 'join') {
136 return $scope.dataProvider.outerRepeatItem ? $scope.dataProvider.outerRepeatItem.repeatIndex : 0;
137 } else {
138 return ctrl.afRepeatItem ? ctrl.afRepeatItem.repeatIndex : 0;
139 }
140 };
141
142 // Params for the Afform.submitFile API when uploading a file field
143 ctrl.getFileUploadParams = function() {
144 return {
145 entityName: ctrl.afFieldset.modelName,
146 fieldName: ctrl.fieldName,
147 joinEntity: ctrl.afJoin ? ctrl.afJoin.entity : null,
148 entityIndex: ctrl.getEntityIndex(),
149 joinIndex: ctrl.afJoin && $scope.dataProvider.repeatIndex || null
150 };
151 };
152
153 $scope.getOptions = function () {
154 return ctrl.defn.options || (ctrl.fieldName === 'is_primary' && ctrl.defn.input_type === 'Radio' ? noOptions : boolOptions);
155 };
156
157 $scope.select2Options = function() {
158 return {
159 results: _.transform($scope.getOptions(), function(result, opt) {
160 result.push({id: opt.id, text: opt.label});
161 }, [])
162 };
163 };
164
165 // Getter/Setter function for fields of type select or entityRef.
166 $scope.getSetSelect = function(val) {
167 var currentVal = $scope.dataProvider.getFieldData()[ctrl.fieldName];
168 // Setter
169 if (arguments.length) {
170 if (ctrl.defn.is_date) {
171 // The '{}' string is a placeholder for "choose date range"
172 if (val === '{}') {
173 val = !_.isPlainObject(currentVal) ? {} : currentVal;
174 }
175 }
176 // If search_range, this select is the "low" value (the high value uses ng-model without a getterSetter fn)
177 else if (ctrl.defn.search_range) {
178 return ($scope.dataProvider.getFieldData()[ctrl.fieldName]['>='] = val);
179 }
180 // A multi-select needs to split string value into an array
181 if (ctrl.defn.input_attrs && ctrl.defn.input_attrs.multiple) {
182 val = val ? val.split(',') : [];
183 }
184 return ($scope.dataProvider.getFieldData()[ctrl.fieldName] = val);
185 }
186 // Getter
187 if (_.isArray(currentVal)) {
188 return currentVal.join(',');
189 }
190 if (ctrl.defn.is_date) {
191 return _.isPlainObject(currentVal) ? '{}' : currentVal;
192 }
193 // If search_range, this select is the "low" value (the high value uses ng-model without a getterSetter fn)
194 else if (ctrl.defn.search_range) {
195 return currentVal['>='];
196 }
197 return currentVal;
198 };
199
200 }
201 });
202 })(angular, CRM.$, CRM._);