diff --git a/extensions/xforms/Makefile.in b/extensions/xforms/Makefile.in index 865f8066a6a..ff9ca2a8b12 100644 --- a/extensions/xforms/Makefile.in +++ b/extensions/xforms/Makefile.in @@ -133,6 +133,7 @@ XPIDLSRCS = \ nsIXFormsRangeAccessors.idl \ nsIXFormsUploadElement.idl \ nsIXFormsUploadUIElement.idl \ + nsIXFormsCopyElement.idl \ $(NULL) # XForms source files @@ -191,6 +192,7 @@ CPPSRCS = \ nsXFormsRangeElement.cpp \ nsXFormsAccessors.cpp \ nsXFormsRangeAccessors.cpp \ + nsXFormsCopyElement.cpp \ $(NULL) # Standard Mozilla make rules diff --git a/extensions/xforms/nsIModelElementPrivate.idl b/extensions/xforms/nsIModelElementPrivate.idl index 120f25817a7..25ef46f1af5 100644 --- a/extensions/xforms/nsIModelElementPrivate.idl +++ b/extensions/xforms/nsIModelElementPrivate.idl @@ -47,7 +47,7 @@ interface nsIDOMNode; * Private interface implemented by the model element for other * elements to use. */ -[uuid(8c5e2b7d-043e-4278-bc3e-1519bd9c62ec)] +[uuid(ccba3717-ef05-41cd-b831-f7f5ab3b921c)] interface nsIModelElementPrivate : nsIXFormsModelElement { /** @@ -99,6 +99,13 @@ interface nsIModelElementPrivate : nsIXFormsModelElement void getNodeValue(in nsIDOMNode contextNode, out AString nodeValue); + /** + * Insert a node under an instance node + */ + void setNodeContent(in nsIDOMNode contextNode, + in nsIDOMNode nodeContent, + out boolean nodeChanged); + /** * Validates the instance node against the schemas loaded by the model. */ diff --git a/extensions/xforms/nsIXFormsAccessors.idl b/extensions/xforms/nsIXFormsAccessors.idl index a7f8b373787..8d532198f79 100644 --- a/extensions/xforms/nsIXFormsAccessors.idl +++ b/extensions/xforms/nsIXFormsAccessors.idl @@ -37,6 +37,7 @@ * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" +#include "nsIDOMNode.idl" /** * Interface exposing the states of an XForms control. @@ -44,7 +45,7 @@ * For more information on this interface please see * http://developer.mozilla.org/en/docs/XForms:Custom_Controls */ -[scriptable, uuid(8e8c5022-a61d-42cf-9551-74215dc611ad)] +[scriptable, uuid(74992960-42a9-4479-a1ff-f7f1b37e187a)] interface nsIXFormsAccessors : nsISupports { /** @@ -81,4 +82,22 @@ interface nsIXFormsAccessors : nsISupports * true, if XForms control is bound to a node in a data model. */ boolean hasBoundNode(); + + /** + * Node that the control is bound to in its data model. + */ + nsIDOMNode getBoundNode(); + + /** + * Used to set the complete contents of the bound node. This function is + * meant to be used like setValue() except that it can be used to set more + * than just the first textnode contained under the bound node. If there + * 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. + */ + void setContent(in nsIDOMNode aNode); }; diff --git a/extensions/xforms/nsIXFormsCopyElement.idl b/extensions/xforms/nsIXFormsCopyElement.idl new file mode 100644 index 00000000000..6b3f93acd3f --- /dev/null +++ b/extensions/xforms/nsIXFormsCopyElement.idl @@ -0,0 +1,54 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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): + * Aaron Reed + * + * Alternatively, the contents of this file may be used under the terms of + * either of 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 ***** */ + +/** + * This interface is implemented by XForms \ elements. + */ + +#include "nsISupports.idl" +#include "nsIDOMNode.idl" + +[uuid(4af9ad8e-5266-44ac-b9f7-ef2cb9fe2637)] +interface nsIXFormsCopyElement : nsISupports +{ + /** + * Returns the node that will be deep copied into the instance data if the + * item element which contains this copy element is selected. + */ + readonly attribute nsIDOMNode copyNode; +}; diff --git a/extensions/xforms/nsIXFormsItemElement.idl b/extensions/xforms/nsIXFormsItemElement.idl index 66da4694dee..b4404167eff 100644 --- a/extensions/xforms/nsIXFormsItemElement.idl +++ b/extensions/xforms/nsIXFormsItemElement.idl @@ -37,11 +37,13 @@ * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" +#include "nsIDOMNode.idl" +#include "nsIXFormsDelegate.idl" /** * Interface implemented by the item element. */ -[scriptable, uuid(796a2e26-a40b-4ebf-be2c-42faf5fea6c4)] +[scriptable, uuid(ec8d3556-8ed2-4143-88d1-6b7b2c8b0b3b)] interface nsIXFormsItemElement : nsISupports { /** @@ -67,4 +69,18 @@ interface nsIXFormsItemElement : nsISupports * \ element, which can then refresh its UI. */ void labelRefreshed(); + + /** + * Indicates whether the item element contains a value child or a copy + * child. We'll assume that if the item is NOT a copy item, then it must + * be a value item. Which means that it must contain a XForms value element + * child. + */ + attribute boolean isCopyItem; + + /* + * returns the node that the contained copy element is bound to + */ + readonly attribute nsIDOMNode copyNode; + }; diff --git a/extensions/xforms/nsIXFormsSelectChild.idl b/extensions/xforms/nsIXFormsSelectChild.idl index 8ec3355d25c..e40fd38e042 100644 --- a/extensions/xforms/nsIXFormsSelectChild.idl +++ b/extensions/xforms/nsIXFormsSelectChild.idl @@ -52,7 +52,7 @@ class nsStringArray; * of an XForms select element (choices, item, itemset). */ -[scriptable, uuid(a29ac2bd-f36a-451e-99e1-0f3bd94ffbef)] +[scriptable, uuid(9fac2f59-4ec8-456f-ad06-4e28cd7d5b2c)] interface nsIXFormsSelectChild : nsISupports { /* @@ -61,4 +61,11 @@ interface nsIXFormsSelectChild : nsISupports * this method returns that \. */ nsIDOMNode selectItemByValue(in AString value); + + /* + * selectItemByNode is used in \ and \. If the + * XFormsSelectChild is or contains an \, which has a node 'equal' the + * parameter |aNode|, this method returns the first \ that matches. + */ + nsIDOMNode selectItemByNode(in nsIDOMNode aNode); }; diff --git a/extensions/xforms/nsXFormsAccessors.cpp b/extensions/xforms/nsXFormsAccessors.cpp index 79f05531a52..e0f30c81f9e 100644 --- a/extensions/xforms/nsXFormsAccessors.cpp +++ b/extensions/xforms/nsXFormsAccessors.cpp @@ -44,6 +44,7 @@ #include "nsDOMString.h" #include "nsIEventStateManager.h" #include "nsIContent.h" +#include "nsIXFormsControl.h" NS_IMPL_ISUPPORTS2(nsXFormsAccessors, nsIXFormsAccessors, nsIClassInfo) @@ -113,6 +114,48 @@ nsXFormsAccessors::IsValid(PRBool *aStateVal) return GetState(NS_EVENT_STATE_VALID, aStateVal); } +NS_IMETHODIMP +nsXFormsAccessors::SetContent(nsIDOMNode *aNode) +{ + NS_ENSURE_STATE(mElement); + + nsCOMPtr boundNode; + nsresult rv = GetBoundNode(getter_AddRefs(boundNode)); + NS_ENSURE_STATE(boundNode); + + nsCOMPtr modelPriv = nsXFormsUtils::GetModel(mElement); + NS_ENSURE_STATE(modelPriv); + + PRBool changed; + rv = modelPriv->SetNodeContent(boundNode, aNode, &changed); + NS_ENSURE_SUCCESS(rv, rv); + if (changed) { + nsCOMPtr model = do_QueryInterface(modelPriv); + + if (model) { + rv = nsXFormsUtils::DispatchEvent(model, eEvent_Recalculate); + NS_ENSURE_SUCCESS(rv, rv); + rv = nsXFormsUtils::DispatchEvent(model, eEvent_Revalidate); + NS_ENSURE_SUCCESS(rv, rv); + rv = nsXFormsUtils::DispatchEvent(model, eEvent_Refresh); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXFormsAccessors::GetBoundNode(nsIDOMNode **aBoundNode) +{ + NS_ENSURE_ARG_POINTER(aBoundNode); + if (mDelegate) { + nsCOMPtr control = do_QueryInterface(mDelegate); + return control->GetBoundNode(aBoundNode); + } + return NS_OK; +} + // nsIClassInfo implementation static const nsIID sScriptingIIDs[] = { diff --git a/extensions/xforms/nsXFormsChoicesElement.cpp b/extensions/xforms/nsXFormsChoicesElement.cpp index 4bde0d6151f..9a8426ff282 100644 --- a/extensions/xforms/nsXFormsChoicesElement.cpp +++ b/extensions/xforms/nsXFormsChoicesElement.cpp @@ -219,6 +219,36 @@ nsXFormsChoicesElement::SelectItemByValue(const nsAString &aValue, nsIDOMNode ** return NS_OK; } +NS_IMETHODIMP +nsXFormsChoicesElement::SelectItemByNode(nsIDOMNode *aNode, + nsIDOMNode **aSelected) +{ + NS_ENSURE_ARG_POINTER(aSelected); + NS_ENSURE_STATE(mElement); + *aSelected = nsnull; + nsCOMPtr children; + nsresult rv = mElement->GetChildNodes(getter_AddRefs(children)); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 childCount = 0; + children->GetLength(&childCount); + + nsCOMPtr childNode; + nsCOMPtr childItem; + + for (PRUint32 i = 0; i < childCount; ++i) { + children->Item(i, getter_AddRefs(childNode)); + childItem = do_QueryInterface(childNode); + if (childItem) { + childItem->SelectItemByNode(aNode, aSelected); + if (*aSelected) + return NS_OK; + } + } + + return NS_OK; +} + // internal methods void diff --git a/extensions/xforms/nsXFormsCopyElement.cpp b/extensions/xforms/nsXFormsCopyElement.cpp new file mode 100644 index 00000000000..8429991c4db --- /dev/null +++ b/extensions/xforms/nsXFormsCopyElement.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** 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): + * Aaron Reed + * + * 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 ***** */ + +#include "nsIXFormsCopyElement.h" +#include "nsXFormsStubElement.h" +#include "nsIXTFGenericElementWrapper.h" +#include "nsXFormsUtils.h" +#include "nsIDOMElement.h" +#include "nsString.h" +#include "nsIXFormsItemElement.h" + +/** + * Implementation of the XForms \ element. + * + * @note The copy element does not display any content, it simply provides + * a node to be copied to instance data when the containing XForms item + * element is selected. + */ + +class nsXFormsCopyElement : public nsXFormsStubElement, + public nsIXFormsCopyElement +{ +public: + nsXFormsCopyElement() : mElement(nsnull) {} + + NS_DECL_ISUPPORTS_INHERITED + + // nsIXTFGenericElement overrides + NS_IMETHOD OnCreated(nsIXTFGenericElementWrapper *aWrapper); + + // nsIXTFElement overrides + NS_IMETHOD ParentChanged(nsIDOMElement *aNewParent); + NS_IMETHOD DocumentChanged(nsIDOMDocument *aNewParent); + + // nsIXFormsCopyElement + NS_DECL_NSIXFORMSCOPYELEMENT + +private: + nsIDOMElement *mElement; +}; + +NS_IMPL_ISUPPORTS_INHERITED1(nsXFormsCopyElement, + nsXFormsStubElement, + nsIXFormsCopyElement) + +NS_IMETHODIMP +nsXFormsCopyElement::OnCreated(nsIXTFGenericElementWrapper *aWrapper) +{ + aWrapper->SetNotificationMask(nsIXTFElement::NOTIFY_PARENT_CHANGED | + nsIXTFElement::NOTIFY_DOCUMENT_CHANGED); + + nsCOMPtr node; + aWrapper->GetElementNode(getter_AddRefs(node)); + + // It's ok to keep pointer to mElement. mElement will have an + // owning reference to this object, so as long as we null out mElement in + // OnDestroyed, it will always be valid. + + mElement = node; + NS_ASSERTION(mElement, "Wrapper is not an nsIDOMElement, we'll crash soon"); + + return NS_OK; +} + +// nsIXTFElement + +NS_IMETHODIMP +nsXFormsCopyElement::ParentChanged(nsIDOMElement *aNewParent) +{ + if (aNewParent) { + if (!nsXFormsUtils::IsXFormsElement(aNewParent, + NS_LITERAL_STRING("itemset")) && + !nsXFormsUtils::IsXFormsElement(aNewParent, + NS_LITERAL_STRING("contextcontainer"))) { + + // parent of a copy element must always be an itemset. We really can't + // enforce this all that well until we have full schema support but for + // now we'll at least warn the author. We are also checking for + // contextcontainer because under Mozilla, the children of an itemset + // element are cloned underneath a contextcontainer which is in turn + // contained in a nsXFormsItemElement. Each such item element is then + // appended as anonymous content of the itemset. + nsXFormsUtils::ReportError(NS_LITERAL_STRING("copyError"), mElement); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsXFormsCopyElement::DocumentChanged(nsIDOMDocument* aNewDocument) +{ + if (!aNewDocument) + return NS_OK; + + // tell grandparent (xf:item) that it contains a xf:copy element and + // not a xf:value element. + nsCOMPtr contextContainer; + nsresult rv = mElement->GetParentNode(getter_AddRefs(contextContainer)); + NS_ENSURE_TRUE(contextContainer, rv); + + nsCOMPtr itemNode; + rv = contextContainer->GetParentNode(getter_AddRefs(itemNode)); + NS_ENSURE_TRUE(itemNode, rv); + + nsCOMPtr item = do_QueryInterface(itemNode); + + // It is possible that the grandparent ISN'T an xf:item, if this is the + // original template copy element whose parent is the xf:itemset and + // grandparent is the xf:select. We'll ignore a copy element in that case + // since it really isn't in play. + if (item) { + item->SetIsCopyItem(PR_TRUE); + } + return NS_OK; +} + +// nsIXFormsCopyElement + +NS_IMETHODIMP +nsXFormsCopyElement::GetCopyNode(nsIDOMNode **aNode) +{ + NS_ENSURE_ARG_POINTER(aNode); + *aNode = nsnull; + + nsCOMPtr model; + nsCOMPtr result; + nsresult rv = + nsXFormsUtils::EvaluateNodeBinding(mElement, + nsXFormsUtils::ELEMENT_WITH_MODEL_ATTR, + NS_LITERAL_STRING("ref"), EmptyString(), + nsIDOMXPathResult::FIRST_ORDERED_NODE_TYPE, + getter_AddRefs(model), + getter_AddRefs(result)); + + NS_ENSURE_SUCCESS(rv, rv); + + if (result) { + nsCOMPtr singleNode; + result->GetSingleNodeValue(getter_AddRefs(singleNode)); + + NS_IF_ADDREF(*aNode = singleNode); + } + + return NS_OK; +} + +NS_HIDDEN_(nsresult) +NS_NewXFormsCopyElement(nsIXTFElement **aResult) +{ + *aResult = new nsXFormsCopyElement(); + if (!*aResult) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/extensions/xforms/nsXFormsElementFactory.cpp b/extensions/xforms/nsXFormsElementFactory.cpp index 8d80258d44d..bab8755877c 100644 --- a/extensions/xforms/nsXFormsElementFactory.cpp +++ b/extensions/xforms/nsXFormsElementFactory.cpp @@ -73,6 +73,7 @@ NS_HIDDEN_(nsresult) NS_NewXFormsValueElement(nsIXTFElement **aElement); NS_HIDDEN_(nsresult) NS_NewXFormsChoicesElement(nsIXTFElement **aElement); NS_HIDDEN_(nsresult) NS_NewXFormsItemSetElement(nsIXTFElement **aElement); NS_HIDDEN_(nsresult) NS_NewXFormsRangeElement(nsIXTFElement **aElement); +NS_HIDDEN_(nsresult) NS_NewXFormsCopyElement(nsIXTFElement **aElement); //Action Module Elements NS_HIDDEN_(nsresult) NS_NewXFormsDispatchElement(nsIXTFElement **aResult); @@ -191,6 +192,8 @@ nsXFormsElementFactory::CreateElement(const nsAString& aTagName, return NS_NewXFormsUploadElement(aElement); if (aTagName.EqualsLiteral("range")) return NS_NewXFormsRangeElement(aElement); + if (aTagName.EqualsLiteral("copy")) + return NS_NewXFormsCopyElement(aElement); *aElement = nsnull; return NS_ERROR_FAILURE; diff --git a/extensions/xforms/nsXFormsItemElement.cpp b/extensions/xforms/nsXFormsItemElement.cpp index 2e28d582b4c..63d5d6f5eeb 100644 --- a/extensions/xforms/nsXFormsItemElement.cpp +++ b/extensions/xforms/nsXFormsItemElement.cpp @@ -56,6 +56,8 @@ #include "nsIXFormsLabelElement.h" #include "nsIDocument.h" #include "nsXFormsModelElement.h" +#include "nsIXFormsCopyElement.h" +#include "nsIDOMEventTarget.h" /** * nsXFormsItemElement implements the XForms \ element. @@ -69,7 +71,8 @@ class nsXFormsItemElement : public nsXFormsBindableStub, public nsIXFormsItemElement { public: - nsXFormsItemElement() : mElement(nsnull), mDoneAddingChildren(PR_FALSE) + nsXFormsItemElement() : mElement(nsnull), mDoneAddingChildren(PR_FALSE), + mIsCopyItem(PR_FALSE) { } @@ -91,9 +94,14 @@ public: // nsIXFormsSelectChild NS_DECL_NSIXFORMSSELECTCHILD + private: nsIDOMElement* mElement; PRBool mDoneAddingChildren; + + // If true, indicates that this item contains a xf:copy element (via + // xf:itemset) rather than a xf:value element + PRBool mIsCopyItem; }; NS_IMPL_ISUPPORTS_INHERITED2(nsXFormsItemElement, @@ -219,12 +227,50 @@ nsXFormsItemElement::SelectItemByValue(const nsAString &aValue, nsIDOMNode **aSe { NS_ENSURE_ARG_POINTER(aSelected); NS_ENSURE_STATE(mElement); + + *aSelected = nsnull; + if (mIsCopyItem) { + // copy items are selected by node, not by value + return NS_OK; + } + nsAutoString value; - GetValue(value); - if (aValue.Equals(value)) { + nsresult rv = GetValue(value); + + if (NS_SUCCEEDED(rv) && aValue.Equals(value)) { + NS_ADDREF(*aSelected = mElement); + } + + return rv; +} + +NS_IMETHODIMP +nsXFormsItemElement::SelectItemByNode(nsIDOMNode *aNode, nsIDOMNode **aSelected) +{ + NS_ENSURE_ARG_POINTER(aSelected); + NS_ENSURE_STATE(mElement); + PRBool isCopyItem; + *aSelected = nsnull; + + // If this item doesn't contain a copy element but instead has a value + // element, then there is no sense testing further. + GetIsCopyItem(&isCopyItem); + if (!isCopyItem) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr copyNode; + nsresult rv = GetCopyNode(getter_AddRefs(copyNode)); + NS_ENSURE_STATE(copyNode); + + PRUint16 nodeType; + copyNode->GetNodeType(&nodeType); + + // copy elements are only allowed to bind to ELEMENT_NODEs per spec. But + // test first before doing all of this work. + if ((nodeType == nsIDOMNode::ELEMENT_NODE) && + (nsXFormsUtils::AreNodesEqual(copyNode, aNode))) { NS_ADDREF(*aSelected = mElement); - } else { - *aSelected = nsnull; } return NS_OK; @@ -233,7 +279,16 @@ nsXFormsItemElement::SelectItemByValue(const nsAString &aValue, nsIDOMNode **aSe NS_IMETHODIMP nsXFormsItemElement::GetValue(nsAString &aValue) { - + PRBool isCopyItem; + GetIsCopyItem(&isCopyItem); + if (isCopyItem) { + // if this item was built by an itemset and the itemset's template used + // a copy element, then there is no value element to be had. No sense + // continuing. + aValue.Truncate(0); + return NS_ERROR_FAILURE; + } + nsCOMPtr firstChild, container; mElement->GetFirstChild(getter_AddRefs(firstChild)); @@ -271,6 +326,52 @@ nsXFormsItemElement::GetValue(nsAString &aValue) return NS_OK; } +NS_IMETHODIMP +nsXFormsItemElement::GetCopyNode(nsIDOMNode **aNode) +{ + NS_ENSURE_ARG_POINTER(aNode); + + PRBool isCopyItem; + GetIsCopyItem(&isCopyItem); + if (!isCopyItem) { + // If this item doesn't contain a copy element but instead has a value + // element, then there is no sense continuing. + *aNode = nsnull; + return NS_ERROR_FAILURE; + } + + // Since this item really contains a copy element, then firstChild MUST be + // a contextcontainer since copy elements can only exist as a child of an + // itemset. + nsCOMPtr container; + mElement->GetFirstChild(getter_AddRefs(container)); + + // Find the copy element contained by this item and get the copyNode from it. + nsCOMPtr children; + nsresult rv = container->GetChildNodes(getter_AddRefs(children)); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 childCount; + children->GetLength(&childCount); + + nsCOMPtr child; + nsAutoString value; + + for (PRUint32 i = 0; i < childCount; ++i) { + children->Item(i, getter_AddRefs(child)); + nsCOMPtr copyElement = do_QueryInterface(child); + if (copyElement) { + return copyElement->GetCopyNode(aNode); + } + } + + // No copy element as a child. Set return node to null and set the copyitem + // boolean to false so we don't go through this unnecessary pain again. + aNode = nsnull; + SetIsCopyItem(PR_FALSE); + return NS_OK; +} + void nsXFormsItemElement::Refresh() { @@ -368,6 +469,21 @@ nsXFormsItemElement::LabelRefreshed() return NS_OK; } +NS_IMETHODIMP +nsXFormsItemElement::GetIsCopyItem(PRBool *aIsCopyItem) +{ + NS_ENSURE_ARG(aIsCopyItem); + *aIsCopyItem = mIsCopyItem; + return NS_OK; +} + +NS_IMETHODIMP +nsXFormsItemElement::SetIsCopyItem(PRBool aIsCopyItem) +{ + mIsCopyItem = aIsCopyItem; + return NS_OK; +} + NS_HIDDEN_(nsresult) NS_NewXFormsItemElement(nsIXTFElement **aResult) { diff --git a/extensions/xforms/nsXFormsItemSetElement.cpp b/extensions/xforms/nsXFormsItemSetElement.cpp index 2ce4b7965cc..ad4956b7772 100644 --- a/extensions/xforms/nsXFormsItemSetElement.cpp +++ b/extensions/xforms/nsXFormsItemSetElement.cpp @@ -190,6 +190,38 @@ nsXFormsItemSetElement::SelectItemByValue(const nsAString &aValue, nsIDOMNode ** return NS_OK; } +NS_IMETHODIMP +nsXFormsItemSetElement::SelectItemByNode(nsIDOMNode *aNode, + nsIDOMNode **aSelected) +{ + NS_ENSURE_ARG_POINTER(aSelected); + NS_ENSURE_STATE(mElement); + *aSelected = nsnull; + // nsIXFormsItemSetUIElement is implemented by the XBL binding. + nsCOMPtr uiItemSet(do_QueryInterface(mElement)); + NS_ENSURE_STATE(uiItemSet); + + nsCOMPtr anonContent; + uiItemSet->GetAnonymousItemSetContent(getter_AddRefs(anonContent)); + NS_ENSURE_STATE(anonContent); + + nsCOMPtr child, tmp; + anonContent->GetFirstChild(getter_AddRefs(child)); + // Trying to select the first possible (generated) \ element. + while (child) { + nsCOMPtr selectChild(do_QueryInterface(child)); + if (selectChild) { + selectChild->SelectItemByNode(aNode, aSelected); + if (*aSelected) { + return NS_OK; + } + } + tmp.swap(child); + tmp->GetNextSibling(getter_AddRefs(child)); + } + return NS_OK; +} + NS_IMETHODIMP nsXFormsItemSetElement::Bind() { diff --git a/extensions/xforms/nsXFormsMDGEngine.cpp b/extensions/xforms/nsXFormsMDGEngine.cpp index 552f2aef8e3..86345d033ef 100644 --- a/extensions/xforms/nsXFormsMDGEngine.cpp +++ b/extensions/xforms/nsXFormsMDGEngine.cpp @@ -47,6 +47,7 @@ #include "nsDeque.h" #include "nsIModelElementPrivate.h" #include "nsXFormsUtils.h" +#include "nsDOMError.h" #ifdef DEBUG //# define DEBUG_XF_MDG @@ -743,6 +744,101 @@ nsXFormsMDGEngine::SetNodeValueInternal(nsIDOMNode *aContextNode, return NS_OK; } +nsresult +nsXFormsMDGEngine::SetNodeContent(nsIDOMNode *aContextNode, + nsIDOMNode *aContentEnvelope, + PRBool *aNodeChanged) +{ + NS_ENSURE_ARG(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. + + if (aNodeChanged) { + *aNodeChanged = PR_FALSE; + } + + const nsXFormsNodeState* ns = GetNodeState(aContextNode); + NS_ENSURE_TRUE(ns, NS_ERROR_FAILURE); + + // If the node is read-only and not set by a @calculate MIP, + // ignore the call + if (ns->IsReadonly()) { + /// + /// @todo Better feedback for readonly nodes? (XXX) + return NS_OK; + } + + PRUint16 nodeType; + nsresult rv = aContextNode->GetNodeType(&nodeType); + NS_ENSURE_SUCCESS(rv, rv); + + if (nodeType != nsIDOMNode::ELEMENT_NODE) { + // got to return something pretty unique that we can check down the road in + // order to dispatch any error events + return NS_ERROR_DOM_WRONG_TYPE_ERR; + } + + PRBool nodesEqual = nsXFormsUtils::AreNodesEqual(aContextNode, + aContentEnvelope, + PR_FALSE); + if (nodesEqual) { + return NS_OK; + } + + // remove any child nodes that aContextNode already contains + nsCOMPtr resultNode; + nsCOMPtr childList; + rv = aContextNode->GetChildNodes(getter_AddRefs(childList)); + NS_ENSURE_SUCCESS(rv, rv); + if (childList) { + PRUint32 length; + rv = childList->GetLength(&length); + NS_ENSURE_SUCCESS(rv, rv); + + for (PRInt32 i = length-1; i >= 0; i--) { + nsCOMPtr childNode; + rv = childList->Item(i, getter_AddRefs(childNode)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aContextNode->RemoveChild(childNode, getter_AddRefs(resultNode)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // add contents of the envelope under aContextNode + nsCOMPtr childNode; + rv = aContentEnvelope->GetFirstChild(getter_AddRefs(childNode)); + NS_ENSURE_SUCCESS(rv, rv); + while (childNode) { + rv = aContextNode->AppendChild(childNode, getter_AddRefs(resultNode)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aContentEnvelope->GetFirstChild(getter_AddRefs(childNode)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // NB: Never reached for Readonly nodes. + if (aNodeChanged) { + *aNodeChanged = PR_TRUE; + } + + // Not calling MarkNodeAsChanged since caller will do a full rebuild + + return NS_OK; +} + const nsXFormsNodeState* nsXFormsMDGEngine::GetNodeState(nsIDOMNode *aContextNode) { diff --git a/extensions/xforms/nsXFormsMDGEngine.h b/extensions/xforms/nsXFormsMDGEngine.h index 28b94094b0c..c16821b4065 100644 --- a/extensions/xforms/nsXFormsMDGEngine.h +++ b/extensions/xforms/nsXFormsMDGEngine.h @@ -326,6 +326,7 @@ protected: PRBool aMarkNode = PR_TRUE, PRBool aIsCalculate = PR_FALSE, PRBool *aNodeChanged = nsnull); + public: /** * Constructor @@ -419,6 +420,18 @@ public: nsresult GetNodeValue(nsIDOMNode *aContextNode, nsAString &aNodeValue); + /** + * Set the contents of a node + * + * @param aContextNode The node to set the contents of + * @param aContentEnvelope The container of the contents that need to be + * moved under aContextNode + * @param aNodeChanged Was node changed? + */ + nsresult SetNodeContent(nsIDOMNode *aContextNode, + nsIDOMNode *aContentEnvelope, + PRBool *aNodeChanged = nsnull); + /** * External interface of GetNCNodeState(), returns const pointer to the node * state. diff --git a/extensions/xforms/nsXFormsModelElement.cpp b/extensions/xforms/nsXFormsModelElement.cpp index e435872289d..b65aa351264 100644 --- a/extensions/xforms/nsXFormsModelElement.cpp +++ b/extensions/xforms/nsXFormsModelElement.cpp @@ -1138,6 +1138,16 @@ nsXFormsModelElement::GetNodeValue(nsIDOMNode *aContextNode, aNodeValue); } +NS_IMETHODIMP +nsXFormsModelElement::SetNodeContent(nsIDOMNode *aContextNode, + nsIDOMNode *aNodeContent, + PRBool *aNodeChanged) +{ + return mMDG.SetNodeContent(aContextNode, + aNodeContent, + aNodeChanged); +} + NS_IMETHODIMP nsXFormsModelElement::ValidateNode(nsIDOMNode *aInstanceNode, PRBool *aResult) { diff --git a/extensions/xforms/nsXFormsUtils.cpp b/extensions/xforms/nsXFormsUtils.cpp index 54769e5ac79..d12290728ce 100644 --- a/extensions/xforms/nsXFormsUtils.cpp +++ b/extensions/xforms/nsXFormsUtils.cpp @@ -95,6 +95,10 @@ #include "nsIDOMAbstractView.h" #include "nsPIDOMWindow.h" +#include "nsIDOMDocumentType.h" +#include "nsIDOMEntity.h" +#include "nsIDOMNotation.h" + #define CANCELABLE 0x01 #define BUBBLES 0x02 @@ -1625,3 +1629,365 @@ nsXFormsUtils::HandleBindingException(nsIDOMElement *aElement) nsnull, getter_AddRefs(messageWindow)); return NS_SUCCEEDED(rv); } + +/* static */ PRBool +nsXFormsUtils::AreEntitiesEqual(nsIDOMNamedNodeMap *aEntities1, + nsIDOMNamedNodeMap *aEntities2) +{ + if (!aEntities1 && !aEntities2) { + return PR_TRUE; + } + + if (!aEntities1 || !aEntities2) { + return PR_FALSE; + } + + PRUint32 entLength1, entLength2; + nsresult rv1 = aEntities1->GetLength(&entLength1); + nsresult rv2 = aEntities2->GetLength(&entLength2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || entLength1 != entLength2) { + return PR_FALSE; + } + + nsAutoString buffer1, buffer2; + for (PRUint32 i = 0; i < entLength1; ++i) { + nsCOMPtr entNode1, entNode2; + + rv1 = aEntities1->Item(i, getter_AddRefs(entNode1)); + rv2 = aEntities2->Item(i, getter_AddRefs(entNode2)); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !entNode1 || !entNode2) { + return PR_FALSE; + } + + nsCOMPtr ent1, ent2; + ent1 = do_QueryInterface(entNode1); + ent2 = do_QueryInterface(entNode2); + if (!ent1 || !ent2) { + return PR_FALSE; + } + + rv1 = ent1->GetPublicId(buffer1); + rv2 = ent2->GetPublicId(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = ent1->GetSystemId(buffer1); + rv2 = ent2->GetSystemId(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = ent1->GetNotationName(buffer1); + rv2 = ent2->GetNotationName(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + // XXX: These will need to be uncommented when Mozilla supports these from + // DOM3 +#if 0 + rv1 = ent1->GetInputEncoding(buffer1); + rv2 = ent2->GetInputEncoding(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = ent1->GetXmlEncoding(buffer1); + rv2 = ent2->GetXmlEncoding(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + rv1 = ent1->GetXmlVersion(buffer1); + rv2 = ent2->GetXmlVersion(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } +#endif + + } + return PR_TRUE; +} + +/* static */ PRBool +nsXFormsUtils::AreNotationsEqual(nsIDOMNamedNodeMap *aNotations1, + nsIDOMNamedNodeMap *aNotations2) +{ + if (!aNotations1 && !aNotations2) { + return PR_TRUE; + } + + if (!aNotations1 || !aNotations2) { + return PR_FALSE; + } + + PRUint32 notLength1, notLength2; + nsresult rv1 = aNotations1->GetLength(¬Length1); + nsresult rv2 = aNotations2->GetLength(¬Length2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || notLength1 != notLength2) { + return PR_FALSE; + } + + nsAutoString buffer1, buffer2; + for (PRUint32 j = 0; j < notLength1; ++j) { + nsCOMPtr notNode1, notNode2; + + rv1 = aNotations1->Item(j, getter_AddRefs(notNode1)); + rv2 = aNotations2->Item(j, getter_AddRefs(notNode2)); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !notNode1 || !notNode2) { + return PR_FALSE; + } + + nsCOMPtr notation1, notation2; + notation1 = do_QueryInterface(notNode1); + notation2 = do_QueryInterface(notNode2); + if (!notation1 || !notation2) { + return PR_FALSE; + } + + rv1 = notation1->GetPublicId(buffer1); + rv2 = notation2->GetPublicId(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = notation1->GetSystemId(buffer1); + rv2 = notation2->GetSystemId(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + } + + return PR_TRUE; +} + +/* static */ PRBool +nsXFormsUtils::AreNodesEqual(nsIDOMNode *aFirstNode, nsIDOMNode *aSecondNode, + PRBool aAlreadyNormalized) +{ + if (!aFirstNode || !aSecondNode) { + return PR_FALSE; + } + + nsresult rv1, rv2; + PRUint16 firstType, secondType; + rv1 = aFirstNode->GetNodeType(&firstType); + rv2 = aSecondNode->GetNodeType(&secondType); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || firstType != secondType) { + return PR_FALSE; + } + + nsAutoString buffer1, buffer2; + if (firstType == nsIDOMNode::DOCUMENT_TYPE_NODE) { + nsCOMPtr doc1 = do_QueryInterface(aFirstNode); + nsCOMPtr doc2 = do_QueryInterface(aSecondNode); + if (!doc1 || !doc2) { + return PR_FALSE; + } + + rv1 = doc1->GetName(buffer1); + rv2 = doc2->GetName(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = doc1->GetPublicId(buffer1); + rv2 = doc2->GetPublicId(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = doc1->GetSystemId(buffer1); + rv2 = doc2->GetSystemId(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = doc1->GetInternalSubset(buffer1); + rv2 = doc2->GetInternalSubset(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + nsCOMPtr map1, map2; + rv1 = doc1->GetEntities(getter_AddRefs(map1)); + rv2 = doc2->GetEntities(getter_AddRefs(map2)); + + // XXX need to handle the case where neither has entities? + if (NS_FAILED(rv1) || NS_FAILED(rv2)) { + return PR_FALSE; + } + + PRBool equal = nsXFormsUtils::AreEntitiesEqual(map1, map2); + if (!equal) { + return PR_FALSE; + } + + rv1 = doc1->GetNotations(getter_AddRefs(map1)); + rv2 = doc2->GetNotations(getter_AddRefs(map2)); + if (NS_FAILED(rv1) || NS_FAILED(rv2)) { + return PR_FALSE; + } + + equal = nsXFormsUtils::AreNotationsEqual(map1, map2); + if (!equal) { + return PR_FALSE; + } + + } + + rv1 = aFirstNode->GetNodeName(buffer1); + rv2 = aSecondNode->GetNodeName(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = aFirstNode->GetLocalName(buffer1); + rv2 = aSecondNode->GetLocalName(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = aFirstNode->GetNamespaceURI(buffer1); + rv2 = aSecondNode->GetNamespaceURI(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = aFirstNode->GetPrefix(buffer1); + rv2 = aSecondNode->GetPrefix(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + rv1 = aFirstNode->GetNodeValue(buffer1); + rv2 = aSecondNode->GetNodeValue(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + + PRBool hasAttr1, hasAttr2; + rv1 = aFirstNode->HasAttributes(&hasAttr1); + rv2 = aSecondNode->HasAttributes(&hasAttr2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || hasAttr1 != hasAttr2) { + return PR_FALSE; + } + + if (hasAttr1) { + nsCOMPtr attrs1, attrs2; + PRUint32 attrLength1, attrLength2; + + rv1 = aFirstNode->GetAttributes(getter_AddRefs(attrs1)); + rv2 = aSecondNode->GetAttributes(getter_AddRefs(attrs2)); + if (NS_FAILED(rv1) || NS_FAILED(rv2)) { + return PR_FALSE; + } + + rv1 = attrs1->GetLength(&attrLength1); + rv2 = attrs2->GetLength(&attrLength2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || attrLength1 != attrLength2) { + return PR_FALSE; + } + + // the order of the attributes on the two nodes doesn't matter. But + // every attribute on node1 must exist on node2 (and no more) + for (PRUint32 i = 0; i < attrLength1; ++i) { + nsCOMPtr attr1, attr2; + rv1 = attrs1->Item(i, getter_AddRefs(attr1)); + if (!attr1) { + return PR_FALSE; + } + + attr1->GetLocalName(buffer1); + attr1->GetNamespaceURI(buffer2); + attrs2->GetNamedItemNS(buffer2, buffer1, getter_AddRefs(attr2)); + if (!attr2) { + return PR_FALSE; + } + + rv1 = attr1->GetNodeValue(buffer1); + rv2 = attr2->GetNodeValue(buffer2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !buffer1.Equals(buffer2)) { + return PR_FALSE; + } + } + } + + // now looking at the child nodes. They have to be 'equal' and at the same + // index inside each of the parent nodes. + PRBool hasChildren1, hasChildren2; + rv1 = aFirstNode->HasChildNodes(&hasChildren1); + rv2 = aSecondNode->HasChildNodes(&hasChildren2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || hasChildren1 != hasChildren2) { + return PR_FALSE; + } + + if (hasChildren1) { + nsCOMPtr children1, children2; + PRUint32 childrenLength1, childrenLength2; + + rv1 = aFirstNode->GetChildNodes(getter_AddRefs(children1)); + rv2 = aSecondNode->GetChildNodes(getter_AddRefs(children2)); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || !children1 || !children2) { + return PR_FALSE; + } + + rv1 = children1->GetLength(&childrenLength1); + rv2 = children2->GetLength(&childrenLength2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || childrenLength1 != childrenLength2) { + return PR_FALSE; + } + + nsCOMPtr clone1, clone2; + if (!aAlreadyNormalized) { + // well we avoided this as long as we can. If we haven't already + // normalized all children, now is the time to do it. We'll have to clone + // nodes since the normalization process actually changes the DOM. + + rv1 = aFirstNode->CloneNode(PR_TRUE, getter_AddRefs(clone1)); + if (NS_FAILED(rv1) || !clone1) { + return PR_FALSE; + } + rv2 = aSecondNode->CloneNode(PR_TRUE, getter_AddRefs(clone2)); + if (NS_FAILED(rv2) || !clone2) { + return PR_FALSE; + } + + rv1 = clone1->Normalize(); + rv2 = clone2->Normalize(); + if (NS_FAILED(rv1) || NS_FAILED(rv2)) { + return PR_FALSE; + } + + // since this already worked once on the original nodes, won't bother + // checking the results for the clones + clone1->GetChildNodes(getter_AddRefs(children1)); + clone2->GetChildNodes(getter_AddRefs(children2)); + + // get length again since normalizing may have eliminated some text nodes + rv1 = children1->GetLength(&childrenLength1); + rv2 = children2->GetLength(&childrenLength2); + if (NS_FAILED(rv1) || NS_FAILED(rv2) || childrenLength1 != childrenLength2) { + return PR_FALSE; + } + } + + for (PRUint32 i = 0; i < childrenLength1; ++i) { + nsCOMPtr child1, child2; + + rv1 = children1->Item(i, getter_AddRefs(child1)); + rv2 = children2->Item(i, getter_AddRefs(child2)); + if (NS_FAILED(rv1) || NS_FAILED(rv2)) { + return PR_FALSE; + } + + PRBool areEqual = nsXFormsUtils::AreNodesEqual(child1, child2, PR_TRUE); + if (!areEqual) { + return PR_FALSE; + } + } + } + + return PR_TRUE; + +} diff --git a/extensions/xforms/nsXFormsUtils.h b/extensions/xforms/nsXFormsUtils.h index efaf9dc19eb..710009781ee 100644 --- a/extensions/xforms/nsXFormsUtils.h +++ b/extensions/xforms/nsXFormsUtils.h @@ -465,6 +465,39 @@ public: * @return Whether handling was successful */ static PRBool HandleBindingException(nsIDOMElement *aElement); + + /** + * Returns whether the given NamedNodeMaps of Entities are equal + * + */ + static NS_HIDDEN_(PRBool) AreEntitiesEqual(nsIDOMNamedNodeMap *aEntities1, + nsIDOMNamedNodeMap *aEntities2); + + /** + * Returns whether the given NamedNodeMaps of Notations are equal + * + */ + static NS_HIDDEN_(PRBool) AreNotationsEqual(nsIDOMNamedNodeMap *aNotations1, + nsIDOMNamedNodeMap *aNotations2); + + /** + * Returns whether the given nodes are equal as described in the isEqualNode + * function defined in the DOM Level 3 Core spec. + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/DOM3-Core.html#core-Node3-isEqualNode + * + * XXX: this is just temporary until isEqualNode is implemented in Mozilla + * (https://bugzilla.mozilla.org/show_bug.cgi?id=159167) + * + * @param aFirstNode The first node to compare + * @param aSecondNode The second node to compare + * @param aAlreadyNormalized Whether the two nodes and their children, etc. + * have already been normalized to allow for + * more accurate child node comparisons, as + * recommended in the DOM Level 3 Core spec. + */ + static NS_HIDDEN_(PRBool) AreNodesEqual(nsIDOMNode *aFirstNode, + nsIDOMNode *aSecondNode, + PRBool aAlreadyNormalized = PR_FALSE); }; #endif diff --git a/extensions/xforms/resources/content/select.xml b/extensions/xforms/resources/content/select.xml index 277d6dcb632..3fa71d88076 100644 --- a/extensions/xforms/resources/content/select.xml +++ b/extensions/xforms/resources/content/select.xml @@ -138,22 +138,49 @@ 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 delegate cache is // null (first load). if (this._accessorValueCache == null || - this._accessorValueCache == this.accessors.getValue()) { - // refresh was not called due to instance data changing, so build the - // UI. - this._buildSelect(); - } else { + 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. + this._buildSelect(containsNonText); + } else if (!containsNonText) { // update selection this._updateSelection(); - } - // store the delegate value - this._accessorValueCache = this.accessors.getValue(); + // store the delegate value + this._accessorValueCache = this.accessors.getValue(); + } this._refreshing = false; @@ -164,28 +191,92 @@ 0 new Array() + 0 + new Array() null + @@ -365,10 +469,33 @@ 0) { + var item = aItemElement.QueryInterface(Components.interfaces.nsIXFormsSelectChild); + for (var j = 0; j < this._selectedElementSize; j++ ) { + var selectedItem = + item.selectItemByNode(this._selectedElementArray[j].element); + if (selectedItem) { + this._selectedElementArray[j].hits++; + option.selected = 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 + // 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. + } + } + } // add to the control array this._controlArray[this._controlArraySize] = @@ -377,6 +504,12 @@ this.uiElement.appendChild(option); + if (!copyItem) { + // if this item contains a value element, then make sure to select + // this item if its value exists under the bound node. + this.preselectItem(itemValue); + } + return itemValue; ]]> @@ -388,7 +521,7 @@ - + @@ -416,6 +570,29 @@ // select if found, unselect if not var options = this._controlArray; + var boundNode = this.accessors.getBoundNode(); + if (!boundNode) { + return; + } + + // 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) { + return; + } + 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 = options[i].option ? options[i].option.selected : options[i].checkbox.checked; @@ -426,31 +603,94 @@ selectedValues += " "; } - selectedValues += - options[i].control.QueryInterface(Components.interfaces.nsIXFormsSelectChild).value; + 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. + bindingException = document.createEvent("Events"); + bindingException.initEvent("xforms-binding-exception", true, false); + this.dispatchEvent(bindingException); - // if it wasn't selected before add to the list of newly selected items - if (!options[i].wasSelected) { - newSelectedControls.push(options[i].control); + // 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. + if (options[i].option) { + options[i].option.selected = false; + } else { + options[i].checkbox.checked = false; + } + } else { + copyNode = item.copyNode; + if (copyNode) { + var clone = copyNode.cloneNode(true); + contentEnvelope.appendChild(clone); + } + + // if it wasn't selected before add to the list of newly + // selected items + if (!options[i].wasSelected) { + newSelectedControls.push(options[i].control); + } + + options[i].wasSelected = 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; + + // if it wasn't selected before add to the list of newly + // selected items + if (!options[i].wasSelected) { + newSelectedControls.push(options[i].control); + } + + options[i].wasSelected = true; } - - options[i].wasSelected = true; } else { // it was selected before, but now unselected 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. } options[i].wasSelected = false; } } + // write out the text nodes before we handle copy + if (boundType == Node.ELEMENT_NODE) { + if (selectedValues.length > 0) { + var textNode = document.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; + } + + selectedValues = ""; + // we send xforms-select after all deselect events are thrown for (var i = 0; i < newSelectedControls.length; i++) { this.dispatchSelectEvent(newSelectedControls[i], "xforms-select"); } - return selectedValues; + return contentEnvelope; ]]> @@ -458,6 +698,11 @@ @@ -531,7 +779,7 @@ 0) { + var item = aItemElement.QueryInterface(Components.interfaces.nsIXFormsSelectChild); + for (var j = 0; j < this._selectedElementSize; j++ ) { + var selectedItem = + item.selectItemByNode(this._selectedElementArray[j].element); + if (selectedItem) { + this._selectedElementArray[j].hits++; + item.firstChild.checked = 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 + // 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. + } + } + } else if (!copyItem) { + // if this item contains a value element, then make sure to select + // this item if its value exists under the bound node. + this.preselectItem(itemValue); + } + return itemValue; ]]> diff --git a/extensions/xforms/resources/content/select1.xml b/extensions/xforms/resources/content/select1.xml index 697da6d0ab6..0e3311ecb50 100644 --- a/extensions/xforms/resources/content/select1.xml +++ b/extensions/xforms/resources/content/select1.xml @@ -154,7 +154,7 @@ @@ -557,7 +608,72 @@ + + + + + + + + + + + // _handleSelection updates the bound node with the value from the + // currently selected item's value element or copy element. + + + @@ -683,6 +912,63 @@ return true; + + + + + + diff --git a/extensions/xforms/resources/locale/en-US/xforms.properties b/extensions/xforms/resources/locale/en-US/xforms.properties index 54954894cfe..830c113e845 100644 --- a/extensions/xforms/resources/locale/en-US/xforms.properties +++ b/extensions/xforms/resources/locale/en-US/xforms.properties @@ -64,6 +64,7 @@ rangeNullInit = XForms Error (25): One or more init() parameters is NaN rangeBeginEndError = XForms Error (26): Begin is higher than end? encodingMemoryError = XForms Error (23): Not enough available memory to encode file %S, size = %S. uploadBoundTypeError = XForms Error (24): Upload element not bound to valid datatype. Must be bound to datatype 'xsd:anyURI', 'xsd:base64Binary', or 'xsd:hexBinary'. +copyError = XForms Error (25): A copy element was found whose parent is not an itemset element # Warning Messages: warnSOAP = XForms Warning (1): You are using the SOAP post feature, which is an experimental feature! Beware that the functionality might change, and forms may stop working at any time.