[Tutorial] WebForms & jQueryUI con Drag & Drop

Hola, hoy veremos un tutorial de como implementar un pequeño sistema en donde vamos a utilizar Drag & Drop con jQueryUI. El escenario es el siguiente, tenemos por un lado la columna de Salas y por otro lado la columna de Pacientes.

DragCam

Los requerimientos son:

  1. Las salas deben obtenerse desde la base de datos
  2. Los pacientes deben obtenerse desde la base de datos
  3. Se debe permitir arrastrar un paciente a una sala y registrar su posicion
  4. Se debe permitir el movimiento de pacientes entre salas
  5. Se debe permitir sacar al paciente desde una sala (arrastrar a la columna de pacientes)
  6. Se debe regitrar todo movimiento del paciente

Con estos requrimientos,, comenzamos a construir nuestra modelo base de datos, el cual es bastante simple y cumple con lo que necesitamos:

image

Para facilitar el manejo de los datos , voy a utilizar LinQtoSQL, para ello vamos a tener el siguiente contexto:

image

Con esto ya tenemos nuestro modelo de datos, veamos ahora los métodos que nos van a permitir obtener la lista de Salas y Pacientes que asociaremos a DataList y Repetears.

EL primer método hace una busqueda de todos los pacientes que estan en “la sala de espera” a esa sala le puse el Id=4, y también llamamos al metodo obtSalasAll, que se obtiene todas las salas (menos la sala 4, es la sala de espera)

Public Sub bindAll()
   DataListPaciente.DataSource = obtPacientesAll(4)      
   DataListPaciente.DataBind()
   DataListSalas.DataSource = obtSalasAll()
   DataListSalas.DataBind()
End Sub


Public Function obtPacientesAll(ByVal sala As Integer) As List(Of PacienteUI)

    Dim contexto As New contextoDataContext

    Dim lista = (From p In contexto.Pacientes Where p.IdSala = sala 
               
Select
IdPaciente = p.IdPaciente, 
                       Nombre = p.NombrePaciente,
                       color = p.Prioridade.ColorPrioridad,
                       IdSala = p.IdSala)

     Dim listaPacienteUI As New List(Of PacienteUI)

     For Each obj In lista

         listaPacienteUI.Add(New PacienteUI() With {.Color = obj.color,
         .NombrePaciente = obj.Nombre, .IdPaciente = obj.IdPaciente,
         .IdSala = obj.IdSala})

     Next

     Return listaPacienteUI

 End Function

 

Public Function obtSalasAll() As List(Of Sala)

    Dim contexto As New contextoDataContext

    Dim lista = (From p In contexto.Salas Order By 
                p.NombreSala
Where
p.IdSala <> 4)

    Return lista.ToList

End Function

Para el mostrar los pacientes dentro de la sala de espera, tengo un objeto definido para ese fin, si quieres podrias mostrar el mismo que entrega el contexto.

Class PacienteUI

   Private _IdPaciente As Integer

   Public Property IdPaciente() As Integer

     Get

       Return _IdPaciente

     End Get

     Set(ByVal value As Integer)

       _IdPaciente = value

     End Set

   End Property

 

   Private _nombre As String

   Public Property NombrePaciente() As String

     Get

       Return _nombre

     End Get

     Set(ByVal value As String)

       _nombre = value

     End Set

   End Property

 

   Private _color As String

   Public Property Color() As String

     Get

       Return _color

     End Get

     Set(ByVal value As String)

       _color = value

     End Set

   End Property

 

   Private _IdSala As Integer

   Public Property IdSala() As Integer

            Get

                Return _IdSala

            End Get

            Set(ByVal value As Integer)

                _IdSala = value

            End Set

        End Property

 

End Class

Para cada cambio de posición, ya sea entre salas y a la sala de espera, vamos a updatear mediante una llamada mediante ajax a un webservice indicando el paciente y la sala. Esta llamada la voy a hacer mediante el registro de la referencia del WebService en el ScriptManager. Luego mediante JavaScript invocaremos al WService.

Este WebService como puedes ver actualiza la posición del paciente y luego inserta en la tabla de moviemientos su nueva Sala, siempre y cuando sea distinta a a que ya tenia asignada. Si bien esto lo vamos a validar tambien en el lado del cliente, es importante hacerlo acá, ya que se podría consumir este servicio desde otra fuente.

