Componente de mensajes con TypeScript, Bootstrap y RequireJS – Parte 2

Posted by in Javascript y TypeScript

Segunda parte y última para terminar de explicar el código del componente de mensajes con TypeScript, Bootstrap y Require, donde en la parte 1 se explicó cómo es la interfaz donde probaremos el componente de mensajes (index.html), y describimos en detalle cómo configurar RequireJS, preparamos la gestión de eventos y app.ts.

Nos queda pendiente ver el código de MsgBox.ts, que es donde se desarrolla la acción, y Menu.ts para probar el componente.

El código completo lo teneis en Github.

Msgbox.ts

El código donde se desarrolla toda la acción está en msgbox.ts, que es bastante completo. Por un lado, para hacer uso de las plantillas HandleBarsJS es necesario incluir las referencias a los archivos hbs tanto como referencias amd cómo más adelante con la carga de las mismas desde require (esto está al principio).

Un detalle inicial es que al iniciar el componente, se cargan todos los formularios para ser utilizados posteriormente, por lo que se puede incluir un DIV donde se hará o no (el componente lo hará por nosotros!).

El componente incluye varias funciones de validación de los formularios de login y de para cambiar de clave, que en casos de fallo o de no cumplir con los requisitos, pueden lanzan mensajes. Todo esto lo puedes tu modificar según tus necesidades!!

Por último, es necesario usar export MsgBox para poder reutilizar el componente.

/// <reference path="../typings/jquery.d.ts" />
/// <reference path="../typings/bootstrap.d.ts" />
/// <reference path="interfaces.ts" />
/// <reference path="LiteEvent.ts" />

/// <amd-dependency path="hbs!templates/msgbox" />
/// <amd-dependency path="hbs!templates/login" />
/// <amd-dependency path="hbs!templates/cambiapass" />
/// <amd-dependency path="hbs!templates/yesno" />

var __UPDATED__ = '2015.11.13';
var __VERSION__ = "1.2.0";
var __AUTHOR__ = 'David Trillo';
var __WEBSITE__ = '';

// http://jschr.github.io/bootstrap-modal/
import $ = require("jquery");
import LiteEvent = require("liteevent");

var MsgBoxTemplate:Function = require('hbs!templates/msgbox');
var LoginTemplate:Function = require('hbs!templates/login');
var CambiaPassTemplate:Function = require('hbs!templates/cambiapass');
var YesNoTemplate:Function = require('hbs!templates/yesno');


class MsgBox {
    private _s = 'show';
    private _h = 'hide';
    private _f = 'form';
    
    private base: JQuery;
    private template:string ;
    private msgbox: JQuery;
    
    private _div_alert: JQuery; // div_alert
    private _div_alert_close_btn: JQuery;
    
    private onAlert = new LiteEvent<boolean>();
    private onLogin = new LiteEvent<string>();   
    private onLogout = new LiteEvent<void>();     
    private onChangePass = new LiteEvent<string>();     
    private onYesNoCancel = new LiteEvent<string>();
    
    public get AlertOK(): ILiteEvent<boolean> { return this.onAlert; } 
    public get LoggedIn(): ILiteEvent<string> { return this.onLogin; } 
    public get LoggedOut(): ILiteEvent<void> { return this.onLogout; }
    public get PassChanged(): ILiteEvent<string> { return this.onChangePass; }
    public get YesNoCancel(): ILiteEvent<string> { return this.onYesNoCancel; }
    
    // Funciones privadas
    private _creadiv(nombre): JQuery {
        var div = "
<div id='" + nombre + "'></div>

";
        this.base.append(div);
        return this.base.find('#'+nombre);
    }
    
    // VALIDACION para Mostrar MsgBOX
    private _valida_opciones_msgbox(opc: IAlert) {
        if (opc.txt_boton_cerrar == undefined) {opc["txt_boton_cerrar"] = "Cerrar";}
        if (opc.boton_cerrar == undefined) {opc["boton_cerrar"] = true;}
        if (opc.timer == undefined) {opc["timer"] = 0; }
        if (opc.modal_header_class == undefined) { opc["modal_header_class"] = "";}
        
        return opc
    }
    
