- Author:
- David Nickerson <nickerso@users.sourceforge.net>
- Date:
- 2009-07-16 02:00:03+12:00
- Desc:
- the starting point for the HH tutorial example
- Permanent Source URI:
- https://models.fieldml.org/workspace/a1/rawfile/f6a8f90307388eb4b040ee3566b84d88b59247f7/dojo-presentation/js/dojo/dojox/lang/observable.js
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 = [];
for(var i in hiddenFunctions){
props.push(i);
}
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.
});