Prototypify

Documentation

The Package is designed permit safe use of the Prototype javascript framework along side arbitrary 3rd party scripts (i.e, advertisements). The Prototype package adds many utility functions to the native javascript Array, String, and Number classes. Due to some peculiarities of the javascript language, these additions will show up when ever a script tries to iterate (using the "for x in l" syntax) over the values in any instance of an Array (or over the characters in a String). This may have dire consequences for an unsuspecting script.

One solution would be to simply discard the Prototype package, but, in addition to the loss of a powerful set of tools, this would also eliminate other packages like Script.acoul.us and Moo.fx. Disposing of the third party code is also undesirable, because it's often what's paying the bills.

Prototypify solves this dilemma by removing prototype functions by default. It then allows the programmer to proxy the functions that are expecting Prototype functionality so that the functions are restored prior to execution and removed again when execution completes.

Installing

To use Prototypify, load prototypify.js immediately after loading Prototype: <script type="text/javascript" language="javascript" src="prototype-1.4.0.js"></script> <script type="text/javascript" language="javascript" src="prototypify.js"></script> This will remove the prototype functions so that 3rd party or legacy code will run correctly. Note that, after loading the Prototypify package, a good deal of Prototype functionality will still work (everything that does not rely on array methods). You'll need to examine the Prototype code to determine which functions will work and which won't.

Prototypify.proxy

If you would like to use the prototype functionality (or call some code that does) you must proxy your function. For a single function you use the Prototype.proxy function. The following produces and calls a proxied function: function unproxiedListToCommaSeparatedString( list ) { var out = '' list.each( function(x,i) { out += ( i == 0 ? x : ',' + x ) } ); return out; } listToCommaSeparatedString = Prototypify.proxy( unproxiedListToCommaSeparatedString ); alert( listToCommaSeparatedString(['apples','bananas','oranges']); You may simplify this to: listToCommaSeparatedString = Prototypify.proxy( function( list ) { var out = '' list.each( function(x,i) { out += ( i == 0 ? x : ',' + x ) } ); return out; } ); alert( listToCommaSeparatedString(['apples','bananas','oranges']);

Prototypify.instrument

Prototypify's instrument function proxies an entire class. The nice thing about this function is that it not only proxies each method within the class, but does so for every instance of the class created thereafter. To use it, simply call Prototypify.instrument on the class constructor function. The following code does essentially the same thing as the last example in an object-oriented fashion. FruitLister = Class.create(); FruitLister.prototype = { init:function( fruit ) { this.fruit = fruit; }, listFruit = function() { var out = '' this.fruit.each( function(x,i) { out += ( i == 0 ? x : ',' + x ) } ); return out; } ); } Prototypify.instrument( FruitLister ); var lister = new FruitLister( ['apples','bananas','oranges'] ); alert( lister.listFruit() ); Note that the instrument method does not proxy super classes, so if FruitLister extends AbstractLister you will need to call Prototypify.instrument on both classes to get all of FruitLister's methods.

Prototypify.instrumentStatic

Prototypify's instrumentStatic function works similarly to the instrument function but works on functions added to the constructor function itself (static functions), rather than functions added to the class's prototype (instance methods). You use it as follows: FruitLister = Class.create(); FruitLister.listFruit = function( fruit ) { var out = '' fruit.each( function(x,i) { out += ( i == 0 ? x : ',' + x ) } ); return out; } Prototypify.instrumentStatic( FruitLister ); alert( FruitLister.listFruit(['apples','bananas','oranges']) );

proxyArguments

The proxy, instrument, and instrumentStatic functions all support an optional 'proxyArguments' parameter. If this parameter is set to true, all callback functions passed as parameters to the proxied function or methods will be automatically proxied as well. For example: Prototypify.instrumentStatic(Event, true); // note the second parameter var fruit = ['apples','bananas','oranges']; Event.observe( 'listButton', 'click', function() { var out = '' fruit.each( function(x,i) { out += ( i == 0 ? x : ',' + x ) } ); alert( out ); } ); will list the fruit when 'listButton' is pressed. This is really just a convenience, since the same effect could be achieved by putting a Prototypify.proxy call around the callback function.

Tests

Test Array, String, and Number objects contain no prototype functions

This test calls the function displayPrototypeValues that iterates through an array, a string, and a number to demostrate that additional properties added by the Prototype package have been removed. There should be no values displayed in the text area.

Test Prototypify.proxy method

This test uses the Prototypify.proxy function to create a proxy of the displayPrototypeValues function called proxiedDisplayPrototypeValues. There should be many function values under each of the three object types.

Test nested functions

This test calls proxiedDisplayTransportationAndFruit which in turn calls proxiedDisplayTransportation. Both of these methods use Prototype's Array.each method to iterate over an array. This confirms that the cleanup (removal of prototype functions) only happens when the outermost nested function exits. After calling these functions, the test calls displayPrototypeValues to confirm the cleanup has happened. You should see a Transportation and Fruit message, a Transportation message, some vehicle types, A Transportation and Fruit message, some fruit types, and then the output of test 1.

Test class instrumentation

This test creates an instance of the Fruit class and calls methods on the instance that use the Array.each method. The Fruit class has had all its methods proxied using the Prototypify.instrument function. This is testing that after instrumenting a class, all instances of that class will have their methods proxied (and thus able to use all prototype functionality). You should see the output of two methods.

Test argument instrumentation

This test first sets an event handler on the test button using Prototype's Event.observe function. Clicking on the button then executes displayPrototypeValues in the textarea. The Event class has been prototypified using the Prototypify.instrument function with the optional proxyArguments parameter set to true. The callback function passed to Event.observe has not been prototyped, so the presence of prototype functions in the text area indicates that the proxyArguments has caused the callback to be prototypified.


Author: Alex Cruikshank
07.27.2006


License

Copyright (c) 2006 CNET Networks, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.