Cómo añadir un campo personalizado a la edición rápida
¿Cómo puedo añadir una nueva columna a la edición rápida e incluir un campo personalizado en ella? He leído esta pregunta pero uno de los enlaces lleva a un error 404. Necesito una nueva columna para mi metaclave personalizada "summary
" y para los tipos de contenido "post
" y "episode
".
Hay algunos pasos para crear la caja de edición rápida personalizada y la columna personalizada
- crear una meta clave personalizada (se asume que ya tienes una)
- agregar título y datos de columna personalizados en el administrador (se asume que quieres mostrar la meta clave personalizada en la columna, si no, también puedes modificar un poco la lógica para lograr el mismo efecto porque el principio es el mismo)
- agregar caja de edición rápida personalizada
- agregar lógica de guardado
- cargar script para modificar la función original inline-edit-post para soportar valores meta personalizados
- preparar el archivo de script
Este ejemplo va a agregar una caja de entrada de edición rápida en el tipo de post Page
. El siguiente código está probado y funciona al colocarlo en functions.php.
1. crear una meta clave personalizada (se asume que ya tienes una)
He preparado una meta clave personalizada remark
para el tipo de post page
2. agregar título y datos de columna personalizados en el administrador
para tipos de post personalizados, puedes usar
Agregar título de columna
- Para ID de Pantalla específico manage_{$screen->id}_columns
- Específicamente para Page manage_pages_columns
- Específicamente para genérico (Post por defecto o tipo de post personalizado) manage_posts_custom_column o para tipo de post específico manage_{$post->post_type}_posts_custom_column para agregar datos de columna
Agregar contenido de columna
- Genérico (Post por defecto o tipo de post personalizado) manage_posts_columns con verificación de tipo de post usando argumento de tipo de post o para tipo de post específico manage_{$post_type}_posts_columns para agregar título de columna
El siguiente es un ejemplo para el tipo de post Page así como tipo de post personalizado + Post
por defecto como ejemplo para configuración de múltiples tipos de post.
// agregar título de columna personalizada para valor meta personalizado
add_filter('manage_edit-page_columns', 'ws365150_add_custom_columns_title' );
function ws365150_add_custom_columns_title( $columns ) {
$columns['page_remark'] = 'Nota'; // puedes usar __() después para soporte de traducción
return $columns;
}
// agregar datos de columna personalizada con valor meta personalizado
add_action('manage_pages_custom_column', 'ws365150_add_custom_column_data', 10, 2 );
function ws365150_add_custom_column_data( $column_name, $post_id ) {
switch ( $column_name ) {
case 'page_remark':
echo get_post_meta( $post_id, 'remark', true );
break;
default:
break;
}
}
El siguiente es un ejemplo para tipo de post personalizado episode
+ Post
por defecto con meta clave summary
// agregar título de columna personalizada para valor meta personalizado
// 'manage_pages_columns' o 'manage_edit-post_columns' ambos funcionan
add_filter('manage_posts_columns', 'ws365150_add_custom_columns_title_pt', 10, 2 );
function ws365150_add_custom_columns_title_pt( $columns, $post_type ) {
switch ( $post_type ) {
case 'post':
case 'episode':
$columns['ws365150_summary'] = 'Resumen'; // puedes usar __() después para soporte de traducción
break;
default:
break;
}
return $columns;
}
// agregar datos de columna personalizada con valor meta personalizado para tipos de post personalizados
add_action('manage_posts_custom_column', 'ws365150_add_custom_column_data_pt', 10, 2 );
function ws365150_add_custom_column_data_pt( $column_name, $post_id ) {
switch ( $column_name ) {
case 'ws365150_summary': // especificado para esta columna asignada en el título de columna
echo get_post_meta( $post_id, 'summary', true );
break;
default:
break;
}
}
3. agregar caja de edición rápida personalizada
// para page
add_action( 'quick_edit_custom_box', 'ws365150_custom_edit_box', 10, 3 );
function ws365150_custom_edit_box( $column_name, $post_type, $taxonomy ) {
global $post;
switch ( $post_type ) {
case 'page':
if( $column_name === 'page_remark' ): // mismo título de columna definido en paso anterior
?>
<?php // echo get_post_meta( $post->ID, 'remark', true ); ?>
<fieldset class="inline-edit-col-right" id="#edit-">
<div class="inline-edit-col">
<label>
<span class="title">Nota</span>
<span class="input-text-wrap"><input type="text" name="remark" class="inline-edit-menu-order-input" value=""></span>
</label>
</div>
</fieldset>
<?php
endif;
// echo 'campo de página personalizado';
break;
default:
break;
}
}
// para Post + tipo de post personalizado
add_action( 'quick_edit_custom_box', 'ws365150_custom_edit_box_pt', 10, 3 );
function ws365150_custom_edit_box_pt( $column_name, $post_type, $taxonomy ) {
global $post;
switch ( $post_type ) {
case 'post':
case 'episode':
if( $column_name === 'ws365150_summary' ): // mismo título de columna definido en paso anterior
?>
<?php // echo get_post_meta( $post->ID, 'remark', true ); ?>
<fieldset class="inline-edit-col-right" id="#edit-">
<div class="inline-edit-col">
<label>
<span class="title">Resumen</span>
<span class="input-text-wrap"><input type="text" name="summary" class="inline-edit-menu-order-input" value=""></span>
</label>
</div>
</fieldset>
<?php
endif;
// echo 'campo de página personalizado';
break;
default:
break;
}
}
4. agregar lógica de guardado
add_action( 'save_post', 'ws365150_update_custom_quickedit_box' );
function ws365150_update_custom_quickedit_box() {
// cualquier lógica de verificación aquí, omitir y mantener simple para propósito de ilustración simple (nonce, existencia de $_POST['remark'], guardado ajax y así
// nota en Page
if( isset( $_POST ) && isset( $_POST['remark'] ) ) { // donde remark está definido en <input name="remark">
update_post_meta($_POST['post_ID'], 'remark', $_POST['remark']);
}
// resumen en Post, tipo de post personalizado
if( isset( $_POST ) && isset( $_POST['summary'] ) ) { // donde summary está definido en <input name="summary">
update_post_meta($_POST['post_ID'], 'summary', $_POST['summary']);
}
// para propósito de depuración en inspector, no es necesario, habilitar esto romperá el guardado pero permitirá ver el retorno ajax
// wp_send_json_success( array(
// 'message' => '¡Prueba de guardado!',
// 'post_data' => $_POST,
// ) );
return; // finalizar la llamada de función
}
5. cargar script para modificar la función original inline-edit-post
add_action( 'admin_enqueue_scripts', function( $page ) {
// agregar lógica de verificación de página, esta es simple, puedes probar tipo de post y así...
if ( 'edit.php' != $page ) {
return;
}
wp_enqueue_script( 'custom-quickedit-box', get_stylesheet_directory_uri() . '/ws365150_custom_quickedit_box.js', array( 'jquery', 'inline-edit-post' ) );
});
6. preparar el archivo de script (ws365150_custom_quickedit_box.js, al probar el código anterior, colocar en la carpeta del tema)
( function( $, wp ) {
// clonar de la función original en inline-post-edit.js para sobrescribir
// en realidad no es necesario crear un objeto alias, sin embargo, crear un alias podría ser una nota y marca de sobrescritura sin olvidar para mantenimiento posterior
window.customInlineEditPost = window.inlineEditPost;
// sobrescribir función: agregar valor meta personalizado, la base está copiada del fuente
customInlineEditPost.edit = function(id) {
// console.log( 'edición personalizada' );
var t = this, fields, editRow, rowData, status, pageOpt, pageLevel, nextPage, pageLoop = true, nextLevel, f, val, pw;
t.revert();
if ( typeof(id) === 'object' ) {
id = t.getId(id);
}
fields = ['post_title', 'post_name', 'post_author', '_status', 'jj', 'mm', 'aa', 'hh', 'mn', 'ss', 'post_password', 'post_format', 'menu_order', 'page_template'];
if ( t.type === 'page' ) {
fields.push('post_parent');
}
// Agregar la nueva fila de edición con una fila en blanco extra debajo para mantener el rayado cebra.
editRow = $('#inline-edit').clone(true);
$( 'td', editRow ).attr( 'colspan', $( 'th:visible, td:visible', '.widefat:first thead' ).length );
$(t.what+id).removeClass('is-expanded').hide().after(editRow).after('<tr class="hidden"></tr>');
// Poblar campos en la ventana de edición rápida.
rowData = $('#inline_'+id);
if ( !$(':input[name="post_author"] option[value="' + $('.post_author', rowData).text() + '"]', editRow).val() ) {
// El autor del post ya no tiene capacidades de edición, así que necesitamos agregarlos a la lista de autores.
$(':input[name="post_author"]', editRow).prepend('<option value="' + $('.post_author', rowData).text() + '">' + $('#' + t.type + '-' + id + ' .author').text() + '</option>');
}
if ( $( ':input[name="post_author"] option', editRow ).length === 1 ) {
$('label.inline-edit-author', editRow).hide();
}
// poblar valor meta personalizado
if ( $( ':input[name="remark"]', editRow ).length === 1 ) {
$( ':input[name="remark"]', editRow ).val( $('#post-' + id + ' .page_remark').text() );
}
if ( $( ':input[name="summary"]', editRow ).length === 1 ) {
$( ':input[name="summary"]', editRow ).val( $('#post-' + id + ' .ws365150_summary').text() );
}
for ( f = 0; f < fields.length; f++ ) {
val = $('.'+fields[f], rowData);
/**
* Reemplaza la imagen para un Twemoji(emoji de Twitter) con su texto alternativo.
*
* @returns Texto alternativo de la imagen.
*/
val.find( 'img' ).replaceWith( function() { return this.alt; } );
val = val.text();
$(':input[name="' + fields[f] + '"]', editRow).val( val );
}
if ( $( '.comment_status', rowData ).text() === 'open' ) {
$( 'input[name="comment_status"]', editRow ).prop( 'checked', true );
}
if ( $( '.ping_status', rowData ).text() === 'open' ) {
$( 'input[name="ping_status"]', editRow ).prop( 'checked', true );
}
if ( $( '.sticky', rowData ).text() === 'sticky' ) {
$( 'input[name="sticky"]', editRow ).prop( 'checked', true );
}
/**
* Crea las cajas de selección para las categorías.
*/
$('.post_category', rowData).each(function(){
var taxname,
term_ids = $(this).text();
if ( term_ids ) {
taxname = $(this).attr('id').replace('_'+id, '');
$('ul.'+taxname+'-checklist :checkbox', editRow).val(term_ids.split(','));
}
});
/**
* Obtiene todas las taxonomías para sugerencias de auto-completado en vivo al escribir el nombre
* de una etiqueta.
*/
$('.tags_input', rowData).each(function(){
var terms = $(this),
taxname = $(this).attr('id').replace('_' + id, ''),
textarea = $('textarea.tax_input_' + taxname, editRow),
comma = inlineEditL10n.comma;
terms.find( 'img' ).replaceWith( function() { return this.alt; } );
terms = terms.text();
if ( terms ) {
if ( ',' !== comma ) {
terms = terms.replace(/,/g, comma);
}
textarea.val(terms);
}
textarea.wpTagsSuggest();
});
// Manejar el estado del post.
status = $('._status', rowData).text();
if ( 'future' !== status ) {
$('select[name="_status"] option[value="future"]', editRow).remove();
}
pw = $( '.inline-edit-password-input' ).prop( 'disabled', false );
if ( 'private' === status ) {
$('input[name="keep_private"]', editRow).prop('checked', true);
pw.val( '' ).prop( 'disabled', true );
}
// Remover la página actual e hijos del menú desplegable de padre.
pageOpt = $('select[name="post_parent"] option[value="' + id + '"]', editRow);
if ( pageOpt.length > 0 ) {
pageLevel = pageOpt[0].className.split('-')[1];
nextPage = pageOpt;
while ( pageLoop ) {
nextPage = nextPage.next('option');
if ( nextPage.length === 0 ) {
break;
}
nextLevel = nextPage[0].className.split('-')[1];
if ( nextLevel <= pageLevel ) {
pageLoop = false;
} else {
nextPage.remove();
nextPage = pageOpt;
}
}
pageOpt.remove();
}
$(editRow).attr('id', 'edit-'+id).addClass('inline-editor').show();
$('.ptitle', editRow).focus();
return false;
};
})( jQuery, window.wp );
Lo anterior es para edición rápida usando el hook quick_edit_custom_box
, y también podrías necesitar manejar la edición masiva. Puedes explorar más leyendo y usando bulk_edit_custom_box
.

