Question

I'm using mootools and working on popup menu:

document.getElement('.cart a').toggle(
  function() {
   this.getParent('div').removeClass('open');
   this.getNext('.cart_contents').hide();               
  },
  function() {
    this.getParent('div').addClass('open');
    this.getNext('.cart_contents').show();
  })
);

The toggle function implementation:

Element.implement({
    toggle: function(fn1,fn2){
        this.store('toggled',false);
        return this.addEvent('click',function(event){
            event.stop();
             if(this.retrieve('toggled')){
                 fn1.call(this);
             }else{
                 fn2.call(this);
             }
            this.store('toggled',!(this.retrieve('toggled')));
        });
    }
});

The outer click function:

Element.Events.outerClick = {
    base : 'click',    
    condition : function(event){
        event.stopPropagation();
        return false;
    },
    onAdd : function(fn){
        this.getDocument().addEvent('click', fn);
    },
    onRemove : function(fn){
        this.getDocument().removeEvent('click', fn);
    }
};

I would like to add outerclick event to close my popup menu:

document.getElement('.cart a').toggle(
      function() {
       this.getParent('div').removeClass('open');
       this.getNext('.cart_contents').hide();               
      },
      function() {
        this.getParent('div').addClass('open');
        this.getNext('.cart_contents').show();
      }).addEvent('outerClick',function() {
        // Error: Wrong code below 
        // Uncaught TypeError: Object #<HTMLDocument> has no method 'getParent' 
        this.getParent('div').removeClass('open');
        this.getNext('.cart_contents').hide();  
});

Error: Uncaught TypeError: Object # has no method 'getParent' Thanks.

Was it helpful?

Solution

This is a problem (or a deficiency) to do with the outerClick implementation by Darren. It's not a bug - it's built to work as fast as possible. you just need to understand what it does when it binds the actual event to document.

Element.Events.outerClick = {
    base : 'click',    
    condition : function(event){
        event.stopPropagation();
        return false;
    },
    onAdd : function(fn){
        // the event actually gets added to document!
        // hence scope in fn will be document as delegator.
        this.getDocument().addEvent('click', fn);
    },
    onRemove : function(fn){
        this.getDocument().removeEvent('click', fn);
    }
};

So the functions will run with context this === document.

One way to fix it is to bind the callback specifically to the element. Problem is, removing it won't work as .bind will return a unique new function that won't match the same function again.

(function(){
    var Element = this.Element,
        Elements = this.Elements;

    [Element, Elements].invoke('implement', {
        toggle: function(){
            var args = Array.prototype.slice.call(arguments), 
                count = args.length-1,
                // start at 0
                index = 0;

            return this.addEvent('click', function(){
                var fn = args[index];
                typeof fn === 'function' && fn.apply(this, arguments);
                // loop args.
                index = count > index ? index+1 : 0;
            });
        }
    });

    Element.Events.outerClick = {
        base : 'click',    
        condition : function(event){
            event.stopPropagation();
            return false;
        },
        onAdd : function(fn){
            this.getDocument().addEvent('click', fn.bind(this));
        },
        onRemove : function(fn){
            // WARNING: fn.bind(this) !== fn.bind(this) so the following
            // will not work. you need to keep track of bound fns or 
            // do it upstream before you add the event.
            this.getDocument().removeEvent('click', fn.bind(this));
        }
    };
}());

document.id('myp').toggle(
    function(e){
        console.log(e); // event.
        this.set('html', 'new text');
    },
    function(){
        console.log(this); // element
        this.set('html', 'old text');
    },
    function(){
        this.set("html", "function 3!");
    }
).addEvent('outerClick', function(e){
     console.log(this, e); 
});

http://jsfiddle.net/dimitar/UZRx5/ - this will work for now - depends if you have a destructor that removes it.

Another approach is when you add the event to do so:

var el = document.getElement('.cart a');
el.addEvent('outerClick', function(){ el.getParent(); // etc });
// or bind it 
el.addEvent('outerClick', function(){ this.getParent(); }.bind(el));

it can then be removes if you save a ref

var el = document.getElement('.cart a'),
    bound = function(){
        this.getParent('div');
    }.bind(el);

el.addEvent('outerClick', bound);

// later
el.removeEvent('outerClick', bound);

that's about it. an alternative outerClick is here: https://github.com/yearofmoo/MooTools-Event.outerClick/blob/master/Source/Event.outerClick.js - not tried it but looks like it tries to do the right thing by changing scope to element and keeping a reference of the memoized functions - though multiple events will likely cause an issue, needs event ids to identify the precise function to remove. Also, it looks quite heavy - considering the event is on document, you want to NOT do too much logic on each click that bubbles to that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top