diff --git a/projects/plugins/jetpack/changelog/update-nova-cpt-jquery-to-js b/projects/plugins/jetpack/changelog/update-nova-cpt-jquery-to-js new file mode 100644 index 0000000000000..a3ef11151211c --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-nova-cpt-jquery-to-js @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Restaurant Menu CPT: Convert much of the jQuery usage to JavaScript diff --git a/projects/plugins/jetpack/modules/custom-post-types/js/many-items.js b/projects/plugins/jetpack/modules/custom-post-types/js/many-items.js index 09c7ad911f091..4a3903dc04a05 100644 --- a/projects/plugins/jetpack/modules/custom-post-types/js/many-items.js +++ b/projects/plugins/jetpack/modules/custom-post-types/js/many-items.js @@ -1,111 +1,144 @@ -( function ( $ ) { - var menuSelector, nonceInput, methods; - - methods = { - init: function ( /*options*/ ) { - var $this = this, - tbody, - row; - - this.on( 'keypress.manyItemsTable', function ( event ) { - if ( 13 !== event.which ) { - return; - } +( function () { + let menuSelector, nonceInput; + const initializedTables = new Set(); + + const methods = { + init: function ( table ) { + let tbody = table.lastElementChild; + while ( tbody && tbody.tagName !== 'TBODY' ) { + tbody = tbody.previousElementSibling; + } + const row = tbody.querySelector( 'tr:first-child' ).cloneNode( true ); + + table.dataset.form = table.closest( 'form' ); + table.dataset.tbody = tbody; + table.dataset.row = row; + table.dataset.currentRow = row; + + menuSelector = document.getElementById( 'nova-menu-tax' ); + nonceInput = document.getElementById( '_wpnonce' ); + + table.addEventListener( 'keypress', function ( event ) { + if ( event.which !== 13 ) return; event.preventDefault(); - if ( 'function' === typeof FormData ) { - methods.submitRow.apply( $this ); + if ( typeof FormData === 'function' ) { + methods.submitRow.call( table ); } - methods.addRow.apply( $this ); - } ).on( 'focus.manyItemsTable', ':input', function ( /*event*/ ) { - $this.data( 'currentRow', $( this ).parents( 'tr:first' ) ); + methods.addRow.call( table ); } ); - tbody = this.find( 'tbody:last' ); - row = tbody.find( 'tr:first' ).clone(); - - this.data( 'form', this.parents( 'form:first' ) ); - this.data( 'tbody', tbody ); - this.data( 'row', row ); - this.data( 'currentRow', row ); - - menuSelector = $( '#nova-menu-tax' ); - nonceInput = $( '#_wpnonce' ); + table.addEventListener( 'focusin', function ( event ) { + if ( event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA' ) { + table.dataset.currentRow = event.target.closest( 'tr' ); + } + } ); - return this; + initializedTables.add( table ); + return table; }, - destroy: function () { - this.off( '.manyItemsTable' ); - - return this; + destroy: function ( table ) { + if ( this.observer ) { + this.observer.disconnect(); + } + table.removeEventListener( 'keypress', methods.keypressHandler ); + table.removeEventListener( 'focusin', methods.focusinHandler ); + initializedTables.delete( table ); + return table; }, - submitRow: function () { - var submittedRow, currentInputs, allInputs, partialFormData; + submitRow: function ( table ) { + const submittedRow = table.dataset.currentRow; + const currentInputs = submittedRow.querySelectorAll( 'input, textarea, select' ); + const form = document.querySelector( table.dataset.form ); + const allInputs = Array.from( form.querySelectorAll( 'input, textarea, select' ) ); - submittedRow = this.data( 'currentRow' ); - currentInputs = submittedRow.find( ':input' ); - allInputs = this.data( 'form' ) - .find( ':input' ) - .not( currentInputs ) - .attr( 'disabled', true ) - .end(); + currentInputs.forEach( input => ( input.disabled = true ) ); + allInputs + .filter( input => ! currentInputs.includes( input ) ) + .forEach( input => ( input.disabled = true ) ); - partialFormData = new FormData( this.data( 'form' ).get( 0 ) ); + const partialFormData = new FormData( form ); partialFormData.append( 'ajax', '1' ); - partialFormData.append( 'nova_menu_tax', menuSelector.val() ); - partialFormData.append( '_wpnonce', nonceInput.val() ); - - allInputs.attr( 'disabled', false ); - - $.ajax( { - url: '', - type: 'POST', - data: partialFormData, - processData: false, - contentType: false, - } ).complete( function ( xhr ) { - submittedRow.html( xhr.responseText ); - } ); + partialFormData.append( 'nova_menu_tax', menuSelector.value ); + partialFormData.append( '_wpnonce', nonceInput.value ); - currentInputs.attr( 'disabled', true ); + fetch( '', { + method: 'POST', + body: partialFormData, + } ) + .then( response => response.text() ) + .then( responseText => { + submittedRow.innerHTML = responseText; + } ); - return this; + allInputs.forEach( input => ( input.disabled = false ) ); + + return table; }, - addRow: function () { - var row = this.data( 'row' ).clone(); - row.appendTo( this.data( 'tbody' ) ); - row.find( ':input:first' ).focus(); + addRow: function ( table ) { + const row = table.dataset.row.cloneNode( true ); + + const tbody = table.dataset.tbody; + tbody.appendChild( row ); + + const firstInput = row.querySelector( 'input, textarea, select' ); + if ( firstInput ) firstInput.focus(); - return this; + return table; }, - }; - $.fn.manyItemsTable = function ( method ) { - // Method calling logic - if ( methods[ method ] ) { - return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); - } else if ( typeof method === 'object' || ! method ) { - return methods.init.apply( this, arguments ); - } - $.error( 'Method ' + method + ' does not exist on jQuery.manyItemsTable' ); - return this; + clickAddRow: function ( table ) { + let tbody = table.lastElementChild; + + while ( tbody && tbody.tagName !== 'TBODY' ) { + tbody = tbody.previousElementSibling; + } + const row = tbody.querySelector( 'tr:first-child' ).cloneNode( true ); + + row.querySelectorAll( 'input, textarea' ).forEach( input => { + input.value = ''; + } ); + + tbody.appendChild( row ); + }, }; - $.fn.clickAddRow = function () { - var tbody = this.find( 'tbody:last' ), - row = tbody.find( 'tr:first' ).clone(); + const observeTableRemoval = function ( list ) { + const observer = new MutationObserver( mutations => { + mutations.forEach( mutation => { + mutation.removedNodes.forEach( node => { + if ( node.matches && node.matches( '.many-items-table' ) ) { + methods.destroy( node ); + } + } ); + } ); + } ); - $( row ).find( 'input, textarea' ).val( '' ); - $( row ).appendTo( tbody ); + observer.observe( list, { childList: true, subtree: true } ); }; -} )( jQuery ); - -jQuery( '.many-items-table' ).one( 'focus', ':input', function ( event ) { - jQuery( event.delegateTarget ).manyItemsTable(); -} ); -jQuery( '.many-items-table' ).on( 'click', 'a.nova-new-row', function ( event ) { - jQuery( event.delegateTarget ).clickAddRow(); -} ); + + // Initialization for many-items-table + document.addEventListener( 'focusin', event => { + const table = event.target.closest( '.many-items-table' ); + if ( table && ! initializedTables.has( table ) ) { + methods.init( table ); + } + } ); + + document.addEventListener( 'click', event => { + if ( event.target.matches( 'a.nova-new-row' ) ) { + const table = event.target.closest( '.many-items-table' ); + if ( table ) { + event.preventDefault(); + methods.clickAddRow( table ); + } + } + } ); + const list = document.querySelector( '#the-list' ); // Scope to the specific table + if ( list ) { + observeTableRemoval( list ); + } +} )(); diff --git a/projects/plugins/jetpack/modules/custom-post-types/js/menu-checkboxes.js b/projects/plugins/jetpack/modules/custom-post-types/js/menu-checkboxes.js index b422eff4e24eb..ed3418d76ccdf 100644 --- a/projects/plugins/jetpack/modules/custom-post-types/js/menu-checkboxes.js +++ b/projects/plugins/jetpack/modules/custom-post-types/js/menu-checkboxes.js @@ -1,48 +1,75 @@ -( function ( $ ) { - var NovaCheckBoxes = { +( function () { + const NovaCheckBoxes = { inputs: null, popInputs: null, initialize: function () { - NovaCheckBoxes.popInputs = $( '#nova_menuchecklist-pop' ).find( ':checkbox' ); + // Get all checkboxes in the "nova_menuchecklist-pop" + NovaCheckBoxes.popInputs = document.querySelectorAll( + '#nova_menuchecklist-pop input[type="checkbox"]' + ); - NovaCheckBoxes.inputs = $( '#nova_menuchecklist' ) - .find( ':checkbox' ) - .change( NovaCheckBoxes.checkOne ) - .change( NovaCheckBoxes.syncPop ); + // Get all checkboxes in the "nova_menuchecklist" and add event listeners + NovaCheckBoxes.inputs = document.querySelectorAll( + '#nova_menuchecklist input[type="checkbox"]' + ); + NovaCheckBoxes.inputs.forEach( input => { + input.addEventListener( 'change', NovaCheckBoxes.checkOne ); + input.addEventListener( 'change', NovaCheckBoxes.syncPop ); + } ); + // If no checkboxes are checked, check the first one if ( ! NovaCheckBoxes.isChecked() ) { NovaCheckBoxes.checkFirst(); } + // Sync the state of the "pop" inputs NovaCheckBoxes.syncPop(); }, syncPop: function () { - NovaCheckBoxes.popInputs.each( function () { - var $this = $( this ); - $this.prop( 'checked', $( '#in-nova_menu-' + $this.val() ).is( ':checked' ) ); + NovaCheckBoxes.popInputs.forEach( popInput => { + const linkedInput = document.querySelector( `#in-nova_menu-${ popInput.value }` ); + popInput.checked = linkedInput ? linkedInput.checked : false; } ); }, isChecked: function () { - return NovaCheckBoxes.inputs.is( ':checked' ); + return Array.from( NovaCheckBoxes.inputs ).some( input => input.checked ); }, checkFirst: function () { - NovaCheckBoxes.inputs.first().prop( 'checked', true ); + const firstInput = NovaCheckBoxes.inputs[ 0 ]; + if ( firstInput ) { + firstInput.checked = true; + } }, - checkOne: function ( /*event*/ ) { - if ( $( this ).is( ':checked' ) ) { - return NovaCheckBoxes.inputs.not( this ).prop( 'checked', false ); + checkOne: function () { + const currentInput = this; + + // If the current checkbox is checked, uncheck all other checkboxes + if ( currentInput.checked ) { + NovaCheckBoxes.inputs.forEach( input => { + if ( input !== currentInput ) { + input.checked = false; + } + } ); + return; } - if ( $( this ).closest( '#nova_menuchecklist' ).find( ':checked' ).length > 0 ) { - return $( this ).prop( 'checked', false ); + const checklist = document.querySelector( '#nova_menuchecklist' ); + + // If at least one checkbox is still checked, uncheck the current one + if ( checklist.querySelectorAll( 'input[type="checkbox"]:checked' ).length > 0 ) { + currentInput.checked = false; + return; } - return NovaCheckBoxes.checkFirst(); + + // Otherwise, check the first checkbox + NovaCheckBoxes.checkFirst(); }, }; - $( NovaCheckBoxes.initialize ); -} )( jQuery ); + // Initialize when the DOM is fully loaded + document.addEventListener( 'DOMContentLoaded', NovaCheckBoxes.initialize ); +} )(); diff --git a/projects/plugins/jetpack/modules/custom-post-types/nova.php b/projects/plugins/jetpack/modules/custom-post-types/nova.php index a5299f4c59fa3..69256670c29aa 100644 --- a/projects/plugins/jetpack/modules/custom-post-types/nova.php +++ b/projects/plugins/jetpack/modules/custom-post-types/nova.php @@ -584,7 +584,7 @@ public function add_admin_menus() { '_inc/build/custom-post-types/js/menu-checkboxes.min.js', 'modules/custom-post-types/js/menu-checkboxes.js' ), - array( 'jquery' ), + array(), $this->version, true ); @@ -1146,7 +1146,7 @@ public function enqueue_many_items_scripts() { '_inc/build/custom-post-types/js/many-items.min.js', 'modules/custom-post-types/js/many-items.js' ), - array( 'jquery' ), + array(), $this->version, true );