source: Dev/trunk/src/client/dojox/lang/observable.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 9.8 KB
Line 
1dojo.provide("dojox.lang.observable");
2// Used to create a wrapper object with monitored reads and writes
3//
4dojo.experimental("dojox.lang.observable");
5// IMPORTANT DISCLAIMER:
6// This is experimental and based on hideous hacks.
7// There are severe limitations on the ability of wrapper objects:
8// Only properties that have vbscript-legal names are accessible (similar to JavaScript, but they can't start with an underscore).
9// The wrapper objects are not expando in IE, because they are built
10// from VBScript objects. This means you can't add new properties after an object is created.
11// The wrapper objects can not be used a prototype for other objects.
12// Only properties with primitive values can be wrapped.
13// This has performance implications as well.
14dojox.lang.observable = function(/*Object*/wrapped,/*function*/onRead,/*function*/onWrite,/*function*/onInvoke){
15        // summary:
16        //              Creates a wrapper object, which can be observed. The wrapper object
17        //              is a proxy to the wrapped object. If you will be making multiple wrapper
18        //              objects with the same set of listeners, it is recommended that you
19        //              use makeObservable, as it is more memory efficient.
20        // wrapped:
21        //              The object to be wrapped and monitored for property access and modification
22        // onRead:
23        //              See dojox.lang.makeObservable.onRead
24        // onWrite:
25        //              See dojox.lang.makeObservable.onWrite
26        // onInvoke:
27        //              See dojox.lang.makeObservable.onInvoke
28       
29        return dojox.lang.makeObservable(onRead,onWrite,onInvoke)(wrapped);
30};
31dojox.lang.makeObservable = function(/*function*/onRead,/*function*/onWrite,/*function*/onInvoke,/*Object*/hiddenFunctions){
32               
33        // summary:
34        //              Creates and returns an observable creator function. All the objects that
35        //              are created with the returned constructor will use the provided onRead and
36        //              onWrite listeners.
37        //              The created constructor should be called with a single argument,
38        //              the object that will be wrapped to be observed. The constructor will
39        //              return the wrapper object.
40        // onRead:
41        //              This is called whenever one of the wrapper objects created
42        //              from the constructor has a property that is accessed. onRead
43        //              will be called with two arguments, the first being the wrapped object,
44        //              and the second is the name of property that is being accessed.
45        //              The value that onRead returns will be used as the value returned
46        //              by the property access
47        // onWrite:
48        //              This is called whenever one of the wrapper objects created
49        //              from the constructor has a property that is modified. onWrite
50        //              will be called with three arguments, the first being the wrapped object,
51        //              the second is the name of property that is being modified, and the
52        //              third is the value that is being set on the property.
53        // onInvoke:
54        //              This is called when a method on the object is invoked. The first
55        //              argument is the wrapper object, the second is the original wrapped object,
56        //              the third is the method name, and the fourth is the arguments.
57        //
58        // hiddenFunctions:
59        //              allows you to define functions that should be delegated
60        //              but may not be enumerable on the wrapped objects, so they must be
61        //              explicitly included
62        // example:
63        //              The following could be used to create a wrapper that would
64        //              prevent functions from being accessed on an object:
65        //      |       function onRead(obj,prop){
66        //      |               return typeof obj[prop] == 'function' ? null : obj[prop];
67        //      |       }
68        //      |       var observable = dojox.lang.makeObservable(onRead,onWrite);
69        //      |       var obj = {foo:1,bar:function(){}};
70        //      |       obj = observable(obj);
71        //      |       obj.foo -> 1
72        //      |       obj.bar -> null
73
74        hiddenFunctions = hiddenFunctions || {};
75        onInvoke = onInvoke || function(scope,obj,method,args){
76                // default implementation for onInvoke, just passes the call through
77                return obj[method].apply(scope,args);
78        };
79        function makeInvoker(scope,wrapped,i){
80                return function(){
81                        // this is function used for all methods in the wrapper object
82                        return onInvoke(scope,wrapped,i,arguments);
83                };
84        }
85       
86        if(dojox.lang.lettableWin){ // create the vb class
87                var factory = dojox.lang.makeObservable;
88                factory.inc = (factory.inc || 0) + 1;
89                // create globals for the getters and setters so they can be accessed from the vbscript
90                var getName = "gettable_"+factory.inc;
91                dojox.lang.lettableWin[getName] = onRead;
92                var setName = "settable_"+factory.inc;
93                dojox.lang.lettableWin[setName] = onWrite;
94                var cache = {};
95                return function(wrapped){
96                        if(wrapped.__observable){ // if it already has an observable, use that
97                                return wrapped.__observable;
98                        }
99                        if(wrapped.data__){
100                                throw new Error("Can wrap an object that is already wrapped");
101                        }
102                        // create the class
103                        var props = [], i, l;
104                        for(i in hiddenFunctions){
105                                props.push(i);
106                        }
107                        var vbReservedWords = {type:1,event:1};
108                        // find the unique signature for the class so we can reuse it if possible
109                        for(i in wrapped){
110                                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
111                                        props.push(i);
112                                }
113                        }
114                        var signature = props.join(",");
115                        var prop,clazz = cache[signature];
116                        if(!clazz){
117                                var tname = "dj_lettable_"+(factory.inc++);
118                                var gtname = tname+"_dj_getter";
119                                var cParts = [
120                                        "Class "+tname,
121                                        "       Public data__" // this our reference to the original object
122                                ];
123                                for(i=0, l=props.length; i<l; i++){
124                                        prop = props[i];
125                                        var type = typeof wrapped[prop];
126                                        if(type == 'function' || hiddenFunctions[prop]){ // functions must go in regular properties for delegation:/
127                                                cParts.push("  Public " + prop);
128                                        }else if(type != 'object'){ // the getters/setters can only be applied to primitives
129                                                cParts.push(
130                                                        "       Public Property Let "+prop+"(val)",
131                                                        "               Call "+setName+"(me.data__,\""+prop+"\",val)",
132                                                        "       End Property",
133                                                        "       Public Property Get "+prop,
134                                                        "               "+prop+" = "+getName+"(me.data__,\""+prop+"\")",
135                                                        "       End Property");
136                                        }
137                                }
138                                cParts.push("End Class");
139                                cParts.push(
140                                        "Function "+gtname+"()",
141                                        "       Dim tmp",
142                                        "       Set tmp = New "+tname,
143                                        "       Set "+gtname+" = tmp",
144                                        "End Function");
145                                dojox.lang.lettableWin.vbEval(cParts.join("\n"));
146                                       
147                                // Put the new class in the cache
148                                cache[signature] = clazz = function(){
149                                        return dojox.lang.lettableWin.construct(gtname); // the class can't be accessed, only called, so we have to wrap it with a function
150                                };
151                        }
152                        console.log("starting5");
153                        var newObj = clazz();
154                        newObj.data__ = wrapped;
155                        console.log("starting6");
156                        try {
157                                wrapped.__observable = newObj;
158                        } catch(e){ // some objects are not expando
159                        }
160                        for(i = 0,  l = props.length; i < l; i++){
161                                prop = props[i];
162                                try {
163                                var val = wrapped[prop];
164                                }
165                                catch(e){
166                                        console.log("error ",prop,e);
167                                }
168                                if(typeof val == 'function' || hiddenFunctions[prop]){ // we can make a delegate function here
169                                        newObj[prop] = makeInvoker(newObj,wrapped,prop);
170                                }
171                        }
172                        return newObj;
173                };
174        }else{
175                return function(wrapped){ // do it with getters and setters
176                        if(wrapped.__observable){ // if it already has an observable, use that
177                                return wrapped.__observable;
178                        }
179                        var newObj = wrapped instanceof Array ? [] : {};
180                        newObj.data__ = wrapped;
181                        for(var i in wrapped){
182                                if(i.charAt(0) != '_'){
183                                        if(typeof wrapped[i] == 'function'){
184                                                newObj[i] = makeInvoker(newObj,wrapped,i); // TODO: setup getters and setters so we can detect when this changes
185                                        }else if(typeof wrapped[i] != 'object'){
186                                                (function(i){
187                                                        newObj.__defineGetter__(i,function(){
188                                                                return onRead(wrapped,i);
189                                                        });
190                                                        newObj.__defineSetter__(i,function(value){
191                                                                return onWrite(wrapped,i,value);
192                                                        });
193                                                })(i);
194                                        }
195                                }
196                        }
197                        for(i in hiddenFunctions){
198                                newObj[i] = makeInvoker(newObj,wrapped,i);
199                        }
200                        wrapped.__observable = newObj;
201                        return newObj;
202                };
203        }
204};
205if(!{}.__defineGetter__){
206        if(dojo.isIE){
207                // to setup the crazy lettable hack we need to
208                // introduce vb script eval
209                // the only way that seems to work for adding a VBScript to the page is with a document.write
210                // document.write is not always available, so we use an iframe to do the document.write
211                // the iframe also provides a good hiding place for all the global variables that we must
212                // create in order for JScript and VBScript to interact.
213                var frame;
214                if(document.body){ // if the DOM is ready we can add it
215                        frame = document.createElement("iframe");
216                        document.body.appendChild(frame);
217                }else{ // other we have to write it out
218                        document.write("<iframe id='dj_vb_eval_frame'></iframe>");
219                        frame = document.getElementById("dj_vb_eval_frame");
220                }
221                frame.style.display="none";
222                var doc = frame.contentWindow.document;
223                dojox.lang.lettableWin = frame.contentWindow;
224                doc.write('<html><head><script language="VBScript" type="text/VBScript">' +
225                        'Function vb_global_eval(code)' +
226                                'ExecuteGlobal(code)' +
227                        'End Function' +
228                        '</script>' +
229                        '<script type="text/javascript">' +
230                        'function vbEval(code){ \n' + // this has to be here to call it from another frame
231                                'return vb_global_eval(code);' +
232                        '}' +
233                        'function construct(name){ \n' + // and this too
234                                'return window[name]();' +
235                        '}' +
236                        '</script>' +
237                        '</head><body>vb-eval</body></html>');
238                doc.close();
239        }else{
240                throw new Error("This browser does not support getters and setters");
241        }
242}
243
244dojox.lang.ReadOnlyProxy =
245// summary:
246//              Provides a read only proxy to another object, this can be
247//              very useful in object-capability systems
248// example:
249//      |       var obj = {foo:"bar"};
250//      |       var readonlyObj = dojox.lang.ReadOnlyProxy(obj);
251//      |       readonlyObj.foo = "test" // throws an error
252//      |       obj.foo = "new bar";
253//      |       readonlyObj.foo -> returns "new bar", always reflects the current value of the original (it is not just a copy)
254dojox.lang.makeObservable(function(obj,i){
255                return obj[i];
256        },function(obj,i,value){
257                // just ignore, exceptions don't seem to propagate through the VB stack.
258});
Note: See TracBrowser for help on using the repository browser.