dojo.provide("dojox.lang.observable"); // Used to create a wrapper object with monitored reads and writes // dojo.experimental("dojox.lang.observable"); // IMPORTANT DISCLAIMER: // This is experimental and based on hideous hacks. // There are severe limitations on the ability of wrapper objects: // Only properties that have vbscript-legal names are accessible (similar to JavaScript, but they can't start with an underscore). // The wrapper objects are not expando in IE, because they are built // from VBScript objects. This means you can't add new properties after an object is created. // The wrapper objects can not be used a prototype for other objects. // Only properties with primitive values can be wrapped. // This has performance implications as well. dojox.lang.observable = function(/*Object*/wrapped,/*function*/onRead,/*function*/onWrite,/*function*/onInvoke){ // summary: // Creates a wrapper object, which can be observed. The wrapper object // is a proxy to the wrapped object. If you will be making multiple wrapper // objects with the same set of listeners, it is recommended that you // use makeObservable, as it is more memory efficient. // // wrapped: // The object to be wrapped and monitored for property access and modification // // onRead: // See dojox.lang.makeObservable.onRead // onWrite: // See dojox.lang.makeObservable.onWrite // onInvoke: // See dojox.lang.makeObservable.onInvoke return dojox.lang.makeObservable(onRead,onWrite,onInvoke)(wrapped); } dojox.lang.makeObservable = function(/*function*/onRead,/*function*/onWrite,/*function*/onInvoke,/*Object*/hiddenFunctions){ // summary: // Creates and returns an observable creator function. All the objects that // are created with the returned constructor will use the provided onRead and // onWrite listeners. // The created constructor should be called with a single argument, // the object that will be wrapped to be observed. The constructor will // return the wrapper object. // // onRead: // This is called whenever one of the wrapper objects created // from the constructor has a property that is accessed. onRead // will be called with two arguments, the first being the wrapped object, // and the second is the name of property that is being accessed. // The value that onRead returns will be used as the value returned // by the property access // // onWrite: // This is called whenever one of the wrapper objects created // from the constructor has a property that is modified. onWrite // will be called with three arguments, the first being the wrapped object, // the second is the name of property that is being modified, and the // third is the value that is being set on the property. // // onInvoke: // This is called when a method on the object is invoked. The first // argument is the wrapper object, the second is the original wrapped object, // the third is the method name, and the fourth is the arguments. // // hiddenFunctions: // allows you to define functions that should be delegated // but may not be enumerable on the wrapped objects, so they must be // explicitly included // // example: // The following could be used to create a wrapper that would // prevent functions from being accessed on an object: // | function onRead(obj,prop){ // | return typeof obj[prop] == 'function' ? null : obj[prop]; // | } // | var observable = dojox.lang.makeObservable(onRead,onWrite); // | var obj = {foo:1,bar:function(){}}; // | obj = observable(obj); // | obj.foo -> 1 // | obj.bar -> null // hiddenFunctions = hiddenFunctions || {}; onInvoke = onInvoke || function(scope,obj,method,args){ // default implementation for onInvoke, just passes the call through return obj[method].apply(scope,args); }; function makeInvoker(scope,wrapped,i){ return function(){ // this is function used for all methods in the wrapper object return onInvoke(scope,wrapped,i,arguments); }; } if(dojox.lang.lettableWin){ // create the vb class var factory = dojox.lang.makeObservable; factory.inc = (factory.inc || 0) + 1; // create globals for the getters and setters so they can be accessed from the vbscript var getName = "gettable_"+factory.inc; dojox.lang.lettableWin[getName] = onRead; var setName = "settable_"+factory.inc; dojox.lang.lettableWin[setName] = onWrite; var cache = {}; return function(wrapped){ if(wrapped.__observable){ // if it already has an observable, use that return wrapped.__observable; } if(wrapped.data__){ throw new Error("Can wrap an object that is already wrapped"); } // create the class var props = [], i, l; for(i in hiddenFunctions){ props.push(i); } var vbReservedWords = {type:1,event:1}; // find the unique signature for the class so we can reuse it if possible for(i in wrapped){ if(i.match(/^[a-zA-Z][\w\$_]*$/) && !(i in hiddenFunctions) && !(i in vbReservedWords)){ //can only do properties with valid vb names/tokens and primitive values props.push(i); } } var signature = props.join(","); var prop,clazz = cache[signature]; if(!clazz){ var tname = "dj_lettable_"+(factory.inc++); var gtname = tname+"_dj_getter"; var cParts = [ "Class "+tname, " Public data__" // this our reference to the original object ]; for(i=0, l=props.length; i"); frame = document.getElementById("dj_vb_eval_frame"); } frame.style.display="none"; var doc = frame.contentWindow.document; dojox.lang.lettableWin = frame.contentWindow; doc.write('' + '' + 'vb-eval'); doc.close(); }else{ throw new Error("This browser does not support getters and setters"); } } dojox.lang.ReadOnlyProxy = // summary: // Provides a read only proxy to another object, this can be // very useful in object-capability systems // example: // | var obj = {foo:"bar"}; // | var readonlyObj = dojox.lang.ReadOnlyProxy(obj); // | readonlyObj.foo = "test" // throws an error // | obj.foo = "new bar"; // | readonlyObj.foo -> returns "new bar", always reflects the current value of the original (it is not just a copy) dojox.lang.makeObservable(function(obj,i){ return obj[i]; },function(obj,i,value){ // just ignore, exceptions don't seem to propagate through the VB stack. });