PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.1
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.1
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 / admin / edit-quiz.js

edit-quiz.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.1, at assets/src/js/admin/edit-quiz.js

598 lines 18.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Edit Quiz JS handler as a class.
3 *
4 * @since 4.2.8.6
5 * @version 1.0.3
6 */
7 import * as lpUtils from '../utils.js';
8 import SweetAlert from 'sweetalert2';
9 import Sortable from 'sortablejs';
10 import * as lpToastify from 'lpAssetsJsPath/lpToastify.js';
11 import 'toastify-js/src/toastify.css';
12 import { EditQuestion } from './edit-question.js';
13 import { LpPopupSelectItemToAdd } from 'lpAssetsJsPath/lpPopupSelectItemToAdd.js';
14
15 let editQuestion;
16
17 const lpPopupSelectItemToAdd = new LpPopupSelectItemToAdd();
18 lpPopupSelectItemToAdd.init();
19
20 class EditQuiz {
21 constructor() {
22 this.idUrlHandle = 'edit-quiz-questions';
23 this.elEditQuizWrap = null;
24 this.elEditListQuestions = null;
25 this.quizID = null;
26 }
27
28 static selectors = {
29 elEditQuizWrap: '.lp-edit-quiz-wrap',
30 elQuestionEditMain: '.lp-question-edit-main',
31 elQuestionToggleAll: '.lp-question-toggle-all',
32 elEditListQuestions: '.lp-edit-list-questions',
33 elQuestionItem: '.lp-question-item',
34 elQuestionToggle: '.lp-question-toggle',
35 elPopupItemsToSelectClone: '.lp-popup-items-to-select.clone',
36 elBtnAddQuestion: '.lp-btn-add-question',
37 elBtnRemoveQuestion: '.lp-btn-remove-question',
38 elBtnUpdateQuestionTitle: '.lp-btn-update-question-title',
39 elBtnCancelUpdateQuestionTitle: '.lp-btn-cancel-update-question-title',
40 elQuestionTitleNewInput: '.lp-question-title-new-input',
41 elQuestionTitleInput: '.lp-question-title-input',
42 elQuestionTypeLabel: '.lp-question-type-label',
43 elQuestionTypeNew: '.lp-question-type-new',
44 elAddNewQuestion: 'add-new-question',
45 elQuestionClone: '.lp-question-item.clone',
46 LPTarget: '.lp-target',
47 elCollapse: 'lp-collapse',
48 };
49
50 init() {
51 lpUtils.lpOnElementReady( EditQuiz.selectors.elEditQuizWrap, ( elEditQuizWrapFound ) => {
52 this.elEditQuizWrap = elEditQuizWrapFound;
53 this.elEditListQuestions = this.elEditQuizWrap.querySelector( EditQuiz.selectors.elEditListQuestions );
54 const elLPTarget = this.elEditQuizWrap.closest( EditQuiz.selectors.LPTarget );
55 const dataSend = window.lpAJAXG.getDataSetCurrent( elLPTarget );
56 this.quizID = dataSend.args.quiz_id;
57
58 this.sortAbleQuestion();
59 editQuestion = new EditQuestion();
60 editQuestion.init();
61 const elQuestionEditMains = elEditQuizWrapFound.querySelectorAll( `${ EditQuiz.selectors.elQuestionEditMain }` );
62 elQuestionEditMains.forEach( ( elQuestionEditMain ) => {
63 editQuestion.sortAbleQuestionAnswer( elQuestionEditMain );
64 } );
65
66 this.events();
67 } );
68 }
69
70 events() {
71 if ( EditQuiz._loadedEvents ) {
72 return;
73 }
74 EditQuiz._loadedEvents = true;
75
76 lpUtils.eventHandlers( 'click', [
77 {
78 selector: EditQuiz.selectors.elQuestionToggleAll,
79 class: this,
80 callBack: this.toggleQuestionAll.name,
81 },
82 {
83 selector: EditQuiz.selectors.elBtnAddQuestion,
84 class: this,
85 callBack: this.addQuestion.name,
86 },
87 {
88 selector: EditQuiz.selectors.elBtnRemoveQuestion,
89 class: this,
90 callBack: this.removeQuestion.name,
91 },
92 {
93 selector: EditQuiz.selectors.elBtnUpdateQuestionTitle,
94 class: this,
95 callBack: this.updateQuestionTitle.name,
96 },
97 {
98 selector: EditQuiz.selectors.elBtnCancelUpdateQuestionTitle,
99 class: this,
100 callBack: this.cancelChangeTitleQuestion.name,
101 },
102 {
103 selector: LpPopupSelectItemToAdd.selectors.elBtnAddItemsSelected,
104 class: lpPopupSelectItemToAdd,
105 callBack: lpPopupSelectItemToAdd.addItemsSelectedToSection.name,
106 callBackHandle: this.addQuestionsSelectedToQuiz.bind( this ),
107 },
108 ] );
109
110 // Keydown
111 lpUtils.eventHandlers( 'keydown', [
112 {
113 selector: EditQuiz.selectors.elQuestionTitleInput,
114 class: this,
115 callBack: this.updateQuestionTitle.name,
116 checkIsEventEnter: true,
117 },
118 {
119 selector: EditQuiz.selectors.elQuestionTitleNewInput,
120 class: this,
121 callBack: this.addQuestion.name,
122 checkIsEventEnter: true,
123 },
124 ] );
125
126 // Keyup
127 lpUtils.eventHandlers( 'keyup', [
128 {
129 selector: EditQuiz.selectors.elQuestionTitleInput,
130 class: this,
131 callBack: this.changeTitleQuestion.name,
132 },
133 {
134 selector: `${ EditQuiz.selectors.elQuestionTitleNewInput }, ${ EditQuiz.selectors.elQuestionTypeNew }`,
135 class: this,
136 callBack: this.checkCanAddQuestion.name,
137 },
138 ] );
139
140 // Change
141 lpUtils.eventHandlers( 'change', [
142 {
143 selector: EditQuiz.selectors.elQuestionTypeNew,
144 class: this,
145 callBack: this.checkCanAddQuestion.name,
146 },
147 ] );
148
149 // Click
150 document.addEventListener( 'click', ( e ) => {
151 const target = e.target;
152
153 lpUtils.toggleCollapse( e, target, EditQuiz.selectors.elQuestionToggle, [], () => this.checkAllQuestionsCollapsed() );
154 } );
155 }
156
157 // Toggle all questions
158 toggleQuestionAll( args ) {
159 const { e, target } = args;
160 const elQuestionToggleAll = target.closest( `${ EditQuiz.selectors.elQuestionToggleAll }` );
161 if ( ! elQuestionToggleAll ) {
162 return;
163 }
164
165 const elQuestionItems = this.elEditQuizWrap.querySelectorAll( `${ EditQuiz.selectors.elQuestionItem }:not(.clone)` );
166
167 elQuestionToggleAll.classList.toggle( `${ EditQuiz.selectors.elCollapse }` );
168
169 elQuestionItems.forEach( ( el ) => {
170 const shouldCollapse = elQuestionToggleAll.classList.contains( `${ EditQuiz.selectors.elCollapse }` );
171 el.classList.toggle( `${ EditQuiz.selectors.elCollapse }`, shouldCollapse );
172 } );
173 }
174
175 checkAllQuestionsCollapsed() {
176 const elQuestionItems = this.elEditQuizWrap.querySelectorAll( `${ EditQuiz.selectors.elQuestionItem }:not(.clone)` );
177 const elQuestionToggleAll = this.elEditQuizWrap.querySelector( `${ EditQuiz.selectors.elQuestionToggleAll }` );
178
179 let isAllExpand = true;
180 elQuestionItems.forEach( ( el ) => {
181 if ( el.classList.contains( `${ EditQuiz.selectors.elCollapse }` ) ) {
182 isAllExpand = false;
183 return false; // Break
184 }
185 } );
186
187 if ( isAllExpand ) {
188 elQuestionToggleAll.classList.remove( `${ EditQuiz.selectors.elCollapse }` );
189 } else {
190 elQuestionToggleAll.classList.add( `${ EditQuiz.selectors.elCollapse }` );
191 }
192 }
193
194 updateCountItems() {
195 const elCountItemsAll = this.elEditQuizWrap.querySelector( '.total-items' );
196 const elItemsAll = this.elEditQuizWrap.querySelectorAll( `${ EditQuiz.selectors.elQuestionItem }:not(.clone)` );
197 const itemsAllCount = elItemsAll.length;
198
199 elCountItemsAll.dataset.count = itemsAllCount;
200 elCountItemsAll.querySelector( '.count' ).textContent = itemsAllCount;
201 }
202
203 // Add question to quiz
204 addQuestion( args ) {
205 const { e, target, callBackNest } = args;
206 e.preventDefault();
207
208 const elAddNewQuestion = target.closest( `.${ EditQuiz.selectors.elAddNewQuestion }` );
209 if ( ! elAddNewQuestion ) {
210 return;
211 }
212
213 const elQuestionTitleNewInput = elAddNewQuestion.querySelector( `${ EditQuiz.selectors.elQuestionTitleNewInput }` );
214 const questionTitle = elQuestionTitleNewInput.value.trim();
215 if ( ! questionTitle ) {
216 lpToastify.show( elQuestionTitleNewInput.dataset.messEmptyTitle, 'error' );
217 return;
218 }
219
220 const elQuestionType = elAddNewQuestion.querySelector( `${ EditQuiz.selectors.elQuestionTypeNew }` );
221 const questionType = elQuestionType.value;
222 if ( ! questionType ) {
223 lpToastify.show( elQuestionType.dataset.messEmptyType, 'error' );
224 return;
225 }
226
227 const elQuestionClone = this.elEditListQuestions.querySelector( `${ EditQuiz.selectors.elQuestionItem }.clone` );
228 const newQuestionItem = elQuestionClone.cloneNode( true );
229 const elQuestionTitleInput = newQuestionItem.querySelector( `${ EditQuiz.selectors.elQuestionTitleInput }` );
230
231 elQuestionTitleInput.value = questionTitle;
232 elQuestionTitleNewInput.value = '';
233 newQuestionItem.classList.remove( 'clone' );
234 lpUtils.lpShowHideEl( newQuestionItem, 1 );
235 elQuestionClone.insertAdjacentElement( 'beforebegin', newQuestionItem );
236 lpUtils.lpSetLoadingEl( newQuestionItem, 1 );
237
238 const callBack = {
239 success: ( response ) => {
240 const { message, status, data } = response;
241 const { question, html_edit_question } = data;
242
243 if ( status === 'error' ) {
244 throw `Error: ${ message }`;
245 } else if ( status === 'success' ) {
246 newQuestionItem.dataset.questionId = question.ID;
247 newQuestionItem.dataset.questionType = question.meta_data._lp_type;
248 newQuestionItem.outerHTML = html_edit_question;
249 const elQuestionItemCreated = this.elEditListQuestions.querySelector( `${ EditQuiz.selectors.elQuestionItem }[data-question-id="${ question.ID }"]` );
250 elQuestionItemCreated.classList.remove( EditQuiz.selectors.elCollapse );
251 this.updateCountItems();
252 editQuestion.initTinyMCE();
253 const elQuestionEditMain = elQuestionItemCreated.querySelector( `${ EditQuiz.selectors.elQuestionEditMain }` );
254 editQuestion.sortAbleQuestionAnswer( elQuestionEditMain );
255
256 // Callback nest
257 if ( callBackNest && typeof callBackNest.success === 'function' ) {
258 callBackNest.success( { response, elQuestionItemCreated } );
259 }
260 }
261
262 lpToastify.show( message, status );
263 },
264 error: ( error ) => {
265 newQuestionItem.remove();
266 lpToastify.show( error, 'error' );
267
268 if ( callBackNest && typeof callBackNest.error === 'function' ) {
269 callBackNest.error( { error, newQuestionItem } );
270 }
271 },
272 completed: () => {
273 lpUtils.lpSetLoadingEl( newQuestionItem, 0 );
274 this.checkCanAddQuestion( { e, target: elQuestionTitleNewInput } );
275
276 if ( callBackNest && typeof callBackNest.completed === 'function' ) {
277 callBackNest.completed( { newQuestionItem } );
278 }
279 },
280 };
281
282 let dataSend = JSON.parse( elQuestionTitleNewInput.dataset.send );
283 dataSend = { ...dataSend,
284 question_title: questionTitle,
285 question_type: questionType,
286 };
287 window.lpAJAXG.fetchAJAX( dataSend, callBack );
288 }
289
290 // Add questions selected from popup to quiz
291 addQuestionsSelectedToQuiz( itemsSelected ) {
292 const questionIds = [];
293 itemsSelected.forEach( ( item ) => {
294 const elQuestionItemClone = this.elEditQuizWrap.querySelector( `${ EditQuiz.selectors.elQuestionItem }.clone` );
295 if ( ! elQuestionItemClone ) {
296 return;
297 }
298
299 questionIds.push( item.id );
300 const elQuestionItemNew = elQuestionItemClone.cloneNode( true );
301 const elQuestionItemTitleInput = elQuestionItemNew.querySelector( `${ EditQuiz.selectors.elQuestionTitleInput }` );
302 elQuestionItemNew.classList.remove( 'clone' );
303 elQuestionItemNew.dataset.questionId = item.id;
304 elQuestionItemTitleInput.value = item.titleSelected;
305
306 lpUtils.lpSetLoadingEl( elQuestionItemNew, 1 );
307 lpUtils.lpShowHideEl( elQuestionItemNew, 1 );
308 elQuestionItemClone.insertAdjacentElement( 'beforebegin', elQuestionItemNew );
309 lpUtils.lpSetLoadingEl( elQuestionItemNew, 1 );
310 } );
311
312 const callBack = {
313 success: ( response ) => {
314 const { message, status, data } = response;
315
316 if ( status === 'success' ) {
317 lpToastify.show( message, status );
318
319 const { html_edit_question } = data;
320 if ( html_edit_question ) {
321 Object.entries( html_edit_question ).forEach( ( [ question_id, item_html ] ) => {
322 const elQuestionItemNew = this.elEditQuizWrap.querySelector(
323 `${ EditQuiz.selectors.elQuestionItem }[data-question-id="${ question_id }"]`
324 );
325 elQuestionItemNew.outerHTML = item_html;
326 } );
327 }
328 this.updateCountItems();
329 editQuestion.initTinyMCE();
330 } else {
331 throw `Error: ${ message }`;
332 }
333 },
334 error: ( error ) => {
335 lpToastify.show( error, 'error' );
336 },
337 completed: () => {
338 // completed handler intentionally empty
339 },
340 };
341
342 const dataSend = { action: 'add_questions_to_quiz', quiz_id: this.quizID, question_ids: questionIds, args: { id_url: this.idUrlHandle } };
343 window.lpAJAXG.fetchAJAX( dataSend, callBack );
344 }
345
346 checkCanAddQuestion( args ) {
347 const { e, target } = args;
348 const elTrigger = target.closest( EditQuiz.selectors.elQuestionTitleNewInput ) || target.closest( EditQuiz.selectors.elQuestionTypeNew );
349 if ( ! elTrigger ) {
350 return;
351 }
352
353 const elAddNewQuestion = elTrigger.closest( `.${ EditQuiz.selectors.elAddNewQuestion }` );
354 if ( ! elAddNewQuestion ) {
355 return;
356 }
357
358 const elBtnAddQuestion = elAddNewQuestion.querySelector( `${ EditQuiz.selectors.elBtnAddQuestion }` );
359 if ( ! elBtnAddQuestion ) {
360 return;
361 }
362
363 const elQuestionTitleInput = elAddNewQuestion.querySelector( `${ EditQuiz.selectors.elQuestionTitleNewInput }` );
364 const elQuestionTypeNew = elAddNewQuestion.querySelector( `${ EditQuiz.selectors.elQuestionTypeNew }` );
365
366 const questionTitle = elQuestionTitleInput.value.trim();
367 const questionType = elQuestionTypeNew.value;
368
369 if ( questionTitle && questionType ) {
370 elBtnAddQuestion.classList.add( 'active' );
371 } else {
372 elBtnAddQuestion.classList.remove( 'active' );
373 }
374 }
375
376 removeQuestion( args ) {
377 const { e, target } = args;
378 const elBtnRemoveQuestion = target.closest( `${ EditQuiz.selectors.elBtnRemoveQuestion }` );
379 if ( ! elBtnRemoveQuestion ) {
380 return;
381 }
382
383 const elQuestionItem = elBtnRemoveQuestion.closest( `${ EditQuiz.selectors.elQuestionItem }` );
384 if ( ! elQuestionItem ) {
385 return;
386 }
387
388 const questionId = elQuestionItem.dataset.questionId;
389
390 SweetAlert.fire( {
391 title: elBtnRemoveQuestion.dataset.title,
392 text: elBtnRemoveQuestion.dataset.content,
393 icon: 'warning',
394 showCloseButton: true,
395 showCancelButton: true,
396 cancelButtonText: lpDataAdmin.i18n.cancel,
397 confirmButtonText: lpDataAdmin.i18n.yes,
398 reverseButtons: true,
399 } ).then( ( result ) => {
400 if ( result.isConfirmed ) {
401 lpUtils.lpSetLoadingEl( elQuestionItem, 1 );
402
403 const callBack = {
404 success: ( response ) => {
405 const { message, status } = response;
406
407 lpToastify.show( message, status );
408
409 if ( status === 'success' ) {
410 elQuestionItem.remove();
411 this.updateCountItems();
412 }
413 },
414 error: ( error ) => {
415 lpToastify.show( error, 'error' );
416 },
417 completed: () => {
418 lpUtils.lpSetLoadingEl( elQuestionItem, 0 );
419 },
420 };
421
422 const dataSend = {
423 quiz_id: this.quizID,
424 action: 'remove_question_from_quiz',
425 question_id: questionId,
426 args: { id_url: this.idUrlHandle },
427 };
428 window.lpAJAXG.fetchAJAX( dataSend, callBack );
429 }
430 } );
431 }
432
433 updateQuestionTitle( args ) {
434 const { e, target } = args;
435 let canHandle = false;
436
437 if ( target.closest( `${ EditQuiz.selectors.elBtnUpdateQuestionTitle }` ) ) {
438 canHandle = true;
439 } else if ( target.closest( `${ EditQuiz.selectors.elQuestionTitleInput }` ) && e.key === 'Enter' ) {
440 canHandle = true;
441 }
442
443 if ( ! canHandle ) {
444 return;
445 }
446
447 e.preventDefault();
448
449 const elQuestionItem = target.closest( `${ EditQuiz.selectors.elQuestionItem }` );
450 if ( ! elQuestionItem ) {
451 return;
452 }
453
454 const elQuestionTitleInput = elQuestionItem.querySelector( `${ EditQuiz.selectors.elQuestionTitleInput }` );
455 if ( ! elQuestionTitleInput ) {
456 return;
457 }
458
459 const questionId = elQuestionItem.dataset.questionId;
460 const questionTitleValue = elQuestionTitleInput.value.trim();
461 const titleOld = elQuestionTitleInput.dataset.old;
462 const message = elQuestionTitleInput.dataset.messEmptyTitle;
463 if ( questionTitleValue.length === 0 ) {
464 lpToastify.show( message, 'error' );
465 return;
466 }
467
468 if ( questionTitleValue === titleOld ) {
469 return;
470 }
471
472 elQuestionTitleInput.blur();
473 lpUtils.lpSetLoadingEl( elQuestionItem, 1 );
474
475 const callBack = {
476 success: ( response ) => {
477 const { message, status } = response;
478
479 if ( status === 'success' ) {
480 elQuestionTitleInput.dataset.old = questionTitleValue;
481 } else {
482 elQuestionTitleInput.value = titleOld;
483 }
484
485 lpToastify.show( message, status );
486 },
487 error: ( error ) => {
488 lpToastify.show( error, 'error' );
489 },
490 completed: () => {
491 lpUtils.lpSetLoadingEl( elQuestionItem, 0 );
492 elQuestionItem.classList.remove( 'editing' );
493 },
494 };
495
496 const dataSend = {
497 quiz_id: this.quizID,
498 action: 'update_question',
499 question_id: questionId,
500 question_title: questionTitleValue,
501 args: { id_url: this.idUrlHandle },
502 };
503 window.lpAJAXG.fetchAJAX( dataSend, callBack );
504 }
505
506 changeTitleQuestion( args ) {
507 const { e, target } = args;
508 const elQuestionTitleInput = target.closest( `${ EditQuiz.selectors.elQuestionTitleInput }` );
509 if ( ! elQuestionTitleInput ) {
510 return;
511 }
512
513 const elQuestionItem = elQuestionTitleInput.closest( `${ EditQuiz.selectors.elQuestionItem }` );
514 const titleValue = elQuestionTitleInput.value.trim();
515 const titleValueOld = elQuestionTitleInput.dataset.old || '';
516
517 if ( titleValue === titleValueOld ) {
518 elQuestionItem.classList.remove( 'editing' );
519 } else {
520 elQuestionItem.classList.add( 'editing' );
521 }
522 }
523
524 cancelChangeTitleQuestion( args ) {
525 const { e, target } = args;
526 const elBtnCancelUpdateQuestionTitle = target.closest( `${ EditQuiz.selectors.elBtnCancelUpdateQuestionTitle }` );
527 if ( ! elBtnCancelUpdateQuestionTitle ) {
528 return;
529 }
530
531 const elQuestionItem = elBtnCancelUpdateQuestionTitle.closest( `${ EditQuiz.selectors.elQuestionItem }` );
532 const elQuestionTitleInput = elQuestionItem.querySelector( `${ EditQuiz.selectors.elQuestionTitleInput }` );
533 elQuestionTitleInput.value = elQuestionTitleInput.dataset.old || '';
534 elQuestionItem.classList.remove( 'editing' );
535 }
536
537 sortAbleQuestion() {
538 let isUpdateSectionPosition = 0;
539 let timeout;
540
541 new Sortable( this.elEditListQuestions, {
542 handle: '.drag',
543 animation: 150,
544 onEnd: ( evt ) => {
545 const elQuestionItem = evt.item;
546 if ( ! isUpdateSectionPosition ) {
547 return;
548 }
549
550 clearTimeout( timeout );
551 timeout = setTimeout( () => {
552 lpUtils.lpSetLoadingEl( elQuestionItem, 1 );
553
554 const questionIds = [];
555 const elQuestionItems = this.elEditListQuestions.querySelectorAll( `${ EditQuiz.selectors.elQuestionItem }:not(.clone)` );
556 elQuestionItems.forEach( ( elItem ) => {
557 const questionId = elItem.dataset.questionId;
558 if ( questionId ) {
559 questionIds.push( questionId );
560 }
561 } );
562
563 const callBack = {
564 success: ( response ) => {
565 const { message, status } = response;
566
567 if ( status === 'success' ) {
568 lpToastify.show( message, status );
569 editQuestion.initTinyMCE();
570 } else {
571 throw `Error: ${ message }`;
572 }
573 },
574 error: ( error ) => {
575 lpToastify.show( error, 'error' );
576 },
577 completed: () => {
578 lpUtils.lpSetLoadingEl( elQuestionItem, 0 ); isUpdateSectionPosition = 0;
579 },
580 };
581
582 const dataSend = { quiz_id: this.quizID, action: 'update_questions_position', question_ids: questionIds, args: { id_url: this.idUrlHandle } };
583 window.lpAJAXG.fetchAJAX( dataSend, callBack );
584 }, 1000 );
585 },
586 onMove: () => {
587 clearTimeout( timeout );
588 },
589 onUpdate: () => {
590 isUpdateSectionPosition = 1;
591 },
592 } );
593 }
594 }
595
596 const editQuiz = new EditQuiz();
597 editQuiz.init();
598