PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / trunk
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses vtrunk
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 4.2.0 4.2.1 All 138 releases
learnpress / assets / src / js / utils.js

utils.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses trunk, at assets/src/js/utils.js

523 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Utils functions
3 *
4 * @param url
5 * @param data
6 * @param functions
7 * @since 4.2.5.1
8 * @version 1.0.7
9 */
10 export const lpClassName = {
11 hidden: 'lp-hidden',
12 loading: 'loading',
13 elCollapse: 'lp-collapse',
14 elSectionToggle: '.lp-section-toggle',
15 elTriggerToggle: '.lp-trigger-toggle',
16 elBtnFullScreen: '.lp-btn-full-screen-view',
17 elFullScreen: 'lp-full-screen-view',
18 elBtnFullScreenClose: 'lp-full-screen-view__close',
19 };
20
21 export const lpFetchAPI = ( url, data = {}, functions = {} ) => {
22 if ( 'function' === typeof functions.before ) {
23 functions.before();
24 }
25
26 fetch( url, { method: 'GET', ...data } )
27 .then( ( response ) => response.json() )
28 .then( ( response ) => {
29 if ( 'function' === typeof functions.success ) {
30 functions.success( response );
31 }
32 } )
33 .catch( ( err ) => {
34 if ( 'function' === typeof functions.error ) {
35 functions.error( err );
36 }
37 } )
38 .finally( () => {
39 if ( 'function' === typeof functions.completed ) {
40 functions.completed();
41 }
42 } );
43 };
44
45 /**
46 * Get current URL without params.
47 *
48 * @since 4.2.5.1
49 */
50 export const lpGetCurrentURLNoParam = () => {
51 let currentUrl = window.location.href;
52 const hasParams = currentUrl.includes( '?' );
53 if ( hasParams ) {
54 currentUrl = currentUrl.split( '?' )[ 0 ];
55 }
56
57 return currentUrl;
58 };
59
60 export const lpAddQueryArgs = ( endpoint, args ) => {
61 const url = new URL( endpoint );
62
63 Object.keys( args ).forEach( ( arg ) => {
64 url.searchParams.set( arg, args[ arg ] );
65 } );
66
67 return url;
68 };
69
70 /**
71 * Listen element viewed.
72 *
73 * @param el
74 * @param callback
75 * @since 4.2.5.8
76 */
77 export const listenElementViewed = ( el, callback ) => {
78 const observerSeeItem = new IntersectionObserver( function ( entries ) {
79 for ( const entry of entries ) {
80 if ( entry.isIntersecting ) {
81 callback( entry );
82 }
83 }
84 } );
85
86 observerSeeItem.observe( el );
87 };
88
89 /**
90 * Listen element created.
91 *
92 * @param callback
93 * @since 4.2.5.8
94 */
95 export const listenElementCreated = ( callback ) => {
96 const observerCreateItem = new MutationObserver( function ( mutations ) {
97 mutations.forEach( function ( mutation ) {
98 if ( mutation.addedNodes ) {
99 mutation.addedNodes.forEach( function ( node ) {
100 if ( node.nodeType === 1 ) {
101 callback( node );
102 }
103 } );
104 }
105 } );
106 } );
107
108 observerCreateItem.observe( document, { childList: true, subtree: true } );
109 // End.
110 };
111
112 /**
113 * Listen element created.
114 *
115 * @param selector
116 * @param callback
117 * @since 4.2.7.1
118 */
119 export const lpOnElementReady = ( selector, callback ) => {
120 const element = document.querySelector( selector );
121 if ( element ) {
122 callback( element );
123 return;
124 }
125
126 const observer = new MutationObserver( ( mutations, obs ) => {
127 const element = document.querySelector( selector );
128 if ( element ) {
129 obs.disconnect();
130 callback( element );
131 }
132 } );
133
134 observer.observe( document.documentElement, {
135 childList: true,
136 subtree: true,
137 } );
138 };
139
140 // Parse JSON from string with content include LP_AJAX_START.
141 export const lpAjaxParseJsonOld = ( data ) => {
142 if ( typeof data !== 'string' ) {
143 return data;
144 }
145
146 const m = String.raw( { raw: data } ).match(
147 /<-- LP_AJAX_START -->(.*)<-- LP_AJAX_END -->/s
148 );
149
150 try {
151 if ( m ) {
152 data = JSON.parse( m[ 1 ].replace( /(?:\r\n|\r|\n)/g, '' ) );
153 } else {
154 data = JSON.parse( data );
155 }
156 } catch ( e ) {
157 data = {};
158 }
159
160 return data;
161 };
162
163 // status 0: hide, 1: show
164 export const lpShowHideEl = ( el, status = 0 ) => {
165 if ( ! el ) {
166 return;
167 }
168
169 if ( ! status ) {
170 el.classList.add( lpClassName.hidden );
171 } else {
172 el.classList.remove( lpClassName.hidden );
173 }
174 };
175
176 // status 0: hide, 1: show
177 export const lpSetLoadingEl = ( el, status ) => {
178 if ( ! el ) {
179 return;
180 }
181
182 if ( ! status ) {
183 el.classList.remove( lpClassName.loading );
184 } else {
185 el.classList.add( lpClassName.loading );
186 }
187 };
188
189 // Toggle collapse section
190 export const toggleCollapse = (
191 e,
192 target,
193 elTriggerClassName = '',
194 elsExclude = [],
195 callback
196 ) => {
197 if ( ! elTriggerClassName ) {
198 elTriggerClassName = lpClassName.elTriggerToggle;
199 }
200
201 // Exclude elements, which should not trigger the collapse toggle
202 if ( elsExclude && elsExclude.length > 0 ) {
203 for ( const elExclude of elsExclude ) {
204 if ( target.closest( elExclude ) ) {
205 return;
206 }
207 }
208 }
209
210 const elTrigger = target.closest( elTriggerClassName );
211 if ( ! elTrigger ) {
212 return;
213 }
214
215 //console.log( 'elTrigger', elTrigger );
216
217 const elSectionToggle = elTrigger.closest(
218 `${ lpClassName.elSectionToggle }`
219 );
220 if ( ! elSectionToggle ) {
221 return;
222 }
223
224 elSectionToggle.classList.toggle( `${ lpClassName.elCollapse }` );
225
226 if ( 'function' === typeof callback ) {
227 callback( elSectionToggle );
228 }
229 };
230
231 // Get data of form
232 export const getDataOfForm = ( form ) => {
233 const dataSend = {};
234 const formData = new FormData( form );
235 for ( const pair of formData.entries() ) {
236 const key = pair[ 0 ];
237 const value = formData.getAll( key );
238 if ( ! dataSend.hasOwnProperty( key ) ) {
239 // Convert value array to string.
240 dataSend[ key ] = value.join( ',' );
241 }
242 }
243
244 return dataSend;
245 };
246
247 // Get field keys of form
248 export const getFieldKeysOfForm = ( form ) => {
249 const keys = [];
250 const elements = form.elements;
251 for ( let i = 0; i < elements.length; i++ ) {
252 const name = elements[ i ].name;
253 if ( name && ! keys.includes( name ) ) {
254 keys.push( name );
255 }
256 }
257 return keys;
258 };
259
260 // Merge data handle with data form.
261 export const mergeDataWithDatForm = ( elForm, dataHandle ) => {
262 const dataForm = getDataOfForm( elForm );
263 const keys = getFieldKeysOfForm( elForm );
264 keys.forEach( ( key ) => {
265 if ( ! dataForm.hasOwnProperty( key ) ) {
266 delete dataHandle[ key ];
267 } else if ( dataForm[ key ][ 0 ] === '' ) {
268 delete dataForm[ key ];
269 delete dataHandle[ key ];
270 }
271 } );
272
273 dataHandle = { ...dataHandle, ...dataForm };
274
275 return dataHandle;
276 };
277
278 /**
279 * Event trigger
280 * For each list of event handlers, listen event on document.
281 *
282 * eventName: 'click', 'change', ...
283 * eventHandlers = [ { selector: '.lp-button', callBack: function(){}, class: object } ]
284 *
285 * @param eventName
286 * @param eventHandlers
287 */
288 export const eventHandlers = ( eventName, eventHandlers ) => {
289 document.addEventListener( eventName, ( e ) => {
290 const target = e.target;
291 let args = {
292 e,
293 target,
294 };
295
296 eventHandlers.forEach( ( eventHandler ) => {
297 args = { ...args, ...eventHandler };
298
299 //console.log( args );
300
301 // Check condition before call back
302 if ( eventHandler.conditionBeforeCallBack ) {
303 if ( eventHandler.conditionBeforeCallBack( args ) !== true ) {
304 return;
305 }
306 }
307
308 // Special check for keydown event with checkIsEventEnter = true
309 if ( eventName === 'keydown' && eventHandler.checkIsEventEnter ) {
310 if ( e.key !== 'Enter' ) {
311 return;
312 }
313 }
314
315 if ( target.closest( eventHandler.selector ) ) {
316 if ( eventHandler.class ) {
317 // Call method of class, function callBack will understand exactly {this} is class object.
318 eventHandler.class[ eventHandler.callBack ]( args );
319 } else {
320 // For send args is objected, {this} is eventHandler object, not class object.
321 eventHandler.callBack( args );
322 }
323 }
324 } );
325 } );
326 };
327
328 /**
329 * Debounce - delays function execution until after `wait` ms of inactivity.
330 *
331 * Each call resets the timer. Only the last call in a burst executes.
332 *
333 * USE CASES:
334 * - Search inputs, form validation, window resize
335 * - Multiple elements need independent timers
336 * - When you need to call with different arguments
337 *
338 * EXAMPLES:
339 * const debouncedSearch = debounce( (query) => fetchResults(query), 300 );
340 * searchInput.addEventListener('input', (e) => debouncedSearch(e.target.value));
341 *
342 * const debouncedResize = debounce( recalculateLayout, 250 );
343 * window.addEventListener('resize', debouncedResize);
344 *
345 * ⚠️ Create ONCE outside event handlers, not inside.
346 *
347 * @param {Function} func - Function to debounce (can be anonymous)
348 * @param {number} wait - Milliseconds to wait (default: 500)
349 * @return {Function} Debounced wrapper function
350 * @since 4.3.7
351 * @version 1.0.0
352 */
353 export const debounce = ( func, wait = 500 ) => {
354 let timer;
355
356 return ( args ) => {
357 clearTimeout( timer );
358 timer = setTimeout( () => func( args ), wait );
359 };
360 };
361
362 /**
363 * Initialize lp-toggle-enable components.
364 *
365 * Finds all `.lp-toggle-enable` elements and wires up toggle behavior.
366 * Reads initial state from `data-enabled` attribute ("true"/"false").
367 * Calls `data-on-toggle` callback (if provided via options) on state change.
368 *
369 * HTML structure:
370 * <label class="lp-toggle-enable" data-enabled="true">
371 * <input type="checkbox" class="lp-toggle-enable__input" />
372 * <span class="lp-toggle-enable__track"></span>
373 * </label>
374 *
375 * @param {string} selector CSS selector for toggle elements (default: '.lp-toggle-enable')
376 * @param {Function} onToggle Optional callback( el, isEnabled ) called on state change
377 * @since 4.4.5
378 * @version 1.0.0
379 */
380 window.lpToggleEnableInit = 0;
381 export const toggleEnable = ( onToggle = null ) => {
382 if ( window.lpToggleEnableInit ) {
383 return;
384 }
385 window.lpToggleEnableInit = 1;
386
387 const selector = '.lp-toggle-enable';
388
389 const updateUI = ( toggle, isEnabled ) => {
390 toggle.classList.toggle( 'is-enabled', isEnabled );
391 const input = toggle.querySelector( '.lp-toggle-enable__input' );
392 if ( input ) {
393 input.checked = isEnabled;
394 input.value = isEnabled ? '1' : '0';
395 }
396 };
397
398 // Delegate click handling via eventHandlers.
399 eventHandlers( 'click', [
400 {
401 selector,
402 callBack: ( args ) => {
403 const { e, target } = args;
404 const toggle = target.closest( selector );
405
406 if ( ! toggle || toggle.classList.contains( 'is-disabled' ) ) {
407 return;
408 }
409
410 e.preventDefault();
411
412 const isEnabled = ! toggle.classList.contains( 'is-enabled' );
413 updateUI( toggle, isEnabled );
414
415 if ( 'function' === typeof onToggle ) {
416 onToggle( toggle, isEnabled );
417 }
418 },
419 },
420 ] );
421 };
422
423 /**
424 * Initialize custom fullscreen view buttons.
425 *
426 * Delegates clicks on `.lp-btn-full-screen-view` buttons to
427 * `lpToggleFullscreenView`. Reads the `data-target` attribute to find the
428 * target element. Falls back to the button's parent element when
429 * `data-target` is not provided.
430 *
431 * @since 4.4.5
432 * @version 1.0.0
433 */
434 window.lpFullScreenViewInit = 0;
435 export const fullScreenView = () => {
436 if ( window.lpFullScreenViewInit ) {
437 return;
438 }
439 window.lpFullScreenViewInit = 1;
440
441 let lastScrollY = 0;
442
443 const lpToggleFullscreenView = ( elTarget, elBtnFullScreen = null ) => {
444 const isFullscreen = elTarget.classList.contains(
445 lpClassName.elFullScreen
446 );
447
448 if ( isFullscreen ) {
449 elTarget.classList.remove( lpClassName.elFullScreen );
450 document.documentElement.classList.remove(
451 'lp-full-screen-active'
452 );
453 window.scrollTo( 0, lastScrollY );
454 } else {
455 lastScrollY = window.scrollY;
456 elTarget.classList.add( lpClassName.elFullScreen );
457 document.documentElement.classList.add( 'lp-full-screen-active' );
458 }
459
460 if ( ! isFullscreen ) {
461 if (
462 ! elTarget.querySelector(
463 `.${ lpClassName.elBtnFullScreenClose }`
464 )
465 ) {
466 const closeButton = document.createElement( 'button' );
467 closeButton.type = 'button';
468 closeButton.className = lpClassName.elBtnFullScreenClose;
469 closeButton.setAttribute( 'aria-label', 'Close' );
470 closeButton.innerHTML =
471 lpData.i18n.closeButtonFullScreen || 'Close &times;';
472
473 closeButton.addEventListener( 'click', ( e ) => {
474 e.preventDefault();
475 lpToggleFullscreenView( elTarget );
476 } );
477
478 elTarget.appendChild( closeButton );
479 }
480 } else {
481 const closeButton = elTarget.querySelector(
482 `.${ lpClassName.elBtnFullScreenClose }`
483 );
484 if ( closeButton ) {
485 closeButton.remove();
486 }
487 }
488 };
489
490 eventHandlers( 'click', [
491 {
492 selector: lpClassName.elBtnFullScreen,
493 callBack: ( args ) => {
494 const { e, target } = args;
495 const elBtnFullScreen = target.closest(
496 lpClassName.elBtnFullScreen
497 );
498
499 if ( ! elBtnFullScreen ) {
500 console.log( 'No full screen button found' );
501 return;
502 }
503
504 e.preventDefault();
505
506 let elTarget = null;
507 const targetSelector = elBtnFullScreen.dataset.targetFullscreen;
508 console.log( targetSelector );
509 if ( targetSelector ) {
510 elTarget = document.querySelector( targetSelector );
511 }
512
513 if ( ! elTarget ) {
514 console.log( 'No target element found' );
515 return;
516 }
517
518 lpToggleFullscreenView( elTarget, elBtnFullScreen );
519 },
520 },
521 ] );
522 };
523