Agregar la confirmación del cliente al eliminar (VB)

Por Scott Mitchell

Descargar PDF

En las interfaces que hemos creado hasta ahora, un usuario puede eliminar datos accidentalmente haciendo clic en el botón Eliminar cuando lo que pretendía era hacer clic en el botón Editar. En este tutorial, agregaremos 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 hemos 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 hemos examinado hasta ahora consistían en un botón Eliminar que, al presionarse, provocaba un postback e invocaba el método Delete() de ObjectDataSource. A continuación, el método Delete() invoca el método configurado desde la capa de lógica empresarial, 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 a través de 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, el registro que pretendía actualizar se eliminará en su lugar. Para evitar esto, en este tutorial agregaremos 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 que viene equipado con dos botones: Aceptar y Cancelar (vea la figura 1). La función confirm(string) devuelve un valor booleano dependiendo 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, podemos 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á. Sin embargo, 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 Usar el método confirm() de JavaScript para controlar el envío de formularios para obtener 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 lo tanto, en este tutorial veremos 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: Crear un control FormView que admita la eliminación

Empiece agregando un control FormView a la página ConfirmationOnDelete.aspx de la carpeta EditInsertDelete, enlazando a un nuevo ObjectDataSource que extraiga la información del producto a través del 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 INSERTAR y ACTUALIZAR están establecidas en (Ninguno). 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.

Puesto que 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. Sin embargo, 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. He configurado el mío 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, tendremos 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 de lo que llevamos hecho hasta ahora visto a través de un explorador.

The FormView Shows Information About a Single Product

Figura 2: El FormView muestra información sobre un solo producto (haga clic para ver la imagen a tamaño completo)

Paso 2: Llamar 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 usando OnClientClick property, que es nuevo en ASP.NET 2.0. Puesto que queremos 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. Sin embargo, 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 de JavaScript confirm(string) se delimita con apóstrofos (en lugar de comillas). En JavaScript, las cadenas se pueden delimitar mediante cualquiera de esos caracteres. Aquí usamos 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: Configurar la propiedad OnClientClick del botón Eliminar en un CommandField

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

Nota:

Al establecer la propiedad OnClientClick del botón Eliminar en el controlador de eventos DataBound adecuado, tenemos acceso a los datos enlazados al registro actual. Esto significa que podemos 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 usando sintaxis de enlace de datos.

Para practicar la configuración de la propiedad OnClientClick de los botones Eliminar en un CommandField, vamos a 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 LinkButton Eliminar, a la que se puede acceder mediante programación desde el controlador de eventos RowDataBound de GridView. Una vez que hagamos referencia a él, podemos establecer su propiedad OnClientClick como corresponda. Cree un controlador de eventos para el evento RowDataBound usando el código siguiente:

Protected Sub GridView1_RowDataBound(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) _
    Handles GridView1.RowDataBound

    If e.Row.RowType = DataControlRowType.DataRow Then
        ' reference the Delete LinkButton
        Dim db As LinkButton = CType(e.Row.Cells(0).Controls(0), LinkButton)

        ' Get information about the product bound to the row
        Dim product As Northwind.ProductsRow = _
            CType(CType(e.Row.DataItem, System.Data.DataRowView).Row, _
            Northwind.ProductsRow)

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

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

Dim obj As ButtonType = _
    CType(e.Row.Cells(commandFieldIndex).Controls(controlIndex), ButtonType)

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 a través de la 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 de 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. Sin embargo, 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 un LiteralControl que se usa para crear espacio entre los botones Editar y Eliminar.

En nuestro ejemplo concreto, CommandField usa controles LinkButton y, siendo el campo más a la izquierda, tiene un commandFieldIndex de 0. Puesto que no hay otros botones más que el botón Eliminar en CommandField, usamos un controlIndex de 0.

Después de hacer referencia al botón Eliminar en CommandField, a continuación se obtiene información sobre el producto enlazado a la fila de GridView actual. Por último, establecemos la propiedad OnClientClick del botón Eliminar en el 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, debemos escapar los apóstrofos que aparecen dentro del nombre del producto. En concreto, los apóstrofos del nombre del producto se escapan con "\'".

Una vez completados 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 tener acceso mediante programación al botón Eliminar en CommandField en un control DetailsView. Sin embargo, 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)

Usar controles TemplateField

Uno de los inconvenientes de CommandField es que hay que acceder a sus botones mediante indexación y que el objeto resultante debe convertirse 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 algún momento en el futuro (por ejemplo, un botón Editar) o cambia la propiedad ButtonType, el código existente seguirá compilando sin errores, pero visitar la página puede provocar una excepción o un comportamiento inesperado, dependiendo 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 vimos con FormView, o se puede acceder a ellas mediante programación en el controlador de eventos DataBound adecuado mediante el siguiente patrón:

Dim obj As ButtonType = CType(e.Row.FindControl("controlID"), ButtonType)

Donde controlID es el valor de la propiedad ID del botón. Aunque este patrón todavía requiere un tipo codificado de forma rígida para la conversión, elimina la necesidad de 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 del lado cliente modal 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 a través de 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 hemos visto en este tutorial.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, lleva trabajando 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 a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.