Adding jQuery plugins using RequireJS and #TypeScript

requireJS

Let’s keep on learning #TypeScript, and now it’s turn to load a jQuery plugin (I am using jQuery versión 2.1.4), by using RequireJS and using #TypeScript (instead of Javascript). The code is available on GitHub.

The plugin chosen for the exercise is jQuery.countdown, a count down timer and when the times finish, a call back can be run. Also, you can include an action while dounting down. For our exercise, let’s add a button to 10 seconds countdown, showing the time to go, and a pause button.

Maybe, the only issue I found with this plugin is that when you pause, the actions while counting down are not being executed although the timer keep counting down. In my case there is no problem for me with this issue, but you should take it into account if you need to use it for something different. When the time expires, you can also launch two events with two different callbacks!

It is a good idea to read the plugin documentation in order to know how to use it!

Preparing the interface

I maight remember you the entrance related to #TypeScript interfaces, becuase for this exercise interfaces are very useful. Another aspect related to the plugin is that there are no defition file in DefinitelyTyped, so, let’s see what alternatives we have for solving it.

The only way to solve the problem is by adding elements to the jQuery interface (the file jquery.d.ts), in a new file called interfaces.ts, a file created by us, and it’s the file that will store our own interfaces (the contrats that variables should accomplish).


interface JQuery { countdown: any }

And this is the secret for increase the jQuery object with new functions (using plugin), avoiding compiling errors!

The HTML

The HTML code is simple, becuase the code is adding two buttons to add functionality.

Typescript CountDown plugin

Typescript CountDown plugin

At the end of the HTML code it is included the calls to RequireJS and to config file.

<!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>

TypeScript code

In order to load the plugin, it is compulsory to use the RequireJS config file, config.ts, with this code:

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

In this code the first that call attention is the first line, asking for the RequireJS defitinion file (line 1, /// <reference path=”./typings/require.d.ts” />). The plugin should be added to path, but there is another important issue to add: configuring it in the shim.

Last line is calling to the app.

App.ts

The code for this file is:

<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(); });
});

The first four lines of code are very important because they are calling the references used by app.ts, including jQuery andrequireJS. Because all action code is stored in menu.ts, it is also neccesary to add this reference too (and very import the amd reference in /// <amd-dependency path=”menu.ts” />).

Menu.ts, the file where the action is writen

The code for Menu.ts should start with a call to references and dependencies, such as jQuery, interfaces (remember we need in add new properties and functions to jQuery object in interfaces.ts). I use to create a module variable where I define two properties: the DIV there the two buttons are, and the label that show messages.


/// <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;

A variable called estado is defined for the pause or resume when the button Pausa is clicked (I already comment that when pausing, the count down continues although there are no callback execution during the countdown becuase the time doesn’t stop!).

I create the MenuApp class, where in the constructor of the class I call the dunction go_countdown, the function that starts the countdown. In the DIV with id=timer is where the message is shown.

When the action button is clicked, it is executed the .countdown() function, and also other actions executed during the coutdown, and also the functions when the countdown ends (you can define even two callbacks functions).

Another important detail in the go_countdown function is:

var that = this;

The that variable is frecuently used in functions for reference to the main object, because inside a function, the variable this references to another object. By using that as a variable you can create a better code, and avoid wierd behaviour. I recommend you to use it!

to end with and in order to use the class MenuApp by RequireJS, the last line is needed: export MenuApp.

Working with RequireJS, jQuery and TypeScript

If you are not used to work with requireJS, you can have some difficulties to understand ist filosophy, and how things should be done, but once you understand it (and appart of how the code is distributed within so many files, such as config.ts, app.ts and menu.ts) the developer gains a lot of control and order. Increase functionality is more easy becuase you only need to add it to the MenuApp class.

The main part here is the RequireJS configuration, the key for the success fo a project, but if you have a template, you can save a lot of time and a lot of work done!!

I also want to focus on the importance of interfaces (yes, TypeScript is the only language where interfaces have sense!), and my personal opinion is that errors can be easily detected with TypeScript due to a well organized code (and you know what I’m talking about if you already suffered when reviewing a code writen long ago!).

I hope you enjoy this entrance, becuase I am waigint for your comments and …

Happy coding!