summaryrefslogtreecommitdiff
path: root/js/dojo-1.6/dojox/lang/observable.js
blob: 34dd68e3a901d37e729e3312e515e1a0a206a114 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
	Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
	Available via Academic Free License >= 2.1 OR the modified BSD license.
	see: http://dojotoolkit.org/license for details
*/


if(!dojo._hasResource["dojox.lang.observable"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.lang.observable"] = true;
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<l; i++){
					prop = props[i];
					var type = typeof wrapped[prop];
					if(type == 'function' || hiddenFunctions[prop]){ // functions must go in regular properties for delegation:/
						cParts.push("  Public " + prop);
					}else if(type != 'object'){ // the getters/setters can only be applied to primitives
						cParts.push(
							"	Public Property Let "+prop+"(val)",
							"		Call "+setName+"(me.data__,\""+prop+"\",val)",
							"	End Property",
							"	Public Property Get "+prop,
							"		"+prop+" = "+getName+"(me.data__,\""+prop+"\")",
							"	End Property");
					}
				}
				cParts.push("End Class");
				cParts.push(
					"Function "+gtname+"()",
					"	Dim tmp",
					"	Set tmp = New "+tname,
					"	Set "+gtname+" = tmp",
					"End Function");
				dojox.lang.lettableWin.vbEval(cParts.join("\n"));
					
				// Put the new class in the cache
				cache[signature] = clazz = function(){
					return dojox.lang.lettableWin.construct(gtname); // the class can't be accessed, only called, so we have to wrap it with a function
				};
			}
			console.log("starting5");
			var newObj = clazz();
			newObj.data__ = wrapped;
			console.log("starting6");
			try {
				wrapped.__observable = newObj;
			} catch(e){ // some objects are not expando
			}
			for(i = 0,  l = props.length; i < l; i++){
				prop = props[i];
				try {
				var val = wrapped[prop];
				}
				catch(e){
					console.log("error ",prop,e);
				}
				if(typeof val == 'function' || hiddenFunctions[prop]){ // we can make a delegate function here
					newObj[prop] = makeInvoker(newObj,wrapped,prop);
				}
			}
			return newObj;
		};
	}else{
		return function(wrapped){ // do it with getters and setters
			if(wrapped.__observable){ // if it already has an observable, use that
				return wrapped.__observable;
			}
			var newObj = wrapped instanceof Array ? [] : {};
			newObj.data__ = wrapped;
			for(var i in wrapped){
				if(i.charAt(0) != '_'){
					if(typeof wrapped[i] == 'function'){
						newObj[i] = makeInvoker(newObj,wrapped,i); // TODO: setup getters and setters so we can detect when this changes
					}else if(typeof wrapped[i] != 'object'){
						(function(i){
							newObj.__defineGetter__(i,function(){
								return onRead(wrapped,i);
							});
							newObj.__defineSetter__(i,function(value){
								return onWrite(wrapped,i,value);
							});
						})(i);
					}
				}
			}
			for(i in hiddenFunctions){
				newObj[i] = makeInvoker(newObj,wrapped,i);
			}
			wrapped.__observable = newObj;
			return newObj;
		};
	}
};
if(!{}.__defineGetter__){
	if(dojo.isIE){
		// to setup the crazy lettable hack we need to
		// introduce vb script eval
		// the only way that seems to work for adding a VBScript to the page is with a document.write
		// document.write is not always available, so we use an iframe to do the document.write
		// the iframe also provides a good hiding place for all the global variables that we must
		// create in order for JScript and VBScript to interact.
		var frame;
		if(document.body){ // if the DOM is ready we can add it
			frame = document.createElement("iframe");
			document.body.appendChild(frame);
		}else{ // other we have to write it out
			document.write("<iframe id='dj_vb_eval_frame'></iframe>");
			frame = document.getElementById("dj_vb_eval_frame");
		}
		frame.style.display="none";
		var doc = frame.contentWindow.document;
		dojox.lang.lettableWin = frame.contentWindow;
		doc.write('<html><head><script language="VBScript" type="text/VBScript">' +
			'Function vb_global_eval(code)' +
				'ExecuteGlobal(code)' +
			'End Function' +
			'</script>' +
			'<script type="text/javascript">' +
			'function vbEval(code){ \n' + // this has to be here to call it from another frame
				'return vb_global_eval(code);' +
			'}' +
			'function construct(name){ \n' + // and this too
				'return window[name]();' +
			'}' +
			'</script>' +
			'</head><body>vb-eval</body></html>');
		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.
});

}