| 1 |
/** |
| 2 |
* JavaScript code for AJAX requests with Notices functionality on admin screens. |
| 3 |
* |
| 4 |
* @package TablePress |
| 5 |
* @subpackage Views JavaScript |
| 6 |
* @author Tobias Bäthge |
| 7 |
* @since 3.1.0 |
| 8 |
*/ |
| 9 |
|
| 10 |
/** |
| 11 |
* WordPress dependencies. |
| 12 |
*/ |
| 13 |
import { |
| 14 |
Icon, |
| 15 |
} from '@wordpress/components'; |
| 16 |
import { RawHTML } from '@wordpress/element'; |
| 17 |
import { __, sprintf } from '@wordpress/i18n'; |
| 18 |
import { buildQueryString } from '@wordpress/url'; |
| 19 |
|
| 20 |
/** |
| 21 |
* Internal dependencies. |
| 22 |
*/ |
| 23 |
import { TablePressIconSimple } from '../../img/tablepress-icon'; |
| 24 |
|
| 25 |
/** |
| 26 |
* Default callback for handling specifics of a successful request. |
| 27 |
* |
| 28 |
* @param {Object} data |
| 29 |
*/ |
| 30 |
const onSuccessfulRequestDefault = ( data ) => { |
| 31 |
const actionMessages = { |
| 32 |
success_save: __( 'The changes were saved successfully.', 'tablepress' ), |
| 33 |
}; |
| 34 |
|
| 35 |
const notice = { |
| 36 |
status: ( data.message.includes( 'error' ) ) ? 'error' : 'success', |
| 37 |
content: actionMessages[ data.message ], |
| 38 |
type: ( data.message.includes( 'error' ) ) ? 'notice' : 'snackbar', |
| 39 |
}; |
| 40 |
|
| 41 |
return { notice }; |
| 42 |
}; |
| 43 |
|
| 44 |
/** |
| 45 |
* Processes an AJAX request with Notices functionality. |
| 46 |
* |
| 47 |
* @param {Object} props Function parameters. |
| 48 |
* @param {Object} props.requestData Request data. |
| 49 |
* @param {Function} props.onSuccessfulRequest Callback for handling a successful save. |
| 50 |
* @param {Function} props.setBusyState Callback for setting the busy state. |
| 51 |
* @param {Function} props.noticeOperations Callbacks for working with notices. |
| 52 |
* @param {Function} props.noticesStoreDispatch Dispatch function for notices store. (Optional, only for "success" Snackbar notices.) |
| 53 |
*/ |
| 54 |
const processAjaxRequest = ( { requestData, onSuccessfulRequest = onSuccessfulRequestDefault, setBusyState, noticeOperations, noticesStoreDispatch } ) => { |
| 55 |
/** |
| 56 |
* Shows a notice to the user. |
| 57 |
* |
| 58 |
* @param {Object} notice Notice data. |
| 59 |
* @param {string} notice.status Status of the notice (error, success, warning, info). |
| 60 |
* @param {string} notice.content Content of the notice. |
| 61 |
*/ |
| 62 |
const showNotice = ( { status, content } ) => { |
| 63 |
const id = `notice-${ Date.now() }`; |
| 64 |
content = <> |
| 65 |
{ /* Notices don't have a DOM ID, so add a custom span which has one, for the fade-out and removal. */ } |
| 66 |
<span id={ id }></span> |
| 67 |
<RawHTML>{ content }</RawHTML> |
| 68 |
</>; |
| 69 |
noticeOperations.createNotice( { id, status, content, isDismissible: ( 'error' === status ) } ); |
| 70 |
|
| 71 |
if ( 'error' !== status ) { |
| 72 |
// Fade out non-error notices after 5 seconds and then remove them. |
| 73 |
setTimeout( () => { |
| 74 |
const notice = document.getElementById( id ).closest( '.components-notice' ); |
| 75 |
notice.addEventListener( 'transitionend', () => noticeOperations.removeNotice( id ) ); |
| 76 |
notice.style.opacity = 0; |
| 77 |
}, 5000 ); |
| 78 |
} else { |
| 79 |
// Scroll error notices into view. |
| 80 |
setTimeout( () => { |
| 81 |
const notice = document.getElementById( id ).closest( '.components-notice' ); |
| 82 |
if ( notice.getBoundingClientRect().bottom > ( window.innerHeight || document.documentElement.clientHeight ) ) { |
| 83 |
notice.scrollIntoView( { behavior: 'smooth', block: 'end', inline: 'nearest' } ); |
| 84 |
} |
| 85 |
}, 1 ); |
| 86 |
} |
| 87 |
}; |
| 88 |
|
| 89 |
// Put the screen into "is busy" mode. |
| 90 |
setBusyState( true ); |
| 91 |
document.body.classList.add( 'wait' ); |
| 92 |
|
| 93 |
// Save the table data to the server via an AJAX request. |
| 94 |
fetch( ajaxurl, { |
| 95 |
method: 'POST', |
| 96 |
headers: { |
| 97 |
'Content-Type': 'application/x-www-form-urlencoded', |
| 98 |
Accept: 'application/json', |
| 99 |
}, |
| 100 |
body: buildQueryString( requestData ), |
| 101 |
} ) |
| 102 |
// Check for HTTP connection problems. |
| 103 |
.then( ( response ) => { |
| 104 |
if ( ! response.ok ) { |
| 105 |
throw new Error( sprintf( __( 'There was a problem with the server, HTTP response code %1$d (%2$s).', 'tablepress' ), response.status, response.statusText ) ); |
| 106 |
} |
| 107 |
return response.json(); |
| 108 |
} ) |
| 109 |
// Check for problems with the transmitted data. |
| 110 |
.then( ( data ) => { |
| 111 |
if ( 'undefined' === typeof data || null === data || '-1' === data || 'undefined' === typeof data.success ) { |
| 112 |
throw new Error( __( 'The JSON data returned from the server is unclear or incomplete.', 'tablepress' ) ); |
| 113 |
} |
| 114 |
|
| 115 |
if ( true !== data.success ) { |
| 116 |
const debugHtml = data.error_details ? `<p>${ __( 'These errors were encountered:', 'tablepress' ) }</p><pre>${ data.error_details }</pre>` : ''; |
| 117 |
throw new Error( `<p>${ __( 'There was a problem with the request.', 'tablepress' ) }</p>${ debugHtml }` ); |
| 118 |
} |
| 119 |
|
| 120 |
handleRequestSuccess( data ); |
| 121 |
} ) |
| 122 |
// Handle errors. |
| 123 |
.catch( ( error ) => { |
| 124 |
handleRequestError( error.message ); |
| 125 |
} ) |
| 126 |
// Clean up. |
| 127 |
.finally( () => { |
| 128 |
// Reset the screen from "is busy" mode. |
| 129 |
setBusyState( false ); |
| 130 |
document.body.classList.remove( 'wait' ); |
| 131 |
} ); |
| 132 |
|
| 133 |
/** |
| 134 |
* Handles a successful AJAX request. |
| 135 |
* |
| 136 |
* @param {Object} data Request response data. |
| 137 |
*/ |
| 138 |
const handleRequestSuccess = ( data ) => { |
| 139 |
const { notice } = onSuccessfulRequest( data ); |
| 140 |
if ( notice ) { |
| 141 |
if ( 'snackbar' === notice.type && 'undefined' !== typeof noticesStoreDispatch ) { |
| 142 |
// Dispatch a Snackbar notice. |
| 143 |
noticesStoreDispatch.createSuccessNotice( |
| 144 |
notice.content, |
| 145 |
{ |
| 146 |
type: 'snackbar', |
| 147 |
icon: <Icon icon={ TablePressIconSimple } />, |
| 148 |
} |
| 149 |
); |
| 150 |
} else { |
| 151 |
// Show a normal notice. |
| 152 |
showNotice( notice ); |
| 153 |
} |
| 154 |
} |
| 155 |
}; |
| 156 |
|
| 157 |
/** |
| 158 |
* Handles an error during the AJAX request. |
| 159 |
* |
| 160 |
* @param {string} message Error message. |
| 161 |
*/ |
| 162 |
const handleRequestError = ( message ) => { |
| 163 |
message = __( 'Attention: Unfortunately, an error occurred.', 'tablepress' ) + ' ' + message + '<br>' + sprintf( __( 'Please see the <a href="%s" target="_blank">TablePress FAQ page</a> for suggestions.', 'tablepress' ), 'https://tablepress.org/faq/common-errors/' ); |
| 164 |
const notice = { |
| 165 |
status: 'error', |
| 166 |
content: message, |
| 167 |
}; |
| 168 |
showNotice( notice ); |
| 169 |
}; |
| 170 |
}; |
| 171 |
|
| 172 |
export default processAjaxRequest; |
| 173 |
|