Agregar la confirmación del cliente al eliminar (C#)

por Scott Mitchell

Descargar PDF

En las interfaces creadas hasta ahora, un usuario puede eliminar datos accidentalmente si hace clic en el botón Eliminar cuando lo que pretendía era hacer clic en el botón Editar. En este tutorial, se agregará un cuadro de diálogo de confirmación del lado cliente que aparecerá cuando se haga clic en el botón Eliminar.

Introducción

En los últimos tutoriales ha visto cómo usar la arquitectura de la aplicación, ObjectDataSource y los controles web de datos de manera conjunta para proporcionar funcionalidades de inserción, edición y eliminación. Las interfaces de eliminación que ha examinado hasta ahora consistían en un botón Eliminar que, al presionarse, provocaba un postback e invocaba el método Delete() de ObjectDataSource. Después, el método Delete() invoca el método configurado desde la capa de lógica de negocios, que propaga la llamada hasta la capa de acceso a datos, emitiendo la instrucción DELETE real a la base de datos.

Aunque esta interfaz de usuario permite a los visitantes eliminar registros desde los controles GridView, DetailsView o FormView, carece de confirmación cuando el usuario hace clic en el botón Eliminar. Si un usuario hace clic accidentalmente en el botón Eliminar cuando lo que quería era hacer clic en Editar, en su lugar se eliminará el registro que pretendía actualizar. Para evitarlo, en este tutorial agregará un cuadro de diálogo de confirmación del lado cliente que aparecerá cuando se haga clic en el botón Eliminar.

La función confirm(string) de JavaScript muestra su parámetro de entrada de cadena como el texto dentro de un cuadro de diálogo modal con dos botones: Aceptar y Cancelar (vea la figura 1). La función confirm(string) devuelve un valor booleano en función de en qué botón se hace clic (true si el usuario hace clic en Aceptar y false si hace clic en Cancelar).

The JavaScript confirm(string) Method Displays a Modal, Client-Side Messagebox

Figura 1: El método confirm(string) de JavaScript muestra un cuadro de mensaje modal del lado cliente

Durante un envío de formulario, si se devuelve un valor de false desde un controlador de eventos del lado cliente, el envío del formulario se cancela. Con esta característica, puede hacer que el controlador de eventos onclick del lado cliente del botón Eliminar devuelva el valor de una llamada a confirm("Are you sure you want to delete this product?"). Si el usuario hace clic en Cancelar, confirm(string) devolverá false, lo que hará que el envío del formulario se cancele. Sin postback, el producto en cuyo botón Eliminar se ha hecho clic no se eliminará. Pero si el usuario hace clic en Aceptar en el cuadro de diálogo de confirmación, el postback continuará sin interrupción y el producto se eliminará. Consulte Uso del método confirm() de JavaScript para controlar el envío de formularios para más información sobre esta técnica.

Agregar el script del lado cliente necesario difiere ligeramente si se usan plantillas que cuando se usa un control CommandField. Por tanto, en este tutorial verá un ejemplo tanto con FormView como con GridView.

Nota:

Al usar técnicas de confirmación del lado cliente como las descritas en este tutorial, se da por hecho que los usuarios utilizan exploradores que admiten JavaScript y que tienen JavaScript habilitado. Si alguna de estas asunciones no se cumple en un usuario determinado, al hacer clic en el botón Eliminar se producirá inmediatamente un postback (no se muestra un cuadro de mensaje de confirmación).

Paso 1: Creación de un control FormView que admita la eliminación

Para empezar, agregue un control FormView a la página ConfirmationOnDelete.aspx de la carpeta EditInsertDelete, enlazando a un nuevo objeto ObjectDataSource que extraiga la información del producto desde el método GetProducts() de la clase ProductsBLL. Configure también ObjectDataSource para que el método DeleteProduct(productID) de la clase ProductsBLL se asigne al método Delete() de ObjectDataSource. Asegúrese de que las listas desplegables de las pestañas INSERT y UPDATE están establecidas en (None). Por último, active la casilla Habilitar paginación de la etiqueta inteligente de FormView.

Tras realizar estos pasos, el nuevo marcado declarativo de ObjectDataSource tendrá un aspecto similar al siguiente:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    DeleteMethod="DeleteProduct" OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProducts" TypeName="ProductsBLL">
    <DeleteParameters>
        <asp:Parameter Name="productID" Type="Int32" />
    </DeleteParameters>
</asp:ObjectDataSource>

Como en los ejemplos anteriores, donde no se usaba la simultaneidad optimista, dedique un momento a borrar la propiedad OldValuesParameterFormatString de ObjectDataSource.

Como se ha enlazado a un control ObjectDataSource que solo admite la eliminación, ItemTemplate de FormView solo ofrece el botón Eliminar, sin los botones Nuevo y Actualizar. Pero el marcado declarativo de FormView incluye unos controles EditItemTemplate y InsertItemTemplate superfluos, que se pueden quitar. Dedique un momento a personalizar ItemTemplate, de forma que solo muestre un subconjunto de los campos de datos del producto. Aquí se ha configurado para que muestre el nombre del producto en un encabezado <h3>, encima de los nombres de proveedor y categoría (junto con el botón Eliminar).

<asp:FormView ID="FormView1" AllowPaging="True" DataKeyNames="ProductID"
    DataSourceID="ObjectDataSource1" runat="server">
    <ItemTemplate>
        <h3><i><%# Eval("ProductName") %></i></h3>
        <b>Category:</b>
        <asp:Label ID="CategoryNameLabel" runat="server"
            Text='<%# Eval("CategoryName") %>'>
        </asp:Label><br />
        <b>Supplier:</b>
        <asp:Label ID="SupplierNameLabel" runat="server"
            Text='<%# Eval("SupplierName") %>'>
        </asp:Label><br />
        <asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False"
            CommandName="Delete" Text="Delete">
        </asp:LinkButton>
    </ItemTemplate>
</asp:FormView>

Con estos cambios, tendrá una página web totalmente funcional que permite al usuario alternar entre los productos de uno en uno, con la capacidad de eliminar un producto simplemente haciendo clic en el botón Eliminar. En la figura 2 se muestra una captura de pantalla del progreso hasta ahora visto en un explorador.

The FormView Shows Information About a Single Product

Figura 2: En el control FormView se muestra información sobre un solo producto (Haga clic para ver la imagen a tamaño completo)

Paso 2: Llamada a la función confirm(string) desde el evento OnClick del lado cliente del botón Eliminar

Con FormView creado, el último paso es configurar el botón Eliminar de forma que, cuando el visitante haga clic en él, se invoque la función confirm(string) de JavaScript. La adición de scripts del lado cliente a un evento onclick del lado cliente de un control Button, LinkButton o ImageButton se puede realizar con OnClientClick property, una novedad de ASP.NET 2.0. Como quiere que se devuelva el valor de la función confirm(string), simplemente establezca esta propiedad en: return confirm('Are you certain that you want to delete this product?');

Después de este cambio, la sintaxis declarativa del control LinkButton Eliminar debe tener un aspecto similar al siguiente:

<asp:LinkButton ID="DeleteButton" runat="server" CausesValidation="False"
    CommandName="Delete" Text="Delete"
    OnClientClick="return confirm('Are you certain you want to delete this product?');">
</asp:LinkButton>

Así de simple. En la figura 3 se muestra una captura de pantalla de esta confirmación en acción. Al hacer clic en el botón Eliminar, se abre el cuadro de diálogo de confirmación. Si el usuario hace clic en Cancelar, el postback se cancela y el producto no se elimina. Pero si el usuario hace clic en Aceptar, el postback continúa y se invoca el método Delete() de ObjectDataSource, que acaba eliminando el registro de base de datos.

Nota:

La cadena que se pasa a la función confirm(string) de JavaScript se delimita con apóstrofos (en lugar de comillas). En JavaScript, las cadenas se pueden delimitar mediante cualquiera de esos caracteres. Aquí se usan apóstrofos para que los delimitadores de la cadena pasada a confirm(string) no introduzcan ambigüedad con los delimitadores usados para el valor de propiedad OnClientClick.

A Confirmation is Now Displayed When Clicking the Delete Button

Figura 3: Ahora se muestra una confirmación al hacer clic en el botón Eliminar (Haga clic para ver la imagen a tamaño completo)

Paso 3: Configuración de la propiedad OnClientClick del botón Eliminar en CommandField

Cuando usan controles Button, LinkButton o ImageButton directamente en una plantilla, se pueden asociar a un cuadro de diálogo de confirmación si se configura su propiedad OnClientClick para devolver los resultados de la función confirm(string) de JavaScript. Pero el elemento CommandField, que agrega un campo de botones Eliminar a GridView o DetailsView, no tiene una propiedad OnClientClick que se pueda establecer mediante declaración. En su lugar, se debe hacer referencia mediante programación al botón Eliminar en el controlador de eventos DataBound adecuado de GridView o DetailsView y, después, establecer allí su propiedad OnClientClick.

Nota:

Al establecer la propiedad OnClientClick del botón Eliminar en el controlador de eventos DataBound adecuado, tiene acceso a los datos enlazados al registro actual. Esto significa que puede ampliar el mensaje de confirmación para incluir detalles sobre el registro en particular, por ejemplo, "¿Está seguro de que desea eliminar el producto Chai?". Esta personalización también es posible en las plantillas mediante la sintaxis de enlace de datos.

Para practicar la configuración de la propiedad OnClientClick de los botones Eliminar en un CommandField, se agregará un control GridView a la página. Configure este control GridView para que use el mismo control ObjectDataSource que FormView utiliza. Limite también los controles BoundField de GridView para que incluyan solo el nombre, la categoría y el proveedor del producto. Por último, active la casilla Habilitar edición de la etiqueta inteligente de GridView. Esto agregará un objeto CommandField a la colección Columns de GridView con la propiedad ShowDeleteButton establecida en true.

Después de realizar estos cambios, el marcado declarativo de GridView debe tener un aspecto similar al siguiente:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">
    <Columns>
        <asp:CommandField ShowDeleteButton="True" />
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" ReadOnly="True"
            SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier" ReadOnly="True"
            SortExpression="SupplierName" />
    </Columns>
</asp:GridView>

CommandField contiene una única instancia de un control LinkButton Eliminar, a la que se puede acceder mediante programación desde el controlador de eventos RowDataBound de GridView. Una vez que se le haga referencia, se puede establecer su propiedad OnClientClick como corresponda. Cree un controlador de eventos para el evento RowDataBound con el código siguiente:

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        // reference the Delete LinkButton
        LinkButton db = (LinkButton)e.Row.Cells[0].Controls[0];

        // Get information about the product bound to the row
        Northwind.ProductsRow product =
            (Northwind.ProductsRow) ((System.Data.DataRowView) e.Row.DataItem).Row;

        db.OnClientClick = string.Format(
            "return confirm('Are you certain you want to delete the {0} product?');",
            product.ProductName.Replace("'", @"\'"));
    }
}

