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