El código del WebService:

Imports System.Web

Imports System.Web.Services

Imports System.Web.Services.Protocols

<System.Web.Script.Services.ScriptService()> _

<WebService(Namespace:="http://tempuri.org/")> _

<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _

<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _

Public Class RegistraMovimientosWS

    Inherits System.Web.Services.WebService

 

<WebMethod()> _

  Public Sub RegistraPosicion(ByVal IdPaciente As Integer,
                             
ByVal IdSala As Integer
)

        Dim contexto As New contextoDataContext

        Dim paciente As New Paciente

        paciente = (From p In contexto.Pacientes Where p.IdPaciente = IdPaciente).Single

        If paciente.IdSala <> IdSala Then

            paciente.IdSala = IdSala

 

            Dim movimiento As New Movimiento

            movimiento.IdPaciente = IdPaciente

            movimiento.IdSala = IdSala

            movimiento.Fecha = Date.Now

 

            contexto.Movimientos.InsertOnSubmit(movimiento)

            contexto.SubmitChanges()

        End If

    End Sub

End Class  

Veamos ahora la estructura de nuestro WebForms, primero la columna de Sala de Espera de Pacientes (Columna derecha).

<form id="form1" runat="server">
    <asp:ScriptManager ID="sm1" runat="server" >
        <Services>
            <asp:ServiceReference InlineScript="false"
                 Path
="~/RegistraMovimientosWS.asmx"
/>
        </Services>
    </asp:ScriptManager>
 

   <asp:Button ID="Button1" style="visibility:hidden" runat="server" Text="Button" />

<asp:UpdatePanel ID="UpdatePanel1" runat="server">

  <ContentTemplate>

    <div>

     <div id="salas">

        <asp:DataList ID="DataListSalas" EnableViewState="false" 
         runat
="server" RepeatColumns
="1"
RepeatDirection="Horizontal">

        <ItemTemplate>

         <div id="<%#Eval("IdSala")%>" class="block" 
         
style="background-color: #<%#Eval("ColorSala")%>
">

        <div style="text-align: center; font-size: 18px; border: solid 1px">

<%#Eval("NombreSala")%></div>

        <asp:HiddenField ID="hidsala" runat="server"
       
Value=’<%# eval("IdSala") %>
/>

         <asp:Repeater ID="DataListPacientesSalas" runat="server"
         
 EnableViewState
="false">

              <ItemTemplate>

                <div class="pacienteEnSala arrastra"
               
 sala="<%#Eval("IdSala")%>" id="<%#Eval("IdPaciente")%>
">

                 <%#Eval("NombrePaciente")%></div>

               </ItemTemplate>

           </asp:Repeater>

         </div>

        </ItemTemplate>

     </asp:DataList>

     </div>

    

   <div id="pacientes">

      <asp:DataList ID="DataListPaciente" runat="server"
          
RepeatColumns="3" RepeatDirection
="Vertical">

       <ItemTemplate>

            <div id="<%#Eval("IdPaciente")%>" 
            
sala="<%#Eval("IdSala")%>" 
            
class="blockPaciente arrastra" 
            
style="background-color: #<%#Eval("Color")%>
">

        <div style="text-align: center; font-size: 18px; border: solid 1px;">
 
   <%#Eval("NombrePaciente")%></div>

     </ItemTemplate>

    </asp:DataList>

   </div>

</div>

</ContentTemplate>

<Triggers>

       <asp:AsyncPostBackTrigger ControlID="Button1"  EventName="Click" />

  </Triggers>

</asp:UpdatePanel>

</form>

En vista de diseño veríamos algo así:

image

Puedes notar también que existe un boton que está oculto mediante un estilo, este servirá para generar un postback asincrono para recargar los datos que tienen los elementos datalist  y repeaters.

Veamos el código  que va a dar soporte a la funcionalidad

Primero que nada, vamos a incluir las librerias jQuery y JqueryUI en nuestro proyecto

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js" type="text/javascript"></script>

<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"type="text/javascript"></script>