Este controlador de eventos funciona con filas de datos (las que tendrán el botón Eliminar) y comienza haciendo referencia al botón Eliminar mediante programación. En general, use el siguiente patrón:

ButtonType obj = (ButtonType) e.Row.Cells[commandFieldIndex].Controls[controlIndex];

ButtonType es el tipo de botón que CommandField usa: Button, LinkButton o ImageButton. De forma predeterminada, CommandField usa controles LinkButton, pero esto se puede personalizar mediante ButtonType property de CommandField. commandFieldIndex es el índice ordinal de CommandField dentro de la colección Columns de GridView, mientras que controlIndexes el índice del botón Eliminar dentro de la colección Controls de CommandField. El valor controlIndex depende de la posición del botón con respecto a otros botones en CommandField. Por ejemplo, si el único botón mostrado en CommandField es el botón Eliminar, use un índice 0. Pero si hay un botón Editar que precede al botón Eliminar, use un índice 2. La razón por la que se usa un índice 2 es porque CommandField agrega dos controles antes del botón Eliminar: el botón Editar y una instancia de LiteralControl que se usa para crear espacio entre los botones Editar y Eliminar.

En este ejemplo concreto, CommandField usa controles LinkButton y, al ser el campo más a la izquierda, tiene un valor commandFieldIndex de 0. Como no hay otros botones más que el botón Eliminar en CommandField, se usa un valor controlIndex de 0.

