Hailwood
Hailwood

Reputation: 92581

call to function inside jQuery-ui plugin has wrong context?

I have a jQuery UI function, inside the _create function I have var self = this so I can access the plugin context from other functions in the plugin.

I also have this code inside the _create function:

//setup sortable handler
if (self.options.sortable !== false) {

     var soptions = {
         items:'.tagit-choice',
         containment:'parent',
         start:function (event, ui) {
             console.log('sorting started', event, ui);
             self._sortable.tag = $(ui.item);
             self._sortable.origIndex = self._sortable.tag.index();
         },
         update:function (event, ui) {
             console.log('sorting started', event, ui);
             self._sortable.newIndex = self._sortable.tag.index();
             self._moveTag(self._sortable.origIndex, self._sortable.newIndex);
             console.log('moved tag', self._sortable.origIndex, 'to', self._sortable.newIndex);

         }
     };

     if (self.options.sortable == 'handle') {
         $('.tagit-choice', this.element).prepend('<a class="ui-icon ui-icon-grip-dotted-vertical" style="float:left"></a>');
         soptions.handle = 'a.ui-icon';
         soptions.cursor = 'move';
     }

     self.element.sortable(soptions);
 }

To setup sortable inside the plugin.

It has a call to self._moveTag where inside I am calling console.log(self) which I expect self to be the plugin instance, but instead it is the DOMWindow. Why is this?

_moveTag: function (old_index, new_index) {
    console.log(self);
    self.tagsArray.splice(new_index, 0, self.tagsArray.splice(old_index, 1)[0]);
    for (var ind in self.tagsArray)
        self.tagsArray[ind].index = ind;

    if(self.options.select){
        $('option:eq(' + old_index + ')', this.select).insertBefore($('option:eq(' + new_index + ')', this.select));
    }
},


Hopefully this example is clearer:

(function($) {

    $.widget("ui.widgetName", {

        options: {
            sortable: true
        },

        _sortable: {},

        _create: function() {

            var self = this;

            if (self.options.sortable !== false) {
                self.element.sortable({
                    items: '.tagit-choice',
                    containment: 'parent',
                    start: function(event, ui) {
                        self._sortable.tag = $(ui.item);
                        self._sortable.origIndex = self._sortable.tag.index();
                    },
                    update: function(event, ui) {
                        self._sortable.newIndex = self._sortable.tag.index();
                        self._moveTag(self._sortable.origIndex, self._sortable.newIndex);
                    }
                });
            }
        },

        _moveTag: function(old_index, new_index) {
            console.log(self); // <-- this returns DOMWindow?!?!?
        }
    });
})(jQuery);​

Upvotes: 1

Views: 1417

Answers (1)

mu is too short
mu is too short

Reputation: 434615

Why would you expect self to be defined at all inside _moveTag? Apparently someone is leaking a self into window.self and you are picking up the bad self inside _moveTag and that self happens to be window; given something like this:

(function($) {
    $.widget('ui.pancakes', {
        _create: function() {
            var self = this;
            self._moveTag();
        },
        _moveTag: function() {
            // ...
        }
    });
})(jQuery);

The self inside _create is the widget you want your self to be but that self is not in scope for _moveTag, _moveTag is picking up the leaked window.self from somewhere else. So you can use self inside _create and any functions defined within _create and you get what expect; but _moveTag is defined as a widget method in the usual way (rather than inside _create's scope) so it picks up the bad outer self and that self is window.

I think your private _moveTags method is guilty of self abuse. You should be using this inside widget methods and make sure the caller uses the correct context; jQuery-UI will call public methods with the right context for you and you should be able to handle private methods (such as _moveTag) quite easily on your own.

Here's a quick demo for illustration: http://jsfiddle.net/ambiguous/UhE3F/


A little investigation reveals a shocking fact: window is supposed to have a self property:

window.self

Returns an object reference to the window object.

and it turns out that the var self = this convention is one of the worst ideas ever or at least using the name self for this is an awful idea. What a lovely source of confusing bugs this is.

Upvotes: 2

Related Questions