Luego vamos a definir la funcionalidad de Drag & Drop mediante el plugin de JqueryUI. Debemos definir cual es el estilo que será seleccionado para atachar el comportamiento de arrastrar (draggable), en mi caso el estilo arrastra,lo mismo ocurre con droppable, en donde definimos la funcionalidad a realizar cuando se hace el drop:

  • Obtenemos el Id del objeto arrastrable, que corresponde al IdPaciente
  • Obtenemos el Id del objeto que acepta al elemento arrastrable, es decir la sala.
  • Obtenemos el Id de la sala que ya tiene asociado el IdPaciente, esto es importante para no hacer la llamada asincrona cuando movemos el objeto dentro del mismo contenedor.
  • Removemos la clase para mostrar un elemento más pequeño dentro de la sala.
  • LLamamos al Webservice mediante Microsoft Ajax (Recuerda que ya lo registramos en el ScriptManager)
  • Hacemos un PostBack Asíncrono para refrescar los webcontrols.

Lo mismo se hace con el la columna de pacientes, el concepto del Drop de jQueryUI es que definas el selector del objecto que va a ser draggable y el objeto que será droppable y cual es el objeto que acepta: “arrastra”, entonces, así por ejemplo el estilo Block acepta que se arrastren dentro de él objetos “arrastra”.

En la columna pacientes, la sala de espera es la Sala 4, esto se puede mejorar en el código (en eso estoy pero queria postear rápido este ejemplo).

<script type="text/javascript">

 

    window.Error = function(msg) {

        alert(msg);

    }

 

    function UpdPanelUpdate() {

       __doPostBack("<%= Button1.ClientID %>", "");

    }

 

    function creaComportamiento() {

        $(".arrastra").draggable({ revert: ‘invalid’ });

        $(".block").droppable({

            accept: ‘.arrastra’,

            activeClass: ‘droppable-active’,

            hoverClass: ‘droppable-hover’,

            drop: function(event, ui) {

                var idPaciente = parseInt($(ui.draggable).attr(‘id’));

                var idSala = $(this).attr(‘id’);

                var idSalaAnterior = $(ui.draggable).attr(‘sala’);

                if (idSala != idSalaAnterior) {

                    $(ui.draggable).removeClass("blockPaciente");

                    RegistraMovimientosWS.RegistraPosicion(idPaciente,
                                                           idSala);

                    UpdPanelUpdate();

                }

               

            }

        });

        $("#pacientes").droppable({

            accept: ‘.arrastra’,

            drop: function(event, ui) {

                $(ui.draggable).addClass("blockPaciente");

                var idSalaAnterior = $(ui.draggable).attr(‘sala’);

                if (4 != idSalaAnterior) {

                    var idPaciente = parseInt($(ui.draggable).attr(‘id’));

                    RegistraMovimientosWS.RegistraPosicion(idPaciente, 4);

                    UpdPanelUpdate();

                }

            }

        });

    }

   

    $(document).ready(function() {

        creaComportamiento();

    });

 

</script>

Ya habrás notado que todo el comportamiento está encapsulado en “crearComportamiento”, esto tiene una razón de ser, y es que cada vez que el updatePanel hace un post asíncrono , vuelve a crear los objetos del DOM , y no se vuelve a ejecutar el $(document).ready lo que conlleva que este código como está solo se ejecuta la primera vez, una vez hecho el postback deja de funcionar, debido a que no existe nuevamente el bind del comportamiento que proporciona jqueryui a los componentes.   La solución es sencilla, y es que cada vez que llamamos al UpdPanelUpdate() en donde ocupamos como trigger un postback con el botón oculto, llamamos a un método que hace el bindAll y luego llama a “crearComportamiento” mediante el ScriptManager.RegisterStartupScript, con esto nos aseguramos que siempre se vuelva a cargar la funcionalidad despues de los refresh del UpdaPanel.

Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click

   bindAll()

   ScriptManager.RegisterStartupScript(Me, Me.GetType, "script",
                                     
"creaComportamiento();", True
)

 End Sub

Y listo! tenemos la funcionalidad que deaseamos cubriendo las necesidades de los requerimientos que planteamos al principio Smile

Descarga el ejemplo completo con los CSS utilizados acá:
https://www.dropbox.com/sh/75za68riilzehhc/Nk4AV_KPUU/PruebaSinet.zip

Espero que te sea útil, nos vemos!
@chalalo

Un comentario en “[Tutorial] WebForms & jQueryUI con Drag & Drop”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *