# learnpress/4.4.9/assets/src/js/frontend/course-builder/builder-form-state.js

LearnPress – WordPress LMS Plugin for Create and Sell Online Courses, version 4.4.9. 233 lines.

- Page: https://pluginprobe.com/plugins/learnpress/4.4.9/code/assets/src/js/frontend/course-builder/builder-form-state.js
- Raw: https://pluginprobe.com/plugins/learnpress/4.4.9/raw/assets/src/js/frontend/course-builder/builder-form-state.js
- Modified: 2026-09-27T10:32:46+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/learnpress/4.4.9/code/assets/src/js/frontend/course-builder/builder-form-state.js#L10-L20`.

```javascript
/**
 * Course Builder - Form State Management
 * Track unsaved changes and handle tab navigation
 *
 * @since 4.3.0
 * @version 1.0.0
 */

export class BuilderFormState {
	constructor() {
		this.hasUnsavedChanges = false;
		this.formElements = [];
		this.originalValues = new Map();
		
		this.init();
	}

	init() {
		this.bindEvents();
		this.captureOriginalValues();
	}

	/**
	 * Capture original form values for comparison
	 */
	captureOriginalValues() {
		const forms = document.querySelectorAll( '.cb-section__course-edit, .lp-cb-tab-content' );
		
		forms.forEach( form => {
			const inputs = form.querySelectorAll( 'input, textarea, select' );
			inputs.forEach( input => {
				const key = input.name || input.id;
				if ( key ) {
					this.originalValues.set( key, this.getInputValue( input ) );
				}
			});
		});
	}

	/**
	 * Get input value based on type
	 */
	getInputValue( input ) {
		if ( input.type === 'checkbox' ) {
			return input.checked;
		}
		if ( input.type === 'radio' ) {
			return input.checked ? input.value : null;
		}
		return input.value;
	}

	/**
	 * Bind all events
	 */
	bindEvents() {
		// Track form changes
		document.addEventListener( 'input', this.handleFormChange.bind( this ) );
		document.addEventListener( 'change', this.handleFormChange.bind( this ) );
		
		// TinyMCE changes (if available)
		this.bindTinyMCEChanges();
		
		// Tab navigation warning
		document.addEventListener( 'click', this.handleTabClick.bind( this ) );
		
		// Browser navigation warning
		window.addEventListener( 'beforeunload', this.handleBeforeUnload.bind( this ) );
		
		// Reset state after successful save
		document.addEventListener( 'lp-course-builder-saved', this.resetState.bind( this ) );
	}

	/**
	 * Bind TinyMCE editor changes
	 */
	bindTinyMCEChanges() {
		// Wait for TinyMCE to be ready
		if ( typeof tinymce !== 'undefined' ) {
			tinymce.on( 'AddEditor', ( e ) => {
				e.editor.on( 'change', () => {
					this.markAsChanged();
				});
				e.editor.on( 'keyup', () => {
					this.markAsChanged();
				});
			});
		}
	}

	/**
	 * Handle form input changes
	 */
	handleFormChange( e ) {
		const target = e.target;

		// Ignore curriculum changes
		if ( target.closest( '#lp-course-edit-curriculum' ) ) {
			return;
		}

		// Ignore question settings form - it auto-saves via AJAX
		if ( target.closest( '.lp-form-setting-question' ) ) {
			return;
		}

		// Ignore lesson settings form - it auto-saves via AJAX
		if ( target.closest( '.lp-form-setting-lesson' ) ) {
			return;
		}

		// Ignore quiz questions list - question changes are tracked separately
		if ( target.closest( '.lp-quiz-questions-list' ) || target.closest( '.lp-edit-quiz-wrap' ) ) {
			return;
		}
		
		// Check if target is within course builder forms
		if ( target.closest( '.cb-section__course-edit' ) || 
			 target.closest( '.lp-cb-tab-content' ) ||
			 target.closest( '.lp-form-setting-course' ) ||
			 target.closest( '.lp-form-setting-quiz' ) ||
			 target.closest( '.cb-section__quiz-edit' ) ) {
			this.markAsChanged();
		}
	}

	/**
	 * Mark form as having unsaved changes
	 */
	markAsChanged() {
		if ( ! this.hasUnsavedChanges ) {
			this.hasUnsavedChanges = true;
			this.updateSaveButtonState();
		}
	}

	/**
	 * Update save button visual state
	 */
	updateSaveButtonState() {
		const saveButtons = document.querySelectorAll( '.cb-btn-update, .lp-cb-save-btn, .cb-btn-main-action' );
		
		saveButtons.forEach( btn => {
			if ( this.hasUnsavedChanges ) {
				btn.classList.add( 'has-changes' );
			} else {
				btn.classList.remove( 'has-changes' );
			}
		});
	}

	/**
	 * Handle tab navigation click
	 */
	handleTabClick( e ) {
		const tabLink = e.target.closest( '.lp-cb-tabs__item, .lp-cb-sidebar__item a' );
		
		if ( ! tabLink ) {
			return;
		}

		if ( tabLink.hasAttribute( 'data-tab-section' ) || 
			 tabLink.getAttribute( 'href' ) === '#' ) {
			return;
		}

		if ( tabLink.classList.contains( 'is-active' ) || 
			 tabLink.closest( '.is-active' ) ) {
			return;
		}

		if ( this.hasUnsavedChanges ) {
			const confirmLeave = confirm( 
				wp?.i18n?.__( 'You have unsaved changes. Are you sure you want to leave this page?', 'learnpress' ) ||
				'You have unsaved changes. Are you sure you want to leave this page?'
			);
			
			if ( ! confirmLeave ) {
				e.preventDefault();
				e.stopPropagation();
				return false;
			}
		}
	}

	/**
	 * Handle browser back/forward/close
	 */
	handleBeforeUnload( e ) {
		if ( this.hasUnsavedChanges ) {
			e.preventDefault();
			e.returnValue = '';
			return '';
		}
	}

	/**
	 * Reset state after successful save
	 */
	resetState() {
		this.hasUnsavedChanges = false;
		this.updateSaveButtonState();
		this.captureOriginalValues();
	}

	/**
	 * Check if form has unsaved changes
	 */
	hasChanges() {
		return this.hasUnsavedChanges;
	}

	/**
	 * Manually set changed state
	 */
	setChanged( changed = true ) {
		this.hasUnsavedChanges = changed;
		this.updateSaveButtonState();
	}
}

// Export singleton instance
let formStateInstance = null;

export const getFormState = () => {
	if ( ! formStateInstance ) {
		formStateInstance = new BuilderFormState();
	}
	return formStateInstance;
};

export default BuilderFormState;

```
