[XForms] Fixes issue with selects and itemsets. Bug 316895, r=allan+smaug

This commit is contained in:
aaronr%us.ibm.com 2006-02-23 23:53:23 +00:00
Родитель 24e6bb8260
Коммит ef65fa07cc
6 изменённых файлов: 154 добавлений и 45 удалений

Просмотреть файл

@ -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);
}
}
]]>