# learnpress/4.4.9/assets/src/js/admin/addons.js

LearnPress – WordPress LMS Plugin for Create and Sell Online Courses, version 4.4.9. 493 lines.

- Page: https://pluginprobe.com/plugins/learnpress/4.4.9/code/assets/src/js/admin/addons.js
- Raw: https://pluginprobe.com/plugins/learnpress/4.4.9/raw/assets/src/js/admin/addons.js
- Modified: 2026-09-27T10:32:46+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/learnpress/4.4.9/code/assets/src/js/admin/addons.js#L10-L20`.

```javascript
/**
 * 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;

```
