mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 00:55:37 +00:00
321 lines
9.7 KiB
C++
321 lines
9.7 KiB
C++
/* -*- 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 XForms code.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Novell, Inc.
|
|
* Portions created by the Initial Developer are Copyright (C) 2005
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Allan Beaufour <abeaufour@novell.com>
|
|
*
|
|
* 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 ***** */
|
|
|
|
#include "nsIDOMEvent.h"
|
|
#include "nsIDOMNode.h"
|
|
#include "nsIDOMElement.h"
|
|
#include "nsIDOMDocument.h"
|
|
#include "nsIDOMNodeList.h"
|
|
#include "nsIXFormsRepeatElement.h"
|
|
#include "nsIXFormsControl.h"
|
|
|
|
#include "nsString.h"
|
|
|
|
#include "nsIInstanceElementPrivate.h"
|
|
#include "nsXFormsActionModuleBase.h"
|
|
#include "nsXFormsActionElement.h"
|
|
#include "nsXFormsUtils.h"
|
|
#include "nsIDOM3Node.h"
|
|
|
|
#include "math.h"
|
|
|
|
#ifdef DEBUG
|
|
//#define DEBUG_XF_INSERTDELETE
|
|
#endif
|
|
|
|
/**
|
|
* Implementation of the XForms \<insert\> and \<delete\> elements.
|
|
*
|
|
* @see http://www.w3.org/TR/xforms/slice9.html#action-insert
|
|
*
|
|
* @todo The spec. states that the events must set their Context Info to:
|
|
* "Path expression used for insert/delete (xsd:string)" (XXX)
|
|
* @see http://www.w3.org/TR/xforms/slice4.html#evt-insert
|
|
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=280423
|
|
*
|
|
*/
|
|
class nsXFormsInsertDeleteElement : public nsXFormsActionModuleBase
|
|
{
|
|
private:
|
|
PRBool mIsInsert;
|
|
|
|
nsresult RefreshRepeats(nsIDOMNode *aNode);
|
|
|
|
public:
|
|
NS_DECL_NSIXFORMSACTIONMODULEELEMENT
|
|
|
|
/** Constructor */
|
|
nsXFormsInsertDeleteElement(PRBool aIsInsert) :
|
|
mIsInsert(aIsInsert)
|
|
{}
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
nsXFormsInsertDeleteElement::HandleAction(nsIDOMEvent *aEvent,
|
|
nsIXFormsActionElement *aParentAction)
|
|
{
|
|
if (!mElement)
|
|
return NS_OK;
|
|
|
|
// First we need to get the three attributes: @nodeset, @at, and possibly
|
|
// @position
|
|
|
|
//
|
|
// 1) Get @nodeset
|
|
nsresult rv;
|
|
nsCOMPtr<nsIModelElementPrivate> model;
|
|
nsCOMPtr<nsIDOMXPathResult> nodeset;
|
|
PRBool usesModelBinding;
|
|
rv = nsXFormsUtils::EvaluateNodeBinding(mElement,
|
|
nsXFormsUtils::ELEMENT_WITH_MODEL_ATTR,
|
|
NS_LITERAL_STRING("nodeset"),
|
|
EmptyString(),
|
|
nsIDOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
|
|
getter_AddRefs(model),
|
|
getter_AddRefs(nodeset),
|
|
&usesModelBinding);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!model || !nodeset)
|
|
return NS_OK;
|
|
|
|
PRUint32 setSize;
|
|
rv = nodeset->GetSnapshotLength(&setSize);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Unfortunately we cannot insert into an empty set, as there's nothing to
|
|
// clone.
|
|
if (setSize < 1)
|
|
return NS_OK;
|
|
|
|
//
|
|
// 2) Get @at
|
|
nsAutoString atExpr;
|
|
mElement->GetAttribute(NS_LITERAL_STRING("at"), atExpr);
|
|
if (atExpr.IsEmpty())
|
|
return NS_OK;
|
|
|
|
// Context node is first node in nodeset
|
|
nsCOMPtr<nsIDOMNode> contextNode;
|
|
nodeset->SnapshotItem(0, getter_AddRefs(contextNode));
|
|
|
|
nsCOMPtr<nsIDOMXPathResult> at;
|
|
rv = nsXFormsUtils::EvaluateXPath(atExpr, contextNode, mElement,
|
|
nsIDOMXPathResult::NUMBER_TYPE,
|
|
getter_AddRefs(at), 1, setSize);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!at)
|
|
return NS_OK;
|
|
|
|
PRUint32 atInt = 0;
|
|
double atDoub;
|
|
rv = at->GetNumberValue(&atDoub);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
/// XXX we need to check for NaN, and select last row. but isnan() is not
|
|
/// portable :(
|
|
if (atDoub < 1) {
|
|
atInt = 1;
|
|
} else {
|
|
atInt = (PRInt32) floor(atDoub + 0.5);
|
|
if (atInt > setSize)
|
|
atInt = setSize;
|
|
}
|
|
|
|
|
|
//
|
|
// 3) Get @position (if \<insert\>)
|
|
nsAutoString position;
|
|
PRBool insertAfter = PR_FALSE;
|
|
if (mIsInsert) {
|
|
mElement->GetAttribute(NS_LITERAL_STRING("position"), position);
|
|
if (position.EqualsLiteral("after")) {
|
|
insertAfter = PR_TRUE;
|
|
} else if (!position.EqualsLiteral("before")) {
|
|
// This is not a valid document...
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_XF_INSERTDELETE
|
|
if (mIsInsert) {
|
|
printf("Will try to INSERT node _%s_ index %d (set size: %d)\n",
|
|
NS_ConvertUTF16toUTF8(position).get(),
|
|
atInt,
|
|
setSize);
|
|
} else {
|
|
printf("Will try to DELETE node at index %d (set size: %d)\n",
|
|
atInt,
|
|
setSize);
|
|
}
|
|
#endif
|
|
|
|
// Find location
|
|
nsCOMPtr<nsIDOMNode> location;
|
|
nodeset->SnapshotItem(atInt - 1, getter_AddRefs(location));
|
|
NS_ENSURE_STATE(location);
|
|
|
|
nsCOMPtr<nsIDOMNode> parent;
|
|
location->GetParentNode(getter_AddRefs(parent));
|
|
NS_ENSURE_STATE(parent);
|
|
|
|
if (mIsInsert && insertAfter) {
|
|
nsCOMPtr<nsIDOMNode> temp;
|
|
// If we're at the end of the nodeset, this returns nsnull, which is fine,
|
|
// because InsertBefore then inserts at the end of the nodeset
|
|
location->GetNextSibling(getter_AddRefs(temp));
|
|
location.swap(temp);
|
|
}
|
|
|
|
nsCOMPtr<nsIDOMNode> resNode;
|
|
if (mIsInsert) {
|
|
// Get prototype and clone it (last member of nodeset)
|
|
nsCOMPtr<nsIDOMNode> prototype;
|
|
nodeset->SnapshotItem(setSize - 1, getter_AddRefs(prototype));
|
|
NS_ENSURE_STATE(prototype);
|
|
|
|
nsCOMPtr<nsIDOMNode> newNode;
|
|
prototype->CloneNode(PR_TRUE, getter_AddRefs(newNode));
|
|
NS_ENSURE_STATE(newNode);
|
|
|
|
parent->InsertBefore(newNode,
|
|
location,
|
|
getter_AddRefs(resNode));
|
|
NS_ENSURE_STATE(resNode);
|
|
|
|
// Set indexes for repeats
|
|
rv = RefreshRepeats(resNode);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
} else {
|
|
rv = parent->RemoveChild(location, getter_AddRefs(resNode));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// Dispatch xforms-insert/delete event to the instance node we have modified
|
|
// data for
|
|
nsCOMPtr<nsIDOMNode> instNode;
|
|
rv = nsXFormsUtils::GetInstanceNodeForData(resNode, getter_AddRefs(instNode));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = nsXFormsUtils::DispatchEvent(instNode,
|
|
mIsInsert ? eEvent_Insert : eEvent_Delete);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Dispatch refreshing events to the model
|
|
nsCOMPtr<nsIDOMElement> modelElem(do_QueryInterface(model));
|
|
NS_ASSERTION(modelElem, "Model not implementing nsIDOMElement?!");
|
|
if (aParentAction) {
|
|
aParentAction->SetRebuild(modelElem, PR_TRUE);
|
|
aParentAction->SetRecalculate(modelElem, PR_TRUE);
|
|
aParentAction->SetRevalidate(modelElem, PR_TRUE);
|
|
aParentAction->SetRefresh(modelElem, PR_TRUE);
|
|
} else {
|
|
nsXFormsUtils::DispatchEvent(modelElem, eEvent_Rebuild);
|
|
nsXFormsUtils::DispatchEvent(modelElem, eEvent_Recalculate);
|
|
nsXFormsUtils::DispatchEvent(modelElem, eEvent_Revalidate);
|
|
nsXFormsUtils::DispatchEvent(modelElem, eEvent_Refresh);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsXFormsInsertDeleteElement::RefreshRepeats(nsIDOMNode *aNode)
|
|
{
|
|
// XXXbeaufour: only check repeats belonging to the same model...
|
|
// possibly use mFormControls? Should be quicker than searching through
|
|
// entire document!! mModel->GetControls("repeat"); Would also possibly
|
|
// save a QI?
|
|
|
|
nsCOMPtr<nsIDOMDocument> document;
|
|
|
|
nsresult rv = mElement->GetOwnerDocument(getter_AddRefs(document));
|
|
NS_ENSURE_STATE(document);
|
|
|
|
nsCOMPtr<nsIDOMNodeList> repeatNodes;
|
|
document->GetElementsByTagNameNS(NS_LITERAL_STRING(NS_NAMESPACE_XFORMS),
|
|
NS_LITERAL_STRING("repeat"),
|
|
getter_AddRefs(repeatNodes));
|
|
NS_ENSURE_STATE(repeatNodes);
|
|
|
|
// work over each node and if the node contains the inserted element
|
|
PRUint32 nodeCount;
|
|
rv = repeatNodes->GetLength(&nodeCount);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
for (PRUint32 node = 0; node < nodeCount; ++node) {
|
|
nsCOMPtr<nsIDOMNode> repeatNode;
|
|
|
|
rv = repeatNodes->Item(node, getter_AddRefs(repeatNode));
|
|
nsCOMPtr<nsIXFormsRepeatElement> repeatEl(do_QueryInterface(repeatNode));
|
|
NS_ENSURE_STATE(repeatEl);
|
|
|
|
rv = repeatEl->HandleNodeInsert(aNode);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_HIDDEN_(nsresult)
|
|
NS_NewXFormsInsertElement(nsIXTFElement **aResult)
|
|
{
|
|
*aResult = new nsXFormsInsertDeleteElement(PR_TRUE);
|
|
if (!*aResult)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ADDREF(*aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_HIDDEN_(nsresult)
|
|
NS_NewXFormsDeleteElement(nsIXTFElement **aResult)
|
|
{
|
|
*aResult = new nsXFormsInsertDeleteElement(PR_FALSE);
|
|
if (!*aResult)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ADDREF(*aResult);
|
|
return NS_OK;
|
|
}
|