-
Notifications
You must be signed in to change notification settings - Fork 104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use AJAX for activating features / plugins in Performance Lab #1646
Changes from 7 commits
47f3938
9d97f59
6f172ad
337da6c
58c37d9
a59909d
f9faaea
770d462
e6710a3
c3d3588
fdf7e0b
b3ecc98
c713e47
1ebe323
c134fe2
369487f
d84e9bd
9bee27e
ce217ba
fa5d869
3a333ae
6250658
a8d89a8
c593ae6
e0691bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -49,9 +49,6 @@ function perflab_load_features_page(): void { | |||||
|
||||||
// Handle style for settings page. | ||||||
add_action( 'admin_head', 'perflab_print_features_page_style' ); | ||||||
|
||||||
// Handle script for settings page. | ||||||
add_action( 'admin_footer', 'perflab_print_plugin_progress_indicator_script' ); | ||||||
} | ||||||
|
||||||
/** | ||||||
|
@@ -230,6 +227,24 @@ function perflab_enqueue_features_page_scripts(): void { | |||||
|
||||||
// Enqueue the a11y script. | ||||||
wp_enqueue_script( 'wp-a11y' ); | ||||||
|
||||||
// Enqueue plugin activate AJAX script and localize script data. | ||||||
wp_enqueue_script( | ||||||
'perflab-plugin-activate-ajax', | ||||||
PERFLAB_PLUGIN_DIR_URL . 'includes/admin/plugin-activate-ajax.js', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
Suggested change
The reason is that this function allows for filtering as needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @westonruter I don't think we should randomly change it here though. If not using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've filed #1647 for this. |
||||||
array( 'jquery', 'wp-i18n', 'wp-a11y' ), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to @westonruter's previous feedback, it would be great not to use jQuery for the functionality, but rather vanilla JavaScript (e.g. |
||||||
(string) filemtime( PERFLAB_PLUGIN_DIR_PATH . 'includes/admin/plugin-activate-ajax.js' ), | ||||||
true | ||||||
); | ||||||
|
||||||
wp_localize_script( | ||||||
'perflab-plugin-activate-ajax', | ||||||
'perflabPluginActivateAjaxData', | ||||||
array( | ||||||
'ajaxUrl' => admin_url( 'admin-ajax.php' ), | ||||||
'nonce' => wp_create_nonce( 'perflab_install_activate_plugin_ajax' ), | ||||||
) | ||||||
); | ||||||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
/** | ||||||
|
@@ -290,6 +305,47 @@ function perflab_install_activate_plugin_callback(): void { | |||||
} | ||||||
add_action( 'admin_action_perflab_install_activate_plugin', 'perflab_install_activate_plugin_callback' ); | ||||||
|
||||||
/** | ||||||
* Callback for handling installation/activation of plugin using ajax. | ||||||
* | ||||||
* @since n.e.x.t | ||||||
*/ | ||||||
function perflab_install_activate_plugin_ajax_callback(): void { | ||||||
check_ajax_referer( 'perflab_install_activate_plugin_ajax', '_ajax_nonce' ); | ||||||
|
||||||
require_once ABSPATH . 'wp-admin/includes/plugin.php'; | ||||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; | ||||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; | ||||||
require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; | ||||||
|
||||||
if ( ! isset( $_POST['slug'] ) ) { | ||||||
wp_send_json_error( __( 'Missing required parameter.', 'performance-lab' ), 400 ); | ||||||
} | ||||||
|
||||||
$plugin_slug = perflab_sanitize_plugin_slug( wp_unslash( $_POST['slug'] ) ); | ||||||
if ( null === $plugin_slug ) { | ||||||
wp_send_json_error( __( 'Invalid plugin.', 'performance-lab' ), 400 ); | ||||||
} | ||||||
|
||||||
// Install and activate the plugin and its dependencies. | ||||||
$result = perflab_install_and_activate_plugin( $plugin_slug ); | ||||||
if ( $result instanceof WP_Error ) { | ||||||
wp_send_json_error( $result->get_error_message() ); | ||||||
} | ||||||
|
||||||
$plugin_settings_url = perflab_get_plugin_settings_url( $plugin_slug ); | ||||||
if ( null !== $plugin_settings_url ) { | ||||||
wp_send_json_success( | ||||||
array( | ||||||
'pluginSettingsURL' => esc_url( $plugin_settings_url ), | ||||||
b1ink0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
) | ||||||
); | ||||||
} | ||||||
|
||||||
wp_send_json_success(); | ||||||
} | ||||||
add_action( 'wp_ajax_perflab_install_activate_plugin', 'perflab_install_activate_plugin_ajax_callback' ); | ||||||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* Callback function to handle admin inline style. | ||||||
* | ||||||
|
@@ -396,42 +452,6 @@ static function ( $name ) { | |||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* Callback function that print plugin progress indicator script. | ||||||
* | ||||||
* @since 3.1.0 | ||||||
*/ | ||||||
function perflab_print_plugin_progress_indicator_script(): void { | ||||||
$js_function = <<<JS | ||||||
function addPluginProgressIndicator( message ) { | ||||||
document.addEventListener( 'DOMContentLoaded', function () { | ||||||
document.addEventListener( 'click', function ( event ) { | ||||||
if ( | ||||||
event.target.classList.contains( | ||||||
'perflab-install-active-plugin' | ||||||
) | ||||||
) { | ||||||
const target = event.target; | ||||||
target.classList.add( 'updating-message' ); | ||||||
target.textContent = message; | ||||||
|
||||||
wp.a11y.speak( message ); | ||||||
} | ||||||
} ); | ||||||
} ); | ||||||
} | ||||||
JS; | ||||||
|
||||||
wp_print_inline_script_tag( | ||||||
sprintf( | ||||||
'( %s )( %s );', | ||||||
$js_function, | ||||||
wp_json_encode( __( 'Activating...', 'default' ) ) | ||||||
), | ||||||
array( 'type' => 'module' ) | ||||||
); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Gets the URL to the plugin settings screen if one exists. | ||||||
* | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
/** | ||
* Handles activation of Performance Features (Plugins) using AJAX. | ||
*/ | ||
|
||
/* global perflabPluginActivateAjaxData */ | ||
( function ( $ ) { | ||
// @ts-ignore | ||
const { i18n, a11y } = wp; | ||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const { __, _x } = i18n; | ||
|
||
/** | ||
* Adds a click event listener to the plugin activate buttons. | ||
* | ||
* @param {MouseEvent} event - The click event object that is triggered when the user clicks on the document. | ||
* | ||
* @return {Promise<void>} - The asynchronous function returns a promise. | ||
*/ | ||
b1ink0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$( document ).on( | ||
'click', | ||
'.perflab-install-active-plugin', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if someone clicks this button while another request is currently in flight? Should it short-circuit so as to avoid duplicating requests? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think as the request could have been half finished we should not short-circuit the request. I think it will be better to just disable it until the request is finished. But as the click event is on the anchor tag we will have to add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As the click event is on the anchor tag and not button we will have to add some other work around for disabling on keyboard one is setting its There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tested this but if user is already focused then There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could add the |
||
async function ( event ) { | ||
// Prevent the default link behavior. | ||
event.preventDefault(); | ||
|
||
// Get the clicked element as a jQuery object. | ||
const target = $( this ); | ||
|
||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
target | ||
.addClass( 'updating-message' ) | ||
.text( __( 'Activating…', 'performance-lab' ) ); | ||
|
||
a11y.speak( __( 'Activating…', 'performance-lab' ) ); | ||
|
||
// Retrieve the plugin slug from the data attribute. | ||
const pluginSlug = $.trim( target.attr( 'data-plugin-slug' ) ); | ||
b1ink0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Send an AJAX POST request to activate the plugin. | ||
$.ajax( { | ||
// @ts-ignore | ||
url: perflabPluginActivateAjaxData.ajaxUrl, | ||
method: 'POST', | ||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
dataType: 'json', | ||
data: { | ||
action: 'perflab_install_activate_plugin', | ||
slug: pluginSlug, | ||
// @ts-ignore | ||
_ajax_nonce: perflabPluginActivateAjaxData.nonce, | ||
}, | ||
success( responseData ) { | ||
if ( ! responseData.success ) { | ||
showAdminNotice( | ||
__( | ||
'There was an error activating the plugin. Please try again.', | ||
'performance-lab' | ||
) | ||
); | ||
|
||
target | ||
.removeClass( 'updating-message' ) | ||
.text( __( 'Activate', 'performance-lab' ) ); | ||
|
||
return; | ||
} | ||
|
||
// Replace the 'Activate' button with a disabled 'Active' button. | ||
target.replaceWith( | ||
$( '<button>', { | ||
type: 'button', | ||
class: 'button button-disabled', | ||
disabled: true, | ||
text: __( 'Active', 'performance-lab' ), | ||
} ) | ||
); | ||
|
||
const pluginSettingsURL = | ||
responseData?.data?.pluginSettingsURL; | ||
|
||
// Select the container for action buttons related to the plugin. | ||
const actionButtonList = $( | ||
`.plugin-card-${ pluginSlug } .plugin-action-buttons` | ||
); | ||
|
||
if ( pluginSettingsURL && actionButtonList ) { | ||
// Append a 'Settings' link to the action buttons. | ||
actionButtonList.append( | ||
$( '<li>' ).append( | ||
$( '<a>', { | ||
href: pluginSettingsURL, | ||
text: __( 'Settings', 'performance-lab' ), | ||
} ) | ||
) | ||
); | ||
} | ||
|
||
showAdminNotice( | ||
__( 'Feature activated.', 'performance-lab' ), | ||
'success', | ||
pluginSettingsURL | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the demo video you showed that this notice is causing layout shifts, resulting in you accidentally clicking the title of a plugin instead of the Activate button. I don't think an admin notice is even needed here, because the button transitions from Activate to Activating... to Active. |
||
}, | ||
error() { | ||
showAdminNotice( | ||
__( | ||
'There was an error activating the plugin. Please try again.', | ||
'performance-lab' | ||
) | ||
); | ||
|
||
target | ||
.removeClass( 'updating-message' ) | ||
.text( __( 'Activate', 'performance-lab' ) ); | ||
}, | ||
} ); | ||
} | ||
); | ||
|
||
/** | ||
* Displays an admin notice with the given message and type. | ||
* | ||
* @param {string} message - The message to display in the notice. | ||
* @param {string} [type='error'] - The type of notice ('error', 'success', etc.). | ||
* @param {string} [pluginSettingsURL] - Optional URL for the plugin settings. | ||
*/ | ||
function showAdminNotice( | ||
message, | ||
type = 'error', | ||
pluginSettingsURL = undefined | ||
) { | ||
a11y.speak( message ); | ||
|
||
// Create the notice container elements. | ||
const notice = $( '<div>', { | ||
class: `notice is-dismissible notice-${ type }`, | ||
} ); | ||
|
||
const messageWrap = $( '<p>' ).text( message ); | ||
|
||
// If a plugin settings URL is provided, append a 'Review settings.' link. | ||
if ( pluginSettingsURL ) { | ||
messageWrap | ||
.append( ` ${ __( 'Review', 'performance-lab' ) } ` ) | ||
.append( | ||
$( '<a>', { | ||
href: pluginSettingsURL, | ||
text: __( 'settings', 'performance-lab' ), | ||
} ) | ||
) | ||
.append( _x( '.', 'Punctuation mark', 'performance-lab' ) ); | ||
} | ||
|
||
const dismissButton = $( '<button>', { | ||
type: 'button', | ||
class: 'notice-dismiss', | ||
click: () => notice.remove(), | ||
} ).append( | ||
$( '<span>', { | ||
class: 'screen-reader-text', | ||
text: __( 'Dismiss this notice.', 'performance-lab' ), | ||
} ) | ||
); | ||
|
||
notice.append( messageWrap, dismissButton ); | ||
|
||
const noticeContainer = $( '.wrap.plugin-install-php' ); | ||
|
||
if ( noticeContainer.length ) { | ||
// If the container exists, insert the notice after the first child. | ||
noticeContainer.children().eq( 0 ).after( notice ); | ||
} else { | ||
$( 'body' ).prepend( notice ); | ||
} | ||
} | ||
|
||
westonruter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// @ts-ignore | ||
} )( window.jQuery ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it make sense to make jQuery a dependency? Would plain JS not be feasible? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1, it would be great to use vanilla JavaScript rather than relying on jQuery, to set a good example for performance. |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -22,6 +22,7 @@ | |||
define( 'PERFLAB_VERSION', '3.5.1' ); | ||||
define( 'PERFLAB_MAIN_FILE', __FILE__ ); | ||||
define( 'PERFLAB_PLUGIN_DIR_PATH', plugin_dir_path( PERFLAB_MAIN_FILE ) ); | ||||
define( 'PERFLAB_PLUGIN_DIR_URL', plugin_dir_url( PERFLAB_MAIN_FILE ) ); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If my suggestion above is accepted, this can then be removed.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above, let's discuss separately, it's not really relevant for this enhancement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least let's avoid introducing a new constant in this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fair, we can simply use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've filed #1647 for the filtering. |
||||
define( 'PERFLAB_SCREEN', 'performance-lab' ); | ||||
|
||||
// If the constant isn't defined yet, it means the Performance Lab object cache file is not loaded. | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need these enqueue now? as it already added through new script dependancy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could remove it as it is not being used anywhere other than the AJAX script as it already added in its dependancy. Should I remove it then?