PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 All 140 releases
learnpress / assets / src / js / admin / addons.js

addons.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.9, at assets/src/js/admin/addons.js

493 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Admin Add-ons page JS handler as a class.
3 *
4 * Add-ons list is rendered server-side via AdminAddonsPage::render_addons()
5 * through TemplateAJAX::load_content_via_ajax(). This class only handles
6 * UI interactions and addon actions via window.lpAJAXG.
7 *
8 * @since 4.4.7
9 * @version 1.0.0
10 */
11 import * as lpToastify from 'lpAssetsJsPath/lpToastify.js';
12 import * as lpUtils from 'lpAssetsJsPath/utils.js';
13
14 class AdminAddons {
15 constructor() {
16 this.elAddonsPage = null;
17 this.urlParams = new URLSearchParams( window.location.search );
18 }
19
20 static selectors = {
21 elAddonsPage: '.lp-addons-page',
22 elLPAddons: '#lp-addons',
23 elAddonItem: '.lp-addon-item',
24 elBtnAction: '.btn-addon-action',
25 elNavTab: '.nav-tab',
26 elNavTabActive: '.nav-tab.nav-tab-active',
27 elCategory: '.lp-addons-category',
28 elCategoryActive: '.lp-addons-category.active',
29 elSearchInput: '#lp-search-addons__input',
30 elItemPurchase: '.lp-addon-item__purchase',
31 elPurchaseInstall: '.purchase-install',
32 elPurchaseCode: '.enter-purchase-code',
33 elLicense: '.lp-addon-license',
34 };
35
36 init() {
37 // Content is injected via AJAX into .lp-target, wait for #lp-addons.
38 lpUtils.lpOnElementReady( AdminAddons.selectors.elLPAddons, () => {
39 this.elAddonsPage = document.querySelector(
40 AdminAddons.selectors.elAddonsPage
41 );
42 if ( ! this.elAddonsPage ) {
43 return;
44 }
45
46 this.filterAddons();
47 this.events();
48 } );
49 }
50
51 /**
52 * Register events.
53 *
54 * @return void
55 */
56 events() {
57 lpUtils.eventHandlers( 'click', [
58 {
59 selector: AdminAddons.selectors.elBtnAction,
60 class: this,
61 callBack: this.handleBtnAction.name,
62 },
63 {
64 selector: AdminAddons.selectors.elNavTab,
65 class: this,
66 callBack: this.handleTabClick.name,
67 },
68 {
69 selector: AdminAddons.selectors.elCategory,
70 class: this,
71 callBack: this.handleCategoryClick.name,
72 },
73 {
74 selector: AdminAddons.selectors.elPurchaseCode,
75 class: this,
76 callBack: this.handlePurchaseCodeClick.name,
77 },
78 ] );
79 lpUtils.eventHandlers( 'input', [
80 {
81 selector: AdminAddons.selectors.elSearchInput,
82 class: this,
83 callBack: this.handleSearchInput.name,
84 },
85 {
86 selector: AdminAddons.selectors.elPurchaseCode,
87 class: this,
88 callBack: this.handlePurchaseCodeInput.name,
89 },
90 ] );
91 }
92
93 /**
94 * Get addon data embedded on the item element.
95 *
96 * @param {Element} elAddonItem Addon item element.
97 * @return {Object|null}
98 */
99 getAddonData( elAddonItem ) {
100 try {
101 return JSON.parse( elAddonItem.dataset.addon );
102 } catch ( e ) {
103 return null;
104 }
105 }
106
107 /**
108 * Send addon action to server via lpAJAXG.
109 *
110 * @param {Object} data { action, addon, purchase_code, el, elAddonItem }.
111 * @return void
112 */
113 addonsAction( data ) {
114 const { action, addon, el, elAddonItem } = data;
115 const selectors = AdminAddons.selectors;
116
117 const params = {
118 action: 'addon_action',
119 action_type: action,
120 addon,
121 purchase_code: data.purchase_code || '',
122 id_url: 'addon-action',
123 };
124
125 window.lpAJAXG.fetchAJAX( params, {
126 success: ( response ) => {
127 const { status, message, data: resData } = response;
128
129 lpToastify.show( message, status );
130
131 if ( status === 'success' && resData?.html ) {
132 elAddonItem.outerHTML = resData.html;
133 } else if (
134 'install' === action ||
135 'update-purchase' === action
136 ) {
137 const elPurchasePanel = el.closest(
138 selectors.elPurchaseInstall
139 );
140 const elPurchaseCode = elPurchasePanel
141 ? elPurchasePanel.querySelector( selectors.elPurchaseCode )
142 : null;
143 if ( elPurchaseCode ) {
144 elPurchaseCode.classList.add( 'is-error' );
145 elPurchaseCode.setAttribute( 'aria-invalid', 'true' );
146 }
147 }
148
149 //this.filterAddons();
150 lpUtils.lpSetLoadingEl( el, 0 );
151 },
152 error: ( error ) => {
153 lpUtils.lpSetLoadingEl( el, 0 );
154 lpToastify.show( `error js: ${ error }`, 'error' );
155 },
156 } );
157 }
158
159 /**
160 * Filter add-ons by status, category, and search query.
161 *
162 * @return void
163 */
164 filterAddons() {
165 const selectors = AdminAddons.selectors;
166 const elAddonItems = this.elAddonsPage.querySelectorAll(
167 selectors.elAddonItem
168 );
169
170 // Count items for each tab
171 this.updateTabCounts( elAddonItems );
172 const elActiveTab = this.elAddonsPage.querySelector(
173 selectors.elNavTabActive
174 );
175 const elActiveCategory = this.elAddonsPage.querySelector(
176 selectors.elCategoryActive
177 );
178 const elSearch = this.elAddonsPage.querySelector(
179 selectors.elSearchInput
180 );
181 const tabName = elActiveTab ? elActiveTab.dataset.tab : 'all';
182 const category = elActiveCategory
183 ? elActiveCategory.dataset.category
184 : 'all';
185 const keyword = elSearch ? elSearch.value.trim().toLowerCase() : '';
186 const searchTerms = keyword.split( /\s+/ ).filter( Boolean );
187
188 // Count items for category by tab
189 this.updateCategoryCounts( elAddonItems, tabName );
190
191 elAddonItems.forEach( ( elAddonItem ) => {
192 const addonInfo = JSON.parse( elAddonItem.dataset.addon );
193
194 const addonName = elAddonItem
195 .querySelector( 'a' )
196 .textContent.toLowerCase();
197 const textCompare = addonName + addonInfo.description;
198
199 const addonCategories = ( elAddonItem.dataset.category || '' )
200 .split( /\s+/ )
201 .filter( Boolean );
202 const matchesTab =
203 'all' === tabName || elAddonItem.classList.contains( tabName );
204 const matchesCategory =
205 'all' === category || addonCategories.includes( category );
206 const matchesSearch = searchTerms.every( ( term ) => textCompare.includes( term ) );
207 const isVisible = matchesTab && matchesCategory && matchesSearch;
208
209 elAddonItem.classList.toggle( 'hide', ! matchesTab );
210 elAddonItem.classList.toggle(
211 'search-not-found',
212 ! isVisible
213 );
214 } );
215 }
216
217 updateCategoryCounts( elAddonItems, tabName ) {
218 const categoryCounts = { all: 0 };
219
220 elAddonItems.forEach( ( elAddonItem ) => {
221 const matchesTab =
222 'all' === tabName || elAddonItem.classList.contains( tabName );
223
224 if ( ! matchesTab ) {
225 return;
226 }
227
228 categoryCounts.all++;
229 ( elAddonItem.dataset.category || '' )
230 .split( /\s+/ )
231 .filter( Boolean )
232 .forEach( ( addonCategory ) => {
233 categoryCounts[ addonCategory ] =
234 ( categoryCounts[ addonCategory ] || 0 ) + 1;
235 } );
236 } );
237
238 this.elAddonsPage
239 .querySelectorAll( AdminAddons.selectors.elCategory )
240 .forEach( ( elCategory ) => {
241 const count =
242 categoryCounts[ elCategory.dataset.category ] || 0;
243 const elCount = elCategory.querySelector(
244 '.lp-addons-category__count'
245 );
246
247 if ( elCount ) {
248 elCount.textContent = `(${ count })`;
249 }
250
251 elCategory.hidden =
252 'all' !== elCategory.dataset.category && 0 === count;
253 } );
254 }
255
256 updateTabCounts( elAddonItems ) {
257 this.elAddonsPage
258 .querySelectorAll( AdminAddons.selectors.elNavTab )
259 .forEach( ( elNavTab ) => {
260 const tabName = elNavTab.dataset.tab;
261 const count = Array.from( elAddonItems ).filter(
262 ( elAddonItem ) =>
263 'all' === tabName ||
264 elAddonItem.classList.contains( tabName )
265 ).length;
266 const elCount = elNavTab.querySelector( 'span' );
267
268 if ( elCount ) {
269 elCount.textContent = count;
270 }
271 } );
272 }
273
274 /**
275 * Handle click events.
276 *
277 * @param {Object} args Click event arguments.
278 * @return void
279 */
280 handlePurchaseCodeClick( args ) {
281 const { target: el } = args;
282
283 if ( ! el.value.startsWith( '***' ) ) {
284 return;
285 }
286
287 el.value = '';
288 const elItemPurchase = el.closest(
289 AdminAddons.selectors.elItemPurchase
290 );
291 if ( elItemPurchase ) {
292 elItemPurchase.querySelector( 'input[name=purchase-code]' ).value = '';
293 }
294 }
295
296 /**
297 * Handle addon action button click.
298 *
299 * @param {Object} args Click event arguments.
300 * @return void
301 */
302 handleBtnAction( args ) {
303 const { e, target } = args;
304 const selectors = AdminAddons.selectors;
305 const el = target.closest( selectors.elBtnAction );
306
307 // Ignore button type link
308 if ( 'A' === el.tagName ) {
309 return;
310 }
311
312 e.preventDefault();
313 lpUtils.lpSetLoadingEl( el, 1 );
314
315 let purchaseCode = '';
316 const elAddonItem = el.closest( selectors.elAddonItem );
317 const addon = this.getAddonData( elAddonItem );
318 const action = el.dataset.action;
319 const elItemPurchase = elAddonItem.querySelector(
320 selectors.elItemPurchase
321 );
322
323 if ( ! addon ) {
324 lpUtils.lpSetLoadingEl( el, 0 );
325 return;
326 }
327
328 if ( action === 'purchase' || action === 'update-purchase-code' ) {
329 const elPurchaseInstall = elItemPurchase.querySelector(
330 selectors.elPurchaseInstall
331 );
332 const elPurchaseCode = elPurchaseInstall.querySelector(
333 selectors.elPurchaseCode
334 );
335 const elPurchaseSubmit = elPurchaseInstall.querySelector(
336 '.lp-addon-purchase__submit'
337 );
338
339 if ( action === 'update-purchase-code' ) {
340 const elLicense = elAddonItem.querySelector( selectors.elLicense );
341 elPurchaseCode.value = elLicense
342 ? elLicense.dataset.purchaseCodeMasked || ''
343 : '';
344 elPurchaseSubmit.dataset.action = 'update-purchase';
345 } else {
346 elPurchaseCode.value = '';
347 elPurchaseSubmit.dataset.action = 'install';
348 }
349
350 elPurchaseCode.classList.remove( 'is-error' );
351 elPurchaseCode.removeAttribute( 'aria-invalid' );
352 elItemPurchase.querySelector( 'input[name=purchase-code]' ).value =
353 '';
354 elPurchaseInstall.style.display = 'flex';
355 elItemPurchase.style.display = 'block';
356 elPurchaseCode.focus();
357 lpUtils.lpSetLoadingEl( el, 0 );
358 return;
359 } else if ( action === 'cancel' ) {
360 elItemPurchase.style.display = 'none';
361 elItemPurchase.querySelector(
362 selectors.elPurchaseInstall
363 ).style.display = 'none';
364 lpUtils.lpSetLoadingEl( el, 0 );
365 return;
366 }
367
368 // Send request to server.
369 if ( elItemPurchase ) {
370 purchaseCode = elItemPurchase.querySelector(
371 'input[name=purchase-code]'
372 ).value;
373 }
374
375 this.addonsAction( {
376 purchase_code: purchaseCode,
377 action,
378 addon,
379 el,
380 elAddonItem,
381 } );
382 }
383
384 /**
385 * Handle tab click.
386 *
387 * @param {Object} args Click event arguments.
388 * @return void
389 */
390 handleTabClick( args ) {
391 const { e, target } = args;
392 const selectors = AdminAddons.selectors;
393 const el = target.closest( selectors.elNavTab );
394 e.preventDefault();
395
396 const elTabs = this.elAddonsPage.querySelectorAll( selectors.elNavTab );
397 elTabs.forEach( ( elTab ) => {
398 elTab.classList.remove( 'nav-tab-active' );
399 elTab.setAttribute( 'aria-pressed', 'false' );
400 } );
401 el.classList.add( 'nav-tab-active' );
402 el.setAttribute( 'aria-pressed', 'true' );
403
404 const tabName = el.dataset.tab;
405 const elSearch = this.elAddonsPage.querySelector(
406 selectors.elSearchInput
407 );
408 elSearch.value = '';
409 this.elAddonsPage
410 .querySelectorAll( selectors.elCategory )
411 .forEach( ( elCategory ) => {
412 const isActive = 'all' === elCategory.dataset.category;
413 elCategory.classList.toggle( 'active', isActive );
414 elCategory.setAttribute(
415 'aria-pressed',
416 isActive ? 'true' : 'false'
417 );
418 } );
419
420 this.urlParams.set( 'tab', tabName );
421 window.history.pushState(
422 {},
423 '',
424 `${ window.location.pathname }?${ this.urlParams.toString() }`
425 );
426 this.filterAddons();
427 }
428
429 /**
430 * Handle category click.
431 *
432 * @param {Object} args Click event arguments.
433 * @return void
434 */
435 handleCategoryClick( args ) {
436 const { e, target } = args;
437 const elCategory = target.closest(
438 AdminAddons.selectors.elCategory
439 );
440 e.preventDefault();
441
442 this.elAddonsPage
443 .querySelectorAll( AdminAddons.selectors.elCategory )
444 .forEach( ( item ) => {
445 const isActive = item === elCategory;
446 item.classList.toggle( 'active', isActive );
447 item.setAttribute(
448 'aria-pressed',
449 isActive ? 'true' : 'false'
450 );
451 } );
452 this.filterAddons();
453 }
454
455 /**
456 * Handle search input.
457 *
458 * @return void
459 */
460 handleSearchInput() {
461 this.filterAddons();
462 }
463
464 /**
465 * Handle purchase code input.
466 *
467 * @param {Object} args Input event arguments.
468 * @return void
469 */
470 handlePurchaseCodeInput( args ) {
471 const { e, target: el } = args;
472 e.preventDefault();
473 el.classList.remove( 'is-error' );
474 el.removeAttribute( 'aria-invalid' );
475 const purchaseCode = el.value;
476 const elItemPurchase = el.closest(
477 AdminAddons.selectors.elItemPurchase
478 );
479
480 if ( elItemPurchase ) {
481 const input = elItemPurchase.querySelector(
482 'input[name=purchase-code]'
483 );
484 input.value = purchaseCode;
485 }
486 }
487 }
488
489 const adminAddons = new AdminAddons();
490 adminAddons.init();
491
492 export default AdminAddons;
493