PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
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 4.1.7.3.1 All 141 releases
← All changes | assets/src/js/frontend/course-builder/builder-form-state.js +232 -232 4.3.9 → 4.4.9 View file →
@@ -1,232 +1,232 @@
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;
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;