Sunday, November 18, 2012

Overloading methods in TypeScript

At yesterday's presentation at Desert Code Camp I felt that I gave an inadequate explanation on how you would implement an overloaded method in TypeScript. Hopefully this code snippet will address that:

interface Thing {
    a: number;
    b: string;
    foo(x: string): string;
    foo(n: number): number;
}

function myfunction(myParam: Thing) {
    var stringResult: string = myParam.foo(myParam.b);
    var numberResult: number = myParam.foo(myParam.a);

    console.log(stringResult);
    console.log(numberResult);
}


var myObj = {
    a: 16,
    b: "My String",
    foo: (x: any) => {
        if (x && typeof x === 'string') {
            return x.length.toString();
        } else if (x && typeof x === 'number') {
            return x * x;
        } else {
            if (x) {
                throw { message: "null parameter is unsupported" };
            } else {
                throw { message: "unsupported type: " + typeof x };
            }
        }
    }
}

myfunction(myObj);

Note that in myfunction() you would not be able to call myParam.foo() with any other type of parameter than string or number because the compiler will complain about that. So even though the implementation checks for another type in the final else you cannot pass in another type (e.g. object) because TypeScript won't let you.

Friday, November 16, 2012

Toastmasters Timer in TypeScript

I recently wrote a Toastmasters Timer in TypeScript for two reasons:
  1. The other timers that I found out on the web didn't quite do what I wanted them to do and
  2. I needed a small project to learn TypeScript
All of the timers I found followed the same approximate format as this one created by Stan Birdwell (Central Toastmasters 2277-31) and Michael K. Heney (Goddard Toastmasters 3496-36):




I was looking for something that would should the time in a large font on the page and change the color of a large portion of the page at each stage. I needed this to be able to constantly monitor the time and color from a distance while I am pacing around the room practicing a speech. I didn't want to have to lean forward to see the time. This is the final result: Toastmasters Timer






Here is the TypeScript source code for that timer (the compiled JS, HTML and CSS can be pulled from the page):
/// <reference path="jquery.d.ts" />

class SpeechType {
    constructor (public name: string, public greenTime: string, public yellowTime: string, public redTime: string, public id: string) {
    }
}

class TSTimer {
    timerToken: number;
    speeches: SpeechType[];
    started: bool;
    startTime: Date;
    stopTime: Date;
    green: number;
    yellow: number;
    red: number;

    constructor (speeches: SpeechType[]) {
        this.started = false;
        this.speeches = speeches;

        $.each(this.speeches, (indexInArray: number, valueOfElement: SpeechType) => {
            var newButton = $('<span>')
                .attr('id', valueOfElement.id)
                .addClass('speech-type')
                .html(valueOfElement.name);
            newButton.click( (event) => {
                this.activateSpeech($(event.target).attr('id'));
            });
            newButton.appendTo('#buttons');
        });
       
        $(window).resize( () => {
            this.resizeTime();
        });
        // call on initialization in case the browser starts off narrow
        this.resizeTime();

        $('#btnReset').click( () => {
            this.resetButton();
        });

        $('#btnStart').click(() => {
            this.startButton();
        });

    }

    resetButton() {
        this.stop();
        $('#trafficlight').text('0:00');
        $('#body').css('background-color', '#EFEEEF');
        this.startTime = null;
    }

    startButton() {
        if (this.started) {
            this.stop();
        } else {
            this.start();
        }
    };

    resizeTime() {
        var width = $(window).width();
        var x: number = Math.floor((width < 900) ? (width / 900) * 28 : 28);
        $('#trafficlight').css('font-size', x + 'em');
    }

    setElementText(elapsedSeconds: number) {
        $('#trafficlight').text(this.formatTime(elapsedSeconds));
        if (elapsedSeconds >= this.red) {
            $('#body').css('background-color', '#FF4040');
        } else if (elapsedSeconds >= this.yellow) {
            $('#body').css('background-color', '#FCDC3B');
        } else if (elapsedSeconds >= this.green) {
            $('#body').css('background-color', '#A7DA7E');
        }
    }

    timerEvent() {
        if (!this.startTime) {
            this.startTime = new Date();
        }
        var timeNow = new Date();
        var elapsedSeconds: number = this.timeDiffInSeconds(this.startTime, timeNow);
        this.setElementText(elapsedSeconds);
    }

    // Returns the difference in seconds between
    timeDiffInSeconds(earlyTime: Date, lateTime: Date): number {
        var diff: number = lateTime.getTime() - earlyTime.getTime();
        return Math.floor(diff / 1000);
    }

    formatTime(elapsedSeconds: number) {
        var minutes: number = Math.floor(elapsedSeconds / 60);
        var seconds: number = elapsedSeconds % 60;
        return minutes + ":" + ((seconds < 10) ? "0" + seconds.toString() : seconds.toString());
    }

