Saturday, March 11, 2006

Those of you who are experienced JavaScript programmers, and who have read my last couple of posts, might be wondering why I have not implemented properties for setting and getting the value of a control, or for changing the enabled state of a control. After all, the syntax associated with the use of properties is arguably more elegant -- instead of calling functions implemented by the object, like this:

var obj = document.getElementById("text1");
if (obj) {
obj.enable(); // enable the text field
obj.setValue("Mozart"); // set the value to "Mozart"
var val = obj.getValue(); // get the value of the text field
obj.disable(); // disable the text field
}

one can achieve the same functionality by setting properties that are exposed by the object. The following code is semantically equivalent to the above, but uses properties instead of calling functions defined on the object:

var obj = document.getElementById("text1");
if (obj) {
obj.enabled = true; // enable the text field
obj.value = "Mozart"; // set the value to "Mozart"
var val = obj.value; // get the value of the text field
obj.enabled = false; // disable the text field
}

Well, turns out I did implement the above, and I'd like to briefly explain how it was done. In addition to implementing functions that can be called on an object, the Mozilla JavaScript engine also supports properties. To associate properties on an object, a JSPropertySpec vector can be used, in a way analogous to the use of the JSFunctionSpec to define functions on an object, to define properties and associate them with C++ callback functions.

The following code declares a JSPropertySpec vector that defines two properties, enabled and value, and defines setters and getters for each:

static JSPropertySpec element_properties[] = {
{"enabled", 0x01, 0, EnabledPropertyGetter, EnabledPropertySetter},
{"value", 0x02, 0, ValuePropertyGetter, ValuePropertySetter},
{0}
};

The first field is the property name. The second field is a unique 8-bit value which is unused in my implementation. The third field is a bitmask that can be used to restrict access to the properties, see the online documentation at mozilla.org for more information. Finally, the last two fields contain the local C callback functions invoked when the JavaScript application reads and writes the properties, respectively. These callbacks are roughly analogous to the callbacks invoked when a function is called on an object.

Here are the function implementations for both ValuePropertyGetter() and ValuePropertySetter(). In the case of ValuePropertySetter(), the value to which the control is to be set is passed via the vp argument. Similarly, the job of ValuePropertyGetter() is to retrieve the value of the control associated with the passed in object, and store its value in the vp argument. This, for the setter, vp should be treated read-only, and for the getter, vp should be considered write-only.

static JSBool
ValuePropertySetter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
JSBool ret = JS_FALSE;

Element *element = Element::FindElementByObj(obj);
if (element) {
Control *control = dynamic_cast<Control *>(element);
if (control && element->SetValueHelper(cx, control, *vp, vp) == PR_SUCCESS)
ret = JS_TRUE;
}

return ret;
}

static JSBool
ValuePropertyGetter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
JSBool ret = JS_FALSE;

Element *element = Element::FindElementByObj(obj);
if (element) {
Control *control = dynamic_cast<Control *>(element);
if (control && element->GetValueHelper(cx, control, vp) == PR_SUCCESS)
ret = JS_TRUE;
}
return ret;
}

Note that both of the functions above use GetValueHelper() and SetValueHelper() in the same way as they are used by DoGetValue() and DoSetValue() (which were both described in my previous post). After all, getting the value of the text field, regardless of how it is done -- by reading a property or via calling a function -- remains the same.

0 Comments:

Post a Comment

<< Home