PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
learnpress / assets / src / js / frontend / course-builder / builder-form-state.js

builder-form-state.js in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.8, at assets/src/js/frontend/course-builder/builder-form-state.js

233 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Course Builder - Form State Management
3 * Track unsaved changes and handle tab navigation
4 *
5 * @since 4.3.0
6 * @version 1.0.0
7 */
8
9 export class BuilderFormState {
10 constructor() {
11 this.hasUnsavedChanges = false;
12 this.formElements = [];
13 this.originalValues = new Map();
14
15 this.init();
16 }
17
18 init() {
19 this.bindEvents();
20 this.captureOriginalValues();
21 }
22
23 /**
24 * Capture original form values for comparison
25 */
26 captureOriginalValues() {
27 const forms = document.querySelectorAll( '.cb-section__course-edit, .lp-cb-tab-content' );
28
29 forms.forEach( form => {
30 const inputs = form.querySelectorAll( 'input, textarea, select' );
31 inputs.forEach( input => {
32 const key = input.name || input.id;
33 if ( key ) {
34 this.originalValues.set( key, this.getInputValue( input ) );
35 }
36 });
37 });
38 }
39
40 /**
41 * Get input value based on type
42 */
43 getInputValue( input ) {
44 if ( input.type === 'checkbox' ) {
45 return input.checked;
46 }
47 if ( input.type === 'radio' ) {
48 return input.checked ? input.value : null;
49 }
50 return input.value;
51 }
52
53 /**
54 * Bind all events
55 */
56 bindEvents() {
57 // Track form changes
58 document.addEventListener( 'input', this.handleFormChange.bind( this ) );
59 document.addEventListener( 'change', this.handleFormChange.bind( this ) );
60
61 // TinyMCE changes (if available)
62 this.bindTinyMCEChanges();
63
64 // Tab navigation warning
65 document.addEventListener( 'click', this.handleTabClick.bind( this ) );
66
67 // Browser navigation warning
68 window.addEventListener( 'beforeunload', this.handleBeforeUnload.bind( this ) );
69
70 // Reset state after successful save
71 document.addEventListener( 'lp-course-builder-saved', this.resetState.bind( this ) );
72 }
73
74 /**
75 * Bind TinyMCE editor changes
76 */
77 bindTinyMCEChanges() {
78 // Wait for TinyMCE to be ready
79 if ( typeof tinymce !== 'undefined' ) {
80 tinymce.on( 'AddEditor', ( e ) => {
81 e.editor.on( 'change', () => {
82 this.markAsChanged();
83 });
84 e.editor.on( 'keyup', () => {
85 this.markAsChanged();
86 });
87 });
88 }
89 }
90
91 /**
92 * Handle form input changes
93 */
94 handleFormChange( e ) {
95 const target = e.target;
96
97 // Ignore curriculum changes
98 if ( target.closest( '#lp-course-edit-curriculum' ) ) {
99 return;
100 }
101
102 // Ignore question settings form - it auto-saves via AJAX
103 if ( target.closest( '.lp-form-setting-question' ) ) {
104 return;
105 }
106
107 // Ignore lesson settings form - it auto-saves via AJAX
108 if ( target.closest( '.lp-form-setting-lesson' ) ) {
109 return;
110 }
111
112 // Ignore quiz questions list - question changes are tracked separately
113 if ( target.closest( '.lp-quiz-questions-list' ) || target.closest( '.lp-edit-quiz-wrap' ) ) {
114 return;
115 }
116
117 // Check if target is within course builder forms
118 if ( target.closest( '.cb-section__course-edit' ) ||
119 target.closest( '.lp-cb-tab-content' ) ||
120 target.closest( '.lp-form-setting-course' ) ||
121 target.closest( '.lp-form-setting-quiz' ) ||
122 target.closest( '.cb-section__quiz-edit' ) ) {
123 this.markAsChanged();
124 }
125 }
126
127 /**
128 * Mark form as having unsaved changes
129 */
130 markAsChanged() {
131 if ( ! this.hasUnsavedChanges ) {
132 this.hasUnsavedChanges = true;
133 this.updateSaveButtonState();
134 }
135 }
136
137 /**
138 * Update save button visual state
139 */
140 updateSaveButtonState() {
141 const saveButtons = document.querySelectorAll( '.cb-btn-update, .lp-cb-save-btn, .cb-btn-main-action' );
142
143 saveButtons.forEach( btn => {
144 if ( this.hasUnsavedChanges ) {
145 btn.classList.add( 'has-changes' );
146 } else {
147 btn.classList.remove( 'has-changes' );
148 }
149 });
150 }
151
152 /**
153 * Handle tab navigation click
154 */
155 handleTabClick( e ) {
156 const tabLink = e.target.closest( '.lp-cb-tabs__item, .lp-cb-sidebar__item a' );
157
158 if ( ! tabLink ) {
159 return;
160 }
161
162 if ( tabLink.hasAttribute( 'data-tab-section' ) ||
163 tabLink.getAttribute( 'href' ) === '#' ) {
164 return;
165 }
166
167 if ( tabLink.classList.contains( 'is-active' ) ||
168 tabLink.closest( '.is-active' ) ) {
169 return;
170 }
171
172 if ( this.hasUnsavedChanges ) {
173 const confirmLeave = confirm(
174 wp?.i18n?.__( 'You have unsaved changes. Are you sure you want to leave this page?', 'learnpress' ) ||
175 'You have unsaved changes. Are you sure you want to leave this page?'
176 );
177
178 if ( ! confirmLeave ) {
179 e.preventDefault();
180 e.stopPropagation();
181 return false;
182 }
183 }
184 }
185
186 /**
187 * Handle browser back/forward/close
188 */
189 handleBeforeUnload( e ) {
190 if ( this.hasUnsavedChanges ) {
191 e.preventDefault();
192 e.returnValue = '';
193 return '';
194 }
195 }
196
197 /**
198 * Reset state after successful save
199 */
200 resetState() {
201 this.hasUnsavedChanges = false;
202 this.updateSaveButtonState();
203 this.captureOriginalValues();
204 }
205
206 /**
207 * Check if form has unsaved changes
208 */
209 hasChanges() {
210 return this.hasUnsavedChanges;
211 }
212
213 /**
214 * Manually set changed state
215 */
216 setChanged( changed = true ) {
217 this.hasUnsavedChanges = changed;
218 this.updateSaveButtonState();
219 }
220 }
221
222 // Export singleton instance
223 let formStateInstance = null;
224
225 export const getFormState = () => {
226 if ( ! formStateInstance ) {
227 formStateInstance = new BuilderFormState();
228 }
229 return formStateInstance;
230 };
231
232 export default BuilderFormState;
233