9e39cf1585b50059c563229efc931ae6253892a5
[civicrm-core.git] / ext / search / ang / search / crmSearchValue.directive.js
1 (function(angular, $, _) {
2 "use strict";
3
4 angular.module('search').directive('crmSearchValue', function($interval, searchMeta, formatForSelect2) {
5 return {
6 scope: {
7 data: '=crmSearchValue'
8 },
9 require: 'ngModel',
10 link: function (scope, element, attrs, ngModel) {
11 var ts = scope.ts = CRM.ts(),
12 multi = _.includes(['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'], scope.data.op),
13 format = scope.data.format;
14
15 function destroyWidget() {
16 var $el = $(element);
17 if ($el.is('.crm-form-date-wrapper .crm-hidden-date')) {
18 $el.crmDatepicker('destroy');
19 }
20 if ($el.is('.select2-container + input')) {
21 $el.crmEntityRef('destroy');
22 }
23 $(element).removeData().removeAttr('type').removeAttr('placeholder').show();
24 }
25
26 function makeWidget(field, op, optionKey) {
27 var $el = $(element),
28 inputType = field.input_type,
29 dataType = field.data_type;
30 if (!op) {
31 op = field.serialize || dataType === 'Array' ? 'IN' : '=';
32 }
33 multi = _.includes(['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'], op);
34 if (op === 'IS NULL' || op === 'IS NOT NULL') {
35 $el.hide();
36 return;
37 }
38 if (inputType === 'Date') {
39 if (_.includes(['=', '!=', '>', '>=', '<', '<='], op)) {
40 $el.crmDatepicker({time: (field.input_attrs && field.input_attrs.time) || false});
41 }
42 } else if (_.includes(['=', '!=', 'IN', 'NOT IN', 'CONTAINS'], op) && (field.fk_entity || field.options || dataType === 'Boolean')) {
43 if (field.options) {
44 if (field.options === true) {
45 $el.addClass('loading');
46 var waitForOptions = $interval(function() {
47 if (field.options !== true) {
48 $interval.cancel(waitForOptions);
49 $el.removeClass('loading').crmSelect2({data: getFieldOptions, multiple: multi});
50 }
51 }, 200);
52 }
53 $el.attr('placeholder', ts('select')).crmSelect2({data: getFieldOptions, multiple: multi});
54 } else if (field.fk_entity) {
55 $el.crmEntityRef({entity: field.fk_entity, select:{multiple: multi}});
56 } else if (dataType === 'Boolean') {
57 $el.attr('placeholder', ts('- select -')).crmSelect2({allowClear: false, multiple: multi, placeholder: ts('- select -'), data: [
58 // FIXME: it would be more correct to use real true/false booleans instead of numbers, but select2 doesn't seem to like them
59 {id: 1, text: ts('Yes')},
60 {id: 0, text: ts('No')}
61 ]});
62 }
63 } else if (dataType === 'Integer' && !multi) {
64 $el.attr('type', 'number');
65 }
66
67 function getFieldOptions() {
68 return {results: formatForSelect2(field.options, optionKey, 'label', ['description', 'color', 'icon'])};
69 }
70 }
71
72 // Copied from ng-list but applied conditionally if field is multi-valued
73 var parseList = function(viewValue) {
74 // If the viewValue is invalid (say required but empty) it will be `undefined`
75 if (_.isUndefined(viewValue)) return;
76
77 if (!multi) {
78 return format === 'json' ? JSON.stringify(viewValue) : viewValue;
79 }
80
81 var list = [];
82
83 if (viewValue) {
84 _.each(viewValue.split(','), function(value) {
85 if (value) list.push(_.trim(value));
86 });
87 }
88
89 return list;
90 };
91
92 // Copied from ng-list
93 ngModel.$parsers.push(parseList);
94 ngModel.$formatters.push(function(value) {
95 return _.isArray(value) ? value.join(', ') : (format === 'json' && value !== '' ? JSON.parse(value) : value);
96 });
97
98 // Copied from ng-list
99 ngModel.$isEmpty = function(value) {
100 return !value || !value.length;
101 };
102
103 scope.$watchCollection('data', function(data) {
104 destroyWidget();
105 var field = searchMeta.parseExpr(data.field).field;
106 if (field) {
107 var optionKey = data.field.split(':')[1] || 'id';
108 makeWidget(field, data.op, optionKey);
109 }
110 });
111 }
112 };
113 });
114
115 })(angular, CRM.$, CRM._);