Después de hacer referencia al botón Eliminar en CommandField, se obtiene información sobre el producto enlazado a la fila de GridView actual. Por último, se establece la propiedad OnClientClick del botón Eliminar en el código JavaScript adecuado, que incluye el nombre del producto. Como la cadena de JavaScript que se pasa a la función confirm(string) se delimita mediante apóstrofos, se debe aplicar escape a los que aparecen dentro del nombre del producto. En concreto, a los apóstrofos del nombre del producto se les aplica escape con "\'".

Una vez que se completen estos cambios, al hacer clic en un botón Eliminar en GridView se muestra un cuadro de diálogo de confirmación personalizado (vea la figura 4). Al igual que con el cuadro de mensaje de confirmación de FormView, si el usuario hace clic en Cancelar, el postback se cancela, lo que impide que se produzca la eliminación.

Nota:

Esta técnica también se puede usar para acceder mediante programación al botón Eliminar de CommandField en un control DetailsView. Pero en el caso de DetailsView, se crea un controlador de eventos para el evento DataBound, ya que DetailsView no tiene un evento RowDataBound.

Clicking the GridView s Delete Button Displays a Customized Confirmation Dialog Box

Figura 4: Al hacer clic en el botón Eliminar de GridView, se abre un cuadro de diálogo de confirmación personalizado (Haga clic para ver la imagen a tamaño completo)

