mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-10 01:40:17 +01:00
1217 lines
42 KiB
XML
1217 lines
42 KiB
XML
<?xml version="1.0"?>
|
|
|
|
<!-- ***** BEGIN LICENSE BLOCK *****
|
|
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
-
|
|
- The contents of this file are subject to the Mozilla Public License Version
|
|
- 1.1 (the "License"); you may not use this file except in compliance with
|
|
- the License. You may obtain a copy of the License at
|
|
- http://www.mozilla.org/MPL/
|
|
-
|
|
- Software distributed under the License is distributed on an "AS IS" basis,
|
|
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
- for the specific language governing rights and limitations under the
|
|
- License.
|
|
-
|
|
- The Original Code is Mozilla XForms support.
|
|
-
|
|
- The Initial Developer of the Original Code is
|
|
- Olli Pettay
|
|
- Portions created by the Initial Developer are Copyright (C) 2005
|
|
- the Initial Developer. All Rights Reserved.
|
|
-
|
|
- Contributor(s):
|
|
- Olli Pettay <Olli.Pettay@helsinki.fi>
|
|
-
|
|
- Alternatively, the contents of this file may be used under the terms of
|
|
- either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
- in which case the provisions of the GPL or the LGPL are applicable instead
|
|
- of those above. If you wish to allow use of your version of this file only
|
|
- under the terms of either the GPL or the LGPL, and not to allow others to
|
|
- use your version of this file under the terms of the MPL, indicate your
|
|
- decision by deleting the provisions above and replace them with the notice
|
|
- and other provisions required by the GPL or the LGPL. If you do not delete
|
|
- the provisions above, a recipient may use your version of this file under
|
|
- the terms of any one of the MPL, the GPL or the LGPL.
|
|
-
|
|
- ***** END LICENSE BLOCK ***** -->
|
|
|
|
<bindings id="select1Bindings"
|
|
xmlns="http://www.mozilla.org/xbl"
|
|
xmlns:html="http://www.w3.org/1999/xhtml"
|
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
|
xmlns:xforms="http://www.w3.org/2002/xforms"
|
|
xmlns:mozType="http://www.mozilla.org/projects/xforms/2005/type">
|
|
|
|
<!-- select1 -->
|
|
<binding id="xformswidget-select1"
|
|
extends="chrome://xforms/content/xforms.xml#xformswidget-base">
|
|
|
|
<!-- The strange indentation is because of the whitespace nodes.-->
|
|
<content>
|
|
<children includes="label|hint"/>
|
|
<html:div class="-moz-xforms-select1-popup" anonid="popup"
|
|
onmouseover="this.parentNode.shouldHandleBlur = false;
|
|
this.parentNode.mouseOver(event);"
|
|
onmouseup="this.parentNode.mouseUp(event);"
|
|
onmouseout="this.parentNode.shouldHandleBlur = true;">
|
|
<children/>
|
|
</html:div>
|
|
<html:span class="-moz-select1-container"
|
|
anonid="container"><html:input
|
|
class="-moz-xforms-select1-input xf-value"
|
|
anonid="control"
|
|
xbl:inherits="accesskey, tabindex"
|
|
onfocus="this.parentNode.parentNode.dispatchDOMUIEvent('DOMFocusIn')"
|
|
onblur="this.parentNode.parentNode.handleBlur(); this.parentNode.parentNode.dispatchDOMUIEvent('DOMFocusOut');"
|
|
onclick="this.parentNode.parentNode.handleControlClick();"
|
|
onkeypress="this.parentNode.parentNode.handleKeyPress(event);"
|
|
oninput="this.parentNode.parentNode.handleInput();"
|
|
/><html:input mozType:dropmarker="true"
|
|
type="button"
|
|
anonid="dropmarker"
|
|
tabindex="-1"
|
|
onmousedown="this.parentNode.parentNode.shouldHandleBlur = false;"
|
|
onmouseup="this.parentNode.parentNode.shouldHandleBlur = true;"
|
|
onclick="this.parentNode.parentNode.togglePopup();
|
|
this.previousSibling.focus();"
|
|
/></html:span></content>
|
|
|
|
<implementation implements="nsIXFormsUIWidget, nsIXFormsNSSelect1Element">
|
|
<!-- nsIXFormsNSSelect1Element -->
|
|
<property name="selectedItem">
|
|
<getter>
|
|
return this._selected;
|
|
</getter>
|
|
<setter>
|
|
if (this._selected == val)
|
|
return;
|
|
|
|
if (this._selected)
|
|
this._selected.setActive(false);
|
|
|
|
this._selected =
|
|
val.QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
if (this._selected) {
|
|
this._selected.setActive(true);
|
|
this.updateInputField();
|
|
this._handleSelection(true);
|
|
}
|
|
</setter>
|
|
</property>
|
|
|
|
<!-- nsIXFormsComboboxUIWidget -->
|
|
<property name="open">
|
|
<getter>
|
|
return this.popupOpen;
|
|
</getter>
|
|
<setter>
|
|
if (val)
|
|
this.showPopup();
|
|
else
|
|
this.hidePopup();
|
|
</setter>
|
|
</property>
|
|
|
|
<field name="_inputField">null</field>
|
|
<field name="_dropMarker">null</field>
|
|
<field name="_popup">null</field>
|
|
<field name="_container">null</field>
|
|
<field name="_width">-1</field>
|
|
|
|
<!-- This is either an nsIXFormsItemElement or null. -->
|
|
<field name="_selected">null</field>
|
|
<field name="_tmpSelected">null</field>
|
|
<field name="_lastSelectedItem">null</field>
|
|
<field name="popupOpen">false</field>
|
|
<field name="shouldHandleBlur">true</field>
|
|
<field name="_outOfRange">false</field>
|
|
|
|
<property name="selectionOpen" readonly="true">
|
|
<getter>
|
|
return this.getAttribute("selection") == "open";
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="inputField" readonly="true">
|
|
<getter>
|
|
if (!this._inputField) {
|
|
this._inputField =
|
|
document.getAnonymousElementByAttribute(this, "anonid", "control");
|
|
}
|
|
return this._inputField;
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="dropMarker" readonly="true">
|
|
<getter>
|
|
if (!this._dropMarker) {
|
|
this._dropMarker =
|
|
document.getAnonymousElementByAttribute(this, "anonid", "dropmarker");
|
|
}
|
|
return this._dropMarker;
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="popup" readonly="true">
|
|
<getter>
|
|
if (!this._popup) {
|
|
this._popup =
|
|
document.getAnonymousElementByAttribute(this, "anonid", "popup");
|
|
}
|
|
return this._popup;
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="container" readonly="true">
|
|
<getter>
|
|
if (!this._container) {
|
|
this._container =
|
|
document.getAnonymousElementByAttribute(this, "anonid", "container");
|
|
}
|
|
return this._container;
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="incremental" readonly="true">
|
|
<getter>
|
|
<![CDATA[
|
|
// default is true
|
|
var incremental = true;
|
|
|
|
if (this.getAttribute("incremental") == "false") {
|
|
incremental = false;
|
|
}
|
|
|
|
return incremental;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="updateInputField">
|
|
<body>
|
|
<![CDATA[
|
|
if (this._selected) {
|
|
// Remove extra white space characters from the beginning of the label.
|
|
this.inputField.value = this._selected.labelText.replace(/^[\s\n]+/, "");
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="handleControlClick">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (this.selectionOpen) {
|
|
if (!this._selected && this.incremental) {
|
|
this._handleSelection(false);
|
|
return;
|
|
}
|
|
if (this.popupOpen) {
|
|
this.hidePopup();
|
|
}
|
|
return;
|
|
}
|
|
this.togglePopup();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="handleKeyPress">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
var key = aEvent.keyCode;
|
|
if (key == aEvent.DOM_VK_RETURN || key == aEvent.DOM_VK_ENTER ||
|
|
key == aEvent.DOM_VK_F4 || aEvent.altKey &&
|
|
(key == aEvent.DOM_VK_UP || key == aEvent.DOM_VK_DOWN)) {
|
|
var open = this.popupOpen;
|
|
this.togglePopup();
|
|
if (open && this._selected) {
|
|
this.updateInputField();
|
|
if (this.incremental) {
|
|
this._handleSelection(true);
|
|
} else {
|
|
this.dispatchSelectEvents();
|
|
}
|
|
}
|
|
if (aEvent.altKey && (key == aEvent.DOM_VK_UP ||
|
|
key == aEvent.DOM_VK_DOWN)) {
|
|
aEvent.preventDefault();
|
|
}
|
|
} else if (key == aEvent.DOM_VK_UP || key == aEvent.DOM_VK_DOWN) {
|
|
if (this.selectionOpen && !this.popupOpen) {
|
|
this.togglePopup();
|
|
}
|
|
this.internalScroll(aEvent.keyCode == aEvent.DOM_VK_DOWN);
|
|
if (this._selected && this.popupOpen) {
|
|
var el = this._selected.QueryInterface(Components.interfaces.nsIDOMElement);
|
|
if ("scrollIntoView" in el) {
|
|
el.scrollIntoView(false);
|
|
}
|
|
}
|
|
|
|
if (!this.popupOpen && this.incremental) {
|
|
if (this._selected) {
|
|
this.updateInputField();
|
|
}
|
|
this._handleSelection(true);
|
|
}
|
|
aEvent.preventDefault();
|
|
} else if (key == aEvent.DOM_VK_TAB) {
|
|
// Hiding popup when user uses keyboard to focus out
|
|
// from <select1>. No need to update the value of the control
|
|
// in this case.
|
|
if (this._selected && this.popupOpen) {
|
|
this.hidePopup();
|
|
this._selected.setActive(false);
|
|
this._selected = this._tmpSelected;
|
|
this._tmpSelected = null;
|
|
if (this._selected) {
|
|
this._selected.setActive(true);
|
|
}
|
|
}
|
|
} else if (aEvent.charCode && !this.selectionOpen) {
|
|
// Cache sequence of last pressed keys and search it in value of
|
|
// xforms:label inside xforms:item elements.
|
|
var currtime = new Date().valueOf();
|
|
var char = String.fromCharCode(aEvent.charCode);
|
|
if (currtime - this.searchKeypressTime <= 1000)
|
|
this.searchValue += char;
|
|
else
|
|
this.searchValue = char;
|
|
|
|
this.selectItemByLabel(this.searchValue);
|
|
this.searchKeypressTime = currtime;
|
|
}
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<field name="searchValue">""</field>
|
|
<field name="searchKeypressTime">0</field>
|
|
|
|
<method name="handleInput">
|
|
<body>
|
|
<![CDATA[
|
|
if (this._selected) {
|
|
this._selected.setActive(false);
|
|
this._selected = null;
|
|
}
|
|
if (this.incremental) {
|
|
this._handleSelection(false);
|
|
}
|
|
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- This is used by internalScroll to find the next
|
|
selectable <item>. Returns a DOMElement or null. -->
|
|
<method name="findNextSelectable">
|
|
<parameter name="aNode"/>
|
|
<parameter name="aDown"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!aNode) {
|
|
return null;
|
|
}
|
|
|
|
var node = aNode;
|
|
var next = null;
|
|
while (node) {
|
|
if (aDown) {
|
|
next = node.nextSibling;
|
|
} else {
|
|
next = node.previousSibling;
|
|
}
|
|
|
|
if (next && next.namespaceURI == this.XFORMS_NS) {
|
|
if (next.localName == "item") {
|
|
return next;
|
|
} else if (next.localName == "choices") {
|
|
var child = null;
|
|
if (aDown) {
|
|
child = next.firstChild;
|
|
} else {
|
|
child = next.lastChild;
|
|
}
|
|
if (child.namespaceURI == this.XFORMS_NS &&
|
|
child.localName == "item") {
|
|
return child;
|
|
}
|
|
child = this.findNextSelectable(child, aDown);
|
|
if (child) {
|
|
return child;
|
|
}
|
|
} else if (next.localName == "itemset" &&
|
|
next.anonymousItemSetContent.childNodes.length > 0) {
|
|
return aDown ? next.anonymousItemSetContent.firstChild
|
|
: next.anonymousItemSetContent.lastChild;
|
|
}
|
|
}
|
|
node = next;
|
|
}
|
|
|
|
// if we are in a choices or itemset element
|
|
var parent = aNode.parentNode;
|
|
if (parent.namespaceURI == this.XFORMS_NS &&
|
|
parent.localName == "choices") {
|
|
var sibling = aDown ? parent.nextSibling : parent.previousSibling;
|
|
|
|
if (sibling.namespaceURI == this.XFORMS_NS &&
|
|
sibling.localName == "item") {
|
|
return sibling;
|
|
}
|
|
return this.findNextSelectable(sibling, aDown);
|
|
}
|
|
|
|
if (parent.parentNode.namespaceURI == this.XFORMS_NS &&
|
|
parent.parentNode.localName == "itemset") {
|
|
var sibling2 = aDown
|
|
? parent.parentNode.nextSibling
|
|
: parent.parentNode.previousSibling;
|
|
|
|
if (sibling2.namespaceURI == this.XFORMS_NS &&
|
|
sibling2.localName == "item") {
|
|
return sibling2;
|
|
}
|
|
return this.findNextSelectable(sibling2, aDown);
|
|
}
|
|
|
|
return null;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="internalScroll">
|
|
<parameter name="aDown"/>
|
|
<body>
|
|
<![CDATA[
|
|
var label = null;
|
|
var next = null, nextItem = null;
|
|
var node = this.firstChild;
|
|
while (node) {
|
|
if (node.namespaceURI == this.XFORMS_NS &&
|
|
node.localName == "label") {
|
|
label = node;
|
|
break;
|
|
}
|
|
node = node.nextSibling;
|
|
}
|
|
|
|
if (this._selected) {
|
|
node = this._selected.QueryInterface(Components.interfaces.nsIDOMNode);
|
|
} else if (label) {
|
|
node = label.nextSibling;
|
|
} else {
|
|
node = this.firstChild;
|
|
}
|
|
|
|
next = this.findNextSelectable(node, aDown);
|
|
if (next) {
|
|
if (this._selected) {
|
|
this._lastSelectedItem = this._selected;
|
|
this._selected.setActive(false);
|
|
this._selected = null;
|
|
}
|
|
|
|
nextItem = next.QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
if (nextItem) {
|
|
this._selected = nextItem;
|
|
this._selected.setActive(true);
|
|
if (!this.selectionOpen)
|
|
this.updateInputField();
|
|
}
|
|
return true;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="mouseUp">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
// Use original target, because <item> is possible (anonymously)
|
|
// inside <itemset>
|
|
var target = aEvent.originalTarget;
|
|
while (target && target != this) {
|
|
if (target.namespaceURI == this.XFORMS_NS &&
|
|
(target.localName == "item" || target.localName == "choices")) {
|
|
break;
|
|
}
|
|
target = target.parentNode;
|
|
}
|
|
|
|
if (target == this) {
|
|
return true;
|
|
}
|
|
|
|
this.hidePopup();
|
|
|
|
if (this._selected) {
|
|
this.updateInputField();
|
|
if (this.incremental) {
|
|
this._handleSelection(true);
|
|
} else {
|
|
this.dispatchSelectEvents();
|
|
}
|
|
}
|
|
this.inputField.focus();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="mouseOver">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
// Use original target, because <item> is possible (anonymously)
|
|
// inside <itemset>
|
|
var target = aEvent.originalTarget;
|
|
while (target && target != this) {
|
|
if (target.namespaceURI == this.XFORMS_NS && target.localName == "item") {
|
|
if (this._selected) {
|
|
this._selected.setActive(false);
|
|
this._selected = null;
|
|
}
|
|
|
|
var item =
|
|
target.QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
if (item) {
|
|
item.setActive(true);
|
|
this._selected = item;
|
|
}
|
|
break;
|
|
}
|
|
target = target.parentNode;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="refreshWidth">
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.popupOpen) {
|
|
this.inputField.removeAttribute("style");
|
|
this.popup.style.width = "auto";
|
|
this.popup.style.height = "auto";
|
|
this.popup.style.maxHeight = "none";
|
|
this._width = -1;
|
|
var popupBox = document.getBoxObjectFor(this.popup);
|
|
var w = popupBox.width;
|
|
if (w > 0) {
|
|
w = w + 12; // Adding some 'padding' for possible scrollbar
|
|
this.inputField.setAttribute("style", "width:" + w + "px;");
|
|
this._width = w + document.getBoxObjectFor(this.dropMarker).width;
|
|
}
|
|
this.popup.style.maxHeight = "10px";
|
|
this.popup.style.left = "0px";
|
|
this.popup.style.top = "0px";
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="hidePopup">
|
|
<body>
|
|
this.popup.style.visibility = "hidden";
|
|
this.popupOpen = false;
|
|
</body>
|
|
</method>
|
|
|
|
<method name="showPopup">
|
|
<body>
|
|
<![CDATA[
|
|
if (this.popupOpen || this.accessors.isReadonly())
|
|
return;
|
|
|
|
// Calculating the size and position of the popup.
|
|
var style = "";
|
|
var containerBox = document.getBoxObjectFor(this.container);
|
|
var x;
|
|
var y;
|
|
var adjust = 0;
|
|
var absolute = false;
|
|
var absoluteOffsetY = 0;
|
|
var p = this.container.offsetParent;
|
|
var compStyle =
|
|
document.defaultView.getComputedStyle(p, null);
|
|
if (compStyle.getPropertyValue("position") != "absolute" &&
|
|
compStyle.getPropertyValue("position") != "relative") {
|
|
adjust = document.documentElement.offsetTop;
|
|
x = containerBox.x;
|
|
y = containerBox.y;
|
|
} else {
|
|
absolute = true;
|
|
x = this.container.offsetLeft;
|
|
y = this.container.offsetTop;
|
|
absoluteOffsetY = y;
|
|
while (p) {
|
|
absoluteOffsetY += p.offsetTop;
|
|
p = p.offsetParent;
|
|
}
|
|
}
|
|
|
|
var h = containerBox.height;
|
|
var w = containerBox.width;
|
|
var targetY = y + h;
|
|
this.popup.style.maxHeight = "none";
|
|
var popupBox = document.getBoxObjectFor(this.popup);
|
|
var popupHeight = popupBox.height;
|
|
var pY = window.pageYOffset;
|
|
var iH = window.innerHeight;
|
|
var belowSelect = absolute
|
|
? (iH - (absoluteOffsetY + h) - h)
|
|
: (iH - (y - pY + h - adjust) - h);
|
|
|
|
if (belowSelect < popupHeight) {
|
|
if ((y - pY) > popupHeight) {
|
|
targetY = y - popupHeight;
|
|
} else if (belowSelect < (y - pY)) {
|
|
style = style + "max-height:" + (y - pY - adjust) + "px;";
|
|
targetY = pY + adjust;
|
|
} else {
|
|
style = style + "max-height:" + belowSelect + "px;";
|
|
}
|
|
}
|
|
style = style + "left:" + x + "px;";
|
|
style = style + "top:" + targetY + "px;";
|
|
|
|
style = style + "width:";
|
|
if (this.selectionOpen) {
|
|
style = style + w + "px;";
|
|
} else if (this._width < 0) {
|
|
style = style + "auto;"
|
|
} else {
|
|
style = style + this._width + "px;";
|
|
}
|
|
|
|
style = style + "visibility:visible;";
|
|
this.popup.setAttribute("style", style);
|
|
this.popupOpen = true;
|
|
this._tmpSelected = this._selected;
|
|
|
|
if (this._selected) {
|
|
var el = this._selected.QueryInterface(Components.interfaces.nsIDOMElement);
|
|
if ("scrollIntoView" in el) {
|
|
el.scrollIntoView(false);
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="togglePopup">
|
|
<body>
|
|
if (!this.popupOpen)
|
|
this.showPopup();
|
|
else
|
|
this.hidePopup();
|
|
</body>
|
|
</method>
|
|
|
|
<method name="refresh">
|
|
<body>
|
|
<![CDATA[
|
|
try {
|
|
if (this._selected) {
|
|
// Verify that the selected item that we know and love is still
|
|
// valid. If it were part of an itemset, this refresh might be
|
|
// happening due to an itemset refresh which means that this item
|
|
// may have already been removed from the DOM. This is just a
|
|
// hack to work around the fact that the original patch for bug
|
|
// 360188 won't work on the branches due to an issue where
|
|
// DOMNodeRemoved isn't ever being dispatched. This will all be
|
|
// fixed when we rework the select/select1, again, to better
|
|
// handle items being dynamically inserted and removed
|
|
// (bug 372197).
|
|
if (!this._selected.parentNode) {
|
|
this._selected = null;
|
|
this._lastSelectedItem = null;
|
|
}
|
|
}
|
|
|
|
var nodeValue = null, newValue = null;
|
|
var boundNode = this.accessors.getBoundNode();
|
|
var outOfRange = false;
|
|
if (boundNode && boundNode.hasChildNodes()) {
|
|
// Since this is a select1, there should normally be just one
|
|
// child node here. But no guarantee that a select1 generated
|
|
// the value coming in. So we'll look for text node with
|
|
// non-whitespace characters to compare with an item's xf:value.
|
|
// Any other node that we encounter we look to match with an
|
|
// item's xf:copy. If more than one of either of these exists
|
|
// in the instance data, we need to generate a xforms-out-of-range
|
|
// event and style the select1 as out-of-range since by
|
|
// definition a select1 can not select more than one item.
|
|
var child = boundNode.firstChild;
|
|
while (child) {
|
|
var type = child.nodeType;
|
|
if (type == Node.TEXT_NODE) {
|
|
// if child is a text node completely filled with
|
|
// whitespace let's ignore it and get the next node
|
|
var string = child.nodeValue;
|
|
var nonWhitespace = false;
|
|
if (string) {
|
|
// this regexp tests whether only whitespace is contained
|
|
// between the beginning and ending of the string.
|
|
nonWhitespace = !(/^\s*$/.test(string));
|
|
}
|
|
if (nonWhitespace) {
|
|
if (newValue || nodeValue) {
|
|
// oh oh! We've already found a selectable node in the
|
|
// instance data and now we have another. That shouldn't
|
|
// happen.
|
|
outOfRange = true;
|
|
}
|
|
newValue = string;
|
|
}
|
|
} else {
|
|
// if it's not a text node, we'll assume that we are looking at
|
|
// a node worth comparing. As such, look for an
|
|
// item with a copy element that might match this node.
|
|
if (newValue || nodeValue) {
|
|
// oh oh! We've already found a selectable node in the
|
|
// instance data and now we have another. That shouldn't
|
|
// happen.
|
|
outOfRange = true;
|
|
}
|
|
nodeValue = child;
|
|
}
|
|
|
|
if (child == boundNode.lastChild) {
|
|
break;
|
|
}
|
|
child = child.nextSibling;
|
|
}
|
|
}
|
|
|
|
if (outOfRange) {
|
|
// a control can't be out of range (or in range for that matter)
|
|
// if there is no selected value, so make sure that there is a
|
|
// bound node first.
|
|
if ((outOfRange != this._outOfRange) && boundNode) {
|
|
this._outOfRange = outOfRange;
|
|
this.accessors.setInRange(false);
|
|
}
|
|
|
|
// can't possibly work, no sense continuing.
|
|
this.inputField.value = "";
|
|
this._selected.setActive(false);
|
|
this._selected = null;
|
|
this.refreshWidth();
|
|
return false;
|
|
}
|
|
|
|
if (!this.selectionOpen || this.accessors.isReadonly()) {
|
|
this.inputField.setAttribute("readonly", "readonly");
|
|
} else {
|
|
this.inputField.removeAttribute("readonly");
|
|
}
|
|
|
|
if (this._selected && !this._selected.isCopyItem) {
|
|
var envelope = this._getSelectedValue();
|
|
if (envelope) {
|
|
var textNode = null;
|
|
if (envelope.nodeType == Node.ELEMENT_NODE) {
|
|
textNode = envelope.firstChild;
|
|
if (newValue == textNode.nodeValue) {
|
|
// Value in instance data already selected. Need to only
|
|
// refresh the width.
|
|
this.refreshWidth();
|
|
return true;
|
|
}
|
|
} else {
|
|
if (newValue == envelope.nodeValue) {
|
|
// Value in instance data already selected. Need to only
|
|
// refresh the width.
|
|
this.refreshWidth();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
this._selected.setActive(false);
|
|
this._selected = null;
|
|
this._lastSelectedItem = null;
|
|
}
|
|
|
|
if (newValue) {
|
|
this.selectItemByValue(newValue);
|
|
} else if (nodeValue) {
|
|
this.selectItemByNode(nodeValue);
|
|
}
|
|
|
|
outOfRange = false;
|
|
if (this._selected) {
|
|
this.updateInputField();
|
|
this._lastSelectedItem = this._selected;
|
|
} else if (this.selectionOpen) {
|
|
this.inputField.value = newValue;
|
|
} else {
|
|
this.inputField.value = "";
|
|
outOfRange = true;
|
|
}
|
|
|
|
// a control can't be out of range (or in range for that matter)
|
|
// if there is no selected value, so make sure that there is a
|
|
// bound node first.
|
|
if ((outOfRange != this._outOfRange) && boundNode) {
|
|
this._outOfRange = outOfRange;
|
|
this.accessors.setInRange(!outOfRange);
|
|
}
|
|
this.refreshWidth();
|
|
} catch (ex) {}
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="focus">
|
|
<body>
|
|
this.inputField.focus();
|
|
return true;
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getCurrentValue">
|
|
<body>
|
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
|
</body>
|
|
</method>
|
|
|
|
<method name="selectItemByLabel">
|
|
<parameter name="aValue"/>
|
|
<parameter name="aContextNode"/>
|
|
<body>
|
|
<![CDATA[
|
|
var item =
|
|
this.searchItemByLabel(new RegExp("^" + aValue, "i"), this);
|
|
|
|
if (this._selected) {
|
|
this._selected.setActive(false);
|
|
this._selected = null;
|
|
}
|
|
this._selected = item;
|
|
if (this._selected) {
|
|
this._selected.setActive(true);
|
|
this.updateInputField();
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="searchItemByLabel">
|
|
<parameter name="aExp"/>
|
|
<parameter name="aContextNode"/>
|
|
<body>
|
|
<![CDATA[
|
|
for (var child = aContextNode.firstChild; child;
|
|
child = child.nextSibling) {
|
|
if (child.namespaceURI != this.XFORMS_NS)
|
|
continue;
|
|
|
|
switch (child.localName) {
|
|
case "item":
|
|
child = child.
|
|
QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
if (child.labelText.search(aExp) != -1) {
|
|
return child;
|
|
}
|
|
break;
|
|
case "choices":
|
|
var item = this.searchItemByLabel(aExp, child);
|
|
if (item)
|
|
return item;
|
|
break;
|
|
case "itemset":
|
|
var item =
|
|
this.searchItemByLabel(aExp, child.anonymousItemSetContent);
|
|
if (item)
|
|
return item;
|
|
break;
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="selectItemByValue">
|
|
<parameter name="aValue"/>
|
|
<body>
|
|
<![CDATA[
|
|
var node = this.firstChild;
|
|
var item;
|
|
while (node) {
|
|
item = null;
|
|
try {
|
|
if (node.nodeType == document.ELEMENT_NODE &&
|
|
node.namespaceURI == this.XFORMS_NS &&
|
|
node.localName != "label") {
|
|
|
|
item = node.QueryInterface(Components.interfaces.nsIXFormsSelectChild);
|
|
if (item) {
|
|
item = item.selectItemByValue(aValue);
|
|
if (item) {
|
|
if (this._selected) {
|
|
this._selected.setActive(false);
|
|
this._selected = null;
|
|
}
|
|
this._selected = item.QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
if (this._selected) {
|
|
this._selected.setActive(true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (ex) {}
|
|
node = node.nextSibling;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="selectItemByNode">
|
|
<parameter name="aNode"/>
|
|
<body>
|
|
<![CDATA[
|
|
|
|
// select the copyItem in this select1 whose copyNode equals aNode
|
|
|
|
var node = this.firstChild;
|
|
var item;
|
|
while (node) {
|
|
item = null;
|
|
try {
|
|
if (node.nodeType == document.ELEMENT_NODE &&
|
|
node.namespaceURI == this.XFORMS_NS &&
|
|
node.localName != "label") {
|
|
|
|
item = node.QueryInterface(Components.interfaces.nsIXFormsSelectChild);
|
|
if (item) {
|
|
item = item.selectItemByNode(aNode);
|
|
if (item) {
|
|
if (this._selected) {
|
|
this._selected.setActive(false);
|
|
this._selected = null;
|
|
}
|
|
this._selected = item.QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
if (this._selected) {
|
|
this._selected.setActive(true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (ex) {}
|
|
node = node.nextSibling;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="handleBlur">
|
|
<body>
|
|
<![CDATA[
|
|
if (this.shouldHandleBlur) {
|
|
var open = this.popupOpen;
|
|
this.hidePopup();
|
|
|
|
if (open) {
|
|
if (this._selected) {
|
|
this._selected.setActive(false);
|
|
this._selected = this._tmpSelected;
|
|
this._tmpSelected = null;
|
|
if (this._selected) {
|
|
this._selected.setActive(true);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (this._selected) {
|
|
this.updateInputField();
|
|
}
|
|
|
|
this._handleSelection(false, true);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- _handleSelection updates the bound node with the value from the
|
|
currently selected item's value element or copy element. -->
|
|
<method name="_handleSelection">
|
|
<parameter name="aDispatchSelectEvents"/>
|
|
<parameter name="aInBlur"/>
|
|
<body>
|
|
<![CDATA[
|
|
// if aDispatchSelectEvents is true, then we need to make sure to
|
|
// dispatch the xforms-deselect and xforms-select events before we
|
|
// change the value of the bound node otherwise we'll get the
|
|
// event ordering wrong. Similar with setting out of/in range (must
|
|
// happen after dispatch select/deselect). aDispatchSelectEvents is
|
|
// a REQUIRED parameter, whether it be true or false.
|
|
|
|
// aInBlur is not a required parameter. It is true if handleSelection
|
|
// was called from the blur handler.
|
|
|
|
var boundNode = this.accessors.getBoundNode();
|
|
if (!boundNode) {
|
|
return;
|
|
}
|
|
|
|
if (this.selectionOpen && !this._selected) {
|
|
if (aDispatchSelectEvents == true) {
|
|
this.dispatchSelectEvents();
|
|
}
|
|
this.accessors.setValue(this.inputField.value);
|
|
|
|
// open selection can't be out of range
|
|
this._outOfRange = false;
|
|
this.accessors.setInRange(true);
|
|
return;
|
|
}
|
|
|
|
if (!this._selected) {
|
|
// no reason to continue
|
|
return;
|
|
}
|
|
|
|
if (aInBlur && aInBlur == true) {
|
|
// if _handleSelection is called due to a blur, we only really care
|
|
// about making sure the bound node is in sync if @incremental is
|
|
// false. Otherwise the bound node is already up to date so might
|
|
// as well return.
|
|
if (this.incremental) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (boundNode.nodeType != boundNode.ELEMENT_NODE) {
|
|
// if the boundNode type isn't an ELEMENT_NODE, then contentEnvelope
|
|
// isn't an ELEMENT_NODE (since it is a clone of the bound node).
|
|
// So if contentEnvelope has a value, it will be the nodeValue.
|
|
var envelope = this._getSelectedValue();
|
|
if (envelope) {
|
|
if (aDispatchSelectEvents == true) {
|
|
this.dispatchSelectEvents();
|
|
}
|
|
this.accessors.setValue(envelope.nodeValue);
|
|
this._outOfRange = false;
|
|
this.accessors.setInRange(true);
|
|
return;
|
|
}
|
|
|
|
// not allowed to copy a node under a non ELEMENT node, so
|
|
// generating a binding exception per spec.
|
|
var ev = document.createEvent("Events");
|
|
ev.initEvent("xforms-binding-exception", true, false);
|
|
this.dispatchEvent(ev);
|
|
|
|
// well, whatever we had selected isn't going to cut it. But the
|
|
// user did choose to deselect the previous item in favor of this
|
|
// this item, so we really shouldn't go back to what
|
|
// was there before. So we'll go to nothing. Make sure bound
|
|
// node reflects this. Seems to be consistent with what Novell
|
|
// and formsPlayer does, too.
|
|
this._selected.setActive(false);
|
|
this._selected = null;
|
|
this.inputField.value = "";
|
|
|
|
if (aDispatchSelectEvents == true) {
|
|
this.dispatchSelectEvents();
|
|
}
|
|
|
|
this.accessors.setValue("");
|
|
this._outOfRange = true;
|
|
this.accessors.setInRange(false);
|
|
return;
|
|
}
|
|
|
|
var contentEnvelope = this._getSelectedValue();
|
|
var copyInvolved = this._selected.isCopyItem;
|
|
if (!copyInvolved && this._lastSelectedItem) {
|
|
copyInvolved = this._lastSelectedItem.isCopyItem;
|
|
}
|
|
if (!copyInvolved) {
|
|
// Since we aren't selecting a copyItem nor causing a copyItem to
|
|
// be deselected, no sense using setContent. Too expensive.
|
|
if (aDispatchSelectEvents == true) {
|
|
this.dispatchSelectEvents();
|
|
}
|
|
this.accessors.setValue(contentEnvelope.textContent);
|
|
this._outOfRange = false;
|
|
this.accessors.setInRange(true);
|
|
} else {
|
|
if (aDispatchSelectEvents == true) {
|
|
this.dispatchSelectEvents();
|
|
}
|
|
this.accessors.setContent(contentEnvelope, true);
|
|
this._outOfRange = false;
|
|
this.accessors.setInRange(true);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="dispatchSelectEvents">
|
|
<body>
|
|
<![CDATA[
|
|
if (this._lastSelectedItem != this._selected) {
|
|
if (this._lastSelectedItem) {
|
|
this.dispatchSelectEvent(this._lastSelectedItem, "xforms-deselect");
|
|
}
|
|
|
|
if (this._selected) {
|
|
this.dispatchSelectEvent(this._selected, "xforms-select");
|
|
}
|
|
|
|
this._lastSelectedItem = this._selected;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="dispatchSelectEvent">
|
|
<parameter name="aElement"/>
|
|
<parameter name="aName"/>
|
|
<body>
|
|
<![CDATA[
|
|
var ev = document.createEvent("Events");
|
|
ev.initEvent(aName, true, false);
|
|
|
|
var elm = aElement;
|
|
|
|
// per errata for XForms 1.0 second edition, we send the event to the
|
|
// item, even if it is contained in an itemset
|
|
|
|
elm.dispatchEvent(ev);
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_getSelectedValue">
|
|
<body>
|
|
<![CDATA[
|
|
// The purpose of this function is to return the select1's currently
|
|
// selected item's value in a contentEnvelope. It achieves this by
|
|
// cloning the bound node to create the contentEnvelope which will be
|
|
// returned. If the contentEnvelope is an element node, the contents
|
|
// of the selected item's value will be inserted as a child of the
|
|
// contentEnvelope. If it is a textnode, the selected item value will
|
|
// be put in the contentEnvelope.nodeValue.
|
|
|
|
if (!this._selected) {
|
|
// this will probably only happen if there was an exception
|
|
// somewhere else first. But no sense continuing below and adding
|
|
// more exceptions to the console.
|
|
return null;
|
|
}
|
|
|
|
var boundNode = this.accessors.getBoundNode();
|
|
if (!boundNode) {
|
|
return null;
|
|
}
|
|
|
|
var contentEnvelope = boundNode.cloneNode(false);
|
|
if (!contentEnvelope) {
|
|
return null;
|
|
}
|
|
var contentDocument = contentEnvelope.ownerDocument;
|
|
|
|
if (contentEnvelope.nodeType == Node.ELEMENT_NODE) {
|
|
var contentNode = null;
|
|
if (this._selected.isCopyItem) {
|
|
var copyNode = this._selected.copyNode;
|
|
if (copyNode) {
|
|
contentNode = contentDocument.importNode(copyNode, true);
|
|
}
|
|
} else {
|
|
contentNode =
|
|
contentDocument.createTextNode(this._selected.value);
|
|
}
|
|
|
|
contentEnvelope.appendChild(contentNode);
|
|
} else {
|
|
// if the selected item is not a copyItem, then we'll just put the
|
|
// item's value in the nodeValue for the contentEnvelope. Otherwise
|
|
// we are stuck trying to stick an element node under a non-element
|
|
// node and that ainna gonna work.
|
|
if (!this._selected.isCopyItem) {
|
|
contentEnvelope.nodeValue = this._selected.value;
|
|
} else {
|
|
contentEnvelope = null;
|
|
}
|
|
}
|
|
|
|
return contentEnvelope;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="keypress" keycode="VK_ESCAPE">
|
|
if (this.popupOpen) {
|
|
this.hidePopup();
|
|
if (this._selected)
|
|
this._selected.setActive(false);
|
|
this._selected = this._tmpSelected;
|
|
this._tmpSelected = null;
|
|
if (this._selected)
|
|
this._selected.setActive(true);
|
|
}
|
|
}
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
|
|
<!-- The binding for <item> is needed only because of the
|
|
scrollIntoView method. -->
|
|
<binding id="xformswidget-select1-item">
|
|
<content>
|
|
<html:div anonid="content">
|
|
<children/>
|
|
</html:div>
|
|
</content>
|
|
|
|
<implementation>
|
|
<field name="_content">null</field>
|
|
<property name="content" readonly="true">
|
|
<getter>
|
|
if (!this._content) {
|
|
this._content =
|
|
document.getAnonymousElementByAttribute(this, "anonid", "content");
|
|
}
|
|
return this._content;
|
|
</getter>
|
|
</property>
|
|
|
|
<!-- Only (X)HTML elements have method 'scrollIntoView',
|
|
so we need to forward this method call to the anonymous content
|
|
of this element. -->
|
|
<method name="scrollIntoView">
|
|
<parameter name="aTop"/>
|
|
<body>
|
|
// this.content is null if anonymous content hasn't been created yet.
|
|
var content = this.content;
|
|
if (content) {
|
|
content.scrollIntoView(aTop);
|
|
}
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
</binding>
|
|
</bindings>
|