¡Genial! ¿Podrías editar el ejemplo para los tipos de post post
y episode
y la meta clave personalizada summary
? Lo siento, no me gusta jugar con el archivo functions.php, podría accidentalmente dañar WordPress. Necesito saber que hay un 100% de probabilidad de que el código funcione.

He agregado código de ejemplo adicional para post
, episode
con el campo meta summary
con notas adicionales sobre el título de la columna y un ejemplo de verificación al guardar. Puedes modificarlo y agregar verificaciones adicionales según el contenido que necesites.

Espera un segundo, este código no funciona y solo está dejando mi página de edición "Todas las páginas" en blanco.

El código ha sido actualizado, por favor cambia add_filter('manage_posts_columns', 'ws365150_add_custom_columns_title_pt' );
por add_filter('manage_posts_columns', 'ws365150_add_custom_columns_title_pt', 10, 2 );
Por favor, no te preocupes por marcar la respuesta como correcta antes de probarla.

Hmm, al probar el código anterior, el nuevo campo de resumen se añade al menú de edición rápida de los tipos de contenido post
y episode
,
pero después de hacer clic en "actualizar", el parámetro summary
aparece vacío.
Incluso si el parámetro summary
ya tenía un valor (sin usar la edición rápida), el campo de edición rápida sigue dejando el parámetro vacío.

