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 ((field
in CRM
.searchBuilder
.fieldTypes
) === true &&
52 CRM
.searchBuilder
.fieldTypes
[field
] == 'Date'
62 * Add appropriate operator to selected field
63 * @param operator: jQuery object
64 * @param options: array
66 function buildOperator(operator
, options
) {
67 var selected
= operator
.val();
69 $.each(options
, function(value
, label
) {
70 operator
.append('<option value="' + value
+ '">' + label
+ '</option>');
72 operator
.val(selected
);
76 * Add select list if appropriate for this operation
77 * @param row: jQuery object
78 * @param field: string
79 * @param skip_fetch: boolean
81 function buildSelect(row
, field
, op
, skip_fetch
) {
83 // Operators that will get a single drop down list of choices.
84 var dropDownSingleOps
= ['=', '!='];
85 // Multiple select drop down list.
86 var dropDownMultipleOps
= ['IN', 'NOT IN'];
88 if ($.inArray(op
, dropDownMultipleOps
) > -1) {
89 multiSelect
= 'multiple="multiple"';
91 else if ($.inArray(op
, dropDownSingleOps
) < 0) {
92 // If this op is neither supported by single or multiple selects, then we should not render a select list.
97 $('.crm-search-value select', row
).remove();
98 $('input[id^=value]', row
)
100 .after('<select class="crm-form-' + multiSelect
.substr(0, 5) + 'select required" ' + multiSelect
+ '><option value="">' + ts('Loading') + '...</option></select>');
102 // Avoid reloading state/county options IF already built, identified by skip_fetch
104 buildOptions(row
, field
);
107 fetchOptions(row
, field
);
112 * Retrieve option list for given row
113 * @param row: jQuery object
114 * @param field: string
116 function fetchOptions(row
, field
) {
117 if (CRM
.searchBuilder
.fieldOptions
[field
] === 'yesno') {
118 CRM
.searchBuilder
.fieldOptions
[field
] = [{key
: 1, value
: ts('Yes')}, {key
: 0, value
: ts('No')}];
120 if (typeof(CRM
.searchBuilder
.fieldOptions
[field
]) == 'string') {
121 CRM
.api(CRM
.searchBuilder
.fieldOptions
[field
], 'getoptions', {field
: field
, sequential
: 1}, {
122 success: function(result
, settings
) {
123 var field
= settings
.field
;
125 CRM
.searchBuilder
.fieldOptions
[field
] = result
.values
;
126 buildOptions(settings
.row
, field
);
129 removeSelect(settings
.row
);
132 error: function(result
, settings
) {
133 removeSelect(settings
.row
);
140 buildOptions(row
, field
);
145 * Populate option list for given row
146 * @param row: jQuery object
147 * @param field: string
149 function buildOptions(row
, field
) {
150 var select
= $('.crm-search-value select', row
);
151 var value
= $('input[id^=value]', row
).val();
152 if (value
.length
&& value
.charAt(0) == '(' && value
.charAt(value
.length
- 1) == ')') {
153 value
= value
.slice(1, -1);
155 var options
= value
.split(',');
156 if (select
.attr('multiple') == 'multiple') {
157 select
.find('option').remove();
160 select
.find('option').text(ts('- select -'));
161 if (options
.length
> 1) {
162 options
= [options
[0]];
165 $.each(CRM
.searchBuilder
.fieldOptions
[field
], function(key
, option
) {
166 var optionKey
= option
.key
;
167 if ($.inArray(field
, CRM
.searchBuilder
.searchByLabelFields
) >= 0) {
168 optionKey
= option
.value
;
170 var selected
= ($.inArray(''+optionKey
, options
) > -1) ? 'selected="selected"' : '';
171 select
.append('<option value="' + optionKey
+ '"' + selected
+ '>' + option
.value
+ '</option>');
177 * Remove select options and restore input to a plain textfield
178 * @param row: jQuery object
180 function removeSelect(row
) {
181 $('.crm-search-value input', row
).show();
182 $('.crm-search-value select', row
).remove();
186 * Add a datepicker if appropriate for this operation
187 * @param row: jQuery object
189 function buildDate(row
, op
) {
190 var input
= $('.crm-search-value input', row
);
191 // These are operations that should not get a datepicker
192 var datePickerOp
= ($.inArray(op
, ['IN', 'NOT IN', 'LIKE', 'RLIKE']) < 0);
196 else if (!input
.hasClass('hasDatepicker')) {
197 input
.addClass('dateplugin').datepicker({
198 dateFormat
: 'yymmdd',
201 yearRange
: '-100:+20'
208 * @param row: jQuery object
210 function removeDate(row
) {
211 var input
= $('.crm-search-value input', row
);
212 if (input
.hasClass('hasDatepicker')) {
213 input
.removeClass('dateplugin').val('').datepicker('destroy');
218 * Load and build select options for state IF country is chosen OR county options if state is chosen
219 * @param mapper: string
220 * @param value: integer
221 * @param location_type: integer
222 * @param section: section in which the country/state selection change occurred
224 function chainSelect(mapper
, value
, location_type
, section
) {
227 field
: (mapper
== 'country_id') ? 'state_province' : 'county',
229 apiParams
[mapper
] = value
;
230 var fieldName
= apiParams
.field
;
231 CRM
.api3('address', 'getoptions', apiParams
, {
232 success: function(result
) {
234 CRM
.searchBuilder
.fieldOptions
[fieldName
] = result
.values
;
235 $('select[id^=mapper_' + section
+ '][id$="_1"]').each(function() {
236 var row
= $(this).closest('tr');
237 var op
= $('select[id^=operator]', row
).val();
238 if ($(this).val() === fieldName
&& location_type
=== $('select[id^=mapper][id$="_2"]', row
).val()) {
239 buildSelect(row
, fieldName
, op
, true);
247 // Initialize display: Hide empty blocks & fields
248 var newBlock
= CRM
.searchBuilder
&& CRM
.searchBuilder
.newBlock
|| 0;
249 function initialize() {
250 $('.crm-search-block', '#Builder').each(function(blockNo
) {
252 var empty
= blockNo
+ 1 > newBlock
;
253 var skippedRow
= false;
254 $('tr:not(.crm-search-builder-add-row)', block
).each(function(rowNo
) {
256 if ($('select:first', row
).val() === '') {
257 if (!skippedRow
&& (rowNo
=== 0 || blockNo
+ 1 == newBlock
)) {
275 $('#crm-main-content-wrapper')
276 // Reset and hide row
277 .on('click', '.crm-reset-builder-row', function() {
278 var row
= $(this).closest('tr');
279 $('input, select', row
).val('').change();
281 // Hide entire block if this is the only visible row
282 if (row
.siblings(':visible').length
< 2) {
283 row
.closest('.crm-search-block').hide();
287 // Add new field - if there's a hidden one, show it
288 // Otherwise allow form to submit and fetch more from the server
289 .on('click', 'input[name^=addMore]', function() {
290 var table
= $(this).closest('table');
291 if ($('tr:hidden', table
).length
) {
292 $('tr:hidden', table
).first().show();
296 // Add new block - if there's a hidden one, show it
297 // Otherwise allow form to submit and fetch more from the server
298 .on('click', '#addBlock', function() {
299 if ($('.crm-search-block:hidden', '#Builder').length
) {
300 var block
= $('.crm-search-block:hidden', '#Builder').first();
302 $('tr:first-child, tr.crm-search-builder-add-row', block
).show();
306 // Handle field and operator selection
307 .on('change', 'select[id^=mapper][id$="_1"], select[id^=operator]', handleUserInputField
)
308 // Handle option selection - update hidden value field
309 .on('change', '.crm-search-value select', function() {
310 var value
= $(this).val() || '';
311 if ($(this).attr('multiple') == 'multiple' && value
.length
) {
312 value
= value
.join(',');
314 $(this).siblings('input').val(value
);
316 var mapper
= $('#' + $(this).siblings('input').attr('id').replace('value_', 'mapper_') + '_1').val();
317 var location_type
= $('#' + $(this).siblings('input').attr('id').replace('value_', 'mapper_') + '_2').val();
318 var section
= $(this).siblings('input').attr('id').replace('value_', '').split('_')[0];
319 if ($.inArray(mapper
, ['state_province', 'country']) > -1) {
320 chainSelect(mapper
+ '_id', value
, location_type
, section
);
324 .on('crmLoad', function() {
326 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField
);
331 // Fetch initial options during page refresh - it's more efficient to bundle them in a single ajax request
332 var initialFields
= {}, fetchFields
= false;
333 $('select[id^=mapper][id$="_1"] option:selected', '#Builder').each(function() {
334 var field
= $(this).attr('value');
335 if (typeof(CRM
.searchBuilder
.fieldOptions
[field
]) == 'string' && CRM
.searchBuilder
.fieldOptions
[field
] !== 'yesno') {
336 initialFields
[field
] = [CRM
.searchBuilder
.fieldOptions
[field
], 'getoptions', {field
: field
, sequential
: 1}];
341 CRM
.api3(initialFields
).done(function(data
) {
342 $.each(data
, function(field
, result
) {
343 CRM
.searchBuilder
.fieldOptions
[field
] = result
.values
;
345 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField
);
348 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField
);