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
← All changes | assets/src/js/admin/edit-course/edit-curriculum/edit-section-item.js +870 -870 4.3.9 → 4.4.10 View file →
@@ -1,870 +1,870 @@
1 -/**
2 - * Edit Section item Script on Curriculum
3 - *
4 - * @version 1.0.3
5 - * @since 4.2.8.6
6 - */
7 -import SweetAlert from 'sweetalert2';
8 -import Sortable from 'sortablejs';
9 -import * as lpUtils from 'lpAssetsJsPath/utils.js';
10 -import * as lpToastify from 'lpAssetsJsPath/lpToastify.js';
11 -import { EditCourseCurriculum } from '../edit-curriculum.js';
12 -import { EditSection } from './edit-section.js';
13 -import { LpPopupSelectItemToAdd } from 'lpAssetsJsPath/lpPopupSelectItemToAdd.js';
14 -
15 -const idUrlHandle = 'edit-course-curriculum';
16 -
17 -const lpPopupSelectItemToAdd = new LpPopupSelectItemToAdd();
18 -
19 -export class EditSectionItem {
20 - constructor() {
21 - this.courseId = null;
22 - this.elCurriculumSections = null;
23 - this.sectionIdSelected = null;
24 - }
25 -
26 - static selectors = {
27 - elSectionListItems: '.section-list-items',
28 - elItemClone: '.section-item.clone',
29 - elSectionItem: '.section-item',
30 - elBtnSelectItemType: '.lp-btn-select-item-type',
31 - elAddItemTypeClone: '.lp-add-item-type.clone',
32 - elSectionActions: '.section-actions',
33 - elAddItemType: '.lp-add-item-type',
34 - elAddItemTypeTitleInput: '.lp-add-item-type-title-input',
35 - elBtnAddItemCancel: '.lp-btn-add-item-cancel',
36 - elBtnAddItem: '.lp-btn-add-item',
37 - elItemTitleInput: '.lp-item-title-input',
38 - elBtnUpdateItemTitle: '.lp-btn-update-item-title',
39 - elBtnCancelUpdateTitle: '.lp-btn-cancel-update-item-title',
40 - elBtnDeleteItem: '.lp-btn-delete-item',
41 - elBtnSetPreviewItem: '.lp-btn-set-preview-item',
42 - };
43 -
44 - init() {
45 - this.elEditCurriculum = document.querySelector(
46 - `${ EditCourseCurriculum.selectors.idElEditCurriculum }`
47 - );
48 - this.elCurriculumSections = this.elEditCurriculum.querySelector(
49 - `${ EditCourseCurriculum.selectors.elCurriculumSections }`
50 - );
51 - const elLPTarget = this.elEditCurriculum.closest(
52 - `${ EditCourseCurriculum.selectors.LPTarget }`
53 - );
54 - const dataSend = window.lpAJAXG.getDataSetCurrent( elLPTarget );
55 - this.courseId = dataSend.args.course_id;
56 -
57 - this.events();
58 - this.sortAbleItem();
59 - lpPopupSelectItemToAdd.init();
60 - }
61 -
62 - /* Events */
63 - events() {
64 - // Check and attach events only once
65 - if ( EditSectionItem._loadedEvents ) {
66 - return;
67 - }
68 -
69 - EditSectionItem._loadedEvents = this;
70 -
71 - // Click events
72 - lpUtils.eventHandlers( 'click', [
73 - {
74 - selector: EditSectionItem.selectors.elBtnSelectItemType,
75 - class: this,
76 - callBack: this.addItemType.name,
77 - },
78 - {
79 - selector: EditSectionItem.selectors.elBtnAddItem,
80 - class: this,
81 - callBack: this.addItemToSection.name,
82 - },
83 - {
84 - selector: EditSectionItem.selectors.elBtnAddItemCancel,
85 - class: this,
86 - callBack: this.cancelAddItemType.name,
87 - },
88 - {
89 - selector: EditSectionItem.selectors.elBtnUpdateItemTitle,
90 - class: this,
91 - callBack: this.updateTitle.name,
92 - },
93 - {
94 - selector: EditSectionItem.selectors.elBtnCancelUpdateTitle,
95 - class: this,
96 - callBack: this.cancelUpdateTitle.name,
97 - },
98 - {
99 - selector: EditSectionItem.selectors.elBtnDeleteItem,
100 - class: this,
101 - callBack: this.deleteItem.name,
102 - },
103 - {
104 - selector: LpPopupSelectItemToAdd.selectors.elBtnShowPopupItemsToSelect,
105 - class: this,
106 - callBack: this.handleShowPopupItemsToSelect.name,
107 - },
108 - {
109 - selector: LpPopupSelectItemToAdd.selectors.elBtnAddItemsSelected,
110 - class: lpPopupSelectItemToAdd,
111 - callBack: lpPopupSelectItemToAdd.addItemsSelectedToSection.name,
112 - callBackHandle: this.addItemsSelectedToSection.bind( this ),
113 - },
114 - {
115 - selector: EditSectionItem.selectors.elBtnSetPreviewItem,
116 - class: this,
117 - callBack: this.updatePreviewItem.name,
118 - },
119 - ] );
120 -
121 - // Keyup events
122 - lpUtils.eventHandlers( 'keyup', [
123 - {
124 - selector: EditSectionItem.selectors.elItemTitleInput,
125 - class: this,
126 - callBack: this.changeTitle.name,
127 - },
128 - {
129 - selector: EditSectionItem.selectors.elAddItemTypeTitleInput,
130 - class: this,
131 - callBack: this.changeTitleAddNew.name,
132 - },
133 - ] );
134 -
135 - // Keydown events
136 - lpUtils.eventHandlers( 'keydown', [
137 - {
138 - selector: EditSectionItem.selectors.elAddItemTypeTitleInput,
139 - class: this,
140 - callBack: this.addItemToSection.name,
141 - checkIsEventEnter: true,
142 - },
143 - {
144 - selector: EditSectionItem.selectors.elItemTitleInput,
145 - class: this,
146 - callBack: this.updateTitle.name,
147 - checkIsEventEnter: true,
148 - },
149 - ] );
150 -
151 - // Focusin events
152 - lpUtils.eventHandlers( 'focusin', [
153 - {
154 - selector: EditSectionItem.selectors.elItemTitleInput,
155 - class: this,
156 - callBack: this.focusTitleInput.name,
157 - },
158 - ] );
159 -
160 - // Focusout events
161 - lpUtils.eventHandlers( 'focusout', [
162 - {
163 - selector: EditSectionItem.selectors.elItemTitleInput,
164 - class: this,
165 - callBack: this.focusTitleInput.name,
166 - focusIn: false,
167 - },
168 - ] );
169 - }
170 -
171 - /* Handle show popup items to select - set sectionIdSelected */
172 - handleShowPopupItemsToSelect( args ) {
173 - const { e, target } = args;
174 -
175 - const elQuizWrap = target.closest( '.lp-edit-quiz-wrap' );
176 - if ( elQuizWrap ) {
177 - this.sectionIdSelected = null;
178 - return;
179 - }
180 -
181 - const elSection = target.closest( EditSection.selectors.elSection );
182 -
183 - const elEditCurriculum =
184 - target.closest( '#lp-course-edit-curriculum' ) ||
185 - target.closest( '.lp-edit-curriculum-wrap' );
186 -
187 - if ( elSection && elEditCurriculum ) {
188 - this.sectionIdSelected = elSection.dataset.sectionId;
189 - } else {
190 - this.sectionIdSelected = null;
191 - }
192 - }
193 -
194 - /* Add item type */
195 - addItemType( args ) {
196 - const { e, target } = args;
197 -
198 - const elBtnSelectItemType = target;
199 -
200 - const itemType = elBtnSelectItemType.dataset.itemType;
201 - const itemPlaceholder = elBtnSelectItemType.dataset.placeholder;
202 - const itemBtnAddText = elBtnSelectItemType.dataset.buttonAddText;
203 -
204 - const elSection = elBtnSelectItemType.closest( `${ EditSection.selectors.elSection }` );
205 - const elSectionActions = elSection.querySelector(
206 - `${ EditSectionItem.selectors.elSectionActions }`
207 - );
208 -
209 - // Insert input item type to add
210 - const elAddItemTypeClone = elSectionActions.querySelector(
211 - `${ EditSectionItem.selectors.elAddItemTypeClone }`
212 - );
213 - const elNewItemByType = elAddItemTypeClone.cloneNode( true );
214 - const elAddItemTypeInput = elNewItemByType.querySelector(
215 - `${ EditSectionItem.selectors.elAddItemTypeTitleInput }`
216 - );
217 - const elBtnAddItem = elNewItemByType.querySelector(
218 - `${ EditSectionItem.selectors.elBtnAddItem }`
219 - );
220 -
221 - elNewItemByType.classList.remove( 'clone' );
222 - elNewItemByType.classList.add( itemType );
223 - lpUtils.lpShowHideEl( elNewItemByType, 1 );
224 - elAddItemTypeInput.setAttribute( 'placeholder', itemPlaceholder );
225 - elAddItemTypeInput.dataset.itemType = itemType;
226 - elBtnAddItem.textContent = itemBtnAddText;
227 - elSectionActions.insertAdjacentElement( 'beforebegin', elNewItemByType );
228 - elAddItemTypeInput.focus();
229 - }
230 -
231 - /* Cancel add item type */
232 - cancelAddItemType( args ) {
233 - const { e, target } = args;
234 - const elAddItemType = target.closest( `${ EditSectionItem.selectors.elAddItemType }` );
235 - if ( elAddItemType ) {
236 - elAddItemType.remove();
237 - }
238 - }
239 -
240 - /* Add item to section */
241 - addItemToSection( args ) {
242 - const { e, target, callBackNest } = args;
243 - e.preventDefault();
244 -
245 - const elAddItemType = target.closest( `${ EditSectionItem.selectors.elAddItemType }` );
246 - const elSection = elAddItemType.closest( `${ EditSection.selectors.elSection }` );
247 - const sectionId = elSection.dataset.sectionId;
248 - const elAddItemTypeTitleInput = elAddItemType.querySelector(
249 - `${ EditSectionItem.selectors.elAddItemTypeTitleInput }`
250 - );
251 - const titleValue = elAddItemTypeTitleInput.value.trim();
252 - const typeValue = elAddItemTypeTitleInput.dataset.itemType;
253 - const message = elAddItemTypeTitleInput.dataset.messEmptyTitle;
254 -
255 - if ( titleValue.length === 0 ) {
256 - lpToastify.show( message, 'error' );
257 - return;
258 - }
259 -
260 - // Clone new section item
261 - const elItemClone = elSection.querySelector( `${ EditSectionItem.selectors.elItemClone }` );
262 - const elItemNew = elItemClone.cloneNode( true );
263 - const elItemTitleInput = elItemNew.querySelector(
264 - `${ EditSectionItem.selectors.elItemTitleInput }`
265 - );
266 -
267 - elItemNew.classList.remove( 'clone' );
268 - elItemNew.classList.add( typeValue );
269 - elItemNew.dataset.itemType = typeValue;
270 - lpUtils.lpShowHideEl( elItemNew, 1 );
271 - lpUtils.lpSetLoadingEl( elItemNew, 1 );
272 - elItemTitleInput.value = titleValue;
273 - elItemTitleInput.dataset.old = titleValue;
274 - elItemClone.insertAdjacentElement( 'beforebegin', elItemNew );
275 - elAddItemType.remove();
276 -
277 - // Call ajax to add item to section
278 - const callBack = {
279 - success: ( response ) => {
280 - const { message, status, data } = response;
281 -
282 - lpToastify.show( message, status );
283 -
284 - if ( status === 'error' ) {
285 - elItemNew.remove();
286 - } else if ( status === 'success' ) {
287 - const { section_item, item_link } = data || {};
288 - const itemId = section_item.item_id || 0;
289 - elItemNew.dataset.itemId = itemId;
290 - elItemNew.querySelector( '.edit-link' ).setAttribute( 'href', item_link || '' );
291 -
292 - // Add popup attributes for Course Builder context
293 - this.addPopupAttributesToItem( elItemNew, itemId, typeValue );
294 -
295 - // Call callback nest if exists
296 - if ( callBackNest && typeof callBackNest.success === 'function' ) {
297 - args.elItemNew = elItemNew;
298 - callBackNest.success( args );
299 - }
300 - }
301 - },
302 - error: ( error ) => {
303 - lpToastify.show( error, 'error' );
304 - elItemNew.remove();
305 - },
306 - completed: () => {
307 - lpUtils.lpSetLoadingEl( elItemNew, 0 );
308 - this.updateCountItems( elSection );
309 -
310 - // Call callback nest if exists
311 - if ( callBackNest && typeof callBackNest.completed === 'function' ) {
312 - args.elItemNew = elItemNew;
313 - callBackNest.completed( args );
314 - }
315 - },
316 - };
317 -
318 - const dataSend = {
319 - course_id: this.courseId,
320 - action: 'create_item_add_to_section',
321 - section_id: sectionId,
322 - item_title: titleValue,
323 - item_type: typeValue,
324 - args: { id_url: idUrlHandle },
325 - };
326 -
327 - if ( document.querySelector( '#lp-course-builder' ) ) {
328 - dataSend.is_course_builder = 1;
329 - }
330 -
331 - window.lpAJAXG.fetchAJAX( dataSend, callBack );
332 - }
333 -
334 - /* Typing in title input */
335 - changeTitle( args ) {
336 - const { target } = args;
337 - const elItemTitleInput = target.closest( `${ EditSectionItem.selectors.elItemTitleInput }` );
338 - if ( ! elItemTitleInput ) {
339 - return;
340 - }
341 -
342 - const elSectionItem = elItemTitleInput.closest(
343 - `${ EditSectionItem.selectors.elSectionItem }`
344 - );
345 - if ( ! elSectionItem ) {
346 - return;
347 - }
348 -
349 - const titleValue = elItemTitleInput.value.trim();
350 - const titleValueOld = elItemTitleInput.dataset.old || '';
351 -
352 - if ( titleValue === titleValueOld ) {
353 - elSectionItem.classList.remove( 'editing' );
354 - } else {
355 - elSectionItem.classList.add( 'editing' );
356 - }
357 - }
358 -
359 - /* Focus in item title input */
360 - focusTitleInput( args ) {
361 - const { target, focusIn = true } = args;
362 -
363 - const elItemTitleInput = target.closest( `${ EditSectionItem.selectors.elItemTitleInput }` );
364 - if ( ! elItemTitleInput ) {
365 - return;
366 - }
367 -
368 - const elSectionItem = elItemTitleInput.closest(
369 - `${ EditSectionItem.selectors.elSectionItem }`
370 - );
371 - if ( ! elSectionItem ) {
372 - return;
373 - }
374 -
375 - if ( focusIn ) {
376 - elSectionItem.classList.add( 'focus' );
377 - } else {
378 - elSectionItem.classList.remove( 'focus' );
379 - }
380 - }
381 -
382 - changeTitleAddNew( args ) {
383 - const { target } = args;
384 - const elAddItemTypeTitleInput = target.closest(
385 - `${ EditSectionItem.selectors.elAddItemTypeTitleInput }`
386 - );
387 - if ( ! elAddItemTypeTitleInput ) {
388 - return;
389 - }
390 -
391 - const elAddItemType = elAddItemTypeTitleInput.closest(
392 - `${ EditSectionItem.selectors.elAddItemType }`
393 - );
394 - if ( ! elAddItemType ) {
395 - return;
396 - }
397 -
398 - const elBtnAddItem = elAddItemType.querySelector(
399 - `${ EditSectionItem.selectors.elBtnAddItem }`
400 - );
401 - if ( ! elBtnAddItem ) {
402 - return;
403 - }
404 -
405 - const titleValue = elAddItemTypeTitleInput.value.trim();
406 - if ( titleValue.length === 0 ) {
407 - elBtnAddItem.classList.remove( 'active' );
408 - } else {
409 - elBtnAddItem.classList.add( 'active' );
410 - }
411 - }
412 -
413 - /* Update item title */
414 - updateTitle( args ) {
415 - const { e, target } = args;
416 -
417 - e.preventDefault();
418 -
419 - const elSectionItem = target.closest( `${ EditSectionItem.selectors.elSectionItem }` );
420 - if ( ! elSectionItem ) {
421 - return;
422 - }
423 -
424 - const elSection = elSectionItem.closest( `${ EditSection.selectors.elSection }` );
425 - if ( ! elSection ) {
426 - return;
427 - }
428 -
429 - const elItemTitleInput = elSectionItem.querySelector(
430 - `${ EditSectionItem.selectors.elItemTitleInput }`
431 - );
432 - if ( ! elItemTitleInput ) {
433 - return;
434 - }
435 -
436 - const itemId = elSectionItem.dataset.itemId;
437 - const itemType = elSectionItem.dataset.itemType;
438 - const itemTitleValue = elItemTitleInput.value.trim();
439 - const titleOld = elItemTitleInput.dataset.old;
440 - const message = elItemTitleInput.dataset.messEmptyTitle;
441 - if ( itemTitleValue.length === 0 ) {
442 - lpToastify.show( message, 'error' );
443 - return;
444 - }
445 -
446 - if ( itemTitleValue === titleOld ) {
447 - return;
448 - }
449 -
450 - // Un-focus input item title
451 - elItemTitleInput.blur();
452 - // show loading
453 - lpUtils.lpSetLoadingEl( elSectionItem, 1 );
454 - // Call ajax to update item title
455 - const callBack = {
456 - success: ( response ) => {
457 - const { message, status } = response;
458 -
459 - if ( status === 'success' ) {
460 - elItemTitleInput.dataset.old = itemTitleValue;
461 - } else {
462 - elItemTitleInput.value = titleOld;
463 - }
464 -
465 - lpToastify.show( message, status );
466 - },
467 - error: ( error ) => {
468 - lpToastify.show( error, 'error' );
469 - },
470 - completed: () => {
471 - lpUtils.lpSetLoadingEl( elSectionItem, 0 );
472 - elSectionItem.classList.remove( 'editing' );
473 - },
474 - };
475 -
476 - const dataSend = {
477 - course_id: this.courseId,
478 - action: 'update_item_of_section',
479 - section_id: elSection.dataset.sectionId,
480 - item_id: itemId,
481 - item_type: itemType,
482 - item_title: itemTitleValue,
483 - args: { id_url: idUrlHandle },
484 - };
485 - window.lpAJAXG.fetchAJAX( dataSend, callBack );
486 - }
487 -
488 - /* Cancel update item title */
489 - cancelUpdateTitle( args ) {
490 - const { e, target } = args;
491 - const elBtnCancelUpdateTitle = target.closest(
492 - `${ EditSectionItem.selectors.elBtnCancelUpdateTitle }`
493 - );
494 - if ( ! elBtnCancelUpdateTitle ) {
495 - return;
496 - }
497 -
498 - const elSectionItem = elBtnCancelUpdateTitle.closest(
499 - `${ EditSectionItem.selectors.elSectionItem }`
500 - );
501 - const elItemTitleInput = elSectionItem.querySelector(
502 - `${ EditSectionItem.selectors.elItemTitleInput }`
503 - );
504 - elItemTitleInput.value = elItemTitleInput.dataset.old || '';
505 - elSectionItem.classList.remove( 'editing' );
506 - }
507 -
508 - /* Delete item from section */
509 - deleteItem( args ) {
510 - const { e, target } = args;
511 - const elBtnDeleteItem = target.closest( `${ EditSectionItem.selectors.elBtnDeleteItem }` );
512 - if ( ! elBtnDeleteItem ) {
513 - return;
514 - }
515 -
516 - const elSectionItem = elBtnDeleteItem.closest( `${ EditSectionItem.selectors.elSectionItem }` );
517 - if ( ! elSectionItem ) {
518 - return;
519 - }
520 -
521 - const itemId = elSectionItem.dataset.itemId;
522 - const elSection = elSectionItem.closest( `${ EditSection.selectors.elSection }` );
523 - const sectionId = elSection.dataset.sectionId;
524 -
525 - SweetAlert.fire( {
526 - title: elBtnDeleteItem.dataset.title,
527 - text: elBtnDeleteItem.dataset.content,
528 - icon: 'warning',
529 - showCloseButton: true,
530 - showCancelButton: true,
531 - cancelButtonText: lpData.i18n.cancel,
532 - confirmButtonText: lpData.i18n.yes,
533 - reverseButtons: true,
534 - } ).then( ( result ) => {
535 - if ( result.isConfirmed ) {
536 - lpUtils.lpSetLoadingEl( elSectionItem, 1 );
537 -
538 - // Call ajax to delete item from section
539 - const callBack = {
540 - success: ( response ) => {
541 - const { message, status } = response;
542 -
543 - lpToastify.show( message, status );
544 -
545 - if ( status === 'success' ) {
546 - elSectionItem.remove();
547 - }
548 - },
549 - error: ( error ) => {
550 - lpToastify.show( error, 'error' );
551 - },
552 - completed: () => {
553 - lpUtils.lpSetLoadingEl( elSectionItem, 0 );
554 - this.updateCountItems( elSection );
555 - },
556 - };
557 -
558 - const dataSend = {
559 - course_id: this.courseId,
560 - action: 'delete_item_from_section',
561 - section_id: sectionId,
562 - item_id: itemId,
563 - args: { id_url: idUrlHandle },
564 - };
565 - window.lpAJAXG.fetchAJAX( dataSend, callBack );
566 - }
567 - } );
568 - }
569 -
570 - /* Sortable items, can drop on multiple sections */
571 - sortAbleItem() {
572 - const elSectionListItems = this.elCurriculumSections.querySelectorAll(
573 - `${ EditSectionItem.selectors.elSectionListItems }`
574 - );
575 - let itemIdChoose = 0;
576 - let elSectionChoose;
577 - let sectionIdChoose = 0;
578 - let sectionIdEnd = 0;
579 - let timeout;
580 - elSectionListItems.forEach( ( elItem ) => {
581 - new Sortable( elItem, {
582 - handle: '.drag',
583 - animation: 150,
584 - group: { name: 'shared' },
585 - onEnd: ( evt ) => {
586 - const dataSectionsItems = [];
587 -
588 - const elItemDragged = evt.item;
589 - sectionIdEnd = elItemDragged.closest( `${ EditSection.selectors.elSection }` ).dataset
590 - .sectionId;
591 -
592 - const dataSend = {
593 - course_id: this.courseId,
594 - args: { id_url: idUrlHandle },
595 - };
596 -
597 - dataSend.action = 'update_item_section_and_position';
598 - dataSend.item_id_change = itemIdChoose;
599 - dataSend.section_id_new_of_item = sectionIdEnd;
600 - dataSend.section_id_old_of_item = sectionIdChoose;
601 -
602 - // Send list items position
603 - const section = this.elCurriculumSections.querySelector(
604 - `.section[data-section-id="${ sectionIdEnd }"]`
605 - );
606 - const items = section.querySelectorAll( `${ EditSectionItem.selectors.elSectionItem }` );
607 - items.forEach( ( elItem ) => {
608 - const itemId = parseInt( elItem.dataset.itemId || 0 );
609 - if ( itemId === 0 ) {
610 - return;
611 - }
612 - dataSectionsItems.push( itemId );
613 - } );
614 - dataSend.items_position = dataSectionsItems;
615 -
616 - // Call ajax to update items position
617 - const callBack = {
618 - success: ( response ) => {
619 - const { message, status } = response;
620 -
621 - lpToastify.show( message, status );
622 - },
623 - error: ( error ) => {
624 - lpToastify.show( error, 'error' );
625 - },
626 - completed: () => {
627 - lpUtils.lpSetLoadingEl( elItemDragged, 0 );
628 - this.updateCountItems( section );
629 - if ( sectionIdChoose !== sectionIdEnd ) {
630 - this.updateCountItems( elSectionChoose );
631 - }
632 - },
633 - };
634 -
635 - lpUtils.lpSetLoadingEl( elItemDragged, 1 );
636 - window.lpAJAXG.fetchAJAX( dataSend, callBack );
637 - },
638 - onMove: ( /*evt*/ ) => {},
639 - onChoose: ( evt ) => {
640 - const elChooseItem = evt.item;
641 - itemIdChoose = elChooseItem.dataset.itemId;
642 - elSectionChoose = elChooseItem.closest( `${ EditSection.selectors.elSection }` );
643 - sectionIdChoose = elSectionChoose.dataset.sectionId;
644 - },
645 - onUpdate: ( /*evt*/ ) => {},
646 - } );
647 - } );
648 - }
649 -
650 - /* Add items selected to section */
651 - addItemsSelectedToSection( itemsSelectedData ) {
652 - // Skip if not in curriculum context (e.g., quiz popup)
653 - if ( ! this.sectionIdSelected ) {
654 - return;
655 - }
656 -
657 - const elSection = document.querySelector(
658 - `.section[data-section-id="${ this.sectionIdSelected }"]`
659 - );
660 -
661 - // Skip if section element not found
662 - if ( ! elSection ) {
663 - return;
664 - }
665 -
666 - const elItemClone = elSection.querySelector( `${ EditSectionItem.selectors.elItemClone }` );
667 -
668 - // Check if we're in Course Builder context
669 - const isCourseBuilder = document.querySelector( '#lp-course-builder' ) !== null;
670 -
671 - itemsSelectedData.forEach( ( item ) => {
672 - const elItemNew = elItemClone.cloneNode( true );
673 - const elInputTitleNew = elItemNew.querySelector(
674 - `${ EditSectionItem.selectors.elItemTitleInput }`
675 - );
676 -
677 - elItemNew.dataset.itemId = item.id;
678 - elItemNew.classList.add( item.type );
679 - elItemNew.classList.remove( 'clone' );
680 - elItemNew.dataset.itemType = item.type;
681 - elItemNew.querySelector( '.edit-link' ).setAttribute( 'href', item.editLink || '' );
682 - elInputTitleNew.value = item.titleSelected || '';
683 -
684 - lpUtils.lpSetLoadingEl( elItemNew, 1 );
685 - lpUtils.lpShowHideEl( elItemNew, 1 );
686 - elItemClone.insertAdjacentElement( 'beforebegin', elItemNew );
687 - } );
688 -
689 - SweetAlert.close();
690 -
691 - const dataSend = {
692 - course_id: this.courseId,
693 - action: 'add_items_to_section',
694 - section_id: this.sectionIdSelected,
695 - items: itemsSelectedData,
696 - is_course_builder: isCourseBuilder ? 1 : 0,
697 - args: { id_url: idUrlHandle },
698 - };
699 - window.lpAJAXG.fetchAJAX( dataSend, {
700 - success: ( response ) => {
701 - const { message, status, data } = response;
702 - const { html } = data || '';
703 - lpToastify.show( message, status );
704 -
705 - itemsSelectedData.forEach( ( item ) => {
706 - const elItemAdded = elSection.querySelector(
707 - `${ EditSectionItem.selectors.elSectionItem }[data-item-id="${ item.id }"]`
708 - );
709 - if ( elItemAdded ) {
710 - elItemAdded.remove();
711 - }
712 - } );
713 -
714 - if ( status === 'success' && html ) {
715 - // Server returns HTML with popup attributes already included when is_course_builder=1
716 - elItemClone.insertAdjacentHTML( 'beforebegin', html );
717 - }
718 - },
719 - error: ( error ) => {
720 - lpToastify.show( error, 'error' );
721 - },
722 - completed: () => {
723 - this.updateCountItems( elSection );
724 - },
725 - } );
726 - }
727 -
728 - /* Enable/disable preview item */
729 - updatePreviewItem( args ) {
730 - const { e, target } = args;
731 - const elBtnSetPreviewItem = target.closest(
732 - `${ EditSectionItem.selectors.elBtnSetPreviewItem }`
733 - );
734 - if ( ! elBtnSetPreviewItem ) {
735 - return;
736 - }
737 -
738 - const elSectionItem = elBtnSetPreviewItem.closest(
739 - `${ EditSectionItem.selectors.elSectionItem }`
740 - );
741 - if ( ! elSectionItem ) {
742 - return;
743 - }
744 -
745 - const icon = elBtnSetPreviewItem.querySelector( 'a' );
746 -
747 - icon.classList.toggle( 'lp-icon-eye' );
748 - icon.classList.toggle( 'lp-icon-eye-slash' );
749 -
750 - const enablePreview = ! icon.classList.contains( 'lp-icon-eye-slash' );
751 -
752 - const itemId = elSectionItem.dataset.itemId;
753 - const itemType = elSectionItem.dataset.itemType;
754 -
755 - lpUtils.lpSetLoadingEl( elSectionItem, 1 );
756 -
757 - // Call ajax to update item preview
758 - const callBack = {
759 - success: ( response ) => {
760 - const { message, status } = response;
761 -
762 - lpToastify.show( message, status );
763 -
764 - if ( status === 'error' ) {
765 - icon.classList.toggle( 'lp-icon-eye' );
766 - icon.classList.toggle( 'lp-icon-eye-slash' );
767 - }
768 - },
769 - error: ( error ) => {
770 - lpToastify.show( error, 'error' );
771 - icon.classList.toggle( 'lp-icon-eye' );
772 - icon.classList.toggle( 'lp-icon-eye-slash' );
773 - },
774 - completed: () => {
775 - lpUtils.lpSetLoadingEl( elSectionItem, 0 );
776 - },
777 - };
778 -
779 - const dataSend = {
780 - course_id: this.courseId,
781 - action: 'update_item_preview',
782 - item_id: itemId,
783 - item_type: itemType,
784 - enable_preview: enablePreview ? 1 : 0,
785 - args: { id_url: idUrlHandle },
786 - };
787 - window.lpAJAXG.fetchAJAX( dataSend, callBack );
788 - }
789 -
790 - /* Update count items when item add/delete or section delete */
791 - updateCountItems( elSection ) {
792 - const elEditCurriculum = this.elEditCurriculum;
793 - const elCountItemsAll = elEditCurriculum.querySelector( '.total-items' );
794 - const elItemsAll = elEditCurriculum.querySelectorAll(
795 - `${ EditSectionItem.selectors.elSectionItem }:not(.clone)`
796 - );
797 - const itemsAllCount = elItemsAll.length;
798 -
799 - elCountItemsAll.dataset.count = itemsAllCount;
800 - elCountItemsAll.querySelector( '.count' ).textContent = itemsAllCount;
801 -
802 - // Count items in section
803 - const elSectionItemsCount = elSection.querySelector( '.section-items-counts' );
804 -
805 - const elItems = elSection.querySelectorAll(
806 - `${ EditSectionItem.selectors.elSectionItem }:not(.clone)`
807 - );
808 - const itemsCount = elItems.length;
809 -
810 - elSectionItemsCount.dataset.count = itemsCount;
811 - elSectionItemsCount.querySelector( '.count' ).textContent = itemsCount;
812 - }
813 -
814 - /**
815 - * Add popup attributes to item element for Course Builder context.
816 - * Only applies when in Course Builder (not admin edit course page).
817 - *
818 - * @param {HTMLElement} elItem - The item element
819 - * @param {number} itemId - The item ID
820 - * @param {string} itemType - The item type (lp_lesson, lp_quiz, lp_assignment)
821 - */
822 - addPopupAttributesToItem( elItem, itemId, itemType ) {
823 - // Check if we're in Course Builder context
824 - const isCourseBuilder = document.querySelector( '#lp-course-builder' ) !== null;
825 -
826 - if ( ! isCourseBuilder || ! itemId ) {
827 - return;
828 - }
829 -
830 - const popupType = itemType.replace( /^lp_/, '' );
831 - const templateSelector = `#lp-tmpl-builder-popup-curriculum-${ popupType }-course-${ this.courseId }`;
832 -
833 - if ( ! popupType || ! document.querySelector( templateSelector ) ) {
834 - return;
835 - }
836 -
837 - // Find the edit link element - it's an <a> with class 'edit-link' inside .item-actions
838 - const editLink = elItem.querySelector( '.item-actions .edit-link' );
839 - if ( ! editLink ) {
840 - return;
841 - }
842 -
843 - // Get the parent <li> element of the edit link
844 - const editBtn = editLink.closest( 'li' );
845 - if ( ! editBtn ) {
846 - return;
847 - }
848 -
849 - // Add popup data attributes based on item type
850 - if ( itemType === 'lp_lesson' ) {
851 - editBtn.setAttribute( 'data-popup-lesson', itemId );
852 - } else if ( itemType === 'lp_quiz' ) {
853 - editBtn.setAttribute( 'data-popup-quiz', itemId );
854 - }
855 -
856 - // Add popup class to the <li> element
857 - editBtn.classList.add( 'lp-btn-edit-item-popup' );
858 -
859 - // Update edit link: remove target="_blank" and update classes for popup behavior
860 - editLink.removeAttribute( 'target' );
861 - editLink.removeAttribute( 'href' );
862 - editLink.classList.add( 'edit-popup-link' );
863 -
864 - // Store additional data for popup on the <li> element
865 - editBtn.setAttribute( 'data-course-id', this.courseId );
866 - editBtn.setAttribute( 'data-popup-type', popupType );
867 - editBtn.setAttribute( 'data-popup-id', itemId );
868 - editBtn.setAttribute( 'data-template', templateSelector );
869 - }
870 -}
1 +/**
2 + * Edit Section item Script on Curriculum
3 + *
4 + * @version 1.0.3
5 + * @since 4.2.8.6
6 + */
7 +import SweetAlert from 'sweetalert2';
8 +import Sortable from 'sortablejs';
9 +import * as lpUtils from 'lpAssetsJsPath/utils.js';
10 +import * as lpToastify from 'lpAssetsJsPath/lpToastify.js';
11 +import { EditCourseCurriculum } from '../edit-curriculum.js';
12 +import { EditSection } from './edit-section.js';
13 +import { LpPopupSelectItemToAdd } from 'lpAssetsJsPath/lpPopupSelectItemToAdd.js';
14 +
15 +const idUrlHandle = 'edit-course-curriculum';
16 +
17 +const lpPopupSelectItemToAdd = new LpPopupSelectItemToAdd();
18 +
19 +export class EditSectionItem {
20 + constructor() {
21 + this.courseId = null;
22 + this.elCurriculumSections = null;
23 + this.sectionIdSelected = null;
24 + }
25 +
26 + static selectors = {
27 + elSectionListItems: '.section-list-items',
28 + elItemClone: '.section-item.clone',
29 + elSectionItem: '.section-item',
30 + elBtnSelectItemType: '.lp-btn-select-item-type',
31 + elAddItemTypeClone: '.lp-add-item-type.clone',
32 + elSectionActions: '.section-actions',
33 + elAddItemType: '.lp-add-item-type',
34 + elAddItemTypeTitleInput: '.lp-add-item-type-title-input',
35 + elBtnAddItemCancel: '.lp-btn-add-item-cancel',
36 + elBtnAddItem: '.lp-btn-add-item',
37 + elItemTitleInput: '.lp-item-title-input',
38 + elBtnUpdateItemTitle: '.lp-btn-update-item-title',
39 + elBtnCancelUpdateTitle: '.lp-btn-cancel-update-item-title',
40 + elBtnDeleteItem: '.lp-btn-delete-item',
41 + elBtnSetPreviewItem: '.lp-btn-set-preview-item',
42 + };
43 +
44 + init() {
45 + this.elEditCurriculum = document.querySelector(
46 + `${ EditCourseCurriculum.selectors.idElEditCurriculum }`
47 + );
48 + this.elCurriculumSections = this.elEditCurriculum.querySelector(
49 + `${ EditCourseCurriculum.selectors.elCurriculumSections }`
50 + );
51 + const elLPTarget = this.elEditCurriculum.closest(
52 + `${ EditCourseCurriculum.selectors.LPTarget }`
53 + );
54 + const dataSend = window.lpAJAXG.getDataSetCurrent( elLPTarget );
55 + this.courseId = dataSend.args.course_id;
56 +
57 + this.events();
58 + this.sortAbleItem();
59 + lpPopupSelectItemToAdd.init();
60 + }
61 +
62 + /* Events */
63 + events() {
64 + // Check and attach events only once
65 + if ( EditSectionItem._loadedEvents ) {
66 + return;
67 + }
68 +
69 + EditSectionItem._loadedEvents = this;
70 +
71 + // Click events
72 + lpUtils.eventHandlers( 'click', [
73 + {
74 + selector: EditSectionItem.selectors.elBtnSelectItemType,
75 + class: this,
76 + callBack: this.addItemType.name,
77 + },
78 + {
79 + selector: EditSectionItem.selectors.elBtnAddItem,
80 + class: this,
81 + callBack: this.addItemToSection.name,
82 + },
83 + {
84 + selector: EditSectionItem.selectors.elBtnAddItemCancel,
85 + class: this,
86 + callBack: this.cancelAddItemType.name,
87 + },
88 + {
89 + selector: EditSectionItem.selectors.elBtnUpdateItemTitle,
90 + class: this,
91 + callBack: this.updateTitle.name,
92 + },
93 + {
94 + selector: EditSectionItem.selectors.elBtnCancelUpdateTitle,
95 + class: this,
96 + callBack: this.cancelUpdateTitle.name,
97 + },
98 + {
99 + selector: EditSectionItem.selectors.elBtnDeleteItem,
100 + class: this,
101 + callBack: this.deleteItem.name,
102 + },
103 + {
104 + selector: LpPopupSelectItemToAdd.selectors.elBtnShowPopupItemsToSelect,
105 + class: this,
106 + callBack: this.handleShowPopupItemsToSelect.name,
107 + },
108 + {
109 + selector: LpPopupSelectItemToAdd.selectors.elBtnAddItemsSelected,
110 + class: lpPopupSelectItemToAdd,
111 + callBack: lpPopupSelectItemToAdd.addItemsSelectedToSection.name,
112 + callBackHandle: this.addItemsSelectedToSection.bind( this ),
113 + },
114 + {
115 + selector: EditSectionItem.selectors.elBtnSetPreviewItem,
116 + class: this,
117 + callBack: this.updatePreviewItem.name,
118 + },
119 + ] );
120 +
121 + // Keyup events
122 + lpUtils.eventHandlers( 'keyup', [
123 + {
124 + selector: EditSectionItem.selectors.elItemTitleInput,
125 + class: this,
126 + callBack: this.changeTitle.name,
127 + },
128 + {
129 + selector: EditSectionItem.selectors.elAddItemTypeTitleInput,
130 + class: this,
131 + callBack: this.changeTitleAddNew.name,
132 + },
133 + ] );
134 +
135 + // Keydown events
136 + lpUtils.eventHandlers( 'keydown', [
137 + {
138 + selector: EditSectionItem.selectors.elAddItemTypeTitleInput,
139 + class: this,
140 + callBack: this.addItemToSection.name,
141 + checkIsEventEnter: true,
142 + },
143 + {
144 + selector: EditSectionItem.selectors.elItemTitleInput,
145 + class: this,
146 + callBack: this.updateTitle.name,
147 + checkIsEventEnter: true,
148 + },
149 + ] );
150 +
151 + // Focusin events
152 + lpUtils.eventHandlers( 'focusin', [
153 + {
154 + selector: EditSectionItem.selectors.elItemTitleInput,
155 + class: this,
156 + callBack: this.focusTitleInput.name,
157 + },
158 + ] );
159 +
160 + // Focusout events
161 + lpUtils.eventHandlers( 'focusout', [
162 + {
163 + selector: EditSectionItem.selectors.elItemTitleInput,
164 + class: this,
165 + callBack: this.focusTitleInput.name,
166 + focusIn: false,
167 + },
168 + ] );
169 + }
170 +
171 + /* Handle show popup items to select - set sectionIdSelected */
172 + handleShowPopupItemsToSelect( args ) {
173 + const { e, target } = args;
174 +
175 + const elQuizWrap = target.closest( '.lp-edit-quiz-wrap' );
176 + if ( elQuizWrap ) {
177 + this.sectionIdSelected = null;
178 + return;
179 + }
180 +
181 + const elSection = target.closest( EditSection.selectors.elSection );
182 +
183 + const elEditCurriculum =
184 + target.closest( '#lp-course-edit-curriculum' ) ||
185 + target.closest( '.lp-edit-curriculum-wrap' );
186 +
187 + if ( elSection && elEditCurriculum ) {
188 + this.sectionIdSelected = elSection.dataset.sectionId;
189 + } else {
190 + this.sectionIdSelected = null;
191 + }
192 + }
193 +
194 + /* Add item type */
195 + addItemType( args ) {
196 + const { e, target } = args;
197 +
198 + const elBtnSelectItemType = target;
199 +
200 + const itemType = elBtnSelectItemType.dataset.itemType;
201 + const itemPlaceholder = elBtnSelectItemType.dataset.placeholder;
202 + const itemBtnAddText = elBtnSelectItemType.dataset.buttonAddText;
203 +
204 + const elSection = elBtnSelectItemType.closest( `${ EditSection.selectors.elSection }` );
205 + const elSectionActions = elSection.querySelector(
206 + `${ EditSectionItem.selectors.elSectionActions }`
207 + );
208 +
209 + // Insert input item type to add
210 + const elAddItemTypeClone = elSectionActions.querySelector(
211 + `${ EditSectionItem.selectors.elAddItemTypeClone }`
212 + );
213 + const elNewItemByType = elAddItemTypeClone.cloneNode( true );
214 + const elAddItemTypeInput = elNewItemByType.querySelector(
215 + `${ EditSectionItem.selectors.elAddItemTypeTitleInput }`
216 + );
217 + const elBtnAddItem = elNewItemByType.querySelector(
218 + `${ EditSectionItem.selectors.elBtnAddItem }`
219 + );
220 +
221 + elNewItemByType.classList.remove( 'clone' );
222 + elNewItemByType.classList.add( itemType );
223 + lpUtils.lpShowHideEl( elNewItemByType, 1 );
224 + elAddItemTypeInput.setAttribute( 'placeholder', itemPlaceholder );
225 + elAddItemTypeInput.dataset.itemType = itemType;
226 + elBtnAddItem.textContent = itemBtnAddText;
227 + elSectionActions.insertAdjacentElement( 'beforebegin', elNewItemByType );
228 + elAddItemTypeInput.focus();
229 + }
230 +
231 + /* Cancel add item type */
232 + cancelAddItemType( args ) {
233 + const { e, target } = args;
234 + const elAddItemType = target.closest( `${ EditSectionItem.selectors.elAddItemType }` );
235 + if ( elAddItemType ) {
236 + elAddItemType.remove();
237 + }
238 + }
239 +
240 + /* Add item to section */
241 + addItemToSection( args ) {
242 + const { e, target, callBackNest } = args;
243 + e.preventDefault();
244 +
245 + const elAddItemType = target.closest( `${ EditSectionItem.selectors.elAddItemType }` );
246 + const elSection = elAddItemType.closest( `${ EditSection.selectors.elSection }` );
247 + const sectionId = elSection.dataset.sectionId;
248 + const elAddItemTypeTitleInput = elAddItemType.querySelector(
249 + `${ EditSectionItem.selectors.elAddItemTypeTitleInput }`
250 + );
251 + const titleValue = elAddItemTypeTitleInput.value.trim();
252 + const typeValue = elAddItemTypeTitleInput.dataset.itemType;
253 + const message = elAddItemTypeTitleInput.dataset.messEmptyTitle;
254 +
255 + if ( titleValue.length === 0 ) {
256 + lpToastify.show( message, 'error' );
257 + return;
258 + }
259 +
260 + // Clone new section item
261 + const elItemClone = elSection.querySelector( `${ EditSectionItem.selectors.elItemClone }` );
262 + const elItemNew = elItemClone.cloneNode( true );
263 + const elItemTitleInput = elItemNew.querySelector(
264 + `${ EditSectionItem.selectors.elItemTitleInput }`
265 + );
266 +
267 + elItemNew.classList.remove( 'clone' );
268 + elItemNew.classList.add( typeValue );
269 + elItemNew.dataset.itemType = typeValue;
270 + lpUtils.lpShowHideEl( elItemNew, 1 );
271 + lpUtils.lpSetLoadingEl( elItemNew, 1 );
272 + elItemTitleInput.value = titleValue;
273 + elItemTitleInput.dataset.old = titleValue;
274 + elItemClone.insertAdjacentElement( 'beforebegin', elItemNew );
275 + elAddItemType.remove();
276 +
277 + // Call ajax to add item to section
278 + const callBack = {
279 + success: ( response ) => {
280 + const { message, status, data } = response;
281 +
282 + lpToastify.show( message, status );
283 +
284 + if ( status === 'error' ) {
285 + elItemNew.remove();
286 + } else if ( status === 'success' ) {
287 + const { section_item, item_link } = data || {};
288 + const itemId = section_item.item_id || 0;
289 + elItemNew.dataset.itemId = itemId;
290 + elItemNew.querySelector( '.edit-link' ).setAttribute( 'href', item_link || '' );
291 +
292 + // Add popup attributes for Course Builder context
293 + this.addPopupAttributesToItem( elItemNew, itemId, typeValue );
294 +
295 + // Call callback nest if exists
296 + if ( callBackNest && typeof callBackNest.success === 'function' ) {
297 + args.elItemNew = elItemNew;
298 + callBackNest.success( args );
299 + }
300 + }
301 + },
302 + error: ( error ) => {
303 + lpToastify.show( error, 'error' );
304 + elItemNew.remove();
305 + },
306 + completed: () => {
307 + lpUtils.lpSetLoadingEl( elItemNew, 0 );
308 + this.updateCountItems( elSection );
309 +
310 + // Call callback nest if exists
311 + if ( callBackNest && typeof callBackNest.completed === 'function' ) {
312 + args.elItemNew = elItemNew;
313 + callBackNest.completed( args );
314 + }
315 + },
316 + };
317 +
318 + const dataSend = {
319 + course_id: this.courseId,
320 + action: 'create_item_add_to_section',
321 + section_id: sectionId,
322 + item_title: titleValue,
323 + item_type: typeValue,
324 + args: { id_url: idUrlHandle },
325 + };
326 +
327 + if ( document.querySelector( '#lp-course-builder' ) ) {
328 + dataSend.is_course_builder = 1;
329 + }
330 +
331 + window.lpAJAXG.fetchAJAX( dataSend, callBack );
332 + }
333 +
334 + /* Typing in title input */
335 + changeTitle( args ) {
336 + const { target } = args;
337 + const elItemTitleInput = target.closest( `${ EditSectionItem.selectors.elItemTitleInput }` );
338 + if ( ! elItemTitleInput ) {
339 + return;
340 + }
341 +
342 + const elSectionItem = elItemTitleInput.closest(
343 + `${ EditSectionItem.selectors.elSectionItem }`
344 + );
345 + if ( ! elSectionItem ) {
346 + return;
347 + }
348 +
349 + const titleValue = elItemTitleInput.value.trim();
350 + const titleValueOld = elItemTitleInput.dataset.old || '';
351 +
352 + if ( titleValue === titleValueOld ) {
353 + elSectionItem.classList.remove( 'editing' );
354 + } else {
355 + elSectionItem.classList.add( 'editing' );
356 + }
357 + }
358 +
359 + /* Focus in item title input */
360 + focusTitleInput( args ) {
361 + const { target, focusIn = true } = args;
362 +
363 + const elItemTitleInput = target.closest( `${ EditSectionItem.selectors.elItemTitleInput }` );
364 + if ( ! elItemTitleInput ) {
365 + return;
366 + }
367 +
368 + const elSectionItem = elItemTitleInput.closest(
369 + `${ EditSectionItem.selectors.elSectionItem }`
370 + );
371 + if ( ! elSectionItem ) {
372 + return;
373 + }
374 +
375 + if ( focusIn ) {
376 + elSectionItem.classList.add( 'focus' );
377 + } else {
378 + elSectionItem.classList.remove( 'focus' );
379 + }
380 + }
381 +
382 + changeTitleAddNew( args ) {
383 + const { target } = args;
384 + const elAddItemTypeTitleInput = target.closest(
385 + `${ EditSectionItem.selectors.elAddItemTypeTitleInput }`
386 + );
387 + if ( ! elAddItemTypeTitleInput ) {
388 + return;
389 + }
390 +
391 + const elAddItemType = elAddItemTypeTitleInput.closest(
392 + `${ EditSectionItem.selectors.elAddItemType }`
393 + );
394 + if ( ! elAddItemType ) {
395 + return;
396 + }
397 +
398 + const elBtnAddItem = elAddItemType.querySelector(
399 + `${ EditSectionItem.selectors.elBtnAddItem }`
400 + );
401 + if ( ! elBtnAddItem ) {
402 + return;
403 + }
404 +
405 + const titleValue = elAddItemTypeTitleInput.value.trim();
406 + if ( titleValue.length === 0 ) {
407 + elBtnAddItem.classList.remove( 'active' );
408 + } else {
409 + elBtnAddItem.classList.add( 'active' );
410 + }
411 + }
412 +
413 + /* Update item title */
414 + updateTitle( args ) {
415 + const { e, target } = args;
416 +
417 + e.preventDefault();
418 +
419 + const elSectionItem = target.closest( `${ EditSectionItem.selectors.elSectionItem }` );
420 + if ( ! elSectionItem ) {
421 + return;
422 + }
423 +
424 + const elSection = elSectionItem.closest( `${ EditSection.selectors.elSection }` );
425 + if ( ! elSection ) {
426 + return;
427 + }
428 +
429 + const elItemTitleInput = elSectionItem.querySelector(
430 + `${ EditSectionItem.selectors.elItemTitleInput }`
431 + );
432 + if ( ! elItemTitleInput ) {
433 + return;
434 + }
435 +
436 + const itemId = elSectionItem.dataset.itemId;
437 + const itemType = elSectionItem.dataset.itemType;
438 + const itemTitleValue = elItemTitleInput.value.trim();
439 + const titleOld = elItemTitleInput.dataset.old;
440 + const message = elItemTitleInput.dataset.messEmptyTitle;
441 + if ( itemTitleValue.length === 0 ) {
442 + lpToastify.show( message, 'error' );
443 + return;
444 + }
445 +
446 + if ( itemTitleValue === titleOld ) {
447 + return;
448 + }
449 +
450 + // Un-focus input item title
451 + elItemTitleInput.blur();
452 + // show loading
453 + lpUtils.lpSetLoadingEl( elSectionItem, 1 );
454 + // Call ajax to update item title
455 + const callBack = {
456 + success: ( response ) => {
457 + const { message, status } = response;
458 +
459 + if ( status === 'success' ) {
460 + elItemTitleInput.dataset.old = itemTitleValue;
461 + } else {
462 + elItemTitleInput.value = titleOld;
463 + }
464 +
465 + lpToastify.show( message, status );
466 + },
467 + error: ( error ) => {
468 + lpToastify.show( error, 'error' );
469 + },
470 + completed: () => {
471 + lpUtils.lpSetLoadingEl( elSectionItem, 0 );
472 + elSectionItem.classList.remove( 'editing' );
473 + },
474 + };
475 +
476 + const dataSend = {
477 + course_id: this.courseId,
478 + action: 'update_item_of_section',
479 + section_id: elSection.dataset.sectionId,
480 + item_id: itemId,
481 + item_type: itemType,
482 + item_title: itemTitleValue,
483 + args: { id_url: idUrlHandle },
484 + };
485 + window.lpAJAXG.fetchAJAX( dataSend, callBack );
486 + }
487 +
488 + /* Cancel update item title */
489 + cancelUpdateTitle( args ) {
490 + const { e, target } = args;
491 + const elBtnCancelUpdateTitle = target.closest(
492 + `${ EditSectionItem.selectors.elBtnCancelUpdateTitle }`
493 + );
494 + if ( ! elBtnCancelUpdateTitle ) {
495 + return;
496 + }
497 +
498 + const elSectionItem = elBtnCancelUpdateTitle.closest(
499 + `${ EditSectionItem.selectors.elSectionItem }`
500 + );
501 + const elItemTitleInput = elSectionItem.querySelector(
502 + `${ EditSectionItem.selectors.elItemTitleInput }`
503 + );
504 + elItemTitleInput.value = elItemTitleInput.dataset.old || '';
505 + elSectionItem.classList.remove( 'editing' );
506 + }
507 +
508 + /* Delete item from section */
509 + deleteItem( args ) {
510 + const { e, target } = args;
511 + const elBtnDeleteItem = target.closest( `${ EditSectionItem.selectors.elBtnDeleteItem }` );
512 + if ( ! elBtnDeleteItem ) {
513 + return;
514 + }
515 +
516 + const elSectionItem = elBtnDeleteItem.closest( `${ EditSectionItem.selectors.elSectionItem }` );
517 + if ( ! elSectionItem ) {
518 + return;
519 + }
520 +
521 + const itemId = elSectionItem.dataset.itemId;
522 + const elSection = elSectionItem.closest( `${ EditSection.selectors.elSection }` );
523 + const sectionId = elSection.dataset.sectionId;
524 +
525 + SweetAlert.fire( {
526 + title: elBtnDeleteItem.dataset.title,
527 + text: elBtnDeleteItem.dataset.content,
528 + icon: 'warning',
529 + showCloseButton: true,
530 + showCancelButton: true,
531 + cancelButtonText: lpData.i18n.cancel,
532 + confirmButtonText: lpData.i18n.yes,
533 + reverseButtons: true,
534 + } ).then( ( result ) => {
535 + if ( result.isConfirmed ) {
536 + lpUtils.lpSetLoadingEl( elSectionItem, 1 );
537 +
538 + // Call ajax to delete item from section
539 + const callBack = {
540 + success: ( response ) => {
541 + const { message, status } = response;
542 +
543 + lpToastify.show( message, status );
544 +
545 + if ( status === 'success' ) {
546 + elSectionItem.remove();
547 + }
548 + },
549 + error: ( error ) => {
550 + lpToastify.show( error, 'error' );
551 + },
552 + completed: () => {
553 + lpUtils.lpSetLoadingEl( elSectionItem, 0 );
554 + this.updateCountItems( elSection );
555 + },
556 + };
557 +
558 + const dataSend = {
559 + course_id: this.courseId,
560 + action: 'delete_item_from_section',
561 + section_id: sectionId,
562 + item_id: itemId,
563 + args: { id_url: idUrlHandle },
564 + };
565 + window.lpAJAXG.fetchAJAX( dataSend, callBack );
566 + }
567 + } );
568 + }
569 +
570 + /* Sortable items, can drop on multiple sections */
571 + sortAbleItem() {
572 + const elSectionListItems = this.elCurriculumSections.querySelectorAll(
573 + `${ EditSectionItem.selectors.elSectionListItems }`
574 + );
575 + let itemIdChoose = 0;
576 + let elSectionChoose;
577 + let sectionIdChoose = 0;
578 + let sectionIdEnd = 0;
579 + let timeout;
580 + elSectionListItems.forEach( ( elItem ) => {
581 + new Sortable( elItem, {
582 + handle: '.drag',
583 + animation: 150,
584 + group: { name: 'shared' },
585 + onEnd: ( evt ) => {
586 + const dataSectionsItems = [];
587 +
588 + const elItemDragged = evt.item;
589 + sectionIdEnd = elItemDragged.closest( `${ EditSection.selectors.elSection }` ).dataset
590 + .sectionId;
591 +
592 + const dataSend = {
593 + course_id: this.courseId,
594 + args: { id_url: idUrlHandle },
595 + };
596 +
597 + dataSend.action = 'update_item_section_and_position';
598 + dataSend.item_id_change = itemIdChoose;
599 + dataSend.section_id_new_of_item = sectionIdEnd;
600 + dataSend.section_id_old_of_item = sectionIdChoose;
601 +
602 + // Send list items position
603 + const section = this.elCurriculumSections.querySelector(
604 + `.section[data-section-id="${ sectionIdEnd }"]`
605 + );
606 + const items = section.querySelectorAll( `${ EditSectionItem.selectors.elSectionItem }` );
607 + items.forEach( ( elItem ) => {
608 + const itemId = parseInt( elItem.dataset.itemId || 0 );
609 + if ( itemId === 0 ) {
610 + return;
611 + }
612 + dataSectionsItems.push( itemId );
613 + } );
614 + dataSend.items_position = dataSectionsItems;
615 +
616 + // Call ajax to update items position
617 + const callBack = {
618 + success: ( response ) => {
619 + const { message, status } = response;
620 +
621 + lpToastify.show( message, status );
622 + },
623 + error: ( error ) => {
624 + lpToastify.show( error, 'error' );
625 + },
626 + completed: () => {
627 + lpUtils.lpSetLoadingEl( elItemDragged, 0 );
628 + this.updateCountItems( section );
629 + if ( sectionIdChoose !== sectionIdEnd ) {
630 + this.updateCountItems( elSectionChoose );
631 + }
632 + },
633 + };
634 +
635 + lpUtils.lpSetLoadingEl( elItemDragged, 1 );
636 + window.lpAJAXG.fetchAJAX( dataSend, callBack );
637 + },
638 + onMove: ( /*evt*/ ) => {},
639 + onChoose: ( evt ) => {
640 + const elChooseItem = evt.item;
641 + itemIdChoose = elChooseItem.dataset.itemId;
642 + elSectionChoose = elChooseItem.closest( `${ EditSection.selectors.elSection }` );
643 + sectionIdChoose = elSectionChoose.dataset.sectionId;
644 + },
645 + onUpdate: ( /*evt*/ ) => {},
646 + } );
647 + } );
648 + }
649 +
650 + /* Add items selected to section */
651 + addItemsSelectedToSection( itemsSelectedData ) {
652 + // Skip if not in curriculum context (e.g., quiz popup)
653 + if ( ! this.sectionIdSelected ) {
654 + return;
655 + }
656 +
657 + const elSection = document.querySelector(
658 + `.section[data-section-id="${ this.sectionIdSelected }"]`
659 + );
660 +
661 + // Skip if section element not found
662 + if ( ! elSection ) {
663 + return;
664 + }
665 +
666 + const elItemClone = elSection.querySelector( `${ EditSectionItem.selectors.elItemClone }` );
667 +
668 + // Check if we're in Course Builder context
669 + const isCourseBuilder = document.querySelector( '#lp-course-builder' ) !== null;
670 +
671 + itemsSelectedData.forEach( ( item ) => {
672 + const elItemNew = elItemClone.cloneNode( true );
673 + const elInputTitleNew = elItemNew.querySelector(
674 + `${ EditSectionItem.selectors.elItemTitleInput }`
675 + );
676 +
677 + elItemNew.dataset.itemId = item.id;
678 + elItemNew.classList.add( item.type );
679 + elItemNew.classList.remove( 'clone' );
680 + elItemNew.dataset.itemType = item.type;
681 + elItemNew.querySelector( '.edit-link' ).setAttribute( 'href', item.editLink || '' );
682 + elInputTitleNew.value = item.titleSelected || '';
683 +
684 + lpUtils.lpSetLoadingEl( elItemNew, 1 );
685 + lpUtils.lpShowHideEl( elItemNew, 1 );
686 + elItemClone.insertAdjacentElement( 'beforebegin', elItemNew );
687 + } );
688 +
689 + SweetAlert.close();
690 +
691 + const dataSend = {
692 + course_id: this.courseId,
693 + action: 'add_items_to_section',
694 + section_id: this.sectionIdSelected,
695 + items: itemsSelectedData,
696 + is_course_builder: isCourseBuilder ? 1 : 0,
697 + args: { id_url: idUrlHandle },
698 + };
699 + window.lpAJAXG.fetchAJAX( dataSend, {
700 + success: ( response ) => {
701 + const { message, status, data } = response;
702 + const { html } = data || '';
703 + lpToastify.show( message, status );
704 +
705 + itemsSelectedData.forEach( ( item ) => {
706 + const elItemAdded = elSection.querySelector(
707 + `${ EditSectionItem.selectors.elSectionItem }[data-item-id="${ item.id }"]`
708 + );
709 + if ( elItemAdded ) {
710 + elItemAdded.remove();
711 + }
712 + } );
713 +
714 + if ( status === 'success' && html ) {
715 + // Server returns HTML with popup attributes already included when is_course_builder=1
716 + elItemClone.insertAdjacentHTML( 'beforebegin', html );
717 + }
718 + },
719 + error: ( error ) => {
720 + lpToastify.show( error, 'error' );
721 + },
722 + completed: () => {
723 + this.updateCountItems( elSection );
724 + },
725 + } );
726 + }
727 +
728 + /* Enable/disable preview item */
729 + updatePreviewItem( args ) {
730 + const { e, target } = args;
731 + const elBtnSetPreviewItem = target.closest(
732 + `${ EditSectionItem.selectors.elBtnSetPreviewItem }`
733 + );
734 + if ( ! elBtnSetPreviewItem ) {
735 + return;
736 + }
737 +
738 + const elSectionItem = elBtnSetPreviewItem.closest(
739 + `${ EditSectionItem.selectors.elSectionItem }`
740 + );
741 + if ( ! elSectionItem ) {
742 + return;
743 + }
744 +
745 + const icon = elBtnSetPreviewItem.querySelector( 'a' );
746 +
747 + icon.classList.toggle( 'lp-icon-eye' );
748 + icon.classList.toggle( 'lp-icon-eye-slash' );
749 +
750 + const enablePreview = ! icon.classList.contains( 'lp-icon-eye-slash' );
751 +
752 + const itemId = elSectionItem.dataset.itemId;
753 + const itemType = elSectionItem.dataset.itemType;
754 +
755 + lpUtils.lpSetLoadingEl( elSectionItem, 1 );
756 +
757 + // Call ajax to update item preview
758 + const callBack = {
759 + success: ( response ) => {
760 + const { message, status } = response;
761 +
762 + lpToastify.show( message, status );
763 +
764 + if ( status === 'error' ) {
765 + icon.classList.toggle( 'lp-icon-eye' );
766 + icon.classList.toggle( 'lp-icon-eye-slash' );
767 + }
768 + },
769 + error: ( error ) => {
770 + lpToastify.show( error, 'error' );
771 + icon.classList.toggle( 'lp-icon-eye' );
772 + icon.classList.toggle( 'lp-icon-eye-slash' );
773 + },
774 + completed: () => {
775 + lpUtils.lpSetLoadingEl( elSectionItem, 0 );
776 + },
777 + };
778 +
779 + const dataSend = {
780 + course_id: this.courseId,
781 + action: 'update_item_preview',
782 + item_id: itemId,
783 + item_type: itemType,
784 + enable_preview: enablePreview ? 1 : 0,
785 + args: { id_url: idUrlHandle },
786 + };
787 + window.lpAJAXG.fetchAJAX( dataSend, callBack );
788 + }
789 +
790 + /* Update count items when item add/delete or section delete */
791 + updateCountItems( elSection ) {
792 + const elEditCurriculum = this.elEditCurriculum;
793 + const elCountItemsAll = elEditCurriculum.querySelector( '.total-items' );
794 + const elItemsAll = elEditCurriculum.querySelectorAll(
795 + `${ EditSectionItem.selectors.elSectionItem }:not(.clone)`
796 + );
797 + const itemsAllCount = elItemsAll.length;
798 +
799 + elCountItemsAll.dataset.count = itemsAllCount;
800 + elCountItemsAll.querySelector( '.count' ).textContent = itemsAllCount;
801 +
802 + // Count items in section
803 + const elSectionItemsCount = elSection.querySelector( '.section-items-counts' );
804 +
805 + const elItems = elSection.querySelectorAll(
806 + `${ EditSectionItem.selectors.elSectionItem }:not(.clone)`
807 + );
808 + const itemsCount = elItems.length;
809 +
810 + elSectionItemsCount.dataset.count = itemsCount;
811 + elSectionItemsCount.querySelector( '.count' ).textContent = itemsCount;
812 + }
813 +
814 + /**
815 + * Add popup attributes to item element for Course Builder context.
816 + * Only applies when in Course Builder (not admin edit course page).
817 + *
818 + * @param {HTMLElement} elItem - The item element
819 + * @param {number} itemId - The item ID
820 + * @param {string} itemType - The item type (lp_lesson, lp_quiz, lp_assignment)
821 + */
822 + addPopupAttributesToItem( elItem, itemId, itemType ) {
823 + // Check if we're in Course Builder context
824 + const isCourseBuilder = document.querySelector( '#lp-course-builder' ) !== null;
825 +
826 + if ( ! isCourseBuilder || ! itemId ) {
827 + return;
828 + }
829 +
830 + const popupType = itemType.replace( /^lp_/, '' );
831 + const templateSelector = `#lp-tmpl-builder-popup-curriculum-${ popupType }-course-${ this.courseId }`;
832 +
833 + if ( ! popupType || ! document.querySelector( templateSelector ) ) {
834 + return;
835 + }
836 +
837 + // Find the edit link element - it's an <a> with class 'edit-link' inside .item-actions
838 + const editLink = elItem.querySelector( '.item-actions .edit-link' );
839 + if ( ! editLink ) {
840 + return;
841 + }
842 +
843 + // Get the parent <li> element of the edit link
844 + const editBtn = editLink.closest( 'li' );
845 + if ( ! editBtn ) {
846 + return;
847 + }
848 +
849 + // Add popup data attributes based on item type
850 + if ( itemType === 'lp_lesson' ) {
851 + editBtn.setAttribute( 'data-popup-lesson', itemId );
852 + } else if ( itemType === 'lp_quiz' ) {
853 + editBtn.setAttribute( 'data-popup-quiz', itemId );
854 + }
855 +
856 + // Add popup class to the <li> element
857 + editBtn.classList.add( 'lp-btn-edit-item-popup' );
858 +
859 + // Update edit link: remove target="_blank" and update classes for popup behavior
860 + editLink.removeAttribute( 'target' );
861 + editLink.removeAttribute( 'href' );
862 + editLink.classList.add( 'edit-popup-link' );
863 +
864 + // Store additional data for popup on the <li> element
865 + editBtn.setAttribute( 'data-course-id', this.courseId );
866 + editBtn.setAttribute( 'data-popup-type', popupType );
867 + editBtn.setAttribute( 'data-popup-id', itemId );
868 + editBtn.setAttribute( 'data-template', templateSelector );
869 + }
870 +}