1// https://civicrm.org/licensing 2(function($, _) { 3 /** 4 * By default this simply loads tabs via ajax CRM.loadPage method 5 * Tabs with class 'ajaxForm' will use CRM.loadForm instead, suitable for most forms 6 * Tabs with class 'livePage' will get popup action links, suitable for crud tables 7 */ 8 $(function($) { 9 // CRM.tabSettings.active is the name of the tab which should open on page load 10 var tabSettings = CRM.tabSettings ? _.cloneDeep(CRM.tabSettings) : {}; 11 tabSettings.active = tabSettings.active ? $('#tab_' + tabSettings.active).prevAll().length : 0; 12 $("#mainTabContainer") 13 .on('tabsbeforeactivate', function(e, ui) { 14 // CRM-14353 - Warn of unsaved changes for all forms except those which have opted out 15 if (CRM.utils.initialValueChanged($('form:not([data-warn-changes=false])', ui.oldPanel))) { 16 CRM.alert(ts('Your changes in the <em>%1</em> tab have not been saved.', {1: ui.oldTab.text()}), ts('Unsaved Changes'), 'warning'); 17 } 18 }) 19 .on('tabsactivate', function(e, ui) { 20 var tabId = ui.newTab.attr('id'); 21 if (tabId && tabId.length) { 22 tabId = tabId.slice(4); // Remove leading 'tab_' 23 updateUrlParameter('selectedChild', tabId); 24 } 25 }) 26 .on('tabsbeforeload', function(e, ui) { 27 // Use civicrm ajax wrappers rather than the default $.load 28 if (!ui.panel.data("civiCrmSnippet")) { 29 var method = ui.tab.hasClass('ajaxForm') ? 'loadForm' : 'loadPage'; 30 var params = {target: ui.panel}; 31 if (method === 'loadForm') { 32 params.autoClose = params.openInline = params.cancelButton = params.refreshAction = false; 33 ui.panel.on('crmFormLoad', function() { 34 // Hack: "Save and done" and "Cancel" buttons submit without ajax 35 $('.cancel.crm-form-submit, button[name$=upload_done]', this).on('click', function(e) { 36 $(this).closest('form').ajaxFormUnbind(); 37 }); 38 }); 39 } 40 if (ui.tab.hasClass('livePage') && CRM.config.ajaxPopupsEnabled) { 41 ui.panel 42 .off('click.crmLivePage') 43 .on('click.crmLivePage', 'a.button, a.action-item', CRM.popup) 44 .on('crmPopupFormSuccess.crmLivePage', 'a.button, a.action-item:not(.crm-enable-disable)', CRM.refreshParent); 45 } 46 ui.panel 47 .off('.tabInfo') 48 .on('crmLoad.tabInfo crmFormSuccess.tabInfo', function(e, data) { 49 if (data) { 50 if (typeof(data.tabCount) !== 'undefined') { 51 CRM.tabHeader.updateCount(ui.tab, data.tabCount); 52 } 53 if (typeof(data.tabValid) !== 'undefined') { 54 var method = data.tabValid ? 'removeClass' : 'addClass'; 55 ui.tab[method]('disabled'); 56 } 57 } 58 }); 59 CRM[method]($('a', ui.tab).attr('href'), params); 60 } 61 e.preventDefault(); 62 }) 63 .tabs(tabSettings); 64 // Any load/submit event could potentially call for tabs to refresh. 65 $(document).on('crmLoad.tabInfo crmFormSuccess.tabInfo', function(e, data) { 66 if (data && $.isPlainObject(data.updateTabs)) { 67 $.each(data.updateTabs, CRM.tabHeader.updateCount); 68 $.each(data.updateTabs, CRM.tabHeader.resetTab); 69 } 70 }); 71 }); 72 73 // Utility functions 74 CRM.tabHeader = CRM.tabHeader || {}; 75 76 /** 77 * Return active tab 78 */ 79 CRM.tabHeader.getActiveTab = function() { 80 return $('.ui-tabs-active', '#mainTabContainer'); 81 }; 82 83 /** 84 * Make a given tab the active one 85 * @param tab jQuery selector 86 */ 87 CRM.tabHeader.focus = function(tab) { 88 $('#mainTabContainer').tabs('option', 'active', $(tab).prevAll().length); 89 }; 90 91 /** 92 * @param tab jQuery selector 93 * @returns panel jQuery object 94 */ 95 CRM.tabHeader.getTabPanel = function(tab) { 96 var selector = $(tab).attr('aria-controls'); 97 return selector ? $('#' + selector) : $(); 98 }; 99 100 /** 101 * @param tab jQuery selector 102 * @returns {string|null} 103 */ 104 function getCountClass(tab) { 105 var $tab = $(tab), 106 css = $tab.attr('class') || '', 107 val = css.match(/(crm-count-\d+)/); 108 return val && val.length ? val[0] : null; 109 } 110 111 /** 112 * @param tab jQuery selector 113 * @returns {Number|null} 114 */ 115 CRM.tabHeader.getCount = function(tab) { 116 var cssClass = getCountClass(tab); 117 return cssClass ? parseInt(cssClass.slice(10), 10) : null; 118 }; 119 120 /** 121 * Update the counter in a tab 122 * @param tab jQuery selector 123 * @param count {Number} 124 */ 125 CRM.tabHeader.updateCount = function(tab, count) { 126 if (typeof count === 'boolean') { 127 return; 128 } 129 var oldClass = getCountClass(tab); 130 if (oldClass) { 131 $(tab).removeClass(oldClass); 132 } 133 $(tab) 134 .addClass('crm-count-' + count) 135 .find('a em').html('' + count); 136 }; 137 138 /** 139 * Refresh tab immediately if it is active (or force=true) 140 * otherwise ensure it will be refreshed next time the user clicks on it 141 * 142 * @param tab 143 * @param force 144 */ 145 CRM.tabHeader.resetTab = function(tab, force) { 146 var $panel = CRM.tabHeader.getTabPanel(tab); 147 // Angular-based tabs - this event will trigger searchKit displays to refresh 148 if ($panel.children('crm-angular-js').length) { 149 $panel.find('form').first().trigger('crmPopupFormSuccess'); 150 } 151 else if ($(tab).hasClass('ui-tabs-active')) { 152 $panel.crmSnippet('refresh'); 153 } 154 else if (force) { 155 if ($panel.data("civiCrmSnippet")) { 156 $panel.crmSnippet('refresh'); 157 } else { 158 $("#mainTabContainer").trigger('tabsbeforeload', [{panel: $panel, tab: $(tab)}]); 159 } 160 } 161 else if ($panel.data("civiCrmSnippet")) { 162 $panel.crmSnippet('destroy'); 163 } 164 }; 165 166 /** 167 * Updates the query parameter in the page URL, 168 * or adds the parameter if its not currently there. 169 * 170 * @param {string} param 171 * @param {string} value 172 * @return void 173 */ 174 function updateUrlParameter(param, value) { 175 var newUrl, 176 newSearch, 177 href = window.location.href, 178 search = window.location.search; 179 if (search.indexOf('?' + param) !== -1 || search.indexOf('&' + param) !== -1 ) { 180 var regExp = new RegExp(param + "(.+?)(&|$)", "g"); 181 newSearch = search.replace(regExp, param + "=" + value + "$2"); 182 newUrl = href.replace(search, newSearch); 183 } else if (search.length) { 184 newSearch = search + '&' + param + "=" + value; 185 newUrl = href.replace(search, newSearch); 186 } else { 187 newSearch = '?' + param + "=" + value; 188 newUrl = location.protocol + '//' + location.hostname + location.pathname + newSearch + location.hash; 189 } 190 window.history.replaceState("", "", newUrl); 191 } 192})(CRM.$, CRM._);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.