mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-10 01:40:17 +01:00
1208 lines
43 KiB
XML
1208 lines
43 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
|
|
- IBM Corporation.
|
|
- Portions created by the Initial Developer are Copyright (C) 2005
|
|
- the Initial Developer. All Rights Reserved.
|
|
-
|
|
- Contributor(s):
|
|
- Doron Rosenberg <doronr@us.ibm.com>
|
|
- Olli Pettay <Olli.Pettay@helsinki.fi>
|
|
- Alexander Surkov <surkov.alexander@gmail.com>
|
|
-
|
|
- 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="xformsSelectBindings"
|
|
xmlns="http://www.mozilla.org/xbl"
|
|
xmlns:html="http://www.w3.org/1999/xhtml"
|
|
xmlns:xbl="http://www.mozilla.org/xbl">
|
|
|
|
<!--
|
|
This file implements the "abstract" UI class for XForms select controls. It
|
|
have "pure virtual" functions that it expect to be implemented by concrete
|
|
application and returned in the getElementControl() call. An example is the
|
|
controls for XHTML in select-xhtml.xml.
|
|
|
|
The "abstact" UI class parses the child nodes of the xforms:select and then
|
|
constructs the anonymous content programmatically.
|
|
|
|
The interface of object returned by getElementControl() method is:
|
|
|
|
readonly - set/get readonly state
|
|
|
|
appendItem(label, value, group) - appends item, returns item control
|
|
@param label - label control
|
|
@param value - item's value
|
|
@param group - parent group
|
|
|
|
appendGroup(label, group) - appends group, returns group control
|
|
@param label - label control
|
|
@param group - parent group
|
|
|
|
removeAllItems() - remove all items
|
|
|
|
addItemToSelection(item) - selects item
|
|
@param item - item control returned by appendItem()
|
|
|
|
removeItemFromSelection(item) - unselects item
|
|
@param item - item control returned by appendItem()
|
|
|
|
isItemSelected(item) - return true if item is selected
|
|
@param item - item control returned by appendItem()
|
|
|
|
getFreeEntryValues() - return space delimited list of free entry items values
|
|
|
|
allowFreeEntry(allowed) - enable/disable free entry
|
|
@param allowed - boolean value
|
|
|
|
appendFreeEntryItem(value) - append and select free entry item
|
|
@param value - item value
|
|
-->
|
|
|
|
<!-- BASE for select/select1 elements. -->
|
|
<binding id="xformswidget-selectcontrols-base"
|
|
extends="chrome://xforms/content/xforms.xml#xformswidget-base">
|
|
|
|
<implementation implements="nsIXFormsUIWidget">
|
|
|
|
<!-- Make sure we don't refresh while we are refreshing (race condition).
|
|
This happens when we are inside a repeat for example. We use the
|
|
_refreshing field to store if we are refreshing or not. -->
|
|
<field name="_refreshing">false</field>
|
|
<method name="refresh">
|
|
<body>
|
|
<![CDATA[
|
|
if (this._refreshing)
|
|
return true;
|
|
|
|
this.control.readonly = this.accessors.isReadonly();
|
|
|
|
this._refreshing = true;
|
|
|
|
// if this node contains a non TEXT node, then we have to throw
|
|
// the 'just string values' logic out the window
|
|
var boundNode = this.accessors.getBoundNode();
|
|
var containsNonText = false;
|
|
if (boundNode && boundNode.hasChildNodes()) {
|
|
var child = boundNode.firstChild;
|
|
while (child) {
|
|
var type = child.nodeType;
|
|
if (type != Node.TEXT_NODE && type != Node.CDATA_SECTION_NODE) {
|
|
containsNonText = true;
|
|
this._accessorValueCache = null;
|
|
break;
|
|
}
|
|
child = child.nextSibling;
|
|
}
|
|
}
|
|
|
|
// We detect if the instance data we bind to has changed. If it has,
|
|
// changed, we simply update the selection. If it hasn't, that means
|
|
// we rebuild the select UI. We also rebuild if the accessor cache is
|
|
// null (first load).
|
|
if (this._accessorValueCache == null ||
|
|
this._accessorValueCache == this.accessors.getValue() ||
|
|
containsNonText) {
|
|
// if we reached here and the instance data only contains text
|
|
// nodes, then we need to rebuild the control since we know it
|
|
// wasn't due to a simple instance data changing scenario. But if
|
|
// the bound node contains non TEXT nodes, then it is too expensive
|
|
// to figure out if this was because a child node changed somewhere
|
|
// along the way. We'd basically have to cache the whole bound node
|
|
// subtree to compare against. To avoid this we'll just rebuild the
|
|
// control from scratch.
|
|
|
|
// XXX at a future time we need to figure out which will be more
|
|
// efficient give the most probable use cases.
|
|
|
|
// builds select UI
|
|
this._buildDefaultValues(containsNonText);
|
|
this._buildSelect();
|
|
|
|
} else if (!containsNonText) {
|
|
// update selection
|
|
this._updateDefaultValues();
|
|
this._updateSelection();
|
|
|
|
// store the accessor value
|
|
this._accessorValueCache = this.accessors.getValue();
|
|
}
|
|
|
|
this._refreshing = false;
|
|
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="focus">
|
|
<body>
|
|
<![CDATA[
|
|
this.control.focus();
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getCurrentValue">
|
|
<body>
|
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
|
</body>
|
|
</method>
|
|
|
|
<property name="incremental">
|
|
<getter>
|
|
<![CDATA[
|
|
// default is true
|
|
var incremental = true;
|
|
|
|
if (this.hasAttribute("incremental")) {
|
|
if (this.getAttribute("incremental") == "false")
|
|
incremental = false;
|
|
}
|
|
|
|
return incremental;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="selection">
|
|
<getter>
|
|
return this.getAttribute("selection");
|
|
</getter>
|
|
<setter>
|
|
this.setAttribute("selection", val);
|
|
|
|
if (val == "open")
|
|
this.control.allowFreeEntry(true);
|
|
else
|
|
this.control.allowFreeEntry(false);
|
|
</setter>
|
|
</property>
|
|
|
|
<method name="_buildSelect">
|
|
<body>
|
|
<![CDATA[
|
|
// select builds its own UI by parsing it's children.
|
|
|
|
// clear the UI children
|
|
this.control.removeAllItems();
|
|
|
|
// create children
|
|
var child, option;
|
|
var childNodes = this.childNodes;
|
|
|
|
// these hold an array of generated HTML controls
|
|
this._controlArray = new Array();
|
|
|
|
for (var i = 0; i < childNodes.length; i++) {
|
|
child = childNodes[i];
|
|
|
|
// we only care about element nodes in the XForms namespace.
|
|
if (child.nodeType != child.ELEMENT_NODE ||
|
|
child.namespaceURI != this.XFORMS_NS) {
|
|
continue;
|
|
}
|
|
|
|
switch (child.localName) {
|
|
case "item":
|
|
this._buildItem(child);
|
|
break;
|
|
case "choices":
|
|
this._buildChoices(child);
|
|
break;
|
|
case "itemset":
|
|
this._buildItemset(child);
|
|
break;
|
|
}
|
|
}
|
|
|
|
var outOfRange = false;
|
|
var selectionOpen = this.selection == 'open';
|
|
// check if any default values were not found
|
|
for (var index in this._defaultHash) {
|
|
if (this._defaultHash[index].hits == 0) {
|
|
if (selectionOpen) {
|
|
this.control.appendFreeEntryItem(index);
|
|
} else {
|
|
// some of default values not found, we need to throw an
|
|
// xforms-out-of-range event, but only if the select is 'closed'.
|
|
outOfRange = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if any default elements were not found
|
|
for (var j = 0; j < this._selectedElementArray.length; j++) {
|
|
if (this._selectedElementArray[j].hits == 0) {
|
|
if (selectionOpen) {
|
|
// XXX: If the select is open, the missing elements should be added
|
|
// and selected per 8.1.10 in the spec.
|
|
} else {
|
|
// some of default values not found, we need to throw an
|
|
// xforms-out-of-range event, but only if the select is 'closed'.
|
|
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 (!selectionOpen && this.accessors.hasBoundNode() &&
|
|
(outOfRange != this._outOfRange)) {
|
|
this._outOfRange = outOfRange;
|
|
this.accessors.setInRange(!outOfRange);
|
|
}
|
|
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_buildDefaultValues">
|
|
<parameter name="aContainsNonText"/>
|
|
<body><![CDATA[
|
|
// builds default values hash
|
|
|
|
// if accessors.value has something, then only text node(s) should
|
|
// exist under the bound node
|
|
|
|
// create a hash from the default values so we can store how often
|
|
// we encountered them. This allows us to figure out later if any
|
|
// were not hit, which requires us to send an event.
|
|
this._defaultHash = new Object();
|
|
|
|
// holds an array of DOMElements that exist under bound node,
|
|
this._selectedElementArray = new Array();
|
|
|
|
if (!aContainsNonText) {
|
|
var selectedArray = this._getValuesArray();
|
|
|
|
for (var run = 0; run < selectedArray.length; run++) {
|
|
this._defaultHash[selectedArray[run]] = {hits: 0}
|
|
}
|
|
return;
|
|
}
|
|
|
|
var boundNode = this.accessors.getBoundNode();
|
|
var child = boundNode ? boundNode.firstChild : null;
|
|
|
|
var childcount = 0;
|
|
for (; child; child = child.nextSibling) {
|
|
var type = child.nodeType;
|
|
|
|
if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_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 (++childcount > 1 && this.localName == "select1")
|
|
break;
|
|
|
|
// When the string is empty, the method returns an array
|
|
// containing one empty string, rather than an empty array.
|
|
// We'll allow that into the defaultHash so that
|
|
// 'xforms-out-of-range' will be correctly generated if none of
|
|
// the items in the select have an empty string as a value.
|
|
var selectedArray = this._getValuesArray(string);
|
|
|
|
for (var run = 0; run < selectedArray.length; run++) {
|
|
this._defaultHash[selectedArray[run]] = {hits: 0}
|
|
}
|
|
}
|
|
} else {
|
|
if (++childcount > 1 && this.localName == "select1")
|
|
break;
|
|
|
|
// 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.
|
|
this._selectedElementArray.push({element: child, hits: 0});
|
|
}
|
|
}
|
|
|
|
if (this.localName == "select1") {
|
|
var outOfRange = childcount > 1;
|
|
if (outOfRange != this._outOfRange) {
|
|
this._outOfRange = outOfRange;
|
|
this.accessors.setInRange(!outOfRange);
|
|
if (outOfRange)
|
|
return;
|
|
}
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="_buildItem">
|
|
<parameter name="aItem"/>
|
|
<parameter name="aParentGroup"/>
|
|
<body>
|
|
// cache the item's label/value
|
|
aItem = aItem.QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
var labels = aItem.getElementsByTagNameNS(this.XFORMS_NS, "label");
|
|
|
|
var controlitem;
|
|
if (labels[0]) {
|
|
controlitem = this.control.
|
|
appendItem(labels[0].nodeValue, aItem.value, aParentGroup);
|
|
} else {
|
|
controlitem = this.control.
|
|
appendItem(null, aItem.value, aParentGroup);
|
|
}
|
|
|
|
var selected = false;
|
|
if (aItem.value in this._defaultHash) {
|
|
selected = true;
|
|
this.control.addItemToSelection(controlitem);
|
|
this._defaultHash[aItem.value].hits++;
|
|
}
|
|
|
|
this._controlArray.push(
|
|
{control: aItem, option: controlitem, wasSelected: selected}
|
|
);
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_buildChoices">
|
|
<parameter name="aChoice"/>
|
|
<parameter name="aParentGroup"/>
|
|
<body>
|
|
<![CDATA[
|
|
// finds label for a choice
|
|
for (var i = 0; i < aChoice.childNodes.length; i++) {
|
|
var item = aChoice.childNodes[i];
|
|
if (item.nodeType == item.ELEMENT_NODE &&
|
|
item.namespaceURI == this.XFORMS_NS &&
|
|
item.localName == "label") {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// creates group for a choice
|
|
var group;
|
|
if (i != aChoice.childNodes.length) {
|
|
// XXX: If xf:label is bounded to instance data then since xf:label
|
|
// gets refresh after xf:choices refresh then label.nodeValue returns
|
|
// incorrect value. Threafore we use xf:label node instead of
|
|
// node returned by label.nodeValue. It is workaround and we should
|
|
// use label.nodeValue when the problem related with it will be fixed.
|
|
var label = aChoice.childNodes[i];
|
|
group = this.control.appendGroup(label, aParentGroup);
|
|
} else {
|
|
group = this.control.appendGroup(null, aParentGroup);
|
|
}
|
|
|
|
// creates the items for a choice and it's children
|
|
for (var i = 0; i < aChoice.childNodes.length; i++) {
|
|
var item = aChoice.childNodes[i];
|
|
if (item.nodeType != item.ELEMENT_NODE)
|
|
continue;
|
|
|
|
switch (item.localName) {
|
|
case "item":
|
|
this._buildItem(item, group);
|
|
break;
|
|
case "choices":
|
|
this._buildChoices(item, group);
|
|
break;
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_buildItemset">
|
|
<parameter name="aItemset"/>
|
|
<body>
|
|
<![CDATA[
|
|
aItemset = aItemset.
|
|
QueryInterface(Components.interfaces.nsIXFormsItemSetUIElement);
|
|
|
|
var containers = aItemset.anonymousItemSetContent.childNodes;
|
|
|
|
// go through each item in the itemset and add it to the
|
|
// html:select. Select any of the items that contain a value
|
|
// that also appears under the bound node.
|
|
for (var y = 0; y < containers.length; y++) {
|
|
if (containers[y].nodeType != Node.ELEMENT_NODE)
|
|
continue;
|
|
|
|
var item = containers[y].
|
|
QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
|
|
var copyItem = item.isCopyItem;
|
|
var valueText = copyItem ? "" : item.value;
|
|
var labelText = item.labelText;
|
|
var textNode = this.ownerDocument.createTextNode(labelText);
|
|
|
|
var controlitem =
|
|
this.control.appendItem(textNode, valueText);
|
|
|
|
var selected = false;
|
|
|
|
// if this item contains a copy element AND if the bound node contains
|
|
// non-text elements, then see if any of these non-text elements match
|
|
// this copyItem's node.
|
|
if (copyItem && this._selectedElementArray.length > 0) {
|
|
item = item.QueryInterface(Components.interfaces.nsIXFormsSelectChild);
|
|
|
|
for (var j = 0; j < this._selectedElementArray.length; j++ ) {
|
|
var selectedItem =
|
|
item.selectItemByNode(this._selectedElementArray[j].element);
|
|
if (selectedItem) {
|
|
this.control.addItemToSelection(controlitem);
|
|
selected = true;
|
|
this._selectedElementArray[j].hits++;
|
|
// XXX It is possible that two identical elements are under the
|
|
// bound node. I guess we shouldn't mark one and not the other
|
|
// if there is an item in the select that matches it. So we'll
|
|
// go through the whole list. But this is quite an edge case
|
|
// and will cause more inefficiency just to prevent an errant
|
|
// xforms-out-of-range.
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!copyItem) {
|
|
if (item.value in this._defaultHash) {
|
|
this.control.addItemToSelection(controlitem);
|
|
selected = true;
|
|
this._defaultHash[item.value].hits++;
|
|
}
|
|
}
|
|
|
|
this._controlArray.push(
|
|
{control: item, option: controlitem, wasSelected: selected}
|
|
);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_updateSelection">
|
|
<body>
|
|
<![CDATA[
|
|
// select if found, unselect if not
|
|
var options = this._controlArray;
|
|
|
|
for (var i = 0; i < options.length; i++) {
|
|
var item = options[i].control.
|
|
QueryInterface(Components.interfaces.nsIXFormsSelectChild);
|
|
if (item.isCopyItem)
|
|
// _updateSelection is used only to select items when _defaultHash
|
|
// contains only text nodes. So we can ignore copyItems.
|
|
continue;
|
|
|
|
var value = item.value;
|
|
var selectionValue = value in this._defaultHash;
|
|
|
|
if (selectionValue) {
|
|
this.control.addItemToSelection(options[i].option);
|
|
options[i].wasSelected = true;
|
|
this._defaultHash[value].hits++;
|
|
}
|
|
else {
|
|
this.control.removeItemFromSelection(options[i].option);
|
|
options[i].wasSelected = false;
|
|
}
|
|
}
|
|
// see if any of default values are not found. If so, we need to
|
|
// throw an xforms-out-of-range event and style the control as
|
|
// being out of range. But only if the select is 'closed'.
|
|
if (this.selection == 'open') {
|
|
return;
|
|
}
|
|
|
|
var outOfRange = false;
|
|
var length = 0;
|
|
for (var index in this._defaultHash) {
|
|
length++;
|
|
if (this._defaultHash[index].hits == 0) {
|
|
outOfRange = true;
|
|
}
|
|
}
|
|
|
|
if (length == 0) {
|
|
// this covers the scenario where some items were previously
|
|
// selected and the user selected a copyItem, yet because the
|
|
// bound node wasn't an element node the copyItem was deselected
|
|
// and the bound node was set to empty. If there is no selected
|
|
// item, then we must be out of range.
|
|
outOfRange = true;
|
|
}
|
|
|
|
if (this._outOfRange != outOfRange) {
|
|
this._outOfRange = outOfRange;
|
|
this.accessors.setInRange(!outOfRange);
|
|
}
|
|
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_updateDefaultValues">
|
|
<body>
|
|
<![CDATA[
|
|
var selectedArray = this._getValuesArray();
|
|
|
|
// store the values in a hash for quick access
|
|
this._defaultHash = new Object();
|
|
for (var run = 0; run < selectedArray.length; run++) {
|
|
this._defaultHash[selectedArray[run]] = {hits: 0}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Return an array of values to be selected for the given string or of
|
|
the bound node if string argument is omitted. -->
|
|
<method name="_getValuesArray">
|
|
<parameter name="aValue"/>
|
|
<body>
|
|
// When the instance value is empty, then we return an array
|
|
// containing one empty string, rather than an empty array. We'll
|
|
// allow that into the defaultHash so that 'xforms-out-of-range' event
|
|
// will be correctly generated if none of the items in the select have
|
|
// an empty string as a value.
|
|
|
|
if (!aValue) {
|
|
aValue = this.accessors.getValue();
|
|
if (!aValue) {
|
|
aValue = "";
|
|
}
|
|
}
|
|
|
|
if (this.localName == "select1")
|
|
return [aValue];
|
|
|
|
// A limitation of the XML Schema list datatypes is that white space
|
|
// characters in the storage values (the value element) are always
|
|
// interpreted as separators between individual data values.
|
|
// Therefore, authors should avoid using white space characters within
|
|
// storage values with list simpleContent.
|
|
|
|
// Replace new line (\n), tabs (\t) and carriage returns (\r) with
|
|
// space (" ").
|
|
aValue = aValue.replace(/\n|\t|\r/g, " ");
|
|
|
|
return aValue.split(/\s/);
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Updates the instance data bound to this control.
|
|
|
|
@param aIncremental - if true then it means the update is additional and
|
|
instance data will be updated only if @incremental attribute value of
|
|
this control is not false.
|
|
-->
|
|
<method name="updateInstanceData">
|
|
<parameter name="aIncremental"/>
|
|
<body>
|
|
<![CDATA[
|
|
// Fire 'xfroms-select'/'xforms-deselect' events and unselect illegaly
|
|
// selected items.
|
|
var copySelectedOrDeselected = new Boolean();
|
|
copySelectedOrDeselected.value = false;
|
|
var contentEnvelope =
|
|
this._processSelectedValues(copySelectedOrDeselected);
|
|
|
|
if (!aIncremental || this.incremental) {
|
|
if (contentEnvelope) {
|
|
this._setBoundValue(contentEnvelope,
|
|
copySelectedOrDeselected.value);
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_setBoundValue">
|
|
<parameter name="aContentEnvelope"/>
|
|
<parameter name="aCopySelectedOrDeselected"/>
|
|
<body>
|
|
<![CDATA[
|
|
var boundNode = this.accessors.getBoundNode();
|
|
if (!boundNode)
|
|
return;
|
|
|
|
// if a copy item is selected or deselected, then we need to replace
|
|
// ALL of the current content with the newly selected content. Which
|
|
// means calling setContent. setValue only messes with the first
|
|
// textnode under the bound node.
|
|
|
|
if (boundNode.nodeType == Node.ELEMENT_NODE) {
|
|
// we shouldn't call setContent if we haven't selected any
|
|
// copyItems. We can't just test for a single text node under
|
|
// the bound node because this could still have been the result
|
|
// of a copyItem being selected. And if a copyItem is selected,
|
|
// then we need to do the whole rebuild, recalculate, revalidate,
|
|
// refresh process according to the spec...whether we really need
|
|
// to or not.
|
|
if (!aCopySelectedOrDeselected) {
|
|
// well, no copyItems are selected, so need to find the value
|
|
// to set. Will be in the first child.
|
|
var firstChild = aContentEnvelope.firstChild;
|
|
var value = null;
|
|
if (firstChild) {
|
|
value = firstChild.nodeValue;
|
|
}
|
|
this.accessors.setValue(value);
|
|
} else {
|
|
this.accessors.setContent(aContentEnvelope, true);
|
|
}
|
|
} else {
|
|
// if some copyItems were selected by the user prior to the call
|
|
// to _processSelectedValues, then we would not have set up
|
|
// _accessorValueCache. Since the node we are bound to can't
|
|
// be set by copyItems (its not an ELEMENT_NODE), any copyItems
|
|
// in this select would have been deselected during
|
|
// _processSelectedValues. Thus, anything in the contentEnvelope at
|
|
// this point should just be strings and so we can set
|
|
// delegate.value directly and use _accessorValueCache after all.
|
|
|
|
this.accessors.setValue(aContentEnvelope.nodeValue);
|
|
this._accessorValueCache = aContentEnvelope.nodeValue;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!--
|
|
The method serves to
|
|
1) unselect illegally selected items
|
|
2) fire "xforms-select"/"xforms-deselect" events
|
|
3) return list of selected values
|
|
-->
|
|
<method name="_processSelectedValues">
|
|
<parameter name="aIsACopyItemSelectedOrDeselected"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aIsACopyItemSelectedOrDeselected) {
|
|
aIsACopyItemSelectedOrDeselected.value = false;
|
|
}
|
|
|
|
var boundNode = this.accessors.getBoundNode();
|
|
if (!boundNode) {
|
|
this._dispatchSelectEvents();
|
|
return null;
|
|
}
|
|
|
|
// we are cloning boundNode to create a node that we will return.
|
|
// By the end of this function, assuming all went well,
|
|
// contentEnvelope will contain the values and copyNodes that are
|
|
// represented by the selected items in this xf:select. Cloning
|
|
// the boundNode to use as the envelope so that the caller could
|
|
// just pass the results straight into accessors.setContent().
|
|
var contentEnvelope = null;
|
|
contentEnvelope = boundNode.cloneNode(false);
|
|
if (!contentEnvelope) {
|
|
this._dispatchSelectEvents();
|
|
return null;
|
|
}
|
|
|
|
var contentDocument = contentEnvelope.ownerDocument;
|
|
|
|
var selectedValues = "";
|
|
if (this.selection == "open") {
|
|
selectedValues = this.control.getFreeEntryValues();
|
|
}
|
|
|
|
var options = this._controlArray;
|
|
var boundType = boundNode.nodeType;
|
|
var copyNode;
|
|
|
|
// keep in mind, to maintain compatibility with XSmiles and Novell, we
|
|
// ultimately need to end up with all 'value' elements contained in a
|
|
// text node and this text node needs to be the first child of the
|
|
// bound node.
|
|
for (var i = 0; i < options.length; i++) {
|
|
var isSelected = this.control.isItemSelected(options[i].option);
|
|
|
|
if (isSelected) {
|
|
// space delimited list
|
|
if (selectedValues.length > 0) {
|
|
selectedValues += " ";
|
|
}
|
|
|
|
var item = options[i].control.
|
|
QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
if (item.isCopyItem) {
|
|
if (boundType && (boundType != Node.ELEMENT_NODE)) {
|
|
// if we are trying to do a copy without being bound to an
|
|
// element node, then we need to throw a binding exception
|
|
// per spec.
|
|
var bindingException = document.createEvent("Events");
|
|
bindingException.initEvent("xforms-binding-exception", true, false);
|
|
this.dispatchEvent(bindingException);
|
|
|
|
// we should probably un-select the option so that the list
|
|
// of selected data is accurate. This WON'T cause a
|
|
// xforms-select/deselect to fire. Since the user just
|
|
// selected this item and we are automatically deselecting
|
|
// it from underneath the user, we'll treat it like nothing
|
|
// happened.
|
|
this.control.removeItemFromSelection(options[i].option);
|
|
} else {
|
|
copyNode = item.copyNode;
|
|
if (copyNode) {
|
|
var clone = contentDocument.importNode(copyNode, true);
|
|
contentEnvelope.appendChild(clone);
|
|
|
|
if (!options[i].wasSelected) {
|
|
if (aIsACopyItemSelectedOrDeselected &&
|
|
aIsACopyItemSelectedOrDeselected.value != true) {
|
|
aIsACopyItemSelectedOrDeselected.value = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// not a copyItem, so grab the item's value and append it to our
|
|
// space seperated list.
|
|
selectedValues += options[i].control.
|
|
QueryInterface(Components.interfaces.nsIXFormsSelectChild).value;
|
|
}
|
|
} else {
|
|
// it was selected before, but now unselected
|
|
if (options[i].wasSelected) {
|
|
// if a copyItem was deselected, we need to make sure to do a
|
|
// rebuild. By setting aIsACopyItemSelectedOrDeselected, this
|
|
// should tell _handleSelection to use setContent with
|
|
// aForceUpdate = true
|
|
var item = options[i].control.
|
|
QueryInterface(Components.interfaces.nsIXFormsItemElement);
|
|
if (item.isCopyItem) {
|
|
if (aIsACopyItemSelectedOrDeselected &&
|
|
aIsACopyItemSelectedOrDeselected.value != true) {
|
|
aIsACopyItemSelectedOrDeselected.value = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// write out the text nodes before we handle copy
|
|
if (boundType == Node.ELEMENT_NODE) {
|
|
if (selectedValues.length > 0) {
|
|
var textNode = contentDocument.createTextNode(selectedValues);
|
|
if (copyNode) {
|
|
// making sure all selected 'values' are in the first text node
|
|
// under the bound node.
|
|
var firstChild = contentEnvelope.firstChild;
|
|
contentEnvelope.insertBefore(textNode, firstChild);
|
|
} else {
|
|
contentEnvelope.appendChild(textNode);
|
|
}
|
|
}
|
|
} else {
|
|
contentEnvelope.nodeValue = selectedValues;
|
|
}
|
|
|
|
this._dispatchSelectEvents();
|
|
return contentEnvelope;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_dispatchSelectEvents">
|
|
<body>
|
|
<![CDATA[
|
|
var options = this._controlArray;
|
|
|
|
for (var i = 0; i < options.length; i++) {
|
|
var selected = this.control.isItemSelected(options[i].option);
|
|
if (options[i].wasSelected && !selected) {
|
|
options[i].wasSelected = false;
|
|
this.dispatchSelectEvent(options[i].control, "xforms-deselect");
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < options.length; i++) {
|
|
var selected = this.control.isItemSelected(options[i].option);
|
|
if (!options[i].wasSelected && selected) {
|
|
options[i].wasSelected = true;
|
|
this.dispatchSelectEvent(options[i].control, "xforms-select");
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="dispatchSelectEvent">
|
|
<parameter name="aElement"/>
|
|
<parameter name="aName"/>
|
|
<body>
|
|
<![CDATA[
|
|
// per errata for XForms 1.0 second edition, we send the event to
|
|
// the item element even if it is contained insided an itemset
|
|
return this.dispatchXFormsNotificationEvent(aName, aElement);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Array of objects that contains objects for all items of this control,
|
|
serves to keep xforms items and native items together.
|
|
Object has next properties:
|
|
control - xforms:item element
|
|
option - native widget for xforms:item
|
|
wasSelected - flag specifies whether native item was selected or not.
|
|
-->
|
|
<field name="_controlArray">new Array()</field>
|
|
|
|
<field name="_selectedElementArray">new Array()</field>
|
|
<field name="_defaultHash">null</field>
|
|
<field name="_accessorValueCache">null</field>
|
|
<field name="_outOfRange">false</field>
|
|
|
|
<method name="getControlElement">
|
|
<body>
|
|
return document.getAnonymousElementByAttribute(this, "anonid", "control");
|
|
</body>
|
|
</method>
|
|
|
|
</implementation>
|
|
|
|
</binding>
|
|
|
|
|
|
<!-- SELECT BASE
|
|
Implements nsIXFormsNSSelectElement interface.
|
|
-->
|
|
<binding id="xformswidget-select-base"
|
|
extends="#xformswidget-selectcontrols-base">
|
|
|
|
<implementation implements="nsIXFormsNSSelectElement">
|
|
|
|
<!-- nsIXFormsNSSelectElement -->
|
|
<property name="selectedItems" readonly="true">
|
|
<getter>
|
|
<![CDATA[
|
|
var items = [];
|
|
for (var i = 0; i < this._controlArray.length; i++) {
|
|
var nativeitem = this._controlArray[i].option;
|
|
if (this.control.isItemSelected(nativeitem)) {
|
|
var item = this._controlArray[i].control;
|
|
items.push(item);
|
|
}
|
|
}
|
|
return items;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="addItemToSelection">
|
|
<parameter name="aItem"/>
|
|
<body>
|
|
var nativeitem = this.getNativeItem(aItem);
|
|
if (!this.control.isItemSelected(nativeitem)) {
|
|
this.control.addItemToSelection(nativeitem);
|
|
this.updateInstanceData();
|
|
}
|
|
</body>
|
|
</method>
|
|
|
|
<method name="removeItemFromSelection">
|
|
<parameter name="aItem"/>
|
|
<body>
|
|
var nativeitem = this.getNativeItem(aItem);
|
|
if (this.control.isItemSelected(nativeitem)) {
|
|
this.control.removeItemFromSelection(nativeitem);
|
|
this.updateInstanceData();
|
|
}
|
|
</body>
|
|
</method>
|
|
|
|
<method name="clearSelection">
|
|
<body>
|
|
<![CDATA[
|
|
changed = false;
|
|
for (var i = 0; i < this._controlArray.length; i++) {
|
|
var nativeitem = this._controlArray[i].option;
|
|
if (this.control.isItemSelected(nativeitem)) {
|
|
this.control.removeItemFromSelection(nativeitem);
|
|
changed = true;
|
|
}
|
|
}
|
|
if (changed)
|
|
this.updateInstanceData();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="selectAll">
|
|
<body>
|
|
<![CDATA[
|
|
var changed = false;
|
|
for (var i = 0; i < this._controlArray.length; i++) {
|
|
var nativeitem = this._controlArray[i].option;
|
|
if (!this.control.isItemSelected(nativeitem)) {
|
|
this.control.addItemToSelection(nativeitem);
|
|
changed = true;
|
|
}
|
|
}
|
|
if (changed)
|
|
this.updateInstanceData();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="isItemSelected">
|
|
<parameter name="aItem"/>
|
|
<body>
|
|
return this.control.isItemSelected(this.getNativeItem(aItem));
|
|
</body>
|
|
</method>
|
|
|
|
<!-- private -->
|
|
<method name="getNativeItem">
|
|
<parameter name="aItem"/>
|
|
<body>
|
|
<![CDATA[
|
|
for (var i = 0; i < this._controlArray.length; i++) {
|
|
if (this._controlArray[i].control == aItem)
|
|
return this._controlArray[i].option;
|
|
}
|
|
return null;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
</binding>
|
|
|
|
|
|
<!-- SELECT1 BASE
|
|
Implements nsIXFormsNSSelect1Element interface.
|
|
-->
|
|
<binding id="xformswidget-select1-base"
|
|
extends="#xformswidget-selectcontrols-base">
|
|
|
|
<implementation implements="nsIXFormsNSSelect1Element">
|
|
<property name="selectedItem">
|
|
<getter>
|
|
<![CDATA[
|
|
for (var i = 0; i < this._controlArray.length; i++) {
|
|
var nativeitem = this._controlArray[i].option;
|
|
var item = this._controlArray[i].control;
|
|
if (this.control.isItemSelected(nativeitem)) {
|
|
return item;
|
|
}
|
|
}
|
|
return null;
|
|
]]>
|
|
</getter>
|
|
<setter>
|
|
<![CDATA[
|
|
var changed = false;
|
|
for (var i = 0; i < this._controlArray.length; i++) {
|
|
var nativeitem = this._controlArray[i].option;
|
|
var item = this._controlArray[i].control;
|
|
if (!this.control.isItemSelected(nativeitem)) {
|
|
if (item == val) {
|
|
this.control.addItemToSelection(nativeitem);
|
|
if (changed)
|
|
break;
|
|
changed = true;
|
|
}
|
|
} else {
|
|
if (item != val) {
|
|
this.control.removeItemFromSelection(nativeitem);
|
|
if (changed)
|
|
break;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
if (changed)
|
|
this.updateInstanceData();
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
</implementation>
|
|
</binding>
|
|
|
|
|
|
<!-- BASE CONTROL WIDGET FOR XFORMS SELECTS
|
|
The 'controlwidget-base' is the base binding of underlying controls. Thease
|
|
underlying controls implement functionality asked for by object returned by
|
|
getControlElement() method of xforms select widgets. Examples of the widget
|
|
using you can find in 'select-xhtml.xml' file.
|
|
|
|
All inherited widgets from this one should be also capable of being bound to
|
|
directly rather than just through xforms select widgets. We are supporting
|
|
the use of a readonly attribute on elements that bind this way.
|
|
-->
|
|
<binding id="controlwidget-base">
|
|
<implementation>
|
|
<!-- Return parent xf:select/xf:select1 control. -->
|
|
<property name="parentControl" readonly="true">
|
|
<getter>
|
|
<![CDATA[
|
|
// returns 'select' or 'select1' parent control
|
|
if (!this._parentControl) {
|
|
var root = this.ownerDocument.documentElement;
|
|
for (var node = this.parentNode; node != root; node = node.parentNode) {
|
|
if (node.namespaceURI == this.XFORMS_NS &&
|
|
(node.localName == "select" || node.localName == "select1")) {
|
|
this._parentControl = node;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return this._parentControl;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
<field name="_parentControl">null</field>
|
|
|
|
<!-- Return native control widget. -->
|
|
<property name="control" readonly="true">
|
|
<getter>
|
|
if (!this._control) {
|
|
this._control =
|
|
document.getAnonymousElementByAttribute(this, "anonid", "control");
|
|
}
|
|
return this._control;
|
|
</getter>
|
|
</property>
|
|
<field name="_control">null</field>
|
|
|
|
<!-- Set readonly state. The state is used to enable/disable native
|
|
control widget during select items adding.
|
|
-->
|
|
<property name="readonly">
|
|
<getter>
|
|
return this.getAttribute("readonly") == "true" ? true : false;
|
|
</getter>
|
|
<setter>
|
|
if (val) {
|
|
this.setAttribute("readonly", "true");
|
|
} else {
|
|
this.removeAttribute("readonly");
|
|
}
|
|
</setter>
|
|
</property>
|
|
|
|
<!-- Advance focus to native widget. -->
|
|
<method name="focus">
|
|
<body>
|
|
this.control.focus();
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Update instance data to native control widget value.
|
|
@param aIncremental - will be true if this function is called because
|
|
the user selected a new value in the native control widget. It will be
|
|
false if this function is called due to focus leaving the native
|
|
control widget.
|
|
-->
|
|
<method name="updateInstanceData">
|
|
<parameter name="aIncremental"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.parentControl)
|
|
return;
|
|
|
|
// No need to update the bound value if the control is incremental and
|
|
// we are losing focus. It should already be up to date.
|
|
if (this.parentControl.incremental && !aIncremental)
|
|
return;
|
|
|
|
this.parentControl.updateInstanceData(aIncremental);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Dispatch event to parent control. -->
|
|
<method name="dispatchDOMUIEvent">
|
|
<parameter name="aType"/>
|
|
<body>
|
|
if (this.parentControl)
|
|
this.parentControl.dispatchDOMUIEvent(aType);
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Return values of free entry. The method is a stub and it should
|
|
be removed when all native controls will support @selection="open".
|
|
-->
|
|
<method name="getFreeEntryValues">
|
|
<body>
|
|
return "";
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Allow free entry. The method is a stub and it should be removed when
|
|
all native controls will support @selection="open".
|
|
-->
|
|
<method name="allowFreeEntry">
|
|
<parameter name="aAllowed"/>
|
|
<body>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Append and select free entry item. The method is a stub and it should
|
|
be removed when all native controls will support @selection="open".
|
|
-->
|
|
<method name="appendFreeEntryItem">
|
|
<parameter name="aValue"/>
|
|
<body>
|
|
</body>
|
|
</method>
|
|
|
|
<property name="XFORMS_NS" readonly="true"
|
|
onget="return 'http://www.w3.org/2002/xforms';"/>
|
|
|
|
<property name="XHTML_NS" readonly="true"
|
|
onget="return 'http://www.w3.org/1999/xhtml';"/>
|
|
|
|
<property name="XUL_NS" readonly="true"
|
|
onget="return 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';"/>
|
|
|
|
<property name="XBL_NS" readonly="true"
|
|
onget="return 'http://www.mozilla.org/xbl';"/>
|
|
|
|
<constructor>
|
|
if (this.parentControl.selection == "open")
|
|
this.allowFreeEntry(true);
|
|
else
|
|
this.allowFreeEntry(false);
|
|
</constructor>
|
|
|
|
</implementation>
|
|
</binding>
|
|
|
|
</bindings>
|