Merge remote-tracking branch 'upstream/4.4' into 4.4-master-2014-07-14-13-42-39
[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="crm-form-' + multiSelect.substr(0, 5) + '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 function initialize() {
175 $('.crm-search-block', '#Builder').each(function(blockNo) {
176 var block = $(this);
177 var empty = blockNo + 1 > newBlock;
178 var skippedRow = false;
179 $('tr:not(.crm-search-builder-add-row)', block).each(function(rowNo) {
180 var row = $(this);
181 if ($('select:first', row).val() === '') {
182 if (!skippedRow && (rowNo == 0 || blockNo + 1 == newBlock)) {
183 skippedRow = true;
184 }
185 else {
186 row.hide();
187 }
188 }
189 else {
190 empty = false;
191 }
192 });
193 if (empty) {
194 block.hide();
195 }
196 });
197 }
198
199 $('#crm-main-content-wrapper')
200 // Reset and hide row
201 .on('click', '.crm-reset-builder-row', function() {
202 var row = $(this).closest('tr');
203 $('input, select', row).val('').change();
204 row.hide();
205 // Hide entire block if this is the only visible row
206 if (row.siblings(':visible').length < 2) {
207 row.closest('.crm-search-block').hide();
208 }
209 return false;
210 })
211 // Add new field - if there's a hidden one, show it
212 // Otherwise allow form to submit and fetch more from the server
213 .on('click', 'input[name^=addMore]', function() {
214 var table = $(this).closest('table');
215 if ($('tr:hidden', table).length) {
216 $('tr:hidden', table).first().show();
217 return false;
218 }
219 })
220 // Add new block - if there's a hidden one, show it
221 // Otherwise allow form to submit and fetch more from the server
222 .on('click', '#addBlock', function() {
223 if ($('.crm-search-block:hidden', '#Builder').length) {
224 var block = $('.crm-search-block:hidden', '#Builder').first();
225 block.show();
226 $('tr:first-child, tr.crm-search-builder-add-row', block).show();
227 return false;
228 }
229 })
230 // Handle field and operator selection
231 .on('change', 'select[id^=mapper][id$="_1"], select[id^=operator]', handleUserInputField)
232 // Handle option selection - update hidden value field
233 .on('change', '.crm-search-value select', function() {
234 var value = $(this).val() || '';
235 if ($(this).attr('multiple') == 'multiple' && value.length) {
236 value = '(' + value.join(',') + ')';
237 }
238 $(this).siblings('input').val(value);
239 })
240 .on('crmLoad', function() {
241 initialize();
242 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField);
243 });
244
245 initialize();
246
247 // Fetch initial options during page refresh - it's more efficient to bundle them in a single ajax request
248 var initialFields = {}, fetchFields = false;
249 $('select[id^=mapper][id$="_1"] option:selected', '#Builder').each(function() {
250 var field = $(this).attr('value');
251 if (typeof(CRM.searchBuilder.fieldOptions[field]) == 'string') {
252 initialFields[field] = [CRM.searchBuilder.fieldOptions[field], 'getoptions', {field: field, sequential: 1}];
253 fetchFields = true;
254 }
255 });
256 if (fetchFields) {
257 CRM.api3(initialFields).done(function(data) {
258 $.each(data, function(field, result) {
259 CRM.searchBuilder.fieldOptions[field] = result.values;
260 });
261 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField);
262 });
263 } else {
264 $('select[id^=mapper][id$="_1"]', '#Builder').each(handleUserInputField);
265 }
266 })(cj, CRM);