source: Dev/branches/rest-dojo-ui/client/dojox/lang/observable.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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