Saturday, May 27, 2006

The following JavaScript code is attached via onclick handlers to two buttons. The first button runs thru all the checkbuttons in the UI and changes their state; the other enables each radiobutton in turn.

Notice how I overloaded the value property (and setvalue/getValue functions) to allow them, for checkboxes and radiobuttons, to take a boolean argument.

var radio = 1;
var check = 1;

function ToggleCheckItem()
{
dump("Entered ToggleCheckItem\n");
var which = "CheckItem" + check;
var obj = document.getElementById(which);
try {
if (obj) {
dump("found item " + which + "\n");
try {
var value = obj.value;
if (value == true)
value = false;
else
value = true;
obj.setValue(value);
} catch (err) {}
}
} catch (err) {}

check = check + 1;
if (check > 8)
check = 1;
dump("Leaving ToggleCheckItem check is " + check + "\n");
}

function SetNextRadio()
{
dump("Entered SetNextRadio");
var which = "RadioButton" + radio;
var obj = document.getElementById(which);
try {
if (obj) {
dump("found item " + which + "\n");
try {
obj.value = true;
} catch (err) {}
}
} catch (err) {}

radio = radio + 1;
if (radio > 8)
radio = 1;
dump("Leaving SetNextRadio radio is " + radio + "\n");
}

Element::SetValueHelper() is the function in the layout engine that responds to assignment of the value property of a JavaScript object. It queries the type of the control that the JavaScript object represents, stores the value in a variant type, and then calls the control's SetValue() function:

PRStatus
Element::SetValueHelper(JSContext *cx, Control *control, jsval val, jsval *rval)
{
if (!control)
return PR_FAILURE;

PRStatus ret = PR_FAILURE;

ContentType type = control->GetContentType();
switch (type) {
case ContentTypeBoolean:
{
JSBool b;
JS_ValueToBoolean(cx, val, &b);
XPVariant v;
v.SetValue(static_cast<bool>(b));
if (control->SetValue(v) == PR_SUCCESS)
ret = PR_SUCCESS;
break;
}
case ContentTypeString:
{
...

Control::SetValue() is implemented by each control. The RadioButton class and Checkbox class both implement it the same way (begging the question of whether or not this should be factored out), extracting the boolean checked/unchecked variable from the variant, then calling the concrete widget's SetChecked() method:

PRStatus RadioButton::SetValue(const XPVariant &v)
{
bool value;
const_cast<XPVariant &>(v).GetValue(value);
if (m_radiobutton)
return m_radiobutton->SetChecked(value);
}

Finally, the CocoaRadioButtonImpl class implements SetChecked (as it is forced to by its inheritance of RadioButtonImpl, which declares SetChecked to be pure virtual):

PRStatus CocoaRadioButtonImpl::HandleCommand()
{
if (m_radiobutton) {
int state = [m_radiobutton state];
RadioGroupManager *mgr = RadioGroupManager::GetManager(GetDocument());
if (mgr)
mgr->SetState(m_group, this);
}
return PR_SUCCESS;
}

PRStatus CocoaRadioButtonImpl::SetChecked(const bool checked)
{
PRStatus ret = PR_FAILURE;
if (m_radiobutton) {
if (checked) {
[m_radiobutton setState: NSOnState];
HandleCommand(); // propogate the change
} else {
[m_radiobutton setState: NSOffState];
}
ret = PR_SUCCESS;
}
return ret;
}

As you can see, we must call HandleCommand() if the state is set to true so that RadioGroupManager can adjust the state of all other radio buttons in the same radio group to the disabled state.

Checkboxes are almost the same, but they do not need to propogate changes (checkboxes are mutually exclusive, in terms of state, to other checkboxes in the UI).

Three languages (JavaScript, C++, Objective-C++) all conspiring together to implement an architecture that is simple, elegant, and powerful.

0 Comments:

Post a Comment

<< Home