    start() {
        $('#btnStart').val('Stop');
        this.started = true;
        if (this.startTime) {
            // i.e. no reset since the last time it was started so adjust the start time
            // to reflect the time that's elapsed since it was stopped.
            var newStartTime = new Date().getTime() - (this.stopTime.getTime() - this.startTime.getTime());
            this.startTime.setTime(newStartTime);
        }
        this.green = this.getSecondsFromTextBox('#green-light');
        this.yellow = this.getSecondsFromTextBox('#yellow-light');
        this.red = this.getSecondsFromTextBox('#red-light');
        this.timerToken = setInterval(() => this.timerEvent(), 1000);
    }

    stop() {
        $('#btnStart').val('Start');
        this.started = false;
        this.stopTime = new Date();
        clearTimeout(this.timerToken);
    }
   

    getSecondsFromTextBox(id: string): number {
        var greenLight: string = $(id).val();
        return parseInt(greenLight.split(':')[0]) * 60 + parseInt(greenLight.split(':')[1]);
    }

    setDefault() {
        this.activateSpeech('st-standard');
    }

    activateSpeech(speechId: string) {
        $.each(this.speeches, function (indexInArray: number, valueOfElement: SpeechType) {
            if (valueOfElement.id === speechId) {
                $('#green-light').val(valueOfElement.greenTime);
                $('#yellow-light').val(valueOfElement.yellowTime);
                $('#red-light').val(valueOfElement.redTime);
            }
        });
        $('.active-speech').removeClass('active-speech');
        $('#' + speechId).addClass('active-speech');
    }
}


$(document).ready(function () {
    var speeches = [];
    speeches.push(new SpeechType("Table&nbsp;Topics", "1:00", "1:30", "2:00", "st-table-topics"));
    speeches.push(new SpeechType("Evaluation", "2:00", "2:30", "3:00", "st-evaluation"));
    speeches.push(new SpeechType("Icebreaker", "4:00", "5:00", "6:00", "st-icebreaker"));
    speeches.push(new SpeechType("Standard", "5:00", "6:00", "7:00", "st-standard"));
    speeches.push(new SpeechType("Advanced", "8:00", "9:00", "10:00", "st-advanced"));
    speeches.push(new SpeechType("Test", "0:02", "0:04", "0:06", "st-test"));
    var timer = new TSTimer(speeches);

    timer.setDefault();
});



Saturday, November 10, 2012

TypeScript template doesn't work in FireFox

If you're using Visual Studio 2012 and you've added the TypeScript for Microsoft Visual Studio 2012 extension then you will be able to jump start a TypeScript project using the template. If you click File > New > Project and look under the Visual C# templates you'll see a template called HTML Application with TypeScript. Selecting this template will generate a new project with a TypeScript file with the following contents:

class Greeter {
    element: HTMLElement;
    span: HTMLElement;
    timerToken: number;
   
    constructor (element: HTMLElement) {
        this.element = element;
        this.element.innerText += "The time is: ";
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.span.innerText = new Date().toUTCString();
    }

    start() {
        this.timerToken = setInterval(() =>
          this.span.innerText = new Date().toUTCString(), 500);
    }

    stop() {
        clearTimeout(this.timerToken);
    }
}

window.onload = () => {
    var el = document.getElementById('content');
    var greeter = new Greeter(el);
    greeter.start();
};

The problem with this code is the use of the .innerText property which is called .textContent in FireFox.

Here is a replacement to the above template that will work in FireFox and other browsers to get you moving forward again.

Tested on:
FireFox 16.0.2
Safari for Windows 5.1.7
IE9
Chrome 23.0.1271.64 m
Opera 12.10

class Greeter {
    element: HTMLElement;
    span: HTMLElement;
    timerToken: number;
    hasInnerText: bool;
   
    constructor (element: HTMLElement) {
        this.element = element;
        // Firefox uses textContent
        this.hasInnerText = !this.element.textContent;
        this.appendElementText(this.element, "The time is: ");
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.setElementText(this.span, new Date().toUTCString());
    }

    setElementText(elem: HTMLElement, value: string) {
        if (this.hasInnerText) {
            elem.innerText = value;
        } else {
            elem.textContent = value;
        }
    }

    appendElementText(elem: HTMLElement, value: string) {
        if (this.hasInnerText) {
            elem.innerText += value;
        } else {
            elem.textContent += value;
        }
    }

    start() {
        this.timerToken = setInterval(() =>
          this.setElementText(this.span, new Date().toUTCString()), 500);
    }

    stop() {
        clearTimeout(this.timerToken);
    }   
}

window.onload = () => {
    var el = document.getElementById('content');
    var greeter = new Greeter(el);
    greeter.start();
};


 

 

Friday, November 2, 2012

TypeScript

I'm going to be giving a presentation called TypeScript, what's all the fuss? at the Fall 2012 Desert Code Camp on November 17, 2012. I'll be adding a list of resources and my slides to this blog post.

TypeScript Resources:

Here's an example of method overloading in TypeScript which I didn't cover adequately in this presentation.