Hi all.
Something Fabien and myself have created in the current version of the framework is the existence of a 'mutate' function in the superclasses for views, models, collections and routers. This currently takes the following form:
mutate: function(proto){
var original = _.extend({}, this.prototype);
this.prototype._original = function(method, arguments) {
[... method validation and workarounds for ie8 ...]
original[method].apply(this, arguments);
}
_.extend(this.prototype, proto);
}
Using this function is quite simple.
For example, imagine you are creating a plugin which requires some changes to the ComponentView object, you could call ComponentView.mutate, and add in any extra functionality you need. You can even create a new version of an existing function, render, for example, and then call this._original("render").
This works very well when you are mutating an object once. However, once you try to mutate an already-mutated object, you run into the classic JavaScript inheritance problem where the scope of "this" doesn't change as you move up the inheritance chain. Meaning you end up in an infinite loop in the first mutated version of a function. Please bear with me on a fairly lengthy example:
Eg.
original:
original {
init: function() {
// initialise things
}
}
mutation1:
original.mutate({
init: function() {
// do some new stuff
this._original("init");
}
})
mutation2:
original.mutate({
init: function() {
// do some different new stuff
this._original("init");
}
})
If you were then to call original.init(), the order of execution would be:
- mutation2's init get's called
- this does "some different new stuff", then calls it's original (mutation1) init function
- this does "some new stuff", then calls 'this._original("init")', but 'this' is still locked into the scope of the original caller, mutation2, meaning that this._original is referring to mutation1.
- It then calls mutation1's init function infinitely.
Obviously this is going to break the minute that more than one plugin needs to mutate something, which I envisage will happen a lot, so we need a more robust way to be able to override methods but then call the original should we need to.
A rough brainstorm of solution ideas:
- Have some sort of mutations array in each of the core objects which stores the 'extended' versions of the functions, and calls all of those versions, as well as its own version, when the function is called.
- What happens if you don't want to call the original?
- What order do you call the functions
- Trigger an event every time a function is called using a standard naming convention, e.g. "[functionName]Called" and have any subclass of that object listen for events and call their own versions where appropriate.
- What happens if you don't want to call the original?
- What if you want to call the mutated function before the original?
- I expect it'd get very messy but you could call two functions, one at the start and one at the end of each core function call, [functionName]Started and [functionName]Ended, or whatever.
- Pass two parameters into the "_original" function, the first as the name of the function, the second as the prototype for the original object to call it on... I haven't given that one much thought but there might be legs in it - it's difficult to see a situation where you wouldn't need "this".
It's a tricky one certainly. Any help, guidance or advice would be appreciated.
Kev