19

Ok so first off here is my very basic jQuery plugin

(function ($){
    $.fn.greenify = function (options) {
        var settings = $.extend({
            // These are the defaults
            color: '#556b2f',
            backgroundColor: 'white'
        }, options);
}(jQuery));

$('a').greenify({
    color: 'orange'
}).showLinkLocation();

Basically all this does is change the text color and background-color with the provided element. Now what I am trying to do is convert this simple plugin into TypeScript

I have tried a few things and the closest I got is this.

TypeScript

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

module Coloring
{
    interface IGreenifyOptions
    {
        color: string;
        backgroundColor: string;
    }

    export class GreenifyOptions implements IGreenifyOptions
    {
        // Fields
        color: string;
        backgroundColor: string;

        constructor(color: string, backgroundColor: string)
        {
            this.color = color;
            this.backgroundColor = backgroundColor;
        }
    }

    export class Greenify
    {
        // Fields
        element: JQuery;
        options: GreenifyOptions;

        constructor(element: JQuery, options: GreenifyOptions)
        {
            this.element = element;
            this.options = options;

            this.OnCreate();
        }

        OnCreate()
        {
            this.element.css('color', this.options.color).css('background-color', this.options.backgroundColor);
        }
    }
}

JQuery which calls it

$(function ()
{
    var options: Coloring.GreenifyOptions = new Coloring.GreenifyOptions('#0F0', '#000');

    var $elems = $('a');
    $elems.each(function()
    {
        var result = new Coloring.Greenify($(this), options)
    });
});

However I don't want to provide the element like the above new Coloring.Greenify($(this), options), I basically want to do something like this

$('a').Coloring.Greenify(options);

or

$('a').Coloring.Greenify(Coloring.GreenifyOptions()
{
    color: '#F0F',
    backgroundColor: '#FFF'
});

But I can't seem to figure how to tell TypeScript that the element it is attached to already is the Jquery element. Could anyone shine some light on this to help me out.

P.S. the above I have works fine, I just want to change the calling code.


Update

This is what I have at the moment and it works

TypeScript

interface JQuery
{
    Greenify();
    Greenify(options: Coloring.GreenifyOptions);
}

(function ($)
{
    $.fn.Greenify = function (options)
    {
        return new Coloring.Greenify(this, options);
    }
})(jQuery);

jQuery

var $elems = $('a').Greenify(options);

However it means I have to provide options and if I do the constructor without options I get options is undefined. The answer I have ticked as correct is correct for the question I have asked. However just keep in mind that the answer provided required you provide options into your typescript constructor, I am going to see on how to have default options and then override them in the constructor but this is a different question :)


Update 2

Just to let everyone know I have found a way to provide options to your plugin or not.

What you can do is this

TypeScript class

export class Greenify
    {
        // Default Options
        static defaultOptions: IGreenifyOptions =
        {
            color: '#F00',
            backgroundColor: '#00F'
        };

        // Fields
        element: JQuery;
        options: GreenifyOptions;

        constructor(element: JQuery, options: GreenifyOptions)
        {
            // Merge options
            var mergedOptions: GreenifyOptions = $.extend(Greenify.defaultOptions, options);
            this.options = mergedOptions;
            this.element = element;

            this.OnCreate();
        }

        OnCreate()
        {
            this.element.css('color', this.options.color).css('background-color', this.options.backgroundColor);
        }

    }

TypeScript Interface

interface JQuery
{
    Greenofy();
    Greenify(obj?: any);
    Greenify(options?: Coloring.GreenifyOptions);
}

(function ($)
{
    $.fn.Greenify = function (options)
    {
        return new Coloring.Greenify(this, options);
    }
})(jQuery);

jQuery code to call the plugin with one optional option

$(function ()
{
    var $elems = $('a').Greenify(<Coloring.GreenifyOptions>{
        color: '#00F'
    });
});

So not the output will make the anchors background color '#00F' which is the default and the option I have provided will make the anchor text color '#00F'.

I hope this helps anyone else that is having the same problem as me :)

Tieson T.
  • 20,774
  • 6
  • 77
  • 92
Canvas
  • 5,779
  • 9
  • 55
  • 98

2 Answers2

10

Creating jQuery plugins along with TypeScript can get a bit messy. I personally prefer keeping the jQuery plugin chaining syntax, mostly for consistency and maintainability .

So, after the module declaration you can basically wrap around your implementation extending the jQuery prototype as:

module Coloring {
  interface IGreenifyOptions {
    color: string;
    backgroundColor: string;
  }

