PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.10
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.10
4.4.10 4.4.9.1 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 All 142 releases
learnpress / assets / src / js / admin / lp-themes.js

lp-themes.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.10, at assets/src/js/admin/lp-themes.js

276 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import * as lpUtils from 'lpAssetsJsPath/utils.js';
2
3 /**
4 * LearnPress themes handler.
5 *
6 * @since 4.4.7
7 * @version 1.0.0
8 */
9 export class LPThemes {
10 /**
11 * Themes container selectors.
12 */
13 static selectors = {
14 container: '.learn-press-themes',
15 elListThemes: '.lp-themes-grid',
16 filter: '.lp-themes-filter__item',
17 filterToggle: '.lp-themes-filter__toggle',
18 count: '.lp-themes-filter__count',
19 search: '.lp-themes-search__input',
20 searchContainer: '.lp-themes-search',
21 searchButton: '.lp-themes-search__btn',
22 searchClear: '.lp-themes-search__btn-clear',
23 searchClose: '.lp-themes-search__close',
24 card: '.lp-theme-card',
25 title: '.lp-theme-card__title',
26 description: '.lp-theme-card__description',
27 };
28
29 /**
30 * Initialize the themes handler.
31 *
32 * @param {HTMLElement} container Themes container.
33 */
34 init( container ) {
35 this.container = container;
36 this.events();
37 this.countThemes();
38 }
39
40 /**
41 * Register theme filter and search events.
42 */
43 events() {
44 if ( LPThemes._loadedEvents ) {
45 return;
46 }
47 LPThemes._loadedEvents = true;
48
49 lpUtils.eventHandlers( 'click', [
50 {
51 selector: LPThemes.selectors.filter,
52 callBack: this.filterByCategory.name,
53 class: this,
54 },
55 ] );
56
57 lpUtils.eventHandlers( 'input', [
58 {
59 selector: LPThemes.selectors.search,
60 callBack: this.filterThemes.name,
61 class: this,
62 },
63 ] );
64
65 lpUtils.eventHandlers( 'click', [
66 {
67 selector: LPThemes.selectors.searchButton,
68 callBack: this.openSearch.name,
69 class: this,
70 },
71 {
72 selector: LPThemes.selectors.searchClear,
73 callBack: this.clearSearch.name,
74 class: this,
75 },
76 {
77 selector: LPThemes.selectors.searchClose,
78 callBack: this.closeSearch.name,
79 class: this,
80 },
81 {
82 selector: LPThemes.selectors.filterToggle,
83 callBack: this.toggleFilterMenu.name,
84 class: this,
85 },
86 ] );
87
88 document.addEventListener( 'click', ( event ) => {
89 const toolbar = this.container?.querySelector( '.lp-themes-toolbar' );
90 if (
91 toolbar?.classList.contains( 'is-filter-open' ) &&
92 ! toolbar.contains( event.target )
93 ) {
94 this.toggleFilterMenu( false );
95 }
96 } );
97
98 document.addEventListener( 'keydown', ( event ) => {
99 if ( 'Escape' === event.key ) {
100 this.closeSearch();
101 this.toggleFilterMenu( false );
102 }
103 } );
104 }
105
106 /** Toggle the tablet and mobile category menu. */
107 toggleFilterMenu( open ) {
108 const toolbar = this.container.querySelector( '.lp-themes-toolbar' );
109 const button = this.container.querySelector(
110 LPThemes.selectors.filterToggle
111 );
112 if ( ! toolbar || ! button ) {
113 return;
114 }
115
116 const shouldOpen =
117 typeof open === 'boolean'
118 ? open
119 : ! toolbar.classList.contains( 'is-filter-open' );
120 toolbar.classList.toggle( 'is-filter-open', shouldOpen );
121 button.setAttribute( 'aria-expanded', shouldOpen ? 'true' : 'false' );
122 }
123
124 /** Open the compact search field. */
125 openSearch() {
126 const search = this.container.querySelector( LPThemes.selectors.searchContainer );
127 if ( ! search ) {
128 return;
129 }
130
131 this.toggleFilterMenu( false );
132 search.classList.add( 'is-open' );
133 search.querySelector( LPThemes.selectors.searchButton )?.setAttribute( 'aria-expanded', 'true' );
134 this.updateSearchClearBtnState();
135 search.querySelector( LPThemes.selectors.search )?.focus();
136 }
137
138 /** Close the compact search field. */
139 closeSearch() {
140 if ( ! this.container ) {
141 return;
142 }
143
144 const search = this.container.querySelector( LPThemes.selectors.searchContainer );
145 if ( ! search ) {
146 return;
147 }
148 if ( ! search.classList.contains( 'is-open' ) ) {
149 return;
150 }
151
152 search.classList.remove( 'is-open' );
153 search.querySelector( LPThemes.selectors.searchButton )?.setAttribute( 'aria-expanded', 'false' );
154 this.updateSearchClearBtnState();
155 search.querySelector( LPThemes.selectors.searchButton )?.focus();
156 }
157
158 /** Clear the search input field and re-filter themes. */
159 clearSearch( args ) {
160 if ( args?.e ) {
161 args.e.preventDefault();
162 }
163 const search = this.container?.querySelector( LPThemes.selectors.search );
164 if ( search ) {
165 search.value = '';
166 this.updateSearchClearBtnState();
167 this.filterThemes();
168 search.focus();
169 }
170 }
171
172 /** Update disabled state of clear search button. */
173 updateSearchClearBtnState() {
174 const search = this.container?.querySelector( LPThemes.selectors.search );
175 const clearBtn = this.container?.querySelector( LPThemes.selectors.searchClear );
176 if ( clearBtn && search ) {
177 clearBtn.disabled = ! search.value.trim();
178 }
179 }
180
181 /**
182 * Count themes and update each category count.
183 */
184 countThemes() {
185 const container = this.container;
186 const cards = container.querySelectorAll( LPThemes.selectors.card );
187 const counts = { all: cards.length };
188
189 cards.forEach( ( card ) => {
190 const key = card.getAttribute( 'data-category' );
191 counts[ key ] = ( counts[ key ] || 0 ) + 1;
192 } );
193
194 container
195 .querySelectorAll( LPThemes.selectors.filter )
196 .forEach( ( item ) => {
197 const count = item.querySelector( LPThemes.selectors.count );
198 const text = `(${
199 counts[ item.getAttribute( 'data-category' ) ] || 0
200 })`;
201 if ( count && count.textContent !== text ) {
202 count.textContent = text;
203 }
204 } );
205 }
206
207 /**
208 * Activate the selected category and filter themes.
209 *
210 * @param {Object} args Event arguments.
211 * @param {Event} args.e Browser event.
212 * @param {Element} args.target Event target.
213 */
214 filterByCategory( { e, target } ) {
215 const filter = target.closest( LPThemes.selectors.filter );
216 const container = filter.closest( LPThemes.selectors.container );
217
218 if ( ! container ) {
219 return;
220 }
221
222 e.preventDefault();
223 this.toggleFilterMenu( false );
224 container
225 .querySelectorAll( LPThemes.selectors.filter )
226 .forEach( ( item ) => {
227 const isActive = item === filter;
228 item.classList.toggle( 'active', isActive );
229 item.setAttribute(
230 'aria-pressed',
231 isActive ? 'true' : 'false'
232 );
233 } );
234
235 this.filterThemes();
236 }
237
238 /**
239 * Filter themes by the active category and search terms.
240 */
241 filterThemes() {
242 const container = this.container;
243 const search = container.querySelector( LPThemes.selectors.search );
244 this.updateSearchClearBtnState();
245 const query = search ? search.value.trim().toLowerCase() : '';
246 const searchTerms = query.split( /\s+/ ).filter( Boolean );
247 const active = container.querySelector(
248 `${ LPThemes.selectors.filter }.active`
249 );
250 const category = active
251 ? active.getAttribute( 'data-category' )
252 : 'all';
253 const cards = container.querySelectorAll( LPThemes.selectors.card );
254
255 cards.forEach( ( card ) => {
256 const title = card.querySelector( LPThemes.selectors.title );
257 const description = card.querySelector(
258 LPThemes.selectors.description
259 );
260 const text = `${ title ? title.textContent : '' } ${
261 description ? description.textContent : ''
262 }`.toLowerCase();
263 card.hidden =
264 ( 'all' !== category &&
265 category !== card.getAttribute( 'data-category' ) ) ||
266 ! searchTerms.every( ( term ) => text.includes( term ) );
267 } );
268 }
269 }
270
271 const lpThemes = new LPThemes();
272 lpUtils.lpOnElementReady( LPThemes.selectors.elListThemes, ( elListThemes ) => {
273 const container = elListThemes.closest( LPThemes.selectors.container );
274 lpThemes.init( container );
275 } );
276