开发者

How to reuse and override inherited javascript function

I have a class for mouse events. I'm using dojo b/c I like its OO approach

dojo.declare("MouseObject", null, {
  constructor: function(){},
  onclick : function(){...},
  _onClick : function(){...}
});

_onCl开发者_开发百科ick() listens for window generated mouse press/release events and determines if a click has occurred. If it has, onClick() is called. onClick() carries out functionality common to all possible clicks, so it needs to be called every time the user clicks.

sometimes the user may want to extend the functionality of onClick() Is there anyway to incorporate the original functionality without copy pasting it? Two ways I can think of, neither of which I like, are

dojo.declare("MouseObjectChild", [MouseObject], {
  constructor: function(){},
  onclick : function(){this.inherited(arguments);...}
});

which has the drawback that I have to keep creating new classes I don't really need, and two adding an intermediate function

dojo.declare("MouseObject", null, {
      constructor: function(){},
      onclick : function(){this._onClick()...}, //child onClick
      _onClick : function(){...}, //parent onClick
      __onClick : function(){...} //listener
    });

but this does not look like good code


wrt the bounty, I want expert advice on how best to address the program-user interaction. If the program provides a crucial function, such as draw a circle on the canvas, then how does the user best interact with that. What if the user wants to draw a triangle behind the circle? A square in front of the circle? Then the program would have to provide pre and post methods like so:

beforeDraw();
draw();
afterDraw();

Is this considered good design? Should I instead put function pointers in an array and call them in order?


As suggested by the "should I put functions in their own array and call them in order" and "build your own event system" ideas, you might want to have a look at the dojox.lang.aspect library that comes bundled in the extended standard library. It should be basically a more advanced version of the "dojo.connect" solution.

dojo.require('dojox.lang.aspect');
var aop = dojox.lang.aspect;

function log(msg){ return function(){
    console.log(msg);
};}

var obj = {
   draw : log('draw circle');
};

obj.foo();
//prints
//  draw circle

aop.advise(obj, 'draw', [
    {before: log('draw triangle')},
    {after: log('draw a square')},
    {after: log('done!')}
]);

obj.foo();
//prints
//  draw a triangle
//  draw a circle
//  draw a square
//  done!


If someone just want to run some code when onclick() is called, he can just connect to it. Note that you also can connect to function calls not only events with dojo.connect().

dojo.connect(obj, 'onclick', function(){
  // Some extra code that runs when obj.onclick() is called
});

If he want to create a new class and extend the feature calling this.inherited(arguments); is the best way of doing it.


Why not create your own simple event model?

// store our callbacks
var hooks = {};

// add a callback
var hook = function(name,fn){
  if(typeof hooks[name] === 'undefined'){
    hooks[name] = [];
  }
  hooks[name].push(fn);
  return true;
};

// remove a callback
var unhook = function(name,fn){
  if(typeof hooks[name] === 'undefined'){ return false }
  var i, u = hooks[name].length;
  for(i=0;i<u;i++){
    if(hooks[name][i] === fn){
      hooks[name].splice(i,1);
      return true;
    }
  }
};

// trigger a callback
var callHook = function(name, data){
   if(typeof hooks[name] === 'undefined'){ return false }
   var i, u = hooks[name].length;
   for(i=0;i<u;i++){
     hooks[name][i](data);
   }
};

Then later on (example passes the context):

hook('beforeDraw',function(ctx){ 
  console.log('I am called prior to drawing');
});

hook('afterDraw',function(ctx){
  console.log('I am called after drawing');
});

var drawCircle = function(){
  callHook('beforeDraw', ctx);
  // drawing code here
  callHook('afterDraw', ctx);
};

Alternatively, if you wanted to pass scope to the callbacks you could change the callHook method to accept a scope argument, and then use call or apply and pass in this or some other object:

var callHook = function(name, scope, data){
   if(typeof hooks[name] === 'undefined'){ return false }
   var i, u = hooks[name].length;
   for(i=0;i<u;i++){
     hooks[name][i].call(scope, data);
   }
};

