зеркало из https://github.com/mozilla/gecko-dev.git
[XForms] Fixes issue with selects and itemsets. Bug 316895, r=allan+smaug
This commit is contained in:
Родитель
24e6bb8260
Коммит
ef65fa07cc
|
@ -47,7 +47,7 @@ interface nsIDOMNode;
|
|||
* Private interface implemented by the model element for other
|
||||
* elements to use.
|
||||
*/
|
||||
[uuid(ccba3717-ef05-41cd-b831-f7f5ab3b921c)]
|
||||
[uuid(a6522c1a-a343-4b36-a130-75eb279a667a)]
|
||||
interface nsIModelElementPrivate : nsIXFormsModelElement
|
||||
{
|
||||
/**
|
||||
|
@ -100,11 +100,16 @@ interface nsIModelElementPrivate : nsIXFormsModelElement
|
|||
out AString nodeValue);
|
||||
|
||||
/**
|
||||
* Insert a node under an instance node
|
||||
* Insert a set of nodes underneath an instance node.
|
||||
* @param aContextNode The instance node
|
||||
* @param aNodeContent Node that holds the contents to insert under
|
||||
* the instance node
|
||||
* @param aNodeChanged Indicates whether the contents of the instance
|
||||
* node really did change due to this action
|
||||
*/
|
||||
void setNodeContent(in nsIDOMNode contextNode,
|
||||
in nsIDOMNode nodeContent,
|
||||
out boolean nodeChanged);
|
||||
void setNodeContent(in nsIDOMNode aContextNode,
|
||||
in nsIDOMNode aNodeContent,
|
||||
out boolean aNodeChanged);
|
||||
|
||||
/**
|
||||
* Validates the instance node against the schemas loaded by the model.
|
||||
|
|
|
@ -95,9 +95,11 @@ interface nsIXFormsAccessors : nsISupports
|
|||
* is nothing contained under aNode, then all children of the bound node
|
||||
* will be eliminated.
|
||||
*
|
||||
* @param aNode aNode should be a copy of the bound node. setContent
|
||||
* will take the contents of aNode and move them under
|
||||
* bound node.
|
||||
* @param aNode setContent will take the contents of aNode and copy
|
||||
* them under the control's bound node.
|
||||
* @param aForceUpdate Indicates whether setContent should rebuild,
|
||||
* recalculate, revalidate and refresh the model that
|
||||
* this control is bound to prior to returning
|
||||
*/
|
||||
void setContent(in nsIDOMNode aNode);
|
||||
void setContent(in nsIDOMNode aNode, in boolean aForceUpdate);
|
||||
};
|
||||
|
|
|
@ -115,9 +115,10 @@ nsXFormsAccessors::IsValid(PRBool *aStateVal)
|
|||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXFormsAccessors::SetContent(nsIDOMNode *aNode)
|
||||
nsXFormsAccessors::SetContent(nsIDOMNode *aNode, PRBool aForceUpdate)
|
||||
{
|
||||
NS_ENSURE_STATE(mElement);
|
||||
NS_ENSURE_ARG(aNode);
|
||||
|
||||
nsCOMPtr<nsIDOMNode> boundNode;
|
||||
nsresult rv = GetBoundNode(getter_AddRefs(boundNode));
|
||||
|
@ -129,10 +130,12 @@ nsXFormsAccessors::SetContent(nsIDOMNode *aNode)
|
|||
PRBool changed;
|
||||
rv = modelPriv->SetNodeContent(boundNode, aNode, &changed);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (changed) {
|
||||
if (aForceUpdate) {
|
||||
nsCOMPtr<nsIDOMNode> model = do_QueryInterface(modelPriv);
|
||||
|
||||
if (model) {
|
||||
rv = nsXFormsUtils::DispatchEvent(model, eEvent_Rebuild);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = nsXFormsUtils::DispatchEvent(model, eEvent_Recalculate);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = nsXFormsUtils::DispatchEvent(model, eEvent_Revalidate);
|
||||
|
|
|
@ -753,17 +753,9 @@ nsXFormsMDGEngine::SetNodeContent(nsIDOMNode *aContextNode,
|
|||
NS_ENSURE_ARG(aContentEnvelope);
|
||||
|
||||
// ok, this is tricky. This function will REPLACE the contents of
|
||||
// aContextNode with the CONTENTS of aContentEnvelope. No, not a clone of
|
||||
// the contents, but the contents themselves. If aContentEnvelope has no
|
||||
// contents, then any contents that aContextNode has will still be removed.
|
||||
// In order to determine whether the incoming node content is the same as what
|
||||
// is already contained in aContextNode, aContentEnvelope MUST be a clone (not
|
||||
// deep) of aContextNode, otherwise aNodeChanged will always be returned as
|
||||
// being PR_TRUE. I took this approach because I think it is much more
|
||||
// efficient for the caller to build a complete list of what goes in the
|
||||
// contents in one go rather than allowing any number of appends to existing
|
||||
// content one node at a time. There are quite a few links in the call chain
|
||||
// to go from nsXFormsDelegateStub to here.
|
||||
// aContextNode with the a clone of the contents of aContentEnvelope. If
|
||||
// aContentEnvelope has no contents, then any contents that aContextNode
|
||||
// has will still be removed.
|
||||
|
||||
if (aNodeChanged) {
|
||||
*aNodeChanged = PR_FALSE;
|
||||
|
@ -790,10 +782,56 @@ nsXFormsMDGEngine::SetNodeContent(nsIDOMNode *aContextNode,
|
|||
return NS_ERROR_DOM_WRONG_TYPE_ERR;
|
||||
}
|
||||
|
||||
PRBool nodesEqual = nsXFormsUtils::AreNodesEqual(aContextNode,
|
||||
aContentEnvelope,
|
||||
PR_FALSE);
|
||||
if (nodesEqual) {
|
||||
// Need to determine if the contents of the context node and content envelope
|
||||
// are already the same. If so, we can avoid some unnecessary work.
|
||||
|
||||
PRBool hasChildren1, hasChildren2, contentsEqual = PR_FALSE;
|
||||
nsresult rv1 = aContextNode->HasChildNodes(&hasChildren1);
|
||||
nsresult rv2 = aContentEnvelope->HasChildNodes(&hasChildren2);
|
||||
if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) && hasChildren1 == hasChildren2) {
|
||||
// First test passed. Both have the same number of children nodes.
|
||||
if (hasChildren1) {
|
||||
nsCOMPtr<nsIDOMNodeList> children1, children2;
|
||||
PRUint32 childrenLength1, childrenLength2;
|
||||
|
||||
rv1 = aContextNode->GetChildNodes(getter_AddRefs(children1));
|
||||
rv2 = aContentEnvelope->GetChildNodes(getter_AddRefs(children2));
|
||||
|
||||
if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) && children1 && children2) {
|
||||
|
||||
// Both have child nodes.
|
||||
rv1 = children1->GetLength(&childrenLength1);
|
||||
rv2 = children2->GetLength(&childrenLength2);
|
||||
if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) &&
|
||||
(childrenLength1 == childrenLength2)) {
|
||||
|
||||
// both have the same number of child nodes. Now checking to see if
|
||||
// each of the children are equal.
|
||||
for (PRUint32 i = 0; i < childrenLength1; ++i) {
|
||||
nsCOMPtr<nsIDOMNode> child1, child2;
|
||||
|
||||
rv1 = children1->Item(i, getter_AddRefs(child1));
|
||||
rv2 = children2->Item(i, getter_AddRefs(child2));
|
||||
if (NS_FAILED(rv1) || NS_FAILED(rv2)) {
|
||||
// Unexpected error. Not as many children in the list as we
|
||||
// were told.
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
contentsEqual = nsXFormsUtils::AreNodesEqual(child1, child2, PR_TRUE);
|
||||
if (!contentsEqual) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// neither have children
|
||||
contentsEqual = PR_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
if (contentsEqual) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -821,12 +859,22 @@ nsXFormsMDGEngine::SetNodeContent(nsIDOMNode *aContextNode,
|
|||
nsCOMPtr<nsIDOMNode> childNode;
|
||||
rv = aContentEnvelope->GetFirstChild(getter_AddRefs(childNode));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIDOMDocument> document;
|
||||
rv = aContextNode->GetOwnerDocument(getter_AddRefs(document));
|
||||
NS_ENSURE_STATE(document);
|
||||
|
||||
while (childNode) {
|
||||
rv = aContextNode->AppendChild(childNode, getter_AddRefs(resultNode));
|
||||
nsCOMPtr<nsIDOMNode> importedNode;
|
||||
rv = document->ImportNode(childNode, PR_TRUE, getter_AddRefs(importedNode));
|
||||
NS_ENSURE_STATE(importedNode);
|
||||
rv = aContextNode->AppendChild(importedNode, getter_AddRefs(resultNode));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = aContentEnvelope->GetFirstChild(getter_AddRefs(childNode));
|
||||
rv = childNode->GetNextSibling(getter_AddRefs(resultNode));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
resultNode.swap(childNode);
|
||||
}
|
||||
|
||||
// NB: Never reached for Readonly nodes.
|
||||
|
@ -834,8 +882,6 @@ nsXFormsMDGEngine::SetNodeContent(nsIDOMNode *aContextNode,
|
|||
*aNodeChanged = PR_TRUE;
|
||||
}
|
||||
|
||||
// Not calling MarkNodeAsChanged since caller will do a full rebuild
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -514,7 +514,7 @@
|
|||
|
||||
// add to the control array
|
||||
this._controlArray[this._controlArraySize] =
|
||||
{control: aItemElement, option: option, type: "item", wasSelected: false}
|
||||
{control: aItemElement, option: option, type: "item", wasSelected: option.selected}
|
||||
this._controlArraySize++;
|
||||
|
||||
this.uiElement.appendChild(option);
|
||||
|
@ -554,10 +554,27 @@
|
|||
return;
|
||||
}
|
||||
|
||||
var contentEnvelope = this._getSelectedValues();
|
||||
// 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.
|
||||
var copySelectedOrDeselected = new Boolean();
|
||||
copySelectedOrDeselected.value = false;
|
||||
var contentEnvelope = this._getSelectedValues(copySelectedOrDeselected);
|
||||
if (contentEnvelope) {
|
||||
if (boundNode.nodeType == Node.ELEMENT_NODE) {
|
||||
this.accessors.setContent(contentEnvelope);
|
||||
// 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 (copySelectedOrDeselected.value == false) {
|
||||
this.accessors.setValue(contentEnvelope.textContent);
|
||||
} else {
|
||||
this.accessors.setContent(contentEnvelope, true);
|
||||
}
|
||||
} else {
|
||||
// if some copyItems were selected by the user prior to the call
|
||||
// to _getSelectedValues, then we would not have set up
|
||||
|
@ -577,6 +594,7 @@
|
|||
</method>
|
||||
|
||||
<method name="_getSelectedValues">
|
||||
<parameter name="aIsACopyItemSelectedOrDeselected"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var selectedValues = "";
|
||||
|
@ -585,6 +603,10 @@
|
|||
// select if found, unselect if not
|
||||
var options = this._controlArray;
|
||||
|
||||
if (aIsACopyItemSelectedOrDeselected) {
|
||||
aIsACopyItemSelectedOrDeselected.value = false;
|
||||
}
|
||||
|
||||
var boundNode = this.accessors.getBoundNode();
|
||||
if (!boundNode) {
|
||||
return;
|
||||
|
@ -650,6 +672,10 @@
|
|||
// selected items
|
||||
if (!options[i].wasSelected) {
|
||||
newSelectedControls.push(options[i].control);
|
||||
if (aIsACopyItemSelectedOrDeselected &&
|
||||
aIsACopyItemSelectedOrDeselected.value != true) {
|
||||
aIsACopyItemSelectedOrDeselected.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
options[i].wasSelected = true;
|
||||
|
@ -673,8 +699,17 @@
|
|||
if (options[i].wasSelected) {
|
||||
this.dispatchSelectEvent(options[i].control, "xforms-deselect");
|
||||
|
||||
// XXX if this is a copyItem, we'll need to rebuild the model
|
||||
// per spec.
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options[i].wasSelected = false;
|
||||
|
@ -1031,6 +1066,8 @@
|
|||
if (selectedItem) {
|
||||
this._selectedElementArray[j].hits++;
|
||||
item.firstChild.checked = true;
|
||||
|
||||
this._controlArray[this._controlArraySize - 1].wasSelected = true;
|
||||
// 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
|
||||
|
|
|
@ -185,8 +185,6 @@
|
|||
// (causes a xforms-binding-exception).
|
||||
if (this._lastSelectedItem != this._selected) {
|
||||
if (this._lastSelectedItem) {
|
||||
// XXX if this is a copyItem, we'll need to rebuild the
|
||||
// model per spec.
|
||||
this.dispatchSelectEvent(this._lastSelectedItem, "xforms-deselect");
|
||||
}
|
||||
|
||||
|
@ -222,8 +220,6 @@
|
|||
// a xforms-binding-exception).
|
||||
if (this._lastSelectedItem != this._selected) {
|
||||
if (this._lastSelectedItem) {
|
||||
// XXX if this is a copyItem, we'll need to rebuild the
|
||||
// model per spec.
|
||||
this.dispatchSelectEvent(this._lastSelectedItem, "xforms-deselect");
|
||||
}
|
||||
|
||||
|
@ -446,8 +442,6 @@
|
|||
// a xforms-binding-exception).
|
||||
if (this._lastSelectedItem != this._selected) {
|
||||
if (this._lastSelectedItem) {
|
||||
// XXX if this is a copyItem, we'll need to rebuild the
|
||||
// model per spec.
|
||||
this.dispatchSelectEvent(this._lastSelectedItem, "xforms-deselect");
|
||||
}
|
||||
|
||||
|
@ -832,15 +826,17 @@
|
|||
if (this._selected) {
|
||||
this.updateInputField();
|
||||
}
|
||||
this._handleSelection();
|
||||
|
||||
this._handleSelection(true);
|
||||
}
|
||||
]]>
|
||||
</body>
|
||||
</method>
|
||||
|
||||
// _handleSelection updates the bound node with the value from the
|
||||
// currently selected item's value element or copy element.
|
||||
<!-- _handleSelection updates the bound node with the value from the
|
||||
currently selected item's value element or copy element. -->
|
||||
<method name="_handleSelection">
|
||||
<parameter name="aInBlur"/>
|
||||
<body>
|
||||
<![CDATA[
|
||||
var boundNode = this.accessors.getBoundNode();
|
||||
|
@ -858,6 +854,16 @@
|
|||
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.getAttribute("incremental") != "false") {
|
||||
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).
|
||||
|
@ -888,7 +894,17 @@
|
|||
}
|
||||
|
||||
var contentEnvelope = this._getSelectedValue();
|
||||
this.accessors.setContent(contentEnvelope);
|
||||
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.
|
||||
this.accessors.setValue(contentEnvelope.textContent);
|
||||
} else {
|
||||
this.accessors.setContent(contentEnvelope, true);
|
||||
}
|
||||
|
||||
}
|
||||
]]>
|
||||
|
|
Загрузка…
Ссылка в новой задаче