He creado un caso de prueba para simular tu situación y he realizado una prueba. He actualizado el javascript y el filtro save_post. Por favor, asegúrate también de que el javascript se carga correctamente. Porque la ruta asume que el javascript está en la carpeta del tema. Debería funcionar correctamente ahora según la simulación.

Buena respuesta, aunque... en general es horrible que tenga que ser tan complicado

@Toskan, podría simplificarse. Es solo que esta respuesta está especificada para el que hizo la pregunta con la meta key de las preguntas. Personalmente prefiero respuestas genéricas en la mayoría de los casos ya que diferentes personas tienen diferentes necesidades. Si eliminamos los ejemplos, hay pocos pasos. En resumen, hay 3 partes: (1)crear una meta key personalizada (puede hacerse manualmente o usando plugins), (2)añadir columnas personalizadas para mostrar y (3)crear la lógica de edición/guardado para la edición rápida. Como la edición rápida se basa en Javascript ajax para proporcionar una UX "instantánea", esta parte parece un poco exagerada.

ah sí, no, no usé tu parte de javascript. Hay una solución mucho más simple para la parte js en algún lugar...

mira este: https://pastebin.com/FDQPsau4

@Toskan Gracias por compartir. Pero un punto a tener en cuenta, el javascript que uso aquí está copiado/basado en el javascript original de WordPress Core con verificaciones como tipo y demás. Sin las verificaciones adecuadas, aunque podría ser más simple y funcionar, podría resultar en errores en algunos casos.

