Hacer un chat web AJAX (Parte 2) - CSS y jQuery

En la segunda parte de este tutorial de dos partes, crearemos el front-end jQuery y CSS de nuestro AJAX Web Chat. En la primera parte, discutimos el lado de PHP y MySQL. Puedes leer la primera parte aquí.

Ahora continuemos desde donde lo dejamos la última vez.

CSS

Los estilos de chat son autónomos y residen en chat.css . Estos estilos son independientes del resto de la página, por lo que es más fácil incrustar la ventana de chat en un sitio web existente. Solo necesita incluir el marcado HTML que discutimos la semana pasada e incluir la hoja de estilo y los archivos JavaScript.

chat.css - Parte 1

/* Main chat container */

#chatContainer{
    width:510px;
    margin:100px auto;
    position:relative;
}

/* Top Bar */

#chatTopBar{
    height:40px;
    background:url('../img/solid_gray.jpg') repeat-x #d0d0d0;
    border:1px solid #fff;
    margin-bottom:15px;
    position:relative;

    color:#777;
    text-shadow:1px 1px 0 #FFFFFF;
}

#chatTopBar .name{
    position:absolute;
    top:10px;
    left:40px;
}

#chatTopBar img{
    left:9px;
    position:absolute;
    top:8px;
}

/* Chats */

#chatLineHolder{
    height:360px;
    width:350px;
    margin-bottom:20px;
    outline:none;
}

.chat{
    background:url('../img/chat_line_bg.jpg') repeat-x #d5d5d5;
    min-height:24px;
    padding:6px;
    border:1px solid #FFFFFF;

    padding:8px 6px 4px 37px;
    position:relative;
    margin:0 10px 10px 0;
}

.chat:last-child{
    margin-bottom:0;
}

.chat .gravatar{
    background:url('http://www.gravatar.com/avatar/ad516503a11cd5ca435acc9bb6523536?size=23') no-repeat;
    left:7px;
    position:absolute;
    top:7px;
}

.chat img{
    display:block;
    visibility:hidden;
}

Empezamos diseñando el #chatContainer división Se centra horizontalmente en la página, con la ayuda de un margen automático. Como vio en la parte anterior del tutorial, este div se divide en una barra superior, un área de chats, un área de usuario y la barra inferior.

La barra superior muestra la información de inicio de sesión del usuario. Se le asigna una posición relativa para que el avatar, el nombre y el botón de cierre de sesión se puedan colocar en consecuencia.

Después de esto, viene el div que contiene todos los chats:#chatLineHolder . Este div tiene un ancho y una altura fijos y, como verá en la parte de jQuery de este tutorial, estamos usando el complemento jScrollPane para convertirlo en un elegante área de desplazamiento con barras laterales personalizadas.

chat.css - Parte 2

/* Chat User Area */

#chatUsers{
    background-color:#202020;
    border:1px solid #111111;
    height:360px;
    position:absolute;
    right:0;
    top:56px;
    width:150px;
}

#chatUsers .user{
    background:url('http://www.gravatar.com/avatar/ad516503a11cd5ca435acc9bb6523536?size=30') no-repeat 1px 1px #444444;
    border:1px solid #111111;
    float:left;
    height:32px;
    margin:10px 0 0 10px;
    width:32px;
}

#chatUsers .user img{
    border:1px solid #444444;
    display:block;
    visibility:hidden;
}

/* Bottom Bar */

#chatBottomBar{
    background:url('../img/solid_gray.jpg') repeat-x #d0d0d0;
    position:relative;
    padding:10px;
    border:1px solid #fff;
}

#chatBottomBar .tip{
    position:absolute;
    width:0;
    height:0;
    border:10px solid transparent;
    border-bottom-color:#eeeeee;
    top:-20px;
    left:20px;
}

#submitForm{
    display:none;
}

