1 <?xml version="1.0"?>
2
3 <bindings id="menulistBindings"
4 xmlns="http://www.mozilla.org/xbl"
5 xmlns:html="http://www.w3.org/1999/xhtml"
6 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
7 xmlns:xbl="http://www.mozilla.org/xbl">
8
9 <binding id="menulist-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
10 <resources>
11 <stylesheet src="chrome://global/skin/menulist.css"/>
12 </resources>
13 </binding>
14
15 <binding id="menulist" display="xul:menu"
16 extends="chrome://global/content/bindings/menulist.xml#menulist-base">
17 <content sizetopopup="pref">
18 <xul:hbox class="menulist-label-box" flex="1">
19 <xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
20 <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
21 </xul:hbox>
22 <xul:dropmarker class="menulist-dropmarker" type="menu" xbl:inherits="disabled"/>
23 <children includes="menupopup"/>
24 </content>
25
26 <handlers>
27 <handler event="command" phase="capturing"
28 action="if (event.target.parentNode.parentNode == this) this.selectedItem = event.target;"/>
29
30 <handler event="popupshowing">
31 <![CDATA[
32 if (event.target.parentNode == this && this.selectedItem)
33 // Not ready for auto-setting the active child in hierarchies yet.
34 // For now, only do this when the outermost menupopup opens.
35 this.menuBoxObject.activeChild = this.mSelectedInternal;
36 ]]>
37 </handler>
38
39 <handler event="keypress" modifiers="shift any">
40 <![CDATA[
41 if (event.originalTarget == this &&
42 (event.keyCode == KeyEvent.DOM_VK_UP ||
43 event.keyCode == KeyEvent.DOM_VK_DOWN ||
44 event.keyCode == KeyEvent.DOM_VK_PAGE_UP ||
45 event.keyCode == KeyEvent.DOM_VK_PAGE_DOWN ||
46 event.keyCode == KeyEvent.DOM_VK_HOME ||
47 event.keyCode == KeyEvent.DOM_VK_END ||
48 event.keyCode == KeyEvent.DOM_VK_BACK_SPACE ||
49 event.charCode > 0)) {
50 // Moving relative to an item: start from the currently selected item
51 this.menuBoxObject.activeChild = this.mSelectedInternal;
52 if (this.menuBoxObject.handleKeyPress(event)) {
53 this.menuBoxObject.activeChild.doCommand();
54 event.preventDefault();
55 }
56 }
57 ]]>
58 </handler>
59 </handlers>
60
61 <implementation implements="nsIDOMXULMenuListElement, nsIAccessibleProvider, nsIDOMEventListener">
62 <constructor>
63 this.setInitialSelection()
64 </constructor>
65
66 <method name="setInitialSelection">
67 <body>
68 <![CDATA[
69 var popup = this.menupopup;
70 if (popup) {
71 var arr = popup.getElementsByAttribute('selected', 'true');
72
73 var editable = this.editable;
74 var value = this.value;
75 if (!arr.item(0) && value)
76 arr = popup.getElementsByAttribute(editable ? 'label' : 'value', value);
77
78 if (arr.item(0))
79 this.selectedItem = arr[0];
80 else if (!editable)
81 this.selectedIndex = 0;
82 }
83 ]]>
84 </body>
85 </method>
86
87 <property name="value" onget="return this.getAttribute('value');">
88 <setter>
89 <![CDATA[
90 // if the new value is null, we still need to remove the old value
91 if (val == null)
92 return this.selectedItem = val;
93
94 var arr = null;
95 var popup = this.menupopup;
96 if (popup)
97 arr = popup.getElementsByAttribute('value', val);
98
99 if (arr && arr.item(0))
100 this.selectedItem = arr[0];
101 else {
102 this.selectedItem = null;
103 this.setAttribute('value', val);
104 }
105
106 return val;
107 ]]>
108 </setter>
109 </property>
110
111 <property name="inputField" readonly="true" onget="return null;"/>
112
113 <property name="crop" onset="this.setAttribute('crop',val); return val;"
114 onget="return this.getAttribute('crop');"/>
115 <property name="image" onset="this.setAttribute('image',val); return val;"
116 onget="return this.getAttribute('image');"/>
117 <property name="label" readonly="true" onget="return this.getAttribute('label');"/>
118 <property name="description" onset="this.setAttribute('description',val); return val;"
119 onget="return this.getAttribute('description');"/>
120 <property name="editable" onset="this.setAttribute('editable',val); return val;"
121 onget="return this.getAttribute('editable') == 'true';"/>
122
123 <property name="open" onset="this.menuBoxObject.openMenu(val);
124 return val;"
125 onget="return this.hasAttribute('open');"/>
126
127 <property name="itemCount" readonly="true"
128 onget="return this.menupopup ? this.menupopup.childNodes.length : 0"/>
129
130 <property name="menupopup" readonly="true">
131 <getter>
132 <![CDATA[
133 var popup = this.firstChild;
134 while (popup && popup.localName != "menupopup")
135 popup = popup.nextSibling;
136 return popup;
137 ]]>
138 </getter>
139 </property>
140
141 <field name="menuBoxObject" readonly="true">
142 this.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject)
143 </field>
144
145 <field name="mSelectedInternal">
146 null
147 </field>
148
149 <method name="contains">
150 <parameter name="item"/>
151 <body>
152 <![CDATA[
153 if (!item)
154 return false;
155
156 var parent = item.parentNode;
157 return (parent && parent.parentNode == this);
158 ]]>
159 </body>
160 </method>
161
162 <property name="selectedIndex">
163 <getter>
164 <![CDATA[
165 // Quick and dirty. We won't deal with hierarchical menulists yet.
166 if (!this.selectedItem ||
167 !this.mSelectedInternal.parentNode ||
168 this.mSelectedInternal.parentNode.parentNode != this)
169 return -1;
170
171 var children = this.mSelectedInternal.parentNode.childNodes;
172 var i = children.length;
173 while (i--)
174 if (children[i] == this.mSelectedInternal)
175 break;
176
177 return i;
178 ]]>
179 </getter>
180 <setter>
181 <![CDATA[
182 var popup = this.menupopup;
183 if (popup && 0 <= val && val < popup.childNodes.length)
184 this.selectedItem = popup.childNodes[val];
185 else
186 this.selectedItem = null;
187 return val;
188 ]]>
189 </setter>
190 </property>
191
192 <property name="selectedItem">
193 <getter>
194 <![CDATA[
195 return this.mSelectedInternal;
196 ]]>
197 </getter>
198 <setter>
199 <![CDATA[
200 var oldval = this.mSelectedInternal;
201 if (oldval == val)
202 return val;
203
204 if (val && !this.contains(val))
205 return val;
206
207 if (oldval) {
208 oldval.removeAttribute('selected');
209 if (document instanceof Components.interfaces.nsIDOMXULDocument) {
210 document.removeBroadcastListenerFor(oldval, this, "value");
211 document.removeBroadcastListenerFor(oldval, this, "label");
212 document.removeBroadcastListenerFor(oldval, this, "image");
213 document.removeBroadcastListenerFor(oldval, this, "description");
214 }
215 else
216 oldval.removeEventListener("DOMAttrModified", this, false);
217 }
218
219 this.mSelectedInternal = val;
220 if (val) {
221 val.setAttribute('selected', 'true');
222 this.setAttribute('value', val.getAttribute('value'));
223 this.setAttribute('image', val.getAttribute('image'));
224 this.setAttribute('label', val.getAttribute('label'));
225 this.setAttribute('description', val.getAttribute('description'));
226 // DOMAttrModified listeners slow down setAttribute calls within
227 // the document, see bug 395496
228 if (document instanceof Components.interfaces.nsIDOMXULDocument) {
229 document.addBroadcastListenerFor(val, this, "value");
230 document.addBroadcastListenerFor(val, this, "label");
231 document.addBroadcastListenerFor(val, this, "image");
232 document.addBroadcastListenerFor(val, this, "description");
233 }
234 else
235 val.addEventListener("DOMAttrModified", this, false);
236 }
237 else {
238 this.removeAttribute('value');
239 this.removeAttribute('image');
240 this.removeAttribute('label');
241 this.removeAttribute('description');
242 }
243
244 var event = document.createEvent("Events");
245 event.initEvent("select", true, true);
246 this.dispatchEvent(event);
247
248 var event = document.createEvent("Events");
249 event.initEvent("ValueChange", true, true);
250 this.dispatchEvent(event);
251
252 return val;
253 ]]>
254 </setter>
255 </property>
256
257 <method name="handleEvent">
258 <parameter name="aEvent"/>
259 <body>
260 <![CDATA[
261 if (aEvent.type == "DOMAttrModified" &&
262 aEvent.target == this.mSelectedInternal) {
263 var attrName = aEvent.attrName;
264 switch (attrName) {
265 case "value":
266 case "label":
267 case "image":
268 case "description":
269 this.setAttribute(attrName, aEvent.newValue);
270 }
271 }
272 ]]>
273 </body>
274 </method>
275
276 <method name="getIndexOfItem">
277 <parameter name="item"/>
278 <body>
279 <![CDATA[
280 var popup = this.menupopup;
281 if (popup) {
282 var children = popup.childNodes;
283 var i = children.length;
284 while (i--)
285 if (children[i] == item)
286 return i;
287 }
288 return -1;
289 ]]>
290 </body>
291 </method>
292
293 <method name="getItemAtIndex">
294 <parameter name="index"/>
295 <body>
296 <![CDATA[
297 var popup = this.menupopup;
298 if (popup) {
299 var children = popup.childNodes;
300 if (index >= 0 && index < children.length)
301 return children[index];
302 }
303 return null;
304 ]]>
305 </body>
306 </method>
307
308 <method name="appendItem">
309 <parameter name="label"/>
310 <parameter name="value"/>
311 <parameter name="description"/>
312 <body>
313 <![CDATA[
314 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
315 var popup = this.menupopup ||
316 this.appendChild(document.createElementNS(XULNS, "menupopup"));
317 var item = document.createElementNS(XULNS, "menuitem");
318 item.setAttribute("label", label);
319 item.setAttribute("value", value);
320 if (description)
321 item.setAttribute("description", description);
322
323 popup.appendChild(item);
324 return item;
325 ]]>
326 </body>
327 </method>
328
329 <method name="insertItemAt">
330 <parameter name="index"/>
331 <parameter name="label"/>
332 <parameter name="value"/>
333 <parameter name="description"/>
334 <body>
335 <![CDATA[
336 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
337 var popup = this.menupopup ||
338 this.appendChild(document.createElementNS(XULNS, "menupopup"));
339 var item = document.createElementNS(XULNS, "menuitem");
340 item.setAttribute("label", label);
341 item.setAttribute("value", value);
342 if (description)
343 item.setAttribute("description", description);
344
345 if (index >= 0 && index < popup.childNodes.length)
346 popup.insertBefore(item, popup.childNodes[index]);
347 else
348 popup.appendChild(item);
349 return item;
350 ]]>
351 </body>
352 </method>
353
354 <method name="removeItemAt">
355 <parameter name="index"/>
356 <body>
357 <![CDATA[
358 var popup = this.menupopup;
359 if (popup && 0 <= index && index < popup.childNodes.length) {
360 var remove = popup.childNodes[index];
361 popup.removeChild(remove);
362 return remove;
363 }
364 return null;
365 ]]>
366 </body>
367 </method>
368
369 <method name="removeAllItems">
370 <body>
371 <![CDATA[
372 this.selectedItem = null;
373 var popup = this.menupopup;
374 if (popup)
375 this.removeChild(popup);
376 ]]>
377 </body>
378 </method>
379
380 <property name="accessibleType" readonly="true">
381 <getter>
382 <![CDATA[
383 <!-- Droppable is currently used for the Firefox bookmarks dialog only -->
384 return (this.getAttribute("droppable") == "false") ?
385 Components.interfaces.nsIAccessibleProvider.XULTextBox :
386 Components.interfaces.nsIAccessibleProvider.XULCombobox;
387 ]]>
388 </getter>
389 </property>
390
391 <destructor>
392 <![CDATA[
393 if (this.mSelectedInternal) {
394 if (document instanceof Components.interfaces.nsIDOMXULDocument) {
395 document.removeBroadcastListenerFor(this.mSelectedInternal, this, "value");
396 document.removeBroadcastListenerFor(this.mSelectedInternal, this, "label");
397 document.removeBroadcastListenerFor(this.mSelectedInternal, this, "image");
398 document.removeBroadcastListenerFor(this.mSelectedInternal, this, "description");
399 }
400 else
401 this.mSelectedInternal.removeEventListener("DOMAttrModified", this, false);
402 }
403 ]]>
404 </destructor>
405 </implementation>
406 </binding>
407
408 <binding id="menulist-editable" extends="chrome://global/content/bindings/menulist.xml#menulist">
409 <content sizetopopup="pref">
410 <xul:hbox class="menulist-editable-box textbox-input-box" xbl:inherits="context,disabled,readonly,focused" flex="1">
411 <html:input class="menulist-editable-input" flex="1" anonid="input" allowevents="true"
412 xbl:inherits="value=label,value,disabled,tabindex,readonly"/>
413 </xul:hbox>
414 <xul:dropmarker class="menulist-dropmarker" type="menu"
415 xbl:inherits="open,disabled,parentfocused=focused"/>
416 <children includes="menupopup"/>
417 </content>
418
419 <implementation>
420 <method name="_selectInputFieldValueInList">
421 <body>
422 <![CDATA[
423 if (this.hasAttribute("disableautoselect"))
424 return;
425
426 // Find and select the menuitem that matches inputField's "value"
427 var arr = null;
428 var popup = this.menupopup;
429
430 if (popup)
431 arr = popup.getElementsByAttribute('label', this.inputField.value);
432
433 this.setSelectionInternal(arr ? arr.item(0) : null);
434 ]]>
435 </body>
436 </method>
437
438 <method name="setSelectionInternal">
439 <parameter name="val"/>
440 <body>
441 <![CDATA[
442 // This is called internally to set selected item
443 // without triggering infinite loop
444 // when using selectedItem's setter
445 if (this.mSelectedInternal == val)
446 return val;
447
448 if (this.mSelectedInternal)
449 this.mSelectedInternal.removeAttribute('selected');
450
451 this.mSelectedInternal = val;
452
453 if (val)
454 val.setAttribute('selected', 'true');
455
456 //Do NOT change the "value", which is owned by inputField
457 return val;
458 ]]>
459 </body>
460 </method>
461
462 <field name="mInputField">null</field>
463
464 <property name="inputField" readonly="true">
465 <getter><![CDATA[
466 if (!this.mInputField)
467 this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
468 return this.mInputField;
469 ]]></getter>
470 </property>
471
472 <property name="label" onset="this.inputField.value = val; return val;"
473 onget="return this.inputField.value;"/>
474
475 <property name="value" onget="return this.inputField.value;">
476 <setter>
477 <![CDATA[
478 // Override menulist's value setter to refer to the inputField's value
479 // (Allows using "menulist.value" instead of "menulist.inputField.value")
480 this.inputField.value = val;
481 this.setAttribute('value', val);
482 this.setAttribute('label', val);
483 this._selectInputFieldValueInList();
484 return val;
485 ]]>
486 </setter>
487 </property>
488
489 <property name="selectedItem">
490 <getter>
491 <![CDATA[
492 // Make sure internally-selected item
493 // is in sync with inputField.value
494 this._selectInputFieldValueInList();
495 return this.mSelectedInternal;
496 ]]>
497 </getter>
498 <setter>
499 <![CDATA[
500 var oldval = this.mSelectedInternal;
501 if (oldval == val)
502 return val;
503
504 if (val && !this.contains(val))
505 return val;
506
507 // This doesn't touch inputField.value or "value" and "label" attributes
508 this.setSelectionInternal(val);
509 if (val) {
510 // Editable menulist uses "label" as its "value"
511 var label = val.getAttribute('label');
512 this.inputField.value = label;
513 this.setAttribute('value', label);
514 this.setAttribute('label', label);
515 }
516 else {
517 this.inputField.value = "";
518 this.removeAttribute('value');
519 this.removeAttribute('label');
520 }
521
522 var event = document.createEvent("Events");
523 event.initEvent("select", true, true);
524 this.dispatchEvent(event);
525
526 var event = document.createEvent("Events");
527 event.initEvent("ValueChange", true, true);
528 this.dispatchEvent(event);
529
530 return val;
531 ]]>
532 </setter>
533 </property>
534 <property name="disableautoselect"
535 onset="if (val) this.setAttribute('disableautoselect','true');
536 else this.removeAttribute('disableautoselect'); return val;"
537 onget="return this.hasAttribute('disableautoselect');"/>
538
539 <property name="editor" readonly="true">
540 <getter><![CDATA[
541 const nsIDOMNSEditableElement = Components.interfaces.nsIDOMNSEditableElement;
542 return this.inputField.QueryInterface(nsIDOMNSEditableElement).editor;
543 ]]></getter>
544 </property>
545
546 <property name="readOnly" onset="this.inputField.readOnly = val;
547 if (val) this.setAttribute('readonly', 'true');
548 else this.removeAttribute('readonly'); return val;"
549 onget="return this.inputField.readOnly;"/>
550
551 <method name="select">
552 <body>
553 this.inputField.select();
554 </body>
555 </method>
556 </implementation>
557
558 <handlers>
559 <handler event="focus" phase="capturing">
560 <![CDATA[
561 if (!this.hasAttribute('focused')) {
562 if (event.originalTarget != this.inputField)
563 this.inputField.focus();
564 this.setAttribute('focused','true');
565 }
566 ]]>
567 </handler>
568
569 <handler event="blur" phase="capturing">
570 <![CDATA[
571 this.removeAttribute('focused');
572 ]]>
573 </handler>
574
575 <handler event="popupshowing">
576 <![CDATA[
577 // editable menulists elements aren't in the focus order,
578 // so when the popup opens we need to force the focus to the inputField
579 if (event.target.parentNode == this) {
580 if (document.commandDispatcher.focusedElement != this.inputField)
581 this.inputField.focus();
582
583 if (this.selectedItem)
584 // Not ready for auto-setting the active child in hierarchies yet.
585 // For now, only do this when the outermost menupopup opens.
586 this.menuBoxObject.activeChild = this.mSelectedInternal;
587 }
588 ]]>
589 </handler>
590
591 <handler event="keypress">
592 <![CDATA[
593 // open popup if key is up arrow, down arrow, or F4
594 if (!event.ctrlKey && !event.shiftKey) {
595 if (event.keyCode == KeyEvent.DOM_VK_UP ||
596 event.keyCode == KeyEvent.DOM_VK_DOWN ||
597 (event.keyCode == KeyEvent.DOM_VK_F4 && !event.altKey)) {
598 event.preventDefault();
599 this.open = true;
600 }
601 }
602 ]]>
603 </handler>
604 </handlers>
605 </binding>
606
607 <binding id="menulist-compact" display="xul:menu"
608 extends="chrome://global/content/bindings/menulist.xml#menulist">
609 <content sizetopopup="false">
610 <xul:dropmarker class="menulist-dropmarker" type="menu"/>
611 <xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
612 <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
613 <children includes="menupopup"/>
614 </content>
615 </binding>
616
617 <binding id="menulist-description" display="xul:menu"
618 extends="chrome://global/content/bindings/menulist.xml#menulist">
619 <content sizetopopup="pref">
620 <xul:hbox class="menulist-label-box" flex="1">
621 <xul:image class="menulist-icon" xbl:inherits="src=image,src"/>
622 <xul:label class="menulist-label" xbl:inherits="value=label,crop,accesskey" crop="right" flex="1"/>
623 <xul:label class="menulist-label menulist-description" xbl:inherits="value=description" crop="right" flex="10000"/>
624 </xul:hbox>
625 <xul:dropmarker class="menulist-dropmarker" type="menu"/>
626 <children includes="menupopup"/>
627 </content>
628 </binding>
629 </bindings>