no reescribí todo el js en el paso 6 solo agregué
(function($){
lesson_orders={};
if($('.post_type_page').length && $('.post_type_page').val()=='resource'){
$('#the-list tr').each(function(){
id=$(this).find('.check-column input').val();
val=$(this).find('.lesson_order').text();
lesson_orders['edit-'+id]=val;
});
}
$(document).on('focus','.ptitle',function(){
id =$(this).closest('.inline-edit-row').attr('id');
$('#'+id+' input[name="lesson_order"]').val(lesson_orders[''+id]);
});
})(jQuery);

Esto es lo que he hecho en 2024 para agregar el campo meta summary
a los tipos de entrada post
y episode
como columnas y editable mediante edición rápida.
Agregar una columna en el administrador
Primero, define las columnas con el filtro manage_posts_columns.
/**
* Agregar Columna de Resumen a Posts y Episodios
*
* @param array $columns Un arreglo asociativo de encabezados de columnas.
* @param string $post_type El slug del tipo de entrada.
*
* @return array Un arreglo posiblemente modificado de columnas.
*/
function prefix_add_summary_column($columns, $post_type)
{
if (in_array($post_type, array('post','episode')) {
return array_merge($columns, array(
'summary' => __('Resumen')
));
}
return $columns;
}
add_filter('manage_posts_columns', 'prefix_add_summary_column', 10, 2);
Alternativamente, puedes usar add_filter('manage_post_posts_columns', 'prefix_add_summary_column');
y add_filter('manage_episode_posts_columns', 'prefix_add_summary_column');
para dirigirte a tipos de entrada específicos y eliminar la declaración if que verifica el tipo de entrada dentro de la función.
Luego, define lo que se muestra en el contenido para tu columna summary
usando la acción manage_posts_custom_column. Para este ejemplo, simplemente muestra el valor del campo meta summary
.
/**
* Mostrar Contenido de la Columna Resumen en Posts y Episodios
*
* @param string $column_name El nombre de la columna a mostrar.
* @param int $post_id El ID de la entrada actual que se está mostrando.
*
* @return void
*/
function prefix_display_summary_column($column_name, $post_id)
{
if ($column_name === 'summary') {
echo esc_html(get_post_meta($post_id,'summary',true));
}
}
add_action('manage_posts_custom_column', 'prefix_display_summary_column', 10, 2);
Nuevamente, puedes usar add_action('manage_post_posts_custom_column', 'prefix_display_summary_column', 10, 2);
y add_action('manage_episode_posts_custom_column', 'prefix_display_summary_column', 10, 2);
para aplicar la acción a tipos de entrada específicos. La razón por la que no hay una declaración if verificando los tipos de entrada es porque esta columna no estará presente en otros tipos de entrada debido al paso anterior.
Agregar campo de edición rápida
Con eso es suficiente para renderizar la columna personalizada summary
y sus datos. Lo siguiente es agregar la funcionalidad para la edición rápida. El primer paso aquí es agregarlo a los datos inline ocultos para almacenarlo en el frontend y recuperarlo más tarde usando la acción add_inline_data así:
/**
* Agregar Resumen a los Datos del Editor Inline
*
* @param WP_Post $post El objeto de entrada actual.
*
* @return void
*/
function prefix_add_summary_inline($post)
{
if (in_array($post->post_type, array('post','episode'))) {
printf(
'<div class="summary">%s</div>',
esc_html(get_post_meta($post->ID,'summary',true))
);
}
}
add_action('add_inline_data', 'prefix_add_summary_inline');
Al momento de escribir esto, no hay una acción específica para tipos de entrada, por lo que se necesita la declaración if que lo verifica. Estoy usando esc_html() aquí, pero usa la función de escape que sea apropiada para tus datos.
Ahora para mostrar el campo de texto en el cuadro de edición rápida usando la acción quick_edit_custom_box así:
/**
* Mostrar Campo de Entrada del Editor de Resumen
*
* @param string $column_name Nombre de la columna a editar.
* @param string $post_type El slug del tipo de entrada
*
* @return void
*/
function prefix_display_summary_field($column_name, $post_type)
{
if ($column_name == 'summary' && in_array($post_type, array('post', 'episode'))) {
printf(
'<fieldset class="inline-edit-col-right">
<div class="inline-edit-col">
<div class="inline-edit-group wp-clearfix">
<label class="inline-edit-au-websites alignleft">
<span class="title">%s</span>
<span class="input-text-wrap">
<input type="text" name="%s" autocomplete="off">
</span>
</label>
</div>
</div>
</fieldset>',
esc_html__('Resumen'),
esc_attr($column_name)
);
}
}
add_action('quick_edit_custom_box', 'prefix_display_summary_field', 10, 2);
Aquí estamos verificando el nombre de nuestra columna personalizada y el tipo de entrada, y mostrando el HTML que usa un marcado similar a los otros campos de edición rápida. Siéntete libre de minimizar el HTML.
Y finalmente, la última pieza es precargar el campo de entrada con los datos ocultos. En lugar de manipular el script del editor, aquí simplemente escuchamos el evento focus que se dispara cuando se abre, luego tomamos nuestro valor del campo oculto y lo establecemos (¡pero solo si nuestro valor oculto tiene datos y el valor actual del campo del editor está vacío!).
Para esto, me engancho a la acción admin_enqueue_scripts y utilizo la función wp_add_inline_script y adjunto nuestro JS personalizado al script del editor inline (inline-edit-post
) que ya está cargado en la página. De esta manera, nuestro script siempre aparecerá después de que se cargue inline-edit-post.min.js
, pero solo en nuestros tipos de entrada especificados post
y episode
.
/**
* Precargar Campo de Resumen con Valor de la Entrada
*
* @param string $hook Sufijo del hook para la página de administración actual
*
* @return void
*/
function prefix_prefill_summary_field($hook) {
if ($hook == 'edit.php' && in_array(get_post_type(), array('post', 'episode'))) {
wp_add_inline_script(
'inline-edit-post', // Handle
'(function($) {
$(".ptitle").on("focus",function(e){
let id = parseInt($(e.target).closest(".quick-edit-row").attr("id").replace("edit-","")),
value = $("#inline_"+id+" .summary").text(),
$input = $("#edit-"+id+".quick-edit-row input[name=summary]");
if(value && !$input.val()) {
$input.val(value);
}
});
})(jQuery);'
);
}
}
add_action('admin_enqueue_scripts', 'prefix_prefill_summary_field');
Todo esto funciona porque extiende lo que WordPress hace de forma nativa, cómo guarda los datos meta y de entrada. Por eso no tengo que escribir nada personalizado para una acción save_post
.

¿Qué hace que el guardado funcione? ¿Cómo sabe WP cuál es el nombre del campo meta para guardar?

@bence-szalai WordPress conoce el nombre de la clave meta a través del atributo name
del elemento de control del formulario en el formulario de edición rápida de la entrada. En el ejemplo anterior, es el valor de $column_name
en la función prefix_display_summary_field()
, por ejemplo summary
. Personalmente, recomendaría prefijar todos tus campos meta para evitar sobrescrituras accidentales. Sabe que debe guardarlo porque existirá en el array $_POST
al enviar el formulario, el cual WP leerá dinámicamente y guardará en los meta de la entrada.
