Incluir plugins de jQuery usando RequireJS y #TypeScript

Posted by in Javascript y TypeScript

Una vuelta de tuerca nueva, y otro reto planteado. En esta ocasión, se plantea un ejercicio para cargar un plugin de jQuery (se está utilizando la versión 2.1.4), usando RequireJS y también usando #TypeScript (en lugar de Javascript). El código lo tienes en GitHub.

El plugin elegido para el ejemplo es jQuery.countdown, un plugin que nos facilita hacer un contador de tiempo para que una acción suceda durante un tiempo, o a partir de un tiempo. Concretamente, pondremos un botón para iniciar el contador de 10 segundos, y nos mostrará el tiempo restante, y otro botón de pausa.

Quizás el único inconveniente del botón de pausa es que al continuar, el tiempo sigue contando sin alargar el final. Pero bueno, a mi lo que más me interesa de este plugin es que suceda algo mientras está en la cuenta atrás, y sobre todo, al finalizar, donde podemos lanzar incluso hasta dos eventos!

Para saber cómo utilizar el plugin, nada mejor que ver algún ejemplo o pasar por la documentación.

Preparación de la interfaz

Os recuerdo la entrada sobre interfaces en TypeScript, porque aquí sí que viene bien su utilización. Y es que este plugin que necesitamos probar no tiene fichero de definición de tipos en DefinitelyTyped, así que tenemos que “buscarnos la vida”.

La forma de resolver este problema es incrementando la interfaz de jQuery (que se carga con jquery.d.ts), desde interfaces.ts, que es un fichero donde iremos almacenando aquellas interfaces que necesitemos.


interface JQuery { countdown: any }

Y este es el secreto para poder ampliar jQuery con el plugin, y que desde el compilador no de errores de tipo!

Aspecto del ejemplo

El código HTML del ejemplo es bastante sencillo, pues sólo incluiremos dos botones a los que se asignará la funcionalidad propuesta.

Typescript CountDown plugin

Typescript CountDown plugin

Al final del código HTML están las llamadas a RequireJS y a la configuración.

<!DOCTYPE html>
<!-- Version 0.1.0 - ManejandoDatos TypeScript Template -->
<html lang="es">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
  
    <link href="css/bootstrap.min.css" rel="stylesheet" type="text/css" />
    <link href="css/bootstrap-extra.css" rel="stylesheet" type="text/css" />
    
    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
    
    <title>Manejando datos Template</title>
</head>

<BODY>

<div class="container">

<div class="jumbotron">CountDown jQuery plugin - RequireJS - TypeScript</div>

    </div>



<div id="countdown" class="container">
        <a href="#" class="btn btn-info" id="start">Start</a>
        <a href="#" class="btn btn-danger" id="pausa">Pausa</a>

<div id="timer"></div>

    </div>

    
    <script src="js/require.min.js"></script>
    <script src="ts/config.js"></script>
</BODY>
    
</html>

Código TypeScript

Para cargar el plugin, se utiliza el fichero de configuración de RequireJS, config.ts, cuyo código es el siguiente:

require.config({
    baseUrl: 'ts/',
    paths: {
        jquery: '../js/jquery',
        "jquery.countdown.min": '../js/jquery.countdown.min'
    },
    shim: {
        "jquery": {
            exports: '$'
        },
        "jquery.countdown.min": ['jquery']
    }
});
require(["app"]);