  export class GreenifyOptions implements IGreenifyOptions {
    // Fields
    color: string;
    backgroundColor: string;

    constructor(color: string, backgroundColor: string) {
      this.color = color;
      this.backgroundColor = backgroundColor;
    }
  }

  export class Greenify {
    // Fields
    element: JQuery;
    options: GreenifyOptions;

    constructor(element: JQuery, options: GreenifyOptions) {
      this.element = element;
      this.options = options;

      this.OnCreate();
    }

    OnCreate() {
      this.element.css('color', this.options.color).css('background-color', this.options.backgroundColor);
    }
  }
}


//jquery plugin wrapper.
;
(function(w, $) {
    //no jQuery around
  if (!$) return false;

  $.fn.extend({
    Coloring: function(opts) {
      //defaults
      var defaults: Coloring.GreenifyOptions = new Coloring.GreenifyOptions('#0F0', '#000');

      //extend the defaults!
      var opts = $.extend({}, defaults, opts)

      return this.each(function() {
        var o = opts;
        var obj = $(this);
        new Coloring.Greenify(obj, o);

      });
    }
  });
})(window, jQuery);

Fetching the plugin as:

$(function() {

  var $a = $('a').Coloring();
  var $div = $('div').Coloring({
    color: '#F0F',
    backgroundColor: '#FFF'
  });
  var $div = $('strong').Coloring({
    color: '#gold',
    backgroundColor: 'pink'
  });
});

Demo

vorillaz
  • 6,098
  • 2
  • 30
  • 46
  • This answer is looking promising however do I have to use the extend? could I not just provide a default constructor to options and then if I pass in options override the options? – Canvas Jan 15 '16 at 14:39
  • @Canvas Sure you can play around and manipulate this sample to fit your needs. Keep in mind that you need to pass an object to the constructor. Here is a relevant quest about mapping objects : http://stackoverflow.com/questions/16793503/initializing-typescript-class-values-from-constructor – vorillaz Jan 15 '16 at 15:00
  • One part of this answer could be improved. The default options could be static (and therefore also modifiable). `$.extend` will modify the first parameter, so you just need to use an empty object as the target like: `var opts = $.extend({}, defaults, opts);`. Also note that original question does not require the overhead of having a `class` for the options. A simple object will do as there are no methods. – iCollect.it Ltd Jan 18 '16 at 10:06
  • @TrueBlueAussie True that, I am updating my answer. Thanks a lot. – vorillaz Jan 18 '16 at 13:38
  • You misunderstand. You only need that correction *if* you move to static default options. It is newing up an object fresh each time (at the moment) so makes no difference :) – iCollect.it Ltd Jan 18 '16 at 14:56
7

You can create and reference your own definitions file greenify.d.ts and add the function like this:

interface Jquery {
     greenify: (options: Coloring.IGreenifyOptions) => void
}

Then you can simple call it like:

$('a').greenify(new GreenifyOptions(...));

or

$('a').greenify({color:'', backgroundColor:''});

Explanation:

At compile time typescript will actually merge all interface definitions.

Side Note:

if you're adamant about having it exactly as $('a').Coloring.Greenify(..) then you'll have to change a lot more:

  • Coloring couldnt be your module's name anymore as you'd have to use it in the interface definition above
  • You would probably have to create a class that has .Greenify as static method
  • Possibly more...

All in all it's probably easier to stick with my initial solution as it its a bit less verbose but that's up to you.

Hope that helps

Update:

To account for the default options you can modify the interface definition to have overrides:

interface Jquery {
     greenify: () => void;
     greenify: (options: Coloring.IGreenifyOptions) => void;
}

interface IGreenifyOptions
{
    color?: string;
    backgroundColor?: string;
}
jsonmurphy
  • 1,600
  • 1
  • 11
  • 19
  • This is what I have at the moment however this means I have to provide options, but what if I wanted to use the default options? – Canvas Jan 15 '16 at 14:41
  • Doesn't the jquery plugin at the top of your question already handle defaults? if those are the defaults you mean then it should be simple enough to add another definition to the interface. See my update – jsonmurphy Jan 15 '16 at 14:50
  • It does, but for example lets say we want to change the background-color but not the color, how would I do that in the constructor? – Canvas Jan 15 '16 at 14:51
  • Oh i see... then its just a matter of setting the options to **optional** by add a `?` to the end of their name. Updated my "update" – jsonmurphy Jan 15 '16 at 14:51
  • I have done that like so `color?: string; backgroundColor?: string;` but when I call `var $elems = $('a').Greenify();` it states that `color` is undefined – Canvas Jan 15 '16 at 14:55