Uso de controles TemplateField

Uno de los inconvenientes de CommandField es que hay que acceder a sus botones mediante indexación y que el objeto resultante se debe convertir al tipo de botón adecuado (Button, LinkButton o ImageButton). El uso de "números mágicos" y de tipos codificados de forma rígida es propenso a problemas que no se pueden detectar hasta el tiempo de ejecución. Por ejemplo, si usted u otro desarrollador agrega botones nuevos al CommandField en el futuro (por ejemplo, un botón Editar) o cambia la propiedad ButtonType, el código existente se seguirá compilando sin errores, pero visitar la página puede provocar una excepción o un comportamiento inesperado, en función de cómo se ha escrito el código y de qué cambios se han realizado.

Un método alternativo consiste en convertir los controles CommandField de GridView y DetailsView en controles TemplateField. Esto generará un control TemplateField con un objeto ItemTemplate que tiene un control LinkButton (o Button, o ImageButton) para cada botón de CommandField. Las propiedades OnClientClick de estos botones se pueden asignar mediante declaración, como con FormView, o se puede acceder a ellas mediante programación en el controlador de eventos DataBound adecuado mediante el siguiente patrón:

ButtonType obj = (ButtonType) e.Row.FindControl("controlID");

Donde controlID es el valor de la propiedad ID del botón. Aunque este patrón todavía necesita un tipo codificado de forma rígida para la conversión, elimina la necesidad de la indexación, lo que permite que el diseño cambie sin provocar un error en tiempo de ejecución.

Resumen

La función confirm(string) de JavaScript es una técnica que se usa habitualmente para controlar el flujo de trabajo de envío de formularios. Cuando se ejecuta, la función muestra un cuadro de diálogo modal del lado cliente que incluye dos botones, Aceptar y Cancelar. Si el usuario hace clic en Aceptar, la función confirm(string) devuelve true; al hacer clic en Cancelar, se devuelve false. Esta funcionalidad, junto con un comportamiento del explorador que cancela un envío de formulario si un controlador de eventos durante el proceso de envío devuelve false, se puede usar para mostrar un cuadro de mensaje de confirmación al eliminar un registro.

La función confirm(string) se puede asociar a un controlador de eventos onclick del lado cliente del control web Button desde la propiedad OnClientClick del control. Al trabajar con un botón Eliminar en una plantilla, ya sea en una de las plantillas de FormView o en un control TemplateField en DetailsView o GridView, esta propiedad se puede establecer mediante declaración o mediante programación, como se ha visto en este tutorial.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP y ASP.NET, y fundador de 4GuysFromRolla.com, trabaja con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él a través de mitchell@4GuysFromRolla.com. o de su blog, que se puede encontrar en http://ScottOnWriting.NET.