aaronr%us.ibm.com cfca86c098 [XForms] Actions inside itemsets behave incorrectly. Bug 374994, r=surkov+olli
git-svn-id: svn://10.0.0.236/trunk@224026 18797224-902f-48f8-a5cc-f745e15eee43
2007-04-02 20:21:08 +00:00

976 lines
30 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
- Mozilla Foundation.
- Portions created by the Initial Developer are Copyright (C) 2007
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Alexander Surkov <surkov.alexander@gmail.com> (original author)
-
- 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="xformsSelectsNativeWidgetBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl">
<!--
This file implements the "abstract" UI classes for XForms select/select1
controls that are used native widgets for their representation.
-->
<!-- BASE binding for select/select1 elements that are used native widgets -->
<binding id="selectcontrols-base"
extends="chrome://xforms/content/selects.xml#selectcontrols-base">
<implementation>
<!-- nsIAccessibleProvider -->
<property name="accessibleType" readonly="true">
<getter>
return Components.interfaces.nsIAccessibleProvider.XFormsSelect;
</getter>
</property>
<!-- overriding the methods from the 'selectcontrols-base' binding -->
<method name="getFreeEntryValues">
<body>
return this.nativeWidget.getFreeEntryValues();
</body>
</method>
<method name="allowFreeEntry">
<parameter name="aAllowed"/>
<body>
return this.nativeWidget.allowFreeEntry(aAllowed);
</body>
</method>
<method name="appendFreeEntryItem">
<parameter name="aValue"/>
<body>
this.nativeWidget.appendFreeEntryItem(aValue);
</body>
</method>
<method name="markItemSelected">
<parameter name="aItem"/>
<parameter name="aIsSelected"/>
<body>
this.nativeWidget.selectItem(aItem, aIsSelected);
</body>
</method>
<method name="isItemMarkedSelected">
<parameter name="aItem"/>
<body>
return this.nativeWidget.isItemSelected(aItem);
</body>
</method>
<method name="markItemDisabled">
<parameter name="aItem"/>
<parameter name="aIsDisabled"/>
<body>
this.nativeWidget.disableItem(aItem, aIsDisabled);
</body>
</method>
<method name="isItemMarkedDisabled">
<parameter name="aItem"/>
<body>
return this.nativeWidget.isItemDisabled(aItem);
</body>
</method>
<method name="focusItem">
<parameter name="aItem"/>
<body>
this.nativeWidget.focus();
</body>
</method>
<!-- private -->
<constructor>
this.handleInsertion(this, null);
</constructor>
<!-- Return native widget control -->
<property name="nativeWidget">
<getter>
if (!this._nativeWidget)
this._nativeWidget = this.ownerDocument.
getAnonymousElementByAttribute(this, "anonid", "nativewidget");
return this._nativeWidget;
</getter>
</property>
<!-- Try to update label element when DOM tree has been changed.
@param aNode - the node that was inserted or will be removed
@param aIsInsertion - if true then node was inserted
-->
<method name="handleLabel">
<parameter name="aNode"/>
<parameter name="aIsInsertion"/>
<body>
<![CDATA[
var node = aNode;
while (node && node != this && (node.namespaceURI != this.XFORMS_NS ||
node.localName != "label")) {
node = node.parentNode;
}
if (!node)
return;
if (!node || node == this)
return;
var label = node;
var parent = label.parentNode;
var labels = parent.getElementsByTagNameNS(this.XFORMS_NS, "label");
if (labels[0] != label)
return;
label = ((aNode == label) && !aIsInsertion) ? null : label;
if (parent.namespaceURI == this.XFORMS_NS) {
// Set label only for xf:item or xf:choices elements.
switch (parent.localName) {
case "choices":
case "item":
this.nativeWidget.setLabelFor(parent, label);
break;
}
}
]]>
</body>
</method>
<!-- Updates native widget when DOM tree has been changed i.e. "item"/
"itemset"/"choices" elements have been inserted or removed.
@param aNode - the given node, is is either "select"/"select1" elements
to initialize native widget or "choices"/"item"/"itemset"
if it was inserted
@param aParentNode - either parent node of the given node or null if
native widget is initialized
@param aParentIndex - index of parent node (for recursive calls)
@param aPrevSiblingIndex - index of previous sibling node (for recursive
calls)
-->
<method name="handleInsertion">
<parameter name="aNode"/>
<parameter name="aParentNode"/>
<parameter name="aParentIndex"/>
<parameter name="aPrevSiblingIndex"/>
<body>
<![CDATA[
const nsIXFormsItemSetUIElement =
Components.interfaces.nsIXFormsItemSetUIElement;
if (aNode.namespaceURI != this.XFORMS_NS)
return -1;
var container = null;
var index = -1;
var prevIndex = -1;
switch (aNode.localName) {
case "select1":
case "select":
case "choices":
container = aNode;
break;
case "item":
break;
case "itemset":
var itemset = aNode.QueryInterface(nsIXFormsItemSetUIElement);
container = itemset.anonymousItemSetContent;
index = aParentIndex;
prevIndex = aPrevSiblingIndex;
break;
default:
return -1;
}
// Create native item/choices and insert it into native widget
// hierarchy if inserted elements are xf:item or xf:choices only.
if (index == -1) {
this.isInsertionHandlingPrevented = true;
if (aParentIndex != null && aParentIndex != -1) {
index = this.nativeWidget.appendElm(aNode, aParentIndex,
aPrevSiblingIndex);
} else {
index = this.nativeWidget.insertElm(aNode, aParentNode);
}
this.isInsertionHandlingPrevented = false;
}
// We return whether inserted element is not container of
// select/select1 child elements or native element wasn't inserted
// successfully.
if (index == -1 || !container)
return index;
// Create/insert native elements for choices/item/itemset child
// elements of inserted element if any.
var hasChildNodes = false;
for (var child = container.firstChild; child; child = child.nextSibling) {
if (child.namespaceURI != this.XFORMS_NS)
continue;
switch (child.localName) {
case "choices":
case "itemset":
case "item":
hasChildNodes = true;
prevIndex = this.handleInsertion(child, null, index, prevIndex);
break;
}
}
return hasChildNodes ? prevIndex : index;
]]>
</body>
</method>
<!-- Flag prevents to handle DOM mutation events if mutation events are
cause of native widget is updated. -->
<field name="isInsertionHandlingPrevented">false</field>
</implementation>
<handlers>
<handler event="DOMNodeInserted">
<![CDATA[
if (this.isInsertionHandlingPrevented)
return;
var target = event.originalTarget;
var parent = event.relatedNode;
// If inserted element is item/itemset/choides element then we should
// update native widget
if (target.namespaceURI == this.XFORMS_NS &&
(target.localName == "choices" || target.localName == "itemset" ||
target.localName == "item")) {
// Search for parent element that is reflected by native widget.
// The parent element can be select/select1, itemset or choices
// element so we skip itemset element because it isn't presented
// in native widget.
while (parent != this && (parent.namespaceURI != this.XFORMS_NS ||
parent.localName != "choices"))
parent = parent.parentNode;
this.handleInsertion(target, parent);
return;
}
// otherwise we try to update label for item/choices element.
this.handleLabel(target, true);
]]>
</handler>
<handler event="DOMNodeRemoved">
if (this.isInsertionHandlingPrevented)
return;
var target = event.originalTarget;
if (target.namespaceURI == this.XFORMS_NS) {
switch (target.localName) {
case "choices":
case "item":
this.nativeWidget.removeNativeElm(target);
return;
case "itemset":
var itemset = target.QueryInterface(Components.interfaces.nsIXFormsItemSetUIElement);
var container = itemset.anonymousItemSetContent;
for (var child = container.firstChild; child; child = child.nextSibling)
this.nativeWidget.removeNativeElm(child);
return;
}
}
this.handleLabel(target, false);
</handler>
<handler event="DOMCharacterDataModified">
if (this.isInsertionHandlingPrevented)
return;
this.handleLabel(event.originalTarget, true);
</handler>
</handlers>
</binding>
<!-- SELECT: BASE
Note, keep synchronized with 'selects.xml#select-base' binding
-->
<binding id="select-base"
extends="#selectcontrols-base">
<implementation implements="nsIXFormsNSSelectElement">
<!-- nsIXFormsNSSelectElement -->
<property name="selectedItems" readonly="true">
<getter>
function _isSelected(aItem, aItemList) {
if (this.isItemMarkedSelected(aItem))
aItemList.push(aItem);
return true;
}
var list = [];
this.traverseItems(this, _isSelected, list);
return list;
</getter>
</property>
<method name="addItemToSelection">
<parameter name="aItem"/>
<body>
this.markItemSelected(aItem, true);
</body>
</method>
<method name="removeItemFromSelection">
<parameter name="aItem"/>
<body>
this.markItemSelected(aItem, false);
</body>
</method>
<method name="clearSelection">
<body>
function _clearSelection(aItem) {
this.markItemSelected(aItem, false);
return true;
}
this.traverseItems(this, _clearSelection);
</body>
</method>
<method name="selectAll">
<body>
function _selectAll(aItem) {
this.markItemSelected(aItem, true);
return true;
}
this.traverseItems(this, _selectAll);
</body>
</method>
<method name="isItemSelected">
<parameter name="aItem"/>
<body>
return this.isItemMarkedSelected(aItem);
</body>
</method>
<!-- Private implementation -->
<method name="getValuesArrayFor">
<parameter name="aString"/>
<parameter name="aWhiteSpaceExpr"/>
<body>
// 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 (" ").
var value = aString.replace(aWhiteSpaceExpr, " ");
return value.split(/\s/);
</body>
</method>
<method name="selectFreeEntryItemElements">
<body>
for (var i = 0; this.values.length; ++i) {
if (!this.values[i].isUsed) {
this.appendFreeEntryItem(this.values[i].value);
this.values[i].isUsed = true;
}
}
</body>
</method>
<method name="isOutOfRange">
<parameter name="aHitArray"/>
<body>
<![CDATA[
if (!aHitArray)
return false;
var i = 0;
for (; i < aHitArray.length && aHitArray[i].isUsed; ++i);
return i != aHitArray.length;
]]>
</body>
</method>
</implementation>
</binding>
<!-- SELECT1: BASE
Note, keep synchronized with 'selects.xml#select1-base' binding.
-->
<binding id="select1-base"
extends="#selectcontrols-base">
<implementation implements="nsIXFormsNSSelect1Element">
<!-- nsIXFormsNSSelect1Element -->
<property name="selectedItem">
<getter>
function getSelectedItem(aItem, aSelectedItem) {
var selected = this.isItemMarkedSelected(aItem);
if (selected)
aSelectedItem.value = aItem;
return !selected;
}
var selectedItem = {value: null};
this.traverseItems(this, getSelectedItem, selectedItem);
return selectedItem.value;
</getter>
<setter>
function setSelectedItem(aItem, aSelectedItem) {
this.markItemSelected(aItem, aItem == aSelectedItem);
return true;
}
this.traverseItems(this, setSelectedItem, val);
</setter>
</property>
<!-- Private implementation -->
<method name="getValuesArrayFor">
<parameter name="aString"/>
<parameter name="aWhiteSpaceExpr"/>
<body>
return [aString];
</body>
</method>
<method name="selectFreeEntryItemElements">
<body>
if (this.values.length != 1)
return;
if (!this.values[0].isUsed) {
this.appendFreeEntryItem(this.values[0].value);
this.values[0].isUsed = true;
}
</body>
</method>
<method name="isOutOfRange">
<parameter name="aHitArray"/>
<body>
<![CDATA[
return aHitArray && (aHitArray.length > 1 || aHitArray.length == 1 &&
!aHitArray[0].isUsed);
]]>
</body>
</method>
</implementation>
</binding>
<!-- BASE binding for NATIVE WIDGET CONTROL -->
<binding id="nativewidget-select">
<implementation>
<!-- public -->
<method name="selectItem">
<parameter name="aItem"/>
<parameter name="aDoSelect"/>
<body>
var index = this.getIndexByElm(aItem);
if (index != -1) {
var listElm = this.nodeList[index];
this.selectItemNative(listElm.nativeElm, aDoSelect);
listElm.wasSelected = aDoSelect;
}
</body>
</method>
<method name="isItemSelected">
<parameter name="aItem"/>
<body>
var index = this.getIndexByElm(aItem);
if (index != -1)
return this.isItemSelectedNative(this.nodeList[index].nativeElm);
return false;
</body>
</method>
<method name="disableItem">
<parameter name="aItem"/>
<parameter name="aDoDisable"/>
<body>
var index = this.getIndexByElm(aItem);
if (index != -1)
this.disableItemNative(this.nodeList[index].nativeElm, aDoDisable);
</body>
</method>
<method name="isItemDisabled">
<parameter name="aItem"/>
<body>
var index = this.getIndexByElm(aItem);
if (index != -1)
return this.isItemDisabledNative(this.nodeList[index].nativeElm);
return false;
</body>
</method>
<method name="focus">
<body>
this.control.focus();
</body>
</method>
<method name="getFreeEntryValues">
<body>
return "";
</body>
</method>
<method name="allowFreeEntry">
<parameter name="aAllowed"/>
<body>
return false;
</body>
</method>
<method name="appendFreeEntryItem">
<parameter name="aValue"/>
<body>
</body>
</method>
<method name="setLabelFor">
<parameter name="aNode"/>
<parameter name="aLabelNode"/>
<body>
var index = this.getIndexByElm(aNode);
if (index != -1)
this.setLabelForNative(this.nodeList[index].nativeElm, aLabelNode);
</body>
</method>
<!-- Create and appends native element.
@param aElm - the given element
@param aParentIndex - the index of parent element
@param aPrevSiblingIndex - the index of previous sibling element
@note - previous sibling elements must be in native widget already
-->
<method name="appendElm">
<parameter name="aElm"/>
<parameter name="aParentIndex"/>
<parameter name="aPrevSiblingIndex"/>
<body>
<![CDATA[
var parentListElm = this.nodeList[aParentIndex];
var nativeParentElm = parentListElm.nativeElm;
var isItemElm = {value: false};
var nativeElm = this.createNativeElm(aElm, nativeParentElm, null,
isItemElm);
if (!nativeElm)
return -1;
var parentElm = parentListElm.elm;
var parentLevel = parentListElm.level;
var listElm = {
elm: aElm,
nativeElm: nativeElm,
level: parentLevel + 1,
};
if (isItemElm.value)
listElm.wasSelected = false;
var index = -1;
if (aPrevSiblingIndex == null || aPrevSiblingIndex == -1)
index = aParentIndex + 1;
else
index = aPrevSiblingIndex + 1;
this.nodeList.splice(index, 0, listElm);
return index;
]]>
</body>
</method>
<!-- Create and insert native element.
@param aElm - the given element
@param aParent - parent of the given element
-->
<method name="insertElm">
<parameter name="aElm"/>
<parameter name="aParent"/>
<body>
<![CDATA[
if (!aParent)
return this.appendTopElm(aElm);
var parentIndex = this.getIndexByElm(aParent);
if (parentIndex == -1)
return -1;
var nativeParent = this.nodeList[parentIndex].nativeElm;
var index = this.getInsertionIndexForElm(aElm, parentIndex);
if (index == -1)
return;
var isItemElm = {value: false};
var nextElm = aElm.nextSibling ? this.nodeList[index].nativeElm : null;
var nativeElm = this.createNativeElm(aElm, nativeParent, nextElm,
isItemElm);
if (!nativeElm)
return -1;
var parentLevel = this.nodeList[parentIndex].level;
var listElm = {
elm: aElm,
nativeElm: nativeElm,
level: parentLevel + 1,
};
if (isItemElm.value)
listElm.wasSelected = false;
this.nodeList.splice(index, 0, listElm);
return index;
]]>
</body>
</method>
<!-- Remove native node and its children. -->
<method name="removeNativeElm">
<parameter name="aElm"/>
<body>
<![CDATA[
var index = this.getIndexByElm(aElm);
if (index == -1)
return;
var nativeElm = this.nodeList[index].nativeElm;
nativeElm.parentNode.removeChild(nativeElm);
var childCount = 1;
var level = this.nodeList[index].level;
for (var i = index + 1; i < this.nodeList.length; ++i) {
var childLevel = this.nodeList[i].level;
if (childLevel <= level)
break;
childCount++;
}
this.nodeList.splice(index, childCount);
]]>
</body>
</method>
<!-- overrideable -->
<method name="disableItemNative">
<parameter name="aItem"/>
<parameter name="aDoDisable"/>
<body>
</body>
</method>
<method name="isItemDisabledNative">
<parameter name="aItem"/>
<body>
return false;
</body>
</method>
<!-- private -->
<method name="appendTopElm">
<parameter name="aSelect"/>
<body>
var listElm = {
elm: aSelect,
nativeElm: this.control,
level: 0,
wasSelected: false
};
this.nodeList = [listElm];
this.selectControl = aSelect;
return 0;
</body>
</method>
<!-- Create and append native element.
@param aElm - the given element, this is either item or choices element
@param aParentElm - parent element of the given element
@param aNextElm - next sibling of the given element if any
@param aIsItemElm - [out] returns true if the given element is item
-->
<method name="createNativeElm">
<parameter name="aElm"/>
<parameter name="aParentElm"/>
<parameter name="aNextElm"/>
<parameter name="aIsItemElm"/>
<body>
switch (aElm.localName) {
case "choices":
aIsItemElm.value = false;
var group = this.createGroupElm(aParentElm, aNextElm,
this.getLabelFor(aElm));
return group ? group : this.control;
case "item":
aIsItemElm.value = true;
return this.createItemElm(aParentElm, aNextElm,
this.getLabelFor(aElm));
}
return null;
</body>
</method>
<!-- Return label element for the given element -->
<method name="getLabelFor">
<parameter name="aNode"/>
<body>
<![CDATA[
if (!aNode)
return null;
for (var child = aNode.firstChild; child; child = child.nextSibling) {
if (child.namespaceURI == this.XFORMS_NS) {
if (child.localName == "label")
return child;
}
}
return null;
]]>
</body>
</method>
<!-- Returns index for the given element.
@param aElm - the given element
@return - index of the given element
-->
<method name="getIndexByElm">
<parameter name="aElm"/>
<body>
<![CDATA[
for (var i = 0; i < this.nodeList.length; ++i) {
if (this.nodeList[i].elm == aElm)
return i;
}
dump("ERROR: selectsnw binding, can't locate the given element in the node list\n");
return -1;
]]>
</body>
</method>
<!-- Return index that the given element will be inserted at
@param aElm - the inserted element
@param aParentIndex - index of parent element
@return - index for inserted element
-->
<method name="getInsertionIndexForElm">
<parameter name="aElm"/>
<parameter name="aParentIndex"/>
<body>
<![CDATA[
var delta = 0;
var elm = aElm;
while (elm = elm.previousSibling)
delta++;
var parentLevel = this.nodeList[aParentIndex].level;
var index = aParentIndex + 1;
var length = this.nodeList.length;
for (; index < length && delta; ++index) {
if (this.nodeList[index].level == parentLevel + 1)
delta--;
}
if (!delta)
return index;
dump("ERROR: selectsnw binding, can't find insertion point in the node list for the given element\n");
return -1;
]]>
</body>
</method>
<!-- Fires 'xforms-select'/'xforms-deselect' events and updates instance
data. The method is used by bindings of native widget implementation.
@param aIncremental - specifies if update is incremental
-->
<method name="updateInstanceData">
<parameter name="aIncremental"/>
<body>
<![CDATA[
if (!this.selectControl)
return;
if (aIncremental) {
var deferredEventTargets = [];
for (var index = 0; index < this.nodeList.length; ++index) {
var listElm = this.nodeList[index];
if (!("wasSelected" in listElm))
continue;
// event state can be either:
// 0 - the state of element wasn't changed
// 1 - the element was selected
// 2 - the element was unselected
var eventState = 0;
var selected = this.isItemSelectedNative(listElm.nativeElm);
if (listElm.wasSelected && !selected)
eventState = 1;
else if (!listElm.wasSelected && selected)
eventState = 2;
if (!eventState)
continue;
var target = listElm.elm;
// First we should send 'xforms-unselect' events for all
// unselected item elements and then 'xforms-select' events.
if (eventState == 1) {
listElm.wasSelected = false;
this.selectControl.
dispatchXFormsNotificationEvent("xforms-deselect", target);
} else {
listElm.wasSelected = true;
deferredEventTargets.push(target);
}
}
for (var index = 0; index < deferredEventTargets.length; ++index) {
this.selectControl.dispatchXFormsNotificationEvent(
"xforms-select", deferredEventTargets[index]);
}
}
this.selectControl.updateInstanceData(aIncremental);
]]>
</body>
</method>
<!-- Returns the underlying control (native widget) -->
<property name="control" readonly="true">
<getter>
if (!this._control)
this._control = this.ownerDocument.
getAnonymousElementByAttribute(this, "anonid", "control");
return this._control;
</getter>
</property>
<!-- Reference on xforms 'select'/'select1' element -->
<field name="selectControl">null</field>
<!-- Contains a list of objects each of them has the following properties:
elm - xforms element (may be select/select1, item or choices)
nativeElm - native element that represents related xforms element
level - level of elements hierarchy (level of select/select1 is 0)
wasSelected - if 'elm' property contains item element then this property
is presented and takes boolean value
First object in the list is for 'select'/'select1' element.
-->
<field name="nodeList">new Array()</field>
<!-- Debug method. Dumps current state of node list into console. -->
<method name="dumpNodeList">
<body>
<![CDATA[
dump("\n\n");
for (var i = 0; i < this.nodeList.length; ++i) {
var obj = this.nodeList[i];
for (var j = 0; j < obj.level; j++)
dump(" ");
dump("elm: " + obj.elm.localName + "\n");
for (var j = 0; j < obj.level; j++)
dump(" ");
dump("native elm: " + obj.nativeElm.localName);
var label = this.getLabelFor(obj.elm);
if (label)
dump(", label: " + label.textValue);
dump("\n");
}
]]>
</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';"/>
</implementation>
<handlers>
<handler event="focus" phase="capturing">
if (this.selectControl)
this.selectControl.dispatchDOMUIEvent("DOMFocusIn");
</handler>
<handler event="blur" phase="capturing">
if (this.selectControl)
this.selectControl.dispatchDOMUIEvent("DOMFocusOut");
</handler>
</handlers>
</binding>
</bindings>