En la segunda parte le damos estilo a los #chatUsers contenedor y el usuario divs. Cada usuario de chat activo está representado por un gravatar de 32 por 32 px. El predeterminado se define como fondo, y cuando se cargan las imágenes de fondo reales, se muestran encima de ellas. Esto evita el molesto parpadeo que suele ocurrir antes de que se cargue la imagen.

El resto del código se ocupa de la barra inferior y los formularios de envío. Puede que le resulte interesante la forma en que el div .tip se convierte en un triángulo CSS puro utilizando una altura y anchura cero, junto con un valor de borde grande. También hemos usado este truco en tutoriales anteriores.

chat.css - Parte 3

/* Overriding the default styles of jScrollPane */

.jspVerticalBar{
    background:none;
    width:20px;
}

.jspTrack{
    background-color:#202020;
    border:1px solid #111111;
    width:3px;
    right:-10px;
}

.jspDrag {
    background:url('../img/slider.png') no-repeat;
    width:20px;
    left:-9px;
    height:20px !important;
    margin-top:-5px;
}

.jspDrag:hover{
    background-position:left bottom;
}

/* Additional styles */

#chatContainer .blueButton{
    background:url('../img/button_blue.png') no-repeat;
    border:none !important;
    color:#516D7F !important;
    display:inline-block;
    font-size:13px;
    height:29px;
    text-align:center;
    text-shadow:1px 1px 0 rgba(255, 255, 255, 0.4);
    width:75px;
    margin:0;
    cursor:pointer;
}

#chatContainer .blueButton:hover{
    background-position:left bottom;
}

En la última parte del código, anulamos el estilo predeterminado del div jScrollPane. Por defecto se muestra con barras de desplazamiento moradas, lo cual no es muy adecuado para nuestro diseño. En lugar de codificar nuestra propia hoja de estilo desde cero, solo incluimos la predeterminada y anulamos algunas de las reglas.

Por último, puedes ver los estilos del botón azul. Puede asignar esta clase a cualquier ancla o botón normal y obtendrá un botón azul bonito.

jQuery

Pasando al último paso de este tutorial:el código jQuery. El chat funciona al escuchar eventos en los formularios de inicio de sesión y envío (y el botón de cierre de sesión), y al programar la solicitud AJAX al servidor para verificar si hay nuevos chats y usuarios.

Como viste en la primera parte del tutorial la semana pasada, en el lado de PHP, las solicitudes de AJAX son manejadas por ajax.php. jQuery emite una serie de solicitudes AJAX:

  • Inicio de sesión de un usuario:esto se hace mediante una sola solicitud POST;
  • Cierre de sesión de un usuario:también una sola solicitud POST;
  • Comprobación de usuarios registrados:esto se realiza una vez cada 15 segundos;
  • Comprobación de nuevos chats:se activa una solicitud GET cada segundo. Esto podría significar potencialmente una gran carga para su servidor web, es por eso que el script está optimizado en el back-end y, dependiendo de la actividad del chat, las solicitudes se reducen a una cada 15 segundos.

Como verá en el código siguiente, hemos definido un envoltorio personalizado para $.get de jQuery y $.post Funciones AJAX, que nos ayudarán a no tener que completar todos los largos parámetros para emitir una solicitud.

Además, todo el código del chat está organizado en un único objeto llamado chat. . Consiste en una serie de métodos útiles, que verá en los fragmentos a continuación.

script.js - Parte 1

$(document).ready(function(){

    chat.init();

});