Desgranando este código, lo primero es la llamada al dichero de declaraciones de RequireJS (linea 1, /// <reference path=”./typings/require.d.ts” />). El plugin queda ubicado (su archivo físico) en path, pero también hay que configurar la parte de shim.

La última línea es la llamada a app.

El código de app.ts

El código es el siguiente:

<br data-mce-bogus="1">

/// <reference path="../typings/jquery.d.ts" />
/// <reference path="../typings/require.d.ts" />

/// <reference path="menu.ts" />
/// <amd-dependency path="menu.ts" />

require(['Menu', 'jquery', "jquery.countdown.min"], (MenuApp, $) => {
    'use strict';
    $(() => { var menu = new MenuApp(); });
});

Las cuatro primeras líneas son muy importantes, pues son las llamadas a las referencias utilizadas por app.ts, y que son tanto jquery como requireJS. Cómo la acción se lleva a cabo desde menu.ts, también tenemos que incorporar dichas referencias (y sobre todo la referencia amd de la línea /// <amd-dependency path=”menu.ts” />).

Menu.ts, o donde se lleva a cabo la acción

El código de Menu.ts debe comenzar por las llamadas a las dependencias, tanto de jQuery como de las interfaces (recuerda que tuvimos que ampliar jQuery en interfaces.ts!!). Se ha creado una variable a nivel de módulo donde se definen dos propiedades: el DIV base de la acción, donde se encuentran tanto los botones como la etiqueta con el tiempo restante, y el tiempo que queremos esperar.


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

import $ = require("jquery");

var opciones_app = {
base: '#contenedor',
tiempo_app: 10000
};

var estado = ["pause", "resume"];

class MenuApp {
private debug: boolean = false;
private _status: string = estado[0];
private div_base: JQuery;

constructor() {
this.div_base = $(opciones_app.base);
console.log('Plantilla cargada con exito!');
this.go_countdown();
}
go_after_countdown() {
console.log('Finalizado CountDown!');
}
go_countdown() {
var that = this;
var muestra = $('#timer');

$('#start').on('click', (e) => {
e.preventDefault();
console.log('Start');
that._status = estado[0];

var retrasar = new Date().getTime() + opciones_app.tiempo_app;
muestra.countdown(retrasar, {elapse: true})
.on('update.countdown', (event) => {
var fecha = event.strftime('%D days %H:%M:%S');
if (event.elapsed) {
fecha = "POST " + fecha;
muestra.countdown('stop');
that.go_after_countdown();
console.log('Evento 2');
}
else { fecha = " Restante ... " + fecha;     }
muestra.html(fecha);
})
.on('finish.countdown', () => { that.go_after_countdown();})
;
});

$('#pausa').on('click', (e) => {
e.preventDefault();
try {
muestra.countdown(that._status);
that._status = (that._status == estado[0]) ? estado[1] : estado[0];
}
catch(exception) { console.log(); }
})
}
}

export = MenuApp;

Se define la variable estado para pausar o continuar (pause y resume) del botón de Pausa (aunque ya he comentado que la pausa paraliza la ejecución de callbacks durante la cuenta atrás, porque el tiempo no se detiene!).

Por último, se crea la clase MenuApp, que en el constructor de la clase llama a go_countdown, que es la función que da la funcionalidad a los botones. En el DIV con id=timer es donde se muestra el tiempo restante, o que ha concluido.

En la acción del botón start es donde se ejecuta .countdown(), y además se detallan las acciones que tienen lugar tanto durante la cuenta atrás, cómo las dos opciones de callbacks una vez llegado al fin del tiempo.

Un último detalle muy importante, que encontrarás dentro de go_countdown:

var that = this;

Esta variable that es ampliamente utilizada en funciones para referirse al propio objeto, porque dentro de una función this hace referencia a otra cosa, y si mezclamos, al final, esto puede acabar en desastre, así que te recomiendo que lo uses!!

Para que la clase MenuApp sea utilizada desde RequireJS, es muy importante la última línea: export MenuApp.

Una visión del conjunto RequireJS, jQuery y TypeScript

Quizás, si no estás habil con requireJS, te pueda costar entender su filosofía, y el por qué se han las cosas así, pero realmente, y a pesar de todos los ficheros (config.ts, app.ts y menu.ts), el orden permite controlar en todo momento qué pasa y a qué nivel. Ampliar la acción es fácil, pues solo hay que trabajar en la clase MenuApp con las nuevas funcionalidades.

La configuración de RequireJS es fundamental para el éxito del proyecto, aunque tienes una plantilla con este ejemplo que te puede facilitar mucho tu trabajo!

También destacar la importancia de las interfaces (sí, es el único lenguaje donde le veo sentido!), y personalmente, creo que la corrección de errores de programación es más clara gracias a un código bien organizado (o si no, ya te acordarás cuando tengas que retomar un código que escribiste meses atrás!).

Espero que te haya gustado la entrada, espero tus comentarios y ….

Happy coding!

Google+ Comments - Comentarios Google+