observable.js 10 KB

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