var chat = {

    // data holds variables for use in the class:

    data : {
        lastID         : 0,
        noActivity    : 0
    },

    // Init binds event listeners and sets up timers:

    init : function(){

        // Using the defaultText jQuery plugin, included at the bottom:
        $('#name').defaultText('Nickname');
        $('#email').defaultText('Email (Gravatars are Enabled)');

        // Converting the #chatLineHolder div into a jScrollPane,
        // and saving the plugin's API in chat.data:

        chat.data.jspAPI = $('#chatLineHolder').jScrollPane({
            verticalDragMinHeight: 12,
            verticalDragMaxHeight: 12
        }).data('jsp');

        // We use the working variable to prevent
        // multiple form submissions:

        var working = false;

        // Logging a person in the chat:

        $('#loginForm').submit(function(){

            if(working) return false;
            working = true;

            // Using our tzPOST wrapper function
            // (defined in the bottom):

            $.tzPOST('login',$(this).serialize(),function(r){
                working = false;

                if(r.error){
                    chat.displayError(r.error);
                }
                else chat.login(r.name,r.gravatar);
            });

            return false;
        });

El propósito de init() El método es vincular todos los controladores de eventos para el chat e iniciar las funciones de tiempo de espera que se utilizan para programar las comprobaciones de nuevos chats y usuarios en línea. Puede ver que hemos utilizado nuestras propias funciones de contenedor - $.tzGET y $.tzPOST . Estos eliminan la carga de tener que especificar una larga lista de parámetros y objetivos para las solicitudes ajax.

script.js - Parte 2

        // Submitting a new chat entry:

        $('#submitForm').submit(function(){

            var text = $('#chatText').val();

            if(text.length == 0){
                return false;
            }

            if(working) return false;
            working = true;

            // Assigning a temporary ID to the chat:
            var tempID = 't'+Math.round(Math.random()*1000000),
                params = {
                    id            : tempID,
                    author        : chat.data.name,
                    gravatar    : chat.data.gravatar,
                    text        : text.replace(/</g,'&lt;').replace(/>/g,'&gt;')
                };

            // Using our addChatLine method to add the chat
            // to the screen immediately, without waiting for
            // the AJAX request to complete:

            chat.addChatLine($.extend({},params));

            // Using our tzPOST wrapper method to send the chat
            // via a POST AJAX request:

            $.tzPOST('submitChat',$(this).serialize(),function(r){
                working = false;

                $('#chatText').val('');
                $('div.chat-'+tempID).remove();

                params['id'] = r.insertID;
                chat.addChatLine($.extend({},params));
            });

            return false;
        });

        // Logging the user out:

        $('a.logoutButton').live('click',function(){

            $('#chatTopBar > span').fadeOut(function(){
                $(this).remove();
            });

            $('#submitForm').fadeOut(function(){
                $('#loginForm').fadeIn();
            });

            $.tzPOST('logout');

            return false;
        });

        // Checking whether the user is already logged (browser refresh)

        $.tzGET('checkLogged',function(r){
            if(r.logged){
                chat.login(r.loggedAs.name,r.loggedAs.gravatar);
            }
        });

        // Self executing timeout functions

        (function getChatsTimeoutFunction(){
            chat.getChats(getChatsTimeoutFunction);
        })();

        (function getUsersTimeoutFunction(){
            chat.getUsers(getUsersTimeoutFunction);
        })();

    },

En la segunda parte del script, continuamos con la vinculación de los detectores de eventos. En el formulario de envío, puede ver que cuando el usuario agrega un nuevo chat, se crea uno temporal y se muestra de inmediato, sin esperar a que se complete la solicitud de AJAX. Una vez que se completa la escritura, el chat temporal se elimina de la pantalla. Esto da a los usuarios la sensación de que el chat es ultrarrápido, mientras que la escritura real se realiza en segundo plano.

Cerca del final del método init, ejecutamos dos funciones nombradas autoejecutables. Las funciones en sí se pasan como parámetros a los respectivos chat.getChats() o chat.getUsers() para que se puedan programar tiempos de espera adicionales (puede ver esto en la parte 5 del código).

script.js - Parte 3

    // The login method hides displays the
    // user's login data and shows the submit form

    login : function(name,gravatar){

        chat.data.name = name;
        chat.data.gravatar = gravatar;
        $('#chatTopBar').html(chat.render('loginTopBar',chat.data));

        $('#loginForm').fadeOut(function(){
            $('#submitForm').fadeIn();
            $('#chatText').focus();
        });

    },

    // The render method generates the HTML markup
    // that is needed by the other methods:

    render : function(template,params){

        var arr = [];
        switch(template){
            case 'loginTopBar':
                arr = [
                '<span><img src="',params.gravatar,'" width="23" height="23" />',
                '<span class="name">',params.name,
                '</span><a href="" class="logoutButton rounded">Logout</a></span>'];
            break;

            case 'chatLine':
                arr = [
                    '<div class="chat chat-',params.id,' rounded"><span class="gravatar">'+
                    '<img src="',params.gravatar,'" width="23" height="23" '+
                    'onload="this.style.visibility=\'visible\'" />',
                    '</span><span class="author">',params.author,
                    ':</span><span class="text">',params.text,
                    '</span><span class="time">',params.time,'</span></div>'];
            break;

            case 'user':
                arr = [
                    '<div class="user" title="',params.name,'"><img src="',params.gravatar,
                    '" width="30" height="30" onload="this.style.visibility=\'visible\'"'+
                    ' /></div>'
                ];
            break;
        }

        // A single array join is faster than
        // multiple concatenations

        return arr.join('');

    },

Aquí el render() método merece la mayor parte de nuestra atención. Lo que hace es ensamblar una plantilla dependiendo del parámetro de plantilla pasado . Luego, el método crea y devuelve el código HTML solicitado, incorporando los valores del segundo parámetro:el objeto params según sea necesario. Esto es usado por la mayoría de los otros métodos discutidos aquí.

script.js - Parte 4

// The addChatLine method ads a chat entry to the page

    addChatLine : function(params){

        // All times are displayed in the user's timezone

        var d = new Date();
        if(params.time) {

            // PHP returns the time in UTC (GMT). We use it to feed the date
            // object and later output it in the user's timezone. JavaScript
            // internally converts it for us.

            d.setUTCHours(params.time.hours,params.time.minutes);
        }

        params.time = (d.getHours() < 10 ? '0' : '' ) + d.getHours()+':'+
                      (d.getMinutes() < 10 ? '0':'') + d.getMinutes();

        var markup = chat.render('chatLine',params),
            exists = $('#chatLineHolder .chat-'+params.id);

        if(exists.length){
            exists.remove();
        }

        if(!chat.data.lastID){
            // If this is the first chat, remove the
            // paragraph saying there aren't any:

            $('#chatLineHolder p').remove();
        }

        // If this isn't a temporary chat:
        if(params.id.toString().charAt(0) != 't'){
            var previous = $('#chatLineHolder .chat-'+(+params.id - 1));
            if(previous.length){
                previous.after(markup);
            }
            else chat.data.jspAPI.getContentPane().append(markup);
        }
        else chat.data.jspAPI.getContentPane().append(markup);

        // As we added new content, we need to
        // reinitialise the jScrollPane plugin:

        chat.data.jspAPI.reinitialise();
        chat.data.jspAPI.scrollToBottom(true);

    },

El addChat() toma un objeto de parámetro con el contenido del chat, el autor y el gravatar, e inserta la nueva línea de chat en el lugar apropiado en el #chatContainer división Cada chat (si no es temporal) tiene una identificación única asignada por MySQL. Esta identificación se asigna como nombre de clase al chat en forma de chat-123 .

Cuando addChat() se ejecuta el método, comprueba si existe la entrada de chat anterior (para chat-123 verificaría chat-122 ). Si existe, inserta el nuevo chat después de él. Si no es así, simplemente lo agrega al div. Esta sencilla técnica logra insertar todos los chats en el orden correcto y mantenerlos así.

script.js - Parte 5

// This method requests the latest chats
    // (since lastID), and adds them to the page.

    getChats : function(callback){
        $.tzGET('getChats',{lastID: chat.data.lastID},function(r){

            for(var i=0;i<r.chats.length;i++){
                chat.addChatLine(r.chats[i]);
            }

            if(r.chats.length){
                chat.data.noActivity = 0;
                chat.data.lastID = r.chats[i-1].id;
            }
            else{
                // If no chats were received, increment
                // the noActivity counter.

                chat.data.noActivity++;
            }

            if(!chat.data.lastID){
                chat.data.jspAPI.getContentPane().html('<p class="noChats">No chats yet</p>');
            }

            // Setting a timeout for the next request,
            // depending on the chat activity:

            var nextRequest = 1000;

            // 2 seconds
            if(chat.data.noActivity > 3){
                nextRequest = 2000;
            }

            if(chat.data.noActivity > 10){
                nextRequest = 5000;
            }

            // 15 seconds
            if(chat.data.noActivity > 20){
                nextRequest = 15000;
            }

            setTimeout(callback,nextRequest);
        });
    },

    // Requesting a list with all the users.

    getUsers : function(callback){
        $.tzGET('getUsers',function(r){

            var users = [];

            for(var i=0; i< r.users.length;i++){
                if(r.users[i]){
                    users.push(chat.render('user',r.users[i]));
                }
            }

            var message = '';

            if(r.total<1){
                message = 'No one is online';
            }
            else {
                message = r.total+' '+(r.total == 1 ? 'person':'people')+' online';
            }

            users.push('<p class="count">'+message+'</p>');

            $('#chatUsers').html(users.join(''));

            setTimeout(callback,15000);
        });
    },

Aquí nos encargamos de gestionar las solicitudes de AJAX. En getChats() puede ver que estamos determinando cuándo volver a ejecutar la función dependiendo de la propiedad noActivity del objeto de datos local. En cada solicitud que no devuelve nuevos chats, incrementamos el contador. Si alcanza cierto umbral, la siguiente solicitud se retrasa.

script.js - Parte 6

    // This method displays an error message on the top of the page:

    displayError : function(msg){
        var elem = $('<div>',{
            id        : 'chatErrorMessage',
            html    : msg
        });

        elem.click(function(){
            $(this).fadeOut(function(){
                $(this).remove();
            });
        });

        setTimeout(function(){
            elem.click();
        },5000);

        elem.hide().appendTo('body').slideDown();
    }
};

