// SpryNestedXMLDataSet.js - version 0.7 - Spry Pre-Release 1.6.1//// Copyright (c) 2007. Adobe Systems Incorporated.// All rights reserved.//// Redistribution and use in source and binary forms, with or without// modification, are permitted provided that the following conditions are met:////   * Redistributions of source code must retain the above copyright notice,//     this list of conditions and the following disclaimer.//   * Redistributions in binary form must reproduce the above copyright notice,//     this list of conditions and the following disclaimer in the documentation//     and/or other materials provided with the distribution.//   * Neither the name of Adobe Systems Incorporated nor the names of its//     contributors may be used to endorse or promote products derived from this//     software without specific prior written permission.//// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE// POSSIBILITY OF SUCH DAMAGE.Spry.Data.NestedXMLDataSet = function(parentDataSet, xpath, options){	this.parentDataSet = parentDataSet;	this.xpath = xpath;	this.nestedDataSets = [];	this.nestedDataSetsHash = {};	this.currentDS = null;	this.currentDSAncestor = null;	this.options = options;	this.ignoreOnDataChanged = false;	this.entityEncodeStrings = parentDataSet ? parentDataSet.entityEncodeStrings : true;	Spry.Data.DataSet.call(this, options);	parentDataSet.addObserver(this);};Spry.Data.NestedXMLDataSet.prototype = new Spry.Data.DataSet();Spry.Data.NestedXMLDataSet.prototype.constructor = Spry.Data.NestedXMLDataSet.prototype;Spry.Data.NestedXMLDataSet.prototype.getParentDataSet = function(){	return this.parentDataSet;};Spry.Data.NestedXMLDataSet.prototype.getNestedDataSetForParentRow = function(parentRow){	// Return the internal nested data set associated with the parent's	// specified row object.		var xmlNode = parentRow.ds_XMLNode;	if (xmlNode && this.nestedDataSets)	{		// Before we go through all of the trouble of looking up the data set		// we want, check to see if it is already our current data set!				if (this.currentDSAncestor && this.currentDSAncestor == xmlNode)			return this.currentDS;		// The caller is asking for a data set that isn't our current one.		// Manually walk through all of the data sets we have, and return the		// one that is associated with xmlNode.		var nDSArr = this.nestedDataSets;		var nDSArrLen = nDSArr.length;		for (var i = 0; i < nDSArrLen; i++)		{			var dsObj = nDSArr[i];			if (dsObj && xmlNode == dsObj.ancestor)				return dsObj.dataSet;		}	}	return null;};Spry.Data.NestedXMLDataSet.prototype.getNestedXMLDataSetsArray = function(){	// Return an array of all of our internal nested data sets.	var resultsArray = [];	if (this.nestedDataSets)	{		var arrDS = this.nestedDataSets;		var numDS = this.nestedDataSets.length;		for (var i = 0; i < numDS; i++)			resultsArray.push(arrDS[i].dataSet);	}	return resultsArray;};Spry.Data.NestedXMLDataSet.prototype.onDataChanged = function(notifier, data){	// This function gets called any time the *parent* data set gets changed.	if (!this.ignoreOnDataChanged)		this.loadData();};Spry.Data.NestedXMLDataSet.prototype.onCurrentRowChanged = function(notifier, data){	// The current row for our parent data set changed. We need to sync	// our internal state so that our current data set is the nested data	// for the parent's current row.	//	// From the outside, this appears as if the *entire* data inside this	// data set changes. We don't want any of our nested child data sets	// to recalculate their internal nested data structures, we simply want	// them to select the correct one from the set they already have. To do	// this, we dispatch a pre and post context change message that allows	// them to figure out what is going on, and that they can safely ignore	// any onDataChanged message they get from their parent.	this.notifyObservers("onPreParentContextChange");	this.currentDS = null;	this.currentDSAncestor = null;	var pCurRow = this.parentDataSet.getCurrentRow();	if (pCurRow)	{		var nestedDS = this.getNestedDataSetForParentRow(pCurRow);		if (nestedDS)		{			this.currentDS = nestedDS;			this.currentDSAncestor = pCurRow.ds_XMLNode;		}	}	this.notifyObservers("onDataChanged");	this.notifyObservers("onPostParentContextChange");	this.ignoreOnDataChanged = false;};// If we get an onPostParentContextChange, we want to treat it as if we got an// onCurrentRowChanged message from our parent, that way, we don't have to recalculate// any of our internal data, we just have to select the correct data set// that matches our parent's current row.Spry.Data.NestedXMLDataSet.prototype.onPostParentContextChange = Spry.Data.NestedXMLDataSet.prototype.onCurrentRowChanged;Spry.Data.NestedXMLDataSet.prototype.onPreParentContextChange = function(notifier, data){	this.ignoreOnDataChanged = true;};Spry.Data.NestedXMLDataSet.prototype.filterAndSortData = function(){	// This method is almost identical to the one from the	// DataSet base class, except that it does not set the	// current row id.	// If there is a data filter installed, run it.	if (this.filterDataFunc)		this.filterData(this.filterDataFunc, true);	// If the distinct flag was set, run through all the records in the recordset	// and toss out any that are duplicates.	if (this.distinctOnLoad)		this.distinct(this.distinctFieldsOnLoad);	// If sortOnLoad was set, sort the data based on the columns	// specified in sortOnLoad.	if (this.keepSorted && this.getSortColumn())		this.sort(this.lastSortColumns, this.lastSortOrder);	else if (this.sortOnLoad)		this.sort(this.sortOnLoad, this.sortOrderOnLoad);	// If there is a view filter installed, run it.	if (this.filterFunc)		this.filter(this.filterFunc, true);};Spry.Data.NestedXMLDataSet.prototype.loadData = function(){	var parentDS = this.parentDataSet;	if (!parentDS || parentDS.getLoadDataRequestIsPending() || !this.xpath)		return;	if (!parentDS.getDataWasLoaded())	{		// Someone is asking us to load our data, but our parent		// hasn't even initiated a load yet. Tell our parent to		// load its data, so we can get our data!		parentDS.loadData();		return;	}	this.notifyObservers("onPreLoad");	this.nestedDataSets = [];	this.currentDS = null;	this.currentDSAncestor = null;	this.data = [];	this.dataHash = {};	var self = this;	var ancestorDS = [ parentDS ];	if (parentDS.getNestedXMLDataSetsArray)		ancestorDS = parentDS.getNestedXMLDataSetsArray();	var currentAncestor = null;	var currentAncestorRow = parentDS.getCurrentRow();	if (currentAncestorRow)		currentAncestor = currentAncestorRow.ds_XMLNode;	var numAncestors = ancestorDS.length;	for (var i = 0; i < numAncestors; i++)	{		// Run through each row of *every* ancestor data set and create		// a nested data set.		var aDS = ancestorDS[i];		var aData = aDS.getData(true);		if (aData)		{			var aDataLen = aData.length;			for (var j = 0; j < aDataLen; j++)			{				var row = aData[j];				if (row && row.ds_XMLNode)				{					// Create an internal nested data set for this row.					var ds = new Spry.Data.DataSet(this.options);					// Make sure the internal nested data set has the same set					// of columnTypes as the nested data set itself.					for (var cname in this.columnTypes)						ds.setColumnType(cname, this.columnTypes[cname]);					// Flatten any data that matches our XPath and stuff it into					// our new nested data set.					var dataArr = Spry.Data.XMLDataSet.getRecordSetFromXMLDoc(row.ds_XMLNode, this.xpath, false, this.entityEncodeStrings);					ds.setDataFromArray(dataArr.data, true);					// Create an object that stores the relationship between our					// internal nested data set, and the ancestor node that was used					// extract the data for the data set.					var dsObj = new Object;					dsObj.ancestor = row.ds_XMLNode;					dsObj.dataSet = ds;					this.nestedDataSets.push(dsObj);					// If this ancestor is the one for our parent's current row,					// make the current data set our current data set.					if (row.ds_XMLNode == currentAncestor)					{						this.currentDS = ds;						this.currentDSAncestor = this.ds_XMLNode;					}							// Add an observer on the nested data set so that whenever it dispatches					// a notifications, we forward it on as if we generated the notification.							ds.addObserver(function(notificationType, notifier, data){ if (notifier == self.currentDS) setTimeout(function() { self.notifyObservers(notificationType, data); }, 0); });				}			}		}	}	this.pendingRequest = new Object;	this.dataWasLoaded = false;	this.pendingRequest.timer = setTimeout(function() {		self.pendingRequest = null;		self.dataWasLoaded = true;		self.disableNotifications();		self.filterAndSortData();		self.enableNotifications();		self.notifyObservers("onPostLoad");		self.notifyObservers("onDataChanged");	}, 0);};Spry.Data.NestedXMLDataSet.prototype.getData = function(unfiltered){	if (this.currentDS)		return this.currentDS.getData(unfiltered);	return [];};Spry.Data.NestedXMLDataSet.prototype.getRowCount = function(unfiltered){	if (this.currentDS)		return this.currentDS.getRowCount(unfiltered);	return 0;};Spry.Data.NestedXMLDataSet.prototype.getRowByID = function(rowID){	if (this.currentDS)		return this.currentDS.getRowByID(rowID);	return undefined;};Spry.Data.NestedXMLDataSet.prototype.getRowByRowNumber = function(rowNumber, unfiltered){	if (this.currentDS)		return this.currentDS.getRowByRowNumber(rowNumber, unfiltered);	return null;};Spry.Data.NestedXMLDataSet.prototype.getCurrentRow = function(){	if (this.currentDS)		return this.currentDS.getCurrentRow();	return null;};Spry.Data.NestedXMLDataSet.prototype.setCurrentRow = function(rowID){	if (this.currentDS)		return this.currentDS.setCurrentRow(rowID);};Spry.Data.NestedXMLDataSet.prototype.getRowNumber = function(row){	if (this.currentDS)		return this.currentDS.getRowNumber(row);	return 0;};Spry.Data.NestedXMLDataSet.prototype.getCurrentRowNumber = function(){	if (this.currentDS)		return this.currentDS.getCurrentRowNumber();	return 0;};Spry.Data.NestedXMLDataSet.prototype.getCurrentRowID = function(){	if (this.currentDS)		return this.currentDS.getCurrentRowID();	return 0;};Spry.Data.NestedXMLDataSet.prototype.setCurrentRowNumber = function(rowNumber){	if (this.currentDS)		return this.currentDS.setCurrentRowNumber(rowNumber);};Spry.Data.NestedXMLDataSet.prototype.findRowsWithColumnValues = function(valueObj, firstMatchOnly, unfiltered){	if (this.currentDS)		return this.currentDS.findRowsWithColumnValues(valueObj, firstMatchOnly, unfiltered);	return firstMatchOnly ? null : [];};Spry.Data.NestedXMLDataSet.prototype.setColumnType = function(columnNames, columnType){	if (columnNames)	{		// Make sure the nested xml data set tracks the column types		// that the user sets so that if our data changes, we can re-apply		// the column types.		Spry.Data.DataSet.prototype.setColumnType.call(this, columnNames, columnType);		// Now apply the column types to any internal nested data sets		// that exist.		var dsArr = this.nestedDataSets;		var dsArrLen = dsArr.length;		for (var i = 0; i < dsArrLen; i++)			dsArr[i].dataSet.setColumnType(columnNames, columnType);	}};Spry.Data.NestedXMLDataSet.prototype.getColumnType = function(columnName){	if (this.currentDS)		return this.currentDS.getColumnType(columnName);	return "string";};Spry.Data.NestedXMLDataSet.prototype.distinct = function(columnNames){	if (columnNames)	{		var dsArr = this.nestedDataSets;		var dsArrLen = dsArr.length;		for (var i = 0; i < dsArrLen; i++)			dsArr[i].dataSet.distinct(columnNames);	}};Spry.Data.NestedXMLDataSet.prototype.sort = function(columnNames, sortOrder){	if (columnNames)	{		// Forward the sort request to all internal data sets.		var dsArr = this.nestedDataSets;		var dsArrLen = dsArr.length;		for (var i = 0; i < dsArrLen; i++)			dsArr[i].dataSet.sort(columnNames, sortOrder);		// Make sure we store a local copy of the last sort order		// column so we can restore it if new data is loaded.		if (dsArrLen > 0)		{			var ds = dsArr[0].dataSet;			this.lastSortColumns = ds.lastSortColumns.slice(0); // Copy the array.			this.lastSortOrder = ds.lastSortOrder;		}	}};Spry.Data.NestedXMLDataSet.prototype.filterData = function(filterFunc, filterOnly){	// Store a copy of the filterFunc so we can apply it	// if the data set loads new data.	this.filterDataFunc = filterFunc;	// Now set the filterFunc on all of the internal	// data sets.	var dsArr = this.nestedDataSets;	var dsArrLen = dsArr.length;	for (var i = 0; i < dsArrLen; i++)		dsArr[i].dataSet.filterData(filterFunc, filterOnly);};Spry.Data.NestedXMLDataSet.prototype.filter = function(filterFunc, filterOnly){	// Store a copy of the filterFunc so we can apply it	// if the data set loads new data.	this.filterFunc = filterFunc;	// Now set the filterFunc on all of the internal	// data sets.	var dsArr = this.nestedDataSets;	var dsArrLen = dsArr.length;	for (var i = 0; i < dsArrLen; i++)		dsArr[i].dataSet.filter(filterFunc, filterOnly);};Spry.Data.NestedXMLDataSet.prototype.setXPath = function(path){	if (this.xpath != path)	{		this.xpath = path;		this.loadData();	}};