2 +--------------------------------------------------------------------+
3 | Copyright CiviCRM LLC. All rights reserved. |
5 | This work is published under the GNU AGPLv3 license with some |
6 | permitted exceptions and without any warranty. For full license |
7 | and copyright information, see https://civicrm.org/licensing |
8 +--------------------------------------------------------------------+
10 {* This form is for Contact Add/Edit interface *}
12 {include file="CRM/Contact/Form/Edit/$blockName.tpl"}
15 {include file="CRM/Contact/Form/Edit/Lock.tpl"}
17 <div class="crm-form-block crm-search-form-block">
18 {if call_user_func(array('CRM_Core_Permission','check'), 'administer CiviCRM') }
19 <a href='{crmURL p="civicrm/admin/setting/preferences/display" q="reset=1"}' title="{ts}Click here to configure the panes.{/ts}"><i class="crm-i fa-wrench" aria-hidden="true"></i></a>
21 <span style="float:right;"><a href="#expand" id="expand">{ts}Expand all tabs{/ts}</a></span>
22 <div class="crm-submit-buttons">
23 {include file="CRM/common/formButtons.tpl" location="top"}
26 <div class="crm-accordion-wrapper crm-contactDetails-accordion">
27 <div class="crm-accordion-header">
28 {ts}Contact Details{/ts}
29 </div><!-- /.crm-accordion-header -->
30 <div class="crm-accordion-body" id="contactDetails">
31 <div id="contactDetails">
32 <div class="crm-section contact_basic_information-section">
33 {include file="CRM/Contact/Form/Edit/$contactType.tpl"}
35 <table class="crm-section contact_information-section form-layout-compressed">
36 {foreach from=$blocks item="label" key="block"}
37 {include file="CRM/Contact/Form/Edit/$block.tpl"}
40 <table class="crm-section contact_source-section form-layout-compressed">
42 <td>{$form.contact_source.label} {help id="id-source"}<br />
43 {$form.contact_source.html|crmAddClass:twenty}
45 <td>{$form.external_identifier.label} {help id="id-external-id"}<br />
46 {$form.external_identifier.html}
50 <label for="internal_identifier_display">{ts}Contact ID{/ts} {help id="id-contact-id"}</label><br />
51 <input id="internal_identifier_display" type="text" class="crm-form-text six" size="6" readonly="readonly" value="{$contactId}">
56 <table class="image_URL-section form-layout-compressed">
59 {$form.image_URL.label} {help id="id-upload-image" file="CRM/Contact/Form/Contact.hlp"}<br />
60 {$form.image_URL.html|crmAddClass:twenty}
61 {if !empty($imageURL)}
62 {include file="CRM/Contact/Page/ContactImage.tpl"}
69 {$form._qf_Contact_refresh_dedupe.html}
72 {$form._qf_Contact_upload_duplicate.html}
74 <div class="spacer"></div>
76 </div><!-- /.crm-accordion-body -->
77 </div><!-- /.crm-accordion-wrapper -->
79 {foreach from = $editOptions item = "title" key="name"}
80 {if $name eq 'CustomData' }
81 <div id='customData'>{include file="CRM/Contact/Form/Edit/CustomData.tpl"}</div>
83 {include file="CRM/Contact/Form/Edit/$name.tpl"}
86 <div class="crm-submit-buttons">
87 {include file="CRM/common/formButtons.tpl" location="bottom"}
92 <script type="text/javascript" >
94 var $form = $("form.{/literal}{$form.formClass}{literal}"),
95 action = {/literal}{$action|intval}{literal},
96 cid = {/literal}{$contactId|intval}{literal},
99 $('.crm-accordion-body').each( function() {
100 //remove tab which doesn't have any element
101 if ( ! $.trim( $(this).text() ) ) {
103 prevEle = $(this).prev();
107 //open tab if form rule throws error
108 if ( $(this).children().find('span.crm-error').text().length > 0 ) {
109 $(this).parents('.collapsed').crmAccordionToggle();
113 $('.crm-accordion-wrapper').not('.crm-accordion-wrapper .crm-accordion-wrapper').each(function() {
116 $('#crm-container').on('change click', '.crm-accordion-body :input, .crm-accordion-body a', function() {
117 highlightTabs($(this).parents('.crm-accordion-wrapper'));
120 function highlightTabs(tab) {
121 //highlight the tab having data inside.
122 $('.crm-accordion-body :input', tab).each( function() {
124 switch($(this).prop('type')) {
127 if($(this).is(':checked') && !$(this).is('[id$=IsPrimary],[id$=IsBilling]')) {
128 $('.crm-accordion-header:first', tab).addClass('active');
136 $('.crm-accordion-header:first', tab).addClass('active');
142 case 'select-multiple':
143 if($(this).val() && $('option[value=""]', this).length > 0) {
144 $('.crm-accordion-header:first', tab).addClass('active');
150 if($(this).next().html()) {
151 $('.crm-accordion-header:first', tab).addClass('active');
156 $('.crm-accordion-header:first', tab).removeClass('active');
160 $('a#expand').click( function() {
161 if( $(this).attr('href') == '#expand') {
162 var message = {/literal}"{ts escape='js'}Collapse all tabs{/ts}"{literal};
163 $(this).attr('href', '#collapse');
164 $('.crm-accordion-wrapper.collapsed').crmAccordionToggle();
167 var message = {/literal}"{ts escape='js'}Expand all tabs{/ts}"{literal};
168 $('.crm-accordion-wrapper:not(.collapsed)').crmAccordionToggle();
169 $(this).attr('href', '#expand');
171 $(this).html(message);
175 $('.customDataPresent').change(function() {
176 var values = $("#contact_sub_type").val();
177 CRM.buildCustomData({/literal}"{$contactType}"{literal}, values).one('crmLoad', function() {
179 loadMultiRecordFields(values);
183 function loadMultiRecordFields(subTypeValues) {
184 if (subTypeValues === false) {
185 subTypeValues = null;
187 else if (!subTypeValues) {
188 subTypeValues = {/literal}"{$paramSubType}"{literal};
190 function loadNextRecord(i, groupValue, groupCount) {
191 if (i < groupCount) {
192 CRM.buildCustomData({/literal}"{$contactType}"{literal}, subTypeValues, null, i, groupValue, true).one('crmLoad', function() {
194 loadNextRecord(i+1, groupValue, groupCount);
199 {foreach from=$customValueCount item="groupCount" key="groupValue"}
200 {if $groupValue}{literal}
201 loadNextRecord(1, {/literal}{$groupValue}{literal}, {/literal}{$groupCount}{literal});
208 loadMultiRecordFields();
210 {/literal}{if $oldSubtypes}{literal}
211 $('button[name=_qf_Contact_upload_view], button[name=_qf_Contact_upload_new]').click(function() {
212 var submittedSubtypes = $('#contact_sub_type').val();
213 var oldSubtypes = {/literal}{$oldSubtypes}{literal};
216 $.each(oldSubtypes, function(index, subtype) {
217 if ( $.inArray(subtype, submittedSubtypes) < 0 ) {
222 return confirm({/literal}'{ts escape="js"}One or more contact subtypes have been de-selected from the list for this contact. Any custom data associated with de-selected subtype will be removed as long as the contact does not have a contact subtype still selected. Click OK to proceed, or Cancel to review your changes before saving.{/ts}'{literal});
226 {/literal}{/if}{literal}
228 // Handle delete of multi-record custom data
229 $form.on('click', '.crm-custom-value-del', function(e) {
232 msg = '{/literal}{ts escape="js"}The record will be deleted immediately. This action cannot be undone.{/ts}{literal}';
233 CRM.confirm({title: $el.attr('title'), message: msg})
234 .on('crmConfirm:yes', function() {
235 var url = CRM.url('civicrm/ajax/customvalue');
236 var request = $.post(url, $el.data('post'));
237 CRM.status({success: '{/literal}{ts escape="js"}Record Deleted{/ts}{literal}'}, request);
238 var addClass = '.add-more-link-' + $el.data('post').groupID;
239 $el.closest('div.crm-custom-accordion').remove();
240 $('div' + addClass).last().show();
244 {/literal}{* Ajax check for matching contacts *}
245 {if $checkSimilar == 1}
246 var contactType = {$contactType|@json_encode},
247 rules = {*$ruleFields|@json_encode*}{literal}[
258 dupeTpl = _.template($('#duplicates-msg-tpl').html()),
260 $.each(rules, function(i, field) {
261 // Match regular fields
262 var $el = $('#' + field + ', #' + field + '_1_' + field, $form).filter(':input');
263 // Match custom fields
264 if (!$el.length && field.lastIndexOf('_') > 0) {
265 var pieces = field.split('_');
266 field = 'custom_' + pieces[pieces.length-1];
267 $el = $('#' + field + ', [name=' + field + '_-1]', $form).filter(':input');
270 ruleFields[field] = $el;
271 $ruleElements = $ruleElements.add($el);
274 // Check for matches on input when action == ADD
276 $ruleElements.on('change', function () {
277 if ($(this).is('input[type=text]') && $(this).val().length < 3) {
280 checkMatches().done(function (data) {
282 title: data.count == 1 ? {/literal}"{ts escape='js'}Similar Contact Found{/ts}" : "{ts escape='js'}Similar Contacts Found{/ts}"{literal},
283 info: "{/literal}{ts escape='js'}If the contact you were trying to add is listed below, click their name to view or edit their record{/ts}{literal}:",
284 contacts: data.values,
288 openDupeAlert(params);
294 // Call the api to check for matching contacts
295 function checkMatches(rule) {
296 var match = {contact_type: contactType},
297 response = $.Deferred(),
298 checkNum = ++runningCheck,
300 options: {sort: 'sort_name'},
301 return: ['display_name', 'email']
303 $.each(ruleFields, function(fieldName, ruleField) {
304 if (ruleField.length > 1) {
305 match[fieldName] = ruleField.filter(':checked').val();
306 } else if (ruleField.is('input[type=text]')) {
307 if (ruleField.val().length > 2) {
308 match[fieldName] = ruleField.val() + (rule ? '' : '%');
311 match[fieldName] = ruleField.val();
314 // CRM-20565 - Need a good default matching rule before using the dedupe engine for checking on-the-fly.
315 // Defaulting to contact.get.
316 var action = rule ? 'duplicatecheck' : 'get';
318 params.rule_type = rule;
319 params.match = match;
320 params.exclude = cid ? [cid] : [];
322 _.extend(params, match);
324 CRM.api3('contact', action, params).done(function(data) {
325 // If a new request has started running, cancel this one.
326 if (checkNum < runningCheck) {
329 response.resolve(data);
335 // Open an alert about possible duplicate contacts
336 function openDupeAlert(data, iconType) {
337 // Close msg if it exists
338 matchMessage && matchMessage.close && matchMessage.close();
339 matchMessage = CRM.alert(dupeTpl(data), _.escape(data.title), iconType, {expires: false});
340 $('.matching-contacts-actions', '#crm-notification-container').on('click', 'a', function() {
341 // No confirmation dialog on click
342 $('[data-warn-changes=true]').attr('data-warn-changes', 'false');
346 // Update the duplicate alert after getting results
347 function updateDupeAlert(data, iconType) {
348 var $alert = $('.matching-contacts-actions', '#crm-notification-container')
349 .closest('.ui-notify-message');
351 .removeClass('crm-msg-loading success info alert error')
353 .find('h1').text(data.title);
355 .find('.notify-content')
356 .html(dupeTpl(data));
359 // Ajaxify the "Check for Matching Contact(s)" button
360 $('#_qf_Contact_refresh_dedupe').click(function(e) {
361 var placeholder = {{/literal}
362 title: "{ts escape='js'}Fetching Matches{/ts}",
363 info: "{ts escape='js'}Checking for similar contacts...{/ts}",
366 openDupeAlert(placeholder, 'crm-msg-loading');
367 checkMatches('Supervised').done(function(data) {
369 title: data.count ? {/literal}"{ts escape='js'}Similar Contact Found{/ts}" : "{ts escape='js'}None Found{/ts}"{literal},
371 "{/literal}{ts escape='js'}If the contact you were trying to add is listed below, click their name to view or edit their record{/ts}{literal}:" :
372 "{/literal}{ts escape='js'}No matches found using the default Supervised deduping rule.{/ts}{literal}",
373 contacts: data.values,
376 updateDupeAlert(params, data.count ? 'alert' : 'success');
380 {/literal}{/if}{literal}
384 <script type="text/template" id="duplicates-msg-tpl">
386 <ul class="matching-contacts-actions">
387 <% _.forEach(contacts, function(contact) { %>
389 <a href="<%= CRM.url('civicrm/contact/view', {reset: 1, cid: contact.id}) %>">
390 <%- contact.display_name %>
394 <% var params = {reset: 1, action: 'update', oid: contact.id > cid ? contact.id : cid, cid: contact.id > cid ? cid : contact.id }; %>
395 (<a href="<%= CRM.url('civicrm/contact/merge', params) %>">{/literal}{ts}Merge{/ts}{literal}</a>)
403 {* jQuery validate *}
404 {include file="CRM/Form/validate.tpl"}
406 {* include common additional blocks tpl *}
407 {include file="CRM/common/additionalBlocks.tpl"}