// later
var drawCircle = function(){
   callHook('beforeDraw', this, ctx);
   // or
   callHook('beforeDraw', someObject, ctx);
};

Depending on how & where you define the event model functions you can scope the functionality to specific instances of an object, every instance of an object, globally, and so on depending on how you want to assign, change and trigger those callbacks.


You have several options.
1. The Promise pattern is gaining popularity (although there are competing specs): http://wiki.commonjs.org/wiki/Promises/B. jQuery has an implementation close to B and it's well documented. It has the most options, it's robust, but not easy to wrap your head around at first.

  1. AOP-style programming allows you to fiddle with before and after advices. However, for that to work you need to stop putting everything in anonymous functions and decide whether you apply advices after instantiation or plan for their use by baking the capability into the class. This takes more planning in code structure but offers a ton of utility.

for example:

var MYAPP = {
  onClick: function () {
    //do something...
  }
};

dojo.declare("MouseObject", null, {
  constructor: function(){},
  onclick : function(){...},
  _onClick : function(){
    return MYAPP.onClick.apply(this, arguments);
  }
});

Since my class calls MYAPP every time (yes there's a scoping charge for this), I can manipulate the function hanging off MYAPP and anyone using it will benefit.

MYAPP.after("onClick", function () {...}); //lots of ways to write .after, just google some AOP js or use a library

The other side:

var MYAPP = {
  onClick: function () {
    //do something...
  }
};

dojo.declare("MouseObject", null, {
  constructor: function(){},
  onclick : function(){...},
  _onClick : MYAPP.onClick
});

Now I can't change MYAPP and see the changes in MouseObject or its instances because the function has passed by reference to MouseObject. I can only change the instances with AOP (or all future instances by changing the prototype on the class).

Something like:

var m = new MouseObject();
m.after("onClick", function () {...}); //you need to add .after to your class or use an apply pattern from library code

It just depends on how you think it would be used: certain instances, all future instances (until turned off), or everything.

  1. An "event queue" was suggested. This is more like an Active Object Pattern where you use an array of objects and a queue manager to manage tasks. This is the easiest to implement but has several drawbacks where the queue can get "jammed up" and no longer processes. I wrote a library for this type of management called proto-q (http://code.google.com/p/proto-q/), based on an iPhone app I did. It worked great for me, but it's not for everyone's application.


__fn gets messier and messier. That, my friend, is the road to hell.

It is symptomatic of using the Magic Pushbutton approach. You should consider moving out to an actual inheritance based class model using many of the OO js libraries around. Then you would be able to call $base.fn() or $super.fn()


If I understand your question, then you want to modify functionality for specific cases. If this is the case, and you don't want any overhead, then I'd rely on overriding methods upon instantiation.

Override Model

Your Class:

dojo.declare("some.path.DrawingThing", [dijit._Widget], {
    draw: function () {
        this.inherited(arguments); // Call super class methods
        // override this method to do specialized things that're specific 
        // to an instance.
    }
});

Client Instantiation:

var drawer = new some.path.DrawingThing({
    draw: function () {
        beforeDraw();
        // Access the pre-existing functionality in a somewhat dirty way.
        this.constructor.prototype.draw.apply(this, arguments);
        afterDraw();
    }
});

If you follow this model there's less code, but it's not as clean. The other option is to write a real API, see below.

Event Model

If you want a true API, then I'd provide events:

// Dojo 1.6 style declaration.
dojo.declare("some.path.DrawingThing", [dijit._Widget], {
    onBeforeDraw: function(){
        // Hook into this method to do something before draw
    },
    onDraw: function(){
        // Hook into this method to do something after draw
    },

    draw: function () {
        // This method is actually called when the draw even fires
        this.onBeforeDraw();
        this.inherited(arguments); // Call super class methods
        this.onDraw();
    }
});
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