!import
1 <?xml version="1.0"?>
2
3 <bindings id="radioBindings"
4 xmlns="http://www.mozilla.org/xbl"
5 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
6 xmlns:xbl="http://www.mozilla.org/xbl">
7
8 <binding id="radiogroup" extends="chrome://global/content/bindings/general.xml#basecontrol">
9 <resources>
10 <stylesheet src="chrome://global/skin/radio.css"/>
11 </resources>
12
13 <implementation implements="nsIDOMXULSelectControlElement, nsIAccessibleProvider">
constructor
14 <constructor>
15 <![CDATA[
16 if (this.getAttribute("disabled") == "true")
17 this.disabled = true;
18
19 var children = this._getRadioChildren();
20 var length = children.length;
21 for (var i = 0; i < length; i++) {
22 if (children[i].getAttribute("selected") == "true") {
23 this.selectedIndex = i;
24 return;
25 }
26 }
27
28 var value = this.value;
29 if (value)
30 this.value = value;
31 else
32 this.selectedIndex = 0;
33 ]]>
34 </constructor>
35
36 <property name="accessibleType" readonly="true">
get_accessibleType
37 <getter>
38 <![CDATA[
39 return Components.interfaces.nsIAccessibleProvider.XULRadioGroup;
40 ]]>
41 </getter>
42 </property>
43
get_value
44 <property name="value" onget="return this.getAttribute('value');">
set_value
45 <setter>
46 <![CDATA[
47 this.setAttribute("value", val);
48 var children = this._getRadioChildren();
49 for (var i = 0; i < children.length; i++) {
50 if (String(children[i].value) == String(val)) {
51 this.selectedItem = children[i];
52 break;
53 }
54 }
55 return val;
56 ]]>
57 </setter>
58 </property>
59 <property name="disabled">
get_disabled
60 <getter>
61 <![CDATA[
62 if (this.getAttribute('disabled') == 'true')
63 return true;
64 var children = this._getRadioChildren();
65 for (var i = 0; i < children.length; ++i) {
66 if (!children[i].hidden && !children[i].collapsed && !children[i].disabled)
67 return false;
68 }
69 return true;
70 ]]>
71 </getter>
set_disabled
72 <setter>
73 <![CDATA[
74 if (val)
75 this.setAttribute('disabled', 'true');
76 else
77 this.removeAttribute('disabled');
78 var children = this._getRadioChildren();
79 for (var i = 0; i < children.length; ++i) {
80 children[i].disabled = val;
81 }
82 return val;
83 ]]>
84 </setter>
85 </property>
86
get_itemCount
87 <property name="itemCount" readonly="true"
88 onget="return this._getRadioChildren().length"/>
89
90 <property name="selectedIndex">
get_selectedIndex
91 <getter>
92 <![CDATA[
93 var children = this._getRadioChildren();
94 for (var i = 0; i < children.length; ++i) {
95 if (children[i].selected)
96 return i;
97 }
98 return -1;
99 ]]>
100 </getter>
set_selectedIndex
101 <setter>
102 <![CDATA[
103 this.selectedItem = this._getRadioChildren()[val];
104 return val;
105 ]]>
106 </setter>
107 </property>
108
109 <property name="selectedItem">
get_selectedItem
110 <getter>
111 <![CDATA[
112 var children = this._getRadioChildren();
113 for (var i = 0; i < children.length; ++i) {
114 if (children[i].selected)
115 return children[i];
116 }
117 return null;
118 ]]>
119 </getter>
set_selectedItem
120 <setter>
121 <![CDATA[
122 var focused = this.getAttribute("focused") == "true";
123 var alreadySelected = false;
124
125 if (val) {
126 alreadySelected = val.getAttribute("selected") == "true";
127 val.setAttribute("focused", focused);
128 val.setAttribute("selected", "true");
129 this.setAttribute("value", val.value);
130 }
131 else {
132 this.removeAttribute("value");
133 }
134
135 // uncheck all other group nodes
136 var children = this._getRadioChildren();
137 var previousItem = null;
138 for (var i = 0; i < children.length; ++i) {
139 if (children[i] != val) {
140 if (children[i].getAttribute("selected") == "true")
141 previousItem = children[i];
142
143 children[i].removeAttribute("selected");
144 children[i].removeAttribute("focused");
145 }
146 }
147
148 var event = document.createEvent("Events");
149 event.initEvent("select", false, true);
150 this.dispatchEvent(event);
151
152 if (!alreadySelected && focused) {
153 // Only report if actual change
154 var myEvent;
155 if (val) {
156 myEvent = document.createEvent("Events");
157 myEvent.initEvent("RadioStateChange", true, true);
158 val.dispatchEvent(myEvent);
159 }
160
161 if (previousItem) {
162 myEvent = document.createEvent("Events");
163 myEvent.initEvent("RadioStateChange", true, true);
164 previousItem.dispatchEvent(myEvent);
165 }
166 }
167
168 return val;
169 ]]>
170 </setter>
171 </property>
172
173 <property name="focusedItem">
get_focusedItem
174 <getter>
175 <![CDATA[
176 var children = this._getRadioChildren();
177 for (var i = 0; i < children.length; ++i) {
178 if (children[i].getAttribute("focused") == "true")
179 return children[i];
180 }
181 return null;
182 ]]>
183 </getter>
set_focusedItem
184 <setter>
185 <![CDATA[
186 if (val) val.setAttribute("focused", "true");
187
188 // unfocus all other group nodes
189 var children = this._getRadioChildren();
190 for (var i = 0; i < children.length; ++i) {
191 if (children[i] != val)
192 children[i].removeAttribute("focused");
193 }
194 return val;
195 ]]>
196 </setter>
197 </property>
198
199 <method name="checkAdjacentElement">
200 <parameter name="aNextFlag"/>
checkAdjacentElement
201 <body>
202 <![CDATA[
203 var currentElement = this.focusedItem || this.selectedItem;
204 var i;
205 var children = this._getRadioChildren();
206 for (i = 0; i < children.length; ++i ) {
207 if (children[i] == currentElement)
208 break;
209 }
210 var index = i;
211
212 if (aNextFlag) {
213 do {
214 if (++i == children.length)
215 i = 0;
216 if (i == index)
217 break;
218 }
219 while (children[i].hidden || children[i].collapsed || children[i].disabled);
220 // XXX check for display/visibility props too
221
222 this.selectedItem = children[i];
223 children[i].doCommand();
224 }
225 else {
226 do {
227 if (i == 0)
228 i = children.length;
229 if (--i == index)
230 break;
231 }
232 while (children[i].hidden || children[i].collapsed || children[i].disabled);
233 // XXX check for display/visibility props too
234
235 this.selectedItem = children[i];
236 children[i].doCommand();
237 }
238 ]]>
239 </body>
240 </method>
field__radioChildren
241 <field name="_radioChildren">null</field>
242 <method name="_getRadioChildren">
_getRadioChildren
243 <body>
244 <![CDATA[
245 if (this._radioChildren)
246 return this._radioChildren;
247
248 var radioChildren = [];
249 var doc = this.ownerDocument;
250
251 if (this.hasChildNodes()) {
252 // Don't store the collected child nodes immediately,
253 // collecting the child nodes could trigger constructors
254 // which would blow away our list.
255
256 const nsIDOMNodeFilter = Components.interfaces.nsIDOMNodeFilter;
257 var iterator = doc.createTreeWalker(this,
258 nsIDOMNodeFilter.SHOW_ELEMENT,
259 this._filterRadioGroup,
260 true);
261 while (iterator.nextNode())
262 radioChildren.push(iterator.currentNode);
263 return this._radioChildren = radioChildren;
264 }
265
266 // We don't have child nodes.
267 const XUL_NS = "http://www.mozilla.org/keymaster/"
268 + "gatekeeper/there.is.only.xul";
269 var elems = doc.getElementsByAttribute("group", this.id);
270 for (var i = 0; i < elems.length; i++) {
271 if ((elems[i].namespaceURI == XUL_NS) &&
272 (elems[i].localName == "radio")) {
273 radioChildren.push(elems[i]);
274 }
275 }
276 return this._radioChildren = radioChildren;
277 ]]>
278 </body>
279 </method>
280 <method name="_filterRadioGroup">
281 <parameter name="node"/>
_filterRadioGroup
282 <body>
283 <![CDATA[
284 switch (node.localName) {
285 case "radio": return NodeFilter.FILTER_ACCEPT;
286 case "template":
287 case "radiogroup": return NodeFilter.FILTER_REJECT;
288 default: return NodeFilter.FILTER_SKIP;
289 }
290 ]]>
291 </body>
292 </method>
293
294 <method name="getIndexOfItem">
295 <parameter name="item"/>
getIndexOfItem
296 <body>
297 return this._getRadioChildren().indexOf(item);
298 </body>
299 </method>
300
301 <method name="getItemAtIndex">
302 <parameter name="index"/>
getItemAtIndex
303 <body>
304 <![CDATA[
305 var children = this._getRadioChildren();
306 return (index >= 0 && index < children.length) ? children[index] : null;
307 ]]>
308 </body>
309 </method>
310
311 <method name="appendItem">
312 <parameter name="label"/>
313 <parameter name="value"/>
appendItem
314 <body>
315 <![CDATA[
316 var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
317 var radio = document.createElementNS(XULNS, "radio");
318 radio.setAttribute("label", label);
319 radio.setAttribute("value", value);
320 this.appendChild(radio);
321 this._radioChildren = null;
322 return radio;
323 ]]>
324 </body>
325 </method>
326
327 <method name="insertItemAt">
328 <parameter name="index"/>
329 <parameter name="label"/>
330 <parameter name="value"/>
insertItemAt
331 <body>
332 <![CDATA[
333 var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
334 var radio = document.createElementNS(XULNS, "radio");
335 radio.setAttribute("label", label);
336 radio.setAttribute("value", value);
337 var before = this.getItemAtIndex(index);
338 if (before)
339 before.parentNode.insertBefore(radio, before);
340 else
341 this.appendChild(radio);
342 this._radioChildren = null;
343 return radio;
344 ]]>
345 </body>
346 </method>
347
348 <method name="removeItemAt">
349 <parameter name="index"/>
removeItemAt
350 <body>
351 <![CDATA[
352 var remove = this.getItemAtIndex(index);
353 if (remove) {
354 remove.parentNode.removeChild(remove);
355 this._radioChildren = null;
356 }
357 return remove;
358 ]]>
359 </body>
360 </method>
361 </implementation>
362
363 <handlers>
onmousedown
364 <handler event="mousedown">
365 if (this.disabled)
366 event.preventDefault();
367 </handler>
368
369 <!-- keyboard navigation -->
370 <!-- Here's how keyboard navigation works in radio groups on Windows:
371 The group takes 'focus'
372 The user is then free to navigate around inside the group
373 using the arrow keys. Accessing previous or following radio buttons
374 is done solely through the arrow keys and not the tab button. Tab
375 takes you to the next widget in the tab order -->
onkeypress
376 <handler event="keypress" key=" " phase="target">
377 this.selectedItem = this.focusedItem;
378 this.selectedItem.doCommand();
379 </handler>
onkeypress
380 <handler event="keypress" keycode="VK_UP" phase="target">
381 this.checkAdjacentElement(false);
382 event.stopPropagation();
383 </handler>
onkeypress
384 <handler event="keypress" keycode="VK_LEFT" phase="target">
385 // left arrow goes back when we are ltr, forward when we are rtl
386 this.checkAdjacentElement(document.defaultView.getComputedStyle(
387 this, "").direction == "rtl");
388 event.stopPropagation();
389 </handler>
onkeypress
390 <handler event="keypress" keycode="VK_DOWN" phase="target">
391 this.checkAdjacentElement(true);
392 event.stopPropagation();
393 </handler>
onkeypress
394 <handler event="keypress" keycode="VK_RIGHT" phase="target">
395 // right arrow goes forward when we are ltr, back when we are rtl
396 this.checkAdjacentElement(document.defaultView.getComputedStyle(
397 this, "").direction == "ltr");
398 event.stopPropagation();
399 </handler>
400
401 <!-- set a focused attribute on the selected item when the group
402 receives focus so that we can style it as if it were focused even though
403 it is not (Windows platform behaviour is for the group to receive focus,
404 not the item -->
onfocus
405 <handler event="focus" phase="target">
406 <![CDATA[
407 this.setAttribute("focused", "true");
408 if (this.focusedItem)
409 return;
410
411 var val = this.selectedItem;
412 if (!val || val.disabled || val.hidden || val.collapsed) {
413 var children = this._getRadioChildren();
414 for (var i = 0; i < children.length; ++i) {
415 if (!children[i].hidden && !children[i].collapsed && !children[i].disabled) {
416 val = children[i];
417 break;
418 }
419 }
420 }
421 this.focusedItem = val;
422 ]]>
423 </handler>
onblur
424 <handler event="blur" phase="target">
425 this.removeAttribute("focused");
426 this.focusedItem = null;
427 </handler>
428 </handlers>
429 </binding>
430
431 <binding id="radio" extends="chrome://global/content/bindings/general.xml#control-item">
432 <resources>
433 <stylesheet src="chrome://global/skin/radio.css"/>
434 </resources>
435
436 <content>
437 <xul:image class="radio-check" xbl:inherits="disabled,selected"/>
438 <xul:hbox class="radio-label-box" align="center" flex="1">
439 <xul:image class="radio-icon" xbl:inherits="src"/>
440 <xul:label class="radio-label" xbl:inherits="xbl:text=label,accesskey,crop" flex="1"/>
441 </xul:hbox>
442 </content>
443
444 <implementation implements="nsIDOMXULSelectControlItemElement, nsIDOMXULLabeledControlElement, nsIAccessibleProvider">
constructor
445 <constructor>
446 <![CDATA[
447 // Just clear out the parent's cached list of radio children
448 var control = this.control;
449 if (control)
450 control._radioChildren = null;
451 ]]>
452 </constructor>
destructor
453 <destructor>
454 <![CDATA[
455 var radioList = this.radioGroup.mRadioChildren;
456 if (!radioList)
457 return;
458 for (var i = 0; i < radioList.length; ++i) {
459 if (radioList[i] == this) {
460 radioList.splice(i, 1);
461 return;
462 }
463 }
464 ]]>
465 </destructor>
466 <property name="accessibleType" readonly="true">
get_accessibleType
467 <getter>
468 <![CDATA[
469 return Components.interfaces.nsIAccessibleProvider.XULRadioButton;
470 ]]>
471 </getter>
472 </property>
473 <property name="selected" readonly="true">
get_selected
474 <getter>
475 <![CDATA[
476 return this.hasAttribute('selected');
477 ]]>
478 </getter>
479 </property>
get_radioGroup
480 <property name="radioGroup" readonly="true" onget="return this.control"/>
481 <property name="control" readonly="true">
get_control
482 <getter>
483 <![CDATA[
484 const XUL_NS = "http://www.mozilla.org/keymaster/"
485 + "gatekeeper/there.is.only.xul";
486 var parent = this.parentNode;
487 while (parent) {
488 if ((parent.namespaceURI == XUL_NS) &&
489 (parent.localName == "radiogroup")) {
490 return parent;
491 }
492 parent = parent.parentNode;
493 }
494
495 var group = this.getAttribute("group");
496 if (!group) {
497 return null;
498 }
499
500 parent = this.ownerDocument.getElementById(group);
501 if (!parent ||
502 (parent.namespaceURI != XUL_NS) ||
503 (parent.localName != "radiogroup")) {
504 parent = null;
505 }
506 return parent;
507 ]]>
508 </getter>
509 </property>
510 </implementation>
511 <handlers>
onclick
512 <handler event="click" button="0">
513 <![CDATA[
514 if (!this.disabled)
515 this.control.selectedItem = this;
516 ]]>
517 </handler>
518
onmousedown
519 <handler event="mousedown" button="0">
520 <![CDATA[
521 if (!this.disabled)
522 this.control.focusedItem = this;
523 ]]>
524 </handler>
onfocus
525 <handler event="focus">
526 <![CDATA[
527 if (!this.disabled) {
528 this.control.focusedItem = this;
529 this.control.focus();
530 }
531 ]]>
532 </handler>
533 </handlers>
534 </binding>
535 </bindings>