/** * Admin Add-ons page JS handler as a class. * * Add-ons list is rendered server-side via AdminAddonsPage::render_addons() * through TemplateAJAX::load_content_via_ajax(). This class only handles * UI interactions and addon actions via window.lpAJAXG. * * @since 4.4.7 * @version 1.0.0 */ import * as lpToastify from 'lpAssetsJsPath/lpToastify.js'; import * as lpUtils from 'lpAssetsJsPath/utils.js'; class AdminAddons { constructor() { this.elAddonsPage = null; this.urlParams = new URLSearchParams( window.location.search ); } static selectors = { elAddonsPage: '.lp-addons-page', elLPAddons: '#lp-addons', elAddonItem: '.lp-addon-item', elBtnAction: '.btn-addon-action', elNavTab: '.nav-tab', elNavTabActive: '.nav-tab.nav-tab-active', elCategory: '.lp-addons-category', elCategoryActive: '.lp-addons-category.active', elSearchInput: '#lp-search-addons__input', elItemPurchase: '.lp-addon-item__purchase', elPurchaseInstall: '.purchase-install', elPurchaseCode: '.enter-purchase-code', elLicense: '.lp-addon-license', }; init() { // Content is injected via AJAX into .lp-target, wait for #lp-addons. lpUtils.lpOnElementReady( AdminAddons.selectors.elLPAddons, () => { this.elAddonsPage = document.querySelector( AdminAddons.selectors.elAddonsPage ); if ( ! this.elAddonsPage ) { return; } this.filterAddons(); this.events(); } ); } /** * Register events. * * @return void */ events() { lpUtils.eventHandlers( 'click', [ { selector: AdminAddons.selectors.elBtnAction, class: this, callBack: this.handleBtnAction.name, }, { selector: AdminAddons.selectors.elNavTab, class: this, callBack: this.handleTabClick.name, }, { selector: AdminAddons.selectors.elCategory, class: this, callBack: this.handleCategoryClick.name, }, { selector: AdminAddons.selectors.elPurchaseCode, class: this, callBack: this.handlePurchaseCodeClick.name, }, ] ); lpUtils.eventHandlers( 'input', [ { selector: AdminAddons.selectors.elSearchInput, class: this, callBack: this.handleSearchInput.name, }, { selector: AdminAddons.selectors.elPurchaseCode, class: this, callBack: this.handlePurchaseCodeInput.name, }, ] ); } /** * Get addon data embedded on the item element. * * @param {Element} elAddonItem Addon item element. * @return {Object|null} */ getAddonData( elAddonItem ) { try { return JSON.parse( elAddonItem.dataset.addon ); } catch ( e ) { return null; } } /** * Send addon action to server via lpAJAXG. * * @param {Object} data { action, addon, purchase_code, el, elAddonItem }. * @return void */ addonsAction( data ) { const { action, addon, el, elAddonItem } = data; const selectors = AdminAddons.selectors; const params = { action: 'addon_action', action_type: action, addon, purchase_code: data.purchase_code || '', id_url: 'addon-action', }; window.lpAJAXG.fetchAJAX( params, { success: ( response ) => { const { status, message, data: resData } = response; lpToastify.show( message, status ); if ( status === 'success' && resData?.html ) { elAddonItem.outerHTML = resData.html; } else if ( 'install' === action || 'update-purchase' === action ) { const elPurchasePanel = el.closest( selectors.elPurchaseInstall ); const elPurchaseCode = elPurchasePanel ? elPurchasePanel.querySelector( selectors.elPurchaseCode ) : null; if ( elPurchaseCode ) { elPurchaseCode.classList.add( 'is-error' ); elPurchaseCode.setAttribute( 'aria-invalid', 'true' ); } } //this.filterAddons(); lpUtils.lpSetLoadingEl( el, 0 ); }, error: ( error ) => { lpUtils.lpSetLoadingEl( el, 0 ); lpToastify.show( `error js: ${ error }`, 'error' ); }, } ); } /** * Filter add-ons by status, category, and search query. * * @return void */ filterAddons() { const selectors = AdminAddons.selectors; const elAddonItems = this.elAddonsPage.querySelectorAll( selectors.elAddonItem ); // Count items for each tab this.updateTabCounts( elAddonItems ); const elActiveTab = this.elAddonsPage.querySelector( selectors.elNavTabActive ); const elActiveCategory = this.elAddonsPage.querySelector( selectors.elCategoryActive ); const elSearch = this.elAddonsPage.querySelector( selectors.elSearchInput ); const tabName = elActiveTab ? elActiveTab.dataset.tab : 'all'; const category = elActiveCategory ? elActiveCategory.dataset.category : 'all'; const keyword = elSearch ? elSearch.value.trim().toLowerCase() : ''; const searchTerms = keyword.split( /\s+/ ).filter( Boolean ); // Count items for category by tab this.updateCategoryCounts( elAddonItems, tabName ); elAddonItems.forEach( ( elAddonItem ) => { const addonInfo = JSON.parse( elAddonItem.dataset.addon ); const addonName = elAddonItem .querySelector( 'a' ) .textContent.toLowerCase(); const textCompare = addonName + addonInfo.description; const addonCategories = ( elAddonItem.dataset.category || '' ) .split( /\s+/ ) .filter( Boolean ); const matchesTab = 'all' === tabName || elAddonItem.classList.contains( tabName ); const matchesCategory = 'all' === category || addonCategories.includes( category ); const matchesSearch = searchTerms.every( ( term ) => textCompare.includes( term ) ); const isVisible = matchesTab && matchesCategory && matchesSearch; elAddonItem.classList.toggle( 'hide', ! matchesTab ); elAddonItem.classList.toggle( 'search-not-found', ! isVisible ); } ); } updateCategoryCounts( elAddonItems, tabName ) { const categoryCounts = { all: 0 }; elAddonItems.forEach( ( elAddonItem ) => { const matchesTab = 'all' === tabName || elAddonItem.classList.contains( tabName ); if ( ! matchesTab ) { return; } categoryCounts.all++; ( elAddonItem.dataset.category || '' ) .split( /\s+/ ) .filter( Boolean ) .forEach( ( addonCategory ) => { categoryCounts[ addonCategory ] = ( categoryCounts[ addonCategory ] || 0 ) + 1; } ); } ); this.elAddonsPage .querySelectorAll( AdminAddons.selectors.elCategory ) .forEach( ( elCategory ) => { const count = categoryCounts[ elCategory.dataset.category ] || 0; const elCount = elCategory.querySelector( '.lp-addons-category__count' ); if ( elCount ) { elCount.textContent = `(${ count })`; } elCategory.hidden = 'all' !== elCategory.dataset.category && 0 === count; } ); } updateTabCounts( elAddonItems ) { this.elAddonsPage .querySelectorAll( AdminAddons.selectors.elNavTab ) .forEach( ( elNavTab ) => { const tabName = elNavTab.dataset.tab; const count = Array.from( elAddonItems ).filter( ( elAddonItem ) => 'all' === tabName || elAddonItem.classList.contains( tabName ) ).length; const elCount = elNavTab.querySelector( 'span' ); if ( elCount ) { elCount.textContent = count; } } ); } /** * Handle click events. * * @param {Object} args Click event arguments. * @return void */ handlePurchaseCodeClick( args ) { const { target: el } = args; if ( ! el.value.startsWith( '***' ) ) { return; } el.value = ''; const elItemPurchase = el.closest( AdminAddons.selectors.elItemPurchase ); if ( elItemPurchase ) { elItemPurchase.querySelector( 'input[name=purchase-code]' ).value = ''; } } /** * Handle addon action button click. * * @param {Object} args Click event arguments. * @return void */ handleBtnAction( args ) { const { e, target } = args; const selectors = AdminAddons.selectors; const el = target.closest( selectors.elBtnAction ); // Ignore button type link if ( 'A' === el.tagName ) { return; } e.preventDefault(); lpUtils.lpSetLoadingEl( el, 1 ); let purchaseCode = ''; const elAddonItem = el.closest( selectors.elAddonItem ); const addon = this.getAddonData( elAddonItem ); const action = el.dataset.action; const elItemPurchase = elAddonItem.querySelector( selectors.elItemPurchase ); if ( ! addon ) { lpUtils.lpSetLoadingEl( el, 0 ); return; } if ( action === 'purchase' || action === 'update-purchase-code' ) { const elPurchaseInstall = elItemPurchase.querySelector( selectors.elPurchaseInstall ); const elPurchaseCode = elPurchaseInstall.querySelector( selectors.elPurchaseCode ); const elPurchaseSubmit = elPurchaseInstall.querySelector( '.lp-addon-purchase__submit' ); if ( action === 'update-purchase-code' ) { const elLicense = elAddonItem.querySelector( selectors.elLicense ); elPurchaseCode.value = elLicense ? elLicense.dataset.purchaseCodeMasked || '' : ''; elPurchaseSubmit.dataset.action = 'update-purchase'; } else { elPurchaseCode.value = ''; elPurchaseSubmit.dataset.action = 'install'; } elPurchaseCode.classList.remove( 'is-error' ); elPurchaseCode.removeAttribute( 'aria-invalid' ); elItemPurchase.querySelector( 'input[name=purchase-code]' ).value = ''; elPurchaseInstall.style.display = 'flex'; elItemPurchase.style.display = 'block'; elPurchaseCode.focus(); lpUtils.lpSetLoadingEl( el, 0 ); return; } else if ( action === 'cancel' ) { elItemPurchase.style.display = 'none'; elItemPurchase.querySelector( selectors.elPurchaseInstall ).style.display = 'none'; lpUtils.lpSetLoadingEl( el, 0 ); return; } // Send request to server. if ( elItemPurchase ) { purchaseCode = elItemPurchase.querySelector( 'input[name=purchase-code]' ).value; } this.addonsAction( { purchase_code: purchaseCode, action, addon, el, elAddonItem, } ); } /** * Handle tab click. * * @param {Object} args Click event arguments. * @return void */ handleTabClick( args ) { const { e, target } = args; const selectors = AdminAddons.selectors; const el = target.closest( selectors.elNavTab ); e.preventDefault(); const elTabs = this.elAddonsPage.querySelectorAll( selectors.elNavTab ); elTabs.forEach( ( elTab ) => { elTab.classList.remove( 'nav-tab-active' ); elTab.setAttribute( 'aria-pressed', 'false' ); } ); el.classList.add( 'nav-tab-active' ); el.setAttribute( 'aria-pressed', 'true' ); const tabName = el.dataset.tab; const elSearch = this.elAddonsPage.querySelector( selectors.elSearchInput ); elSearch.value = ''; this.elAddonsPage .querySelectorAll( selectors.elCategory ) .forEach( ( elCategory ) => { const isActive = 'all' === elCategory.dataset.category; elCategory.classList.toggle( 'active', isActive ); elCategory.setAttribute( 'aria-pressed', isActive ? 'true' : 'false' ); } ); this.urlParams.set( 'tab', tabName ); window.history.pushState( {}, '', `${ window.location.pathname }?${ this.urlParams.toString() }` ); this.filterAddons(); } /** * Handle category click. * * @param {Object} args Click event arguments. * @return void */ handleCategoryClick( args ) { const { e, target } = args; const elCategory = target.closest( AdminAddons.selectors.elCategory ); e.preventDefault(); this.elAddonsPage .querySelectorAll( AdminAddons.selectors.elCategory ) .forEach( ( item ) => { const isActive = item === elCategory; item.classList.toggle( 'active', isActive ); item.setAttribute( 'aria-pressed', isActive ? 'true' : 'false' ); } ); this.filterAddons(); } /** * Handle search input. * * @return void */ handleSearchInput() { this.filterAddons(); } /** * Handle purchase code input. * * @param {Object} args Input event arguments. * @return void */ handlePurchaseCodeInput( args ) { const { e, target: el } = args; e.preventDefault(); el.classList.remove( 'is-error' ); el.removeAttribute( 'aria-invalid' ); const purchaseCode = el.value; const elItemPurchase = el.closest( AdminAddons.selectors.elItemPurchase ); if ( elItemPurchase ) { const input = elItemPurchase.querySelector( 'input[name=purchase-code]' ); input.value = purchaseCode; } } } const adminAddons = new AdminAddons(); adminAddons.init(); export default AdminAddons;