|
Creating
Dynamic Objects
Contents
Dynamic
Objects
Writing Dynamic Functions
Available Dynamic Properties
Building Your Own Classes
Prototype Vs _Prototype, Or What's The Deal With This?
Now Back To The Program
Let's Be Courteous, Ok?
Growth, Expansion, World Domination... Muhahahaha
Starting From Scratch
Your Grandfather's Pretty Stingy, Or Why Dynamic Properties
Don't Inherit
Leave Me Alone, You Bother Me, Kid
DYNAMIC
OBJECTS
ScriptEase allows the Javascript programmer to change
how an object's properties are accessed. Let's say you
have an object 'x' which you created using this line
of code:
var
x = new Object();
Normally, whenever any property of 'x' is accessed or
written to, the interpreter runs its internal code which
looks up the value of the given property or changes
it. For example, if you use:
var
y = x.foo;
the internal code checks to see if 'x' has a property
called 'foo' and if so, assigns it to 'y', otherwise
it assigns the undefined value to 'y'.
That is the usual behavior that everyone expects. However,
suppose instead you want to create a special object
that doesn't do these normal things. Instead, lets say
that you want to create a new object 'x' that behaves
differently.
'x' will have only the property 'color', and if you
assign to it, you want the color displayed by your monitor
to change. If you read from the property 'color', you
want it to check what the current color is and return
it. In this way, you can write programs that fight with
each other changing the color of your screen (what fun!).
This capability is what dynamic objects give you.
CREATING
A DYNAMIC OBJECT
What makes a dynamic object special? Good question.
A dynamic object is just like any other object, except
that it has some special properties which tell the ScriptEase
processor how to perform the tasks you want. The simplest
dynamic property is '_get'. When any member of an object
(except the dynamic properties) is read from, if the
object has a '_get' property, it is used to get the
value of the property. Here is a short example:
function
my_get(property)
{
Clib.printf("Someone tried to read property
'%s'\n",property);
return 10;
}
var
x = new Object();
x._get = my_get;
That's it, now 'x' is a dynamic object. Whenever anyone
tries to read a property of 'x', your function will
be called. So, if someone wrote 'a = x.foo', they are
trying to access the property 'foo' of 'x', and your
function is called. The parameter 'property' is a string
with the text of the property being accessed. The 'this'
value is set the object being manipulated. So in this
example, 'property' would have the value "foo"
and 'this' would be the same as 'x'. This example '_get'
property prints the name of the property being accessed
and then returns 10. So for this object, all properties
have the value of 10 and print a short message when
they are accessed.
WRITING
DYNAMIC FUNCTIONS
A couple of notes are in order. First, you may use the
ScriptEase toolkit API to perform the same tasks. jseCreateWrapperFunction()
and jseMemberWrapperFunction() will be useful in getting
the function you want to be called assigned to the member.
Second, both API and script code performs identically;
whatever value you return from your function is the
value that the member has THIS TIME. If someone accesses
the member again, and you return a different value,
that is OK! Remember that in ECMAScript, all functions
return a value. If you don't explicitly return one using
the return statement (or the equivelent API call), the
undefined value is returned.
I mentioned before that '_get' is not used to look up
dynamic properties. That means that when the interpretter
is looking to see if your object has a '_get' property,
it doesn't try to use your '_get' to do it (which wouldn't
make much sense.) It doesn't use '_get' for any other
dynamic property either.
Finally, what if you want to access the real members
in your object inside your '_get' function? You might
think that doing so would cause your '_get' to be called
again, and so on, leading to a big mess. Fortunately
that is not the case. Inside any dynamic function, that
particular dynamic function is turned off. So inside
'_get', if you access a member of your object, it does
the 'normal' thing. The standard way to read a property
inside your object is to use 'this[property]'. If you
want to write what I've referred to as the 'normal'
thing in ScriptEase, you might do this:
function
normal_get(property)
{
return this[property];
}
var
x = new Object();
x._get = normal_get;
This dynamic '_get' property just returns that value
of the property exactly like what normally happens (except
that it takes a lot longer).
Please note that only the dynamic property being called
is shut off. So if in your '_get' property you assign
a value to a property, your dynamic '_put' function
will be called if it exists.
AVAILABLE
DYNAMIC PROPERTIES
What object behaviors can you change? Glad you asked.
What follows is a description of each dynamic property,
including the property name you must assign it to, what
parameters it receives, and what the heck it is supposed
to do. For all dynamic properties, 'this' is set to
the object being manipulated. Finally, the dynamic property
replaces the existing behavior; it is not in addition
to it.
SYNTAX
|
function
._get(property)
|
DESCRIPTION
|
The '_get' dynamic property is called whenever
any property of the object is read from. It is
passed the property name being accessed as a parameter.
Whatever value is returned is taken to be the
properties' value THIS TIME. If you don't explicitly
return a value, the undefined value is returned.
|
SYNTAX
|
function ._put(property,value)
|
DESCRIPTION
|
The '_put' dynamic property is called whenever
any property is assigned a new value. The first
parameter is the property name being assigned
and the second parameter is the actual value being
assigned. The value could be anything, from a
number to a string to an object. No return value
is expected and is ignored if you provide one.
The default behavior is to just set the value
if the property in question is not read-only.
If it is read-only, nothing is done, but this
is not considered an error.
|
SYNTAX
|
function ._canPut(property)
|
DESCRIPTION
|
Before any value is put into a property, if you
have a '_canPut' dynamic property, it is called.
If you return False, the attempt to put the value
is ignored (but it is not considered to be an
error.) If True is returned, the put process continues
as normal. You can use this dynamic property as
a security guard to not allow certain properties
to be changed, or to put up a dialog box asking
for a password for examples. The default behavior
for this is to always return True. If you return
no value, True is assumed.
|
SYNTAX
|
function ._hasProperty(property)
|
DESCRIPTION
|
When Javascript it trying to resolve a variable
name in the script, it calls the 'hasProperty'
property of each object in the scope chain. If
the object has the property in question, True
is returned. The object's prototype is also searched.
If you return no value, False is assumed.
NOTE: It is unlikely your object will ever end
up in the scope chain, so this property will probably
never be used.
|
SYNTAX
|
function ._delete(property)
|
DESCRIPTION
|
The '_delete' dynamic property is used whenever
the delete operator is used to delete an object's
property. The default behavior is to remove
the specified property if it exists and is not
marked as jseDontDelete.
When an object itself is deleted (such as going
out of scope), the object's members are NOT deleted
individually. However, the 'delete' property
will be called with "_delete" as the
property name. This is a special courtesy to allow
object writers to destroy objects and can be considered
a destructor. However, if an object is part of
a cyclic loop there is no guarantee that the destructor
will be called. Currently, it is called in most
cases, but that may change in a later version.
For example:
var a.x = a;
a._delete = my_delete_function;
In this case, 'a' may never have its destructor
called. You have been warned.
A note to toolkit users: jseDontDelete is checked
only by the Javascript language delete operator,
not by the jseDeleteMember() API call.
|
SYNTAX
|
function ._defaultValue([hint])
|
DESCRIPTION
|
When an object is converted to a primitive type,
this routine is called to get its value. A single
parameter is optionally passed to it, called 'hint'.
'hint' is either a string or a number. Its value
is unimportant, only its type is; the 'hint's
type is the same as what we would like the return
from '_defaultValue' to be. For example, if the
hint is a string, we would like a string. You
may check the type of the 'hint' by using 'typeof
hint'.
The default behavior is to check the object for
either a 'toString()' method or a 'valueOf()'
method, and call those if found. If the hint is
a string, 'toString()' is looked for first, else
'valueOf()' is looked for first.
These two functions are not internal functions,
so '_defaultValue' searches the prototype for
them. The easiest way to implement this functionality
is to leave '_defaultValue' alone and simply provide
these two functions in your prototype.
|
SYNTAX
|
function ._construct(...)
|
DESCRIPTION
|
When an object is used as a constructor, it can
only do so if it has the '_construct' property.
By default, the '_construct' property is the same
as the '_call' property, but it can be changed.
Whatever parameters were part of the 'new' call
are passed along.
'this' is setup already as a blank slate object
with the prototype already copied in. You can
just fill it in. If you return no value or don't
return an object, the filled-in 'this' object
becomes the result of the 'new' call. Alternately,
you can return an object which becomes the result.
|
SYNTAX
|
function ._call(...)
|
DESCRIPTION
|
Whenever a function is called, the function's
'_call' property is used to determine what routine
to call. You may set up any object's '_call' dynamic
property to point to a function to be called.
Whatever parameters were passed to the call are
received. Whatever value is returned from the
call is the result of the call.
|
BUILDING
YOUR OWN CLASSES
Using the capabilities of ScriptEase, it is easy to
build your own classes. You can build a class from scratch
by defining a new variable and filling in all of the
fields as described below. Alternately, you can start
building by declaring a function with the name of your
class. That function will automatically become the constructor
for your new class.
The most basic operation that your new class will perform
is to construct new objects of your class. Let us start
with a minimal beginning:
function MyClass()
{
}
MyClass.prototype.foo
= 10;
Now, we can construct an object of MyClass by using
this code:
var myobj = new MyClass();
What actually happens in this call? First a new object
is created which will be the object that is generated
by the new operator. Next, its internal prototype property
(_prototype) is set to point to the same object as 'MyClass.prototype'.
Finally, MyClass() is called with this newly constructed
object as the 'this' variable. MyClass() could do any
initialization of this object before it returns.
PROTOTYPE VS _PROTOTYPE, or WHAT'S THE DEAL WITH
THIS?
The difference between 'prototype' and '_prototype'
can be confusing. After all, they look similar. Let's
start with '_prototype.' EVERY object has an '_prototype'
property. Whenever a member of that object is read from,
if the object doesn't seem to contain that property,
the '_prototype' object is searched to see if it contains
it, and if it does, we use that one. This allows a form
of inheritance. If that object doesn't contain it, its
'_prototype' is searched, and so on until a '_prototype'
that is null is encountered, which signals the end of
the chain.
So what the heck is 'prototype' without the underscore?
Think of 'prototype' as a construction area. You
use it to set up the '_prototype' of any object derived
from it using the new operator. Whenever any object
is created using new, its '_prototype' is set to point
back to the 'prototype' of the parent. A bit confusing,
huh? Its best to forget about '_prototype' and just
remember that you set up the 'prototype' of the new
class you are building and when you are done, objects
created from that class will inherit that prototype.
NOW
BACK TO THE PROGRAM
So the example we gave above constructs a new object,
but the constructor doesn't do anything with it. The
only difference between this object and any old generic
object so far is that its '_prototype' points to an
object with one member: foo. That means that this object
will act just like any old other object, except if someone
tries to read its member 'foo', it will find that it
is already there, inherited through the prototype. In
fact, all members of this class will have that same
member foo. It would be a good idea to grab a book on
Javascript to see how prototypes work and some good
uses you can put them too. The most common use it to
stick in functions which you'd like all members of the
object to have.
LET'S
BE COURTEOUS, OK?
All of the native Javascript objects have a _class property.
That is just a text string that holds the name of the
class.
It isn't used for anything by the language, but it is
a courtesy to programmers who might want to use your
class; if something isn't happening the way they expect,
they can easily print out the class string to find out
what is going wrong (huh, I expected class "Foo"
but it is "String" instead - oh, I see, I
used the wrong variable... oops.) You can do this by
simply assigning an appropriate text string in the prototype
like this:
MyClass.prototype._class = "MyClass";
Although class looks like a dynamic property because
of the '_', it is never used by the Javascript interpretter.
This allows you to treat it just likely a regular class
member and inherit it through the prototype since you
will be the only one ever accessing it. If you want,
you can set the _class on each individual object
in the constructor instead.
GROWTH, EXPANSION, WORLD DOMINATION... MUHAHAHAHA
Let's expand our class to have a function like we talked
about above. Also, we'll make sure it is only called
on objects of our class.
function foo()
{
Clib.assert( this._class=="MyClass"
);
Clib.printf("Hi there!\n");
}
MyClass.prototype.hello = foo;
There, add that to the previous code, and now new class
objects have a function that can be called! You'd do
it just like this:
myobj.hello();
STARTING FROM SCRATCH
You may have noticed that you can still call MyClass()
just like any other function, only in this case the
'this' property is set to the global object. That's
because generic functions have both a '_call' and '_construct'
method. In each case, both are just set up to call the
function. BUT, you can redirect either, so that a different
function will be invoced for a call than will be for
a constructor. The easiest way is to start with a blank
object and explicitly assign these properties.
function MyClassConstructor()
{
}
var MyClass;
MyClass._construct = MyClassConstructor;
There, now your class exists, and new objects can be
constructed from it. However, if anyone tries to call
it, an error will be generated. You are safe to play
around with the 'this' if you want because you know
it will always be a newly constructed object ready for
you to extend.
It is possible to completely ignore the 'this' object
in a constructor and build your own up from scratch!
To do this, simple build an object in any way you like
and return it (using the return statement.) It will
be the result of the constructor and the original object
will be thrown away.
YOUR GRANDFATHER'S PRETTY STINGEE, or WHY DYNAMIC
PROPERTIES DON'T INHERIT
You want your class objects to have a dynamic property,
perhaps a '_get'. Ahah, you say! I'll stick it in the
prototype and all my objects will inherit it. Unfortunately,
you try it and it doesn't work. There are a number of
reasons for this. First, it makes it hard to track bugs,
since a user may have an object that doesn't seem to
have a dynamic property, but acts weird nonetheless.
Second, the dynamic properties are just the regular
Javascript internal properties given accessable names.
Although it might be reasonable to search for them in
the prototype, it is just as reasonable to treat them
as internal to the particular object. Third, performance
would be negatively impacted by having to search prototype
chains for these properties. Thus, the current decision
is that they DON'T inherit.
So, how do you get around this? Simple, inside your
constructor you can do any initializations on the object
before you finish. So you might write your constructor
like this:
function MyClassConstructor()
{
this._get = MyDynamicGetFunction;
}
LEAVE ME ALONE, YOU BOTHER ME, KID.
Well,
that's it, all the information you need to start writing
your own classes, perhaps that even use dynamic properties.
To
help you, Nombas has written a number of sample classes
in both Javascript and using the C++ ScriptEase ISDK
toolkit API. These dynamic-object samples ship with
the ISDK as files ISDK\SAMPLES\OBJSAMPS\OBJECT?.C.
For
ScriptEase:Desktop you can find JavaScript source versions
of these samples as OBJECT?.JSE. Take a look at them
to see how you might go about writing your own.
|