1 // http://civicrm.org/licensing
5 /* jshint validthis: true */
7 * Handle user input - field or operator selection.
9 * Decide whether to display select drop down, regular text or date
10 * field for the given field and row.
12 function handleUserInputField() {
13 var row
= $(this).closest('tr');
14 var field
= $('select[id^=mapper][id$="_1"]', row
).val();
15 field
= (field
=== 'world_region') ? 'worldregion_id': field
;
16 var operator
= $('select[id^=operator]', row
);
17 var op
= operator
.val();
19 var patt
= /_1$/; // pattern to check if the change event came from field name
20 if (field
!== null && patt
.test(this.id
)) {
21 // based on data type remove invalid operators e.g. IS EMPTY doesn't work with Boolean type column
22 var operators
= CRM
.searchBuilder
.generalOperators
;
23 if ((field
in CRM
.searchBuilder
.fieldTypes
) === true) {
24 if ($.inArray(CRM
.searchBuilder
.fieldTypes
[field
], ['Boolean', 'Int']) > -1) {
25 operators
= _
.omit(operators
, ['IS NOT EMPTY', 'IS EMPTY']);
27 else if (CRM
.searchBuilder
.fieldTypes
[field
] == 'String') {
28 operators
= _
.omit(operators
, ['>', '<', '>=', '<=']);
31 buildOperator(operator
, operators
);
34 // These Ops don't get any input field.
35 var noFieldOps
= ['', 'IS EMPTY', 'IS NOT EMPTY', 'IS NULL', 'IS NOT NULL'];
37 if ($.inArray(op
, noFieldOps
) > -1) {
38 // Hide the fields and return.
39 $('.crm-search-value', row
).hide().find('input, select').val('');
42 $('.crm-search-value', row
).show();
44 if (!CRM
.searchBuilder
.fieldOptions
[field
]) {
48 buildSelect(row
, field
, op
, false);
51 if (CRM
.searchBuilder
.fieldTypes
[field
] === 'Date' || CRM
.searchBuilder
.fieldTypes
[field
] === 'Timestamp') {
52 buildDate(row
, op
, CRM
.searchBuilder
.fieldTypes
[field
] === 'Timestamp');
60 * Add appropriate operator to selected field
61 * @param operator: jQuery object
62 * @param options: array
64 function buildOperator(operator
, options
) {
65 var selected
= operator
.val();
67 $.each(options
, function(value
, label
) {
68 operator
.append('<option value="' + value
+ '">' + label
+ '</option>');
70 operator
.val(selected
);
74 * Add select list if appropriate for this operation
75 * @param row: jQuery object
76 * @param field: string
77 * @param skip_fetch: boolean
79 function buildSelect(row
, field
, op
, skip_fetch
) {
81 // Operators that will get a single drop down list of choices.
82 var dropDownSingleOps
= ['=', '!='];
83 // Multiple select drop down list.
84 var dropDownMultipleOps
= ['IN', 'NOT IN'];
86 if ($.inArray(op
, dropDownMultipleOps
) > -1) {
87 multiSelect
= 'multiple="multiple"';
89 else if ($.inArray(op
, dropDownSingleOps
) < 0) {
90 // If this op is neither supported by single or multiple selects, then we should not render a select list.
95 $('.crm-search-value select', row
).remove();
96 $('input[id^=value]', row
)
98 .after('<select class="crm-form-' + multiSelect
.substr(0, 5) + 'select required" ' + multiSelect
+ '><option value="">' + ts('Loading') + '...</option></select>');
100 // Avoid reloading state/county options IF already built, identified by skip_fetch
102 buildOptions(row
, field
);
105 fetchOptions(row
, field
);
110 * Retrieve option list for given row
111 * @param row: jQuery object
112 * @param field: string
114 function fetchOptions(row
, field
) {
115 if (CRM
.searchBuilder
.fieldOptions
[field
] === 'yesno') {
116 CRM
.searchBuilder
.fieldOptions
[field
] = [{key
: 1, value
: ts('Yes')}, {key
: 0, value
: ts('No')}];
118 if (typeof(CRM
.searchBuilder
.fieldOptions
[field
]) == 'string') {
119 CRM
.api(CRM
.searchBuilder
.fieldOptions
[field
], 'getoptions', {field
: field
, sequential
: 1}, {
120 success: function(result
, settings
) {
121 var field
= settings
.field
;
123 CRM
.searchBuilder
.fieldOptions
[field
] = result
.values
;
124 buildOptions(settings
.row
, field
);
127 removeSelect(settings
.row
);
130 error: function(result
, settings
) {
131 removeSelect(settings
.row
);
138 buildOptions(row
, field
);
143 * Populate option list for given row
144 * @param row: jQuery object
145 * @param field: string
147 function buildOptions(row
, field
) {
148 var select
= $('.crm-search-value select', row
);
149 var value
= $('input[id^=value]', row
).val();
150 if (value
.length
&& value
.charAt(0) == '(' && value
.charAt(value
.length
- 1) == ')') {
151 value
= value
.slice(1, -1);
153 var options
= value
.split(',');
154 if (select
.attr('multiple') == 'multiple') {
155 select
.find('option').remove();
158 select
.find('option').text(ts('- select -'));
159 if (options
.length
> 1) {
160 options
= [options
[0]];
163 $.each(CRM
.searchBuilder
.fieldOptions
[field
], function(key
, option
) {
164 var optionKey
= option
.key
;
165 if ($.inArray(field
, CRM
.searchBuilder
.searchByLabelFields
) >= 0) {
166 optionKey
= option
.value
;
168 var selected
= ($.inArray(''+optionKey
, options
) > -1) ? 'selected="selected"' : '';
169 select
.append('<option value="' + optionKey
+ '"' + selected
+ '>' + option
.value
+ '</option>');
175 * Remove select options and restore input to a plain textfield
176 * @param row: jQuery object
178 function removeSelect(row
) {
179 $('.crm-search-value input', row
).not('.crm-hidden-date').show();
180 $('.crm-search-value select', row
).remove();
184 * Add a datepicker if appropriate for this operation
185 * @param row: jQuery object
187 function buildDate(row
, op
, time
) {
188 var input
= $('.crm-search-value input', row
);
189 // These are operations that should not get a datepicker
190 var datePickerOp
= ($.inArray(op
, ['IN', 'NOT IN', 'LIKE', 'RLIKE']) < 0);
194 else if (!$('input.crm-hidden-date', row
).length
) {
195 // Unfortunately the search builder form expects yyyymmdd and crmDatepicker gives yyyy-mm-dd so we have to fudge it
196 var val
= input
.val();
197 if (val
&& val
.length
=== 8) {
198 input
.val(val
.substr(0, 4) + '-' + val
.substr(4, 2) + '-' + val
.substr(6, 2));
199 } else if (val
&& val
.length
=== 14) {
200 input
.val(val
.substr(0, 4) + '-' + val
.substr(4, 2) + '-' + val
.substr(6, 2) + ' ' + val
.substr(8, 2) + ':' + val
.substr(10, 2) + ':' + val
.substr(12, 2));
203 .on('change.searchBuilder', function() {
205 $(this).val($(this).val().replace(/[: -]/g, ''));
210 yearRange
: '-100:+20'
212 .triggerHandler('change', ['userInput']);
218 * @param row: jQuery object
220 function removeDate(row
) {
221 $('.crm-search-value input.crm-hidden-date', row
).off('.searchBuilder').crmDatepicker('destroy');
225 * Load and build select options for state IF country is chosen OR county options if state is chosen
226 * @param mapper: string
227 * @param value: integer
228 * @param location_type: integer
229 * @param section: section in which the country/state selection change occurred
231 function chainSelect(mapper
, value
, location_type
, section
) {
234 field
: (mapper
== 'country_id') ? 'state_province' : 'county',
236 apiParams
[mapper
] = value
;
237 var fieldName
= apiParams
.field
;
238 CRM
.api3('address', 'getoptions', apiParams
, {
239 success: function(result
) {
241 CRM
.searchBuilder
.fieldOptions
[fieldName
] = result
.values
;
242 $('select[id^=mapper_' + section
+ '][id$="_1"]').each(function() {
243 var row
= $(this).closest('tr');
244 var op
= $('select[id^=operator]', row
).val();
245 if ($(this).val() === fieldName
&& location_type
=== $('select[id^=mapper][id$="_2"]', row
).val()) {
246 buildSelect(row
, fieldName
, op
, true);
254 // Initialize display: Hide empty blocks & fields
255 var newBlock
= CRM
.searchBuilder
&& CRM
.searchBuilder
.newBlock
|| 0;
256 function initialize() {
257 $('.crm-search-block', '#Builder').each(function(blockNo
) {
259 var empty
= blockNo
+ 1 > newBlock
;
260 var skippedRow
= false;
261 $('tr:not(.crm-search-builder-add-row)', block
).each(function(rowNo
) {
263 if ($('select:first', row
).val() === '') {
264 if (!skippedRow
&& (rowNo
=== 0 || blockNo
+ 1 == newBlock
)) {
282 $('#crm-main-content-wrapper')
283 // Reset and hide row
284 .on('click', '.crm-reset-builder-row', function() {
285 var row
= $(this).closest('tr');
286 $('input, select', row
).val('').change();
288 // Hide entire block if this is the only visible row
289 if (row
.siblings(':visible').length
< 2) {
290 row
.closest('.crm-search-block').hide();
294 // Add new field - if there's a hidden one, show it
295 // Otherwise allow form to submit and fetch more from the server
296 .on('click', 'input[name^=addMore]', function() {
297 var table
= $(this).closest('table');
298 if ($('tr:hidden', table
).length
) {
299 $('tr:hidden', table
).first().show();
303 // Add new block - if there's a hidden one, show it
304 // Otherwise allow form to submit and fetch more from the server
305 .on('click', '#addBlock', function() {
306 if ($('.crm-search-block:hidden', '#Builder').length
) {
307 var block
= $('.crm-search-block:hidden', '#Builder').first();
309 $('tr:first-child, tr.crm-search-builder-add-row', block
).show();
313 // Handle field and operator selection
314 .on('change', 'select[id^=mapper][id$="_1"], select[id^=operator]', handleUserInputField
)
315 // Handle option selection - update hidden value field
316 .on('change', '.crm-search-value select', function() {
317 var value
= $(this).val() || '';
318 if ($(this).attr('multiple') == 'multiple' && value
.length
) {
319 value
= value
.join(',');
321 $(this).siblings('input').val(value
);
323 var mapper
= $('#' + $(this).siblings('input').attr('id').replace('value_', 'mapper_') + '_1').val();
324 var location_type
= $('#' + $(this).siblings('input').attr('id').replace('value_', 'mapper_') + '_2').val();
325 var section
= $(this).siblings('input').attr('id').replace('value_', '').split('_')[0];
326 if ($.inArray(mapper
, ['state_province', 'country']) > -1) {
327 chainSelect(mapper
+ '_id', value
, location_type
, section
);
331 .on('crmLoad', function() {
333 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField
);
338 // Fetch initial options during page refresh - it's more efficient to bundle them in a single ajax request
339 var initialFields
= {}, fetchFields
= false;
340 $('select[id^=mapper][id$="_1"] option:selected', '#Builder').each(function() {
341 var field
= $(this).attr('value');
342 if (typeof(CRM
.searchBuilder
.fieldOptions
[field
]) == 'string' && CRM
.searchBuilder
.fieldOptions
[field
] !== 'yesno') {
343 initialFields
[field
] = [CRM
.searchBuilder
.fieldOptions
[field
], 'getoptions', {field
: field
, sequential
: 1}];
348 CRM
.api3(initialFields
).done(function(data
) {
349 $.each(data
, function(field
, result
) {
350 CRM
.searchBuilder
.fieldOptions
[field
] = result
.values
;
352 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField
);
355 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField
);