---
title: bootstrap-button.jsをTypeScriptで写経してみた
tags: []
categories: ["Programming", "JavaScript", "TypeScript"]
date: 2012-11-06T17:58:03Z
updated: 2012-11-06T17:58:03Z
---

TypeScriptでjQueryプラグインを書いてみたいと思ったが、どうやって書くべきかわからなかったのでぐぐったところ

[http://typescript.codeplex.com/discussions/398401][1]

> You would create your plugin the way you normally do as above... Then
> you need to extend TypeScripts definition of JQuery to include your
> added plugin methods.  So for the above plugin its simply:
> 
> interface JQuery {
>     myPlugin(): JQuery; }

...要は普通にjavascriptで書いてJQueryオブジェクトを返すinterfaceだけ用意しておけばおk、という回答。。

まあクラスを使っているならTypeScriptを使った方が良いだろう、と思って既存のjQueryプラグインをTypeScriptで書いてみた。対象は[BootStrapのbuttonプラグイン][2]。

書き直すとこんな感じ

    /// <reference path="jquery.d.ts" />
    
    interface JQuery {
        button(option: ButtonSettings): JQuery;
    }
    
    interface ButtonSettings {
        loadingText?: string;
    }
    
    module $ {
        class Button {
    	public $element: JQuery;
    	public options: ButtonSettings;
    
    	constructor (element: HTMLElement, options: ButtonSettings) {
    	    this.$element = $(element);
    	    this.options = <ButtonSettings> $.extend({}, $.fn.button.defaults, options);
    	}
    
    	setState(state: string): void {
    	    var d = 'disabled'
    	    , $el = this.$element
    	    , data = $el.data()
    	    , val = $el.is('input') ? 'val' : 'html';
    	    
    	    state = state + 'Text';
    	    data.resetText || $el.data('resetText', $el[val]());
    
    	    $el[val](data[state] || this.options[state]);
    
    	    // push to event loop to allow forms to submit
    	    setTimeout(() => {
    		state == 'loadingText' ? 
    		    $el.addClass(d).attr(d, d) :
    		    $el.removeClass(d).removeAttr(d);
    	    }, 0);
    	}
    
    	toggle(): void {
    	    var $parent = this.$element.closest('[data-toggle="buttons-radio"]');
    	    $parent && $parent.find('.active').removeClass('active');
    	    this.$element.toggleClass('active');
    	}
        }
    
        /* BUTTON PLUGIN DEFINITION
         * ======================== */
        $.fn.button = function(option) {
    	return this.each(function() {
    	    var $this = $(this)
    	    , data = $this.data('button')
    	    , options = typeof option == 'object' && option;
    	    
    	    if(!data) {
    		$this.data('button', (data = new Button(this, options)));
    	    }
    	    if (option == 'toggle') {
    		data.toggle();
    	    } else if (option) {
    		data.setState(option);
    	    }
    	});
        }
    }

プラグインとして定義しているところは素のjavascriptです。。`module $.fn`にexportする関数として定義してもよいと思ったけど、元のコードに合わせてみた。

コンパイルすると

    var $;
    (function ($) {
        var Button = (function () {
            function Button(element, options) {
                this.$element = $(element);
                this.options = $.extend({
                }, $.fn.button.defaults, options);
            }
            Button.prototype.setState = function (state) {
                var d = 'disabled';
                var $el = this.$element;
                var data = $el.data();
                var val = $el.is('input') ? 'val' : 'html';
    
                state = state + 'Text';
                data.resetText || $el.data('resetText', $el[val]());
                $el[val](data[state] || this.options[state]);
                setTimeout(function () {
                    state == 'loadingText' ? $el.addClass(d).attr(d, d) : $el.removeClass(d).removeAttr(d);
                }, 0);
            };
            Button.prototype.toggle = function () {
                var $parent = this.$element.closest('[data-toggle="buttons-radio"]');
                $parent && $parent.find('.active').removeClass('active');
                this.$element.toggleClass('active');
            };
            return Button;
        })();    
        $.fn.button = function (option) {
            return this.each(function () {
                var $this = $(this);
                var data = $this.data('button');
                var options = typeof option == 'object' && option;
    
                if(!data) {
                    $this.data('button', (data = new Button(this, options)));
                }
                if(option == 'toggle') {
                    data.toggle();
                } else {
                    if(option) {
                        data.setState(option);
                    }
                }
            });
        };
    })($ || ($ = {}));


まあこんなもんか。ちなみにjquery.d.tsの

    [x: string]: HTMLElement;

を

    [x: string]: Function;

に直さないとコンパイルできなかった。


`module $.fn`で定義した場合は

    /// <reference path="jquery.d.ts" />
    
    interface JQuery {
        button(option: ButtonSettings): JQuery;
    }
    
    interface ButtonSettings {
        loadingText?: string;
    }
    
    module $.fn {
        class Button {
    	public $element: JQuery;
    	public options: ButtonSettings;
    
    	constructor (element: HTMLElement, options: ButtonSettings) {
    	    this.$element = $(element);
    	    this.options = <ButtonSettings> $.extend({}, $.fn.button.defaults, options);
    	}
    
    	setState(state: string): void {
    	    var d = 'disabled'
    	    , $el = this.$element
    	    , data = $el.data()
    	    , val = $el.is('input') ? 'val' : 'html';
    	    
    	    state = state + 'Text';
    	    data.resetText || $el.data('resetText', $el[val]());
    
    	    $el[val](data[state] || this.options[state]);
    
    	    // push to event loop to allow forms to submit
    	    setTimeout(() => {
    		state == 'loadingText' ? 
    		    $el.addClass(d).attr(d, d) :
    		    $el.removeClass(d).removeAttr(d);
    	    }, 0);
    	}
    
    	toggle(): void {
    	    var $parent = this.$element.closest('[data-toggle="buttons-radio"]');
    	    $parent && $parent.find('.active').removeClass('active');
    	    this.$element.toggleClass('active');
    	}
        }
    
        /* BUTTON PLUGIN DEFINITION
         * ======================== */
        export function button(option: ButtonSettings) {
    	return this.each(function() {
    	    var $this = $(this)
    	    , data = $this.data('button')
    	    , options = typeof option == 'object' && option;
    	    
    	    if(!data) {
    		$this.data('button', (data = new Button(this, options)));
    	    }
    	    if (option == 'toggle') {
    		data.toggle();
    	    } else if (option) {
    		data.setState(option);
    	    }
    	});
        }
    }

でコンパイル結果が

    var $;
    (function ($) {
        (function (fn) {
            var Button = (function () {
                function Button(element, options) {
                    this.$element = $(element);
                    this.options = $.extend({
                    }, $.fn.button.defaults, options);
                }
                Button.prototype.setState = function (state) {
                    var d = 'disabled';
                    var $el = this.$element;
                    var data = $el.data();
                    var val = $el.is('input') ? 'val' : 'html';
    
                    state = state + 'Text';
                    data.resetText || $el.data('resetText', $el[val]());
                    $el[val](data[state] || this.options[state]);
                    setTimeout(function () {
                        state == 'loadingText' ? $el.addClass(d).attr(d, d) : $el.removeClass(d).removeAttr(d);
                    }, 0);
                };
                Button.prototype.toggle = function () {
                    var $parent = this.$element.closest('[data-toggle="buttons-radio"]');
                    $parent && $parent.find('.active').removeClass('active');
                    this.$element.toggleClass('active');
                };
                return Button;
            })();        
            function button(option) {
                return this.each(function () {
                    var $this = $(this);
                    var data = $this.data('button');
                    var options = typeof option == 'object' && option;
    
                    if(!data) {
                        $this.data('button', (data = new Button(this, options)));
                    }
                    if(option == 'toggle') {
                        data.toggle();
                    } else {
                        if(option) {
                            data.setState(option);
                        }
                    }
                });
            }
            fn.button = button;
        })($.fn || ($.fn = {}));
        var fn = $.fn;
    
    })($ || ($ = {}));

になる。どっちが良いかな？

  [1]: http://typescript.codeplex.com/discussions/398401
  [2]: https://github.com/twitter/bootstrap/blob/master/js/bootstrap-button.js