// Custom GET & POST wrappers:

$.tzPOST = function(action,data,callback){
    $.post('php/ajax.php?action='+action,data,callback,'json');
}

$.tzGET = function(action,data,callback){
    $.get('php/ajax.php?action='+action,data,callback,'json');
}

// A custom jQuery method for placeholder text:

$.fn.defaultText = function(value){

    var element = this.eq(0);
    element.data('defaultText',value);

    element.focus(function(){
        if(element.val() == value){
            element.val('').removeClass('defaultText');
        }
    }).blur(function(){
        if(element.val() == '' || element.val() == value){
            element.addClass('defaultText').val(value);
        }
    });

    return element.blur();
}

Y, por último, estos son nuestros métodos y funciones auxiliares. El displayError() El método muestra la barra roja amigable en la parte superior de la página si ocurre un error. Después de esto vienen nuestros envoltorios personalizados $.tzGET y $.tzPOST, y finalmente el defaultText complemento, que desarrollamos hace un tiempo para mostrar texto de marcador de posición.

¡Con esto nuestro chat AJAX está completo!

Para terminar

En este tutorial de dos partes, desarrollamos una solución de chat completa con clases de PHP, una base de datos MySQL y una interfaz ágil de jQuery. Puede usarlo para brindar soporte en vivo para su sitio web, o dejar que los visitantes de su sitio se diviertan con él. Es una gran solución para chats de tamaño pequeño a mediano (menos de 20 personas simultáneamente).

Si le gustó este tutorial, asegúrese de seguirnos en Twitter para obtener enlaces útiles para desarrolladores web y suscríbase a nuestra fuente RSS.