    constructor(div_base?: JQuery) {
        if (div_base == undefined) {
            this.base = $('
<div id="msgbox_container" />').appendTo('body');
        } else { this.base = $(div_base);}
        this.msgbox = this._creadiv('div_msgbox');    // DIV q almacena los .hbs
    }
    
    // Alert MsgBox
    public show_alert(opc: IAlert) {
        var div = "#msgbox";
        var that = this;
        opc = that._valida_opciones_msgbox(opc);
        var tmp = MsgBoxTemplate(opc);
        that.msgbox.html(tmp);
        var div_msg = that.msgbox.find(div);
        
        div_msg.modal(that._s);
        
        if (opc.timer > 0) {
            setTimeout(() => { div_msg.modal(that._h); }, opc.timer);
        }
        div_msg.find('#btn_login').on('click', (e) => { 
            e.preventDefault();
            div_msg.modal(that._h);
            that.onAlert.trigger(true);    
        });
    }
    
    // Login MsgBOX
    show_login(opc): any {
        var div = '#form_login';
        var that = this;
        var tmp = LoginTemplate(opc);
        this.msgbox.html(tmp);
        var form = this.msgbox.find(div);
        this._clear_login(form);
        form.modal(that._s);
        form.find('#btn_login').on('click', (e) => { 
            e.preventDefault();
            form.modal(that._h);
            if (that._valida_login(form)) {
                var cadena = form.find(that._f).serialize();
                that.onLogin.trigger(cadena);    
            }
            else {
                console.log('Sin datos para Login')
            }
        });
    }
    
    private _valida_login(form): boolean {
        var campos = ["user", "password"];
        var tmp: string = '';
        var valores = [];
        var devuelve_cero = false;
        for (var i = 0; i < campos.length; ++i) {             tmp = form.find('#' + campos[i]).val();             if (tmp.length == 0) { devuelve_cero = true;}             else {valores.push(tmp)}         }         return (valores.length == campos.length)     }     private _clear_login(form: JQuery): void {         var inputs = form.find(':input');         inputs.each((i) => {
            $(i).val('');
        })
    }
    
    // CambiaPass MsgBox
    show_cambiapass(opc: ICambiaPass) {
        var delay: number = 1000;
        var that = this;
        var tmp = CambiaPassTemplate(opc);
        this.msgbox.html(tmp);
        var form = this.msgbox.find('#form_cambiapass');
        form.modal(that._s);
        form.find('#btn_login').on('click', (e) => { 
            e.preventDefault();
            var subform = form.find(that._f);
            var cadena = subform.serialize();
            form.modal(that._h);
            var que: number = that._valida_cambiapass(subform); // Que devuelve 0 si no hace nada, 1 si es OK y 2 si hay error!
            if (que == 1) {
                // Si todo es correcto!
                that.onChangePass.trigger(cadena);
            }
            else if (que == 2){
                var aler: IAlert = that._valida_opciones_msgbox(opc.alert_change_password_error);
                setTimeout(() => { that.show_alert(aler); }, delay);
            }
        });
        
    }
    
    // Valida los datos de cambiapass
    private _valida_cambiapass(form: JQuery): number {
        var campos = ["oldpass", "newpass", "newpass2"];
        var tmp: string = '';
        var valores = [];
        var devuelve_cero = false;
        for (var i = 0; i < campos.length; ++i) {             tmp = form.find('#' + campos[i]).val();             if (tmp.length == 0) { devuelve_cero = true;}             valores.push(tmp)         }         if (devuelve_cero) {             return 0         }         var bln1: boolean = valores[0] != valores[1];         var bln2: boolean = valores[2] === valores[1];         var bln3: boolean = (valores[1].length > 0);
        var bln4: boolean = (valores[0].length > 0)
        // Para que sea True, debe de ocurrir que oldpass != newpass y que newpass == newpass2
        
        return (bln1 && bln2 && bln3 && bln4) ? 1 : 2;
    }
    
    // Yes-NO MsgBox
    show_yesno(opc: IYesNoCancel) {
        var delay: number = 1000;
        var div = "#msgbox_yesnocancel";
        var that = this;
        var tmp = YesNoTemplate(opc);
        that.msgbox.html(tmp);
        var div_msg = that.msgbox.find(div);
        div_msg.modal(that._s);
        
        div_msg.find('#btn_yes').on('click', (e) => { 
            e.preventDefault();
            div_msg.modal(that._h);
            opc.funcion_click_yes('Pulsado SI');
            setTimeout(() => { that.onYesNoCancel.trigger('yes');; }, delay);
        });
        div_msg.find('#btn_no').on('click', (e) => { 
            e.preventDefault();
            div_msg.modal(that._h);
            opc.funcion_click_no('Pulsado NO');
            setTimeout(() => { that.onYesNoCancel.trigger('no');; }, delay);    
        });
        div_msg.find('#btn_cancel').on('click', (e) => { 
            e.preventDefault();
            div_msg.modal(that._h);
            opc.funcion_click_cancel('Pulsado CANCEL');
            setTimeout(() => { that.onYesNoCancel.trigger('cancel');; }, delay);    
        });
        
    }
    
    set_div_alert(div: string) {
        this._div_alert = $(div);
    }
    div_alert(html: string, timer = 0) {
        this._div_alert.show();
        this._div_alert_close_btn = this._div_alert.find('button');
        var _msg = this._div_alert.find('#mensaje');
        _msg.html(html);
        if (timer > 0) { setTimeout(() => { this._div_alert_close_btn.hide(); }, timer);
        }
    }
    div_alert_stop() {
        // this._div_alert.addClass('hide');
        this._div_alert.hide();
    }
}    
export = MsgBox ;

Menu.ts

Sólo nos queda hablar de menu.ts, donde se prueba el componente de mensajes. El contructor de la clase llama a la función refresca, que es donde se refresca la funcionalidad.

Aunque el código completo es bastante similar para cada uno de los botones, sólo comentaré uno de ellos:


this.div_base.find('#simple').on('click', (e) => {
e.preventDefault();
var opc: IAlert = {
titulo: 'Test',
mensaje: 'Esto es una prueba',
txt_boton_cerrar: 'Cerrar',
btn_class: 'btn-default',
modal_header_class: 'modal-header-success',
boton_cerrar: true,
funcion_click_cerrar: that.test
}
that.msg.show_alert(opc);
that.msg.AlertOK.on((bln) => { that.test('Simple'); });
});

En concreto, analizamos el evento click sobre el botón simple. Es fundamental parar la ejecución de evento con preventDefault(), para que el siguiente paso sea preparar la variable con todas las opciones sobre el mensaje a generar. En este caso, usamos IAlert (definido en interfaces.ts).

La siguiente instrucción es mostrar la alerta, enviando las opciones de configuración.

La última instrucción es la que «espera» a que el evento suceda (trigger) para que ocurra algo. En este caso, se llama a la función test(), que envia un mensaje a la consola.

var that = this

Quería llamar tu atención porque en TS es muy corriente ver el cambio de referencia dentro de las funciones. This hace referencia al propietario de la función y su valor depende de la función y objeto que lo llamó. That permite tener una referencia a un objeto, donde más adelante This cambia de propietario. Un poco lioso, pero si utilizais AJAX lo entenderéis, y aquí he usado yo esto.

Los motivos de este componente

Seguramente hubiera sido más fácil reutilizar alguno de los plugins de Bootstrap sobre modals, que hay varios (y alguno también lo he utilizado), pero el hecho de crear el mio propio me ha permitido mejorar el conocimiento de TypeScript, aprender también más sobre HandleBarsJS, y sobre todo, que estoy usando este componente en varios proyectos, y las mejoras de uno se migran a los otros.

msgbox component

msgbox component

Y esto ha sido todo. Estoy seguro de que se te ocurrirán muchas cosas para mejorar, así que, no dudes en escribirme o comentarmelo.

Un saludo y happy coding!