Merge pull request #1843 from ravishnair/CRM-12310
[civicrm-core.git] / templates / CRM / Contact / Form / Search / Builder.js
1 // http://civicrm.org/licensing
2 (function($, CRM) {
3 'use strict';
4
5 /**
6 * Handle user input - field or operator selection.
7 *
8 * Decide whether to display select drop down, regular text or date
9 * field for the given field and row.
10 */
11 function handleUserInputField() {
12 var row = $(this).closest('tr');
13 var field = $('select[id^=mapper][id$="_1"]', row).val();
14 var op = $('select[id^=operator]', row).val();
15
16 // These Ops don't get any input field.
17 var noFieldOps = ['', 'IS EMPTY', 'IS NOT EMPTY', 'IS NULL', 'IS NOT NULL'];
18
19 if ($.inArray(op, noFieldOps) > -1) {
20 // Hide the fields and return.
21 $('.crm-search-value', row).hide().find('input, select').val('');
22 return;
23 }
24 $('.crm-search-value', row).show();
25
26 if (!CRM.searchBuilder.fieldOptions[field]) {
27 removeSelect(row);
28 }
29 else {
30 buildSelect(row, field, op);
31 }
32
33 if ($.inArray(field, CRM.searchBuilder.dateFields) < 0) {
34 removeDate(row);
35 }
36 else {
37 buildDate(row, op);
38 }
39 }
40
41 /**
42 * Add select list if appropriate for this operation
43 * @param row: jQuery object
44 * @param field: string
45 */
46 function buildSelect(row, field, op) {
47 var multiSelect = '';
48 // Operators that will get a single drop down list of choices.
49 var dropDownSingleOps = ['=', '!='];
50 // Multiple select drop down list.
51 var dropDownMultipleOps = ['IN', 'NOT IN'];
52
53 if ($.inArray(op, dropDownMultipleOps) > -1) {
54 multiSelect = 'multiple="multiple"';
55 }
56 else if ($.inArray(op, dropDownSingleOps) < 0) {
57 // If this op is neither supported by single or multiple selects, then we should not render a select list.
58 removeSelect(row);
59 return;
60 }
61
62 $('.crm-search-value select', row).remove();
63 $('input[id^=value]', row)
64 .hide()
65 .after('<select class="form-select required" ' + multiSelect + '><option value="">' + ts('Loading') + '...</option></select>');
66
67 fetchOptions(row, field);
68 }
69
70 /**
71 * Retrieve option list for given row
72 * @param row: jQuery object
73 * @param field: string
74 */
75 function fetchOptions(row, field) {
76 if (CRM.searchBuilder.fieldOptions[field] === 'yesno') {
77 CRM.searchBuilder.fieldOptions[field] = [{key: 1, value: ts('Yes')}, {key: 0, value: ts('No')}];
78 }
79 if (typeof(CRM.searchBuilder.fieldOptions[field]) == 'string') {
80 CRM.api(CRM.searchBuilder.fieldOptions[field], 'getoptions', {field: field, sequential: 1}, {
81 success: function(result, settings) {
82 var field = settings.field;
83 if (result.count) {
84 CRM.searchBuilder.fieldOptions[field] = result.values;
85 buildOptions(settings.row, field);
86 }
87 else {
88 removeSelect(settings.row);
89 }
90 },
91 error: function(result, settings) {
92 removeSelect(settings.row);
93 },
94 row: row,
95 field: field
96 });
97 }
98 else {
99 buildOptions(row, field);
100 }
101 }
102
103 /**
104 * Populate option list for given row
105 * @param row: jQuery object
106 * @param field: string
107 */
108 function buildOptions(row, field) {
109 var select = $('.crm-search-value select', row);
110 var value = $('input[id^=value]', row).val();
111 if (value.length && value.charAt(0) == '(' && value.charAt(value.length - 1) == ')') {
112 value = value.slice(1, -1);
113 }
114 var options = value.split(',');
115 if (select.attr('multiple') == 'multiple') {
116 select.find('option').remove();
117 }
118 else {
119 select.find('option').text(ts('- select -'));
120 if (options.length > 1) {
121 options = [options[0]];
122 }
123 }
124 $.each(CRM.searchBuilder.fieldOptions[field], function(key, option) {
125 var selected = ($.inArray(option.key, options) > -1) ? 'selected="selected"' : '';
126 select.append('<option value="' + option.key + '"' + selected + '>' + option.value + '</option>');
127 });
128 select.change();
129 }
130
131 /**
132 * Remove select options and restore input to a plain textfield
133 * @param row: jQuery object
134 */
135 function removeSelect(row) {
136 $('.crm-search-value input', row).show();
137 $('.crm-search-value select', row).remove();
138 }
139
140 /**
141 * Add a datepicker if appropriate for this operation
142 * @param row: jQuery object
143 */
144 function buildDate(row, op) {
145 var input = $('.crm-search-value input', row);
146 // These are operations that should not get a datepicker
147 var datePickerOp = ($.inArray(op, ['IN', 'NOT IN', 'LIKE', 'RLIKE']) < 0);
148 if (!datePickerOp) {
149 removeDate(row);
150 }
151 else if (!input.hasClass('hasDatepicker')) {
152 input.addClass('dateplugin').datepicker({
153 dateFormat: 'yymmdd',
154 changeMonth: true,
155 changeYear: true,
156 yearRange: '-100:+20'
157 });
158 }
159 }
160
161 /**
162 * Remove datepicker
163 * @param row: jQuery object
164 */
165 function removeDate(row) {
166 var input = $('.crm-search-value input', row);
167 if (input.hasClass('hasDatepicker')) {
168 input.removeClass('dateplugin').val('').datepicker('destroy');
169 }
170 }
171
172 // Initialize display: Hide empty blocks & fields
173 var newBlock = CRM.searchBuilder && CRM.searchBuilder.newBlock || 0;
174 $('.crm-search-block', '#Builder').each(function(blockNo) {
175 var block = $(this);
176 var empty = blockNo + 1 > newBlock;
177 var skippedRow = false;
178 $('tr:not(.crm-search-builder-add-row)', block).each(function(rowNo) {
179 var row = $(this);
180 if ($('select:first', row).val() === '') {
181 if (!skippedRow && (rowNo == 0 || blockNo + 1 == newBlock)) {
182 skippedRow = true;
183 }
184 else {
185 row.hide();
186 }
187 }
188 else {
189 empty = false;
190 }
191 });
192 if (empty) {
193 block.hide();
194 }
195 });
196
197 $('#Builder')
198 // Reset and hide row
199 .on('click', '.crm-reset-builder-row', function() {
200 var row = $(this).closest('tr');
201 $('input, select', row).val('').change();
202 row.hide();
203 // Hide entire block if this is the only visible row
204 if (row.siblings(':visible').length < 2) {
205 row.closest('.crm-search-block').hide();
206 }
207 return false;
208 })
209 // Add new field - if there's a hidden one, show it
210 // Otherwise allow form to submit and fetch more from the server
211 .on('click', 'input[name^=addMore]', function() {
212 var table = $(this).closest('table');
213 if ($('tr:hidden', table).length) {
214 $('tr:hidden', table).first().show();
215 return false;
216 }
217 })
218 // Add new block - if there's a hidden one, show it
219 // Otherwise allow form to submit and fetch more from the server
220 .on('click', '#addBlock', function() {
221 if ($('.crm-search-block:hidden', '#Builder').length) {
222 var block = $('.crm-search-block:hidden', '#Builder').first();
223 block.show();
224 $('tr:first-child, tr.crm-search-builder-add-row', block).show();
225 return false;
226 }
227 })
228 // Handle field and operator selection
229 .on('change', 'select[id^=mapper][id$="_1"], select[id^=operator]', handleUserInputField)
230 // Handle option selection - update hidden value field
231 .on('change', '.crm-search-value select', function() {
232 var value = $(this).val() || '';
233 if ($(this).attr('multiple') == 'multiple' && value.length) {
234 value = '(' + value.join(',') + ')';
235 }
236 $(this).siblings('input').val(value);
237 })
238 ;
239
240 $().crmAccordions();
241 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField);
242 })(cj, CRM);